Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 81 additions & 38 deletions rust/private/rustc.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -884,9 +884,14 @@ def _should_add_oso_prefix(toolchain):
def _extract_allowed_unstable_features_from_flags(rust_flags, all_allowed_unstable_features):
other_flags = []
for flag in rust_flags:
if flag.startswith("-Zallow-features="):
all_allowed_unstable_features.extend(flag.removeprefix("-Zallow-features=").split(","))
if type(flag) == "string":
if flag.startswith("-Zallow-features="):
all_allowed_unstable_features.extend(flag.removeprefix("-Zallow-features=").split(","))
else:
other_flags.append(flag)
else:
# It's a tuple/list (format_string, File), or at least not a string.
# We assume it's not a -Zallow-features flag.
other_flags.append(flag)
return other_flags

Expand Down Expand Up @@ -952,21 +957,22 @@ def construct_arguments(
ambiguous_libs (dict): Ambiguous libs, see `_disambiguate_libs`
output_hash (str): The hashed path of the crate root
rust_flags (list or Args): Additional flags to pass to rustc. Accepts
either a plain `list[str]` (folded into the main `rustc_flags`
`Args` so flags intermix with the rest of the command line,
with any `-Zallow-features=` entries extracted and merged
with `unstable_rust_features_config`) or a `ctx.actions.args()`
`Args` object (returned on the `args` struct as
`extra_rustc_flags` and appended to `args.all` as a separate
either a plain `list[str | (str, File)]` (folded into the main
`rustc_flags` `Args` so flags intermix with the rest of the
command line, with any `-Zallow-features=` entries extracted and
merged with `unstable_rust_features_config`) or a
`ctx.actions.args()` `Args` object (returned on the `args` struct
as `extra_rustc_flags` and appended to `args.all` as a separate
entry, since `Args` cannot be merged with one another). The
`Args` form is opaque at analysis time, so any
`-Zallow-features=` it carries passes through to rustc
unchanged — callers that need it merged with
`unstable_rust_features_config` should keep using the
`list[str]` form. Use the `Args` form when the caller needs
`Args.add_all` features such as `map_each` (e.g. for
`File`-derived flags that must be rewritten by Bazel path
mapping).
`unstable_rust_features_config` should keep using the list form.
Use the `Args` form when the caller needs `Args.add_all` features
such as `map_each`. For individual `File`-derived flags that must
be rewritten by Bazel path mapping, they can be passed as
`(format_string, File)` tuples within the list form (e.g.
`("-Zsplit-dwarf-out-dir=%s", dwo_outputs)`).
out_dir (File, optional): The build script's output directory.
When provided, the directory is handed to `process_wrapper`
via an explicit `--out-dir <path>` arg sourced from a
Expand Down Expand Up @@ -1228,27 +1234,37 @@ def construct_arguments(
uniquify = True,
)

# `rust_flags` is either a plain `list[str]` or a `ctx.actions.args()`
# `Args` object. Lists are folded into the main `rustc_flags` `Args`
# here, with any `-Zallow-features=` entries extracted into
# `all_allowed_unstable_features` so they can be merged with
# `unstable_rust_features_config` and re-emitted as a single arg
# below. `Args` inputs cannot be merged with another `Args` and are
# opaque at analysis time, so we capture the caller's `Args` here
# and append it as a separate entry in `args.all` (after the
# main `rustc_flags` `Args`, consistent with the existing "later
# flags win" semantics). Any `-Zallow-features=` baked into an
# `Args` value passes through to rustc unchanged — callers that
# need it merged with `unstable_rust_features_config` should keep
# using the `list[str]` form.
# `rust_flags` is either a plain `list[str | (format_string, File)]` or a
# `ctx.actions.args()` `Args` object.
#
# - Lists are folded into the main `rustc_flags` `Args` here, with any
# `-Zallow-features=` entries extracted into
# `all_allowed_unstable_features` so they can be merged with
# `unstable_rust_features_config` and re-emitted as a single arg below.
#
# - `Args` inputs cannot be merged with another `Args` and are opaque at
# analysis time, so we capture the caller's `Args` here and append it as a
# separate entry in `args.all` (after the main `rustc_flags` `Args`,
# consistent with the existing "later flags win" semantics). Any
# `-Zallow-features=` baked into an `Args` value passes through to rustc
# unchanged — callers that need it merged with
# `unstable_rust_features_config` should keep using the list form.
rust_flags_args = None
if type(rust_flags) == "Args":
rust_flags_args = rust_flags
elif rust_flags:
rustc_flags.add_all(
_extract_allowed_unstable_features_from_flags(rust_flags, all_allowed_unstable_features),
map_each = map_flag,
)
for flag in _extract_allowed_unstable_features_from_flags(rust_flags, all_allowed_unstable_features):
if type(flag) in ["tuple", "list"] and len(flag) == 2:
rustc_flags.add_all(
[flag[1]],
format_each = flag[0],
expand_directories = False,
)
else:
if map_flag:
flag = map_flag(flag)
if flag != None:
rustc_flags.add(flag)

# Gather data path from crate_info since it is inherited from real crate for rust_doc and rust_test
# Deduplicate data paths due to https://github.com/bazelbuild/bazel/issues/14681
Expand Down Expand Up @@ -1682,6 +1698,22 @@ def rustc_compile_action(
elif ctx.attr.require_explicit_unstable_features == -1:
require_explicit_unstable_features = toolchain.require_explicit_unstable_features

use_split_debuginfo = (
feature_configuration and
cc_common.is_enabled(feature_configuration = feature_configuration, feature_name = "per_object_debug_info") and
ctx.fragments.cpp.fission_active_for_current_compilation_mode()
)
if use_split_debuginfo:
rust_flags = rust_flags + [
"--codegen=split-debuginfo=unpacked",
"--codegen=debuginfo=full",
]
fission_directory = crate_info.name + "_fission"
if output_hash:
fission_directory = fission_directory + "-" + output_hash
dwo_outputs = ctx.actions.declare_directory(fission_directory, sibling = crate_info.output)
rust_flags.append(("-Zsplit-dwarf-out-dir=%s", dwo_outputs))

args, env_from_args = construct_arguments(
ctx = ctx,
attr = attr,
Expand Down Expand Up @@ -1777,6 +1809,9 @@ def rustc_compile_action(
dsym_folder = ctx.actions.declare_directory(crate_info.output.basename + ".dSYM", sibling = crate_info.output)
action_outputs.append(dsym_folder)

if use_split_debuginfo:
action_outputs.append(dwo_outputs) # buildifier: disable=uninitialized

if ctx.executable._process_wrapper:
# Run as normal
ctx.actions.run(
Expand Down Expand Up @@ -1840,15 +1875,19 @@ def rustc_compile_action(
else:
fail("No process wrapper was defined for {}".format(ctx.label))

cco_args = {}
if experimental_use_cc_common_link:
# Wrap the main `.o` file into a compilation output suitable for
# cc_common.link. The main `.o` file is useful in both PIC and non-PIC
# modes.
compilation_outputs = cc_common.create_compilation_outputs(
objects = depset([output_o]),
pic_objects = depset([output_o]),
)

cco_args["objects"] = depset([output_o])
cco_args["pic_objects"] = depset([output_o])
if use_split_debuginfo:
cco_args["dwo_objects"] = depset([dwo_outputs]) # buildifier: disable=uninitialized
cco_args["pic_dwo_objects"] = depset([dwo_outputs]) # buildifier: disable=uninitialized
compilation_outputs = cc_common.create_compilation_outputs(**cco_args)
debug_context = cc_common.create_debug_context(compilation_outputs)
if experimental_use_cc_common_link:
malloc_library = ctx.attr._custom_malloc or ctx.attr.malloc

# Collect the linking contexts of the standard library and dependencies.
Expand Down Expand Up @@ -2021,7 +2060,7 @@ def rustc_compile_action(
else:
providers.extend([crate_info, dep_info])

providers += establish_cc_info(ctx, attr, crate_info, toolchain, cc_toolchain, feature_configuration, interface_library)
providers += establish_cc_info(ctx, attr, crate_info, toolchain, cc_toolchain, feature_configuration, interface_library, debug_context)

output_group_info = {}

Expand Down Expand Up @@ -2155,7 +2194,7 @@ def _add_codegen_units_flags(toolchain, emit, args):

args.add("-Ccodegen-units={}".format(toolchain._codegen_units))

def establish_cc_info(ctx, attr, crate_info, toolchain, cc_toolchain, feature_configuration, interface_library):
def establish_cc_info(ctx, attr, crate_info, toolchain, cc_toolchain, feature_configuration, interface_library, debug_context = None):
"""If the produced crate is suitable yield a CcInfo to allow for interop with cc rules

Args:
Expand All @@ -2166,6 +2205,7 @@ def establish_cc_info(ctx, attr, crate_info, toolchain, cc_toolchain, feature_co
cc_toolchain (CcToolchainInfo): The current `CcToolchainInfo`
feature_configuration (FeatureConfiguration): Feature configuration to be queried.
interface_library (File): Optional interface library for cdylib crates on Windows.
debug_context (CcDebugContextInfo): The current debug context.

Returns:
list: A list containing the `CcInfo` provider and optionally `AllocatorLibrariesImplInfo`
Expand Down Expand Up @@ -2234,7 +2274,10 @@ def establish_cc_info(ctx, attr, crate_info, toolchain, cc_toolchain, feature_co
)

cc_infos = [
CcInfo(linking_context = linking_context),
CcInfo(
linking_context = linking_context,
debug_context = debug_context,
),
toolchain.stdlib_linkflags,
]

Expand Down
151 changes: 151 additions & 0 deletions test/unit/debug_info/debug_info_analysis_test.bzl
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
"""Analysis tests for debug info in cdylib and bin targets."""

load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
load("//rust:defs.bzl", "rust_binary", "rust_shared_library", "rust_test")
load(
"//test/unit:common.bzl",
"assert_argv_contains",
"assert_argv_contains_not",
"assert_argv_contains_prefix",
"assert_argv_contains_prefix_not",
)

def _pdb_file_test_impl(ctx, expect_pdb_file):
env = analysistest.begin(ctx)
Expand Down Expand Up @@ -70,6 +78,112 @@ def _dsym_folder_test_impl(ctx):

dsym_folder_test = analysistest.make(_dsym_folder_test_impl)

def _fission_test_impl(ctx):
env = analysistest.begin(ctx)
target = analysistest.target_under_test(env)

action = None
for a in target.actions:
if a.mnemonic == "Rustc":
action = a
break
asserts.true(env, action != None, "Expected to find Rustc action")

assert_argv_contains(env, action, "--codegen=split-debuginfo=unpacked")
assert_argv_contains(env, action, "--codegen=debuginfo=full")
assert_argv_contains_prefix(env, action, "-Zsplit-dwarf-out-dir=")

# Verify that the fission directory is in the action outputs.
outputs = action.outputs.to_list()
fission_dir_prefix = target.label.name + "_fission"
found_fission_dir = False
for out in outputs:
if out.basename.startswith(fission_dir_prefix):
found_fission_dir = True
asserts.true(env, out.is_directory, "Expected fission output to be a directory")
break
asserts.true(env, found_fission_dir, "Expected to find fission directory in action outputs")

# Verify CcInfo contains the debug_context with the dwo files (if CcInfo is provided).
if CcInfo in target:
cc_info = target[CcInfo]
asserts.true(env, hasattr(cc_info, "_debug_context"), "Expected CcInfo to have _debug_context")
debug_context = cc_info._debug_context
asserts.true(env, debug_context != None, "Expected _debug_context to be not None")

dwo_files = debug_context.files.to_list()
asserts.equals(env, 1, len(dwo_files))
asserts.true(
env,
dwo_files[0].basename.startswith(fission_dir_prefix),
"Expected dwo file basename to start with %s, got %s" % (fission_dir_prefix, dwo_files[0].basename),
)

pic_dwo_files = debug_context.pic_files.to_list()
asserts.equals(env, 1, len(pic_dwo_files))
asserts.true(
env,
pic_dwo_files[0].basename.startswith(fission_dir_prefix),
"Expected pic dwo file basename to start with %s, got %s" % (fission_dir_prefix, pic_dwo_files[0].basename),
)

return analysistest.end(env)

fission_test = analysistest.make(
_fission_test_impl,
config_settings = {
"//command_line_option:features": ["per_object_debug_info"],
"//command_line_option:fission": ["yes"],
},
)

def _no_fission_test_impl(ctx):
env = analysistest.begin(ctx)
target = analysistest.target_under_test(env)

action = None
for a in target.actions:
if a.mnemonic == "Rustc":
action = a
break
asserts.true(env, action != None, "Expected to find Rustc action")

assert_argv_contains_not(env, action, "--codegen=split-debuginfo=unpacked")
assert_argv_contains_not(env, action, "--codegen=debuginfo=full")
assert_argv_contains_prefix_not(env, action, "-Zsplit-dwarf-out-dir=")

# Verify no fission directory in outputs.
outputs = action.outputs.to_list()
fission_dir_name = target.label.name + "_fission"
for out in outputs:
asserts.true(env, out.basename != fission_dir_name, "Did not expect fission directory in outputs")

# CcInfo's debug_context should not contain dwo files.
if CcInfo in target:
cc_info = target[CcInfo]
if hasattr(cc_info, "_debug_context") and cc_info._debug_context:
asserts.equals(env, 0, len(cc_info._debug_context.files.to_list()))
asserts.equals(env, 0, len(cc_info._debug_context.pic_files.to_list()))

return analysistest.end(env)

no_fission_test = analysistest.make(
_no_fission_test_impl,
config_settings = {
"//command_line_option:fission": ["no"],
},
)

# Fission requires `-Zsplit-dwarf-out-dir` which is a nightly-only flag.
# Even though these are analysis tests (which normally don't execute actions),
# `bazel coverage` forces compilation of the `target_under_test`. To prevent
# compilation failures on the stable channel during coverage runs, we restrict
# Fission tests to the nightly channel.
_FISSION_COMPATIBILITY = ["@platforms//os:linux"] + select({
"//rust/toolchain/channel:nightly": [],
"//conditions:default": ["@platforms//:incompatible"],
})

def debug_info_analysis_test_suite(name):
"""Analysis tests for debug info in cdylib and bin targets.

Expand Down Expand Up @@ -171,12 +285,49 @@ def debug_info_analysis_test_suite(name):
target_compatible_with = ["@platforms//os:macos"],
)

# Fission tests
fission_test(
name = "lib_fission_test",
target_under_test = ":mylib",
target_compatible_with = _FISSION_COMPATIBILITY,
)
no_fission_test(
name = "lib_no_fission_test",
target_under_test = ":mylib",
)

fission_test(
name = "bin_fission_test",
target_under_test = ":myrustbin",
target_compatible_with = _FISSION_COMPATIBILITY,
)
no_fission_test(
name = "bin_no_fission_test",
target_under_test = ":myrustbin",
)

fission_test(
name = "test_fission_test",
target_under_test = ":myrusttest",
target_compatible_with = _FISSION_COMPATIBILITY,
)
no_fission_test(
name = "test_no_fission_test",
target_under_test = ":myrusttest",
)

native.test_suite(
name = name,
tests = [
":lib_dsym_test",
":bin_dsym_test",
":test_dsym_test",
":lib_fission_test",
":lib_no_fission_test",
":bin_fission_test",
":bin_no_fission_test",
":test_fission_test",
":test_no_fission_test",
] + [
":lib_pdb_test_{}".format(compilation_mode)
for compilation_mode in pdb_file_tests
Expand Down
Loading