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}