Skip to content

Commit 1678ed0

Browse files
authored
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.
1 parent b3c8bad commit 1678ed0

File tree

12 files changed

+181
-83
lines changed

12 files changed

+181
-83
lines changed

.bazelci/presubmit.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,7 @@ platforms:
3030
- "//test/..."
3131
- "@examples//..."
3232
- "-//test/conflicting_deps:conflicting_deps_test"
33-
# Runfiles currently not working properly with remote execution
34-
# https://github.com/bazelbuild/rules_rust/issues/112
35-
- "-@examples//hello_runfiles:hello_runfiles_test"
3633
# rust_doc_test is likely not fully sandboxed
3734
- "-@examples//fibonacci:fibonacci_doc_test"
3835
- "-@examples//hello_lib:hello_lib_doc_test"
36+
- "-//tools/runfiles:runfiles_doc_test"

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
1+
# vim
12
*.swp
3+
4+
# bazel
25
/bazel-*
6+
7+
# rustfmt
8+
*.rs.bk

examples/ffi/c_calling_rust/BUILD

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
package(default_visibility = ["//visibility:private"])
2-
31
load("@//rust:rust.bzl", "rust_library", "rust_test", "rust_binary")
42

53
rust_library(

examples/fibonacci/BUILD

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
package(default_visibility = ["//visibility:public"])
2-
31
load(
42
"@io_bazel_rules_rust//rust:rust.bzl",
53
"rust_library",

examples/hello_out_dir/BUILD

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
package(default_visibility = ["//visibility:public"])
2-
31
load(
42
"@io_bazel_rules_rust//rust:rust.bzl",
53
"rust_binary",

examples/hello_runfiles/BUILD

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,14 @@ package(default_visibility = ["//visibility:public"])
22

33
load(
44
"@io_bazel_rules_rust//rust:rust.bzl",
5-
"rust_library",
65
"rust_binary",
6+
"rust_library",
77
"rust_test",
88
)
99

10-
rust_library(
11-
name = "runfiles",
12-
srcs = ["src/lib.rs"],
13-
)
14-
1510
rust_binary(
1611
name = "hello_runfiles",
17-
srcs = ["src/main.rs"],
18-
data = ["data/sample.txt"],
19-
deps = [":runfiles"],
20-
)
21-
22-
rust_test(
23-
name = "hello_runfiles_test",
24-
data = ["data/sample.txt"],
25-
deps = [":runfiles"],
12+
srcs = ["hello_runfiles.rs"],
13+
data = ["hello_runfiles.rs"], # Yes, we're being cute.
14+
deps = ["@io_bazel_rules_rust//tools/runfiles"],
2615
)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
extern crate runfiles;
2+
3+
use std::io::prelude::*;
4+
use std::fs::File;
5+
6+
use runfiles::Runfiles;
7+
8+
fn main() {
9+
let r = Runfiles::create().unwrap();
10+
11+
let mut f = File::open(r.rlocation("examples/hello_runfiles/hello_runfiles.rs")).unwrap();
12+
13+
let mut buffer = String::new();
14+
f.read_to_string(&mut buffer).unwrap();
15+
16+
assert_eq!(buffer.len(), 427);
17+
println!("This program's source is:\n```\n{}\n```", buffer);
18+
}

examples/hello_runfiles/src/lib.rs

Lines changed: 0 additions & 43 deletions
This file was deleted.

examples/hello_runfiles/src/main.rs

Lines changed: 0 additions & 16 deletions
This file was deleted.

tools/runfiles/BUILD

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
load(
2+
"@io_bazel_rules_rust//rust:rust.bzl",
3+
"rust_doc_test",
4+
"rust_library",
5+
"rust_test",
6+
)
7+
8+
rust_library(
9+
name = "runfiles",
10+
srcs = ["runfiles.rs"],
11+
visibility = ["//visibility:public"],
12+
)
13+
14+
rust_test(
15+
name = "runfiles_test",
16+
data = ["data/sample.txt"],
17+
deps = [":runfiles"],
18+
)
19+
20+
rust_doc_test(
21+
name = "runfiles_doc_test",
22+
dep = ":runfiles",
23+
)

tools/runfiles/runfiles.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
//! Runfiles lookup library for Bazel-built Rust binaries and tests.
2+
//!
3+
//! USAGE:
4+
//!
5+
//! 1. Depend on this runfiles library from your build rule:
6+
//! ```python
7+
//! rust_binary(
8+
//! name = "my_binary",
9+
//! ...
10+
//! data = ["//path/to/my/data.txt"],
11+
//! deps = ["@io_bazel_rules_rust//tools/runfiles"],
12+
//! )
13+
//! ```
14+
//!
15+
//! 2. Import the runfiles library.
16+
//! ```
17+
//! extern crate runfiles;
18+
//!
19+
//! use runfiles::Runfiles;
20+
//! ```
21+
//!
22+
//! 3. Create a Runfiles object and use rlocation to look up runfile paths:
23+
//! ```ignore -- This doesn't work under rust_doc_test because argv[0] is not what we expect.
24+
//!
25+
//! use runfiles::Runfiles;
26+
//!
27+
//! let r = Runfiles::create().unwrap();
28+
//! let path = r.rlocation("my_workspace/path/to/my/data.txt");
29+
//!
30+
//! let f = File::open(path).unwrap();
31+
//! // ...
32+
//! ```
33+
34+
use std::io;
35+
use std::fs;
36+
use std::path::PathBuf;
37+
use std::path::Path;
38+
use std::env;
39+
40+
pub struct Runfiles {
41+
runfiles_dir: PathBuf,
42+
}
43+
44+
impl Runfiles {
45+
/// Creates a directory based Runfiles object.
46+
///
47+
/// Manifest based creation is not currently supported.
48+
pub fn create() -> io::Result<Self> {
49+
Ok(Runfiles { runfiles_dir: find_runfiles_dir()? })
50+
}
51+
52+
/// Returns the runtime path of a runfile.
53+
///
54+
/// Runfiles are data-dependencies of Bazel-built binaries and tests.
55+
/// The returned path may not be valid. The caller should check the path's
56+
/// validity and that the path exists.
57+
pub fn rlocation(&self, path: impl AsRef<Path>) -> PathBuf {
58+
let path = path.as_ref();
59+
if path.is_absolute() {
60+
return path.to_path_buf();
61+
}
62+
self.runfiles_dir.join(path)
63+
}
64+
}
65+
66+
/// Returns the .runfiles directory for the currently executing binary.
67+
fn find_runfiles_dir() -> io::Result<PathBuf> {
68+
let exec_path = std::env::args().nth(0).expect("arg 0 was not set");
69+
70+
let mut binary_path = PathBuf::from(&exec_path);
71+
loop {
72+
// Check for our neighboring $binary.runfiles directory.
73+
let mut runfiles_name = binary_path.file_name().unwrap().to_owned();
74+
runfiles_name.push(".runfiles");
75+
76+
let runfiles_path = binary_path.with_file_name(&runfiles_name);
77+
if runfiles_path.is_dir() {
78+
return Ok(runfiles_path);
79+
}
80+
81+
// Check if we're already under a *.runfiles directory.
82+
{
83+
// TODO: 1.28 adds Path::ancestors() which is a little simpler.
84+
let mut next = binary_path.parent();
85+
while let Some(ancestor) = next {
86+
if ancestor.file_name().map_or(false, |f| {
87+
f.to_string_lossy().ends_with(".runfiles")
88+
})
89+
{
90+
return Ok(ancestor.to_path_buf());
91+
}
92+
next = ancestor.parent();
93+
}
94+
}
95+
96+
if !fs::symlink_metadata(&binary_path)?.file_type().is_symlink() {
97+
break;
98+
}
99+
// Follow symlinks and keep looking.
100+
binary_path = binary_path.read_link()?;
101+
if binary_path.is_relative() {
102+
binary_path = env::current_dir()?.join(binary_path)
103+
}
104+
}
105+
106+
panic!("Failed to find .runfiles directory.");
107+
}
108+
109+
#[cfg(test)]
110+
mod test {
111+
use super::*;
112+
113+
use std::io::prelude::*;
114+
use std::fs::File;
115+
116+
#[test]
117+
fn test_can_read_data_from_runfiles() {
118+
let r = Runfiles::create().unwrap();
119+
120+
let mut f = File::open(r.rlocation(
121+
"io_bazel_rules_rust/tools/runfiles/data/sample.txt",
122+
)).unwrap();
123+
124+
let mut buffer = String::new();
125+
f.read_to_string(&mut buffer).unwrap();
126+
127+
assert_eq!("Example Text!", buffer);
128+
}
129+
}

0 commit comments

Comments
 (0)