Last active
December 7, 2015 10:46
-
-
Save davidzchen/d3dec1b2617e70cf0b6f to your computer and use it in GitHub Desktop.
rust.bzl with docstring-style inline documentation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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. | |
"""Rust rules for Bazel""" | |
RUST_FILETYPE = FileType([".rs"]) | |
A_FILETYPE = FileType([".a"]) | |
LIBRARY_CRATE_TYPES = ["lib", "rlib", "dylib", "staticlib"] | |
# Used by rust_doc | |
HTML_MD_FILETYPE = FileType([".html", ".md"]) | |
CSS_FILETYPE = FileType([".css"]) | |
ZIP_PATH = "/usr/bin/zip" | |
def _path_parts(path): | |
"""Takes a path and returns a list of its parts with all "." elements removed. | |
The main use case of this function is if one of the inputs to _relative() | |
is a relative path, such as "./foo". | |
Args: | |
path_parts: A list containing parts of a path. | |
Returns: | |
Returns a list containing the path parts with all "." elements removed. | |
""" | |
path_parts = path.split("/") | |
return [part for part in path_parts if part != "."] | |
def _relative(src_path, dest_path): | |
"""Returns the relative path from src_path to dest_path.""" | |
src_parts = _path_parts(src_path) | |
dest_parts = _path_parts(dest_path) | |
n = 0 | |
done = False | |
for src_part, dest_part in zip(src_parts, dest_parts): | |
if src_part != dest_part: | |
break | |
n += 1 | |
relative_path = "" | |
for i in range(n, len(src_parts)): | |
relative_path += "../" | |
relative_path += "/".join(dest_parts[n:]) | |
return relative_path | |
def _create_setup_cmd(lib, deps_dir, in_runfiles): | |
""" | |
Helper function to construct a command for symlinking a library into the | |
deps directory. | |
""" | |
lib_path = lib.short_path if in_runfiles else lib.path | |
return ( | |
"ln -sf " + _relative(deps_dir, lib_path) + " " + | |
deps_dir + "/" + lib.basename + "\n" | |
) | |
def _setup_deps(deps, name, working_dir, is_library=False, in_runfiles=False): | |
""" | |
Walks through dependencies and constructs the necessary commands for linking | |
to all the necessary dependencies. | |
Args: | |
deps: List of Labels containing deps from ctx.attr.deps. | |
name: Name of the current target. | |
working_dir: The output directory for the current target's outputs. | |
is_library: True if the current target is a rust_library target, False | |
otherwise. | |
in_runfiles: True if the setup commands will be run in a .runfiles | |
directory. In this case, the working dir should be '.', and the deps | |
will be symlinked into the .deps dir from the runfiles tree. | |
Returns: | |
Returns a struct containing the following fields: | |
libs: | |
transitive_libs: | |
setup_cmd: | |
search_flags: | |
link_flags: | |
""" | |
deps_dir = working_dir + "/" + name + ".deps" | |
setup_cmd = ["rm -rf " + deps_dir + "; mkdir " + deps_dir + "\n"] | |
has_rlib = False | |
has_native = False | |
libs = set() | |
transitive_libs = set() | |
symlinked_libs = set() | |
link_flags = [] | |
for dep in deps: | |
if hasattr(dep, "rust_lib"): | |
# This dependency is a rust_library | |
libs += [dep.rust_lib] | |
transitive_libs += [dep.rust_lib] + dep.transitive_libs | |
symlinked_libs += [dep.rust_lib] + dep.transitive_libs | |
link_flags += [( | |
"--extern " + dep.label.name + "=" + | |
deps_dir + "/" + dep.rust_lib.basename | |
)] | |
has_rlib = True | |
elif hasattr(dep, "cc"): | |
if not is_library: | |
fail("Only rust_library targets can depend on cc_library") | |
# This dependency is a cc_library | |
native_libs = A_FILETYPE.filter(dep.cc.libs) | |
libs += native_libs | |
transitive_libs += native_libs | |
symlinked_libs += native_libs | |
link_flags += ["-l static=" + dep.label.name] | |
has_native = True | |
else: | |
fail(("rust_library" if is_library else "rust_binary and rust_test") + | |
" targets can only depend on rust_library " + | |
("or cc_library " if is_library else "") + "targets") | |
for symlinked_lib in symlinked_libs: | |
setup_cmd += [_create_setup_cmd(symlinked_lib, deps_dir, in_runfiles)] | |
search_flags = [] | |
if has_rlib: | |
search_flags += ["-L dependency=%s" % deps_dir] | |
if has_native: | |
search_flags += ["-L native=%s" % deps_dir] | |
return struct( | |
libs = list(libs), | |
transitive_libs = list(transitive_libs), | |
setup_cmd = setup_cmd, | |
search_flags = search_flags, | |
link_flags = link_flags) | |
def _get_features_flags(features): | |
""" | |
Constructs a string containing the feature flags from the features specified | |
in the features attribute. | |
""" | |
features_flags = [] | |
for feature in features: | |
features_flags += ["--cfg feature=\\\"%s\\\"" % feature] | |
return features_flags | |
def _rust_toolchain(ctx): | |
return struct( | |
rustc_path = ctx.file._rustc.path, | |
rustc_lib_path = ctx.files._rustc_lib[0].dirname, | |
rustlib_path = ctx.files._rustlib[0].dirname, | |
rustdoc_path = ctx.file._rustdoc.path) | |
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. | |
Args: | |
ctx: The ctx object for the current target. | |
crate_type: The type of crate to build ("lib" or "bin") | |
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 | |
Return: | |
String containing the rustc command. | |
""" | |
# Paths to the Rust compiler and standard libraries. | |
toolchain = _rust_toolchain(ctx) | |
# Paths to cc (for linker) and ar | |
cpp_fragment = ctx.fragments.cpp | |
cc = cpp_fragment.compiler_executable | |
ar = cpp_fragment.ar_executable | |
# Currently, the CROSSTOOL config for darwin sets ar to "libtool". Because | |
# rust uses ar-specific flags, use /usr/bin/ar in this case. | |
# TODO(dzc): This is not ideal. Remove this workaround once ar_executable | |
# always points to an ar binary. | |
ar_str = "%s" % ar | |
if ar_str.find("libtool", 0) != -1: | |
ar = "/usr/bin/ar" | |
# Construct features flags | |
features_flags = _get_features_flags(ctx.attr.crate_features) | |
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, | |
"--codegen link-args='%s'" % ' '.join(cpp_fragment.link_options), | |
"-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 | |
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 = _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 | |
output_dir = rust_lib.dirname | |
# Dependencies | |
depinfo = _setup_deps(ctx.attr.deps, | |
ctx.label.name, | |
output_dir, | |
is_library=True) | |
# Build rustc command | |
cmd = _build_rustc_command( | |
ctx = ctx, | |
crate_name = ctx.label.name, | |
crate_type = crate_type, | |
src = lib_rs, | |
output_dir = output_dir, | |
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) | |
ctx.action( | |
inputs = compile_inputs, | |
outputs = [rust_lib], | |
mnemonic = 'Rustc', | |
command = cmd, | |
use_default_shell_env = True, | |
progress_message = ("Compiling Rust library %s (%d files)" | |
% (ctx.label.name, len(ctx.files.srcs)))) | |
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(ctx): | |
"""Implementation for rust_binary Skylark rule.""" | |
# Find main.rs. | |
main_rs = _crate_root_src(ctx, ["main.rs"]) | |
# Output binary | |
rust_binary = ctx.outputs.executable | |
output_dir = rust_binary.dirname | |
# Dependencies | |
depinfo = _setup_deps(ctx.attr.deps, | |
ctx.label.name, | |
output_dir, | |
is_library=False) | |
# 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) | |
# 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) | |
ctx.action( | |
inputs = compile_inputs, | |
outputs = [rust_binary], | |
mnemonic = "Rustc", | |
command = cmd, | |
use_default_shell_env = True, | |
progress_message = ("Compiling Rust binary %s (%d files)" | |
% (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_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. | |
""" | |
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 = ctx.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 Skylark rule. | |
""" | |
_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.""" | |
doc_flags = [] | |
doc_flags += [ | |
"--markdown-css %s" % css.path for css in ctx.files.markdown_css] | |
if hasattr(ctx.file, "html_in_header"): | |
doc_flags += ["--html-in-header %s" % ctx.file.html_in_header.path] | |
if hasattr(ctx.file, "html_before_content"): | |
doc_flags += ["--html-before-content %s" % | |
ctx.file.html_before_content.path] | |
if hasattr(ctx.file, "html_after_content"): | |
doc_flags += ["--html-after-content %s"] | |
return doc_flags | |
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.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 | |
output_dir = rust_doc_zip.dirname | |
depinfo = _setup_deps(target.deps, | |
target.name, | |
output_dir, | |
is_library=False) | |
# Rustdoc flags. | |
doc_flags = _build_rustdoc_flags(ctx) | |
# Build rustdoc command. | |
toolchain = _rust_toolchain(ctx) | |
docs_dir = rust_doc_zip.dirname + "/_rust_docs" | |
doc_cmd = " ".join( | |
["set -e;"] + | |
depinfo.setup_cmd + [ | |
"rm -rf %s;" % docs_dir, | |
"mkdir %s;" % docs_dir, | |
"LD_LIBRARY_PATH=%s" % toolchain.rustc_lib_path, | |
"DYLD_LIBRARY_PATH=%s" % toolchain.rustc_lib_path, | |
toolchain.rustdoc_path, | |
lib_rs.path, | |
"--crate-name %s" % target.name, | |
"-L all=%s" % toolchain.rustlib_path, | |
"-o %s" % docs_dir, | |
] + | |
doc_flags + | |
depinfo.search_flags + | |
depinfo.link_flags + [ | |
"&&", | |
"(cd %s" % docs_dir, | |
"&&", | |
ZIP_PATH, | |
"-qR", | |
rust_doc_zip.basename, | |
"$(find . -type f) )", | |
"&&", | |
"mv %s/%s %s" % (docs_dir, rust_doc_zip.basename, rust_doc_zip.path), | |
]) | |
# Rustdoc action | |
rustdoc_inputs = (target.srcs + | |
depinfo.libs + | |
[ctx.file._rustdoc] + | |
ctx.files._rustc_lib + | |
ctx.files._rustlib) | |
ctx.action( | |
inputs = rustdoc_inputs, | |
outputs = [rust_doc_zip], | |
mnemonic = "Rustdoc", | |
command = doc_cmd, | |
use_default_shell_env = True, | |
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.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 | |
output_dir = rust_doc_test.dirname | |
depinfo = _setup_deps(target.deps, | |
target.name, | |
working_dir=".", | |
is_library=False, | |
in_runfiles=True) | |
# 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 = { | |
"data": attr.label_list(allow_files = True, cfg = DATA_CFG), | |
"crate_features": attr.string_list(), | |
"rustc_flags": attr.string_list(), | |
} | |
_rust_toolchain_attrs = { | |
"_rustc": attr.label( | |
default = Label("//tools/build_rules/rust:rustc"), | |
executable = True, | |
single_file = True), | |
"_rustc_lib": attr.label( | |
default = Label("//tools/build_rules/rust:rustc_lib")), | |
"_rustlib": attr.label(default = Label("//tools/build_rules/rust:rustlib")), | |
"_rustdoc": attr.label( | |
default = Label("//tools/build_rules/rust:rustdoc"), | |
executable = True, | |
single_file = True), | |
} | |
rust_library = rule( | |
_rust_library_impl, | |
attrs = _rust_library_attrs + _rust_toolchain_attrs + { | |
"srcs": attr.label_list(allow_files = RUST_FILETYPE), | |
"deps": attr.label_list(), | |
"crate_root": attr.label(allow_files = RUST_FILETYPE, | |
single_file = True), | |
"crate_type": attr.string(), | |
}, | |
outputs = { | |
"rust_lib": "lib%{name}.rlib", | |
}, | |
fragments = ["cpp"], | |
doc = _RUST_LIBRARY_DOC, | |
) | |
"""Builds a Rust library. | |
# Example | |
Suppose you have the following directory structure for a simple Rust library | |
crate: | |
``` | |
[workspace]/ | |
WORKSPACE | |
hello_lib/ | |
BUILD | |
src/ | |
greeter.rs | |
lib.rs | |
``` | |
`hello_lib/src/greeter.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/src/lib.rs`: | |
```rust | |
pub mod greeter; | |
``` | |
`hello_lib/BUILD`: | |
```python | |
package(default_visibility = ["//visibility:public"]) | |
load("/tools/build_rules/rust/rust", "rust_library") | |
rust_library( | |
name = "hello_lib", | |
srcs = [ | |
"src/greeter.rs", | |
"src/lib.rs", | |
], | |
) | |
``` | |
Build the library: | |
``` | |
$ bazel build //hello_lib | |
INFO: Found 1 target... | |
Target //examples/rust/hello_lib:hello_lib up-to-date: | |
bazel-bin/examples/rust/hello_lib/libhello_lib.rlib | |
INFO: Elapsed time: 1.245s, Critical Path: 1.01s | |
``` | |
Args: | |
srcs: 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. | |
deps: 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. | |
crate_root: 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. | |
crate_type: The type of library crate to build. | |
Possible values are `lib`, `rlib`, `dylib`, and `staticlib`. | |
data: 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!`](https://doc.rust-lang.org/std/macro.include_str!.html) | |
macro. | |
rust_features: 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 compiler flags passed to `rustc`. | |
""" | |
rust_binary = rule( | |
_rust_binary_impl, | |
executable = True, | |
attrs = _rust_common_attrs + _rust_toolchain_attrs + { | |
"srcs": attr.label_list(allow_files = RUST_FILETYPE), | |
"deps": attr.label_list(), | |
"crate_root": attr.label(allow_files = RUST_FILETYPE, | |
single_file = True), | |
}, | |
fragments = ["cpp"], | |
doc = _RUST_BINARY_DOC, | |
) | |
"""Builds a 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`: | |
``` | |
$ 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 | |
``` | |
Args: | |
srcs: List of Rust `.rs` source files used to build the binary. | |
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. | |
deps: List of other libraries to be linked to this binary target. | |
These must be `rust_library` targets. | |
crate_root: 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. | |
data: 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!`](https://doc.rust-lang.org/std/macro.include_str!.html) | |
macro. | |
rust_features: 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 compiler flags passed to `rustc`. | |
""" | |
rust_test = rule( | |
_rust_test_impl, | |
executable = True, | |
attrs = _rust_common_attrs + _rust_toolchain_attrs + { | |
"srcs": attr.label_list(allow_files = RUST_FILETYPE), | |
"deps": attr.label_list(), | |
"crate_root": attr.label(allow_files = RUST_FILETYPE, | |
single_file = True), | |
}, | |
test = True, | |
fragments = ["cpp"], | |
doc = _RUST_TEST_DOC, | |
) | |
"""Builds a Rust test binary. | |
# 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 | |
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`. | |
Args: | |
srcs: 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. | |
deps: List of other libraries to be linked to this test target. | |
These must be `rust_library` targets. | |
crate_root: 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. | |
data: 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!`](https://doc.rust-lang.org/std/macro.include_str!.html) | |
macro. | |
rust_features: 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 compiler flags passed to `rustc`. | |
""" | |
rust_bench_test = rule( | |
_rust_bench_test_impl, | |
executable = True, | |
attrs = _rust_common_attrs + _rust_toolchain_attrs + { | |
"srcs": attr.label_list(allow_files = RUST_FILETYPE), | |
"deps": attr.label_list(), | |
"crate_root": attr.label(allow_files = RUST_FILETYPE, | |
single_file = True), | |
}, | |
test = True, | |
fragments = ["cpp"], | |
doc = _RUST_BENCH_TEST_DOC, | |
) | |
"""Builds a Rust bench mark test binary. | |
**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 | |
# 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`. | |
Args: | |
srcs: List of Rust `.rs` source files used to build the benchmark test. | |
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. | |
deps: List of other libraries to be linked to this test target. | |
These must be `rust_library` targets. | |
crate_root: 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. | |
data: 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!`](https://doc.rust-lang.org/std/macro.include_str!.html) | |
macro. | |
rust_features: 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 compiler flags passed to `rustc`. | |
""" | |
_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_doc = rule( | |
_rust_doc_impl, | |
attrs = _rust_doc_common_attrs + _rust_toolchain_attrs + { | |
"dep": attr.label(mandatory = True), | |
}, | |
outputs = { | |
"rust_doc_zip": "%{name}-docs.zip", | |
}, | |
doc = _RUST_DOC_DOC, | |
) | |
"""Generates rustdoc code documentation. | |
# Example | |
Suppose you have the following directory structure for a Rust library crate: | |
``` | |
[workspace]/ | |
WORKSPACE | |
hello_lib/ | |
BUILD | |
src/ | |
lib.rs | |
``` | |
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: | |
[rustdoc]: https://doc.rust-lang.org/book/documentation.html | |
```python | |
package(default_visibility = ["//visibility:public"]) | |
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`. | |
Args: | |
dep: The label of the target to generate code documentation for. | |
`rust_doc` can generate HTML code documentation for the source files of | |
``rust_library` or `rust_binary` targets. | |
markdown_css: CSS files to include via `<link>` in a rendered Markdown file. | |
html_in_header: File to add to `<head>`." | |
html_before_content: File to add in `<body>`, before content." | |
html_after_content: File to add in `<body>`, after content." | |
""" | |
rust_doc_test = rule( | |
_rust_doc_test_impl, | |
attrs = _rust_doc_common_attrs + _rust_toolchain_attrs + { | |
"dep": attr.label(mandatory = True), | |
}, | |
executable = True, | |
test = True, | |
doc = _RUST_DOC_TEST_DOC, | |
) | |
"""Runs Rust documentation tests. | |
# 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. | |
Args: | |
dep: 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. | |
markdown_css: CSS files to include via `<link>` in a rendered Markdown file. | |
html_in_header: File to add to `<head>`." | |
html_before_content: File to add in `<body>`, before content." | |
html_after_content: File to add in `<body>`, after content." | |
""" | |
def rust_repositories(): | |
"""Adds the external repositories for the Rust rules to the workspace.""" | |
native.new_http_archive( | |
name = "rust-linux-x86_64", | |
url = "https://static.rust-lang.org/dist/rust-1.4.0-x86_64-unknown-linux-gnu.tar.gz", | |
strip_prefix = "rust-1.4.0-x86_64-unknown-linux-gnu", | |
sha256 = "2de2424b50ca2ab3a67c495b6af03c720801a2928ad30884438ad0f5436ac51d", | |
build_file = "tools/build_rules/rust/rust.BUILD", | |
) | |
native.new_http_archive( | |
name = "rust-darwin-x86_64", | |
url = "https://static.rust-lang.org/dist/rust-1.4.0-x86_64-apple-darwin.tar.gz", | |
strip_prefix = "rust-1.4.0-x86_64-apple-darwin", | |
sha256 = "7256617aec7c106be2aa3c5df0a2e613b13ec55e6237ab612bb4164719e09e21", | |
build_file = "tools/build_rules/rust/rust.BUILD", | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment