diff --git a/.bazelignore b/.bazelignore index 0b25ffe44..8aaeb6a16 100644 --- a/.bazelignore +++ b/.bazelignore @@ -5,3 +5,4 @@ crate_universe/private/bootstrap test/bzlmod_repo_mapping test/cc_common_link test/no_std +.direnv \ No newline at end of file diff --git a/docs/flatten.md b/docs/flatten.md index 334b50708..e9a222cc8 100644 --- a/docs/flatten.md +++ b/docs/flatten.md @@ -1132,7 +1132,7 @@ Run the test with `bazel test //hello_lib:greeting_test`. ## rust_toolchain
-rust_toolchain(name, allocator_library, binary_ext, cargo, clippy_driver, debug_info, +rust_toolchain(name, allocator_library, binary_ext, cargo, cargo_clippy, clippy_driver, debug_info, default_edition, dylib_ext, env, exec_triple, experimental_link_std_dylib, experimental_use_cc_common_link, extra_exec_rustc_flags, extra_rustc_flags, extra_rustc_flags_for_crate_types, global_allocator_library, llvm_cov, llvm_profdata, @@ -1192,6 +1192,7 @@ See `@rules_rust//rust:repositories.bzl` for examples of defining the `@rust_cpu | allocator_library | Target that provides allocator functions when rust_library targets are embedded in a cc_binary. | Label | optional | `"@rules_rust//ffi/cc/allocator_library"` | | binary_ext | The extension for binaries created from rustc. | String | required | | | cargo | The location of the `cargo` binary. Can be a direct source or a filegroup containing one item. | Label | optional | `None` | +| cargo_clippy | The location of the `cargo_clippy` binary. Can be a direct source or a filegroup containing one item. | Label | optional | `None` | | clippy_driver | The location of the `clippy-driver` binary. Can be a direct source or a filegroup containing one item. | Label | optional | `None` | | debug_info | Rustc debug info levels per opt level | Dictionary: String -> String | optional | `{"dbg": "2", "fastbuild": "0", "opt": "0"}` | | default_edition | The edition to use for rust_* rules that don't specify an edition. If absent, every rule is required to specify its `edition` attribute. | String | optional | `""` | diff --git a/docs/rust_repositories.md b/docs/rust_repositories.md index 729e4ba1e..f815779ef 100644 --- a/docs/rust_repositories.md +++ b/docs/rust_repositories.md @@ -36,7 +36,7 @@ A dedicated filegroup-like rule for Rust stdlib artifacts. ## rust_toolchain-rust_toolchain(name, allocator_library, binary_ext, cargo, clippy_driver, debug_info, +rust_toolchain(name, allocator_library, binary_ext, cargo, cargo_clippy, clippy_driver, debug_info, default_edition, dylib_ext, env, exec_triple, experimental_link_std_dylib, experimental_use_cc_common_link, extra_exec_rustc_flags, extra_rustc_flags, extra_rustc_flags_for_crate_types, global_allocator_library, llvm_cov, llvm_profdata, @@ -96,6 +96,7 @@ See `@rules_rust//rust:repositories.bzl` for examples of defining the `@rust_cpu | allocator_library | Target that provides allocator functions when rust_library targets are embedded in a cc_binary. | Label | optional | `"@rules_rust//ffi/cc/allocator_library"` | | binary_ext | The extension for binaries created from rustc. | String | required | | | cargo | The location of the `cargo` binary. Can be a direct source or a filegroup containing one item. | Label | optional | `None` | +| cargo_clippy | The location of the `cargo_clippy` binary. Can be a direct source or a filegroup containing one item. | Label | optional | `None` | | clippy_driver | The location of the `clippy-driver` binary. Can be a direct source or a filegroup containing one item. | Label | optional | `None` | | debug_info | Rustc debug info levels per opt level | Dictionary: String -> String | optional | `{"dbg": "2", "fastbuild": "0", "opt": "0"}` | | default_edition | The edition to use for rust_* rules that don't specify an edition. If absent, every rule is required to specify its `edition` attribute. | String | optional | `""` | diff --git a/examples/.bazelignore b/examples/.bazelignore index e8cae6593..5c00b61cb 100644 --- a/examples/.bazelignore +++ b/examples/.bazelignore @@ -1,4 +1,5 @@ android +bazel_env bzlmod cargo_manifest_dir/external_crate crate_universe diff --git a/examples/bazel_env/.bazelignore b/examples/bazel_env/.bazelignore new file mode 100644 index 000000000..ff51edffc --- /dev/null +++ b/examples/bazel_env/.bazelignore @@ -0,0 +1 @@ +.direnv \ No newline at end of file diff --git a/examples/bazel_env/.bazelrc b/examples/bazel_env/.bazelrc new file mode 100644 index 000000000..f5a104e0d --- /dev/null +++ b/examples/bazel_env/.bazelrc @@ -0,0 +1,15 @@ +# Required on windows +common --enable_platform_specific_config +startup --windows_enable_symlinks +build:windows --enable_runfiles + +build --experimental_enable_bzlmod + +# This isn't currently the defaut in Bazel, but we enable it to test we'll be ready if/when it flips. +build --incompatible_disallow_empty_glob + +# Required for cargo_build_script support before Bazel 7 +build --incompatible_merge_fixed_and_default_shell_env + +# Do not import the PATH etc. from the host environment +common --incompatible_strict_action_env diff --git a/examples/bazel_env/.envrc b/examples/bazel_env/.envrc new file mode 100644 index 000000000..4722fbe14 --- /dev/null +++ b/examples/bazel_env/.envrc @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# ^^^ make IDEs happy + +watch_file bazel-out/bazel_env-opt/bin/env/env/bin +PATH_add bazel-out/bazel_env-opt/bin/env/env/bin +if [[ ! -d bazel-out/bazel_env-opt/bin/env/env/bin ]]; then + log_error "ERROR[bazel_env.bzl]: Run 'bazel run //env:env' to regenerate bazel-out/bazel_env-opt/bin/env/env/bin" +fi \ No newline at end of file diff --git a/examples/bazel_env/.gitignore b/examples/bazel_env/.gitignore new file mode 100644 index 000000000..a6ef824c1 --- /dev/null +++ b/examples/bazel_env/.gitignore @@ -0,0 +1 @@ +/bazel-* diff --git a/examples/bazel_env/BUILD.bazel b/examples/bazel_env/BUILD.bazel new file mode 100644 index 000000000..0d1b02883 --- /dev/null +++ b/examples/bazel_env/BUILD.bazel @@ -0,0 +1,50 @@ +"Tests for upstream wrappers." + +sh_test( + name = "upstream_cargo_test", + size = "small", + srcs = ["cargo_test.sh"], + args = [ + "$(rlocationpath @rules_rust//tools/upstream_wrapper:cargo)", + ], + data = [ + "Cargo.lock", + "Cargo.toml", + "//rust/hello_world:Cargo.toml", + "//rust/hello_world:src/main.rs", + "@rules_rust//tools/upstream_wrapper:cargo", + ], + deps = [ + "@bazel_tools//tools/bash/runfiles", + ], +) + +sh_test( + name = "upstream_rustc_test", + size = "small", + srcs = ["rustc_test.sh"], + args = [ + "$(rlocationpath @rules_rust//tools/upstream_wrapper:rustc)", + ], + data = [ + "@rules_rust//tools/upstream_wrapper:rustc", + ], + deps = [ + "@bazel_tools//tools/bash/runfiles", + ], +) + +sh_test( + name = "upstream_rustfmt_test", + size = "small", + srcs = ["rustfmt_test.sh"], + args = [ + "$(rlocationpath @rules_rust//tools/upstream_wrapper:rustfmt)", + ], + data = [ + "@rules_rust//tools/upstream_wrapper:rustfmt", + ], + deps = [ + "@bazel_tools//tools/bash/runfiles", + ], +) diff --git a/examples/bazel_env/Cargo.lock b/examples/bazel_env/Cargo.lock new file mode 100644 index 000000000..c2e8cbe09 --- /dev/null +++ b/examples/bazel_env/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "hello_world" +version = "0.0.0" diff --git a/examples/bazel_env/Cargo.toml b/examples/bazel_env/Cargo.toml new file mode 100644 index 000000000..35e6b18f1 --- /dev/null +++ b/examples/bazel_env/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +resolver = "2" +members = [ + "rust/hello_world" +] \ No newline at end of file diff --git a/examples/bazel_env/MODULE.bazel b/examples/bazel_env/MODULE.bazel new file mode 100644 index 000000000..e4e934075 --- /dev/null +++ b/examples/bazel_env/MODULE.bazel @@ -0,0 +1,44 @@ +"""bazelbuild/rules_rust - bazel_env/bzlmod example + +See https://github.com/buildbuddy-io/bazel_env.bzl. +""" + +module( + name = "all_crate_deps_bzlmod_example", + version = "0.0.0", +) + +bazel_dep(name = "platforms", version = "0.0.9") +bazel_dep( + name = "rules_rust", + version = "0.0.0", +) +local_path_override( + module_name = "rules_rust", + path = "../..", +) + +rust = use_extension("@rules_rust//rust:extensions.bzl", "rust") +rust.toolchain(edition = "2021") +use_repo( + rust, + "rust_toolchains", +) + +register_toolchains("@rust_toolchains//:all") + +crate = use_extension( + "@rules_rust//crate_universe:extension.bzl", + "crate", +) +crate.from_cargo( + name = "crates", + cargo_lockfile = "//:Cargo.lock", + manifests = [ + "//:Cargo.toml", + "//rust/hello_world:Cargo.toml", + ], +) +use_repo(crate, "crates") + +bazel_dep(name = "bazel_env.bzl", version = "0.1.1") diff --git a/examples/bazel_env/README.md b/examples/bazel_env/README.md new file mode 100644 index 000000000..34550116e --- /dev/null +++ b/examples/bazel_env/README.md @@ -0,0 +1,11 @@ +# rules_rust with bazel_env + +This example uses [bazel_env.bzl](https://github.com/buildbuddy-io/bazel_env.bzl) to +provide `cargo`, `cargo-clippy`, `rustc` and `rustfmt` from the bazel toolchains +to the user. They will be available directly in the PATH when the user +enters this directory and has [direnv](https://direnv.net/) set up. + +Advantages: + +- The user doesn't have to install the toolchains themselves. +- The tool versions will always match exactly those that bazel uses. diff --git a/examples/bazel_env/WORKSPACE.bazel b/examples/bazel_env/WORKSPACE.bazel new file mode 100644 index 000000000..b543b79e9 --- /dev/null +++ b/examples/bazel_env/WORKSPACE.bazel @@ -0,0 +1 @@ +# Intentionally blank; using bzlmod diff --git a/examples/bazel_env/WORKSPACE.bzlmod b/examples/bazel_env/WORKSPACE.bzlmod new file mode 100644 index 000000000..8e081c0b5 --- /dev/null +++ b/examples/bazel_env/WORKSPACE.bzlmod @@ -0,0 +1 @@ +# Intentionally blank; enable strict mode for bzlmod diff --git a/examples/bazel_env/cargo_test.sh b/examples/bazel_env/cargo_test.sh new file mode 100755 index 000000000..664067aea --- /dev/null +++ b/examples/bazel_env/cargo_test.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +# --- begin runfiles.bash initialization v3 --- +# Copy-pasted from the Bazel Bash runfiles library v3. +set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash +source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ + source "$0.runfiles/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e +# --- end runfiles.bash initialization v3 --- + + +set -euo pipefail + +# MARK - Functions + +fail() { + echo >&2 "$@" + exit 1 +} + +# MARK - Args + +if [[ "$#" -lt 1 ]]; then + fail "Usage: $0 /path/to/bin" +fi +CARGO="$(rlocation "$1")" + +# MARK - Test + +# simulate bazel run with setting BUILD_WORKING_DIRECTORY +export BUILD_WORKING_DIRECTORY="$PWD" + +# Run hello_world, invoking rustc +OUTPUT="$("${CARGO}" run)" + +EXPECTED_OUTPUT="Hello, world!" +[[ "${OUTPUT}" == "${EXPECTED_OUTPUT}" ]] || + fail 'Expected "'"${EXPECTED_OUTPUT}"'", but was' "${OUTPUT}" + +# Crash tests, no error + +${CARGO} clippy || + fail "couldn't execute \"cargo clippy\"" diff --git a/examples/bazel_env/env/BUILD.bazel b/examples/bazel_env/env/BUILD.bazel new file mode 100644 index 000000000..0c64a5883 --- /dev/null +++ b/examples/bazel_env/env/BUILD.bazel @@ -0,0 +1,13 @@ +load("@bazel_env.bzl", "bazel_env") + +package(default_visibility = ["//visibility:public"]) + +bazel_env( + name = "env", + tools = { + "cargo": "@rules_rust//tools/upstream_wrapper:cargo", + "cargo-clippy": "@rules_rust//tools/upstream_wrapper:cargo_clippy", + "rustc": "@rules_rust//tools/upstream_wrapper:rustc", + "rustfmt": "@rules_rust//tools/upstream_wrapper:rustfmt", + }, +) diff --git a/examples/bazel_env/hello b/examples/bazel_env/hello new file mode 100755 index 000000000..182a8addb Binary files /dev/null and b/examples/bazel_env/hello differ diff --git a/examples/bazel_env/rust/hello_world/BUILD.bazel b/examples/bazel_env/rust/hello_world/BUILD.bazel new file mode 100644 index 000000000..0f5efa03f --- /dev/null +++ b/examples/bazel_env/rust/hello_world/BUILD.bazel @@ -0,0 +1,15 @@ +load("@crates//:defs.bzl", "all_crate_deps") +load("@rules_rust//rust:defs.bzl", "rust_binary") + +package(default_visibility = ["//visibility:public"]) + +exports_files([ + "Cargo.toml", + "src/main.rs", +]) + +rust_binary( + name = "hello_world", + srcs = ["src/main.rs"], + deps = all_crate_deps(normal = True), +) diff --git a/examples/bazel_env/rust/hello_world/Cargo.toml b/examples/bazel_env/rust/hello_world/Cargo.toml new file mode 100644 index 000000000..755a8dd14 --- /dev/null +++ b/examples/bazel_env/rust/hello_world/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "hello_world" +version = "0.0.0" +edition = "2021" +publish = false + +[lib] +path = "/dev/null" diff --git a/examples/bazel_env/rust/hello_world/src/main.rs b/examples/bazel_env/rust/hello_world/src/main.rs new file mode 100644 index 000000000..317f56458 --- /dev/null +++ b/examples/bazel_env/rust/hello_world/src/main.rs @@ -0,0 +1,17 @@ +// Copyright 2015 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. + +fn main() { + println!("Hello, world!"); +} diff --git a/examples/bazel_env/rustc_test.sh b/examples/bazel_env/rustc_test.sh new file mode 100755 index 000000000..ecef60919 --- /dev/null +++ b/examples/bazel_env/rustc_test.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +# --- begin runfiles.bash initialization v3 --- +# Copy-pasted from the Bazel Bash runfiles library v3. +set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash +source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ + source "$0.runfiles/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e +# --- end runfiles.bash initialization v3 --- + + +set -euo pipefail + +# MARK - Functions + +fail() { + echo >&2 "$@" + exit 1 +} + +# MARK - Args + +if [[ "$#" -lt 1 ]]; then + fail "Usage: $0 /path/to/bin " +fi +RUSTC="$(rlocation "$1")" + +# MARK - Test +DIR=$(mktemp -d "rustc-test-XXXXXXX") + +set -x +echo "fn main() {}" >"$DIR/main.rs" +cd "$DIR" +# simulate bazel run with setting BUILD_WORKING_DIRECTORY +export BUILD_WORKING_DIRECTORY="$PWD" +"$RUSTC" "main.rs" || + fail "Couldn't compile main.rs" diff --git a/examples/bazel_env/rustfmt_test.sh b/examples/bazel_env/rustfmt_test.sh new file mode 100755 index 000000000..177201077 --- /dev/null +++ b/examples/bazel_env/rustfmt_test.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +# --- begin runfiles.bash initialization v3 --- +# Copy-pasted from the Bazel Bash runfiles library v3. +set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash +source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ + source "$0.runfiles/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e +# --- end runfiles.bash initialization v3 --- + + +set -euo pipefail + +# MARK - Functions + +fail() { + echo >&2 "$@" + exit 1 +} + +# MARK - Args + +if [[ "$#" -lt 1 ]]; then + fail "Usage: $0 /path/to/bin " +fi +RUSTFMT="$(rlocation "$1")" + +# MARK - Test + +# simulate bazel run with setting BUILD_WORKING_DIRECTORY +export BUILD_WORKING_DIRECTORY="$PWD" + +OUTPUT="$(echo -e "fn main() {\n\n}" | "$RUSTFMT")" + +# without newlines in body +EXPECTED_OUTPUT="fn main() {}" +[[ "${OUTPUT}" == "${EXPECTED_OUTPUT}" ]] || + fail 'Expected "'"${EXPECTED_OUTPUT}"'", but was' "${OUTPUT}" \ No newline at end of file diff --git a/rust/private/repository_utils.bzl b/rust/private/repository_utils.bzl index 67f1cd0aa..15d11e683 100644 --- a/rust/private/repository_utils.bzl +++ b/rust/private/repository_utils.bzl @@ -122,14 +122,6 @@ def BUILD_for_rustfmt(target_triple): binary_ext = system_to_binary_ext(target_triple.system), ) -_build_file_for_clippy_template = """\ -filegroup( - name = "clippy_driver_bin", - srcs = ["bin/clippy-driver{binary_ext}"], - visibility = ["//visibility:public"], -) -""" - _build_file_for_rust_analyzer_proc_macro_srv = """\ filegroup( name = "rust_analyzer_proc_macro_srv", @@ -150,6 +142,19 @@ def BUILD_for_rust_analyzer_proc_macro_srv(exec_triple): binary_ext = system_to_binary_ext(exec_triple.system), ) +_build_file_for_clippy_template = """\ +filegroup( + name = "clippy_driver_bin", + srcs = ["bin/clippy-driver{binary_ext}"], + visibility = ["//visibility:public"], +) +filegroup( + name = "cargo_clippy_bin", + srcs = ["bin/cargo-clippy{binary_ext}"], + visibility = ["//visibility:public"], +) +""" + def BUILD_for_clippy(target_triple): """Emits a BUILD file the clippy archive. @@ -244,6 +249,7 @@ rust_toolchain( rustfmt = {rustfmt_label}, cargo = "//:cargo", clippy_driver = "//:clippy_driver_bin", + cargo_clippy = "//:cargo_clippy_bin", llvm_cov = {llvm_cov_label}, llvm_profdata = {llvm_profdata_label}, rustc_lib = "//:rustc_lib", diff --git a/rust/private/toolchain_utils.bzl b/rust/private/toolchain_utils.bzl index 64f759f50..078e6f34c 100644 --- a/rust/private/toolchain_utils.bzl +++ b/rust/private/toolchain_utils.bzl @@ -13,6 +13,16 @@ def _toolchain_files_impl(ctx): ], transitive_files = toolchain.rustc_lib, ) + elif ctx.attr.tool == "cargo-clippy": + files = depset([toolchain.cargo_clippy]) + runfiles = ctx.runfiles( + files = [ + toolchain.cargo_clippy, + toolchain.clippy_driver, + toolchain.rustc, + ], + transitive_files = toolchain.rustc_lib, + ) elif ctx.attr.tool == "clippy": files = depset([toolchain.clippy_driver]) runfiles = ctx.runfiles( @@ -60,6 +70,7 @@ toolchain_files = rule( doc = "The desired tool to get form the current rust_toolchain", values = [ "cargo", + "cargo-clippy", "clippy", "rust_lib", "rust_std", diff --git a/rust/toolchain.bzl b/rust/toolchain.bzl index b10ddb1c8..ae3fcefeb 100644 --- a/rust/toolchain.bzl +++ b/rust/toolchain.bzl @@ -377,6 +377,7 @@ def _generate_sysroot( rustc_lib, cargo = None, clippy = None, + cargo_clippy = None, llvm_tools = None, rust_std = None, rustfmt = None): @@ -388,6 +389,7 @@ def _generate_sysroot( rustdoc (File): The path to a `rustdoc` executable. rustc_lib (Target): A collection of Files containing dependencies of `rustc`. cargo (File, optional): The path to a `cargo` executable. + cargo_clippy (File, optional): The path to a `cargo-clippy` executable. clippy (File, optional): The path to a `clippy-driver` executable. llvm_tools (Target, optional): A collection of llvm tools used by `rustc`. rust_std (Target, optional): A collection of Files containing Rust standard library components. @@ -428,6 +430,12 @@ def _generate_sysroot( sysroot_cargo = _symlink_sysroot_bin(ctx, name, "bin", cargo) direct_files.extend([sysroot_cargo]) + # Cargo-clippy + sysroot_cargo_clippy = None + if cargo_clippy: + sysroot_cargo_clippy = _symlink_sysroot_bin(ctx, name, "bin", cargo_clippy) + direct_files.extend([sysroot_cargo_clippy]) + # Rustfmt sysroot_rustfmt = None if rustfmt: @@ -453,6 +461,7 @@ def _generate_sysroot( content = "\n".join([ "cargo: {}".format(cargo), "clippy: {}".format(clippy), + "cargo-clippy: {}".format(cargo_clippy), "llvm_tools: {}".format(llvm_tools), "rust_std: {}".format(rust_std), "rustc_lib: {}".format(rustc_lib), @@ -469,6 +478,7 @@ def _generate_sysroot( all_files = all_files, cargo = sysroot_cargo, clippy = sysroot_clippy, + cargo_clippy = sysroot_cargo_clippy, rust_std = sysroot_rust_std, rustc = sysroot_rustc, rustc_lib = sysroot_rustc_lib, @@ -529,6 +539,7 @@ def _rust_toolchain_impl(ctx): rustfmt = ctx.file.rustfmt, clippy = ctx.file.clippy_driver, cargo = ctx.file.cargo, + cargo_clippy = ctx.file.cargo_clippy, llvm_tools = ctx.attr.llvm_tools, ) @@ -571,6 +582,7 @@ def _rust_toolchain_impl(ctx): "RUSTDOC": sysroot.rustdoc.path, "RUST_DEFAULT_EDITION": ctx.attr.default_edition or "", "RUST_SYSROOT": sysroot_path, + "RUST_SYSROOT_SHORT": sysroot_short_path, } if sysroot.cargo: @@ -631,6 +643,7 @@ def _rust_toolchain_impl(ctx): binary_ext = ctx.attr.binary_ext, cargo = sysroot.cargo, clippy_driver = sysroot.clippy, + cargo_clippy = sysroot.cargo_clippy, compilation_mode_opts = compilation_mode_opts, crosstool_files = ctx.files._cc_toolchain, default_edition = ctx.attr.default_edition, @@ -702,6 +715,11 @@ rust_toolchain = rule( allow_single_file = True, cfg = "exec", ), + "cargo_clippy": attr.label( + doc = "The location of the `cargo_clippy` binary. Can be a direct source or a filegroup containing one item.", + allow_single_file = True, + cfg = "exec", + ), "clippy_driver": attr.label( doc = "The location of the `clippy-driver` binary. Can be a direct source or a filegroup containing one item.", allow_single_file = True, diff --git a/rust/toolchain/BUILD.bazel b/rust/toolchain/BUILD.bazel index b5e57af79..c839c9136 100644 --- a/rust/toolchain/BUILD.bazel +++ b/rust/toolchain/BUILD.bazel @@ -13,6 +13,11 @@ toolchain_files( tool = "clippy", ) +toolchain_files( + name = "current_cargo_clippy_files", + tool = "cargo-clippy", +) + toolchain_files( name = "current_rustc_files", tool = "rustc", diff --git a/tools/rustfmt/BUILD.bazel b/tools/rustfmt/BUILD.bazel index c8aeb81a6..ecbc1a9d2 100644 --- a/tools/rustfmt/BUILD.bazel +++ b/tools/rustfmt/BUILD.bazel @@ -36,7 +36,7 @@ rust_library( alias( name = "rustfmt", actual = ":target_aware_rustfmt", - deprecation = "Prefer //tools/rustfmt:target_aware_rustfmt", + deprecation = "Prefer //tools/upstream_wrapper:rustfmt", visibility = ["//visibility:public"], ) @@ -85,20 +85,10 @@ rust_clippy( ], ) -# This is a small wrapper around the raw upstream rustfmt binary which can be `bazel run`. -rust_binary( +# Deprecated but present for compatibility. +alias( name = "upstream_rustfmt", - srcs = [ - "src/upstream_rustfmt_wrapper.rs", - ], - data = ["//rust/toolchain:current_rustfmt_toolchain_for_target"], - edition = "2018", - rustc_env = { - "RUSTFMT": "$(rlocationpath //rust/toolchain:current_rustfmt_toolchain_for_target)", - }, - toolchains = ["@rules_rust//rust/toolchain:current_rust_toolchain"], + actual = "//tools/upstream_wrapper:rustfmt", + deprecation = "Prefer //tools/upstream_wrapper:rustfmt", visibility = ["//visibility:public"], - deps = [ - "//tools/runfiles", - ], ) diff --git a/tools/rustfmt/src/upstream_rustfmt_wrapper.rs b/tools/rustfmt/src/upstream_rustfmt_wrapper.rs deleted file mode 100644 index 33c091aa1..000000000 --- a/tools/rustfmt/src/upstream_rustfmt_wrapper.rs +++ /dev/null @@ -1,38 +0,0 @@ -use std::path::PathBuf; -use std::process::{exit, Command}; - -fn main() { - let runfiles = runfiles::Runfiles::create().unwrap(); - - let rustfmt = runfiles::rlocation!(runfiles, env!("RUSTFMT")); - if !rustfmt.exists() { - panic!("rustfmt does not exist at: {}", rustfmt.display()); - } - - let working_directory = std::env::var_os("BUILD_WORKING_DIRECTORY") - .map(PathBuf::from) - .unwrap_or_else(|| std::env::current_dir().expect("Failed to get working directory")); - - let status = Command::new(rustfmt) - .current_dir(&working_directory) - .args(std::env::args_os().skip(1)) - .status() - .expect("Failed to run rustfmt"); - if let Some(exit_code) = status.code() { - exit(exit_code); - } - exit_for_signal(&status); - panic!("Child rustfmt process didn't exit or get a signal - don't know how to handle it"); -} - -#[cfg(target_family = "unix")] -fn exit_for_signal(status: &std::process::ExitStatus) { - use std::os::unix::process::ExitStatusExt; - if let Some(signal) = status.signal() { - exit(signal); - } -} - -#[cfg(not(target_family = "unix"))] -#[allow(unused)] -fn exit_for_signal(status: &std::process::ExitStatus) {} diff --git a/tools/upstream_wrapper/BUILD.bazel b/tools/upstream_wrapper/BUILD.bazel new file mode 100644 index 000000000..ad9ab7eaf --- /dev/null +++ b/tools/upstream_wrapper/BUILD.bazel @@ -0,0 +1,33 @@ +load("//rust:defs.bzl", "rust_binary") + +tools = { + "cargo": "//rust/toolchain:current_cargo_files", + "cargo_clippy": "//rust/toolchain:current_cargo_clippy_files", + "rustc": "//rust/toolchain:current_rustc_files", + "rustfmt": "//rust/toolchain:current_rustfmt_toolchain_for_target", +} + +all_tools = [target for target in tools.values()] + +[ + rust_binary( + name = tool_name, + srcs = [ + "src/main.rs", + ], + # Cargo calls out to the other tools. + # Make sure that they are included in the runfiles. + data = all_tools if tool_name == "cargo" else [target], + edition = "2018", + rustc_env = { + "WRAPPED_TOOL_NAME": tool_name, + "WRAPPED_TOOL_TARGET": "$(rlocationpath {})".format(target), + }, + toolchains = ["@rules_rust//rust/toolchain:current_rust_toolchain"], + visibility = ["//visibility:public"], + deps = [ + "//tools/runfiles", + ], + ) + for (tool_name, target) in tools.items() +] diff --git a/tools/upstream_wrapper/README.md b/tools/upstream_wrapper/README.md new file mode 100644 index 000000000..3b1c8975c --- /dev/null +++ b/tools/upstream_wrapper/README.md @@ -0,0 +1,15 @@ +# upstream_wrapper + +Wrap the binaries from the current toolchain so that +they can be easily invoked with `bazel run`: + +```bash +bazel run @rules_rust//tools/upstream_wrapper:cargo +bazel run @rules_rust//tools/upstream_wrapper:cargo -- clippy +bazel run @rules_rust//tools/upstream_wrapper:cargo -- fmt +bazel run @rules_rust//tools/upstream_wrapper:rustc -- main.rs +bazel run @rules_rust//tools/upstream_wrapper:rustfmt +``` + +Alternatively, look at the [bazel_env example](../../examples/bazel_env/) +to include them in the users path with direnv. diff --git a/tools/upstream_wrapper/src/main.rs b/tools/upstream_wrapper/src/main.rs new file mode 100644 index 000000000..6d6e167c2 --- /dev/null +++ b/tools/upstream_wrapper/src/main.rs @@ -0,0 +1,59 @@ +use std::ffi::OsString; +use std::path::PathBuf; +use std::process::{exit, Command}; + +const WRAPPED_TOOL_NAME: &str = env!("WRAPPED_TOOL_NAME"); +const WRAPPED_TOOL_TARGET: &str = env!("WRAPPED_TOOL_TARGET"); + +#[cfg(not(target_os = "windows"))] +const PATH_SEPARATOR: &str = ":"; +#[cfg(target_os = "windows")] +const PATH_SEPARATOR: &str = ";"; + +fn main() { + let runfiles = runfiles::Runfiles::create().unwrap(); + + let wrapped_tool_path = runfiles::rlocation!(runfiles, WRAPPED_TOOL_TARGET); + if !wrapped_tool_path.exists() { + panic!( + "{WRAPPED_TOOL_NAME} does not exist at: {}", + wrapped_tool_path.display() + ); + } + + let tool_directory = wrapped_tool_path + .parent() + .expect("parent directory of tool binary"); + let old_path = std::env::var_os("PATH").unwrap_or_default(); + let mut new_path = OsString::from(tool_directory); + new_path.push(PATH_SEPARATOR); + new_path.push(&old_path); + + let working_directory = std::env::var_os("BUILD_WORKING_DIRECTORY") + .map(PathBuf::from) + .unwrap_or_else(|| std::env::current_dir().expect("Failed to get working directory")); + + let status = Command::new(wrapped_tool_path) + .current_dir(&working_directory) + .args(std::env::args_os().skip(1)) + .env("PATH", new_path) + .status() + .unwrap_or_else(|e| panic!("Failed to run {WRAPPED_TOOL_NAME} {:#}", e)); + if let Some(exit_code) = status.code() { + exit(exit_code); + } + exit_for_signal(&status); + panic!("Child rustfmt process didn't exit or get a signal - don't know how to handle it"); +} + +#[cfg(target_family = "unix")] +fn exit_for_signal(status: &std::process::ExitStatus) { + use std::os::unix::process::ExitStatusExt; + if let Some(signal) = status.signal() { + exit(signal); + } +} + +#[cfg(not(target_family = "unix"))] +#[allow(unused)] +fn exit_for_signal(status: &std::process::ExitStatus) {}