Implement Clippy Aspect & Build Rule (#339)
To use as an aspect:
$ bazel build --aspects=@io_bazel_rules_rust//rust:rust.bzl%rust_clippy_aspect
--output_groups=clippy_checks //...
To use as a rule:
load("@io_bazel_rules_rust//rust:rust.bzl", "rust_clippy")
rust_clippy(
name = "my_clippy_target",
deps = [
":library_on_which_we_run_clippy",
],
)
diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml
index 1a0c597..e11304e 100644
--- a/.bazelci/presubmit.yml
+++ b/.bazelci/presubmit.yml
@@ -50,3 +50,18 @@
working_directory: examples
test_targets:
- //...
+ clippy_examples:
+ name: Clippy on Examples
+ platform: ubuntu1804
+ working_directory: examples
+ build_flags:
+ - "--aspects=@io_bazel_rules_rust//rust:rust.bzl%rust_clippy_aspect"
+ - "--output_groups=clippy_checks"
+ build_targets:
+ - //...
+ clippy_failure:
+ name: Negative Clippy Tests
+ platform: ubuntu1804
+ shell_commands:
+ - ./test/clippy/clippy_failure_test.sh
+
diff --git a/examples/ffi/rust_calling_c/src/matrix.rs b/examples/ffi/rust_calling_c/src/matrix.rs
index c9359a3..0c4c5fd 100644
--- a/examples/ffi/rust_calling_c/src/matrix.rs
+++ b/examples/ffi/rust_calling_c/src/matrix.rs
@@ -45,7 +45,7 @@
if matrix.is_null() {
panic!("Failed to allocate Matrix.");
}
- Matrix { matrix: matrix }
+ Matrix { matrix }
}
}
diff --git a/examples/proto/helloworld/greeter_server/greeter_server.rs b/examples/proto/helloworld/greeter_server/greeter_server.rs
index fa46bca..f2018ff 100644
--- a/examples/proto/helloworld/greeter_server/greeter_server.rs
+++ b/examples/proto/helloworld/greeter_server/greeter_server.rs
@@ -36,7 +36,7 @@
fn main() {
let mut server = grpc::ServerBuilder::<tls_api_stub::TlsAcceptor>::new();
- let port = u16::from_str(&env::args().nth(1).unwrap_or("50051".to_owned())).unwrap();
+ let port = u16::from_str(&env::args().nth(1).unwrap_or_else(|| "50051".to_owned())).unwrap();
server.http.set_port(port);
server.add_service(GreeterServer::new_service_def(GreeterImpl));
server.http.set_cpu_pool_threads(4);
diff --git a/examples/proto/helloworld/helloworld_test.rs b/examples/proto/helloworld/helloworld_test.rs
index 3608a2d..0324a83 100644
--- a/examples/proto/helloworld/helloworld_test.rs
+++ b/examples/proto/helloworld/helloworld_test.rs
@@ -56,17 +56,19 @@
.expect("Waiting for server startup");
line = line.trim().to_owned();
if line.starts_with(port_prefix) {
- port = u16::from_str(&line[port_prefix.len()..]).expect(&format!(
- "Invalid port number {}",
- &line[port_prefix.len()..]
- ))
+ port = u16::from_str(&line[port_prefix.len()..]).unwrap_or_else(|_|
+ panic!(
+ "Invalid port number {}",
+ &line[port_prefix.len()..]
+ )
+ )
}
}
}
println!("Started server on port {}", port);
ServerInfo {
process: c,
- port: port,
+ port,
}
}
diff --git a/rust/private/clippy.bzl b/rust/private/clippy.bzl
new file mode 100644
index 0000000..f1f248e
--- /dev/null
+++ b/rust/private/clippy.bzl
@@ -0,0 +1,215 @@
+# 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.
+
+load(
+ "@io_bazel_rules_rust//rust:private/rustc.bzl",
+ "CrateInfo",
+ "collect_deps",
+ "collect_inputs",
+ "construct_arguments",
+ "construct_compile_command",
+)
+load(
+ "@io_bazel_rules_rust//rust:private/rust.bzl",
+ "crate_root_src",
+)
+load("@io_bazel_rules_rust//rust:private/utils.bzl", "find_toolchain")
+
+_rust_extensions = [
+ "rs",
+]
+
+def _is_rust_target(srcs):
+ return any([src.extension in _rust_extensions for src in srcs])
+
+def _rust_sources(target, rule):
+ srcs = []
+ if "srcs" in dir(rule.attr):
+ srcs += [f for src in rule.attr.srcs for f in src.files.to_list()]
+ if "hdrs" in dir(rule.attr):
+ srcs += [f for hdr in rule.attr.hdrs for f in hdr.files.to_list()]
+ return [src for src in srcs if src.extension in _rust_extensions]
+
+def _clippy_aspect_impl(target, ctx):
+ if CrateInfo not in target:
+ return []
+ rust_srcs = _rust_sources(target, ctx.rule)
+ if rust_srcs == []:
+ return []
+
+ toolchain = find_toolchain(ctx)
+ root = crate_root_src(ctx.rule.attr, srcs = rust_srcs)
+ crate_info = target[CrateInfo]
+
+ dep_info, build_info = collect_deps(
+ ctx.label,
+ crate_info.deps,
+ crate_info.proc_macro_deps,
+ crate_info.aliases,
+ toolchain,
+ )
+
+ compile_inputs, out_dir = collect_inputs(
+ ctx,
+ ctx.rule.file,
+ ctx.rule.files,
+ toolchain,
+ crate_info,
+ dep_info,
+ build_info
+ )
+
+ args, env = construct_arguments(
+ ctx,
+ ctx.rule.file,
+ toolchain,
+ crate_info,
+ dep_info,
+ output_hash = repr(hash(root.path)),
+ rust_flags = [])
+
+ # A marker file indicating clippy has executed successfully.
+ # This file is necessary because "ctx.actions.run" mandates an output.
+ clippy_marker = ctx.actions.declare_file(ctx.label.name + "_clippy.ok")
+
+ command = construct_compile_command(
+ ctx,
+ toolchain.clippy_driver.path,
+ toolchain,
+ crate_info,
+ build_info,
+ out_dir,
+ ) + (" && touch %s" % clippy_marker.path)
+
+ # Deny the default-on clippy warning levels.
+ #
+ # If these are left as warnings, then Bazel will consider the execution
+ # result of the aspect to be "success", and Clippy won't be re-triggered
+ # unless the source file is modified.
+ args.add("-Dclippy::style")
+ args.add("-Dclippy::correctness");
+ args.add("-Dclippy::complexity");
+ args.add("-Dclippy::perf");
+
+ ctx.actions.run_shell(
+ command = command,
+ inputs = compile_inputs,
+ outputs = [clippy_marker],
+ env = env,
+ tools = [toolchain.clippy_driver],
+ arguments = [args],
+ mnemonic = "Clippy",
+ )
+
+ return [
+ OutputGroupInfo(clippy_checks = depset([clippy_marker])),
+ ]
+
+# Example: Run the clippy checker on all targets in the codebase.
+# bazel build --aspects=@io_bazel_rules_rust//rust:rust.bzl%rust_clippy_aspect \
+# --output_groups=clippy_checks \
+# //...
+rust_clippy_aspect = aspect(
+ fragments = ["cpp"],
+ host_fragments = ["cpp"],
+ attrs = {
+ "_cc_toolchain": attr.label(
+ default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
+ ),
+ },
+ toolchains = [
+ "@io_bazel_rules_rust//rust:toolchain",
+ "@bazel_tools//tools/cpp:toolchain_type"
+ ],
+ implementation = _clippy_aspect_impl,
+ doc = """
+Executes the clippy checker 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 `hello_lib/BUILD`:
+
+```python
+package(default_visibility = ["//visibility:public"])
+
+load("@io_bazel_rules_rust//rust:rust.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 analyzed with clippy using the following command:
+
+$ bazel build --aspects=@io_bazel_rules_rust//rust:rust.bzl%rust_clippy_aspect \
+ --output_groups=clippy_checks //hello_lib:all
+""",
+
+)
+
+def _rust_clippy_rule_impl(ctx):
+ files = depset([], transitive = [dep[OutputGroupInfo].clippy_checks for dep in ctx.attr.deps])
+ return [DefaultInfo(files = files)]
+
+rust_clippy = rule(
+ implementation = _rust_clippy_rule_impl,
+ attrs = {
+ 'deps': attr.label_list(aspects = [rust_clippy_aspect]),
+ },
+ doc = """
+Executes the clippy checker on a specific target.
+
+Similar to `rust_clippy_aspect`, but allows specifying a list of dependencies
+within the build system.
+
+For example, given the following example targets:
+
+```python
+package(default_visibility = ["//visibility:public"])
+
+load("@io_bazel_rules_rust//rust:rust.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 clippy can be set as a build target with the following:
+
+```python
+rust_clippy(
+ name = "hello_library_clippy",
+ testonly = True,
+ deps = [
+ ":hello_lib",
+ ":greeting_test",
+ ],
+)
+```
+""",
+)
diff --git a/rust/private/rust.bzl b/rust/private/rust.bzl
index 043dbdb..9ab873e 100644
--- a/rust/private/rust.bzl
+++ b/rust/private/rust.bzl
@@ -71,24 +71,28 @@
extension = extension,
)
-def _get_edition(ctx, toolchain):
- if getattr(ctx.attr, "edition"):
- return ctx.attr.edition
+def get_edition(attr, toolchain):
+ if getattr(attr, "edition"):
+ return attr.edition
else:
return toolchain.default_edition
-def _crate_root_src(ctx, file_name = "lib.rs"):
+def crate_root_src(attr, srcs, file_name = "lib.rs"):
"""Finds the source file for the crate root."""
- srcs = ctx.files.srcs
- crate_root = (
- ctx.file.crate_root or
- (srcs[0] if len(srcs) == 1 else None) or
- _shortest_src_with_basename(srcs, file_name) or
- _shortest_src_with_basename(srcs, ctx.attr.name + ".rs")
- )
+ crate_root = None
+ if hasattr(attr, "crate_root"):
+ if attr.crate_root:
+ crate_root = attr.crate_root.files.to_list()[0]
+
if not crate_root:
- file_names = [file_name, ctx.attr.name + ".rs"]
+ crate_root = (
+ (srcs[0] if len(srcs) == 1 else None) or
+ _shortest_src_with_basename(srcs, file_name) or
+ _shortest_src_with_basename(srcs, attr.name + ".rs")
+ )
+ if not crate_root:
+ file_names = [file_name, attr.name + ".rs"]
fail("No {} source file found.".format(" or ".join(file_names)), "srcs")
return crate_root
@@ -105,7 +109,7 @@
def _rust_library_impl(ctx):
# Find lib.rs
- lib_rs = _crate_root_src(ctx)
+ lib_rs = crate_root_src(ctx.attr, ctx.files.srcs)
toolchain = find_toolchain(ctx)
@@ -133,7 +137,7 @@
proc_macro_deps = ctx.attr.proc_macro_deps,
aliases = ctx.attr.aliases,
output = rust_lib,
- edition = _get_edition(ctx, toolchain),
+ edition = get_edition(ctx.attr, toolchain),
rustc_env = ctx.attr.rustc_env,
),
output_hash = output_hash,
@@ -156,13 +160,13 @@
crate_info = CrateInfo(
name = crate_name,
type = crate_type,
- root = _crate_root_src(ctx, "main.rs"),
+ root = crate_root_src(ctx.attr, ctx.files.srcs, file_name = "main.rs"),
srcs = ctx.files.srcs,
deps = ctx.attr.deps,
proc_macro_deps = ctx.attr.proc_macro_deps,
aliases = ctx.attr.aliases,
output = output,
- edition = _get_edition(ctx, toolchain),
+ edition = get_edition(ctx.attr, toolchain),
rustc_env = ctx.attr.rustc_env,
),
)
@@ -205,13 +209,13 @@
target = CrateInfo(
name = test_binary.basename,
type = "lib",
- root = _crate_root_src(ctx),
+ root = crate_root_src(ctx.attr, ctx.files.srcs),
srcs = ctx.files.srcs,
deps = ctx.attr.deps,
proc_macro_deps = ctx.attr.proc_macro_deps,
aliases = ctx.attr.aliases,
output = test_binary,
- edition = _get_edition(ctx, toolchain),
+ edition = get_edition(ctx.attr, toolchain),
rustc_env = ctx.attr.rustc_env,
)
diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl
index 48c8079..8a9d160 100644
--- a/rust/private/rustc.bzl
+++ b/rust/private/rustc.bzl
@@ -238,30 +238,33 @@
def _add_out_dir_to_compile_inputs(
ctx,
+ file,
build_info,
compile_inputs):
- out_dir = _create_out_dir_action(ctx, build_info.out_dir if build_info else None)
+ out_dir = _create_out_dir_action(ctx, file, build_info.out_dir if build_info else None)
if out_dir:
compile_inputs = depset([out_dir], transitive = [compile_inputs])
return compile_inputs, out_dir
-def _collect_inputs(
+def collect_inputs(
ctx,
+ file,
+ files,
toolchain,
crate_info,
dep_info,
build_info):
- linker_script = getattr(ctx.file, "linker_script") if hasattr(ctx.file, "linker_script") else None
+ linker_script = getattr(file, "linker_script") if hasattr(file, "linker_script") else None
if (len(BAZEL_VERSION) == 0 or
versions.is_at_least("0.25.0", BAZEL_VERSION)):
linker_depset = find_cpp_toolchain(ctx).all_files
else:
- linker_depset = depset(ctx.files._cc_toolchain)
+ linker_depset = depset(files._cc_toolchain)
compile_inputs = depset(
crate_info.srcs +
- getattr(ctx.files, "data", []) +
+ getattr(files, "data", []) +
dep_info.transitive_libs +
[toolchain.rustc] +
toolchain.crosstool_files +
@@ -273,18 +276,19 @@
linker_depset,
],
)
- return _add_out_dir_to_compile_inputs(ctx, build_info, compile_inputs)
+ return _add_out_dir_to_compile_inputs(ctx, file, build_info, compile_inputs)
-def _construct_arguments(
+def construct_arguments(
ctx,
+ file,
toolchain,
crate_info,
dep_info,
output_hash,
rust_flags):
- output_dir = crate_info.output.dirname
+ output_dir = getattr(crate_info.output, "dirname") if hasattr(crate_info.output, "dirname") else None
- linker_script = getattr(ctx.file, "linker_script") if hasattr(ctx.file, "linker_script") else None
+ linker_script = getattr(file, "linker_script") if hasattr(file, "linker_script") else None
env = _get_rustc_env(ctx, toolchain)
@@ -296,7 +300,8 @@
# Mangle symbols to disambiguate crates with the same name
extra_filename = "-" + output_hash if output_hash else ""
args.add("--codegen=metadata=" + extra_filename)
- args.add("--out-dir=" + output_dir)
+ if output_dir:
+ args.add("--out-dir=" + output_dir)
args.add("--codegen=extra-filename=" + extra_filename)
compilation_mode = get_compilation_mode_opts(ctx, toolchain)
@@ -313,7 +318,6 @@
# Gets the paths to the folders containing the standard library (or libcore)
rust_lib_paths = depset([file.dirname for file in toolchain.rust_lib.files.to_list()]).to_list()
-
# Tell Rustc where to find the standard library
args.add_all(rust_lib_paths, before_each = "-L", format_each = "%s")
@@ -377,9 +381,13 @@
package_dir = ctx.build_file_path[:ctx.build_file_path.rfind("/")]
manifest_dir_env = "CARGO_MANIFEST_DIR=$(pwd)/{} ".format(package_dir)
- return out_dir_env + manifest_dir_env
+ # This empty value satisfies Clippy, which otherwise complains about the
+ # sysroot being undefined.
+ sysroot_env= "SYSROOT= "
-def _construct_compile_command(
+ return out_dir_env + manifest_dir_env + sysroot_env
+
+def construct_compile_command(
ctx,
command,
toolchain,
@@ -399,7 +407,7 @@
# not the _ version. So we rename the rustc-generated file (with _s) to
# have -s if needed.
maybe_rename = ""
- if crate_info.type == "bin":
+ if crate_info.type == "bin" and crate_info.output != None:
generated_file = crate_info.name
if toolchain.target_arch == "wasm32":
generated_file = generated_file + ".wasm"
@@ -411,7 +419,7 @@
return '{}{}{} "$@" --remap-path-prefix="$(pwd)"=__bazel_redacted_pwd{}{}'.format(
rustc_env_expansion,
command_env,
- toolchain.rustc.path,
+ command,
build_flags_expansion,
maybe_rename,
)
@@ -439,16 +447,19 @@
toolchain,
)
- compile_inputs, out_dir = _collect_inputs(
+ compile_inputs, out_dir = collect_inputs(
ctx,
+ ctx.file,
+ ctx.files,
toolchain,
crate_info,
dep_info,
build_info
)
- args, env = _construct_arguments(
+ args, env = construct_arguments(
ctx,
+ ctx.file,
toolchain,
crate_info,
dep_info,
@@ -456,7 +467,7 @@
rust_flags
)
- command = _construct_compile_command(
+ command = construct_compile_command(
ctx,
toolchain.rustc.path,
toolchain,
@@ -508,8 +519,8 @@
if crate.edition != "2015":
args.add("--edition={}".format(crate.edition))
-def _create_out_dir_action(ctx, build_info_out_dir = None):
- tar_file = getattr(ctx.file, "out_dir_tar", None)
+def _create_out_dir_action(ctx, file, build_info_out_dir = None):
+ tar_file = getattr(file, "out_dir_tar", None)
if not tar_file:
return build_info_out_dir
else:
diff --git a/rust/rust.bzl b/rust/rust.bzl
index 2881ca1..98723db 100644
--- a/rust/rust.bzl
+++ b/rust/rust.bzl
@@ -28,6 +28,11 @@
"@io_bazel_rules_rust//rust:private/rustdoc_test.bzl",
_rust_doc_test = "rust_doc_test",
)
+load(
+ "@io_bazel_rules_rust//rust:private/clippy.bzl",
+ _rust_clippy_aspect = "rust_clippy_aspect",
+ _rust_clippy = "rust_clippy",
+)
rust_library = _rust_library
""" See @io_bazel_rules_rust//rust:private/rust.bzl for a complete description. """
@@ -48,4 +53,10 @@
""" See @io_bazel_rules_rust//rust:private/rustdoc.bzl for a complete description. """
rust_doc_test = _rust_doc_test
-""" See @io_bazel_rules_rust//rust:private/rustdoc.bzl for a complete description. """
+""" See @io_bazel_rules_rust//rust:private/rustdoc_test.bzl for a complete description. """
+
+rust_clippy_aspect = _rust_clippy_aspect
+""" See @io_bazel_rules_rust//rust:private/clippy.bzl for a complete description. """
+
+rust_clippy = _rust_clippy
+""" See @io_bazel_rules_rust//rust:private/clippy.bzl for a complete description. """
diff --git a/test/clippy/BUILD b/test/clippy/BUILD
new file mode 100644
index 0000000..a56c1e9
--- /dev/null
+++ b/test/clippy/BUILD
@@ -0,0 +1,86 @@
+load(
+ "//rust:rust.bzl",
+ "rust_binary",
+ "rust_clippy",
+ "rust_library",
+ "rust_test",
+)
+
+# Declaration of passing targets.
+
+rust_binary(
+ name = "ok_binary",
+ srcs = ["src/main.rs"],
+ edition = "2018",
+)
+
+rust_library(
+ name = "ok_library",
+ srcs = ["src/lib.rs"],
+ edition = "2018",
+)
+
+rust_test(
+ name = "ok_test",
+ srcs = ["src/lib.rs"],
+ edition = "2018",
+)
+
+# Clippy analysis of passing targets.
+
+rust_clippy(
+ name = "ok_binary_clippy",
+ deps = [":ok_binary"],
+)
+
+rust_clippy(
+ name = "ok_library_clippy",
+ deps = [":ok_library"],
+)
+
+rust_clippy(
+ name = "ok_test_clippy",
+ deps = [":ok_test"],
+ testonly = True,
+)
+
+# Declaration of failing targets.
+
+rust_binary(
+ name = "bad_binary",
+ srcs = ["bad_src/main.rs"],
+ edition = "2018",
+)
+
+rust_library(
+ name = "bad_library",
+ srcs = ["bad_src/lib.rs"],
+ edition = "2018",
+)
+
+rust_test(
+ name = "bad_test",
+ srcs = ["bad_src/lib.rs"],
+ edition = "2018",
+)
+
+# Clippy analysis of failing targets.
+
+rust_clippy(
+ name = "bad_binary_clippy",
+ deps = [":bad_binary"],
+ tags = [ "manual" ],
+)
+
+rust_clippy(
+ name = "bad_library_clippy",
+ deps = [":bad_library"],
+ tags = [ "manual" ],
+)
+
+rust_clippy(
+ name = "bad_test_clippy",
+ deps = [":bad_test"],
+ tags = [ "manual" ],
+ testonly = True,
+)
diff --git a/test/clippy/README.md b/test/clippy/README.md
new file mode 100644
index 0000000..40db34f
--- /dev/null
+++ b/test/clippy/README.md
@@ -0,0 +1,12 @@
+# Clippy Tests
+
+This directory tests integration of the Clippy static analyzer aspect.
+
+It is split into a couple different directories:
+
+* [src](src) contains a simple binary, library and test which should compile
+successfully when built normally, and pass without error when analyzed
+with clippy.
+* [bad\_src](bad_src) also contains a binary, library, and test which compile
+normally, but which intentionally contain "junk code" which will trigger
+Clippy lints.
diff --git a/test/clippy/bad_src/lib.rs b/test/clippy/bad_src/lib.rs
new file mode 100644
index 0000000..ec29e04
--- /dev/null
+++ b/test/clippy/bad_src/lib.rs
@@ -0,0 +1,19 @@
+pub fn foobar() {
+ assert!(true);
+ loop {
+ println!("{}", "Hello World");
+ break;
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn test_works() {
+ assert!(true);
+ loop {
+ println!("{}", "Hello World");
+ break;
+ }
+ }
+}
diff --git a/test/clippy/bad_src/main.rs b/test/clippy/bad_src/main.rs
new file mode 100644
index 0000000..cf9d4e6
--- /dev/null
+++ b/test/clippy/bad_src/main.rs
@@ -0,0 +1,6 @@
+fn main() {
+ loop {
+ println!("{}", "Hello World");
+ break;
+ }
+}
diff --git a/test/clippy/clippy_failure_test.sh b/test/clippy/clippy_failure_test.sh
new file mode 100755
index 0000000..b6c0002
--- /dev/null
+++ b/test/clippy/clippy_failure_test.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+
+# Runs Bazel build commands over clippy rules, where some are expected
+# to fail.
+#
+# Can be run from anywhere within the rules_rust workspace.
+
+set -euo pipefail
+
+# Executes a bazel build command and handles the return value, exiting
+# upon seeing an error.
+#
+# Takes two arguments:
+# ${1}: The expected return code.
+# ${2}: The target within "//test/clippy" to be tested.
+function check_build_result() {
+ local ret=0
+ echo -n "Testing ${2}... "
+ (bazel build //test/clippy:"${2}" &> /dev/null) || ret="$?" && true
+ if [[ "${ret}" -ne "${1}" ]]; then
+ echo "FAIL: Unexpected return code [saw: ${ret}, want: ${1}] building target //test/clippy:${2}"
+ echo " Run \"bazel build //test/clippy:${2}\" to see the output"
+ exit 1
+ else
+ echo "OK"
+ fi
+}
+
+function test_all() {
+ local -r BUILD_OK=0
+ local -r BUILD_FAILED=1
+ local -r TEST_FAIL=3
+
+ check_build_result $BUILD_OK ok_binary_clippy
+ check_build_result $BUILD_OK ok_library_clippy
+ check_build_result $BUILD_OK ok_test_clippy
+ check_build_result $BUILD_FAILED bad_binary_clippy
+ check_build_result $BUILD_FAILED bad_library_clippy
+ check_build_result $BUILD_FAILED bad_test_clippy
+}
+
+test_all
diff --git a/test/clippy/src/lib.rs b/test/clippy/src/lib.rs
new file mode 100644
index 0000000..63fb7fd
--- /dev/null
+++ b/test/clippy/src/lib.rs
@@ -0,0 +1,11 @@
+pub fn foo() {
+ println!("Hello world");
+}
+
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn it_works() {
+ assert_eq!(2 + 2, 4);
+ }
+}
diff --git a/test/clippy/src/main.rs b/test/clippy/src/main.rs
new file mode 100644
index 0000000..5bf256e
--- /dev/null
+++ b/test/clippy/src/main.rs
@@ -0,0 +1,3 @@
+fn main() {
+ println!("Hello world");
+}