Support passing arbitrary extra flags to rustc (#566)
* Support passing arbitrary extra codegen args to rustc
* Update defs.bzl
* buildifier fixes
* Add doc string
* Regenerate documentation
* fix lint
* PR feedback
* Regenerate documentation
* Fix link for codegen
* Regenerate documentation
* Rename to extra_rustc_flags
* Regenerate documentation
* buildifier
* Better method to exclude from the exec configuration
* Regenerate documentation
* Refactor is_exec_configuration utility function
* buildifier
* Regenerate documentation
* Add test
* Revert extraneous changes
* Regenerate documentation
diff --git a/BUILD.bazel b/BUILD.bazel
index f9849b6..fa25a30 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -1,5 +1,5 @@
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
-load("//rust:rust.bzl", "error_format")
+load("//rust:rust.bzl", "error_format", "extra_rustc_flags")
exports_files(["LICENSE"])
@@ -18,6 +18,15 @@
visibility = ["//visibility:public"],
)
+# This setting may be used to pass extra options to rustc from the command line.
+# It applies across all targets whereas the rustc_flags option on targets applies only
+# to that target. This can be useful for passing build-wide options such as LTO.
+extra_rustc_flags(
+ name = "extra_rustc_flags",
+ build_setting_default = [],
+ visibility = ["//visibility:public"],
+)
+
# This setting is used by the clippy rules. See https://bazelbuild.github.io/rules_rust/rust_clippy.html
label_flag(
name = "clippy.toml",
diff --git a/docs/BUILD.bazel b/docs/BUILD.bazel
index 8142a7a..2c73545 100644
--- a/docs/BUILD.bazel
+++ b/docs/BUILD.bazel
@@ -59,6 +59,7 @@
"rust_benchmark",
"rust_test",
"rust_test_suite",
+ "extra_rustc_flags",
],
),
page(
diff --git a/docs/defs.md b/docs/defs.md
index 60ecff4..2941a29 100644
--- a/docs/defs.md
+++ b/docs/defs.md
@@ -9,6 +9,25 @@
* [rust_benchmark](#rust_benchmark)
* [rust_test](#rust_test)
* [rust_test_suite](#rust_test_suite)
+* [extra_rustc_flags](#extra_rustc_flags)
+
+<a id="#extra_rustc_flags"></a>
+
+## extra_rustc_flags
+
+<pre>
+extra_rustc_flags(<a href="#extra_rustc_flags-name">name</a>)
+</pre>
+
+Add additional rustc_flags from the command line with `--@rules_rust//:extra_rustc_flags`. This flag should only be used for flags that need to be applied across the entire build. For options that apply to individual crates, use the rustc_flags attribute on the individual crate's rule instead. NOTE: These flags are currently excluded from the exec configuration (proc-macros, cargo_build_script, etc).
+
+**ATTRIBUTES**
+
+
+| Name | Description | Type | Mandatory | Default |
+| :------------- | :------------- | :------------- | :------------- | :------------- |
+| <a id="extra_rustc_flags-name"></a>name | A unique name for this target. | <a href="https://bazel.build/docs/build-ref.html#name">Name</a> | required | |
+
<a id="#rust_benchmark"></a>
diff --git a/docs/flatten.md b/docs/flatten.md
index 51a1cb2..30f3bd7 100644
--- a/docs/flatten.md
+++ b/docs/flatten.md
@@ -7,6 +7,7 @@
* [cargo_build_script](#cargo_build_script)
* [crate](#crate)
* [crate_universe](#crate_universe)
+* [extra_rustc_flags](#extra_rustc_flags)
* [fail_when_enabled](#fail_when_enabled)
* [incompatible_flag](#incompatible_flag)
* [rust_analyzer](#rust_analyzer)
@@ -117,6 +118,24 @@
| <a id="crate_universe-version"></a>version | The version of cargo the resolver should use | String | optional | "1.54.0" |
+<a id="#extra_rustc_flags"></a>
+
+## extra_rustc_flags
+
+<pre>
+extra_rustc_flags(<a href="#extra_rustc_flags-name">name</a>)
+</pre>
+
+Add additional rustc_flags from the command line with `--@rules_rust//:extra_rustc_flags`. This flag should only be used for flags that need to be applied across the entire build. For options that apply to individual crates, use the rustc_flags attribute on the individual crate's rule instead. NOTE: These flags are currently excluded from the exec configuration (proc-macros, cargo_build_script, etc).
+
+**ATTRIBUTES**
+
+
+| Name | Description | Type | Mandatory | Default |
+| :------------- | :------------- | :------------- | :------------- | :------------- |
+| <a id="extra_rustc_flags-name"></a>name | A unique name for this target. | <a href="https://bazel.build/docs/build-ref.html#name">Name</a> | required | |
+
+
<a id="#fail_when_enabled"></a>
## fail_when_enabled
diff --git a/docs/symbols.bzl b/docs/symbols.bzl
index df65877..f174b27 100644
--- a/docs/symbols.bzl
+++ b/docs/symbols.bzl
@@ -41,6 +41,7 @@
)
load(
"@rules_rust//rust:defs.bzl",
+ _extra_rustc_flags = "extra_rustc_flags",
_rust_analyzer = "rust_analyzer",
_rust_analyzer_aspect = "rust_analyzer_aspect",
_rust_benchmark = "rust_benchmark",
@@ -141,6 +142,7 @@
rustfmt_aspect = _rustfmt_aspect
rustfmt_test = _rustfmt_test
+extra_rustc_flags = _extra_rustc_flags
incompatible_flag = _incompatible_flag
fail_when_enabled = _fail_when_enabled
diff --git a/rust/defs.bzl b/rust/defs.bzl
index 9563656..1692aba 100644
--- a/rust/defs.bzl
+++ b/rust/defs.bzl
@@ -43,6 +43,7 @@
load(
"//rust/private:rustc.bzl",
_error_format = "error_format",
+ _extra_rustc_flags = "extra_rustc_flags",
)
load(
"//rust/private:rustdoc.bzl",
@@ -97,6 +98,9 @@
error_format = _error_format
# See @rules_rust//rust/private:rustc.bzl for a complete description.
+extra_rustc_flags = _extra_rustc_flags
+# See @rules_rust//rust/private:rustc.bzl for a complete description.
+
rust_common = _rust_common
# See @rules_rust//rust/private:common.bzl for a complete description.
diff --git a/rust/private/clippy.bzl b/rust/private/clippy.bzl
index 0ece4fa..34ad861 100644
--- a/rust/private/clippy.bzl
+++ b/rust/private/clippy.bzl
@@ -162,6 +162,7 @@
doc = "The desired `--error-format` flags for clippy",
default = "//:error_format",
),
+ "_extra_rustc_flags": attr.label(default = "//:extra_rustc_flags"),
"_process_wrapper": attr.label(
doc = "A process wrapper for running clippy on all platforms",
default = Label("//util/process_wrapper"),
diff --git a/rust/private/rust.bzl b/rust/private/rust.bzl
index ad35712..d7d5a27 100644
--- a/rust/private/rust.bzl
+++ b/rust/private/rust.bzl
@@ -663,6 +663,7 @@
default = "@bazel_tools//tools/cpp:current_cc_toolchain",
),
"_error_format": attr.label(default = "//:error_format"),
+ "_extra_rustc_flags": attr.label(default = "//:extra_rustc_flags"),
"_process_wrapper": attr.label(
default = Label("//util/process_wrapper"),
executable = True,
diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl
index 7f5a8db..a37bf81 100644
--- a/rust/private/rustc.bzl
+++ b/rust/private/rustc.bzl
@@ -27,6 +27,7 @@
"find_cc_toolchain",
"get_lib_name",
"get_preferred_artifact",
+ "is_exec_configuration",
"make_static_lib_symlink",
"relativize",
)
@@ -57,6 +58,11 @@
fields = {"error_format": "(string) [" + ", ".join(_error_format_values) + "]"},
)
+ExtraRustcFlagsInfo = provider(
+ doc = "Pass each value as an additional flag to rustc invocations",
+ fields = {"extra_rustc_flags": "List[string] Extra flags to pass to rustc"},
+)
+
def _get_rustc_env(attr, toolchain):
"""Gathers rustc environment variables
@@ -519,6 +525,10 @@
# Set the SYSROOT to the directory of the rust_lib files passed to the toolchain
env["SYSROOT"] = paths.dirname(toolchain.rust_lib.files.to_list()[0].short_path)
+ # extra_rustc_flags apply to the target configuration, not the exec configuration.
+ if hasattr(ctx.attr, "_extra_rustc_flags") and is_exec_configuration(ctx):
+ rustc_flags.add_all(ctx.attr._extra_rustc_flags[ExtraRustcFlagsInfo].extra_rustc_flags)
+
# Create a struct which keeps the arguments separate so each may be tuned or
# replaced where necessary
args = struct(
@@ -1015,10 +1025,23 @@
error_format = rule(
doc = (
- "A helper rule for controlling the rustc " +
- "[--error-format](https://doc.rust-lang.org/rustc/command-line-arguments.html#option-error-format) " +
- "flag."
+ "Change the [--error-format](https://doc.rust-lang.org/rustc/command-line-arguments.html#option-error-format) " +
+ "flag from the command line with `--@rules_rust//:error_format`. See rustc documentation for valid values."
),
implementation = _error_format_impl,
build_setting = config.string(flag = True),
)
+
+def _extra_rustc_flags_impl(ctx):
+ return ExtraRustcFlagsInfo(extra_rustc_flags = ctx.build_setting_value)
+
+extra_rustc_flags = rule(
+ doc = (
+ "Add additional rustc_flags from the command line with `--@rules_rust//:extra_rustc_flags`. " +
+ "This flag should only be used for flags that need to be applied across the entire build. For options that " +
+ "apply to individual crates, use the rustc_flags attribute on the individual crate's rule instead. NOTE: " +
+ "These flags are currently excluded from the exec configuration (proc-macros, cargo_build_script, etc)."
+ ),
+ implementation = _extra_rustc_flags_impl,
+ build_setting = config.string_list(flag = True),
+)
diff --git a/rust/private/utils.bzl b/rust/private/utils.bzl
index e0591d3..d615c43 100644
--- a/rust/private/utils.bzl
+++ b/rust/private/utils.bzl
@@ -323,3 +323,19 @@
dot_a = actions.declare_file(basename + ".a", sibling = rlib_file)
actions.symlink(output = dot_a, target_file = rlib_file)
return dot_a
+
+def is_exec_configuration(ctx):
+ """Determine if a context is building for the exec configuration.
+
+ This is helpful when processing command line flags that should apply
+ to the target configuration but not the exec configuration.
+
+ Args:
+ ctx (ctx): The ctx object for the current target.
+
+ Returns:
+ True if the exec configuration is detected, False otherwise.
+ """
+
+ # TODO(djmarcin): Is there any better way to determine cfg=exec?
+ return ctx.genfiles_dir.path.find("-exec-") == -1
diff --git a/rust/rust.bzl b/rust/rust.bzl
index f66541e..a31cff5 100644
--- a/rust/rust.bzl
+++ b/rust/rust.bzl
@@ -17,6 +17,7 @@
load(
"//rust:defs.bzl",
_error_format = "error_format",
+ _extra_rustc_flags = "extra_rustc_flags",
_rust_analyzer = "rust_analyzer",
_rust_analyzer_aspect = "rust_analyzer_aspect",
_rust_benchmark = "rust_benchmark",
@@ -93,5 +94,8 @@
error_format = _error_format
# See @rules_rust//rust/private:rustc.bzl for a complete description.
+extra_rustc_flags = _extra_rustc_flags
+# See @rules_rust//rust/private:rustc.bzl for a complete description.
+
rust_common = _rust_common
# See @rules_rust//rust/private:common.bzl for a complete description.
diff --git a/test/unit/extra_rustc_flags/BUILD.bazel b/test/unit/extra_rustc_flags/BUILD.bazel
new file mode 100644
index 0000000..52be64d
--- /dev/null
+++ b/test/unit/extra_rustc_flags/BUILD.bazel
@@ -0,0 +1,5 @@
+load(":extra_rustc_flags_test.bzl", "extra_rustc_flags_test_suite")
+
+extra_rustc_flags_test_suite(
+ name = "extra_rustc_flags_test_suite",
+)
diff --git a/test/unit/extra_rustc_flags/extra_rustc_flags_test.bzl b/test/unit/extra_rustc_flags/extra_rustc_flags_test.bzl
new file mode 100644
index 0000000..8a0bc9f
--- /dev/null
+++ b/test/unit/extra_rustc_flags/extra_rustc_flags_test.bzl
@@ -0,0 +1,94 @@
+"""Unittest to verify compile_data (attribute) propagation"""
+
+load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
+load("//rust:defs.bzl", "rust_library")
+load("//test/unit:common.bzl", "assert_argv_contains", "assert_argv_contains_not")
+
+EXTRA_FLAG = "--codegen=linker-plugin-lto"
+
+def target_action_contains_not_flag(env, target):
+ action = target.actions[0]
+ asserts.equals(env, "Rustc", action.mnemonic)
+
+ assert_argv_contains_not(
+ env = env,
+ action = action,
+ flag = EXTRA_FLAG,
+ )
+
+def target_action_contains_flag(env, target):
+ action = target.actions[0]
+ asserts.equals(env, "Rustc", action.mnemonic)
+
+ assert_argv_contains(
+ env = env,
+ action = action,
+ flag = EXTRA_FLAG,
+ )
+
+def _extra_rustc_flags_not_present_test(ctx):
+ env = analysistest.begin(ctx)
+ target = analysistest.target_under_test(env)
+ target_action_contains_not_flag(env, target)
+
+ return analysistest.end(env)
+
+def _extra_rustc_flags_present_test(ctx):
+ env = analysistest.begin(ctx)
+ target = analysistest.target_under_test(env)
+ target_action_contains_flag(env, target)
+
+ # Check the exec configuration target does NOT contain.
+ target = ctx.attr.lib_exec
+ target_action_contains_not_flag(env, target)
+
+ return analysistest.end(env)
+
+extra_rustc_flags_not_present_test = analysistest.make(_extra_rustc_flags_not_present_test)
+extra_rustc_flags_present_test = analysistest.make(
+ _extra_rustc_flags_present_test,
+ attrs = {
+ "lib_exec": attr.label(
+ mandatory = True,
+ cfg = "exec",
+ ),
+ },
+ config_settings = {
+ "//:extra_rustc_flags": [EXTRA_FLAG],
+ },
+)
+
+def _define_test_targets():
+ rust_library(
+ name = "lib",
+ srcs = ["lib.rs"],
+ edition = "2018",
+ )
+
+def extra_rustc_flags_test_suite(name):
+ """Entry-point macro called from the BUILD file.
+
+ Args:
+ name (str): Name of the macro.
+ """
+
+ _define_test_targets()
+
+ extra_rustc_flags_not_present_test(
+ name = "extra_rustc_flags_not_present_test",
+ target_under_test = ":lib",
+ )
+
+ extra_rustc_flags_present_test(
+ name = "extra_rustc_flags_present_test",
+ target_under_test = ":lib",
+ lib_exec = ":lib",
+ )
+
+ native.test_suite(
+ name = name,
+ tests = [
+ ":extra_rustc_flags_not_present_test",
+ ":extra_rustc_flags_present_test",
+ ],
+ )
diff --git a/test/unit/extra_rustc_flags/lib.rs b/test/unit/extra_rustc_flags/lib.rs
new file mode 100644
index 0000000..a38192a
--- /dev/null
+++ b/test/unit/extra_rustc_flags/lib.rs
@@ -0,0 +1 @@
+pub fn call() {}