diff --git a/d/private/rules/binary.bzl b/d/private/rules/binary.bzl index 3b64c8e..3e0c0e8 100644 --- a/d/private/rules/binary.bzl +++ b/d/private/rules/binary.bzl @@ -8,7 +8,8 @@ load("//d/private/rules:link.bzl", "link_action") def _d_binary_impl(ctx): """Implementation of d_binary rule.""" d_info = compilation_action(ctx, target_type = TARGET_TYPE.BINARY) - output = link_action(ctx, d_info) + link_result = link_action(ctx, d_info) + output = link_result.executable env_with_expansions = { k: expand_variables(ctx, ctx.expand_location(v, ctx.files.data), [output], "env") for k, v in ctx.attr.env.items() @@ -16,6 +17,7 @@ def _d_binary_impl(ctx): return [ DefaultInfo( executable = output, + files = depset([output] + link_result.additional_outputs), runfiles = ctx.runfiles(files = ctx.files.data), ), RunEnvironmentInfo(environment = env_with_expansions), diff --git a/d/private/rules/compile.bzl b/d/private/rules/compile.bzl index 8d0568c..b6653d0 100644 --- a/d/private/rules/compile.bzl +++ b/d/private/rules/compile.bzl @@ -71,6 +71,15 @@ runnable_attrs = dicts.add( "fat_lto": attr.string(default = "auto", values = ["auto", "on", "off"], doc = "Whether to use fat LTO."), "linker_script": attr.label(allow_single_file = True, doc = "Linker script to be used for the binary."), "link_with_d": attr.string(default = "auto", values = ["auto", "on", "off"], doc = "Whether to use the D compiler for linking."), + "additional_linker_inputs": attr.label_list( + allow_files = True, + default = [], + doc = "Additional inputs made visible to the link action (matches cc_library.additional_linker_inputs).", + ), + "link_output_dirs": attr.string_list( + default = [], + doc = "Names of additional TreeArtifact directory outputs the link action will produce. The link action declares them, surfaces them via DefaultInfo.files, and (cc_common.link path only) passes them to cc_common.link's additional_outputs so the linker — or a wrapper invoked through it — can populate them.", + ), "_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)", @@ -513,19 +522,27 @@ def compilation_action(ctx, target_type = TARGET_TYPE.LIBRARY, cycle_breaker = F bc_output = compilation_result.bc_object bc_library_to_link = compilation_result.bc_library_to_link + # Expand $(location ...) / $(execpath ...) / $(rootpath ...) against the + # rule's additional_linker_inputs (matching cc_library.linkopts semantics). + # Done here so the expanded form propagates through both linker_input.user_link_flags + # and DInfo.linker_flags, avoiding double-add in link.bzl. + expanded_linkopts = [ + ctx.expand_location(opt, targets = getattr(ctx.attr, "additional_linker_inputs", [])) + for opt in ctx.attr.linkopts + ] linker_input = None if library_to_link: linker_input = cc_common.create_linker_input( owner = ctx.label, libraries = depset(direct = [library_to_link] if library_to_link else None), - user_link_flags = ctx.attr.linkopts, + user_link_flags = expanded_linkopts, ) bc_linker_input = linker_input if bc_library_to_link: bc_linker_input = cc_common.create_linker_input( owner = ctx.label, libraries = depset(direct = [bc_library_to_link] if bc_library_to_link else None), - user_link_flags = ctx.attr.linkopts, + user_link_flags = expanded_linkopts, ) linking_context = cc_common.create_linking_context( linker_inputs = depset( @@ -558,7 +575,7 @@ def compilation_action(ctx, target_type = TARGET_TYPE.LIBRARY, cycle_breaker = F ), linking_context = linking_context, # Already includes all deps internally linker_flags = depset( - ctx.attr.linkopts, + expanded_linkopts, transitive = [d.linker_flags for d in d_deps], # Only regular deps ), source_map = export_source_map, diff --git a/d/private/rules/link.bzl b/d/private/rules/link.bzl index 0e05535..d98018b 100644 --- a/d/private/rules/link.bzl +++ b/d/private/rules/link.bzl @@ -16,13 +16,24 @@ def link_action(ctx, d_info): ctx: The rule context. d_info: The DInfo provider containing the linking context. Returns: - A File for the linked binary. + A struct with: + executable: File for the linked binary. + additional_outputs: list of TreeArtifact File objects for the + directories declared via the `link_output_dirs` attribute + (empty if none requested). """ + additional_outputs = [ + ctx.actions.declare_directory(name) + for name in getattr(ctx.attr, "link_output_dirs", []) + ] toolchain = ctx.toolchains["//d:toolchain_type"].d_toolchain_info link_with_d = resolve_tristate_flag(ctx.attr.link_with_d, toolchain.link_with_d) fat_lto = resolve_tristate_flag(ctx.attr.fat_lto, toolchain.fat_lto) if link_with_d: - return link_with_d_action(ctx, d_info, fat_lto) + return struct( + executable = link_with_d_action(ctx, d_info, fat_lto, additional_outputs), + additional_outputs = additional_outputs, + ) druntime = None mode = ctx.var["COMPILATION_MODE"] if fat_lto: @@ -63,7 +74,8 @@ def link_action(ctx, d_info): if toolchain.linker_flags_per_mode and compilation_mode in toolchain.linker_flags_per_mode: user_link_flags.extend(toolchain.linker_flags_per_mode[compilation_mode]) - user_link_flags.extend(ctx.attr.linkopts) + # ctx.attr.linkopts is propagated via d_info.linker_flags (with location + # expansion already applied in compile.bzl). Adding it here would double-add. user_link_flags.extend(d_info.linker_flags.to_list()) additional_inputs = [] @@ -77,13 +89,20 @@ def link_action(ctx, d_info): additional_inputs.append(dynamic_symbols) user_link_flags.append("-Wl,--dynamic-list=%s" % dynamic_symbols.path) - return cc_common.link( - name = ctx.label.name, - actions = ctx.actions, - feature_configuration = cc_linker_info.feature_configuration, - cc_toolchain = cc_linker_info.cc_toolchain, - compilation_outputs = compilation_outputs, - linking_contexts = linking_contexts, - user_link_flags = user_link_flags, - additional_inputs = additional_inputs, - ).executable + for t in getattr(ctx.attr, "additional_linker_inputs", []): + additional_inputs.extend(t.files.to_list()) + + return struct( + executable = cc_common.link( + name = ctx.label.name, + actions = ctx.actions, + feature_configuration = cc_linker_info.feature_configuration, + cc_toolchain = cc_linker_info.cc_toolchain, + compilation_outputs = compilation_outputs, + linking_contexts = linking_contexts, + user_link_flags = user_link_flags, + additional_inputs = additional_inputs, + additional_outputs = additional_outputs, + ).executable, + additional_outputs = additional_outputs, + ) diff --git a/d/private/rules/link_with_d.bzl b/d/private/rules/link_with_d.bzl index 1941360..4c9857a 100644 --- a/d/private/rules/link_with_d.bzl +++ b/d/private/rules/link_with_d.bzl @@ -6,13 +6,17 @@ Action supporting old style linking using the D compiler. load("//d/private/rules:cc_toolchain.bzl", "find_cc_toolchain_for_linking") load("//d/private/rules:utils.bzl", "binary_name", "get_os") -def link_with_d_action(ctx, d_info, fat_lto): +def link_with_d_action(ctx, d_info, fat_lto, additional_outputs = []): """Action supporting old style linking using the D compiler. Args: ctx: The rule context. d_info: The DInfo provider containing the linking context. fat_lto: Whether to use fat LTO. + additional_outputs: extra File outputs (typically TreeArtifacts) to + declare on the link action so a wrapper or post-link tooling can + populate them. They are added to the action's outputs as-is — LDC + does not see them. Returns: A File for the linked binary. """ @@ -87,10 +91,10 @@ def link_with_d_action(ctx, d_info, fat_lto): if get_os(ctx) != "windows": # DMD on Windows doesn't support -Xcc= args.add_all(cc_linker_info.cc_linking_options, format_each = "-Xcc=%s") - inputs = depset(direct = [object] + libraries + unpacked_lib_dirs) + inputs = depset(direct = [object] + libraries + unpacked_lib_dirs + ctx.files.additional_linker_inputs) ctx.actions.run( inputs = inputs, - outputs = [output], + outputs = [output] + additional_outputs, executable = toolchain.d_compiler[DefaultInfo].files_to_run, tools = [cc_linker_info.cc_toolchain.all_files], arguments = [args], diff --git a/d/private/rules/test.bzl b/d/private/rules/test.bzl index b635f59..4d3cf95 100644 --- a/d/private/rules/test.bzl +++ b/d/private/rules/test.bzl @@ -9,7 +9,8 @@ load("//d/private/rules:link.bzl", "link_action") def _d_test_impl(ctx): """Implementation of d_test rule.""" d_info = compilation_action(ctx, target_type = TARGET_TYPE.TEST) - output = link_action(ctx, d_info) + link_result = link_action(ctx, d_info) + output = link_result.executable env_with_expansions = { k: expand_variables(ctx, ctx.expand_location(v, ctx.files.data), [output], "env") for k, v in ctx.attr.env.items() @@ -17,6 +18,7 @@ def _d_test_impl(ctx): return [ DefaultInfo( executable = output, + files = depset([output] + link_result.additional_outputs), runfiles = ctx.runfiles(files = ctx.files.data), ), RunEnvironmentInfo(environment = env_with_expansions),