diff --git a/d/private/rules/BUILD.bazel b/d/private/rules/BUILD.bazel index 1ce446f..ba62490 100644 --- a/d/private/rules/BUILD.bazel +++ b/d/private/rules/BUILD.bazel @@ -15,6 +15,20 @@ bzl_library( deps = ["//d/private/rules:common"], ) +bzl_library( + name = "cc_toolchain", + srcs = ["cc_toolchain.bzl"], + visibility = [ + "//d:__subpackages__", + "//docs:__subpackages__", + ], + deps = [ + "@rules_cc//cc:action_names_bzl", + "@rules_cc//cc:find_cc_toolchain_bzl", + "@rules_cc//cc/common", + ], +) + bzl_library( name = "common", srcs = ["common.bzl"], @@ -24,6 +38,7 @@ bzl_library( ], deps = [ "//d/private:providers", + "//d/private/rules:cc_toolchain", "@bazel_lib//lib:expand_make_vars", "@bazel_skylib//lib:dicts", "@bazel_skylib//lib:paths", @@ -38,7 +53,10 @@ bzl_library( "//d:__subpackages__", "//docs:__subpackages__", ], - deps = ["//d/private/rules:common"], + deps = [ + "//d/private/rules:common", + "@rules_cc//cc:find_cc_toolchain_bzl", + ], ) bzl_library( @@ -48,5 +66,8 @@ bzl_library( "//d:__subpackages__", "//docs:__subpackages__", ], - deps = ["//d/private/rules:common"], + deps = [ + "//d/private/rules:common", + "@rules_cc//cc:find_cc_toolchain_bzl", + ], ) diff --git a/d/private/rules/binary.bzl b/d/private/rules/binary.bzl index 0d70fef..8ccd77c 100644 --- a/d/private/rules/binary.bzl +++ b/d/private/rules/binary.bzl @@ -1,5 +1,6 @@ """D test rule for compiling binaries.""" +load("@rules_cc//cc:find_cc_toolchain.bzl", "use_cc_toolchain") load("//d/private/rules:common.bzl", "TARGET_TYPE", "compilation_action", "runnable_attrs") def _d_binary_impl(ctx): @@ -9,6 +10,7 @@ def _d_binary_impl(ctx): d_binary = rule( implementation = _d_binary_impl, attrs = runnable_attrs, - toolchains = ["//d:toolchain_type"], + toolchains = ["//d:toolchain_type"] + use_cc_toolchain(), + fragments = ["cpp"], executable = True, ) diff --git a/d/private/rules/cc_toolchain.bzl b/d/private/rules/cc_toolchain.bzl new file mode 100644 index 0000000..93e8634 --- /dev/null +++ b/d/private/rules/cc_toolchain.bzl @@ -0,0 +1,95 @@ +""" +Helper functions to extract the C++ toolchain and linker options for linking. +""" + +load("@rules_cc//cc:action_names.bzl", "CPP_LINK_EXECUTABLE_ACTION_NAME") +load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain") +load("@rules_cc//cc/common:cc_common.bzl", "cc_common") + +_UNSUPPORTED_FEATURES = [ + # These toolchain features require special rule support and will thus break + # with D. + # Taken from rules_go + "thin_lto", + "module_maps", + "use_header_modules", + "fdo_instrument", + "fdo_optimize", + # This is a nonspecific unsupported feature which allows the authors of C++ + # toolchain to apply separate flags when compiling D code. + "rules_d_unsupported_feature", +] + +_LINKER_OPTIONS_DENYLIST = { + # Don't link C++ libraries + "-lstdc++": None, + "-lc++": None, + "-lc++abi": None, + # libm and libobjc are added by the D compiler already, so suppress them here, to avoid warnings + "-lm": None, + "-lobjc": None, + "-fobjc-link-runtime": None, + # --target is passed by the D compiler + "--target=": None, + # --target passed by the D compiler conflicts with -mmacosx-version-min set by cc_toolchain + "-mmacosx-version-min=": None, +} + +def _match_option(option, pattern): + if pattern.endswith("="): + return option.startswith(pattern) + else: + return option == pattern + +def _filter_options(options, denylist): + return [ + option + for option in options + if not any([_match_option(option, pattern) for pattern in denylist]) + ] + +def find_cc_toolchain_for_linking(ctx): + """ + Find the C++ toolchain and linker options for linking. + + Args: + ctx: The rule context + Returns: + A struct with the following fields: + - cc_toolchain: The C++ toolchain + - cc_compiler: The C/C++ compiler + - cc_linking_options: The linker options + - env: The environment variables to set for the linker + """ + cc_toolchain = find_cc_toolchain(ctx) + feature_configuration = cc_common.configure_features( + ctx = ctx, + cc_toolchain = cc_toolchain, + unsupported_features = _UNSUPPORTED_FEATURES, + ) + linker_variables = cc_common.create_link_variables( + feature_configuration = feature_configuration, + cc_toolchain = cc_toolchain, + is_linking_dynamic_library = False, + ) + cc_compiler = cc_common.get_tool_for_action( + feature_configuration = feature_configuration, + action_name = CPP_LINK_EXECUTABLE_ACTION_NAME, + ) + cc_linking_options = _filter_options(cc_common.get_memory_inefficient_command_line( + feature_configuration = feature_configuration, + action_name = CPP_LINK_EXECUTABLE_ACTION_NAME, + variables = linker_variables, + ), _LINKER_OPTIONS_DENYLIST) + env = cc_common.get_environment_variables( + feature_configuration = feature_configuration, + action_name = CPP_LINK_EXECUTABLE_ACTION_NAME, + variables = linker_variables, + ) + + return struct( + cc_toolchain = cc_toolchain, + cc_compiler = cc_compiler, + cc_linking_options = cc_linking_options, + env = env, + ) diff --git a/d/private/rules/common.bzl b/d/private/rules/common.bzl index 3605fe7..6d8cf7e 100644 --- a/d/private/rules/common.bzl +++ b/d/private/rules/common.bzl @@ -5,6 +5,7 @@ load("@bazel_skylib//lib:dicts.bzl", "dicts") load("@bazel_skylib//lib:paths.bzl", "paths") load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") load("//d/private:providers.bzl", "DInfo") +load("//d/private/rules:cc_toolchain.bzl", "find_cc_toolchain_for_linking") D_FILE_EXTENSIONS = [".d", ".di"] @@ -37,6 +38,10 @@ runnable_attrs = dicts.add( { "env": attr.string_dict(doc = "Environment variables for the binary at runtime. Subject of location and make variable expansion."), "data": attr.label_list(allow_files = True, doc = "List of files to be made available at runtime."), + "_cc_toolchain": attr.label( + default = "@rules_cc//cc:current_cc_toolchain", + doc = "Default CC toolchain, used for linking. Remove after https://github.com/bazelbuild/bazel/issues/7260 is flipped (and support for old Bazel version is not needed)", + ), }, ) @@ -142,6 +147,8 @@ def compilation_action(ctx, target_type = TARGET_TYPE.LIBRARY): args.add_all(toolchain.linker_flags) args.add_all(linker_flags.to_list(), format_each = "-L=%s") output = None + cc_toolchain = None + env = ctx.var if target_type in [TARGET_TYPE.BINARY, TARGET_TYPE.TEST]: for dep in d_deps: args.add_all(dep.libraries) @@ -150,6 +157,23 @@ def compilation_action(ctx, target_type = TARGET_TYPE.LIBRARY): args.add_all(["-main", "-unittest"]) output = ctx.actions.declare_file(_binary_name(ctx, ctx.label.name)) args.add(output, format = "-of=%s") + cc_linker_info = find_cc_toolchain_for_linking(ctx) + env = dict(cc_linker_info.env) + env.update({ + "CC": cc_linker_info.cc_compiler, # Have to use the env variable here, since DMD doesn't support -gcc= flag + # Ok, this is a bit weird. Local toolchain from rules_cc works fine if we don't set PATH here. + # But doesn't work if we set it to an empty string. + # OTOH the toolchain from toolchains_llvm doesn't work without setting PATH here. (Can't find the linker executable) + # Even though the cc_wrapper script adds "/usr/bin" to the PATH variable, + # it only works if the PATH is already in the environment. (I think they have to `export`) + # So toolchains_llvm works if we set PATH to "" but doesn't work if we don't set it at all. + # So, to get to a common ground, we set PATH to something generic. + "PATH": "/bin:/usr/bin:/usr/local/bin", + }) + if _get_os(ctx) != "windows": + # DMD doesn't support -Xcc on Windows + args.add_all(cc_linker_info.cc_linking_options, format_each = "-Xcc=%s") + cc_toolchain = cc_linker_info.cc_toolchain elif target_type == TARGET_TYPE.LIBRARY: args.add("-lib") output = ctx.actions.declare_file(_static_library_name(ctx, ctx.label.name)) @@ -169,11 +193,12 @@ def compilation_action(ctx, target_type = TARGET_TYPE.LIBRARY): ctx.actions.run( inputs = inputs, + tools = [cc_toolchain.all_files] if cc_toolchain else [], outputs = [output], executable = toolchain.d_compiler[DefaultInfo].files_to_run, arguments = [args], - env = ctx.var, - use_default_shell_env = target_type != TARGET_TYPE.LIBRARY, # True to make the linker work properly + env = env, + use_default_shell_env = False, mnemonic = "Dcompile", progress_message = "Compiling D %s %s" % (target_type, ctx.label.name), ) diff --git a/d/private/rules/test.bzl b/d/private/rules/test.bzl index d24b5fa..14d347f 100644 --- a/d/private/rules/test.bzl +++ b/d/private/rules/test.bzl @@ -1,5 +1,6 @@ """D test rule for compiling and running D unit tests.""" +load("@rules_cc//cc:find_cc_toolchain.bzl", "use_cc_toolchain") load("//d/private/rules:common.bzl", "TARGET_TYPE", "compilation_action", "runnable_attrs") def _d_test_impl(ctx): @@ -9,6 +10,7 @@ def _d_test_impl(ctx): d_test = rule( implementation = _d_test_impl, attrs = runnable_attrs, - toolchains = ["//d:toolchain_type"], + toolchains = ["//d:toolchain_type"] + use_cc_toolchain(), + fragments = ["cpp"], test = True, )