bazel_cwd/lib.rs
1// Copyright 2025 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//! Resolve paths in a Bazel-aware manner
15//!
16//! The `bazel run` command runs the specified executable with the working directory set to the
17//! runfiles tree of the binary, not the location where `bazel run` was run from. See
18//! <https://bazel.build/docs/user-manual#running-executables> for more.
19//!
20//! This crates provides a [`BazelPath`] helper trait with an [`unbazelify`][BazelPath::unbazelify]
21//! function to resolve paths relative to wherever `bazel run` was run from, allowing for path
22//! parsing in a more user-friendly way.
23//!
24//! ```
25//! use std::path::Path;
26//! use bazel_cwd::BazelPath;
27//!
28//! let path = Path::new("foo/bar");
29//! let resolved_path = path.unbazelify().unwrap();
30//! // resolved_path will be an absolute path, resolving the relative path against the directory
31//! // `bazel run` was run in.
32//! ```
33
34use std::ffi::OsString;
35use std::path::{Path, PathBuf};
36
37pub trait BazelPath {
38 /// Resolve the given path to an absolute path in a Bazel-aware manner.
39 ///
40 /// If the current program was run via `bazel run`, relative paths will be resolved using the
41 /// directory the bazel command was run from. Otherwise, it resolves relative paths against the
42 /// current working directory. Absolute paths are left as is.
43 ///
44 /// # Errors
45 ///
46 /// Returns an `Err` if the current working directory could not be determined, e.g. because it
47 /// does not exist or there are insufficient permissions to access it.
48 fn unbazelify(&self) -> std::io::Result<PathBuf>;
49}
50impl<T> BazelPath for T
51where
52 T: AsRef<Path>,
53{
54 fn unbazelify(&self) -> std::io::Result<PathBuf> {
55 unbazelify_impl(self.as_ref())
56 }
57}
58
59fn unbazelify_impl(path: &Path) -> Result<PathBuf, std::io::Error> {
60 if !path.is_relative() {
61 return Ok(path.into());
62 }
63 let root: PathBuf = if let Some(bazel_dir) = std::env::var_os("BUILD_WORKING_DIRECTORY") {
64 <OsString as AsRef<Path>>::as_ref(&bazel_dir).to_path_buf()
65 } else {
66 std::env::current_dir()?
67 };
68 Ok(root.join(path))
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn absolute_paths() {
77 for absolute_path in ["/", "/foo/bar", "/foo/bar/"].into_iter().map(Path::new) {
78 assert_eq!(
79 absolute_path,
80 absolute_path.unbazelify().unwrap(),
81 "absolute paths should remain unchanged"
82 );
83 }
84 }
85
86 #[test]
87 fn relative_paths() {
88 for relative_path in [
89 "",
90 ".",
91 "./foo",
92 "foo/",
93 "..",
94 "../foo",
95 "foo/bar/",
96 "foo/../bar",
97 ]
98 .into_iter()
99 .map(Path::new)
100 {
101 assert_ne!(
102 relative_path,
103 relative_path.unbazelify().unwrap(),
104 "relative paths should be resolved to an absolute path"
105 );
106 }
107 }
108}