bazel-contrib / rules_foreign_cc

Build rules for interfacing with "foreign" (non-Bazel) build systems (CMake, configure-make, GNU Make, boost, ninja, Meson)
https://bazel-contrib.github.io/rules_foreign_cc
Apache License 2.0
673 stars 248 forks source link

cmake toolchain archive tools issues #947

Open bedbad opened 2 years ago

bedbad commented 2 years ago

While building a big Cmake Project (Qt6) with cmake() rule the following error occurs:

CMake Error: archive_write_header: Raw format only supports filetype AE_IFREG CMake Error at src/corelib/CMakeLists.txt:1236 (file): file failed to compress: /private/var/tmp/_bazel_im/ef20e9a172588342b056f208aba834f5/sandbox/darwin-sandbox/48/execroot/Qt/bazel-out/darwin_arm64-fastbuild/bin/qtbase.build_tmpdir/src/corelib/.rcc/qmimeprovider_database.cpp.archive

This appears to be caused by improper packaging of the bundled Cmake in intermediary source files fast compression described here:

https://gitlab.kitware.com/cmake/cmake/-/issues/21552 https://bugreports.qt.io/browse/QTBUG-89108

Platform: Apple Silicon(M1) rules_foreign_cc versions tested: 0.3.0 - 0.9.0 Reproduction: WORKSPACE:

workspace(name = "Qt")
load("@bazel_tools//tools/build_defs/repo:http.bzl","http_archive")
http_archive(
    name = "rules_foreign_cc",
    strip_prefix = "rules_foreign_cc-0.9.0",
    url = "https://github.com/bazelbuild/rules_foreign_cc/archive/0.9.0.tar.gz",
)
load("@rules_foreign_cc//foreign_cc:repositories.bzl", "rules_foreign_cc_dependencies")
rules_foreign_cc_dependencies()

all_content = """filegroup(
    name = "all_srcs",
    srcs = glob(["**"]),
    visibility = ["//visibility:public"]
)
"""
http_archive(
    name = "md4c",
    build_file_content = all_content,
    strip_prefix = "md4c-master",
    urls = ["https://github.com/mity/md4c/archive/refs/heads/master.zip"]
)
http_archive(
    name = "qt",
    build_file_content = all_content,
    strip_prefix = "qtbase-6.4",
    urls = ["https://github.com/qt/qtbase/archive/refs/heads/6.4.zip"],
)

BUILD:

cmake(
    name = "md4c",
    build_args = [
        "--verbose",
        "--",  # <- Pass remaining options to the native tool.
        "-j 12",
        "-l 8"],
    cache_entries = {
        "CMAKE_BUILD_TYPE" : "Release",
        "BUILD_SHARED_LIBS" : "ON"
        },
    lib_source = "@md4c//:all_srcs",
    out_shared_libs = select({
        "@platforms//os:osx" : [ "libmd4c.dylib"],
        "@platforms//os:linux" : ["libmd4c.so"],
        "@platforms//os:windows" : ["libmd4c.dll"],
        "//conditions:default" : []
    }),
    install = True,
)
cmake(
    name = "qtbase",
    build_args = [
        "--verbose",
        "--",
        "-j 12",
        "-l 8"],
    cache_entries = {
        "CMAKE_PREFIX_PATH" : "/usr/local",
        "BUILD_SHARED_LIBS" : "ON",
        # "FEATURE_system_zlib" : "OFF",
        "FEATURE_framework" : "OFF",
        #"QT_USE_CCACHE" : "ON",
        # "QT_BUILD_TESTS" : "ON",
    },
    deps = [":ccache", ":md4c"],
    # generate_args = ["-GNinja"],
    out_include_dir = "include",
    lib_source = "@qt//:all_srcs",
    install = True,
)
jsharpe commented 2 years ago

Is this something that has regressed in 0.9.0? If so then its likely a regression in the upstream cmake packages. You can explicitly choose a cmake version (pass it to rules_foreign_cc_dependencies()) to roll back to a different release if this is the case.

