diff --git a/foreign_cc/built_tools/make_build.bzl b/foreign_cc/built_tools/make_build.bzl index 0baaa5c..b784dce 100644 --- a/foreign_cc/built_tools/make_build.bzl +++ b/foreign_cc/built_tools/make_build.bzl @@ -15,6 +15,7 @@ load( "get_flags_info", "get_tools_info", ) +load("//foreign_cc/private:detect_xcompile.bzl", "detect_xcompile") load("//foreign_cc/private/framework:platform.bzl", "os_name") def _make_tool_impl(ctx): @@ -74,6 +75,25 @@ def _make_tool_impl(ctx): if os_name(ctx) == "macos": non_sysroot_ldflags += ["-undefined", "error"] + configure_options = [ + "--without-guile", + "--with-guile=no", + "--disable-dependency-tracking", + "--prefix=$$INSTALLDIR$$", + ] + + install_cmd = ["./make install"] + + xcompile_options = detect_xcompile(ctx) + if xcompile_options: + configure_options.extend(xcompile_options) + + # We can't use make to install make when cross-compiling + install_cmd = [ + "mkdir -p $$INSTALLDIR$$/bin", + "cp -p make $$INSTALLDIR$$/bin/make", + ] + env.update({ "AR": absolute_ar, "ARFLAGS": _join_flags_list(ctx.workspace_name, arflags), @@ -85,11 +105,10 @@ def _make_tool_impl(ctx): configure_env = " ".join(["%s=\"%s\"" % (key, value) for key, value in env.items()]) script = [ - "%s ./configure --without-guile --with-guile=no --disable-dependency-tracking --prefix=$$INSTALLDIR$$" % configure_env, + "%s ./configure %s" % (configure_env, " ".join(configure_options)), "cat build.cfg", "./build.sh", - "./make install", - ] + ] + install_cmd return built_tool_rule_impl( ctx, diff --git a/foreign_cc/built_tools/pkgconfig_build.bzl b/foreign_cc/built_tools/pkgconfig_build.bzl index f623c76..1bbf9a6 100644 --- a/foreign_cc/built_tools/pkgconfig_build.bzl +++ b/foreign_cc/built_tools/pkgconfig_build.bzl @@ -15,6 +15,7 @@ load( "get_flags_info", "get_tools_info", ) +load("//foreign_cc/private:detect_xcompile.bzl", "detect_xcompile") load("//foreign_cc/private/framework:platform.bzl", "os_name") load("//toolchains/native_tools:tool_access.bzl", "get_make_data") @@ -61,6 +62,15 @@ def _pkgconfig_tool_impl(ctx): make_data = get_make_data(ctx) + configure_options = [ + "--with-internal-glib", + "--prefix=$$INSTALLDIR$$", + ] + + xcompile_options = detect_xcompile(ctx) + if xcompile_options: + configure_options.extend(xcompile_options) + env.update({ "AR": absolute_ar, "ARFLAGS": _join_flags_list(ctx.workspace_name, arflags), @@ -73,7 +83,7 @@ def _pkgconfig_tool_impl(ctx): configure_env = " ".join(["%s=\"%s\"" % (key, value) for key, value in env.items()]) script = [ - "%s ./configure --with-internal-glib --prefix=$$INSTALLDIR$$" % configure_env, + "%s ./configure %s" % (configure_env, " ".join(configure_options)), "%s" % make_data.path, "%s install" % make_data.path, ] diff --git a/foreign_cc/built_tools/private/built_tools_framework.bzl b/foreign_cc/built_tools/private/built_tools_framework.bzl index bec5599..1431378 100644 --- a/foreign_cc/built_tools/private/built_tools_framework.bzl +++ b/foreign_cc/built_tools/private/built_tools_framework.bzl @@ -5,9 +5,16 @@ load("//foreign_cc/private:cc_toolchain_util.bzl", "absolutize_path_in_str") load("//foreign_cc/private:detect_root.bzl", "detect_root") load("//foreign_cc/private:framework.bzl", "get_env_prelude", "wrap_outputs") load("//foreign_cc/private/framework:helpers.bzl", "convert_shell_script", "shebang") +load("//foreign_cc/private/framework:platform.bzl", "PLATFORM_CONSTRAINTS_RULE_ATTRIBUTES") # Common attributes for all built_tool rules FOREIGN_CC_BUILT_TOOLS_ATTRS = { + "configure_xcompile": attr.bool( + doc = ( + "If this is set and an xcompile scenario is detected, pass the necessary autotools flags. (Only applies if autotools is used)" + ), + default = False, + ), "env": attr.string_dict( doc = "Environment variables to set during the build. This attribute is subject to make variable substitution.", default = {}, @@ -26,6 +33,9 @@ FOREIGN_CC_BUILT_TOOLS_ATTRS = { ), } +# this would be cleaner as x | y, but that's not supported in bazel 5.4.0 +FOREIGN_CC_BUILT_TOOLS_ATTRS.update(PLATFORM_CONSTRAINTS_RULE_ATTRIBUTES) + # Common fragments for all built_tool rules FOREIGN_CC_BUILT_TOOLS_FRAGMENTS = [ "apple", diff --git a/foreign_cc/cmake.bzl b/foreign_cc/cmake.bzl index d781dcd..17b5239 100644 --- a/foreign_cc/cmake.bzl +++ b/foreign_cc/cmake.bzl @@ -253,6 +253,7 @@ def _create_configure_script(configureParameters): configure_script = create_cmake_script( workspace_name = ctx.workspace_name, + current_label = ctx.label, target_os = target_os_name(ctx), target_arch = target_arch_name(ctx), host_os = os_name(ctx), diff --git a/foreign_cc/configure.bzl b/foreign_cc/configure.bzl index a0e0519..d0f50d1 100644 --- a/foreign_cc/configure.bzl +++ b/foreign_cc/configure.bzl @@ -9,6 +9,7 @@ load( ) load("//foreign_cc/private:configure_script.bzl", "create_configure_script") load("//foreign_cc/private:detect_root.bzl", "detect_root") +load("//foreign_cc/private:detect_xcompile.bzl", "detect_xcompile") load( "//foreign_cc/private:framework.bzl", "CC_EXTERNAL_RULE_ATTRIBUTES", @@ -78,6 +79,10 @@ def _create_configure_script(configureParameters): configure_prefix = "{} ".format(expand_locations_and_make_variables(ctx, ctx.attr.configure_prefix, "configure_prefix", data)) if ctx.attr.configure_prefix else "" configure_options = [expand_locations_and_make_variables(ctx, option, "configure_option", data) for option in ctx.attr.configure_options] if ctx.attr.configure_options else [] + xcompile_options = detect_xcompile(ctx) + if xcompile_options: + configure_options.extend(xcompile_options) + for target in ctx.attr.targets: # Configure will have generated sources into `$BUILD_TMPDIR` so make sure we `cd` there make_commands.append("{prefix}{make} {target} {args}".format( @@ -187,6 +192,12 @@ def _attrs(): "configure_prefix": attr.string( doc = "A prefix for the call to the `configure_command`.", ), + "configure_xcompile": attr.bool( + doc = ( + "If this is set and an xcompile scenario is detected, pass the necessary autotools flags." + ), + default = False, + ), "install_prefix": attr.string( doc = ( "Install prefix, i.e. relative path to where to install the result of the build. " + diff --git a/foreign_cc/private/BUILD.bazel b/foreign_cc/private/BUILD.bazel index ccbe9db..d08851b 100644 --- a/foreign_cc/private/BUILD.bazel +++ b/foreign_cc/private/BUILD.bazel @@ -39,9 +39,11 @@ bzl_library( deps = [ ":cc_toolchain_util", ":detect_root", + ":detect_xcompile.bzl", ":run_shell_file_utils", "//foreign_cc:providers", "//foreign_cc/private/framework:helpers", + "//foreign_cc/private/framework:platform", "@bazel_skylib//lib:collections", "@bazel_skylib//lib:paths", "@bazel_tools//tools/cpp:toolchain_utils.bzl", diff --git a/foreign_cc/private/cmake_script.bzl b/foreign_cc/private/cmake_script.bzl index bfe21bd..b735162 100644 --- a/foreign_cc/private/cmake_script.bzl +++ b/foreign_cc/private/cmake_script.bzl @@ -31,6 +31,9 @@ _TARGET_ARCH_PARAMS = { "aarch64": { "CMAKE_SYSTEM_PROCESSOR": "aarch64", }, + "s390x": { + "CMAKE_SYSTEM_PROCESSOR": "s390x", + }, "x86_64": { "CMAKE_SYSTEM_PROCESSOR": "x86_64", }, @@ -38,6 +41,7 @@ _TARGET_ARCH_PARAMS = { def create_cmake_script( workspace_name, + current_label, target_os, target_arch, host_os, @@ -59,6 +63,7 @@ def create_cmake_script( Args: workspace_name: current workspace name + current_label: The label of the target currently being built target_os: The target OS for the build target_arch: The target arch for the build host_os: The execution OS for the build @@ -120,7 +125,10 @@ def create_cmake_script( # by setting CMAKE_SYSTEM_NAME and CMAKE_SYSTEM_PROCESSOR, # see https://github.com/bazelbuild/rules_foreign_cc/issues/289, # and https://github.com/bazelbuild/rules_foreign_cc/pull/1062 - if target_os != host_os and target_os != "unknown": + if target_os == "unknown": + # buildifier: disable=print + print("target_os is unknown, please update foreign_cc/private/framework/platform.bzl and foreign_cc/private/cmake_script.bzl; triggered by", current_label) + elif target_os != host_os: params.cache.update(_TARGET_OS_PARAMS.get(target_os, {})) params.cache.update(_TARGET_ARCH_PARAMS.get(target_arch, {})) diff --git a/foreign_cc/private/detect_xcompile.bzl b/foreign_cc/private/detect_xcompile.bzl new file mode 100644 index 0000000..62b74a3 --- /dev/null +++ b/foreign_cc/private/detect_xcompile.bzl @@ -0,0 +1,61 @@ +# buildifier: disable=module-docstring +load( + "//foreign_cc/private/framework:platform.bzl", + "arch_name", + "os_name", + "target_arch_name", + "target_os_name", + "triplet_name", +) + +def detect_xcompile(ctx): + """A helper function for detecting and setting autoconf-style xcompile flags + + Args: + ctx (ctx): The current rule's context object + + Returns: + list(str): The flags to set, or None if xcompiling is disabled + """ + + if not ctx.attr.configure_xcompile: + return None + + host_triplet = triplet_name( + os_name(ctx), + arch_name(ctx), + ) + + if host_triplet == "unknown": + # buildifier: disable=print + print("host is unknown; please update foreign_cc/private/framework/platform.bzl; triggered by", ctx.label) + return None + + target_triplet = triplet_name( + target_os_name(ctx), + target_arch_name(ctx), + ) + + if target_triplet == "unknown": + # buildifier: disable=print + print("target is unknown; please update foreign_cc/private/framework/platform.bzl; triggered by", ctx.label) + return None + + if target_triplet == host_triplet: + return None + + # We pass both --host and --build here, even though we technically only + # need to pass --host. This is because autotools compares them (without + # normalization) to determine if a build is a cross-compile + # + # If we don't pass --build, autotools will populate it itself, and it might + # be normalized such that autotools thinks it's a cross-compile, but it + # shouldn't be. + # + # An example of this is if we pass --host=x86_64-pc-linux-gnu but the + # target compiler thinks it's for x86_64-unknown-linux-gnu; if we don't + # pass --build, that will incorrectly be considered a cross-compile. + # + # Also, no, this isn't backwards. --host means target + # https://www.gnu.org/software/automake/manual/html_node/Cross_002dCompilation.html + return ["--host=" + target_triplet, "--build=" + host_triplet] diff --git a/foreign_cc/private/framework.bzl b/foreign_cc/private/framework.bzl index eb2eedd..b4b8d63 100644 --- a/foreign_cc/private/framework.bzl +++ b/foreign_cc/private/framework.bzl @@ -15,6 +15,7 @@ load( "script_extension", "shebang", ) +load("//foreign_cc/private/framework:platform.bzl", "PLATFORM_CONSTRAINTS_RULE_ATTRIBUTES") load( ":cc_toolchain_util.bzl", "LibrariesToLinkInfo", @@ -209,8 +210,6 @@ CC_EXTERNAL_RULE_ATTRIBUTES = { cfg = "exec", default = [], ), - "_aarch64_constraint": attr.label(default = Label("@platforms//cpu:aarch64")), - "_android_constraint": attr.label(default = Label("@platforms//os:android")), # we need to declare this attribute to access cc_toolchain "_cc_toolchain": attr.label( default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), @@ -220,10 +219,11 @@ CC_EXTERNAL_RULE_ATTRIBUTES = { cfg = "exec", default = Label("@rules_foreign_cc//foreign_cc/private/framework:platform_info"), ), - "_linux_constraint": attr.label(default = Label("@platforms//os:linux")), - "_x86_64_constraint": attr.label(default = Label("@platforms//cpu:x86_64")), } +# this would be cleaner as x | y, but that's not supported in bazel 5.4.0 +CC_EXTERNAL_RULE_ATTRIBUTES.update(PLATFORM_CONSTRAINTS_RULE_ATTRIBUTES) + # A list of common fragments required by rules using this framework CC_EXTERNAL_RULE_FRAGMENTS = [ "apple", diff --git a/foreign_cc/private/framework/platform.bzl b/foreign_cc/private/framework/platform.bzl index 8c9fc9a..45860f0 100644 --- a/foreign_cc/private/framework/platform.bzl +++ b/foreign_cc/private/framework/platform.bzl @@ -1,8 +1,40 @@ """A helper module containing tools for detecting platform information""" +SUPPORTED_CPU = [ + "aarch64", + "s390x", + "x86_64", +] + +SUPPORTED_OS = [ + "android", + "freebsd", + "ios", + "linux", + "macos", + "none", + "openbsd", + "qnx", + "tvos", + "watchos", + "windows", +] + +PLATFORM_CONSTRAINTS_RULE_ATTRIBUTES = { + "_{}_constraint".format(i): attr.label(default = Label("@platforms//os:{}".format(i))) + for i in SUPPORTED_OS +} + +# this would be cleaner as x | y, but that's not supported in bazel 5.4.0 +PLATFORM_CONSTRAINTS_RULE_ATTRIBUTES.update({ + "_{}_constraint".format(i): attr.label(default = Label("@platforms//cpu:{}".format(i))) + for i in SUPPORTED_CPU +}) + ForeignCcPlatformInfo = provider( doc = "A provider containing information about the current platform", fields = { + "cpu": "The platform cpu", "os": "The platform os", }, ) @@ -18,12 +50,16 @@ def _framework_platform_info_impl(ctx): """ return [ForeignCcPlatformInfo( os = ctx.attr.os, + cpu = ctx.attr.cpu, )] _framework_platform_info = rule( doc = "A rule defining platform information used by the foreign_cc framework", implementation = _framework_platform_info_impl, attrs = { + "cpu": attr.string( + doc = "The platform's cpu", + ), "os": attr.string( doc = "The platform's operating system", ), @@ -31,23 +67,33 @@ _framework_platform_info = rule( ) def framework_platform_info(name = "platform_info"): - """Define a target containing platform information used in the foreign_cc framework""" + """Define a target containing platform information used in the foreign_cc framework + + Args: + name: A unique name for this target. + """ + + # this would be cleaner as x | y, but that's not supported in bazel 5.4.0 + select_os = { + "@platforms//os:{}".format(i): i + for i in SUPPORTED_OS + } + select_os.update({ + "//conditions:default": "unknown", + }) + + select_cpu = { + "@platforms//cpu:{}".format(i): i + for i in SUPPORTED_CPU + } + select_cpu.update({ + "//conditions:default": "unknown", + }) + _framework_platform_info( name = name, - os = select({ - "@platforms//os:android": "android", - "@platforms//os:freebsd": "freebsd", - "@platforms//os:ios": "ios", - "@platforms//os:linux": "linux", - "@platforms//os:macos": "macos", - "@platforms//os:none": "none", - "@platforms//os:openbsd": "openbsd", - "@platforms//os:qnx": "qnx", - "@platforms//os:tvos": "tvos", - "@platforms//os:watchos": "watchos", - "@platforms//os:windows": "windows", - "//conditions:default": "unknown", - }), + os = select(select_os), + cpu = select(select_cpu), visibility = ["//visibility:public"], ) @@ -66,6 +112,21 @@ def os_name(ctx): return platform_info[ForeignCcPlatformInfo].os +def arch_name(ctx): + """A helper function for getting the arch name from a `ForeignCcPlatformInfo` provider + + Args: + ctx (ctx): The current rule's context object + + Returns: + str: The string of the current platform + """ + platform_info = getattr(ctx.attr, "_foreign_cc_framework_platform") + if not platform_info: + return "unknown" + + return platform_info[ForeignCcPlatformInfo].cpu + def target_arch_name(ctx): """A helper function for getting the target architecture name based on the constraints @@ -75,8 +136,7 @@ def target_arch_name(ctx): Returns: str: The string of the current platform """ - archs = ["x86_64", "aarch64"] - for arch in archs: + for arch in SUPPORTED_CPU: constraint = getattr(ctx.attr, "_{}_constraint".format(arch)) if constraint and ctx.target_platform_has_constraint(constraint[platform_common.ConstraintValueInfo]): return arch @@ -92,10 +152,50 @@ def target_os_name(ctx): Returns: str: The string of the current platform """ - operating_systems = ["android", "linux"] - for os in operating_systems: + for os in SUPPORTED_OS: constraint = getattr(ctx.attr, "_{}_constraint".format(os)) if constraint and ctx.target_platform_has_constraint(constraint[platform_common.ConstraintValueInfo]): return os return "unknown" + +def triplet_name(os, arch): + """A helper function for getting the platform triplet from the results of the above arch/os functions + + Args: + os (str): the os + arch (str): the arch + + Returns: + str: The string of the current platform + """ + + # This is like a simplified config.guess / config.sub from autotools + if os == "linux": + # The linux values here were what config.guess returns on ubuntu:22.04; + # specifically, this version: + # https://git.savannah.gnu.org/gitweb/?p=config.git;a=blob;f=config.guess;hb=00b15927496058d23e6258a28d8996f87cf1f191 + # + # bazel doesn't have common libc constraints, which makes it difficult + # to guess what the correct value for the last field might be (it would + # be musl on alpine, for example). This doesn't change what the + # compiler itself will do, though, so as long as we normalize + # consistently, I don't think this will break alpine. + if arch == "aarch64": + return "aarch64-unknown-linux-gnu" + elif arch == "s390x": + return "s390x-ibm-linux-gnu" + elif arch == "x86_64": + return "x86_64-pc-linux-gnu" + + elif os == "macos": + # These are _not_ what config.guess would return for darwin; + # config.guess puts the release version in the field, e.g. + # darwin23.4.0. We can't correctly guess the darwin version, so we use + # macos instead, which is also recognized/honored by autotools + if arch == "aarch64": + return "aarch64-apple-macos" + elif arch == "x86_64": + return "x86_64-apple-macos" + + return "unknown" diff --git a/test/cmake_text_tests.bzl b/test/cmake_text_tests.bzl index 105faf5..4130507 100644 --- a/test/cmake_text_tests.bzl +++ b/test/cmake_text_tests.bzl @@ -242,6 +242,7 @@ def _merge_flag_values_no_toolchain_file_test(ctx): script = create_cmake_script( "ws", + ctx.label, "unknown", "unknown", "unknown", @@ -294,6 +295,7 @@ def _create_min_cmake_script_no_toolchain_file_test(ctx): script = create_cmake_script( "ws", + ctx.label, "unknown", "unknown", "unknown", @@ -350,6 +352,7 @@ def _create_min_cmake_script_wipe_toolchain_test(ctx): script = create_cmake_script( "ws", + ctx.label, "unknown", "unknown", "unknown", @@ -402,6 +405,7 @@ def _create_min_cmake_script_toolchain_file_test(ctx): script = create_cmake_script( "ws", + ctx.label, "unknown", "unknown", "unknown", @@ -482,6 +486,7 @@ def _create_cmake_script_no_toolchain_file_test(ctx): script = create_cmake_script( "ws", + ctx.label, "unknown", "unknown", "unknown", @@ -549,6 +554,7 @@ def _create_cmake_script_android_test(ctx): script = create_cmake_script( "ws", + ctx.label, "android", "x86_64", "unknown", @@ -616,6 +622,7 @@ def _create_cmake_script_linux_test(ctx): script = create_cmake_script( "ws", + ctx.label, "linux", "aarch64", "unknown", @@ -682,6 +689,7 @@ def _create_cmake_script_toolchain_file_test(ctx): script = create_cmake_script( "ws", + ctx.label, "unknown", "unknown", "unknown",