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
17 changes: 3 additions & 14 deletions extensions/prost/private/prost.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ load(
"can_build_metadata",
"can_use_metadata_for_pipelining",
"generate_output_diagnostics",
"metadata_output_path",
)
load("//:providers.bzl", "ProstProtoInfo")
load(":prost_transform.bzl", "ProstTransformInfo")
Expand Down Expand Up @@ -176,26 +177,14 @@ def _compile_rust(
extension = ".rlib",
)

rmeta_name = "{prefix}{name}-{lib_hash}{extension}".format(
prefix = "lib",
name = crate_name,
lib_hash = output_hash,
extension = ".rmeta",
)

lib = ctx.actions.declare_file(lib_name)
rmeta = None
rustc_rmeta_output = None
metadata_supports_pipelining = False

if can_build_metadata(toolchain, ctx, "rlib"):
rmeta_name = "{prefix}{name}-{lib_hash}{extension}".format(
prefix = "lib",
name = crate_name,
lib_hash = output_hash,
extension = ".rmeta",
)
rmeta = ctx.actions.declare_file(rmeta_name)
rmeta_rel_path = metadata_output_path(toolchain, lib_name)
rmeta = ctx.actions.declare_file(rmeta_rel_path, sibling = lib)
rustc_rmeta_output = generate_output_diagnostics(ctx, rmeta)
metadata_supports_pipelining = can_use_metadata_for_pipelining(toolchain, "rlib")