jheaff1 commented 2 years ago

I’m currently working on a GitHub repo that provides Bazel rules which build qt6 from source and I too had this issue on Linux. From memory the issue was that Cmake could not archive a file that, due to Bazel sandboxing, was a symlink to the original file.

try adding tags=[“no-sandbox”] to your qtbase target.

bedbad commented 2 years ago

Here's background on bazelizing Qt. The bazel integrating Qt given you input the paths to .so libraries and headers already works, Credit to Ben Breslauer and Justin Buchanan https://github.com/justbuchanan/bazel_rules_qt I did some correcting, qt_resource and qt_test, this is the full build needed: qt.bzl:

load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")

def qt_ui_library(name, ui, deps, **kwargs):
    """Compiles a QT UI file and makes a library for it.
    Args:
      name: A name for the rule.
      ui: The ui file to compile.
      deps: cc_library dependencies for the library.
    """
    uif_path = ui if len(ui.split('/')) >= 2 else "%s/%s" % (native.package_name(), ui)
    uif_name = ui.split("/")[-1].split(".")[0]

    native.genrule(
        name = "%s_uic" % name,
        srcs = [ui],
        outs = [ "headers/ui_%s.h" % (uif_name) ],
        cmd = select({
            "@platforms//os:osx": "uic $(locations %s) -o $@" % uif_path,
            "@platforms//os:linux": "uic $(locations %s) -o $@" % uif_path,
            "@platforms//os:windows": "$(location @qt//:uic) $(locations %s) -o $@" % uif_path,
        }),
        tools = select({
            "@platforms//os:osx": [],
            "@platforms//os:linux": [],
            "@platforms//os:windows": ["@qt//:uic"],
        }),
    )
    cc_library(
        name = name,
        hdrs = [":%s_uic" % name],
        deps = deps,
        **kwargs
    )

def _genqrc(ctx):
    qrc_output = ctx.outputs.qrc
    qrc_content = "<RCC>\n  <qresource prefix=\\\"/\\\">"
    for f in ctx.files.files:
        qrc_content += "\n    <file>%s</file>" % f.path
    qrc_content += "\n  </qresource>\n</RCC>"
    cmd = ["echo", "\"%s\"" % qrc_content, ">", qrc_output.path]
    ctx.actions.run_shell(
        command = " ".join(cmd),
        outputs = [qrc_output],
    )
    return [OutputGroupInfo(qrc = depset([qrc_output]))]

genqrc = rule(
    implementation = _genqrc,
    attrs = {
        "files": attr.label_list(allow_files = True, mandatory = True),
        "qrc": attr.output(),
    },
)

def qt_resource(name, files, qrc_file=None, **kwargs):
    """Creates a cc_library containing the contents of all input files using qt's `rcc` tool.

    Args:
      name: rule name
      files: a list of files to be included in the resource bundle
      kwargs: extra args to pass to the cc_library
    """
    if (qrc_file == None):
        print("RCC step creating new qrc file")
        qrc_file = name + "_qrc.qrc"

    genqrc(name = name + "_qrc", files = files, qrc = qrc_file)
    print("RCC:", "qrc file generated")
    # every resource cc_library that is linked into the same binary needs a
    # unique 'name'.
    rsrc_name = native.package_name().replace("/", "_") + "_" + name
    outfile = name + "_gen.cpp"
    native.genrule(
        name = name + "_gen",
        srcs = [qrc_file] + files,
        outs = [outfile],
        cmd = "cp $(location %s) . && rcc --name %s --output $(OUTS) %s" % (qrc_file, rsrc_name, qrc_file),
    )
    cc_library(
        name = name,
        srcs = [outfile],
        alwayslink = 1,
        **kwargs
    )

