xref: /third_party/rust/crates/which-rs/src/finder.rs (revision b3ba51a1)
1b3ba51a1Sopenharmony_ciuse crate::checker::CompositeChecker;
2b3ba51a1Sopenharmony_ciuse crate::error::*;
3b3ba51a1Sopenharmony_ci#[cfg(windows)]
4b3ba51a1Sopenharmony_ciuse crate::helper::has_executable_extension;
5b3ba51a1Sopenharmony_ciuse either::Either;
6b3ba51a1Sopenharmony_ci#[cfg(feature = "regex")]
7b3ba51a1Sopenharmony_ciuse regex::Regex;
8b3ba51a1Sopenharmony_ci#[cfg(feature = "regex")]
9b3ba51a1Sopenharmony_ciuse std::borrow::Borrow;
10b3ba51a1Sopenharmony_ciuse std::env;
11b3ba51a1Sopenharmony_ciuse std::ffi::OsStr;
12b3ba51a1Sopenharmony_ci#[cfg(any(feature = "regex", target_os = "windows"))]
13b3ba51a1Sopenharmony_ciuse std::fs;
14b3ba51a1Sopenharmony_ciuse std::iter;
15b3ba51a1Sopenharmony_ciuse std::path::{Path, PathBuf};
16b3ba51a1Sopenharmony_ci
17b3ba51a1Sopenharmony_cipub trait Checker {
18b3ba51a1Sopenharmony_ci    fn is_valid(&self, path: &Path) -> bool;
19b3ba51a1Sopenharmony_ci}
20b3ba51a1Sopenharmony_ci
21b3ba51a1Sopenharmony_citrait PathExt {
22b3ba51a1Sopenharmony_ci    fn has_separator(&self) -> bool;
23b3ba51a1Sopenharmony_ci
24b3ba51a1Sopenharmony_ci    fn to_absolute<P>(self, cwd: P) -> PathBuf
25b3ba51a1Sopenharmony_ci    where
26b3ba51a1Sopenharmony_ci        P: AsRef<Path>;
27b3ba51a1Sopenharmony_ci}
28b3ba51a1Sopenharmony_ci
29b3ba51a1Sopenharmony_ciimpl PathExt for PathBuf {
30b3ba51a1Sopenharmony_ci    fn has_separator(&self) -> bool {
31b3ba51a1Sopenharmony_ci        self.components().count() > 1
32b3ba51a1Sopenharmony_ci    }
33b3ba51a1Sopenharmony_ci
34b3ba51a1Sopenharmony_ci    fn to_absolute<P>(self, cwd: P) -> PathBuf
35b3ba51a1Sopenharmony_ci    where
36b3ba51a1Sopenharmony_ci        P: AsRef<Path>,
37b3ba51a1Sopenharmony_ci    {
38b3ba51a1Sopenharmony_ci        if self.is_absolute() {
39b3ba51a1Sopenharmony_ci            self
40b3ba51a1Sopenharmony_ci        } else {
41b3ba51a1Sopenharmony_ci            let mut new_path = PathBuf::from(cwd.as_ref());
42b3ba51a1Sopenharmony_ci            new_path.push(self);
43b3ba51a1Sopenharmony_ci            new_path
44b3ba51a1Sopenharmony_ci        }
45b3ba51a1Sopenharmony_ci    }
46b3ba51a1Sopenharmony_ci}
47b3ba51a1Sopenharmony_ci
48b3ba51a1Sopenharmony_cipub struct Finder;
49b3ba51a1Sopenharmony_ci
50b3ba51a1Sopenharmony_ciimpl Finder {
51b3ba51a1Sopenharmony_ci    pub fn new() -> Finder {
52b3ba51a1Sopenharmony_ci        Finder
53b3ba51a1Sopenharmony_ci    }
54b3ba51a1Sopenharmony_ci
55b3ba51a1Sopenharmony_ci    pub fn find<T, U, V>(
56b3ba51a1Sopenharmony_ci        &self,
57b3ba51a1Sopenharmony_ci        binary_name: T,
58b3ba51a1Sopenharmony_ci        paths: Option<U>,
59b3ba51a1Sopenharmony_ci        cwd: Option<V>,
60b3ba51a1Sopenharmony_ci        binary_checker: CompositeChecker,
61b3ba51a1Sopenharmony_ci    ) -> Result<impl Iterator<Item = PathBuf>>
62b3ba51a1Sopenharmony_ci    where
63b3ba51a1Sopenharmony_ci        T: AsRef<OsStr>,
64b3ba51a1Sopenharmony_ci        U: AsRef<OsStr>,
65b3ba51a1Sopenharmony_ci        V: AsRef<Path>,
66b3ba51a1Sopenharmony_ci    {
67b3ba51a1Sopenharmony_ci        let path = PathBuf::from(&binary_name);
68b3ba51a1Sopenharmony_ci
69b3ba51a1Sopenharmony_ci        let binary_path_candidates = match cwd {
70b3ba51a1Sopenharmony_ci            Some(cwd) if path.has_separator() => {
71b3ba51a1Sopenharmony_ci                // Search binary in cwd if the path have a path separator.
72b3ba51a1Sopenharmony_ci                Either::Left(Self::cwd_search_candidates(path, cwd).into_iter())
73b3ba51a1Sopenharmony_ci            }
74b3ba51a1Sopenharmony_ci            _ => {
75b3ba51a1Sopenharmony_ci                // Search binary in PATHs(defined in environment variable).
76b3ba51a1Sopenharmony_ci                let p = paths.ok_or(Error::CannotFindBinaryPath)?;
77b3ba51a1Sopenharmony_ci                let paths: Vec<_> = env::split_paths(&p).collect();
78b3ba51a1Sopenharmony_ci
79b3ba51a1Sopenharmony_ci                Either::Right(Self::path_search_candidates(path, paths).into_iter())
80b3ba51a1Sopenharmony_ci            }
81b3ba51a1Sopenharmony_ci        };
82b3ba51a1Sopenharmony_ci
83b3ba51a1Sopenharmony_ci        Ok(binary_path_candidates
84b3ba51a1Sopenharmony_ci            .filter(move |p| binary_checker.is_valid(p))
85b3ba51a1Sopenharmony_ci            .map(correct_casing))
86b3ba51a1Sopenharmony_ci    }
87b3ba51a1Sopenharmony_ci
88b3ba51a1Sopenharmony_ci    #[cfg(feature = "regex")]
89b3ba51a1Sopenharmony_ci    pub fn find_re<T>(
90b3ba51a1Sopenharmony_ci        &self,
91b3ba51a1Sopenharmony_ci        binary_regex: impl Borrow<Regex>,
92b3ba51a1Sopenharmony_ci        paths: Option<T>,
93b3ba51a1Sopenharmony_ci        binary_checker: CompositeChecker,
94b3ba51a1Sopenharmony_ci    ) -> Result<impl Iterator<Item = PathBuf>>
95b3ba51a1Sopenharmony_ci    where
96b3ba51a1Sopenharmony_ci        T: AsRef<OsStr>,
97b3ba51a1Sopenharmony_ci    {
98b3ba51a1Sopenharmony_ci        let p = paths.ok_or(Error::CannotFindBinaryPath)?;
99b3ba51a1Sopenharmony_ci        // Collect needs to happen in order to not have to
100b3ba51a1Sopenharmony_ci        // change the API to borrow on `paths`.
101b3ba51a1Sopenharmony_ci        #[allow(clippy::needless_collect)]
102b3ba51a1Sopenharmony_ci        let paths: Vec<_> = env::split_paths(&p).collect();
103b3ba51a1Sopenharmony_ci
104b3ba51a1Sopenharmony_ci        let matching_re = paths
105b3ba51a1Sopenharmony_ci            .into_iter()
106b3ba51a1Sopenharmony_ci            .flat_map(fs::read_dir)
107b3ba51a1Sopenharmony_ci            .flatten()
108b3ba51a1Sopenharmony_ci            .flatten()
109b3ba51a1Sopenharmony_ci            .map(|e| e.path())
110b3ba51a1Sopenharmony_ci            .filter(move |p| {
111b3ba51a1Sopenharmony_ci                if let Some(unicode_file_name) = p.file_name().unwrap().to_str() {
112b3ba51a1Sopenharmony_ci                    binary_regex.borrow().is_match(unicode_file_name)
113b3ba51a1Sopenharmony_ci                } else {
114b3ba51a1Sopenharmony_ci                    false
115b3ba51a1Sopenharmony_ci                }
116b3ba51a1Sopenharmony_ci            })
117b3ba51a1Sopenharmony_ci            .filter(move |p| binary_checker.is_valid(p));
118b3ba51a1Sopenharmony_ci
119b3ba51a1Sopenharmony_ci        Ok(matching_re)
120b3ba51a1Sopenharmony_ci    }
121b3ba51a1Sopenharmony_ci
122b3ba51a1Sopenharmony_ci    fn cwd_search_candidates<C>(binary_name: PathBuf, cwd: C) -> impl IntoIterator<Item = PathBuf>
123b3ba51a1Sopenharmony_ci    where
124b3ba51a1Sopenharmony_ci        C: AsRef<Path>,
125b3ba51a1Sopenharmony_ci    {
126b3ba51a1Sopenharmony_ci        let path = binary_name.to_absolute(cwd);
127b3ba51a1Sopenharmony_ci
128b3ba51a1Sopenharmony_ci        Self::append_extension(iter::once(path))
129b3ba51a1Sopenharmony_ci    }
130b3ba51a1Sopenharmony_ci
131b3ba51a1Sopenharmony_ci    fn path_search_candidates<P>(
132b3ba51a1Sopenharmony_ci        binary_name: PathBuf,
133b3ba51a1Sopenharmony_ci        paths: P,
134b3ba51a1Sopenharmony_ci    ) -> impl IntoIterator<Item = PathBuf>
135b3ba51a1Sopenharmony_ci    where
136b3ba51a1Sopenharmony_ci        P: IntoIterator<Item = PathBuf>,
137b3ba51a1Sopenharmony_ci    {
138b3ba51a1Sopenharmony_ci        let new_paths = paths.into_iter().map(move |p| p.join(binary_name.clone()));
139b3ba51a1Sopenharmony_ci
140b3ba51a1Sopenharmony_ci        Self::append_extension(new_paths)
141b3ba51a1Sopenharmony_ci    }
142b3ba51a1Sopenharmony_ci
143b3ba51a1Sopenharmony_ci    #[cfg(unix)]
144b3ba51a1Sopenharmony_ci    fn append_extension<P>(paths: P) -> impl IntoIterator<Item = PathBuf>
145b3ba51a1Sopenharmony_ci    where
146b3ba51a1Sopenharmony_ci        P: IntoIterator<Item = PathBuf>,
147b3ba51a1Sopenharmony_ci    {
148b3ba51a1Sopenharmony_ci        paths
149b3ba51a1Sopenharmony_ci    }
150b3ba51a1Sopenharmony_ci
151b3ba51a1Sopenharmony_ci    #[cfg(windows)]
152b3ba51a1Sopenharmony_ci    fn append_extension<P>(paths: P) -> impl IntoIterator<Item = PathBuf>
153b3ba51a1Sopenharmony_ci    where
154b3ba51a1Sopenharmony_ci        P: IntoIterator<Item = PathBuf>,
155b3ba51a1Sopenharmony_ci    {
156b3ba51a1Sopenharmony_ci        use once_cell::sync::Lazy;
157b3ba51a1Sopenharmony_ci
158b3ba51a1Sopenharmony_ci        // Sample %PATHEXT%: .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
159b3ba51a1Sopenharmony_ci        // PATH_EXTENSIONS is then [".COM", ".EXE", ".BAT", …].
160b3ba51a1Sopenharmony_ci        // (In one use of PATH_EXTENSIONS we skip the dot, but in the other we need it;
161b3ba51a1Sopenharmony_ci        // hence its retention.)
162b3ba51a1Sopenharmony_ci        static PATH_EXTENSIONS: Lazy<Vec<String>> = Lazy::new(|| {
163b3ba51a1Sopenharmony_ci            env::var("PATHEXT")
164b3ba51a1Sopenharmony_ci                .map(|pathext| {
165b3ba51a1Sopenharmony_ci                    pathext
166b3ba51a1Sopenharmony_ci                        .split(';')
167b3ba51a1Sopenharmony_ci                        .filter_map(|s| {
168b3ba51a1Sopenharmony_ci                            if s.as_bytes().first() == Some(&b'.') {
169b3ba51a1Sopenharmony_ci                                Some(s.to_owned())
170b3ba51a1Sopenharmony_ci                            } else {
171b3ba51a1Sopenharmony_ci                                // Invalid segment; just ignore it.
172b3ba51a1Sopenharmony_ci                                None
173b3ba51a1Sopenharmony_ci                            }
174b3ba51a1Sopenharmony_ci                        })
175b3ba51a1Sopenharmony_ci                        .collect()
176b3ba51a1Sopenharmony_ci                })
177b3ba51a1Sopenharmony_ci                // PATHEXT not being set or not being a proper Unicode string is exceedingly
178b3ba51a1Sopenharmony_ci                // improbable and would probably break Windows badly. Still, don't crash:
179b3ba51a1Sopenharmony_ci                .unwrap_or_default()
180b3ba51a1Sopenharmony_ci        });
181b3ba51a1Sopenharmony_ci
182b3ba51a1Sopenharmony_ci        paths
183b3ba51a1Sopenharmony_ci            .into_iter()
184b3ba51a1Sopenharmony_ci            .flat_map(move |p| -> Box<dyn Iterator<Item = _>> {
185b3ba51a1Sopenharmony_ci                // Check if path already have executable extension
186b3ba51a1Sopenharmony_ci                if has_executable_extension(&p, &PATH_EXTENSIONS) {
187b3ba51a1Sopenharmony_ci                    Box::new(iter::once(p))
188b3ba51a1Sopenharmony_ci                } else {
189b3ba51a1Sopenharmony_ci                    let bare_file = p.extension().map(|_| p.clone());
190b3ba51a1Sopenharmony_ci                    // Appended paths with windows executable extensions.
191b3ba51a1Sopenharmony_ci                    // e.g. path `c:/windows/bin[.ext]` will expand to:
192b3ba51a1Sopenharmony_ci                    // [c:/windows/bin.ext]
193b3ba51a1Sopenharmony_ci                    // c:/windows/bin[.ext].COM
194b3ba51a1Sopenharmony_ci                    // c:/windows/bin[.ext].EXE
195b3ba51a1Sopenharmony_ci                    // c:/windows/bin[.ext].CMD
196b3ba51a1Sopenharmony_ci                    // ...
197b3ba51a1Sopenharmony_ci                    Box::new(
198b3ba51a1Sopenharmony_ci                        bare_file
199b3ba51a1Sopenharmony_ci                            .into_iter()
200b3ba51a1Sopenharmony_ci                            .chain(PATH_EXTENSIONS.iter().map(move |e| {
201b3ba51a1Sopenharmony_ci                                // Append the extension.
202b3ba51a1Sopenharmony_ci                                let mut p = p.clone().into_os_string();
203b3ba51a1Sopenharmony_ci                                p.push(e);
204b3ba51a1Sopenharmony_ci
205b3ba51a1Sopenharmony_ci                                PathBuf::from(p)
206b3ba51a1Sopenharmony_ci                            })),
207b3ba51a1Sopenharmony_ci                    )
208b3ba51a1Sopenharmony_ci                }
209b3ba51a1Sopenharmony_ci            })
210b3ba51a1Sopenharmony_ci    }
211b3ba51a1Sopenharmony_ci}
212b3ba51a1Sopenharmony_ci
213b3ba51a1Sopenharmony_ci#[cfg(target_os = "windows")]
214b3ba51a1Sopenharmony_cifn correct_casing(mut p: PathBuf) -> PathBuf {
215b3ba51a1Sopenharmony_ci    if let (Some(parent), Some(file_name)) = (p.parent(), p.file_name()) {
216b3ba51a1Sopenharmony_ci        if let Ok(iter) = fs::read_dir(parent) {
217b3ba51a1Sopenharmony_ci            for e in iter.filter_map(std::result::Result::ok) {
218b3ba51a1Sopenharmony_ci                if e.file_name().eq_ignore_ascii_case(file_name) {
219b3ba51a1Sopenharmony_ci                    p.pop();
220b3ba51a1Sopenharmony_ci                    p.push(e.file_name());
221b3ba51a1Sopenharmony_ci                    break;
222b3ba51a1Sopenharmony_ci                }
223b3ba51a1Sopenharmony_ci            }
224b3ba51a1Sopenharmony_ci        }
225b3ba51a1Sopenharmony_ci    }
226b3ba51a1Sopenharmony_ci    p
227b3ba51a1Sopenharmony_ci}
228b3ba51a1Sopenharmony_ci
229b3ba51a1Sopenharmony_ci#[cfg(not(target_os = "windows"))]
230b3ba51a1Sopenharmony_cifn correct_casing(p: PathBuf) -> PathBuf {
231b3ba51a1Sopenharmony_ci    p
232b3ba51a1Sopenharmony_ci}
233