Squashed 'third_party/rules_rust/' content from commit bf59038ca
git-subtree-dir: third_party/rules_rust
git-subtree-split: bf59038cac11798cbaef9f3bf965bad8182b97fa
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I5a20e403203d670df467ea97dde9a4ac40339a8d
diff --git a/tools/runfiles/runfiles.rs b/tools/runfiles/runfiles.rs
new file mode 100644
index 0000000..04f3366
--- /dev/null
+++ b/tools/runfiles/runfiles.rs
@@ -0,0 +1,250 @@
+//! 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 = ["@rules_rust//tools/runfiles"],
+//! )
+//! ```
+//!
+//! 2. Import the runfiles library.
+//! ```ignore
+//! 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::collections::HashMap;
+use std::env;
+use std::ffi::OsString;
+use std::fs;
+use std::io;
+use std::path::Path;
+use std::path::PathBuf;
+
+#[derive(Debug)]
+enum Mode {
+ DirectoryBased(PathBuf),
+ ManifestBased(HashMap<PathBuf, PathBuf>),
+}
+
+#[derive(Debug)]
+pub struct Runfiles {
+ mode: Mode,
+}
+
+impl Runfiles {
+ /// Creates a manifest based Runfiles object when
+ /// RUNFILES_MANIFEST_ONLY environment variable is present,
+ /// or a directory based Runfiles object otherwise.
+ pub fn create() -> io::Result<Self> {
+ if is_manifest_only() {
+ Self::create_manifest_based()
+ } else {
+ Self::create_directory_based()
+ }
+ }
+
+ fn create_directory_based() -> io::Result<Self> {
+ Ok(Runfiles {
+ mode: Mode::DirectoryBased(find_runfiles_dir()?),
+ })
+ }
+
+ fn create_manifest_based() -> io::Result<Self> {
+ let manifest_path = find_manifest_path()?;
+ let manifest_content = std::fs::read_to_string(manifest_path)?;
+ let path_mapping = manifest_content
+ .lines()
+ .map(|line| {
+ let pair = line
+ .split_once(' ')
+ .expect("manifest file contained unexpected content");
+ (pair.0.into(), pair.1.into())
+ })
+ .collect::<HashMap<_, _>>();
+ Ok(Runfiles {
+ mode: Mode::ManifestBased(path_mapping),
+ })
+ }
+
+ /// 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();
+ }
+ match &self.mode {
+ Mode::DirectoryBased(runfiles_dir) => runfiles_dir.join(path),
+ Mode::ManifestBased(path_mapping) => path_mapping
+ .get(path)
+ .unwrap_or_else(|| {
+ panic!("Path {} not found among runfiles.", path.to_string_lossy())
+ })
+ .clone(),
+ }
+ }
+}
+
+/// Returns the .runfiles directory for the currently executing binary.
+pub fn find_runfiles_dir() -> io::Result<PathBuf> {
+ assert_ne!(
+ std::env::var_os("RUNFILES_MANIFEST_ONLY").unwrap_or_else(|| OsString::from("0")),
+ "1"
+ );
+
+ // If bazel told us about the runfiles dir, use that without looking further.
+ if let Some(test_srcdir) = std::env::var_os("TEST_SRCDIR").map(PathBuf::from) {
+ if test_srcdir.is_dir() {
+ return Ok(test_srcdir);
+ }
+ }
+
+ // Consume the first argument (argv[0])
+ let exec_path = std::env::args().next().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.
+ let link_target = binary_path.read_link()?;
+ binary_path = if link_target.is_absolute() {
+ link_target
+ } else {
+ let link_dir = binary_path.parent().unwrap();
+ env::current_dir()?.join(link_dir).join(link_target)
+ }
+ }
+
+ Err(make_io_error("failed to find .runfiles directory"))
+}
+
+fn make_io_error(msg: &str) -> io::Error {
+ io::Error::new(io::ErrorKind::Other, msg)
+}
+
+fn is_manifest_only() -> bool {
+ match std::env::var("RUNFILES_MANIFEST_ONLY") {
+ Ok(val) => val == "1",
+ Err(_) => false,
+ }
+}
+
+fn find_manifest_path() -> io::Result<PathBuf> {
+ assert_eq!(
+ std::env::var_os("RUNFILES_MANIFEST_ONLY").expect("RUNFILES_MANIFEST_ONLY was not set"),
+ OsString::from("1")
+ );
+ match std::env::var_os("RUNFILES_MANIFEST_FILE") {
+ Some(path) => Ok(path.into()),
+ None => Err(
+ make_io_error(
+ "RUNFILES_MANIFEST_ONLY was set to '1', but RUNFILES_MANIFEST_FILE was not set. Did Bazel change?"))
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ use std::fs::File;
+ use std::io::prelude::*;
+
+ #[test]
+ fn test_can_read_data_from_runfiles() {
+ // We want to run two test cases: one with the $TEST_SRCDIR environment variable set and one
+ // with it not set. Since environment variables are global state, we need to ensure the two
+ // test cases do not run concurrently. Rust runs tests in parallel and does not provide an
+ // easy way to synchronise them, so we run both test cases in the same #[test] function.
+
+ let test_srcdir = env::var_os("TEST_SRCDIR").expect("bazel did not provide TEST_SRCDIR");
+
+ // Test case 1: $TEST_SRCDIR is set.
+ {
+ let r = Runfiles::create().unwrap();
+
+ let mut f =
+ File::open(r.rlocation("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);
+ }
+
+ // Test case 2: $TEST_SRCDIR is *not* set.
+ {
+ env::remove_var("TEST_SRCDIR");
+
+ let r = Runfiles::create().unwrap();
+
+ let mut f =
+ File::open(r.rlocation("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);
+
+ env::set_var("TEST_SRCDIR", test_srcdir);
+ }
+ }
+
+ #[test]
+ fn test_manifest_based_can_read_data_from_runfiles() {
+ let mut path_mapping = HashMap::new();
+ path_mapping.insert("a/b".into(), "c/d".into());
+ let r = Runfiles {
+ mode: Mode::ManifestBased(path_mapping),
+ };
+
+ assert_eq!(r.rlocation("a/b"), PathBuf::from("c/d"));
+ }
+}