def qt_cc_test(name, srcs, deps = None, **kwargs):
    """ Compile A Qt CC test
        Args:
        name: name for the rule
        srcs: the cpp files to compile
        deps: cc_library depenencies for the library
        kwargs: Any additional arguments that are passed to the cc_library
    """
    _moc_srcs = []
    for src in srcs:
        path = "%s/%s" % (native.package_name(), src) if len(native.package_name()) > 0 else src
        moc_name = "%s_moc" % src.replace(".", "_") + ".cc"
        out_name = native.package_name() + "/" +moc_name 
        native.genrule(
            name = name + "_moc",
            srcs = [src],
            outs = [out_name],
            cmd = "moc $(location %s) -f '%s' > ./%s" % (src,src,moc_name),
        )
        _moc_srcs.append(moc_name)
    cc_test(
        name = name,
        srcs = _moc_srcs,
        includes = srcs,
        deps = deps,
        **kwargs,
    )

def qt_cc_library(name, srcs, hdrs, normal_hdrs = [], deps = None, **kwargs):
    """Compiles a QT library and generates the MOC for it.

    Args:
      name: A name for the rule.
      srcs: The cpp files to compile.
      hdrs: The header files that the MOC compiles to src.
      normal_hdrs: Headers which are not sources for generated code.
      deps: cc_library dependencies for the library.
      kwargs: Any additional arguments are passed to the cc_library rule.
    """
    _moc_srcs = []
    for hdr in hdrs:
        header_path = "%s/%s" % (native.package_name(), hdr) if len(native.package_name()) > 0 else hdr
        moc_name = "%s_moc" % hdr.replace(".", "_")
        native.genrule(
            name = moc_name,
            srcs = [hdr],
            outs = [moc_name + ".cc"],
            cmd = select({
                "@platforms//os:osx": "moc $(location %s) -o $@ -f '%s'" % (hdr, header_path),
                "@platforms//os:linux": "moc $(location %s) -o $@ -f'z%s'" % (hdr, header_path),
                "@platforms//os:windows": "$(location @qt//:moc) $(locations %s) -o $@ -f'%s'" % (hdr, header_path),
            }),
            tools = select({
                "@platforms//os:osx": [],
                "@platforms//os:linux": [],
                "@platforms//os:windows": ["@qt//:moc"],
            }),
        )
        _moc_srcs.append(":" + moc_name)
    cc_library(
        name = name,
        srcs = srcs + _moc_srcs,
        hdrs = hdrs + normal_hdrs,
        deps = deps,
        **kwargs
    )

qt.BUILD:

load("@rules_cc//cc:defs.bzl", "cc_import", "cc_library")

QT_LIBRARIES = [
#   name                include_folder          library_name            dependencies
    ("core",             "QtCore",              "Qt5Core",              []),
    ("network",          "QtNetwork",           "Qt5Network",           []),
    ("widgets",          "QtWidgets",           "Qt5Widgets",           [":qt_core", ":qt_gui"]),
    ("quick",            "QtQuick",             "Qt5Quick",             [":qt_gui", ":qt_qml", ":qt_qml_models"]),
    ("qml",              "QtQml",               "Qt5Qml",               [":qt_core", ":qt_network"]),
    ("qml_models",       "QtQmlModels",         "Qt5QmlModels",         []),
    ("gui",              "QtGui",               "Qt5Gui",               [":qt_core"]),
    ("opengl",           "QtOpenGL",            "Qt5OpenGL",            []),
    ("multimediawidgets","QtMultimediaWidgets", "Qt5MultimediaWidgets", []),
    ("multimedia",       "QtMultimedia",        "Qt5Multimedia",        []),
    ("test",             "QtTest",              "Qt5Test",              []),
]

[
    cc_library(
        name = "qt_%s_linux" % name,
        # When being on Windows this glob will be empty
        hdrs = glob(["%s/**" % include_folder], allow_empty=True),
        includes = ["."],
        linkopts = ["-l%s" % library_name],
        # Available from Bazel 4.0.0
        target_compatible_with = ["@platforms//os:linux"],
    )
    for name, include_folder, library_name, _ in QT_LIBRARIES
]

