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);
+    }
+}