Implement directory based runfiles lookup (#132)
* Move runfiles library to tools/
* Implement directory based runfiles lookup.
* Add doc_test for runfiles.
* Update CI config.
* Spice up runfiles example.
diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml
index d57caf1..91fa6ae 100644
--- a/.bazelci/presubmit.yml
+++ b/.bazelci/presubmit.yml
@@ -30,9 +30,7 @@
- "//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 5a10533..0d43ef1 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 940af5f..47960e4 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 8a3dc4e..21bb74b 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 6e3904e..e92e287 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 e6426b1..5503251 100644
--- a/examples/hello_runfiles/BUILD
+++ b/examples/hello_runfiles/BUILD
@@ -2,25 +2,14 @@
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 0000000..caeb81a
--- /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 3e8aa63..0000000
--- 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<PathBuf> {
- 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 ae0f641..0000000
--- 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 0000000..e9be3e5
--- /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 0000000..f2b9486
--- /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<Self> {
+ 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<Path>) -> 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<PathBuf> {
+ 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);
+ }
+}