[
    cc_library(
        name = "qt_%s_macos" % name,
        hdrs = glob(["include/%s/**" % include_folder], allow_empty=True),
        includes = ["include/"],
        srcs = ["lib/lib%s.dylib" % library_name],
        # Available from Bazel 4.0.0
        visibility = ["//visibility:public"],
        # target_compatible_with = ["@platforms//os:macos"],
    )
    for name, include_folder, library_name, _ in QT_LIBRARIES
]

[
    cc_import(
        name = "qt_%s_windows_import" % name,
        hdrs = glob(["include/%s/**" % include_folder], allow_empty=True),
        # When being on Linux this glob will be empty
        interface_library = "lib/%s.lib" % library_name,
        shared_library = "bin/%s.dll" % library_name,
        # Not available in cc_import (See: https://github.com/bazelbuild/bazel/issues/12745)
        # target_compatible_with = ["@platforms//os:windows"],
    )
    for name, include_folder, library_name, _ in QT_LIBRARIES
]

[
    cc_library(
        name = "qt_%s_windows" % name,
        # When being on Linux this glob will be empty
        hdrs = glob(["include/%s/**" % include_folder], allow_empty=True),
        includes = ["include"],
        # Available from Bazel 4.0.0
        # target_compatible_with = ["@platforms//os:windows"],
        deps = [":qt_%s_windows_import" % name],
    )
    for name, include_folder, _, _ in QT_LIBRARIES
]

[
    cc_library(
        name = "qt_%s" % name,
        visibility = ["//visibility:public"],
        deps = dependencies + select({
            "@platforms//os:linux": [":qt_%s_linux" % name],
            "@platforms//os:osx": [":qt_%s_macos" % name],
            "@platforms//os:windows": [":qt_%s_windows" % name],
        }),
    )
    for name, _, _, dependencies in QT_LIBRARIES
]

# TODO: Make available also for Windows
cc_library(
    name = "qt_3d",
    # When being on Windows this glob will be empty
    hdrs = glob([
        "Qt3DAnimation/**",
        "Qt3DCore/**",
        "Qt3DExtras/**",
        "Qt3DInput/**",
        "Qt3DLogic/**",
        "Qt3DQuick/**",
        "Qt3DQuickAnimation/**",
        "Qt3DQuickExtras/**",
        "Qt3DQuickInput/**",
        "Qt3DQuickRender/**",
        "Qt3DQuickScene2D/**",
        "Qt3DRender/**",
    ], allow_empty=True),
    includes = ["."],
    linkopts = [
        "-lQt53DAnimation",
        "-lQt53DCore",
        "-lQt53DExtras",
        "-lQt53DInput",
        "-lQt53DLogic",
        "-lQt53DQuick",
        "-lQt53DQuickAnimation",
        "-lQt53DQuickExtras",
        "-lQt53DQuickInput",
        "-lQt53DQuickRender",
        "-lQt53DQuickScene2D",
        "-lQt53DRender",
    ],
)

filegroup(
    name = "uic",
    srcs = ["bin/uic.exe"],
    visibility = ["//visibility:public"],
)

filegroup(
    name = "moc",
    srcs = ["bin/moc.exe"],
    visibility = ["//visibility:public"],
)

And here's the Qt path intake script, which attempts to be ridiculously versbose and most probably still doesn't accomplish automation and better of just manually setting the path:

