diff --git a/crate_universe/src/rendering.rs b/crate_universe/src/rendering.rs index a96e65b454..9aa394096b 100644 --- a/crate_universe/src/rendering.rs +++ b/crate_universe/src/rendering.rs @@ -798,6 +798,10 @@ impl Renderer { ), platforms, ), + // Enable configuration trimming for third-party crates to improve cache hit rates. + // This ensures that per_crate_rustc_flag settings (which don't affect third-party + // crates) don't cause unnecessary rebuilds. + skip_per_crate_rustc_flags: true, srcs: target.srcs.clone(), tags: { let mut tags = BTreeSet::from_iter(krate.common_attrs.tags.iter().cloned()); diff --git a/crate_universe/src/utils/starlark.rs b/crate_universe/src/utils/starlark.rs index 14ff5f8c23..a532110d25 100644 --- a/crate_universe/src/utils/starlark.rs +++ b/crate_universe/src/utils/starlark.rs @@ -210,6 +210,10 @@ pub(crate) struct CommonAttrs { pub(crate) rustc_env_files: SelectSet, #[serde(skip_serializing_if = "SelectList::is_empty")] pub(crate) rustc_flags: SelectList, + /// Trim per_crate_rustc_flag from configuration to improve cache hit rates. + /// This is set to true for third-party crates generated by crate_universe. + #[serde(skip_serializing_if = "std::ops::Not::not")] + pub(crate) skip_per_crate_rustc_flags: bool, pub(crate) srcs: Glob, #[serde(skip_serializing_if = "Set::is_empty")] pub(crate) tags: Set, diff --git a/rust/private/per_crate_flag_trim.bzl b/rust/private/per_crate_flag_trim.bzl new file mode 100644 index 0000000000..1c45581ab3 --- /dev/null +++ b/rust/private/per_crate_flag_trim.bzl @@ -0,0 +1,60 @@ +# Copyright 2024 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Transition to trim per_crate_rustc_flag for non-matching targets. + +This module provides a configuration trimming mechanism for the +`experimental_per_crate_rustc_flag` setting. When a target has +`skip_per_crate_rustc_flags = True`, this transition clears the setting, +putting the target back into a canonical configuration. + +This is useful for third-party crates (e.g., from crate_universe) that will +never match any per-crate flag filter. Without trimming, these crates would +be rebuilt unnecessarily when any per-crate flag is set, even though the +filter doesn't match them. + +Usage: + Third-party crate generators (like crate_universe) should set + `skip_per_crate_rustc_flags = True` on generated rust_library targets. +""" + +_PER_CRATE_FLAG_SETTING = "@rules_rust//rust/settings:experimental_per_crate_rustc_flag" + +def _per_crate_flag_trim_transition_impl(settings, attr): + """Clear per_crate_rustc_flag for targets marked to skip it. + + Args: + settings: A dict of current build settings. + attr: The attributes of the target being configured. + + Returns: + A dict with the per_crate_rustc_flag setting (cleared or preserved). + """ + # If this target is marked to skip per-crate flags, clear the setting + # to return it to a canonical configuration + if getattr(attr, "skip_per_crate_rustc_flags", False): + return { + _PER_CRATE_FLAG_SETTING: [], + } + + # Otherwise, keep the current value + return { + _PER_CRATE_FLAG_SETTING: settings[_PER_CRATE_FLAG_SETTING], + } + +per_crate_flag_trim_transition = transition( + implementation = _per_crate_flag_trim_transition_impl, + inputs = [_PER_CRATE_FLAG_SETTING], + outputs = [_PER_CRATE_FLAG_SETTING], +) diff --git a/rust/private/rust.bzl b/rust/private/rust.bzl index d50abbf180..4e271207f1 100644 --- a/rust/private/rust.bzl +++ b/rust/private/rust.bzl @@ -25,6 +25,10 @@ load( "CrateInfo", "LintsInfo", ) +load( + ":per_crate_flag_trim.bzl", + "per_crate_flag_trim_transition", +) load( ":rust_allocator_libraries.bzl", "RUSTC_ALLOCATOR_LIBRARIES_ATTRS", @@ -56,6 +60,9 @@ load( # TODO(marco): Separate each rule into its own file. +# Setting path for per_crate_rustc_flag, used in transition definitions +_PER_CRATE_FLAG_SETTING = "@rules_rust//rust/settings:experimental_per_crate_rustc_flag" + def _assert_no_deprecated_attributes(_ctx): """Forces a failure if any deprecated attributes were specified @@ -808,6 +815,17 @@ _COMMON_ATTRS = { doc = "Enable collection of cfg flags with results stored in CrateInfo.cfgs.", default = Label("//rust/settings:collect_cfgs"), ), + "skip_per_crate_rustc_flags": attr.bool( + doc = dedent("""\ + If True, the `experimental_per_crate_rustc_flag` setting is trimmed from this + target's configuration. This puts the target back into a canonical configuration, + improving cache hit rates for targets that would never match any per-crate filter. + + This attribute is primarily used by crate_universe for generated third-party crates. + First-party crates should leave this as False (the default). + """), + default = False, + ), } | RUSTC_ATTRS | RUSTC_ALLOCATOR_LIBRARIES_ATTRS _PLATFORM_ATTRS = { @@ -912,6 +930,7 @@ _RUST_TEST_ATTRS = { rust_library = rule( implementation = _rust_library_impl, provides = COMMON_PROVIDERS, + cfg = per_crate_flag_trim_transition, attrs = _COMMON_ATTRS | { "disable_pipelining": attr.bool( default = False, @@ -921,6 +940,9 @@ rust_library = rule( crates will instead use the `.rlib` file. """), ), + "_allowlist_function_transition": attr.label( + default = "@bazel_tools//tools/allowlists/function_transition_allowlist", + ), }, fragments = ["cpp"], toolchains = [ @@ -994,17 +1016,22 @@ rust_library = rule( ) def _rust_static_library_transition_impl(settings, attr): + # Trim per_crate_rustc_flag for third-party crates to improve cache hit rates + per_crate_flags = [] if getattr(attr, "skip_per_crate_rustc_flags", False) else settings[_PER_CRATE_FLAG_SETTING] return { "//command_line_option:platforms": str(attr.platform) if attr.platform else settings["//command_line_option:platforms"], + _PER_CRATE_FLAG_SETTING: per_crate_flags, } _rust_static_library_transition = transition( implementation = _rust_static_library_transition_impl, inputs = [ "//command_line_option:platforms", + _PER_CRATE_FLAG_SETTING, ], outputs = [ "//command_line_option:platforms", + _PER_CRATE_FLAG_SETTING, ], ) @@ -1035,17 +1062,22 @@ rust_static_library = rule( ) def _rust_shared_library_transition_impl(settings, attr): + # Trim per_crate_rustc_flag for third-party crates to improve cache hit rates + per_crate_flags = [] if getattr(attr, "skip_per_crate_rustc_flags", False) else settings[_PER_CRATE_FLAG_SETTING] return { "//command_line_option:platforms": str(attr.platform) if attr.platform else settings["//command_line_option:platforms"], + _PER_CRATE_FLAG_SETTING: per_crate_flags, } _rust_shared_library_transition = transition( implementation = _rust_shared_library_transition_impl, inputs = [ "//command_line_option:platforms", + _PER_CRATE_FLAG_SETTING, ], outputs = [ "//command_line_option:platforms", + _PER_CRATE_FLAG_SETTING, ], ) @@ -1090,6 +1122,7 @@ _proc_macro_dep_transition = transition( rust_proc_macro = rule( implementation = _rust_proc_macro_impl, provides = COMMON_PROVIDERS, + cfg = per_crate_flag_trim_transition, # Start by copying the common attributes, then override the `deps` attribute # to apply `_proc_macro_dep_transition`. To add this transition we additionally # need to declare `_allowlist_function_transition`, see @@ -1157,17 +1190,22 @@ _RUST_BINARY_ATTRS = { } | _EXPERIMENTAL_USE_CC_COMMON_LINK_ATTRS def _rust_binary_transition_impl(settings, attr): + # Trim per_crate_rustc_flag for third-party crates to improve cache hit rates + per_crate_flags = [] if getattr(attr, "skip_per_crate_rustc_flags", False) else settings[_PER_CRATE_FLAG_SETTING] return { "//command_line_option:platforms": str(attr.platform) if attr.platform else settings["//command_line_option:platforms"], + _PER_CRATE_FLAG_SETTING: per_crate_flags, } _rust_binary_transition = transition( implementation = _rust_binary_transition_impl, inputs = [ "//command_line_option:platforms", + _PER_CRATE_FLAG_SETTING, ], outputs = [ "//command_line_option:platforms", + _PER_CRATE_FLAG_SETTING, ], ) @@ -1404,17 +1442,22 @@ rust_test_without_process_wrapper_test = rule( ) def _rust_test_transition_impl(settings, attr): + # Trim per_crate_rustc_flag for third-party crates to improve cache hit rates + per_crate_flags = [] if getattr(attr, "skip_per_crate_rustc_flags", False) else settings[_PER_CRATE_FLAG_SETTING] return { "//command_line_option:platforms": str(attr.platform) if attr.platform else settings["//command_line_option:platforms"], + _PER_CRATE_FLAG_SETTING: per_crate_flags, } _rust_test_transition = transition( implementation = _rust_test_transition_impl, inputs = [ "//command_line_option:platforms", + _PER_CRATE_FLAG_SETTING, ], outputs = [ "//command_line_option:platforms", + _PER_CRATE_FLAG_SETTING, ], )