Expand Down
2 changes: 1 addition & 1 deletion rust/private/clippy.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ def rust_clippy_action(ctx, clippy_executable, process_wrapper, crate_info, conf
out_dir = out_dir,
build_env_files = build_env_files,
build_flags_files = build_flags_files,
emit = ["dep-info", "metadata"],
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we removing dep-info?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly because it's not necessary - Nothing in rules_rust uses the outputs

emit = ["metadata"],
skip_expanding_rustc_env = True,
use_json_output = bool(clippy_diagnostics_file),
error_format = error_format,
Expand Down
9 changes: 5 additions & 4 deletions rust/private/rust.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ load(
"generate_output_diagnostics",
"get_edition",
"get_import_macro_deps",
"metadata_output_path",
"transform_deps",
"transform_sources",
)
Expand Down Expand Up @@ -206,7 +207,7 @@ def _rust_library_common(ctx, crate_type):
disable_pipelining = getattr(ctx.attr, "disable_pipelining", False),
):
rust_metadata = ctx.actions.declare_file(
paths.replace_extension(rust_lib_name, ".rmeta"),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the .rlib and _meta.rlib files are in the same directory, how will the compiler know which one to pick when it encounters a -Ldependency=blaze-bin/rlib/containing/directory/? I think this won't necessarily be an issue with sandboxed and remote execution, which may only have the _meta.rlib file present, but in local non-sandboxed builds I can see how this can pose a problem.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, you're right. I ran into this while playing with an experimental mode to allow incremental builds with local execution & I've pushed a fix.

metadata_output_path(toolchain, rust_lib_name),
sibling = rust_lib,
)
rustc_rmeta_output = generate_output_diagnostics(ctx, rust_metadata)
Expand Down Expand Up @@ -279,7 +280,7 @@ def _rust_binary_impl(ctx):
rustc_rmeta_output = None
if can_build_metadata(toolchain, ctx, ctx.attr.crate_type):
rust_metadata = ctx.actions.declare_file(
paths.replace_extension("lib" + crate_name, ".rmeta"),
metadata_output_path(toolchain, "lib" + crate_name),
sibling = output,
)
rustc_rmeta_output = generate_output_diagnostics(ctx, rust_metadata)
Expand Down Expand Up @@ -381,7 +382,7 @@ def _rust_test_impl(ctx):
rustc_rmeta_output = None
if can_build_metadata(toolchain, ctx, crate_type):
rust_metadata = ctx.actions.declare_file(
paths.replace_extension("lib" + crate_name, ".rmeta"),
metadata_output_path(toolchain, "lib" + crate_name),
sibling = output,
)
rustc_rmeta_output = generate_output_diagnostics(ctx, rust_metadata)
Expand Down Expand Up @@ -451,7 +452,7 @@ def _rust_test_impl(ctx):
rustc_rmeta_output = None
if can_build_metadata(toolchain, ctx, crate_type):
rust_metadata = ctx.actions.declare_file(
paths.replace_extension("lib" + crate_name, ".rmeta"),
metadata_output_path(toolchain, "lib" + crate_name),
sibling = output,
)
rustc_rmeta_output = generate_output_diagnostics(ctx, rust_metadata)
Expand Down
171 changes: 154 additions & 17 deletions rust/private/rustc.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -922,7 +922,7 @@ def construct_arguments(
out_dir,
build_env_files,
build_flags_files,
emit = ["dep-info", "link"],
emit = ["link"],
force_all_deps_direct = False,
add_flags_for_binary = False,
include_link_flags = True,
Expand All @@ -933,6 +933,7 @@ def construct_arguments(
force_depend_on_objects = False,
skip_expanding_rustc_env = False,
require_explicit_unstable_features = False,
inject_allow_features_guardrail = False,
error_format = None):
"""Builds an Args object containing common rustc flags

Expand Down Expand Up @@ -968,6 +969,7 @@ def construct_arguments(
force_depend_on_objects (bool): Force using `.rlib` object files instead of metadata (`.rmeta`) files even if they are available.
skip_expanding_rustc_env (bool): Whether to skip expanding CrateInfo.rustc_env_attr
require_explicit_unstable_features (bool): Whether to require all unstable features to be explicitly opted in to using `-Zallow-features=...`.
inject_allow_features_guardrail (bool): When True, inject `-Zallow-features=` alongside `-Zno-codegen` to prevent silent unstable-feature enablement via RUSTC_BOOTSTRAP=1. Disabled on nightly toolchains (where unstable features are already allowed by default) and when the user manages bootstrap/allow-features themselves; gated in rustc_compile_action.
error_format (str, optional): Error format to pass to the `--error-format` command line argument. If set to None, uses the "_error_format" entry in `attr`.

Returns:
Expand Down Expand Up @@ -1070,13 +1072,32 @@ def construct_arguments(
error_format = "json"

if build_metadata:
# Configure process_wrapper to terminate rustc when metadata are emitted
process_wrapper_flags.add("--rustc-quit-on-rmeta", "true")
if toolchain._incompatible_use_unstable_no_codegen_for_pipelining:
# RUSTC_BOOTSTRAP=1 is set on the action env in rustc_compile_action,
# required for -Zno-codegen on stable/beta rustc. -Zallow-features=
# (empty list) blocks all #![feature(...)] attributes to prevent the
# bootstrap env from silently enabling unstable language features in
# user code. Both are gated on inject_allow_features_guardrail, which
# is False on nightly (unstable features already allowed) and when
# the user manages bootstrap/allow-features themselves.
rustc_flags.add("-Zno-codegen")
if inject_allow_features_guardrail:
rustc_flags.add("-Zallow-features=")
else:
process_wrapper_flags.add("--rustc-quit-on-rmeta", "true")
if crate_info.rustc_rmeta_output:
process_wrapper_flags.add("--output-file", crate_info.rustc_rmeta_output.path)
elif crate_info.rustc_output:
process_wrapper_flags.add("--output-file", crate_info.rustc_output.path)

if not build_metadata and toolchain._incompatible_use_unstable_no_codegen_for_pipelining and bool(crate_info.metadata):
# Mirror the metadata action's -Zallow-features= so the bootstrap env
# does not silently enable unstable language features on the full
# compile. Gated on inject_allow_features_guardrail (computed in
# rustc_compile_action via _user_manages_bootstrap).
if inject_allow_features_guardrail:
rustc_flags.add("-Zallow-features=")

rustc_flags.add(error_format, format = "--error-format=%s")

# Mangle symbols to disambiguate crates with the same name. Used for
Expand Down Expand Up @@ -1105,8 +1126,17 @@ def construct_arguments(
rustc_flags.add("--remap-path-prefix=${{output_base}}={}".format(remap_path_prefix))

emit_without_paths = []
redirect_link_to_metadata = (
build_metadata and
crate_info.metadata != None and
toolchain._incompatible_use_unstable_no_codegen_for_pipelining
)
for kind in emit:
if kind == "link" and crate_info.type == "bin" and crate_info.output != None:
if kind == "link" and redirect_link_to_metadata:
# -Zno-codegen --emit=link writes lib<name>.rlib by default, which would
# collide with the full action's output; redirect to the hollow path.
rustc_flags.add(crate_info.metadata, format = "--emit=link=%s")
elif kind == "link" and crate_info.type == "bin" and crate_info.output != None:
rustc_flags.add(crate_info.output, format = "--emit=link=%s")
else:
emit_without_paths.append(kind)
Expand Down Expand Up @@ -1193,7 +1223,13 @@ def construct_arguments(
use_metadata = _depend_on_metadata(crate_info, force_depend_on_objects)

# These always need to be added, even if not linking this crate.
add_crate_link_flags(rustc_flags, dep_info, force_all_deps_direct, use_metadata)
add_crate_link_flags(
rustc_flags,
dep_info,
force_all_deps_direct,
use_metadata,
metadata_in_separate_dir = toolchain._incompatible_use_unstable_no_codegen_for_pipelining,
)

needs_extern_proc_macro_flag = _is_proc_macro(crate_info) and crate_info.edition != "2015"
if needs_extern_proc_macro_flag:
Expand Down Expand Up @@ -1274,6 +1310,44 @@ def construct_arguments(

return args, env

def _flag_is_allow_features(argv, index):
"""Returns True if the flag at argv[index] is a -Zallow-features flag.

Handles three forms rustc accepts:
- "-Zallow-features=..."
- "-Zallow-features" followed by the value in the next token
- "-Z" followed by "allow-features[=...]" in the next token
"""
flag = argv[index]
if flag == "-Zallow-features" or flag.startswith("-Zallow-features="):
return True
if flag == "-Z" and index + 1 < len(argv):
next_tok = argv[index + 1]
if next_tok == "allow-features" or next_tok.startswith("allow-features="):
return True
return False

def _user_manages_bootstrap(ctx, attr, env_before_inject, collected_extra_flags):
"""Return True if the user has set RUSTC_BOOTSTRAP or -Zallow-features themselves.

When True, rules_rust skips auto-injecting both RUSTC_BOOTSTRAP=1 and
-Zallow-features= for this target, treating the user's configuration as
authoritative.
"""
if "RUSTC_BOOTSTRAP" in env_before_inject:
return True

attr_flags = getattr(attr, "rustc_flags", []) or []
for i in range(len(attr_flags)):
if _flag_is_allow_features(attr_flags, i):
return True

for i in range(len(collected_extra_flags)):
if _flag_is_allow_features(collected_extra_flags, i):
return True

return False

def collect_extra_rustc_flags(ctx, toolchain, crate_root, crate_type):
"""Gather all 'extra' rustc flags from the target's attributes and toolchain.

Expand Down Expand Up @@ -1413,18 +1487,21 @@ def rustc_compile_action(
experimental_use_cc_common_link = experimental_use_cc_common_link,
)

use_no_codegen_pipelining = toolchain._incompatible_use_unstable_no_codegen_for_pipelining

# The types of rustc outputs to emit.
# If we build metadata, we need to keep the command line of the two invocations
# (rlib and rmeta) as similar as possible, otherwise rustc rejects the rmeta as
# a candidate.
# Because of that we need to add emit=metadata to both the rlib and rmeta invocation.
#
# When cc_common linking is enabled, emit a `.o` file, which is later
# passed to the cc_common.link action.
emit = ["dep-info", "link"]
if build_metadata:
emit.append("metadata")
if use_no_codegen_pipelining:
# Metadata is emitted by a separate -Zno-codegen action; the full action
# only produces the linked rlib.
emit = ["link"]
else:
# Legacy pipelining: rustc matches metadata against full-compile command
# lines, so both actions must request `--emit=...,metadata`.
emit = ["dep-info", "link"]
if build_metadata:
emit.append("metadata")
if experimental_use_cc_common_link:
# Pass a `.o` to the cc_common.link action.
emit = ["obj"]

# Determine whether to pass `--require-explicit-unstable-features true` to the process wrapper:
Expand All @@ -1437,6 +1514,35 @@ def rustc_compile_action(
elif ctx.attr.require_explicit_unstable_features == -1:
require_explicit_unstable_features = toolchain.require_explicit_unstable_features

# Build the env snapshot used to detect a user-managed RUSTC_BOOTSTRAP before
# any rules_rust-injected values are added. Matches the merge order in the
# main env assembly below.
env_before_inject = dict(ctx.configuration.default_shell_env)
env_before_inject.update(crate_info.rustc_env)
if hasattr(ctx.attr, "_extra_rustc_env") and not is_exec_configuration(ctx):
env_before_inject.update(
ctx.attr._extra_rustc_env[ExtraRustcEnvInfo].extra_rustc_env,
)

collected_extra_flags = collect_extra_rustc_flags(
ctx,
toolchain,
crate_info.root,
crate_info.type,
)

user_manages_bootstrap = _user_manages_bootstrap(
ctx,
attr,
env_before_inject,
collected_extra_flags,
)
inject_allow_features_guardrail = (
use_no_codegen_pipelining and
not user_manages_bootstrap and
toolchain.channel != "nightly"
)

args, env_from_args = construct_arguments(
ctx = ctx,
attr = attr,
Expand All @@ -1460,18 +1566,20 @@ def rustc_compile_action(
use_json_output = bool(build_metadata) or bool(rustc_output) or bool(rustc_rmeta_output),
skip_expanding_rustc_env = skip_expanding_rustc_env,
require_explicit_unstable_features = require_explicit_unstable_features,
inject_allow_features_guardrail = inject_allow_features_guardrail,
)

args_metadata = None
if build_metadata:
metadata_emit = ["link"] if use_no_codegen_pipelining else emit
args_metadata, _ = construct_arguments(
ctx = ctx,
attr = attr,
file = ctx.file,
toolchain = toolchain,
tool_path = toolchain.rustc.path,
cc_toolchain = cc_toolchain,
emit = emit,
emit = metadata_emit,
feature_configuration = feature_configuration,
crate_info = crate_info,
dep_info = dep_info,
Expand All @@ -1487,13 +1595,22 @@ def rustc_compile_action(
use_json_output = True,
build_metadata = True,
require_explicit_unstable_features = require_explicit_unstable_features,
inject_allow_features_guardrail = inject_allow_features_guardrail,
)

env = dict(ctx.configuration.default_shell_env)

# this is the final list of env vars
env.update(env_from_args)

if build_metadata and use_no_codegen_pipelining and inject_allow_features_guardrail:
# RUSTC_BOOTSTRAP=1 is required for -Zno-codegen on stable/beta rustc, and
# must be set on both the metadata and full actions for SVH compatibility
# (since RUSTC_BOOTSTRAP affects the crate hash). Skipped on nightly
# toolchains (where -Zno-codegen works without bootstrap) and when the user
# manages RUSTC_BOOTSTRAP or -Zallow-features themselves (escape hatch).
env["RUSTC_BOOTSTRAP"] = "1"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change makes pipelining work only for users that are fine with relying on unstable features. I think that's fine, however I think we should go through the incompatible change process, where we add an --incompatible_use_unstable_no_codegen_for_pipelining build_setting, and gate the changes in this PR behind it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reasonable


if hasattr(attr, "version") and attr.version != "0.0.0":
formatted_version = " v{}".format(attr.version)
else:
Expand Down Expand Up @@ -2166,7 +2283,7 @@ def _get_dir_names(files):
dirs[f.dirname] = None
return dirs.keys()

def add_crate_link_flags(args, dep_info, force_all_deps_direct = False, use_metadata = False):
def add_crate_link_flags(args, dep_info, force_all_deps_direct = False, use_metadata = False, metadata_in_separate_dir = False):
"""Adds link flags to an Args object reference

Args:
Expand All @@ -2175,6 +2292,9 @@ def add_crate_link_flags(args, dep_info, force_all_deps_direct = False, use_meta
force_all_deps_direct (bool, optional): Whether to pass the transitive rlibs with --extern
to the commandline as opposed to -L.
use_metadata (bool, optional): Build command line arguments using metadata for crates that provide it.
metadata_in_separate_dir (bool, optional): If True, add an extra `-Ldependency` pointing at
the `_meta/` subdir where hollow rlibs live under `-Zno-codegen` pipelining. Under legacy
pipelining the `.rmeta` is a sibling of the full rlib, so no extra flag is needed.
"""

direct_crates = depset(
Expand All @@ -2194,6 +2314,14 @@ def add_crate_link_flags(args, dep_info, force_all_deps_direct = False, use_meta
format_each = "-Ldependency=%s",
)

if use_metadata and metadata_in_separate_dir:
args.add_all(
dep_info.transitive_crates,
map_each = _get_crate_metadata_dirname,
uniquify = True,
format_each = "-Ldependency=%s",
)

def _crate_to_link_flag_metadata(crate):
"""A helper macro used by `add_crate_link_flags` for adding crate link flags to a Arg object

Expand Down Expand Up @@ -2247,6 +2375,15 @@ def _get_crate_dirname(crate):
"""
return crate.output.dirname

def _get_crate_metadata_dirname(crate):
"""Returns the `_meta/` subdir containing `crate`'s hollow `_meta.rlib`, or None.

Only meaningful under `-Zno-codegen` pipelining; callers gate on that flag.
"""
if crate.metadata and crate.metadata_supports_pipelining and crate.metadata.dirname != crate.output.dirname:
return crate.metadata.dirname
return None

def portable_link_flags(
lib,
use_pic,
Expand Down
Loading