def qt_autoconf_impl(repository_ctx):
    """
    Generate BUILD file with 'local_qt_path' function to get the Qt local path.
    Args:
        repository_ctx: repository context
    """
    print('Configuring Qt')
    os_name = repository_ctx.os.name.lower()
    is_windows_machine = os_name.find("windows") >= 0
    is_linux_machine = os_name.find("linux") >=0
    is_macos_machine = os_name.find("os x")>0 or os_name.find("mac")>0
    if is_windows_machine:
        # In Windows, this is folder you can find include, lib and bin folders in
        default_qt_path = "C:\\\\Qt\\\\Qt5.15\\\\5.15.2\\\\msvc2019_64\\\\"
    elif is_macos_machine:
        # Similar in Macos
        print('Configuring Build on Mac OS - OS X')
        default_qt_path = "/opt/qt/"
    elif is_linux_machine:
        default_qt_path = "/usr/include/x86_64-linux-gnu/qt5"
        if not repository_ctx.path(default_qt_path).exists:
            default_qt_path = "/usr/include/qt"
    if repository_ctx.path(default_qt_path).exists:
        print("Installation available on the default path: ", default_qt_path)

    qt_path = None
    for key, val in repository_ctx.os.environ.items():
        if "QT_DIR".lower() = key.lower():
            qt_path = val
    #overwrite it
    if qt_path != default_qt_path:
        print("However QT_DIR is defined and will be used: ", qt_path)
    #     # In Linux in case that we have a standalone installation, we need to provide the path inside the include folder
    print("FINAL QT PATH DECIDED AS: ", qt_path)
    repository_ctx.file("BUILD", "# empty BUILD file so that bazel sees this as a valid package directory")
    repository_ctx.template(
        "local_qt.bzl",
        repository_ctx.path(Label("//:BUILD.local_qt.tpl")),
        {"%{path}": qt_path},
    )
qt_autoconf = repository_rule(
    implementation = qt_autoconf_impl,
    configure = True,
)
def qt_configure():
    qt_autoconf(name = "local_config_qt")

That's all you need if you have precompiled shared libs and headers. The negative points of this approach you proabably know - that you have to rebuild the libs for each flavor and rebuild them for every cross-compile version managing the paths/versions

Due to this, I think what we all really need is just a source repo with proper BUILD:

http_archive(
    name = "qtbase", #the more extensions the better, though qtbase with test is really necessary
    strip_prefix = "qtbase-dev",
    urls = ["https://github.com/qt/qtbase/archive/refs/heads/dev.zip"], # INSTEAD the bazelized repo
    build_file_content = all_content, #INSTEAD, the granular BUILD building qt module targets,
)

and in WORKSPACE:

load("@//:qt_configure.bzl", "qt_configure")
qt_configure()
load("@local_config_qt//:local_qt.bzl", "local_qt_path")
new_local_repository(
    name = "qt",
    build_file = "//:qt.BUILD",
    path = local_qt_path(),
)

Rewriting the entire Cmake Qt Build system in Starlark is a monumental task. It took many month for the Qt foundation itself to do it from qmake in Qt5->Qt6, which theoretically was much easier. Normally, they should be interested in this instead, though they are very reclusive about the source doing the OpenSource/Commercial license on that source combination which is their entire commercial model. Fundamentally because of that the quality of their available integration is so painful. @jheaff1 If that's you're doing right now let me know how I can help, I'm sure others will too. A lot of people would really benefit from it.

What's left less ambitiously- though accomplish almost same - is to do granular foreign_rules_cc cmake target Qt modules(dependencies as in BUILD table). It might be doable depending on how many rough edges like the above laid in the Qt's Cmake source itself. @jheaff1 If you share your repo, I'm sure I can contribute to it.

jheaff1 commented 2 years ago

Hi @bedbad,

Thanks for your detailed message. The repo I’m working on simply builds the qt-everywhere-src tarball (Qt6, not Qt5). It does so using a rules_foreign_cc cmake() target.

I’m trying to make the build as hermetic as possible, so that Qt6 builds in a minimal docker container that contains only a C/C++ compiler.

One of the Qt dependencies on Linux must be built with meson, so I’m currently working on adding meson support to rules_foreign_cc.

The repo is still a work in progress and not really in a shareable state yet. I’ll let you know when it is 😁

bedbad commented 2 years ago

