Add support for manifest based runfiles. (#760)

* Add support for manifest based runfiles.

These are typically used on Windows when using symlinks which are needed
for directory based runfiles is not possible.
diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml
index 9881deb..478cfdf 100644
--- a/.bazelci/presubmit.yml
+++ b/.bazelci/presubmit.yml
@@ -53,7 +53,6 @@
       - "-//test/chained_direct_deps:mod3_doc_test"
       - "-@examples//fibonacci:fibonacci_doc_test"
       - "-@examples//hello_lib:hello_lib_doc_test"
-      - "-//tools/runfiles:runfiles_doc_test"
       - "-@examples//ffi/rust_calling_c/simple/..."
       # See https://github.com/bazelbuild/bazel/issues/9987
       - "-@examples//ffi/rust_calling_c:matrix_dylib_test"
@@ -71,7 +70,6 @@
       - "-//test/test_env/..."
       - "-//test/proto/..."
       - "-//tools/rust_analyzer/..."
-      - "-//tools/runfiles/..."
       - "-//test/rustfmt/..."
       - "@examples//..."
       - "-@examples//ffi/rust_calling_c:matrix_dylib_test"
diff --git a/tools/runfiles/runfiles.rs b/tools/runfiles/runfiles.rs
index 8f3bf5d..6486c2e 100644
--- a/tools/runfiles/runfiles.rs
+++ b/tools/runfiles/runfiles.rs
@@ -13,7 +13,7 @@
 //!     ```
 //!
 //! 2.  Import the runfiles library.
-//!     ```
+//!     ```ignore
 //!     extern crate runfiles;
 //!
 //!     use runfiles::Runfiles;
@@ -31,23 +31,55 @@
 //!     // ...
 //!     ```
 
+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;
 
+enum Mode {
+    DirectoryBased(PathBuf),
+    ManifestBased(HashMap<PathBuf, PathBuf>),
+}
+
 pub struct Runfiles {
-    runfiles_dir: PathBuf,
+    mode: Mode,
 }
 
 impl Runfiles {
-    /// Creates a directory based Runfiles object.
-    ///
-    /// Manifest based creation is not currently supported.
+    /// 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 {
-            runfiles_dir: find_runfiles_dir()?,
+            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),
         })
     }
 
@@ -61,12 +93,25 @@
         if path.is_absolute() {
             return path.to_path_buf();
         }
-        self.runfiles_dir.join(path)
+        match &self.mode {
+            Mode::DirectoryBased(runfiles_dir) => runfiles_dir.join(path),
+            Mode::ManifestBased(path_mapping) => path_mapping
+                .get(path)
+                .expect(&format!(
+                    "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(OsString::from("0")),
+        "1"
+    );
     let exec_path = std::env::args().nth(0).expect("arg 0 was not set");
 
     let mut binary_path = PathBuf::from(&exec_path);
@@ -108,10 +153,31 @@
         }
     }
 
-    Err(io::Error::new(
-        io::ErrorKind::Other,
-        "Failed to find .runfiles directory.",
-    ))
+    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)]
@@ -132,4 +198,15 @@
 
         assert_eq!("Example Text!", buffer);
     }
+
+    #[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"));
+    }
 }