Skip to main content

run_make_support/
path_helpers.rs

1//! Collection of path-related helpers.
2
3use std::path::{Path, PathBuf};
4
5use crate::env::env_var;
6use crate::rfs;
7
8/// Return the current working directory.
9///
10/// This forwards to [`std::env::current_dir`], please see its docs regarding platform-specific
11/// behavior.
12#[must_use]
13pub fn cwd() -> PathBuf {
14    std::env::current_dir().unwrap()
15}
16
17/// Construct a `PathBuf` relative to the current working directory by joining `cwd()` with the
18/// relative path. This is mostly a convenience helper so the test writer does not need to write
19/// `PathBuf::from(path_like_string)`.
20///
21/// # Example
22///
23/// ```rust
24/// # use run_make_support::path;
25/// let p = path("support_file.txt");
26/// ```
27pub fn path<P: AsRef<Path>>(p: P) -> PathBuf {
28    cwd().join(p.as_ref())
29}
30
31/// Path to the root `rust-lang/rust` source checkout.
32#[must_use]
33pub fn source_root() -> PathBuf {
34    env_var("SOURCE_ROOT").into()
35}
36
37/// Path to the build directory root.
38#[must_use]
39pub fn build_root() -> PathBuf {
40    env_var("BUILD_ROOT").into()
41}
42
43/// Browse the directory `path` non-recursively and return all files which respect the parameters
44/// outlined by `closure`.
45#[track_caller]
46pub fn shallow_find_files<P: AsRef<Path>, F: Fn(&PathBuf) -> bool>(
47    path: P,
48    filter: F,
49) -> Vec<PathBuf> {
50    let mut matching_files = Vec::new();
51    for entry in rfs::read_dir(path) {
52        let entry = entry.expect("failed to read directory entry.");
53        let path = entry.path();
54
55        if path.is_file() && filter(&path) {
56            matching_files.push(path);
57        }
58    }
59    matching_files
60}
61
62/// Browse the directory `path` recursively and return all files which respect the parameters
63/// outlined by `closure`.
64#[track_caller]
65pub fn recursive_find_files<P: AsRef<Path>, F: Fn(&PathBuf) -> bool>(
66    path: P,
67    filter: F,
68) -> Vec<PathBuf> {
69    let mut matching_files = Vec::new();
70    let mut stack = vec![path.as_ref().to_path_buf()];
71    while let Some(dir) = stack.pop() {
72        for entry in rfs::read_dir(dir) {
73            let entry = entry.expect("failed to read directory entry.");
74            let path = entry.path();
75
76            if path.is_dir() {
77                stack.push(path);
78            } else if path.is_file() && filter(&path) {
79                matching_files.push(path);
80            }
81        }
82    }
83    matching_files
84}
85
86/// Browse the directory `path` non-recursively and return all directories which respect the
87/// parameters outlined by `closure`.
88#[track_caller]
89pub fn shallow_find_directories<P: AsRef<Path>, F: Fn(&PathBuf) -> bool>(
90    path: P,
91    filter: F,
92) -> Vec<PathBuf> {
93    let mut matching_files = Vec::new();
94    for entry in rfs::read_dir(path) {
95        let entry = entry.expect("failed to read directory entry.");
96        let path = entry.path();
97
98        if path.is_dir() && filter(&path) {
99            matching_files.push(path);
100        }
101    }
102    matching_files
103}
104
105/// Returns true if the filename at `path` does not contain `expected`.
106pub fn not_contains<P: AsRef<Path>>(path: P, expected: &str) -> bool {
107    !path.as_ref().file_name().is_some_and(|name| name.to_str().unwrap().contains(expected))
108}
109
110/// Returns true if the filename at `path` is not in `expected`.
111pub fn filename_not_in_denylist<P: AsRef<Path>, V: AsRef<[String]>>(path: P, expected: V) -> bool {
112    let expected = expected.as_ref();
113    path.as_ref()
114        .file_name()
115        .is_some_and(|name| !expected.contains(&name.to_str().unwrap().to_owned()))
116}
117
118/// Returns true if the filename at `path` starts with `prefix`.
119pub fn has_prefix<P: AsRef<Path>>(path: P, prefix: &str) -> bool {
120    path.as_ref().file_name().is_some_and(|name| name.to_str().unwrap().starts_with(prefix))
121}
122
123/// Returns true if the filename at `path` has the extension `extension`.
124pub fn has_extension<P: AsRef<Path>>(path: P, extension: &str) -> bool {
125    path.as_ref().extension().is_some_and(|ext| ext == extension)
126}
127
128/// Returns true if the filename at `path` ends with `suffix`.
129pub fn has_suffix<P: AsRef<Path>>(path: P, suffix: &str) -> bool {
130    path.as_ref().file_name().is_some_and(|name| name.to_str().unwrap().ends_with(suffix))
131}
132
133/// Returns true if the filename at `path` contains `needle`.
134pub fn filename_contains<P: AsRef<Path>>(path: P, needle: &str) -> bool {
135    path.as_ref().file_name().is_some_and(|name| name.to_str().unwrap().contains(needle))
136}
137
138/// Helper for reading entries in a given directory and its children.
139pub fn read_dir_entries_recursive<P: AsRef<Path>, F: FnMut(&Path)>(dir: P, mut callback: F) {
140    fn read_dir_entries_recursive_inner<P: AsRef<Path>, F: FnMut(&Path)>(dir: P, callback: &mut F) {
141        for entry in rfs::read_dir(dir) {
142            let path = entry.unwrap().path();
143            callback(&path);
144            if path.is_dir() {
145                read_dir_entries_recursive_inner(path, callback);
146            }
147        }
148    }
149
150    read_dir_entries_recursive_inner(dir, &mut callback);
151}