From ab141f8d1035eb30bbb9dc09d89ea6a3ba83cc0f Mon Sep 17 00:00:00 2001 From: David Chen Date: Mon, 5 Oct 2015 12:21:32 +0000 Subject: [rust] Add rust_bench_test and rust_doc_test rules and improve usability of rust_test rule. * Add rust_bench_test rule to run benchmark tests * Add rust_doc_test rule to run Rust documentation tests. * Enable rust_test and rust_bench_test to depend directly on a rust_library target. * Rename rust_docs rule to rust_doc for consistency. RELNOTES: [rust] Add rust_bench_test and rust_doc_test rules and improve usability of rust_test tule. -- MOS_MIGRATED_REVID=104648497 --- WORKSPACE | 2 + examples/rust/fibonacci/BUILD | 36 ++ examples/rust/fibonacci/benches/fibonacci_bench.rs | 25 + examples/rust/fibonacci/src/lib.rs | 48 ++ examples/rust/hello_lib/BUILD | 26 +- examples/rust/hello_lib/src/greeter.rs | 11 + examples/rust/hello_world/BUILD | 6 +- tools/build_rules/rust/README.md | 719 +++++++++++++++++---- tools/build_rules/rust/rust.BUILD | 28 +- tools/build_rules/rust/rust.bzl | 263 ++++++-- 10 files changed, 973 insertions(+), 191 deletions(-) create mode 100644 examples/rust/fibonacci/BUILD create mode 100644 examples/rust/fibonacci/benches/fibonacci_bench.rs create mode 100644 examples/rust/fibonacci/src/lib.rs diff --git a/WORKSPACE b/WORKSPACE index b3aebe8e76..f9d107a03c 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -47,6 +47,7 @@ new_http_archive( url = "https://static.rust-lang.org/dist/rust-1.3.0-x86_64-unknown-linux-gnu.tar.gz", sha256 = "fa755b6331ff7554e6e8545ee20af7897b0adc65f471dd24ae8a467a944755b4", build_file = "tools/build_rules/rust/rust.BUILD", + strip_prefix = "rust-1.3.0-x86_64-unknown-linux-gnu", ) new_http_archive( @@ -54,6 +55,7 @@ new_http_archive( url = "https://static.rust-lang.org/dist/rust-1.3.0-x86_64-apple-darwin.tar.gz", sha256 = "bfeac876e22cc5fe63a250644ce1a6f3892c13a5461131a881419bd06fcb2011", build_file = "tools/build_rules/rust/rust.BUILD", + strip_prefix = "rust-1.3.0-x86_64-apple-darwin", ) # In order to run the Android integration tests, uncomment these rules, point diff --git a/examples/rust/fibonacci/BUILD b/examples/rust/fibonacci/BUILD new file mode 100644 index 0000000000..a29e2123ba --- /dev/null +++ b/examples/rust/fibonacci/BUILD @@ -0,0 +1,36 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "/tools/build_rules/rust/rust", + "rust_library", + "rust_test", + "rust_bench_test", + "rust_doc", + "rust_doc_test", +) + +rust_library( + name = "fibonacci", + srcs = ["src/lib.rs"], +) + +rust_test( + name = "fibonacci_test", + deps = [":fibonacci"], +) + +rust_bench_test( + name = "fibonacci_bench", + srcs = ["benches/fibonacci_bench.rs"], + deps = [":fibonacci"], +) + +rust_doc( + name = "fibonacci_doc", + dep = ":fibonacci", +) + +rust_doc_test( + name = "fibonacci_doc_test", + dep = ":fibonacci", +) diff --git a/examples/rust/fibonacci/benches/fibonacci_bench.rs b/examples/rust/fibonacci/benches/fibonacci_bench.rs new file mode 100644 index 0000000000..80aa1a84ce --- /dev/null +++ b/examples/rust/fibonacci/benches/fibonacci_bench.rs @@ -0,0 +1,25 @@ +// 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. + +#![feature(test)] + +extern crate test; +extern crate fibonacci; + +use test::Bencher; + +#[bench] +fn bench_fibonacci(b: &mut Bencher) { + b.iter(|| fibonacci::fibonacci(40)); +} diff --git a/examples/rust/fibonacci/src/lib.rs b/examples/rust/fibonacci/src/lib.rs new file mode 100644 index 0000000000..d8ae6e3fca --- /dev/null +++ b/examples/rust/fibonacci/src/lib.rs @@ -0,0 +1,48 @@ +// 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. + +/// Returns the nth Fibonacci number. +/// +/// # Examples +/// +/// ``` +/// fibonacci::fibonacci(5) +/// ``` +pub fn fibonacci(n: u64) -> u64 { + if n < 2 { + return n; + } + let mut n1: u64 = 0; + let mut n2: u64 = 1; + for _ in 1..n { + let sum = n1 + n2; + n1 = n2; + n2 = sum; + } + n2 +} + +#[cfg(test)] +mod test { + use super::fibonacci; + + #[test] + fn test_fibonacci() { + let numbers : Vec = + vec![0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]; + for (i, number) in numbers.iter().enumerate() { + assert_eq!(*number, fibonacci(i as u64)); + } + } +} diff --git a/examples/rust/hello_lib/BUILD b/examples/rust/hello_lib/BUILD index a075f8b168..90941e6a42 100644 --- a/examples/rust/hello_lib/BUILD +++ b/examples/rust/hello_lib/BUILD @@ -1,6 +1,12 @@ package(default_visibility = ["//visibility:public"]) -load("/tools/build_rules/rust/rust", "rust_library", "rust_docs", "rust_test") +load( + "/tools/build_rules/rust/rust", + "rust_library", + "rust_test", + "rust_doc", + "rust_doc_test", +) rust_library( name = "hello_lib", @@ -10,13 +16,23 @@ rust_library( ], ) -rust_docs( - name = "hello_lib_docs", - dep = ":hello_lib", +rust_test( + name = "hello_lib_test", + deps = [":hello_lib"], ) rust_test( - name = "greeting", + name = "greeting_test", srcs = ["tests/greeting.rs"], deps = [":hello_lib"], ) + +rust_doc( + name = "hello_lib_doc", + dep = ":hello_lib", +) + +rust_doc_test( + name = "hello_lib_doc_test", + dep = ":hello_lib", +) diff --git a/examples/rust/hello_lib/src/greeter.rs b/examples/rust/hello_lib/src/greeter.rs index 2a7d7153b9..bf332e4bd1 100644 --- a/examples/rust/hello_lib/src/greeter.rs +++ b/examples/rust/hello_lib/src/greeter.rs @@ -60,3 +60,14 @@ impl Greeter { println!("{} {}", &self.greeting, thing); } } + +#[cfg(test)] +mod test { + use super::Greeter; + + #[test] + fn test_greeting() { + let hello = Greeter::new("Hi"); + assert_eq!("Hi Rust", hello.greeting("Rust")); + } +} diff --git a/examples/rust/hello_world/BUILD b/examples/rust/hello_world/BUILD index ccd87f1b93..1f5365675b 100644 --- a/examples/rust/hello_world/BUILD +++ b/examples/rust/hello_world/BUILD @@ -1,6 +1,6 @@ package(default_visibility = ["//visibility:public"]) -load("/tools/build_rules/rust/rust", "rust_binary", "rust_docs") +load("/tools/build_rules/rust/rust", "rust_binary", "rust_doc") rust_binary( name = "hello_world", @@ -8,7 +8,7 @@ rust_binary( deps = ["//examples/rust/hello_lib"], ) -rust_docs( - name = "hello_world_docs", +rust_doc( + name = "hello_world_doc", dep = ":hello_world", ) diff --git a/tools/build_rules/rust/README.md b/tools/build_rules/rust/README.md index 536c10f837..a040ad306f 100644 --- a/tools/build_rules/rust/README.md +++ b/tools/build_rules/rust/README.md @@ -1,18 +1,16 @@ # Rust Rules for Bazel +* [`rust_library`](#rust_library) +* [`rust_binary`](#rust_binary) +* [`rust_test`](#rust_test) +* [`rust_bench_test`](#rust_bench_test) +* [`rust_doc`](#rust_doc) +* [`rust_doc_test`](#rust_doc_test) + ## Overview These build rules are used for building [Rust][rust] projects with Bazel. -* [Setup](#setup) -* [Basic Example](#basic-example) -* [Build Rule Reference](#reference) - * [`rust_library`](#reference-rust_library) - * [`rust_binary`](#reference-rust_binary) - * [`rust_test`](#reference-rust_test) - * [`rust_docs`](#reference-rust_docs) -* [Roadmap](#roadmap) - [rust]: http://www.rust-lang.org/ @@ -21,8 +19,121 @@ These build rules are used for building [Rust][rust] projects with Bazel. To use the Rust rules, simply copy the contents of `rust.WORKSPACE` to your `WORKSPACE` file. - -## Basic Example + +## Roadmap + +* Add `rust_toolchain` rule to make it easy to use a custom Rust toolchain. +* Add tool for taking `Cargo.toml` and generating a `WORKSPACE` file with + workspace rules for pulling external dependencies. +* Improve expressiveness of features and support for [Cargo's feature + groups](http://doc.crates.io/manifest.html#the-[features]-section). +* Add `cargo_crate` workspace rule for pulling crates from + [Cargo](https://crates.io/). + + +## rust_library + +```python +rust_library(name, srcs, deps, data, crate_features, rustc_flags) +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescription
name + Name, required +

A unique name for this rule.

+

+ This name will also be used as the name of the library crate built by + this rule. +

+
srcs + List of labels, required +

List of Rust .rs source files used to build the + library.

+

+ If srcs contains more than one file, then there must be + a file either named lib.rs. Otherwise, + crate_root must be set to the source file that is the + root of the crate to be passed to rustc to build this + crate. +

+
crate_root + Label, optional +

+ The file that will be passed to rustc to be used for + building this crate. +

+

+ If crate_root is not set, then this rule will look for + a lib.rs file or the single file in srcs + if srcs contains only one file. +

+
deps + List of labels, optional +

List of other libraries to be linked to this library target.

+

+ These can be either other rust_library targets or + cc_library targets if linking a native library. +

+
data + List of labels, optional +

List of files used by this rule at runtime.

+

+ This attribute can be used to specify any data files that are embedded + into the library, such as via the + include_str! + macro. +

+
crate_features + List of strings, optional +

List of features to enable for this crate.

+

+ Features are defined in the code using the + #[cfg(feature = "foo")] configuration option. The + features listed here will be passed to rustc with + --cfg feature="${feature_name}" flags. +

+
rustc_flags + List of strings, optional +

List of compiler flags passed to rustc.

+
+ +### Example Suppose you have the following directory structure for a simple Rust library crate: @@ -88,69 +199,13 @@ Target //examples/rust/hello_lib:hello_lib up-to-date: INFO: Elapsed time: 1.245s, Critical Path: 1.01s ``` -Now, let's add a binary crate that uses the `hello_lib` library. The directory -structure now looks like the following: + +## rust_binary ``` -[workspace]/ - WORKSPACE - hello_lib/ - BUILD - src/ - greeter.rs - lib.rs - hello_world/ - BUILD - src/ - main.rs +rust_binary(name, srcs, deps, data, crate_features, rustc_flags) ``` -`hello_world/src/main.rs`: - -```rust -extern crate hello_lib; - -use hello_lib::greeter; - -fn main() { - let hello = greeter::Greeter::new("Hello"); - hello.greet("world"); -} -``` - -`hello_world/BUILD`: - -```python -load("/tools/build_rules/rust/rust", "rust_binary") - -rust_binary( - name = "hello_world", - srcs = ["src/main.rs"], - deps = ["//hello_lib"], -) -``` - -Build and run `hello_world`: - -``` -$ bazel run //hello_world -INFO: Found 1 target... -Target //examples/rust/hello_world:hello_world up-to-date: - bazel-bin/examples/rust/hello_world/hello_world -INFO: Elapsed time: 1.308s, Critical Path: 1.22s - -INFO: Running command line: bazel-bin/examples/rust/hello_world/hello_world -Hello world -``` - - -## Build Rule Reference - - -### `rust_library` - -`rust_library(name, srcs, deps, data, crate_features, rustc_flags)` - @@ -165,7 +220,7 @@ Hello world Name, required

A unique name for this rule.

- This name will also be used as the name of the library crate built by + This name will also be used as the name of the binary crate built by this rule.

@@ -175,24 +230,38 @@ Hello world + + + + @@ -232,10 +301,98 @@ Hello world
List of labels, required

List of Rust .rs source files used to build the - library.

+ binary.

- There must be a file either named lib.rs or with a name - matching the name of this crate. For example, if the name of a given - rule is foo, then there must be a file named - lib.rs or foo.rs in srcs. - This file will be passed to rustc as the crate root. + If srcs contains more than one file, then there must be + a file either named main.rs. Otherwise, + crate_root must be set to the source file that is the + root of the crate to be passed to rustc to build this + crate.

crate_root + Label, optional +

+ The file that will be passed to rustc to be used for + building this crate. +

+

+ If crate_root is not set, then this rule will look for + a main.rs file or the single file in srcs + if srcs contains only one file. +

+
deps List of labels, optional

List of other libraries to be linked to this library target.

- These can be either other rust_library targets or - cc_library targets if linking a native library. + These must be rust_library targets.

- -### `rust_binary` +### Example + +Suppose you have the following directory structure for a Rust project with a +library crate, `hello_lib`, and a binary crate, `hello_world` that uses the +`hello_lib` library: + +``` +[workspace]/ + WORKSPACE + hello_lib/ + BUILD + src/ + lib.rs + hello_world/ + BUILD + src/ + main.rs +``` + +`hello_lib/src/lib.rs`: + +```rust +pub struct Greeter { + greeting: String, +} + +impl Greeter { + pub fn new(greeting: &str) -> Greeter { + Greeter { greeting: greeting.to_string(), } + } + + pub fn greet(&self, thing: &str) { + println!("{} {}", &self.greeting, thing); + } +} +``` + +`hello_lib/BUILD`: + +```python +package(default_visibility = ["//visibility:public"]) + +load("/tools/build_rules/rust/rust", "rust_library") + +rust_library( + name = "hello_lib", + srcs = ["src/lib.rs"], +) +``` + +`hello_world/src/main.rs`: + +```rust +extern crate hello_lib; + +fn main() { + let hello = hello_lib::Greeter::new("Hello"); + hello.greet("world"); +} +``` + +`hello_world/BUILD`: + +```python +load("/tools/build_rules/rust/rust", "rust_binary") + +rust_binary( + name = "hello_world", + srcs = ["src/main.rs"], + deps = ["//hello_lib"], +) +``` + +Build and run `hello_world`: -`rust_binary(name, srcs, deps, data, crate_features, rustc_flags)` +``` +$ bazel run //hello_world +INFO: Found 1 target... +Target //examples/rust/hello_world:hello_world up-to-date: + bazel-bin/examples/rust/hello_world/hello_world +INFO: Elapsed time: 1.308s, Critical Path: 1.22s + +INFO: Running command line: bazel-bin/examples/rust/hello_world/hello_world +Hello world +``` + + +## rust_test + +```python +rust_test(name, srcs, deps, data, crate_features, rustc_flags) +``` @@ -251,8 +408,8 @@ Hello world Name, required

A unique name for this rule.

- This name will also be used as the name of the binary crate built by - this rule. + This name will also be used as the name of the binary test crate + built by this rule.

@@ -261,22 +418,36 @@ Hello world + + + +
List of labels, required

List of Rust .rs source files used to build the - binary.

+ library.

- There must be a file either named main.rs or with a name - matching the name of this crate that contains the main - function. For example, if the name of a given - rule is foo, then there must be a file named - main.rs or foo.rs in srcs. - This file will be passed to rustc as the crate root. + If srcs contains more than one file, then there must be + a file either named lib.rs. Otherwise, + crate_root must be set to the source file that is the + root of the crate to be passed to rustc to build this + crate.

crate_root + Label, optional +

+ The file that will be passed to rustc to be used for + building this crate. +

+

+ If crate_root is not set, then this rule will look for + a lib.rs file or the single file in srcs + if srcs contains only one file. +

+
deps List of labels, optional -

List of other libraries to be linked to this library target.

+

List of other libraries to be linked to this test target.

These must be rust_library targets.

@@ -318,13 +489,146 @@ Hello world
- -### `rust_test` +### Example + +Suppose you have the following directory structure for a Rust library crate +with unit test code in the library sources: + +``` +[workspace]/ + WORKSPACE + hello_lib/ + BUILD + src/ + lib.rs +``` + +`hello_lib/src/lib.rs`: + +```rust +pub struct Greeter { + greeting: String, +} + +impl Greeter { + pub fn new(greeting: &str) -> Greeter { + Greeter { greeting: greeting.to_string(), } + } + + pub fn greet(&self, thing: &str) { + println!("{} {}", &self.greeting, thing); + } +} + +#[cfg(test)] +mod test { + use super::Greeter; + + #[test] + fn test_greeting() { + let hello = Greeter::new("Hi"); + assert_eq!("Hi Rust", hello.greeting("Rust")); + } +} +``` + +To build and run the tests, simply add a `rust_test` rule with no `srcs` and +only depends on the `hello_lib` `rust_library` target: + +`hello_lib/BUILD`: ```python -rust_test(name, srcs, deps, data, crate_features, rustc_flags) +package(default_visibility = ["//visibility:public"]) + +load("/tools/build_rules/rust/rust", "rust_library", "rust_test") + +rust_library( + name = "hello_lib", + srcs = ["src/lib.rs"], +) + +rust_test( + name = "hello_lib_test", + deps = [":hello_lib"], +) +``` + +Run the test with `bazel build //hello_lib:hello_lib_test`. + +### Example: `test` directory + +Integration tests that live in the [`tests` directory][int-tests], they are +essentially built as separate crates. Suppose you have the following directory +structure where `greeting.rs` is an integration test for the `hello_lib` +library crate: + +[int-tests]: http://doc.rust-lang.org/book/testing.html#the-tests-directory + +``` +[workspace]/ + WORKSPACE + hello_lib/ + BUILD + src/ + lib.rs + tests/ + greeting.rs +``` + +`hello_lib/tests/greeting.rs`: + +```rust +extern crate hello_lib; + +use hello_lib; + +#[test] +fn test_greeting() { + let hello = greeter::Greeter::new("Hello"); + assert_eq!("Hello world", hello.greeting("world")); +} +``` + +To build the `greeting.rs` integration test, simply add a `rust_test` target +with `greeting.rs` in `srcs` and a dependency on the `hello_lib` target: + +`hello_lib/BUILD`: + +```python +package(default_visibility = ["//visibility:public"]) + +load("/tools/build_rules/rust/rust", "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"], +) +``` + +Run the test with `bazel build //hello_lib:hello_lib_test`. + + +## rust_bench_test + +```python +rust_bench_test(name, srcs, deps, data, crate_features, rustc_flags) ``` +**Warning**: This rule is currently experimental. [Rust Benchmark +tests][rust-bench] require the `Bencher` interface in the unstable `libtest` +crate, which is behind the `test` unstable feature gate. As a result, using +this rule would require using a nightly binary release of Rust. A +`rust_toolchain` rule will be added in the [near future](#roadmap) to make it +easy to use a custom Rust toolchain, such as a nightly release. + +[rust-bench]: https://doc.rust-lang.org/book/benchmark-tests.html + @@ -351,14 +655,29 @@ rust_test(name, srcs, deps, data, crate_features, rustc_flags)

List of Rust .rs source files used to build the library.

- There must be a file either with a name matching the name of this - test. For example, if the name of a rust_test rule is - foo, then there must be a file named foo.rs - in srcs. This file will be passed to rustc - as the crate root. + If srcs contains more than one file, then there must be + a file either named lib.rs. Otherwise, + crate_root must be set to the source file that is the + root of the crate to be passed to rustc to build this + crate.

+ + + +
crate_root + Label, optional +

+ The file that will be passed to rustc to be used for + building this crate. +

+

+ If crate_root is not set, then this rule will look for + a lib.rs file or the single file in srcs + if srcs contains only one file. +

+
deps @@ -405,11 +724,84 @@ rust_test(name, srcs, deps, data, crate_features, rustc_flags)
- -### `rust_docs` +### Example + +Suppose you have the following directory structure for a Rust project with a +library crate, `fibonacci` with benchmarks under the `benches/` directory: + +``` +[workspace]/ + WORKSPACE + fibonacci/ + BUILD + src/ + lib.rs + benches/ + fibonacci_bench.rs +``` + +`fibonacci/src/lib.rs`: + +```rust +pub fn fibonacci(n: u64) -> u64 { + if n < 2 { + return n; + } + let mut n1: u64 = 0; + let mut n2: u64 = 1; + for _ in 1..n { + let sum = n1 + n2; + n1 = n2; + n2 = sum; + } + n2 +} +``` + +`fibonacci/benches/fibonacci_bench.rs`: + +```rust +#![feature(test)] + +extern crate test; +extern crate fibonacci; + +use test::Bencher; + +#[bench] +fn bench_fibonacci(b: &mut Bencher) { + b.iter(|| fibonacci::fibonacci(40)); +} +``` + +To build the benchmark test, simply add a `rust_bench_test` target: + +`fibonacci/BUILD`: + +```python +package(default_visibility = ["//visibility:public"]) + +load("/tools/build_rules/rust/rust", "rust_library", "rust_bench_test") + +rust_library( + name = "fibonacci", + srcs = ["src/lib.rs"], +) + +rust_bench_test( + name = "fibonacci_bench", + srcs = ["benches/fibonacci_bench.rs"], + deps = [":fibonacci"], +) +``` + +Run the benchmark test using: `bazel build //fibonacci:fibonacci_bench`. + + +## rust_doc ```python -rust_docs(name, dep, markdown_css, html_in_header, html_before_content, html_after_content) +rust_doc(name, dep, markdown_css, html_in_header, html_before_content, html_after_content) ``` @@ -433,7 +825,7 @@ rust_docs(name, dep, markdown_css, html_in_header, html_before_content, html_aft Label, required

The label of the target to generate code documentation for.

- rust_docs can generate HTML code documentation for the + rust_doc can generate HTML code documentation for the source files of rust_library or rust_binary targets.

@@ -473,20 +865,113 @@ rust_docs(name, dep, markdown_css, html_in_header, html_before_content, html_aft
- -## Roadmap +### Example + +Suppose you have the following directory structure for a Rust library crate: + +``` +[workspace]/ + WORKSPACE + hello_lib/ + BUILD + src/ + lib.rs +``` -### Near-term roadmap +To build [`rustdoc`][rustdoc] documentation for the `hello_lib` crate, define +a `rust_doc` rule that depends on the the `hello_lib` `rust_library` target: -* Enable `rust_test` to depend solely on a `rust_library` since many projects - intermix `#[test]` methods in implementation source. -* Improve documentation with more detailed examples. +[rustdoc]: https://doc.rust-lang.org/book/documentation.html -### Longer-term roadmap +```python +package(default_visibility = ["//visibility:public"]) -* Add tool for taking `Cargo.toml` and generating a `WORKSPACE` file with - workspace rules for pulling external dependencies. -* Improve expressiveness of features and support for [Cargo's feature - groups](http://doc.crates.io/manifest.html#the-[features]-section). -* Add `cargo_crate` workspace rule for pulling crates from - [Cargo](https://crates.io/). +load("/tools/build_rules/rust/rust", "rust_library", "rust_doc") + +rust_library( + name = "hello_lib", + srcs = ["src/lib.rs"], +) + +rust_doc( + name = "hello_lib_doc", + dep = ":hello_lib", +) +``` + +Running `bazel build //hello_lib:hello_lib_doc` will build a zip file containing +the documentation for the `hello_lib` library crate generated by `rustdoc`. + + +### `rust_doc_test` + +```python +rust_doc_test(name, dep) +``` + + + + + + + + + + + + + + + + + + +
AttributeDescription
name + Name, required +

A unique name for this rule.

+
dep + Label, required +

The label of the target to run documentation tests for.

+

+ rust_doc_test can run documentation tests for the + source files of rust_library or rust_binary + targets. +

+
+ +### Example + +Suppose you have the following directory structure for a Rust library crate: + +``` +[workspace]/ + WORKSPACE + hello_lib/ + BUILD + src/ + lib.rs +``` + +To run [documentation tests][doc-test] for the `hello_lib` crate, define a +`rust_doc_test` target that depends on the `hello_lib` `rust_library` target: + +[doc-test]: https://doc.rust-lang.org/book/documentation.html#documentation-as-tests + +```python +package(default_visibility = ["//visibility:public"]) + +load("/tools/build_rules/rust/rust", "rust_library", "rust_doc_test") + +rust_library( + name = "hello_lib", + srcs = ["src/lib.rs"], +) + +rust_doc_test( + name = "hello_lib_doc_test", + dep = ":hello_lib", +) +``` + +Running `bazel test //hello_lib:hello_lib_doc_test` will run all documentation +tests for the `hello_lib` library crate. diff --git a/tools/build_rules/rust/rust.BUILD b/tools/build_rules/rust/rust.BUILD index 576f8b868b..8a0e469b57 100644 --- a/tools/build_rules/rust/rust.BUILD +++ b/tools/build_rules/rust/rust.BUILD @@ -1,7 +1,3 @@ -RUST_VERSION = "1.3.0" -LINUX_BASE_DIR = "rust-%s-x86_64-unknown-linux-gnu/" % RUST_VERSION -DARWIN_BASE_DIR = "rust-%s-x86_64-apple-darwin/" % RUST_VERSION - config_setting( name = "darwin", values = {"host_cpu": "darwin"}, @@ -15,8 +11,8 @@ config_setting( filegroup( name = "rustc", srcs = select({ - ":darwin": [DARWIN_BASE_DIR + "rustc/bin/rustc"], - ":k8": [LINUX_BASE_DIR + "rustc/bin/rustc"], + ":darwin": ["rustc/bin/rustc"], + ":k8": ["rustc/bin/rustc"], }), visibility = ["//visibility:public"], ) @@ -24,8 +20,8 @@ filegroup( filegroup( name = "rustc_lib", srcs = select({ - ":darwin": glob([DARWIN_BASE_DIR + "rustc/lib/*.dylib"]), - ":k8": glob([LINUX_BASE_DIR + "rustc/lib/*.so"]), + ":darwin": glob(["rustc/lib/*.dylib"]), + ":k8": glob(["rustc/lib/*.so"]), }), visibility = ["//visibility:public"], ) @@ -33,8 +29,8 @@ filegroup( filegroup( name = "rustdoc", srcs = select({ - ":darwin": [DARWIN_BASE_DIR + "rustc/bin/rustdoc"], - ":k8": [LINUX_BASE_DIR + "rustc/bin/rustdoc"], + ":darwin": ["rustc/bin/rustdoc"], + ":k8": ["rustc/bin/rustdoc"], }), visibility = ["//visibility:public"], ) @@ -43,14 +39,14 @@ filegroup( name = "rustlib", srcs = select({ ":darwin": glob([ - DARWIN_BASE_DIR + "rustc/lib/rustlib/x86_64-apple-darwin/lib/*.rlib", - DARWIN_BASE_DIR + "rustc/lib/rustlib/x86_64-apple-darwin/lib/*.dylib", - DARWIN_BASE_DIR + "rustc/lib/rustlib/x86_64-apple-darwin/lib/*.a", + "rustc/lib/rustlib/x86_64-apple-darwin/lib/*.rlib", + "rustc/lib/rustlib/x86_64-apple-darwin/lib/*.dylib", + "rustc/lib/rustlib/x86_64-apple-darwin/lib/*.a", ]), ":k8": glob([ - LINUX_BASE_DIR + "rustc/lib/rustlib/x86_64-unknown-linux-gnu/lib/*.rlib", - LINUX_BASE_DIR + "rustc/lib/rustlib/x86_64-unknown-linux-gnu/lib/*.so", - LINUX_BASE_DIR + "rustc/lib/rustlib/x86_64-unknown-linux-gnu/lib/*.a", + "rustc/lib/rustlib/x86_64-unknown-linux-gnu/lib/*.rlib", + "rustc/lib/rustlib/x86_64-unknown-linux-gnu/lib/*.so", + "rustc/lib/rustlib/x86_64-unknown-linux-gnu/lib/*.a", ]), }), visibility = ["//visibility:public"], diff --git a/tools/build_rules/rust/rust.bzl b/tools/build_rules/rust/rust.bzl index 69a54b1a12..5dc3c0b132 100644 --- a/tools/build_rules/rust/rust.bzl +++ b/tools/build_rules/rust/rust.bzl @@ -17,7 +17,9 @@ RUST_FILETYPE = FileType([".rs"]) A_FILETYPE = FileType([".a"]) -# Used by rust_docs +LIBRARY_CRATE_TYPES = ["lib", "rlib", "dylib", "staticlib"] + +# Used by rust_doc HTML_MD_FILETYPE = FileType([".html", ".md"]) CSS_FILETYPE = FileType([".css"]) @@ -143,8 +145,8 @@ def _rust_toolchain(ctx): rustlib_path = ctx.files._rustlib[0].dirname, rustdoc_path = ctx.file._rustdoc.path) -def _build_rustc_command(ctx, crate_type, src, output_dir, depinfo, - extra_flags=[]): +def _build_rustc_command(ctx, crate_name, crate_type, src, output_dir, + depinfo, rust_flags=[]): """Builds the rustc command. Constructs the rustc command used to build the current target. @@ -152,11 +154,10 @@ def _build_rustc_command(ctx, crate_type, src, output_dir, depinfo, Args: ctx: The ctx object for the current target. crate_type: The type of crate to build ("lib" or "bin") - src: The path to the crate root source file ("lib.rs" or "main.rs") + src: The File object for crate root source file ("lib.rs" or "main.rs") output_dir: The output directory for the target. depinfo: Struct containing information about dependencies as returned by _setup_deps - extra_flags: Additional command line flags. Return: String containing the rustc command. @@ -180,42 +181,61 @@ def _build_rustc_command(ctx, crate_type, src, output_dir, depinfo, # Construct features flags features_flags = _get_features_flags(ctx.attr.crate_features) - return " ".join([ - "set -e;", - " ".join(depinfo.setup_cmd), - "LD_LIBRARY_PATH=%s" % toolchain.rustc_lib_path, - "DYLD_LIBRARY_PATH=%s" % toolchain.rustc_lib_path, - toolchain.rustc_path, - src, - "--crate-name %s" % ctx.label.name, - "--crate-type %s" % crate_type, - "-C opt-level=3", - "--codegen ar=%s" % ar, - "--codegen linker=%s" % cc, - "-L all=%s" % toolchain.rustlib_path, - " ".join(extra_flags), - " ".join(features_flags), - "--out-dir %s" % output_dir, - "--emit=dep-info,link", - " ".join(depinfo.search_flags), - " ".join(depinfo.link_flags), - " ".join(ctx.attr.rustc_flags), - ]) + return " ".join( + ["set -e;"] + + depinfo.setup_cmd + + [ + "LD_LIBRARY_PATH=%s" % toolchain.rustc_lib_path, + "DYLD_LIBRARY_PATH=%s" % toolchain.rustc_lib_path, + toolchain.rustc_path, + src.path, + "--crate-name %s" % crate_name, + "--crate-type %s" % crate_type, + "-C opt-level=3", + "--codegen ar=%s" % ar, + "--codegen linker=%s" % cc, + "-L all=%s" % toolchain.rustlib_path, + "--out-dir %s" % output_dir, + "--emit=dep-info,link", + ] + + features_flags + + rust_flags + + depinfo.search_flags + + depinfo.link_flags + + ctx.attr.rustc_flags) def _find_crate_root_src(srcs, file_names=["lib.rs"]): """Finds the source file for the crate root.""" + if len(srcs) == 1: + return srcs[0] for src in srcs: if src.basename in file_names: - return src.path + return src fail("No %s source file found." % " or ".join(file_names), "srcs") +def _crate_root_src(ctx, file_names=["lib.rs"]): + if ctx.file.crate_root == None: + return _find_crate_root_src(ctx.files.srcs, file_names) + else: + return ctx.file.crate_root + def _rust_library_impl(ctx): """ Implementation for rust_library Skylark rule. """ # Find lib.rs - lib_rs = _find_crate_root_src(ctx.files.srcs) + lib_rs = _crate_root_src(ctx) + + # Validate crate_type + crate_type = "" + if ctx.attr.crate_type != "": + if ctx.attr.crate_type not in LIBRARY_CRATE_TYPES: + fail("Invalid crate_type for rust_library. Allowed crate types are: %s" + % " ".join(LIBRARY_CRATE_TYPES), "crate_type") + crate_type += ctx.attr.crate_type + else: + crate_type += "lib" # Output library rust_lib = ctx.outputs.rust_lib @@ -230,7 +250,8 @@ def _rust_library_impl(ctx): # Build rustc command cmd = _build_rustc_command( ctx = ctx, - crate_type = "lib", + crate_name = ctx.label.name, + crate_type = crate_type, src = lib_rs, output_dir = output_dir, depinfo = depinfo) @@ -240,6 +261,7 @@ def _rust_library_impl(ctx): ctx.files.srcs + ctx.files.data + depinfo.libs + + depinfo.transitive_libs + [ctx.file._rustc] + ctx.files._rustc_lib + ctx.files._rustlib) @@ -255,16 +277,18 @@ def _rust_library_impl(ctx): return struct( files = set([rust_lib]), + crate_type = crate_type, + crate_root = lib_rs, rust_srcs = ctx.files.srcs, rust_deps = ctx.attr.deps, transitive_libs = depinfo.transitive_libs, rust_lib = rust_lib) -def _rust_binary_impl_common(ctx, extra_flags = []): +def _rust_binary_impl(ctx): """Implementation for rust_binary Skylark rule.""" # Find main.rs. - main_rs = _find_crate_root_src(ctx.files.srcs, ["main.rs"]) + main_rs = _crate_root_src(ctx, ["main.rs"]) # Output binary rust_binary = ctx.outputs.executable @@ -278,17 +302,18 @@ def _rust_binary_impl_common(ctx, extra_flags = []): # Build rustc command. cmd = _build_rustc_command(ctx = ctx, + crate_name = ctx.label.name, crate_type = "bin", src = main_rs, output_dir = output_dir, - depinfo = depinfo, - extra_flags = extra_flags) + depinfo = depinfo) # Compile action. compile_inputs = ( ctx.files.srcs + ctx.files.data + depinfo.libs + + depinfo.transitive_libs + [ctx.file._rustc] + ctx.files._rustc_lib + ctx.files._rustlib) @@ -303,19 +328,89 @@ def _rust_binary_impl_common(ctx, extra_flags = []): % (ctx.label.name, len(ctx.files.srcs)))) return struct(rust_srcs = ctx.files.srcs, + crate_root = main_rs, rust_deps = ctx.attr.deps) -def _rust_binary_impl(ctx): - """ - Implementation for rust_binary Skylark rule. +def _rust_test_common(ctx, test_binary): + """Builds a Rust test binary. + + Args: + ctx: The ctx object for the current target. + test_binary: The File object for the test binary. """ - return _rust_binary_impl_common(ctx) + output_dir = test_binary.dirname + + if len(ctx.attr.deps) == 1 and len(ctx.files.srcs) == 0: + # Target has a single dependency but no srcs. Build the test binary using + # the dependency's srcs. + dep = ctx.attr.deps[0] + crate_type = dep.crate_type if hasattr(dep, "crate_type") else "bin" + target = struct(name = dep.label.name, + srcs = dep.rust_srcs, + deps = dep.rust_deps, + crate_root = dep.crate_root, + crate_type = crate_type) + else: + # Target is a standalone crate. Build the test binary as its own crate. + target = struct(name = ctx.label.name, + srcs = ctx.files.srcs, + deps = ctx.attr.deps, + crate_root = _crate_root_src(ctx), + crate_type = "lib") + + # Get information about dependencies + depinfo = _setup_deps(target.deps, + target.name, + output_dir, + is_library=False) + + cmd = _build_rustc_command(ctx = ctx, + crate_name = test_binary.basename, + crate_type = target.crate_type, + src = target.crate_root, + output_dir = output_dir, + depinfo = depinfo, + rust_flags = ["--test"]) + + compile_inputs = (target.srcs + + depinfo.libs + + depinfo.transitive_libs + + [ctx.file._rustc] + + ctx.files._rustc_lib + + ctx.files._rustlib) + + ctx.action( + inputs = compile_inputs, + outputs = [test_binary], + mnemonic = "RustcTest", + command = cmd, + use_default_shell_env = True, + progress_message = ("Compiling Rust test %s (%d files)" + % (ctx.label.name, len(target.srcs)))) def _rust_test_impl(ctx): """ - Implementation for rust_test and rust_bench_test Skylark rules. + Implementation for rust_test Skylark rule. """ - return _rust_binary_impl_common(ctx, ["--test"]) + _rust_test_common(ctx, ctx.outputs.executable) + +def _rust_bench_test_impl(ctx): + """Implementation for the rust_bench_test Skylark rule.""" + rust_bench_test = ctx.outputs.executable + test_binary = ctx.new_file(ctx.configuration.bin_dir, + "%s_bin" % rust_bench_test.basename) + _rust_test_common(ctx, test_binary) + + ctx.file_action( + output = rust_bench_test, + content = " ".join([ + "#!/bin/bash\n", + "set -e\n", + "%s --bench\n" % test_binary.short_path]), + executable = True) + + runfiles = ctx.runfiles(files = [test_binary], collect_data = True) + return struct(runfiles = runfiles) def _build_rustdoc_flags(ctx): """Collects the rustdoc flags.""" @@ -331,19 +426,21 @@ def _build_rustdoc_flags(ctx): doc_flags += ["--html-after-content %s"] return doc_flags -def _rust_docs_impl(ctx): - """Implementation of the rust_docs rule.""" +def _rust_doc_impl(ctx): + """Implementation of the rust_doc rule.""" rust_doc_zip = ctx.outputs.rust_doc_zip # Gather attributes about the rust_library target to generated rustdocs for. target = struct(name = ctx.attr.dep.label.name, srcs = ctx.attr.dep.rust_srcs, - deps = ctx.attr.dep.rust_deps) + deps = ctx.attr.dep.rust_deps, + crate_root = ctx.attr.dep.crate_root) # Find lib.rs - lib_rs = _find_crate_root_src(target.srcs, ["lib.rs", "main.rs"]) + lib_rs = (_find_crate_root_src(target.srcs, ["lib.rs", "main.rs"]) + if target.crate_root == None else target.crate_root) - # Dependencies + # Get information about dependencies output_dir = rust_doc_zip.dirname depinfo = _setup_deps(target.deps, target.name, @@ -364,7 +461,7 @@ def _rust_docs_impl(ctx): "LD_LIBRARY_PATH=%s" % toolchain.rustc_lib_path, "DYLD_LIBRARY_PATH=%s" % toolchain.rustc_lib_path, toolchain.rustdoc_path, - lib_rs, + lib_rs.path, "--crate-name %s" % target.name, "-L all=%s" % toolchain.rustlib_path, "-o %s" % docs_dir, @@ -399,8 +496,60 @@ def _rust_docs_impl(ctx): progress_message = ("Generating rustdoc for %s (%d files)" % (target.name, len(target.srcs)))) +def _rust_doc_test_impl(ctx): + """Implementation for the rust_doc_test rule.""" + rust_doc_test = ctx.outputs.executable + + # Gather attributes about the rust_library target to generated rustdocs for. + target = struct(name = ctx.attr.dep.label.name, + srcs = ctx.attr.dep.rust_srcs, + deps = ctx.attr.dep.rust_deps, + crate_root = ctx.attr.dep.crate_root) + + # Find lib.rs + lib_rs = (_find_crate_root_src(target.srcs, ["lib.rs", "main.rs"]) + if target.crate_root == None else target.crate_root) + + # Get information about dependencies + depinfo = _setup_deps(target.deps, + target.name, + working_dir=".", + is_library=False) + + # Construct rustdoc test command, which will be written to a shell script + # to be executed to run the test. + toolchain = _rust_toolchain(ctx) + doc_test_cmd = " ".join( + ["#!/bin/bash\n"] + + ["set -e\n"] + + depinfo.setup_cmd + + [ + "LD_LIBRARY_PATH=%s" % toolchain.rustc_lib_path, + "DYLD_LIBRARY_PATH=%s" % toolchain.rustc_lib_path, + toolchain.rustdoc_path, + lib_rs.path, + ] + + depinfo.search_flags + + depinfo.link_flags) + + ctx.file_action(output = rust_doc_test, + content = doc_test_cmd, + executable = True) + + doc_test_inputs = (target.srcs + + depinfo.libs + + depinfo.transitive_libs + + [ctx.file._rustdoc] + + ctx.files._rustc_lib + + ctx.files._rustlib) + + runfiles = ctx.runfiles(files = doc_test_inputs, collect_data = True) + return struct(runfiles = runfiles) + _rust_common_attrs = { "srcs": attr.label_list(allow_files = RUST_FILETYPE), + "crate_root": attr.label(allow_files = RUST_FILETYPE, + single_file = True), "data": attr.label_list(allow_files = True, cfg = DATA_CFG), "deps": attr.label_list(), "crate_features": attr.string_list(), @@ -421,9 +570,13 @@ _rust_toolchain_attrs = { single_file = True), } +_rust_library_attrs = _rust_common_attrs + { + "crate_type": attr.string(), +} + rust_library = rule( _rust_library_impl, - attrs = _rust_common_attrs + _rust_toolchain_attrs, + attrs = _rust_library_attrs + _rust_toolchain_attrs, outputs = { "rust_lib": "lib%{name}.rlib", }, @@ -446,25 +599,35 @@ rust_test = rule( ) rust_bench_test = rule( - _rust_test_impl, + _rust_bench_test_impl, executable = True, attrs = _rust_common_attrs + _rust_toolchain_attrs, test = True, fragments = ["cpp"], ) -_rust_doc_attrs = { +_rust_doc_common_attrs = { "dep": attr.label(mandatory = True), +} + +_rust_doc_attrs = _rust_doc_common_attrs + { "markdown_css": attr.label_list(allow_files = CSS_FILETYPE), "html_in_header": attr.label(allow_files = HTML_MD_FILETYPE), "html_before_content": attr.label(allow_files = HTML_MD_FILETYPE), "html_after_content": attr.label(allow_files = HTML_MD_FILETYPE), } -rust_docs = rule( - _rust_docs_impl, +rust_doc = rule( + _rust_doc_impl, attrs = _rust_doc_attrs + _rust_toolchain_attrs, outputs = { "rust_doc_zip": "%{name}-docs.zip", }, ) + +rust_doc_test = rule( + _rust_doc_test_impl, + attrs = _rust_doc_common_attrs + _rust_toolchain_attrs, + executable = True, + test = True, +) -- cgit v1.2.3