@jheaff1 I couldn't find Meson as the Qt6 proper(qtbase..) build dependency. It's a build system which parallels the task of foreign_rules_cc, maybe the issue here that dependencies are confused, I.e you build something else with meson and it needs meson qt6 module (https://mesonbuild.com/Qt6-module.html) so dependency is the other way? In this case you can just wrap bazel command as bash step and wrap the output shared libraries as step in meson CI.

Beyond that it appears to be the same problem. Bazel does make it possible for you to make hermetic build system, although the default cmake toolchain pulls the external system dependencies based on project's CMake files.

I tried to add them by removing all default packages from building platform and looking at the -s, verbose subcommand log. Here're some.

cmake (
    name = "ccache",
    build_args = [
        "--",  # <- Pass remaining options to the native tool.
        "-j 12",
        "-l 8"],
    cache_entries = {
        "REDIS_STORAGE_BACKEND" : "OFF",
        },
    out_binaries = ["ccache"],
    lib_source = "@ccache//:all_srcs",
)
cmake(
    name = "md4c",
    build_args = [
        "--",  # <- Pass remaining options to the native tool.
        "-j 12",
        "-l 8"],
    cache_entries = {
        "BUILD_SHARED_LIBS" : "ON"
        },
    lib_source = "@md4c//:all_srcs",
    out_shared_libs = select({
        "@platforms//os:osx" : [ "libmd4c.dylib"],
        "@platforms//os:linux" : ["libmd4c.so"],
        "@platforms//os:windows" : ["libmd4c.dll"],
        "//conditions:default" : []
    }),
) 

In short it's the part of the same problem. Bazel builds are initially intended to be hermetic rather than not. Those dependencies ideally should all be part of the repo as flavored bazel targets.

eli-schwartz commented 2 years ago

@jheaff1 I couldn't find Meson as the Qt6 proper(qtbase..) build dependency.

It's not a dependency for Qt6, it's a dependency of a dependency of Qt6:

One of the Qt dependencies on Linux must be built with meson

Mesa, libinput, xorg, etc.

bedbad commented 2 years ago

@eli-schwartz as a Meson developer you're of coarse biased towards it, however, it should not compete with build system like bazel. If there would ever be a full build of a library like Tensorflow and Torch in it, let me know - until then all people working in AI would want use something like bazel for complete hermetic build systems.

It's quite dangerous to name Xorg, Mesa, libinput as build dependencies for production application or library project. They are a proper part of OS Distro, like Cocoa for Macos or System32 for Windows. Qt needs them for interface purposes and people shouldn't ever debug their code together with their project code, i.e. you shouldn't ever get *-dev packages of them or compile them with their codebase when they're working on end project like self-driving car or medical edge device.

They are the standard parts of any Linux Distros with Graphics, historically built with make/cmake (only recently switched to a more modern Meson) and the support for those version including cmake will run for decades for those that need to build it. Qt doesn't, adding them in the transitive closure of your project codebase like you add Qt(whose code you may need to to see when debugging) is wrong.

bedbad commented 2 years ago

It's not a dependency for Qt6, it's a dependency of a dependency of Qt6

That's the point that it's impossible, dependency here mtransitive. dependency in the dep =[] sense, i.e smth that's can't not include transitivity.

@jheaff1 Maybe just have both Meson and bazel when building your image, having one finished before the other

That was quite a diversion from the issue of packaged cmake in cmake() rule

jheaff1 commented 2 years ago

As @eli-schwartz said, building Qt6 from source depends on mesa headers, typically installed via something like “apt install libgl-mesa-dev”. In the interest of hermeticity, I’m building mesa, and it’s dependencies, from source using Bazel.

You’re welcome to view my repo, once I’m done with it, which should clarify what I mean

jheaff1 commented 2 years ago

Regarding your issue, have you tried adding tags = [“no-sandbox”]?

jsharpe commented 2 years ago

One of the Qt dependencies on Linux must be built with meson, so I’m currently working on adding meson support to rules_foreign_cc.

@jheaff1 take a look at https://github.com/enfabrica/enkit/tree/master/bazel/meson to get started with this if you haven't already. I also have an interest in having mesa built within bazel and have tried a bit in the past but haven't quite got to the point where I got it working.

jheaff1 commented 2 years ago

Cheers, @jsharpe. I’ll take a look

bedbad commented 2 years ago

@jheaff1 I was able to get through that by bisecting cmake libarchive changes and specifying cmake_version = 3.16.5 from the version list Note, for older cmake versions the flavor for M1(darwin_arm64) is absent and the default cmake source is selected according to the cmake load code, whilst for newer cmake-s macos flavor is universal.

I also reproduced and verified that tags = [“no-sandbox”] works too.

The current issue is: [ 38%] Linking CXX static library libBootstrap.a XcodeDefault.xctoolchain/usr/bin/libtool: no output file specified (specify with -o output)

The Qt6 cmake options appear to expect AR as libtool and pass wrong flags

eli-schwartz commented 2 years ago

@eli-schwartz as a Meson developer you're of coarse biased towards it, however, it should not compete with build system like bazel. If there would ever be a full build of a library like Tensorflow and Torch in it, let me know - until then all people working in AI would want use something like bazel for complete hermetic build systems.

That's nice but also I'm not sure what it has to do with anything I said? :)

(Also, where does AI come into this?)

It's quite dangerous to name Xorg, Mesa, libinput as build dependencies for production application or library project. They are a proper part of OS Distro, like Cocoa for Macos or System32 for Windows. Qt needs them for interface purposes and people shouldn't ever debug their code together with their project code, i.e. you shouldn't ever get *-dev packages of them or compile them with their codebase when they're working on end project like self-driving car or medical edge device.

Well, you say this, but:

Anyway, my point remains the original thing I said, which was pretty simple: you wanted to know why Meson could in someone's workflow be a build dependency of Qt6, I pointed to a quote from that person clarifying why that person was using it.

jheaff1 commented 2 years ago

@bedbad I think your libtool issue seems to happen a lot when using rules_foreign_cc.

The examples in this repo workaround it but clearing the AR variable

https://github.com/bazelbuild/rules_foreign_cc/blob/30fdd1193582f81f3eceb0c4505008a99fe15686/examples/third_party/curl/BUILD.curl.bazel#L23

bedbad commented 2 years ago

@jheaff1 there are already workarounds for it encoded in two places in rules_foreing_cc: https://github.com/bazelbuild/rules_foreign_cc/blob/0dafcb29a8ce9c3b49a211ebba6c9c0f79eb93d6/foreign_cc/built_tools/make_build.bzl#L71 https://github.com/bazelbuild/rules_foreign_cc/blob/33bce66655fa5433c655315824c815e8b6a574d6/foreign_cc/private/cmake_script.bzl#L286

And that doesn't work for Qt for instance because of this(what causes that concrete error line in my referenced case): https://github.com/qt/qtbase/blob/72c609203b48a684afe51878bd7bd487fe96a103/cmake/QtBuild.cmake#L411 Maybe a foundational solution in foreign_rules for libtool/ar issue is needed, that will be hermetic for all platforms?

Have you been able to build Qt6 with cmake() rule on Linux?

jheaff1 commented 2 years ago

Yeah I’ve been able to build the qt-everywhere-src tarball using a rules_foreign_cc cmake() rule on Linux, after adding the “no-sandbox” tag

bedbad commented 2 years ago

That's good, can you share your build?

jheaff1 commented 2 years ago

My repo is still a work-in-progress, it needs some tidying up but I've created a gist containing the cmake() instantiation:

https://gist.github.com/jheaff1/d31e2330e1f06dca03f8a6c3abd49f80

bedbad commented 2 years ago

@bedbad I think your libtool issue seems to happen a lot when using rules_foreign_cc.

The examples in this repo workaround it but clearing the AR variable

This won't work for Qt6, it still touches outputs from "CMAKE_AR" dependent commands:

cache_entries = {
            "CMAKE_AR" : "",
}

results in

