diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index 91e5992811..d76e37acd9 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -411,6 +411,16 @@ tasks: - "//..." test_targets: - "//..." + expand_examples_ubuntu2004: + name: Expand Examples + platform: ubuntu2004 + working_directory: examples/expand + build_targets: + - "//..." + test_targets: + - "//..." + build_flags: + - "--config=expanded" cc_common_link_ubuntu2004: name: Build via cc_common.link platform: ubuntu2004 diff --git a/examples/.bazelignore b/examples/.bazelignore index cd22faa3b8..390163fdd4 100644 --- a/examples/.bazelignore +++ b/examples/.bazelignore @@ -2,5 +2,6 @@ android cargo_manifest_dir/external_crate crate_universe crate_universe_unnamed +expand ios ios_build diff --git a/examples/expand/.bazelrc b/examples/expand/.bazelrc new file mode 100644 index 0000000000..e16272e947 --- /dev/null +++ b/examples/expand/.bazelrc @@ -0,0 +1,10 @@ +# `.bazelrc` is a Bazel configuration file. +# https://bazel.build/docs/best-practices#bazelrc-file + +# Enable rustc expand targets +build:expanded --aspects=@rules_rust//rust:defs.bzl%rust_expand_aspect +build:expanded --output_groups=+expanded + +# This import should always be last to allow users to override +# settings for local development. +try-import %workspace%/user.bazelrc diff --git a/examples/expand/BUILD.bazel b/examples/expand/BUILD.bazel new file mode 100644 index 0000000000..213a321d1f --- /dev/null +++ b/examples/expand/BUILD.bazel @@ -0,0 +1,62 @@ +load("@bazel_skylib//rules:diff_test.bzl", "diff_test") +load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_expand", "rust_library", "rust_test") + +package(default_visibility = ["//test:__subpackages__"]) + +# Declaration of targets. + +rust_binary( + name = "ok_binary", + srcs = ["src/main.rs"], + edition = "2021", +) + +rust_library( + name = "ok_library", + srcs = ["src/lib.rs"], + edition = "2021", +) + +rust_test( + name = "ok_test", + srcs = ["src/lib.rs"], + edition = "2021", +) + +# Expand targets. + +rust_expand( + name = "ok_binary_expand", + deps = [":ok_binary"], +) + +rust_expand( + name = "ok_library_expand", + deps = [":ok_library"], +) + +rust_expand( + name = "ok_test_expand", + testonly = True, + deps = [":ok_test"], +) + +# Assert on expanded targets. + +diff_test( + name = "ok_binary_expand_test", + file1 = ":ok_binary.expand.expected.rs", + file2 = ":ok_binary_expand", +) + +diff_test( + name = "ok_library_expand_test", + file1 = ":ok_library.expand.expected.rs", + file2 = ":ok_library_expand", +) + +diff_test( + name = "ok_test_expand_test", + file1 = ":ok_test.expand.expected.rs", + file2 = ":ok_test_expand", +) diff --git a/examples/expand/README.md b/examples/expand/README.md new file mode 100644 index 0000000000..26cb207e33 --- /dev/null +++ b/examples/expand/README.md @@ -0,0 +1,76 @@ +### Expand Example + +Rustc can be used to expand all macros so that you can inspect the generated source files easier. + +This feature is enabled via `-Zunpretty=expanded`. The `-Z` flag is only available in the nightly +version of `rustc`. + + +### Expanding + +Build and test your targets normally. + +``` +bazel build //:ok_binary +INFO: Analyzed target //:ok_binary (0 packages loaded, 0 targets configured). +INFO: Found 1 target... +Target //:ok_binary up-to-date: + bazel-bin/ok_binary +INFO: Elapsed time: 0.081s, Critical Path: 0.00s +INFO: 1 process: 1 internal. +INFO: Build completed successfully, 1 total action +``` + +Use the aspect to generate the expanded files in as a one-off build. + +``` +bazel build --config=expanded //:ok_binary +INFO: Analyzed target //:ok_binary (1 packages loaded, 2 targets configured). +INFO: Found 1 target... +Aspect @rules_rust//rust/private:expand.bzl%rust_expand_aspect of //:ok_binary up-to-date: + bazel-bin/ok_binary.expand.rs +INFO: Elapsed time: 0.149s, Critical Path: 0.00s +INFO: 1 process: 1 internal. +INFO: Build completed successfully, 1 total action +``` + +Targeting tests is valid as well. + +``` +bazel build --config=expanded //:ok_test +INFO: Analyzed target //:ok_test (0 packages loaded, 2 targets configured). +INFO: Found 1 target... +Aspect @rules_rust//rust/private:expand.bzl%rust_expand_aspect of //:ok_test up-to-date: + bazel-bin/test-397521499/ok_test.expand.rs +INFO: Elapsed time: 0.113s, Critical Path: 0.00s +INFO: 1 process: 1 internal. +INFO: Build completed successfully, 1 total action +``` + +Finally, manually wire up a `rust_expand` target explicitly if you want a target to build. + +``` +load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_expand") + +rust_binary( + name = "ok_binary", + srcs = ["src/main.rs"], + edition = "2021", +) + +rust_expand( + name = "ok_binary_expand", + deps = [":ok_binary"], +) +``` + +``` +bazel build //:ok_binary_expand +INFO: Analyzed target //:ok_binary_expand (0 packages loaded, 1 target configured). +INFO: Found 1 target... +Target //:ok_binary_expand up-to-date: + bazel-bin/ok_binary.expand.rs +INFO: Elapsed time: 0.090s, Critical Path: 0.00s +INFO: 1 process: 1 internal. +INFO: Build completed successfully, 1 total action +``` diff --git a/examples/expand/WORKSPACE.bazel b/examples/expand/WORKSPACE.bazel new file mode 100644 index 0000000000..b863c96196 --- /dev/null +++ b/examples/expand/WORKSPACE.bazel @@ -0,0 +1,16 @@ +workspace(name = "examples") + +local_repository( + name = "rules_rust", + path = "../../", +) + +load("@rules_rust//rust:repositories.bzl", "rules_rust_dependencies", "rust_register_toolchains") + +rules_rust_dependencies() + +rust_register_toolchains( + edition = "2021", + iso_date = "2022-11-01", + version = "nightly", +) diff --git a/examples/expand/ok_binary.expand.expected.rs b/examples/expand/ok_binary.expand.expected.rs new file mode 100755 index 0000000000..d6bcef8269 --- /dev/null +++ b/examples/expand/ok_binary.expand.expected.rs @@ -0,0 +1,11 @@ +#![feature(prelude_import)] +#[prelude_import] +use std::prelude::rust_2021::*; +#[macro_use] +extern crate std; +fn main() { + { + ::std::io::_print(::core::fmt::Arguments::new_v1(&["Hello world\n"], + &[])); + }; +} diff --git a/examples/expand/ok_library.expand.expected.rs b/examples/expand/ok_library.expand.expected.rs new file mode 100755 index 0000000000..95789e4a2e --- /dev/null +++ b/examples/expand/ok_library.expand.expected.rs @@ -0,0 +1,10 @@ +#![feature(prelude_import)] +#[prelude_import] +use std::prelude::rust_2021::*; +#[macro_use] +extern crate std; +pub fn greeting() -> String { "Hello World".to_owned() } + +// too_many_args/clippy.toml will require no more than 2 args. +pub fn with_args(_: u32, _: u32, _: u32) {} + diff --git a/examples/expand/ok_test.expand.expected.rs b/examples/expand/ok_test.expand.expected.rs new file mode 100755 index 0000000000..b141973e9f --- /dev/null +++ b/examples/expand/ok_test.expand.expected.rs @@ -0,0 +1,47 @@ +#![feature(prelude_import)] +#[prelude_import] +use std::prelude::rust_2021::*; +#[macro_use] +extern crate std; +pub fn greeting() -> String { "Hello World".to_owned() } + +// too_many_args/clippy.toml will require no more than 2 args. +pub fn with_args(_: u32, _: u32, _: u32) {} + +#[cfg(test)] +mod tests { + use super::*; + extern crate test; + #[cfg(test)] + #[rustc_test_marker = "tests::it_works"] + pub const it_works: test::TestDescAndFn = + test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("tests::it_works"), + ignore: false, + ignore_message: ::core::option::Option::None, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::UnitTest, + }, + testfn: test::StaticTestFn(|| + test::assert_test_result(it_works())), + }; + fn it_works() { + match (&greeting(), &"Hello World".to_owned()) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed(kind, &*left_val, + &*right_val, ::core::option::Option::None); + } + } + }; + } +} +#[rustc_main] +pub fn main() -> () { + extern crate test; + test::test_main_static(&[&it_works]) +} diff --git a/examples/expand/src/lib.rs b/examples/expand/src/lib.rs new file mode 100644 index 0000000000..c55a340f33 --- /dev/null +++ b/examples/expand/src/lib.rs @@ -0,0 +1,15 @@ +pub fn greeting() -> String { + "Hello World".to_owned() +} + +// too_many_args/clippy.toml will require no more than 2 args. +pub fn with_args(_: u32, _: u32, _: u32) {} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn it_works() { + assert_eq!(greeting(), "Hello World".to_owned()); + } +} diff --git a/examples/expand/src/main.rs b/examples/expand/src/main.rs new file mode 100644 index 0000000000..5bf256ea97 --- /dev/null +++ b/examples/expand/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello world"); +} diff --git a/rust/defs.bzl b/rust/defs.bzl index 92c8a77406..59cf8b710a 100644 --- a/rust/defs.bzl +++ b/rust/defs.bzl @@ -26,6 +26,7 @@ load( _rust_clippy_aspect = "rust_clippy_aspect", ) load("//rust/private:common.bzl", _rust_common = "rust_common") +load("//rust/private:expand.bzl", _rust_expand = "rust_expand", _rust_expand_aspect = "rust_expand_aspect") load( "//rust/private:rust.bzl", _rust_binary = "rust_binary", @@ -103,6 +104,12 @@ rust_clippy = _rust_clippy capture_clippy_output = _capture_clippy_output # See @rules_rust//rust/private:clippy.bzl for a complete description. +rust_expand_aspect = _rust_expand_aspect +# See @rules_rust//rust/private:expand.bzl for a complete description. + +rust_expand = _rust_expand +# See @rules_rust//rust/private:expand.bzl for a complete description. + error_format = _error_format # See @rules_rust//rust/private:rustc.bzl for a complete description. diff --git a/rust/private/expand.bzl b/rust/private/expand.bzl new file mode 100644 index 0000000000..df24f401cb --- /dev/null +++ b/rust/private/expand.bzl @@ -0,0 +1,247 @@ +# Copyright 2020 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. + +"""A module defining Rust expansion rules""" + +load("//rust/private:common.bzl", "rust_common") +load( + "//rust/private:rustc.bzl", + "collect_deps", + "collect_inputs", + "construct_arguments", +) +load( + "//rust/private:utils.bzl", + "determine_output_hash", + "find_cc_toolchain", + "find_toolchain", +) + +def _get_expand_ready_crate_info(target, aspect_ctx): + """Check that a target is suitable for expansion and extract the `CrateInfo` provider from it. + + Args: + target (Target): The target the aspect is running on. + aspect_ctx (ctx, optional): The aspect's context object. + + Returns: + CrateInfo, optional: A `CrateInfo` provider if rust expand should be run or `None`. + """ + + # Ignore external targets + if target.label.workspace_root.startswith("external"): + return None + + # Targets with specific tags will not be formatted + if aspect_ctx: + ignore_tags = [ + "noexpand", + "no-expand", + ] + + for tag in ignore_tags: + if tag in aspect_ctx.rule.attr.tags: + return None + + # Obviously ignore any targets that don't contain `CrateInfo` + if rust_common.crate_info not in target: + return None + + return target[rust_common.crate_info] + +def _expand_aspect_impl(target, ctx): + crate_info = _get_expand_ready_crate_info(target, ctx) + if not crate_info: + return [] + + toolchain = find_toolchain(ctx) + cc_toolchain, feature_configuration = find_cc_toolchain(ctx) + + dep_info, build_info, linkstamps = collect_deps( + deps = crate_info.deps, + proc_macro_deps = crate_info.proc_macro_deps, + aliases = crate_info.aliases, + # Rust expand doesn't need to invoke transitive linking, therefore doesn't need linkstamps. + are_linkstamps_supported = False, + ) + + compile_inputs, out_dir, build_env_files, build_flags_files, linkstamp_outs, ambiguous_libs = collect_inputs( + ctx, + ctx.rule.file, + ctx.rule.files, + linkstamps, + toolchain, + cc_toolchain, + feature_configuration, + crate_info, + dep_info, + build_info, + ) + + args, env = construct_arguments( + ctx = ctx, + attr = ctx.rule.attr, + file = ctx.file, + toolchain = toolchain, + tool_path = toolchain.rustc.path, + cc_toolchain = cc_toolchain, + feature_configuration = feature_configuration, + crate_info = crate_info, + dep_info = dep_info, + linkstamp_outs = linkstamp_outs, + ambiguous_libs = ambiguous_libs, + output_hash = determine_output_hash(crate_info.root, ctx.label), + rust_flags = [], + out_dir = out_dir, + build_env_files = build_env_files, + build_flags_files = build_flags_files, + emit = ["dep-info", "metadata"], + ) + + if crate_info.is_test: + args.rustc_flags.add("--test") + + expand_out = ctx.actions.declare_file(ctx.label.name + ".expand.rs", sibling = crate_info.output) + args.process_wrapper_flags.add("--stdout-file", expand_out.path) + + # Expand all macros and dump the source to stdout. + args.rustc_flags.add("-Zunpretty=expanded") + + ctx.actions.run( + executable = ctx.executable._process_wrapper, + inputs = compile_inputs, + outputs = [expand_out], + env = env, + arguments = args.all, + mnemonic = "RustExpand", + ) + + return [ + OutputGroupInfo(expanded = depset([expand_out])), + ] + +# Example: Expand all rust targets in the codebase. +# bazel build --aspects=@rules_rust//rust:defs.bzl%rust_expand_aspect \ +# --output_groups=expanded \ +# //... +rust_expand_aspect = aspect( + fragments = ["cpp"], + host_fragments = ["cpp"], + attrs = { + "_cc_toolchain": attr.label( + doc = ( + "Required attribute to access the cc_toolchain. See [Accessing the C++ toolchain]" + + "(https://docs.bazel.build/versions/master/integrating-with-rules-cc.html#accessing-the-c-toolchain)" + ), + default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), + ), + "_extra_rustc_flag": attr.label(default = "//:extra_rustc_flag"), + "_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"), + executable = True, + cfg = "exec", + ), + }, + toolchains = [ + str(Label("//rust:toolchain_type")), + "@bazel_tools//tools/cpp:toolchain_type", + ], + incompatible_use_toolchain_transition = True, + implementation = _expand_aspect_impl, + doc = """\ +Executes Rust expand on specified targets. + +This aspect applies to existing rust_library, rust_test, and rust_binary rules. + +As an example, if the following is defined in `examples/hello_lib/BUILD.bazel`: + +```python +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") + +rust_library( + name = "hello_lib", + srcs = ["src/lib.rs"], +) + +rust_test( + name = "greeting_test", + srcs = ["tests/greeting.rs"], + deps = [":hello_lib"], +) +``` + +Then the targets can be expanded with the following command: + +```output +$ bazel build --aspects=@rules_rust//rust:defs.bzl%rust_expand_aspect \ + --output_groups=expanded //hello_lib:all +``` +""", +) + +def _rust_expand_rule_impl(ctx): + expand_ready_targets = [dep for dep in ctx.attr.deps if "expanded" in dir(dep[OutputGroupInfo])] + files = depset([], transitive = [dep[OutputGroupInfo].expanded for dep in expand_ready_targets]) + return [DefaultInfo(files = files)] + +rust_expand = rule( + implementation = _rust_expand_rule_impl, + attrs = { + "deps": attr.label_list( + doc = "Rust targets to run expand on.", + providers = [rust_common.crate_info], + aspects = [rust_expand_aspect], + ), + }, + doc = """\ +Executes rust expand on a specific target. + +Similar to `rust_expand_aspect`, but allows specifying a list of dependencies \ +within the build system. + +For example, given the following example targets: + +```python +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") + +rust_library( + name = "hello_lib", + srcs = ["src/lib.rs"], +) + +rust_test( + name = "greeting_test", + srcs = ["tests/greeting.rs"], + deps = [":hello_lib"], +) +``` + +Rust expand can be set as a build target with the following: + +```python +load("@rules_rust//rust:defs.bzl", "rust_expand") + +rust_expand( + name = "hello_library_expand", + testonly = True, + deps = [ + ":hello_lib", + ":greeting_test", + ], +) +``` +""", +)