diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index d57caf1743..91fa6aec85 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -30,9 +30,7 @@ platforms: - "//test/..." - "@examples//..." - "-//test/conflicting_deps:conflicting_deps_test" - # Runfiles currently not working properly with remote execution - # https://github.com/bazelbuild/rules_rust/issues/112 - - "-@examples//hello_runfiles:hello_runfiles_test" # rust_doc_test is likely not fully sandboxed - "-@examples//fibonacci:fibonacci_doc_test" - "-@examples//hello_lib:hello_lib_doc_test" + - "-//tools/runfiles:runfiles_doc_test" diff --git a/.gitignore b/.gitignore index 5a105330db..0d43ef1f0f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,8 @@ +# vim *.swp + +# bazel /bazel-* + +# rustfmt +*.rs.bk diff --git a/examples/ffi/c_calling_rust/BUILD b/examples/ffi/c_calling_rust/BUILD index 940af5f62b..47960e4ab2 100644 --- a/examples/ffi/c_calling_rust/BUILD +++ b/examples/ffi/c_calling_rust/BUILD @@ -1,5 +1,3 @@ -package(default_visibility = ["//visibility:private"]) - load("@//rust:rust.bzl", "rust_library", "rust_test", "rust_binary") rust_library( diff --git a/examples/fibonacci/BUILD b/examples/fibonacci/BUILD index 8a3dc4ed3e..21bb74b2e5 100644 --- a/examples/fibonacci/BUILD +++ b/examples/fibonacci/BUILD @@ -1,5 +1,3 @@ -package(default_visibility = ["//visibility:public"]) - load( "@io_bazel_rules_rust//rust:rust.bzl", "rust_library", diff --git a/examples/hello_out_dir/BUILD b/examples/hello_out_dir/BUILD index 6e3904e392..e92e287a32 100644 --- a/examples/hello_out_dir/BUILD +++ b/examples/hello_out_dir/BUILD @@ -1,5 +1,3 @@ -package(default_visibility = ["//visibility:public"]) - load( "@io_bazel_rules_rust//rust:rust.bzl", "rust_binary", diff --git a/examples/hello_runfiles/BUILD b/examples/hello_runfiles/BUILD index e6426b1a90..55032515c5 100644 --- a/examples/hello_runfiles/BUILD +++ b/examples/hello_runfiles/BUILD @@ -2,25 +2,14 @@ package(default_visibility = ["//visibility:public"]) load( "@io_bazel_rules_rust//rust:rust.bzl", - "rust_library", "rust_binary", + "rust_library", "rust_test", ) -rust_library( - name = "runfiles", - srcs = ["src/lib.rs"], -) - rust_binary( name = "hello_runfiles", - srcs = ["src/main.rs"], - data = ["data/sample.txt"], - deps = [":runfiles"], -) - -rust_test( - name = "hello_runfiles_test", - data = ["data/sample.txt"], - deps = [":runfiles"], + srcs = ["hello_runfiles.rs"], + data = ["hello_runfiles.rs"], # Yes, we're being cute. + deps = ["@io_bazel_rules_rust//tools/runfiles"], ) diff --git a/examples/hello_runfiles/hello_runfiles.rs b/examples/hello_runfiles/hello_runfiles.rs new file mode 100644 index 0000000000..caeb81a1ba --- /dev/null +++ b/examples/hello_runfiles/hello_runfiles.rs @@ -0,0 +1,18 @@ +extern crate runfiles; + +use std::io::prelude::*; +use std::fs::File; + +use runfiles::Runfiles; + +fn main() { + let r = Runfiles::create().unwrap(); + + let mut f = File::open(r.rlocation("examples/hello_runfiles/hello_runfiles.rs")).unwrap(); + + let mut buffer = String::new(); + f.read_to_string(&mut buffer).unwrap(); + + assert_eq!(buffer.len(), 427); + println!("This program's source is:\n```\n{}\n```", buffer); +} diff --git a/examples/hello_runfiles/src/lib.rs b/examples/hello_runfiles/src/lib.rs deleted file mode 100644 index 3e8aa63cfc..0000000000 --- a/examples/hello_runfiles/src/lib.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::io; -use std::path::PathBuf; - -/// Returns the .runfiles directory for the currently executing binary. -pub fn get_runfiles_dir() -> io::Result { - let mut path = std::env::current_exe()?; - - if cfg!(target_os = "macos") { - path.pop(); - } else { - let mut name = path.file_name().unwrap().to_owned(); - name.push(".runfiles"); - path.pop(); - path.push(name); - } - - Ok(path) -} - - -#[cfg(test)] -mod test { - use super::*; - - use std::io::prelude::*; - use std::fs::File; - - #[test] - fn test_can_read_data_from_runfiles() { - let runfiles = get_runfiles_dir().unwrap(); - - let mut f = if cfg!(target_os = "macos") { - File::open(runfiles.join("data/sample.txt")).unwrap() - } else { - File::open(runfiles.join("examples/hello_runfiles/data/sample.txt")).unwrap() - }; - let mut buffer = String::new(); - - f.read_to_string(&mut buffer).unwrap(); - - assert_eq!("Example Text!", buffer); - } -} diff --git a/examples/hello_runfiles/src/main.rs b/examples/hello_runfiles/src/main.rs deleted file mode 100644 index ae0f641f34..0000000000 --- a/examples/hello_runfiles/src/main.rs +++ /dev/null @@ -1,16 +0,0 @@ -extern crate runfiles; - -use std::io::prelude::*; -use std::fs::File; - - -fn main() { - let runfiles = runfiles::get_runfiles_dir().unwrap(); - - let mut f = File::open(runfiles.join("examples/hello_runfiles/data/sample.txt")).unwrap(); - let mut buffer = String::new(); - - f.read_to_string(&mut buffer).unwrap(); - - println!("{}", buffer); -} \ No newline at end of file diff --git a/tools/runfiles/BUILD b/tools/runfiles/BUILD new file mode 100644 index 0000000000..e9be3e5f26 --- /dev/null +++ b/tools/runfiles/BUILD @@ -0,0 +1,23 @@ +load( + "@io_bazel_rules_rust//rust:rust.bzl", + "rust_doc_test", + "rust_library", + "rust_test", +) + +rust_library( + name = "runfiles", + srcs = ["runfiles.rs"], + visibility = ["//visibility:public"], +) + +rust_test( + name = "runfiles_test", + data = ["data/sample.txt"], + deps = [":runfiles"], +) + +rust_doc_test( + name = "runfiles_doc_test", + dep = ":runfiles", +) diff --git a/examples/hello_runfiles/data/sample.txt b/tools/runfiles/data/sample.txt similarity index 100% rename from examples/hello_runfiles/data/sample.txt rename to tools/runfiles/data/sample.txt diff --git a/tools/runfiles/runfiles.rs b/tools/runfiles/runfiles.rs new file mode 100644 index 0000000000..f2b9486079 --- /dev/null +++ b/tools/runfiles/runfiles.rs @@ -0,0 +1,129 @@ +//! Runfiles lookup library for Bazel-built Rust binaries and tests. +//! +//! USAGE: +//! +//! 1. Depend on this runfiles library from your build rule: +//! ```python +//! rust_binary( +//! name = "my_binary", +//! ... +//! data = ["//path/to/my/data.txt"], +//! deps = ["@io_bazel_rules_rust//tools/runfiles"], +//! ) +//! ``` +//! +//! 2. Import the runfiles library. +//! ``` +//! extern crate runfiles; +//! +//! use runfiles::Runfiles; +//! ``` +//! +//! 3. Create a Runfiles object and use rlocation to look up runfile paths: +//! ```ignore -- This doesn't work under rust_doc_test because argv[0] is not what we expect. +//! +//! use runfiles::Runfiles; +//! +//! let r = Runfiles::create().unwrap(); +//! let path = r.rlocation("my_workspace/path/to/my/data.txt"); +//! +//! let f = File::open(path).unwrap(); +//! // ... +//! ``` + +use std::io; +use std::fs; +use std::path::PathBuf; +use std::path::Path; +use std::env; + +pub struct Runfiles { + runfiles_dir: PathBuf, +} + +impl Runfiles { + /// Creates a directory based Runfiles object. + /// + /// Manifest based creation is not currently supported. + pub fn create() -> io::Result { + Ok(Runfiles { runfiles_dir: find_runfiles_dir()? }) + } + + /// Returns the runtime path of a runfile. + /// + /// Runfiles are data-dependencies of Bazel-built binaries and tests. + /// The returned path may not be valid. The caller should check the path's + /// validity and that the path exists. + pub fn rlocation(&self, path: impl AsRef) -> PathBuf { + let path = path.as_ref(); + if path.is_absolute() { + return path.to_path_buf(); + } + self.runfiles_dir.join(path) + } +} + +/// Returns the .runfiles directory for the currently executing binary. +fn find_runfiles_dir() -> io::Result { + let exec_path = std::env::args().nth(0).expect("arg 0 was not set"); + + let mut binary_path = PathBuf::from(&exec_path); + loop { + // Check for our neighboring $binary.runfiles directory. + let mut runfiles_name = binary_path.file_name().unwrap().to_owned(); + runfiles_name.push(".runfiles"); + + let runfiles_path = binary_path.with_file_name(&runfiles_name); + if runfiles_path.is_dir() { + return Ok(runfiles_path); + } + + // Check if we're already under a *.runfiles directory. + { + // TODO: 1.28 adds Path::ancestors() which is a little simpler. + let mut next = binary_path.parent(); + while let Some(ancestor) = next { + if ancestor.file_name().map_or(false, |f| { + f.to_string_lossy().ends_with(".runfiles") + }) + { + return Ok(ancestor.to_path_buf()); + } + next = ancestor.parent(); + } + } + + if !fs::symlink_metadata(&binary_path)?.file_type().is_symlink() { + break; + } + // Follow symlinks and keep looking. + binary_path = binary_path.read_link()?; + if binary_path.is_relative() { + binary_path = env::current_dir()?.join(binary_path) + } + } + + panic!("Failed to find .runfiles directory."); +} + +#[cfg(test)] +mod test { + use super::*; + + use std::io::prelude::*; + use std::fs::File; + + #[test] + fn test_can_read_data_from_runfiles() { + let r = Runfiles::create().unwrap(); + + let mut f = File::open(r.rlocation( + "io_bazel_rules_rust/tools/runfiles/data/sample.txt", + )).unwrap(); + + let mut buffer = String::new(); + f.read_to_string(&mut buffer).unwrap(); + + assert_eq!("Example Text!", buffer); + } +}