From 8793f2ca4f53168a7054800f5b66ac376d8d7463 Mon Sep 17 00:00:00 2001 From: fricklerhandwerk Date: Wed, 9 Mar 2022 10:05:17 +0100 Subject: [PATCH] extract CC toolchain into module leave aliases to existing files to ensure backwards compatibility --- nixpkgs/BUILD.bazel | 1 + nixpkgs/nixpkgs.bzl | 406 +------------------ nixpkgs/toolchains/foreign_cc.bzl | 115 +----- tests/BUILD.bazel | 1 + toolchains/cc/BUILD.bazel | 6 + toolchains/cc/MODULE.bazel | 6 + toolchains/cc/cc.bzl | 398 ++++++++++++++++++ {nixpkgs/toolchains => toolchains/cc}/cc.nix | 0 toolchains/cc/foreign_cc.bzl | 114 ++++++ 9 files changed, 539 insertions(+), 508 deletions(-) create mode 100644 toolchains/cc/BUILD.bazel create mode 100644 toolchains/cc/MODULE.bazel create mode 100644 toolchains/cc/cc.bzl rename {nixpkgs/toolchains => toolchains/cc}/cc.nix (100%) create mode 100644 toolchains/cc/foreign_cc.bzl diff --git a/nixpkgs/BUILD.bazel b/nixpkgs/BUILD.bazel index 65f1539a2..3f36ebe59 100644 --- a/nixpkgs/BUILD.bazel +++ b/nixpkgs/BUILD.bazel @@ -43,6 +43,7 @@ bzl_library( "@rules_nixpkgs_core//:core", "//toolchains/python:python.bzl", "//toolchains/java:java.bzl", + "//toolchains/cc:cc.bzl", ":bazel_tools", "@bazel_skylib//lib:new_sets", "@bazel_skylib//lib:paths", diff --git a/nixpkgs/nixpkgs.bzl b/nixpkgs/nixpkgs.bzl index 1dab37804..ea99dc035 100644 --- a/nixpkgs/nixpkgs.bzl +++ b/nixpkgs/nixpkgs.bzl @@ -1,21 +1,22 @@ """Rules for importing Nixpkgs packages.""" -load("@bazel_skylib//lib:sets.bzl", "sets") -load("@bazel_skylib//lib:versions.bzl", "versions") -load("@bazel_tools//tools/cpp:cc_configure.bzl", "cc_autoconf_impl") load( "@bazel_tools//tools/cpp:lib_cc_configure.bzl", "get_cpu_value", - "get_starlark_list", - "write_builtin_include_directory_paths", ) -load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") load( "@rules_nixpkgs_core//:nixpkgs.bzl", _nixpkgs_git_repository = "nixpkgs_git_repository", _nixpkgs_local_repository = "nixpkgs_local_repository", _nixpkgs_package = "nixpkgs_package", ) +load("@bazel_tools//tools/cpp:cc_configure.bzl", "cc_autoconf_impl") +load( + "@rules_nixpkgs_core//:util.bzl", + "execute_or_fail", + "find_children", + "is_supported_platform", +) load( "//toolchains/python:python.bzl", _nixpkgs_python_configure = "nixpkgs_python_configure", @@ -25,11 +26,8 @@ load( _nixpkgs_java_configure = "nixpkgs_java_configure", ) load( - "@rules_nixpkgs_core//:util.bzl", - "ensure_constraints", - "execute_or_fail", - "find_children", - "is_supported_platform", + "//toolchains/cc:cc.bzl", + _nixpkgs_cc_configure = "nixpkgs_cc_configure", ) # aliases for backwards compatibility prior to `bzlmod` @@ -38,389 +36,7 @@ nixpkgs_local_repository = _nixpkgs_local_repository nixpkgs_package = _nixpkgs_package nixpkgs_python_configure = _nixpkgs_python_configure nixpkgs_java_configure = _nixpkgs_java_configure - -def _parse_cc_toolchain_info(content, filename): - """Parses the `CC_TOOLCHAIN_INFO` file generated by Nix. - - Attrs: - content: string, The content of the `CC_TOOLCHAIN_INFO` file. - filename: string, The path to the `CC_TOOLCHAIN_INFO` file, used for error reporting. - - Returns: - struct, The substitutions for `@bazel_tools//tools/cpp:BUILD.tpl`. - """ - - # Parse the content of CC_TOOLCHAIN_INFO. - # - # Each line has the form - # - # :::... - info = {} - for line in content.splitlines(): - fields = line.split(":") - if len(fields) == 0: - fail( - "Malformed CC_TOOLCHAIN_INFO '{}': Empty line encountered.".format(filename), - "cc_toolchain_info", - ) - info[fields[0]] = fields[1:] - - # Validate the keys in CC_TOOLCHAIN_INFO. - expected_keys = sets.make([ - "TOOL_NAMES", - "TOOL_PATHS", - "CXX_BUILTIN_INCLUDE_DIRECTORIES", - "COMPILE_FLAGS", - "CXX_FLAGS", - "LINK_FLAGS", - "LINK_LIBS", - "OPT_COMPILE_FLAGS", - "OPT_LINK_FLAGS", - "UNFILTERED_COMPILE_FLAGS", - "DBG_COMPILE_FLAGS", - "COVERAGE_COMPILE_FLAGS", - "COVERAGE_LINK_FLAGS", - "SUPPORTS_START_END_LIB", - "IS_CLANG", - ]) - actual_keys = sets.make(info.keys()) - missing_keys = sets.difference(expected_keys, actual_keys) - unexpected_keys = sets.difference(actual_keys, expected_keys) - if sets.length(missing_keys) > 0: - fail( - "Malformed CC_TOOLCHAIN_INFO '{}': Missing entries '{}'.".format( - filename, - "', '".join(sets.to_list(missing_keys)), - ), - "cc_toolchain_info", - ) - if sets.length(unexpected_keys) > 0: - fail( - "Malformed CC_TOOLCHAIN_INFO '{}': Unexpected entries '{}'.".format( - filename, - "', '".join(sets.to_list(unexpected_keys)), - ), - "cc_toolchain_info", - ) - - return struct( - tool_paths = { - tool: path - for (tool, path) in zip(info["TOOL_NAMES"], info["TOOL_PATHS"]) - }, - cxx_builtin_include_directories = info["CXX_BUILTIN_INCLUDE_DIRECTORIES"], - compile_flags = info["COMPILE_FLAGS"], - cxx_flags = info["CXX_FLAGS"], - link_flags = info["LINK_FLAGS"], - link_libs = info["LINK_LIBS"], - opt_compile_flags = info["OPT_COMPILE_FLAGS"], - opt_link_flags = info["OPT_LINK_FLAGS"], - unfiltered_compile_flags = info["UNFILTERED_COMPILE_FLAGS"], - dbg_compile_flags = info["DBG_COMPILE_FLAGS"], - coverage_compile_flags = info["COVERAGE_COMPILE_FLAGS"], - coverage_link_flags = info["COVERAGE_LINK_FLAGS"], - supports_start_end_lib = info["SUPPORTS_START_END_LIB"] == ["True"], - is_clang = info["IS_CLANG"] == ["True"], - ) - -def _nixpkgs_cc_toolchain_config_impl(repository_ctx): - cpu_value = get_cpu_value(repository_ctx) - darwin = cpu_value == "darwin" - - cc_toolchain_info_file = repository_ctx.path(repository_ctx.attr.cc_toolchain_info) - if not cc_toolchain_info_file.exists and not repository_ctx.attr.fail_not_supported: - return - info = _parse_cc_toolchain_info( - repository_ctx.read(cc_toolchain_info_file), - cc_toolchain_info_file, - ) - - # Generate the cc_toolchain workspace following the example from - # `@bazel_tools//tools/cpp:unix_cc_configure.bzl`. - # Uses the corresponding templates from `@bazel_tools` as well, see the - # private attributes of the `_nixpkgs_cc_toolchain_config` rule. - repository_ctx.symlink( - repository_ctx.path(repository_ctx.attr._unix_cc_toolchain_config), - "cc_toolchain_config.bzl", - ) - repository_ctx.symlink( - repository_ctx.path(repository_ctx.attr._armeabi_cc_toolchain_config), - "armeabi_cc_toolchain_config.bzl", - ) - - # A module map is required for clang starting from Bazel version 3.3.0. - # https://github.com/bazelbuild/bazel/commit/8b9f74649512ee17ac52815468bf3d7e5e71c9fa - needs_module_map = info.is_clang and versions.is_at_least("3.3.0", versions.get()) - if needs_module_map: - generate_system_module_map = [ - repository_ctx.path(repository_ctx.attr._generate_system_module_map), - ] - repository_ctx.file( - "module.modulemap", - execute_or_fail( - repository_ctx, - generate_system_module_map + info.cxx_builtin_include_directories, - "Failed to generate system module map.", - ).stdout.strip(), - executable = False, - ) - cc_wrapper_src = ( - repository_ctx.attr._osx_cc_wrapper if darwin else repository_ctx.attr._linux_cc_wrapper - ) - repository_ctx.template( - "cc_wrapper.sh", - repository_ctx.path(cc_wrapper_src), - { - "%{cc}": info.tool_paths["gcc"], - "%{env}": "", - }, - ) - if darwin: - info.tool_paths["gcc"] = "cc_wrapper.sh" - info.tool_paths["ar"] = "/usr/bin/libtool" - write_builtin_include_directory_paths( - repository_ctx, - info.tool_paths["gcc"], - info.cxx_builtin_include_directories, - ) - repository_ctx.template( - "BUILD.bazel", - repository_ctx.path(repository_ctx.attr._build), - { - "%{cc_toolchain_identifier}": "local", - "%{name}": cpu_value, - "%{modulemap}": ("\":module.modulemap\"" if needs_module_map else "None"), - "%{supports_param_files}": "0" if darwin else "1", - "%{cc_compiler_deps}": get_starlark_list( - [":builtin_include_directory_paths"] + ( - [":cc_wrapper"] if darwin else [] - ), - ), - "%{compiler}": "compiler", - "%{abi_version}": "local", - "%{abi_libc_version}": "local", - "%{host_system_name}": "local", - "%{target_libc}": "macosx" if darwin else "local", - "%{target_cpu}": cpu_value, - "%{target_system_name}": "local", - "%{tool_paths}": ",\n ".join( - ['"%s": "%s"' % (k, v) for (k, v) in info.tool_paths.items()], - ), - "%{cxx_builtin_include_directories}": get_starlark_list(info.cxx_builtin_include_directories), - "%{compile_flags}": get_starlark_list(info.compile_flags), - "%{cxx_flags}": get_starlark_list(info.cxx_flags), - "%{link_flags}": get_starlark_list(info.link_flags), - "%{link_libs}": get_starlark_list(info.link_libs), - "%{opt_compile_flags}": get_starlark_list(info.opt_compile_flags), - "%{opt_link_flags}": get_starlark_list(info.opt_link_flags), - "%{unfiltered_compile_flags}": get_starlark_list(info.unfiltered_compile_flags), - "%{dbg_compile_flags}": get_starlark_list(info.dbg_compile_flags), - "%{coverage_compile_flags}": get_starlark_list(info.coverage_compile_flags), - "%{coverage_link_flags}": get_starlark_list(info.coverage_link_flags), - "%{supports_start_end_lib}": repr(info.supports_start_end_lib), - }, - ) - -_nixpkgs_cc_toolchain_config = repository_rule( - _nixpkgs_cc_toolchain_config_impl, - attrs = { - "cc_toolchain_info": attr.label(), - "fail_not_supported": attr.bool(), - "_unix_cc_toolchain_config": attr.label( - default = Label("@bazel_tools//tools/cpp:unix_cc_toolchain_config.bzl"), - ), - "_armeabi_cc_toolchain_config": attr.label( - default = Label("@bazel_tools//tools/cpp:armeabi_cc_toolchain_config.bzl"), - ), - "_generate_system_module_map": attr.label( - default = Label("@bazel_tools//tools/cpp:generate_system_module_map.sh"), - ), - "_osx_cc_wrapper": attr.label( - default = Label("@bazel_tools//tools/cpp:osx_cc_wrapper.sh.tpl"), - ), - "_linux_cc_wrapper": attr.label( - default = Label("@bazel_tools//tools/cpp:linux_cc_wrapper.sh.tpl"), - ), - "_build": attr.label( - default = Label("@bazel_tools//tools/cpp:BUILD.tpl"), - ), - }, -) - -def _nixpkgs_cc_toolchain_impl(repository_ctx): - cpu = get_cpu_value(repository_ctx) - exec_constraints, target_constraints = ensure_constraints(repository_ctx) - - repository_ctx.file( - "BUILD.bazel", - executable = False, - content = """\ -package(default_visibility = ["//visibility:public"]) - -toolchain( - name = "cc-toolchain-{cpu}", - toolchain = "@{cc_toolchain_config}//:cc-compiler-{cpu}", - toolchain_type = "@rules_cc//cc:toolchain_type", - exec_compatible_with = {exec_constraints}, - target_compatible_with = {target_constraints}, -) - -toolchain( - name = "cc-toolchain-armeabi-v7a", - toolchain = "@{cc_toolchain_config}//:cc-compiler-armeabi-v7a", - toolchain_type = "@rules_cc//cc:toolchain_type", - exec_compatible_with = {exec_constraints}, - target_compatible_with = [ - "@platforms//cpu:arm", - "@platforms//os:android", - ], -) -""".format( - cc_toolchain_config = repository_ctx.attr.cc_toolchain_config, - cpu = cpu, - exec_constraints = exec_constraints, - target_constraints = target_constraints, - ), - ) - -_nixpkgs_cc_toolchain = repository_rule( - _nixpkgs_cc_toolchain_impl, - attrs = { - "cc_toolchain_config": attr.string(), - "exec_constraints": attr.string_list(), - "target_constraints": attr.string_list(), - }, -) - -def nixpkgs_cc_configure( - name = "local_config_cc", - attribute_path = "", - nix_file = None, - nix_file_content = "", - nix_file_deps = [], - repositories = {}, - repository = None, - nixopts = [], - quiet = False, - fail_not_supported = True, - exec_constraints = None, - target_constraints = None, - register = True): - """Use a CC toolchain from Nixpkgs. No-op if not a nix-based platform. - - By default, Bazel auto-configures a CC toolchain from commands (e.g. - `gcc`) available in the environment. To make builds more hermetic, use - this rule to specify explicitly which commands the toolchain should use. - - Specifically, it builds a Nix derivation that provides the CC toolchain - tools in the `bin/` path and constructs a CC toolchain that uses those - tools. Tools that aren't found are replaced by `${coreutils}/bin/false`. - You can inspect the resulting `@_info//:CC_TOOLCHAIN_INFO` to see - which tools were discovered. - - This rule depends on [`rules_cc`](https://github.com/bazelbuild/rules_cc). - - **Note:** - You need to configure `--crosstool_top=@//:toolchain` to activate - this toolchain. - - Args: - attribute_path: optional, string, Obtain the toolchain from the Nix expression under this attribute path. Requires `nix_file` or `nix_file_content`. - nix_file: optional, Label, Obtain the toolchain from the Nix expression defined in this file. Specify only one of `nix_file` or `nix_file_content`. - nix_file_content: optional, string, Obtain the toolchain from the given Nix expression. Specify only one of `nix_file` or `nix_file_content`. - nix_file_deps: optional, list of Label, Additional files that the Nix expression depends on. - repositories: dict of Label to string, Provides `` and other repositories. Specify one of `repositories` or `repository`. - repository: Label, Provides ``. Specify one of `repositories` or `repository`. - nixopts: optional, list of string, Extra flags to pass when calling Nix. Subject to location expansion, any instance of `$(location LABEL)` will be replaced by the path to the file ferenced by `LABEL` relative to the workspace root. - quiet: bool, Whether to hide `nix-build` output. - fail_not_supported: bool, Whether to fail if `nix-build` is not available. - exec_constraints: Constraints for the execution platform. - target_constraints: Constraints for the target platform. - register: bool, enabled by default, Whether to register (with `register_toolchains`) the generated toolchain and install it as the default cc_toolchain. - """ - - nixopts = list(nixopts) - nix_file_deps = list(nix_file_deps) - - nix_expr = None - if nix_file and nix_file_content: - fail("Cannot specify both 'nix_file' and 'nix_file_content'.") - elif nix_file: - nix_expr = "import $(location {})".format(nix_file) - nix_file_deps.append(nix_file) - elif nix_file_content: - nix_expr = nix_file_content - - if attribute_path and nix_expr == None: - fail("'attribute_path' requires one of 'nix_file' or 'nix_file_content'", "attribute_path") - elif attribute_path: - nixopts.extend([ - "--argstr", - "ccType", - "ccTypeAttribute", - "--argstr", - "ccAttrPath", - attribute_path, - "--arg", - "ccAttrSet", - nix_expr, - ]) - elif nix_expr: - nixopts.extend([ - "--argstr", - "ccType", - "ccTypeExpression", - "--arg", - "ccExpr", - nix_expr, - ]) - else: - nixopts.extend([ - "--argstr", - "ccType", - "ccTypeDefault", - ]) - - # Invoke `toolchains/cc.nix` which generates `CC_TOOLCHAIN_INFO`. - nixpkgs_package( - name = "{}_info".format(name), - nix_file = "@io_tweag_rules_nixpkgs//nixpkgs:toolchains/cc.nix", - nix_file_deps = nix_file_deps, - build_file_content = "exports_files(['CC_TOOLCHAIN_INFO'])", - repositories = repositories, - repository = repository, - nixopts = nixopts, - quiet = quiet, - fail_not_supported = fail_not_supported, - ) - - # Generate the `cc_toolchain_config` workspace. - _nixpkgs_cc_toolchain_config( - name = "{}".format(name), - cc_toolchain_info = "@{}_info//:CC_TOOLCHAIN_INFO".format(name), - fail_not_supported = fail_not_supported, - ) - - # Generate the `cc_toolchain` workspace. - if (exec_constraints == None) != (target_constraints == None): - fail("Both exec_constraints and target_constraints need to be provided or none of them.") - _nixpkgs_cc_toolchain( - name = "{}_toolchains".format(name), - cc_toolchain_config = name, - exec_constraints = exec_constraints, - target_constraints = target_constraints, - ) - - if register: - maybe( - native.bind, - name = "cc_toolchain", - actual = "@{}//:toolchain".format(name), - ) - native.register_toolchains("@{}_toolchains//:all".format(name)) - -def _readlink(repository_ctx, path): - return repository_ctx.path(path).realpath +nixpkgs_cc_configure = _nixpkgs_cc_configure def nixpkgs_cc_autoconf_impl(repository_ctx): cpu_value = get_cpu_value(repository_ctx) @@ -445,7 +61,7 @@ def nixpkgs_cc_autoconf_impl(repository_ctx): # the Bazel autoconfiguration with the tools we found. bin_contents = find_children(repository_ctx, workspace_root + "/bin") overriden_tools = { - tool: _readlink(repository_ctx, entry) + tool: repository_ctx.path(entry).realpath for entry in bin_contents for tool in [entry.rpartition("/")[-1]] # Compute basename } diff --git a/nixpkgs/toolchains/foreign_cc.bzl b/nixpkgs/toolchains/foreign_cc.bzl index fd262bfb1..7f1a61298 100644 --- a/nixpkgs/toolchains/foreign_cc.bzl +++ b/nixpkgs/toolchains/foreign_cc.bzl @@ -1,114 +1,3 @@ -load("//nixpkgs:nixpkgs.bzl", "nixpkgs_package") -load("//core:util.bzl", "ensure_constraints") +load("//toolchaings/cc:foreign_cc.bzl", _nixpkgs_foreign_cc_configure = "nixpkgs_foreign_cc_configure") -_foreign_cc_nix_build = """ -load("@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl", "native_tool_toolchain") - -filegroup( - name = "data", - srcs = glob(["bin/**"]), -) -native_tool_toolchain( - name = "cmake_nix_impl", - path = "bin/cmake", - target = ":data", -) -native_tool_toolchain( - name = "make_nix_impl", - path = "bin/make", - target = ":data", -) -native_tool_toolchain( - name = "ninja_nix_impl", - path = "bin/ninja", - target = ":data", -) -""" - -_foreign_cc_nix_toolchain = """ -toolchain( - name = "cmake_nix_toolchain", - toolchain = "@{toolchain_repo}//:cmake_nix_impl", - toolchain_type = "@rules_foreign_cc//toolchains:cmake_toolchain", - exec_compatible_with = {exec_constraints}, - target_compatible_with = {target_constraints}, -) -toolchain( - name = "make_nix_toolchain", - toolchain = "@{toolchain_repo}//:make_nix_impl", - toolchain_type = "@rules_foreign_cc//toolchains:make_toolchain", - exec_compatible_with = {exec_constraints}, - target_compatible_with = {target_constraints}, -) -toolchain( - name = "ninja_nix_toolchain", - toolchain = "@{toolchain_repo}//:ninja_nix_impl", - toolchain_type = "@rules_foreign_cc//toolchains:ninja_toolchain", - exec_compatible_with = {exec_constraints}, - target_compatible_with = {target_constraints}, -) -""" - -def _nixpkgs_foreign_cc_toolchain_impl(repository_ctx): - exec_constraints, target_constraints = ensure_constraints(repository_ctx) - repository_ctx.file( - "BUILD.bazel", - executable = False, - content = _foreign_cc_nix_toolchain.format( - toolchain_repo = repository_ctx.attr.toolchain_repo, - exec_constraints = exec_constraints, - target_constraints = target_constraints, - ), - ) - -_nixpkgs_foreign_cc_toolchain = repository_rule( - _nixpkgs_foreign_cc_toolchain_impl, - attrs = { - "toolchain_repo": attr.string(), - "exec_constraints": attr.string_list(), - "target_constraints": attr.string_list(), - }, -) - -def nixpkgs_foreign_cc_configure( - name = "nixpkgs_foreign_cc", - repository = None, - repositories = {}, - nix_file = None, - nix_file_deps = None, - nix_file_content = None, - nixopts = [], - fail_not_supported = True, - quiet = False, - exec_constraints = None, - target_constraints = None): - if not nix_file and not nix_file_content: - nix_file_content = """ - with import { config = {}; overlays = []; }; buildEnv { - name = "bazel-foreign-cc-toolchain"; - paths = [ cmake gnumake ninja glibc ]; - } - """ - nixpkgs_package( - name = name, - repository = repository, - repositories = repositories, - nix_file = nix_file, - nix_file_deps = nix_file_deps, - nix_file_content = nix_file_content, - build_file_content = _foreign_cc_nix_build, - nixopts = nixopts, - fail_not_supported = fail_not_supported, - quiet = quiet, - ) - _nixpkgs_foreign_cc_toolchain( - name = name + "_toolchain", - toolchain_repo = name, - exec_constraints = exec_constraints, - target_constraints = target_constraints, - ) - native.register_toolchains( - str(Label("@{}_toolchain//:cmake_nix_toolchain".format(name))), - str(Label("@{}_toolchain//:make_nix_toolchain".format(name))), - str(Label("@{}_toolchain//:ninja_nix_toolchain".format(name))), - ) +nixpkgs_foreign_cc_configure = _nixpkgs_foreign_cc_configure diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 089d41a85..d69d8482e 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -142,6 +142,7 @@ sh_test( data = [ "//nixpkgs:srcs", "//tests/invalid_nixpkgs_package:srcs", + "//toolchains/cc:cc.bzl", "@nix-unstable//:bin", ] + select({ "@platforms//os:linux": ["@busybox_static//:bin"], diff --git a/toolchains/cc/BUILD.bazel b/toolchains/cc/BUILD.bazel new file mode 100644 index 000000000..4d2e72b45 --- /dev/null +++ b/toolchains/cc/BUILD.bazel @@ -0,0 +1,6 @@ +package(default_visibility = ["//visibility:public"]) + +exports_files([ + "cc.bzl", + "foreign_cc.bzl", +]) diff --git a/toolchains/cc/MODULE.bazel b/toolchains/cc/MODULE.bazel new file mode 100644 index 000000000..a6dc3e21c --- /dev/null +++ b/toolchains/cc/MODULE.bazel @@ -0,0 +1,6 @@ +module( + name = "rules_nixpkgs_python", + version = "0.8.1", +) + +bazel_dep(name = "rules_nixpkgs_core", version = "0.8.1") diff --git a/toolchains/cc/cc.bzl b/toolchains/cc/cc.bzl new file mode 100644 index 000000000..d81cddd16 --- /dev/null +++ b/toolchains/cc/cc.bzl @@ -0,0 +1,398 @@ +"""Rules for importing a C++ toolchain from Nixpkgs. +""" + +load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") +load( + "@bazel_tools//tools/cpp:lib_cc_configure.bzl", + "get_cpu_value", + "get_starlark_list", + "write_builtin_include_directory_paths", +) +load("@bazel_skylib//lib:sets.bzl", "sets") +load("@bazel_skylib//lib:versions.bzl", "versions") +load("@rules_nixpkgs_core//:nixpkgs.bzl", "nixpkgs_package") +load( + "@rules_nixpkgs_core//:util.bzl", + "ensure_constraints", + "execute_or_fail", +) + +def _parse_cc_toolchain_info(content, filename): + """Parses the `CC_TOOLCHAIN_INFO` file generated by Nix. + + Attrs: + content: string, The content of the `CC_TOOLCHAIN_INFO` file. + filename: string, The path to the `CC_TOOLCHAIN_INFO` file, used for error reporting. + + Returns: + struct, The substitutions for `@bazel_tools//tools/cpp:BUILD.tpl`. + """ + + # Parse the content of CC_TOOLCHAIN_INFO. + # + # Each line has the form + # + # :::... + info = {} + for line in content.splitlines(): + fields = line.split(":") + if len(fields) == 0: + fail( + "Malformed CC_TOOLCHAIN_INFO '{}': Empty line encountered.".format(filename), + "cc_toolchain_info", + ) + info[fields[0]] = fields[1:] + + # Validate the keys in CC_TOOLCHAIN_INFO. + expected_keys = sets.make([ + "TOOL_NAMES", + "TOOL_PATHS", + "CXX_BUILTIN_INCLUDE_DIRECTORIES", + "COMPILE_FLAGS", + "CXX_FLAGS", + "LINK_FLAGS", + "LINK_LIBS", + "OPT_COMPILE_FLAGS", + "OPT_LINK_FLAGS", + "UNFILTERED_COMPILE_FLAGS", + "DBG_COMPILE_FLAGS", + "COVERAGE_COMPILE_FLAGS", + "COVERAGE_LINK_FLAGS", + "SUPPORTS_START_END_LIB", + "IS_CLANG", + ]) + actual_keys = sets.make(info.keys()) + missing_keys = sets.difference(expected_keys, actual_keys) + unexpected_keys = sets.difference(actual_keys, expected_keys) + if sets.length(missing_keys) > 0: + fail( + "Malformed CC_TOOLCHAIN_INFO '{}': Missing entries '{}'.".format( + filename, + "', '".join(sets.to_list(missing_keys)), + ), + "cc_toolchain_info", + ) + if sets.length(unexpected_keys) > 0: + fail( + "Malformed CC_TOOLCHAIN_INFO '{}': Unexpected entries '{}'.".format( + filename, + "', '".join(sets.to_list(unexpected_keys)), + ), + "cc_toolchain_info", + ) + + return struct( + tool_paths = { + tool: path + for (tool, path) in zip(info["TOOL_NAMES"], info["TOOL_PATHS"]) + }, + cxx_builtin_include_directories = info["CXX_BUILTIN_INCLUDE_DIRECTORIES"], + compile_flags = info["COMPILE_FLAGS"], + cxx_flags = info["CXX_FLAGS"], + link_flags = info["LINK_FLAGS"], + link_libs = info["LINK_LIBS"], + opt_compile_flags = info["OPT_COMPILE_FLAGS"], + opt_link_flags = info["OPT_LINK_FLAGS"], + unfiltered_compile_flags = info["UNFILTERED_COMPILE_FLAGS"], + dbg_compile_flags = info["DBG_COMPILE_FLAGS"], + coverage_compile_flags = info["COVERAGE_COMPILE_FLAGS"], + coverage_link_flags = info["COVERAGE_LINK_FLAGS"], + supports_start_end_lib = info["SUPPORTS_START_END_LIB"] == ["True"], + is_clang = info["IS_CLANG"] == ["True"], + ) + +def _nixpkgs_cc_toolchain_config_impl(repository_ctx): + cpu_value = get_cpu_value(repository_ctx) + darwin = cpu_value == "darwin" + + cc_toolchain_info_file = repository_ctx.path(repository_ctx.attr.cc_toolchain_info) + if not cc_toolchain_info_file.exists and not repository_ctx.attr.fail_not_supported: + return + info = _parse_cc_toolchain_info( + repository_ctx.read(cc_toolchain_info_file), + cc_toolchain_info_file, + ) + + # Generate the cc_toolchain workspace following the example from + # `@bazel_tools//tools/cpp:unix_cc_configure.bzl`. + # Uses the corresponding templates from `@bazel_tools` as well, see the + # private attributes of the `_nixpkgs_cc_toolchain_config` rule. + repository_ctx.symlink( + repository_ctx.path(repository_ctx.attr._unix_cc_toolchain_config), + "cc_toolchain_config.bzl", + ) + repository_ctx.symlink( + repository_ctx.path(repository_ctx.attr._armeabi_cc_toolchain_config), + "armeabi_cc_toolchain_config.bzl", + ) + + # A module map is required for clang starting from Bazel version 3.3.0. + # https://github.com/bazelbuild/bazel/commit/8b9f74649512ee17ac52815468bf3d7e5e71c9fa + needs_module_map = info.is_clang and versions.is_at_least("3.3.0", versions.get()) + if needs_module_map: + generate_system_module_map = [ + repository_ctx.path(repository_ctx.attr._generate_system_module_map), + ] + repository_ctx.file( + "module.modulemap", + execute_or_fail( + repository_ctx, + generate_system_module_map + info.cxx_builtin_include_directories, + "Failed to generate system module map.", + ).stdout.strip(), + executable = False, + ) + cc_wrapper_src = ( + repository_ctx.attr._osx_cc_wrapper if darwin else repository_ctx.attr._linux_cc_wrapper + ) + repository_ctx.template( + "cc_wrapper.sh", + repository_ctx.path(cc_wrapper_src), + { + "%{cc}": info.tool_paths["gcc"], + "%{env}": "", + }, + ) + if darwin: + info.tool_paths["gcc"] = "cc_wrapper.sh" + info.tool_paths["ar"] = "/usr/bin/libtool" + write_builtin_include_directory_paths( + repository_ctx, + info.tool_paths["gcc"], + info.cxx_builtin_include_directories, + ) + repository_ctx.template( + "BUILD.bazel", + repository_ctx.path(repository_ctx.attr._build), + { + "%{cc_toolchain_identifier}": "local", + "%{name}": cpu_value, + "%{modulemap}": ("\":module.modulemap\"" if needs_module_map else "None"), + "%{supports_param_files}": "0" if darwin else "1", + "%{cc_compiler_deps}": get_starlark_list( + [":builtin_include_directory_paths"] + ( + [":cc_wrapper"] if darwin else [] + ), + ), + "%{compiler}": "compiler", + "%{abi_version}": "local", + "%{abi_libc_version}": "local", + "%{host_system_name}": "local", + "%{target_libc}": "macosx" if darwin else "local", + "%{target_cpu}": cpu_value, + "%{target_system_name}": "local", + "%{tool_paths}": ",\n ".join( + ['"%s": "%s"' % (k, v) for (k, v) in info.tool_paths.items()], + ), + "%{cxx_builtin_include_directories}": get_starlark_list(info.cxx_builtin_include_directories), + "%{compile_flags}": get_starlark_list(info.compile_flags), + "%{cxx_flags}": get_starlark_list(info.cxx_flags), + "%{link_flags}": get_starlark_list(info.link_flags), + "%{link_libs}": get_starlark_list(info.link_libs), + "%{opt_compile_flags}": get_starlark_list(info.opt_compile_flags), + "%{opt_link_flags}": get_starlark_list(info.opt_link_flags), + "%{unfiltered_compile_flags}": get_starlark_list(info.unfiltered_compile_flags), + "%{dbg_compile_flags}": get_starlark_list(info.dbg_compile_flags), + "%{coverage_compile_flags}": get_starlark_list(info.coverage_compile_flags), + "%{coverage_link_flags}": get_starlark_list(info.coverage_link_flags), + "%{supports_start_end_lib}": repr(info.supports_start_end_lib), + }, + ) + +_nixpkgs_cc_toolchain_config = repository_rule( + _nixpkgs_cc_toolchain_config_impl, + attrs = { + "cc_toolchain_info": attr.label(), + "fail_not_supported": attr.bool(), + "_unix_cc_toolchain_config": attr.label( + default = Label("@bazel_tools//tools/cpp:unix_cc_toolchain_config.bzl"), + ), + "_armeabi_cc_toolchain_config": attr.label( + default = Label("@bazel_tools//tools/cpp:armeabi_cc_toolchain_config.bzl"), + ), + "_generate_system_module_map": attr.label( + default = Label("@bazel_tools//tools/cpp:generate_system_module_map.sh"), + ), + "_osx_cc_wrapper": attr.label( + default = Label("@bazel_tools//tools/cpp:osx_cc_wrapper.sh.tpl"), + ), + "_linux_cc_wrapper": attr.label( + default = Label("@bazel_tools//tools/cpp:linux_cc_wrapper.sh.tpl"), + ), + "_build": attr.label( + default = Label("@bazel_tools//tools/cpp:BUILD.tpl"), + ), + }, +) + +def _nixpkgs_cc_toolchain_impl(repository_ctx): + cpu = get_cpu_value(repository_ctx) + exec_constraints, target_constraints = ensure_constraints(repository_ctx) + + repository_ctx.file( + "BUILD.bazel", + executable = False, + content = """\ +package(default_visibility = ["//visibility:public"]) + +toolchain( + name = "cc-toolchain-{cpu}", + toolchain = "@{cc_toolchain_config}//:cc-compiler-{cpu}", + toolchain_type = "@rules_cc//cc:toolchain_type", + exec_compatible_with = {exec_constraints}, + target_compatible_with = {target_constraints}, +) + +toolchain( + name = "cc-toolchain-armeabi-v7a", + toolchain = "@{cc_toolchain_config}//:cc-compiler-armeabi-v7a", + toolchain_type = "@rules_cc//cc:toolchain_type", + exec_compatible_with = {exec_constraints}, + target_compatible_with = [ + "@platforms//cpu:arm", + "@platforms//os:android", + ], +) +""".format( + cc_toolchain_config = repository_ctx.attr.cc_toolchain_config, + cpu = cpu, + exec_constraints = exec_constraints, + target_constraints = target_constraints, + ), + ) + +_nixpkgs_cc_toolchain = repository_rule( + _nixpkgs_cc_toolchain_impl, + attrs = { + "cc_toolchain_config": attr.string(), + "exec_constraints": attr.string_list(), + "target_constraints": attr.string_list(), + }, +) + +def nixpkgs_cc_configure( + name = "local_config_cc", + attribute_path = "", + nix_file = None, + nix_file_content = "", + nix_file_deps = [], + repositories = {}, + repository = None, + nixopts = [], + quiet = False, + fail_not_supported = True, + exec_constraints = None, + target_constraints = None, + register = True): + """Use a CC toolchain from Nixpkgs. No-op if not a nix-based platform. + + By default, Bazel auto-configures a CC toolchain from commands (e.g. + `gcc`) available in the environment. To make builds more hermetic, use + this rule to specify explicitly which commands the toolchain should use. + + Specifically, it builds a Nix derivation that provides the CC toolchain + tools in the `bin/` path and constructs a CC toolchain that uses those + tools. Tools that aren't found are replaced by `${coreutils}/bin/false`. + You can inspect the resulting `@_info//:CC_TOOLCHAIN_INFO` to see + which tools were discovered. + + This rule depends on [`rules_cc`](https://github.com/bazelbuild/rules_cc). + + **Note:** + You need to configure `--crosstool_top=@//:toolchain` to activate + this toolchain. + + Args: + attribute_path: optional, string, Obtain the toolchain from the Nix expression under this attribute path. Requires `nix_file` or `nix_file_content`. + nix_file: optional, Label, Obtain the toolchain from the Nix expression defined in this file. Specify only one of `nix_file` or `nix_file_content`. + nix_file_content: optional, string, Obtain the toolchain from the given Nix expression. Specify only one of `nix_file` or `nix_file_content`. + nix_file_deps: optional, list of Label, Additional files that the Nix expression depends on. + repositories: dict of Label to string, Provides `` and other repositories. Specify one of `repositories` or `repository`. + repository: Label, Provides ``. Specify one of `repositories` or `repository`. + nixopts: optional, list of string, Extra flags to pass when calling Nix. Subject to location expansion, any instance of `$(location LABEL)` will be replaced by the path to the file ferenced by `LABEL` relative to the workspace root. + quiet: bool, Whether to hide `nix-build` output. + fail_not_supported: bool, Whether to fail if `nix-build` is not available. + exec_constraints: Constraints for the execution platform. + target_constraints: Constraints for the target platform. + register: bool, enabled by default, Whether to register (with `register_toolchains`) the generated toolchain and install it as the default cc_toolchain. + """ + + nixopts = list(nixopts) + nix_file_deps = list(nix_file_deps) + + nix_expr = None + if nix_file and nix_file_content: + fail("Cannot specify both 'nix_file' and 'nix_file_content'.") + elif nix_file: + nix_expr = "import $(location {})".format(nix_file) + nix_file_deps.append(nix_file) + elif nix_file_content: + nix_expr = nix_file_content + + if attribute_path and nix_expr == None: + fail("'attribute_path' requires one of 'nix_file' or 'nix_file_content'", "attribute_path") + elif attribute_path: + nixopts.extend([ + "--argstr", + "ccType", + "ccTypeAttribute", + "--argstr", + "ccAttrPath", + attribute_path, + "--arg", + "ccAttrSet", + nix_expr, + ]) + elif nix_expr: + nixopts.extend([ + "--argstr", + "ccType", + "ccTypeExpression", + "--arg", + "ccExpr", + nix_expr, + ]) + else: + nixopts.extend([ + "--argstr", + "ccType", + "ccTypeDefault", + ]) + + # Invoke `cc.nix` which generates `CC_TOOLCHAIN_INFO`. + nixpkgs_package( + name = "{}_info".format(name), + nix_file = "//toolchains/cc:cc.nix", + nix_file_deps = nix_file_deps, + build_file_content = "exports_files(['CC_TOOLCHAIN_INFO'])", + repositories = repositories, + repository = repository, + nixopts = nixopts, + quiet = quiet, + fail_not_supported = fail_not_supported, + ) + + # Generate the `cc_toolchain_config` workspace. + _nixpkgs_cc_toolchain_config( + name = "{}".format(name), + cc_toolchain_info = "@{}_info//:CC_TOOLCHAIN_INFO".format(name), + fail_not_supported = fail_not_supported, + ) + + # Generate the `cc_toolchain` workspace. + if (exec_constraints == None) != (target_constraints == None): + fail("Both exec_constraints and target_constraints need to be provided or none of them.") + _nixpkgs_cc_toolchain( + name = "{}_toolchains".format(name), + cc_toolchain_config = name, + exec_constraints = exec_constraints, + target_constraints = target_constraints, + ) + + if register: + maybe( + native.bind, + name = "cc_toolchain", + actual = "@{}//:toolchain".format(name), + ) + native.register_toolchains("@{}_toolchains//:all".format(name)) diff --git a/nixpkgs/toolchains/cc.nix b/toolchains/cc/cc.nix similarity index 100% rename from nixpkgs/toolchains/cc.nix rename to toolchains/cc/cc.nix diff --git a/toolchains/cc/foreign_cc.bzl b/toolchains/cc/foreign_cc.bzl new file mode 100644 index 000000000..bdec71ced --- /dev/null +++ b/toolchains/cc/foreign_cc.bzl @@ -0,0 +1,114 @@ +load("@rules_nixpkgs_core//:nixpkgs.bzl", "nixpkgs_package") +load("@rules_nixpkgs_core//:util.bzl", "ensure_constraints") + +_foreign_cc_nix_build = """ +load("@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl", "native_tool_toolchain") + +filegroup( + name = "data", + srcs = glob(["bin/**"]), +) +native_tool_toolchain( + name = "cmake_nix_impl", + path = "bin/cmake", + target = ":data", +) +native_tool_toolchain( + name = "make_nix_impl", + path = "bin/make", + target = ":data", +) +native_tool_toolchain( + name = "ninja_nix_impl", + path = "bin/ninja", + target = ":data", +) +""" + +_foreign_cc_nix_toolchain = """ +toolchain( + name = "cmake_nix_toolchain", + toolchain = "@{toolchain_repo}//:cmake_nix_impl", + toolchain_type = "@rules_foreign_cc//toolchains:cmake_toolchain", + exec_compatible_with = {exec_constraints}, + target_compatible_with = {target_constraints}, +) +toolchain( + name = "make_nix_toolchain", + toolchain = "@{toolchain_repo}//:make_nix_impl", + toolchain_type = "@rules_foreign_cc//toolchains:make_toolchain", + exec_compatible_with = {exec_constraints}, + target_compatible_with = {target_constraints}, +) +toolchain( + name = "ninja_nix_toolchain", + toolchain = "@{toolchain_repo}//:ninja_nix_impl", + toolchain_type = "@rules_foreign_cc//toolchains:ninja_toolchain", + exec_compatible_with = {exec_constraints}, + target_compatible_with = {target_constraints}, +) +""" + +def _nixpkgs_foreign_cc_toolchain_impl(repository_ctx): + exec_constraints, target_constraints = ensure_constraints(repository_ctx) + repository_ctx.file( + "BUILD.bazel", + executable = False, + content = _foreign_cc_nix_toolchain.format( + toolchain_repo = repository_ctx.attr.toolchain_repo, + exec_constraints = exec_constraints, + target_constraints = target_constraints, + ), + ) + +_nixpkgs_foreign_cc_toolchain = repository_rule( + _nixpkgs_foreign_cc_toolchain_impl, + attrs = { + "toolchain_repo": attr.string(), + "exec_constraints": attr.string_list(), + "target_constraints": attr.string_list(), + }, +) + +def nixpkgs_foreign_cc_configure( + name = "nixpkgs_foreign_cc", + repository = None, + repositories = {}, + nix_file = None, + nix_file_deps = None, + nix_file_content = None, + nixopts = [], + fail_not_supported = True, + quiet = False, + exec_constraints = None, + target_constraints = None): + if not nix_file and not nix_file_content: + nix_file_content = """ + with import { config = {}; overlays = []; }; buildEnv { + name = "bazel-foreign-cc-toolchain"; + paths = [ cmake gnumake ninja glibc ]; + } + """ + nixpkgs_package( + name = name, + repository = repository, + repositories = repositories, + nix_file = nix_file, + nix_file_deps = nix_file_deps, + nix_file_content = nix_file_content, + build_file_content = _foreign_cc_nix_build, + nixopts = nixopts, + fail_not_supported = fail_not_supported, + quiet = quiet, + ) + _nixpkgs_foreign_cc_toolchain( + name = name + "_toolchain", + toolchain_repo = name, + exec_constraints = exec_constraints, + target_constraints = target_constraints, + ) + native.register_toolchains( + str(Label("@{}_toolchain//:cmake_nix_toolchain".format(name))), + str(Label("@{}_toolchain//:make_nix_toolchain".format(name))), + str(Label("@{}_toolchain//:ninja_nix_toolchain".format(name))), + )