[ 29%] Linking CXX static library libBootstrap.a
Error running link command: No such file or directory
make[3]: *** [src/tools/bootstrap/CMakeFiles/Bootstrap.dir/build.make:1698: src/tools/bootstrap/libBootstrap.a] Error 2
bedbad commented 2 years ago

Specifying AR proper for CMAKE_AR passes it a couple build steps further (and breaks hermeticity):

"CMAKE_AR" : "/usr/bin/ar",

[ 33%] Linking CXX executable ../../../libexec/rcc
ld: archive has no table of contents file '../bootstrap/libBootstrap.a' for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

that appears to be the Qt cmake build bug for not correctly calling the ranlib after asking ar "-S" not to generate symbol table

The fact that ar needs to be hardcoded appears to be under foreign_cc responsibility. I'm thinking of writing a wrapper around foreign_cc's libtool that parses the relevent commands adding proper version switches like "-o" without side-effect that breaks anything. There are only 4 such commands dependent on CMAKE_AR as I know and here's their syntax:


"CMAKE_CXX_ARCHIVE_CREATE" : "<CMAKE_AR> <AR_FLAGS(qcS..)><TARGET> <LINK_FLAGS> <-o?><OBJECTS>"
"CMAKE_CXX_ARCHIVE_APPEND" :"<CMAKE_AR> <AR_FLAGS(qS..)> <TARGET> <LINK_FLAGS> <-o?> <OBJECTS>
"CMAKE_C_ARCHIVE_CREATE" : "<CMAKE_AR> <AR_FLAGS> <TARGET> <LINK_FLAGS> <-o?> <OBJECTS>"
"CMAKE_C_ARCHIVE_APPEND" : "<CMAKE_AR> <AR_FLAGS> <TARGET> <LINK_FLAGS> <-o?> <OBJECTS>" 

This seems though as quite a work and may discover issues down the road that's maybe why it hadn't been done: @jheaff1 https://github.com/bazelbuild/rules_foreign_cc/pull/935 @fmeum https://github.com/bazelbuild/rules_foreign_cc/pull/817 What do you think?

fmeum commented 2 years ago

@bedbad I'm convinced that's the right path forward (see my failed attempt at https://github.com/bazelbuild/rules_foreign_cc/pull/819), but as you remark, could be quite a bit of work. It would be great to have though.

jagobagascon commented 1 year ago

Note: I'm using a MacOS (arm64)

I've been using this project for a while to compile Mbed-TLS, but today I tried to update Mbed-TLS to a newer version and had the libtool: no output file specified error.

I tried all the possible solutions mentioned above without any luck, so I decided to check what changed in the Mbed-TLS build. I found out that the new build file now includes this:

if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
    SET(CMAKE_C_ARCHIVE_CREATE   "<CMAKE_AR> Scr <TARGET> <LINK_FLAGS> <OBJECTS>")
    SET(CMAKE_CXX_ARCHIVE_CREATE "<CMAKE_AR> Scr <TARGET> <LINK_FLAGS> <OBJECTS>")
    SET(CMAKE_C_ARCHIVE_FINISH   "<CMAKE_RANLIB> -no_warning_for_no_symbols -c <TARGET>")
    SET(CMAKE_CXX_ARCHIVE_FINISH "<CMAKE_RANLIB> -no_warning_for_no_symbols -c <TARGET>")
endif()

The way I fixed it, as stupid as it sounds, is by setting the CMAKE_SYSTEM_NAME value to anything other than Darwin:

cmake(
    name = "mbedtls",
    # Values to be passed as -Dkey=value on the CMake command line
    cache_entries = select({
        "@platforms//os:macos": {
            "CMAKE_SYSTEM_NAME": "not-darwin",
        },
        "//conditions:default": default_cache_entries,
    ,
    ...
)

I don't like having this in my BUILD files, so is there any other "less dirty" way of fixing this?

Right now I'm not sure whether this is a problem with my tooling, a bug in rules_foreign_cc or some other problem.