diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-05-10 07:23:16 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-05-10 07:23:16 +0000 |
commit | e42091b9afa8b1d9cb0d0418bba7a2b1e23eeca8 (patch) | |
tree | 0ee35a94c440d4b17b59f4adbc625f2cee3fb7f3 | |
parent | e19c8c561565fcf1fec05e94aefa07728f69409a (diff) | |
parent | 6f0fadcda199702a28974afba6e4b75bf6a20b2a (diff) | |
download | which-e42091b9afa8b1d9cb0d0418bba7a2b1e23eeca8.tar.gz |
Snap for 8564071 from 6f0fadcda199702a28974afba6e4b75bf6a20b2a to mainline-os-statsd-releaseaml_sta_331910000aml_sta_331811000aml_sta_331711010aml_sta_331610000aml_sta_331511000aml_sta_331410000aml_sta_331311000aml_sta_331010010aml_sta_330910000android13-mainline-os-statsd-release
Change-Id: Ic5119e86eef4af1a83cc4a5cf482ac2e7b915bf7
-rw-r--r-- | .cargo_vcs_info.json | 7 | ||||
-rw-r--r-- | .github/workflows/rust.yml | 97 | ||||
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Android.bp | 8 | ||||
-rw-r--r-- | Cargo.toml | 19 | ||||
-rw-r--r-- | Cargo.toml.orig | 9 | ||||
-rw-r--r-- | METADATA | 10 | ||||
-rw-r--r-- | README.md | 23 | ||||
-rw-r--r-- | TEST_MAPPING | 14 | ||||
-rw-r--r-- | cargo2android.json | 5 | ||||
-rw-r--r-- | src/finder.rs | 104 | ||||
-rw-r--r-- | src/helper.rs | 10 | ||||
-rw-r--r-- | src/lib.rs | 105 | ||||
-rw-r--r-- | tests/basic.rs | 48 |
14 files changed, 376 insertions, 84 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json index fb1169d..692ca11 100644 --- a/.cargo_vcs_info.json +++ b/.cargo_vcs_info.json @@ -1,5 +1,6 @@ { "git": { - "sha1": "2c54067bab846e96f07be60e78250f385825f09f" - } -} + "sha1": "8fbe34239c16af9cd253e36e9c2d3384f9b55f83" + }, + "path_in_vcs": "" +}
\ No newline at end of file diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 288ccaf..dd380d3 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,28 +1,93 @@ -name: Rust - +name: Main workflow on: push: - branches: [ master ] pull_request: - branches: [ master ] - -env: - CARGO_TERM_COLOR: always jobs: + # Run the `rustfmt` code formatter + rustfmt: + name: Rustfmt [Formatter] + runs-on: ubuntu-latest + steps: + - name: Setup | Checkout + uses: actions/checkout@v2 + + - name: Setup | Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + profile: minimal + components: rustfmt + + - name: Build | Format + run: cargo fmt --all -- --check + + # Run the `clippy` linting tool + clippy: + name: Clippy [Linter] + strategy: + matrix: + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Setup | Checkout + uses: actions/checkout@v2 + - name: Setup | Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + profile: minimal + components: clippy + + - name: Build | Lint + uses: actions-rs/cargo@v1 + with: + command: clippy + args: --workspace --all-targets --all-features + + # Ensure that the project could be successfully compiled + cargo_check: + name: Compile + runs-on: ubuntu-latest + steps: + - name: Setup | Checkout + uses: actions/checkout@v2 + + - name: Setup | Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + override: true + + - name: Build | Check + run: cargo check --workspace + + # Run tests on Linux, macOS, and Windows + # On both Rust stable and Rust nightly test: - name: Build and test + name: Test Suite runs-on: ${{ matrix.os }} + needs: cargo_check # First check then run expansive tests strategy: fail-fast: false matrix: - os: - - ubuntu-latest - - windows-latest - - macos-latest - + os: [ubuntu-latest, windows-latest] + rust: [stable, nightly] steps: - - uses: actions/checkout@v2 - - run: cargo build --verbose - - run: cargo test --verbose + - name: Setup | Checkout + uses: actions/checkout@v2 + + - name: Setup | Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + profile: minimal + override: true + + # Run the ignored tests that expect the above setup + - name: Build | Test + run: cargo test --workspace --all-features -- -Z unstable-options --include-ignored @@ -1,2 +1,3 @@ target Cargo.lock +.vscode/ @@ -1,4 +1,4 @@ -// This file is generated by cargo2android.py --run --dependencies --device --features=. +// This file is generated by cargo2android.py --config cargo2android.json. // Do not modify this file as changes will be overridden on upgrade. package { @@ -22,6 +22,8 @@ rust_library { name: "libwhich", host_supported: true, crate_name: "which", + cargo_env_compat: true, + cargo_pkg_version: "4.2.4", srcs: ["src/lib.rs"], edition: "2018", rustlibs: [ @@ -29,7 +31,3 @@ rust_library { "liblibc", ], } - -// dependent_library ["feature_list"] -// either-1.6.1 "default,use_std" -// libc-0.2.92 "default,std" @@ -3,17 +3,16 @@ # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies -# to registry (e.g., crates.io) dependencies +# to registry (e.g., crates.io) dependencies. # -# If you believe there's an error in this file please file an -# issue against the rust-lang/cargo repository. If you're -# editing this file be aware that the upstream Cargo.toml -# will likely look very different (and much more reasonable) +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "which" -version = "4.1.0" +version = "4.2.4" authors = ["Harry Fei <tiziyuanfang@gmail.com>"] description = "A Rust equivalent of Unix command \"which\". Locate installed executable in cross platforms." documentation = "https://docs.rs/which/" @@ -22,10 +21,18 @@ keywords = ["which", "which-rs", "unix", "command"] categories = ["os", "filesystem"] license = "MIT" repository = "https://github.com/harryfei/which-rs.git" +[package.metadata.docs.rs] +all-features = true [dependencies.either] version = "1.6" [dependencies.libc] version = "0.2.65" + +[dependencies.regex] +version = "1.5.4" +optional = true [dev-dependencies.tempdir] version = "0.3.7" +[target."cfg(windows)".dependencies.lazy_static] +version = "1" diff --git a/Cargo.toml.orig b/Cargo.toml.orig index c3c472e..ec31c55 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -1,6 +1,6 @@ [package] name = "which" -version = "4.1.0" +version = "4.2.4" edition = "2018" authors = ["Harry Fei <tiziyuanfang@gmail.com>"] repository = "https://github.com/harryfei/which-rs.git" @@ -14,6 +14,13 @@ keywords = ["which", "which-rs", "unix", "command"] [dependencies] either = "1.6" libc = "0.2.65" +regex = { version = "1.5.4", optional = true } + +[target.'cfg(windows)'.dependencies] +lazy_static = "1" [dev-dependencies] tempdir = "0.3.7" + +[package.metadata.docs.rs] +all-features = true @@ -7,13 +7,13 @@ third_party { } url { type: ARCHIVE - value: "https://static.crates.io/crates/which/which-4.1.0.crate" + value: "https://static.crates.io/crates/which/which-4.2.4.crate" } - version: "4.1.0" + version: "4.2.4" license_type: NOTICE last_upgrade_date { - year: 2021 - month: 4 - day: 2 + year: 2022 + month: 3 + day: 1 } } @@ -10,16 +10,25 @@ A Rust equivalent of Unix command "which". Locate installed executable in cross * Windows * macOS -## Example +## Examples -To find which rustc exectable binary is using. +1) To find which rustc executable binary is using. -``` rust -use which::which; + ``` rust + use which::which; -let result = which::which("rustc").unwrap(); -assert_eq!(result, PathBuf::from("/usr/bin/rustc")); -``` + let result = which("rustc").unwrap(); + assert_eq!(result, PathBuf::from("/usr/bin/rustc")); + ``` + +2. After enabling the `regex` feature, find all cargo subcommand executables on the path: + + ``` rust + use which::which_re; + + which_re(Regex::new("^cargo-.*").unwrap()).unwrap() + .for_each(|pth| println!("{}", pth.to_string_lossy())); + ``` ## Documentation diff --git a/TEST_MAPPING b/TEST_MAPPING index df48c8d..e4ec3b3 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -1,14 +1,24 @@ // Generated by update_crate_tests.py for tests that depend on this crate. { + "imports": [ + { + "path": "external/rust/crates/libsqlite3-sys" + } + ], "presubmit": [ { - "name": "libsqlite3-sys_device_test_src_lib" + "name": "keystore2_test" }, { + "name": "legacykeystore_test" + } + ], + "presubmit-rust": [ + { "name": "keystore2_test" }, { - "name": "vpnprofilestore_test" + "name": "legacykeystore_test" } ] } diff --git a/cargo2android.json b/cargo2android.json new file mode 100644 index 0000000..5fd6c55 --- /dev/null +++ b/cargo2android.json @@ -0,0 +1,5 @@ +{ + "device": true, + "features": "", + "run": true +}
\ No newline at end of file diff --git a/src/finder.rs b/src/finder.rs index 91c6cab..43b659d 100644 --- a/src/finder.rs +++ b/src/finder.rs @@ -1,12 +1,16 @@ use crate::checker::CompositeChecker; use crate::error::*; -use either::Either; #[cfg(windows)] use crate::helper::has_executable_extension; +use either::Either; +#[cfg(feature = "regex")] +use regex::Regex; +#[cfg(feature = "regex")] +use std::borrow::Borrow; use std::env; use std::ffi::OsStr; -#[cfg(windows)] -use std::ffi::OsString; +#[cfg(feature = "regex")] +use std::fs; use std::iter; use std::path::{Path, PathBuf}; @@ -52,7 +56,7 @@ impl Finder { &self, binary_name: T, paths: Option<U>, - cwd: V, + cwd: Option<V>, binary_checker: CompositeChecker, ) -> Result<impl Iterator<Item = PathBuf>> where @@ -62,20 +66,54 @@ impl Finder { { let path = PathBuf::from(&binary_name); - let binary_path_candidates = if path.has_separator() { - // Search binary in cwd if the path have a path separator. - Either::Left(Self::cwd_search_candidates(path, cwd).into_iter()) - } else { - // Search binary in PATHs(defined in environment variable). - let p = paths.ok_or(Error::CannotFindBinaryPath)?; - let paths: Vec<_> = env::split_paths(&p).collect(); - - Either::Right(Self::path_search_candidates(path, paths).into_iter()) + let binary_path_candidates = match cwd { + Some(cwd) if path.has_separator() => { + // Search binary in cwd if the path have a path separator. + Either::Left(Self::cwd_search_candidates(path, cwd).into_iter()) + } + _ => { + // Search binary in PATHs(defined in environment variable). + let p = paths.ok_or(Error::CannotFindBinaryPath)?; + let paths: Vec<_> = env::split_paths(&p).collect(); + + Either::Right(Self::path_search_candidates(path, paths).into_iter()) + } }; Ok(binary_path_candidates.filter(move |p| binary_checker.is_valid(p))) } + #[cfg(feature = "regex")] + pub fn find_re<T>( + &self, + binary_regex: impl Borrow<Regex>, + paths: Option<T>, + binary_checker: CompositeChecker, + ) -> Result<impl Iterator<Item = PathBuf>> + where + T: AsRef<OsStr>, + { + let p = paths.ok_or(Error::CannotFindBinaryPath)?; + let paths: Vec<_> = env::split_paths(&p).collect(); + + let matching_re = paths + .into_iter() + .flat_map(fs::read_dir) + .flatten() + .flatten() + .map(|e| e.path()) + .filter(move |p| { + if let Some(unicode_file_name) = p.file_name().unwrap().to_str() { + binary_regex.borrow().is_match(unicode_file_name) + } else { + false + } + }) + .filter(move |p| binary_checker.is_valid(p)); + + Ok(matching_re) + } + fn cwd_search_candidates<C>(binary_name: PathBuf, cwd: C) -> impl IntoIterator<Item = PathBuf> where C: AsRef<Path>, @@ -110,19 +148,35 @@ impl Finder { where P: IntoIterator<Item = PathBuf>, { - // Read PATHEXT env variable and split it into vector of String - let path_exts = - env::var_os("PATHEXT").unwrap_or(OsString::from(env::consts::EXE_EXTENSION)); - - let exe_extension_vec = env::split_paths(&path_exts) - .filter_map(|e| e.to_str().map(|e| e.to_owned())) - .collect::<Vec<_>>(); + // Sample %PATHEXT%: .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC + // PATH_EXTENSIONS is then [".COM", ".EXE", ".BAT", …]. + // (In one use of PATH_EXTENSIONS we skip the dot, but in the other we need it; + // hence its retention.) + lazy_static! { + static ref PATH_EXTENSIONS: Vec<String> = + env::var("PATHEXT") + .map(|pathext| { + pathext.split(';') + .filter_map(|s| { + if s.as_bytes().first() == Some(&b'.') { + Some(s.to_owned()) + } else { + // Invalid segment; just ignore it. + None + } + }) + .collect() + }) + // PATHEXT not being set or not being a proper Unicode string is exceedingly + // improbable and would probably break Windows badly. Still, don't crash: + .unwrap_or(vec![]); + } paths .into_iter() .flat_map(move |p| -> Box<dyn Iterator<Item = _>> { // Check if path already have executable extension - if has_executable_extension(&p, &exe_extension_vec) { + if has_executable_extension(&p, &PATH_EXTENSIONS) { Box::new(iter::once(p)) } else { // Appended paths with windows executable extensions. @@ -131,15 +185,13 @@ impl Finder { // c:/windows/bin.EXE // c:/windows/bin.CMD // ... - let ps = exe_extension_vec.clone().into_iter().map(move |e| { + Box::new(PATH_EXTENSIONS.iter().map(move |e| { // Append the extension. - let mut p = p.clone().to_path_buf().into_os_string(); + let mut p = p.clone().into_os_string(); p.push(e); PathBuf::from(p) - }); - - Box::new(ps) + })) } }) } diff --git a/src/helper.rs b/src/helper.rs index 71658a0..eb96891 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -1,10 +1,10 @@ use std::path::Path; /// Check if given path has extension which in the given vector. -pub fn has_executable_extension<T: AsRef<Path>, S: AsRef<str>>(path: T, exts_vec: &Vec<S>) -> bool { +pub fn has_executable_extension<T: AsRef<Path>, S: AsRef<str>>(path: T, pathext: &[S]) -> bool { let ext = path.as_ref().extension().and_then(|e| e.to_str()); match ext { - Some(ext) => exts_vec + Some(ext) => pathext .iter() .any(|e| ext.eq_ignore_ascii_case(&e.as_ref()[1..])), _ => false, @@ -21,12 +21,12 @@ mod test { // Case insensitive assert!(has_executable_extension( PathBuf::from("foo.exe"), - &vec![".COM", ".EXE", ".CMD"] + &[".COM", ".EXE", ".CMD"] )); assert!(has_executable_extension( PathBuf::from("foo.CMD"), - &vec![".COM", ".EXE", ".CMD"] + &[".COM", ".EXE", ".CMD"] )); } @@ -34,7 +34,7 @@ mod test { fn test_extension_not_in_extension_vector() { assert!(!has_executable_extension( PathBuf::from("foo.bar"), - &vec![".COM", ".EXE", ".CMD"] + &[".COM", ".EXE", ".CMD"] )); } } @@ -9,17 +9,25 @@ //! use which::which; //! use std::path::PathBuf; //! -//! let result = which::which("rustc").unwrap(); +//! let result = which("rustc").unwrap(); //! assert_eq!(result, PathBuf::from("/usr/bin/rustc")); //! //! ``` +#[cfg(windows)] +#[macro_use] +extern crate lazy_static; + mod checker; mod error; mod finder; #[cfg(windows)] mod helper; +#[cfg(feature = "regex")] +use regex::Regex; +#[cfg(feature = "regex")] +use std::borrow::Borrow; use std::env; use std::fmt; use std::path; @@ -57,9 +65,50 @@ pub fn which<T: AsRef<OsStr>>(binary_name: T) -> Result<path::PathBuf> { /// Find all binaries with `binary_name` in the path list `paths`, using `cwd` to resolve relative paths. pub fn which_all<T: AsRef<OsStr>>(binary_name: T) -> Result<impl Iterator<Item = path::PathBuf>> { - let cwd = env::current_dir().map_err(|_| Error::CannotGetCurrentDir)?; + let cwd = env::current_dir().ok(); + + let binary_checker = build_binary_checker(); + + let finder = Finder::new(); + + finder.find(binary_name, env::var_os("PATH"), cwd, binary_checker) +} - which_in_all(binary_name, env::var_os("PATH"), cwd) +/// Find all binaries matching a regular expression in a the system PATH. +/// +/// Only available when feature `regex` is enabled. +/// +/// # Arguments +/// +/// * `regex` - A regular expression to match binaries with +/// +/// # Examples +/// +/// Find Python executables: +/// +/// ```no_run +/// use regex::Regex; +/// use which::which; +/// use std::path::PathBuf; +/// +/// let re = Regex::new(r"python\d$").unwrap(); +/// let binaries: Vec<PathBuf> = which::which_re(re).unwrap().collect(); +/// let python_paths = vec![PathBuf::from("/usr/bin/python2"), PathBuf::from("/usr/bin/python3")]; +/// assert_eq!(binaries, python_paths); +/// ``` +/// +/// Find all cargo subcommand executables on the path: +/// +/// ``` +/// use which::which_re; +/// use regex::Regex; +/// +/// which_re(Regex::new("^cargo-.*").unwrap()).unwrap() +/// .for_each(|pth| println!("{}", pth.to_string_lossy())); +/// ``` +#[cfg(feature = "regex")] +pub fn which_re(regex: impl Borrow<Regex>) -> Result<impl Iterator<Item = path::PathBuf>> { + which_re_in(regex, env::var_os("PATH")) } /// Find `binary_name` in the path list `paths`, using `cwd` to resolve relative paths. @@ -73,6 +122,44 @@ where .and_then(|mut i| i.next().ok_or(Error::CannotFindBinaryPath)) } +/// Find all binaries matching a regular expression in a list of paths. +/// +/// Only available when feature `regex` is enabled. +/// +/// # Arguments +/// +/// * `regex` - A regular expression to match binaries with +/// * `paths` - A string containing the paths to search +/// (separated in the same way as the PATH environment variable) +/// +/// # Examples +/// +/// ```no_run +/// use regex::Regex; +/// use which::which; +/// use std::path::PathBuf; +/// +/// let re = Regex::new(r"python\d$").unwrap(); +/// let paths = Some("/usr/bin:/usr/local/bin"); +/// let binaries: Vec<PathBuf> = which::which_re_in(re, paths).unwrap().collect(); +/// let python_paths = vec![PathBuf::from("/usr/bin/python2"), PathBuf::from("/usr/bin/python3")]; +/// assert_eq!(binaries, python_paths); +/// ``` +#[cfg(feature = "regex")] +pub fn which_re_in<T>( + regex: impl Borrow<Regex>, + paths: Option<T>, +) -> Result<impl Iterator<Item = path::PathBuf>> +where + T: AsRef<OsStr>, +{ + let binary_checker = build_binary_checker(); + + let finder = Finder::new(); + + finder.find_re(regex, paths, binary_checker) +} + /// Find all binaries with `binary_name` in the path list `paths`, using `cwd` to resolve relative paths. pub fn which_in_all<T, U, V>( binary_name: T, @@ -84,13 +171,17 @@ where U: AsRef<OsStr>, V: AsRef<path::Path>, { - let binary_checker = CompositeChecker::new() - .add_checker(Box::new(ExistedChecker::new())) - .add_checker(Box::new(ExecutableChecker::new())); + let binary_checker = build_binary_checker(); let finder = Finder::new(); - finder.find(binary_name, paths, cwd, binary_checker) + finder.find(binary_name, paths, Some(cwd), binary_checker) +} + +fn build_binary_checker() -> CompositeChecker { + CompositeChecker::new() + .add_checker(Box::new(ExistedChecker::new())) + .add_checker(Box::new(ExecutableChecker::new())) } /// An owned, immutable wrapper around a `PathBuf` containing the path of an executable. diff --git a/tests/basic.rs b/tests/basic.rs index 7cb7a08..897e912 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -1,11 +1,13 @@ extern crate tempdir; extern crate which; -use std::env; +#[cfg(feature = "regex")] +use regex::Regex; use std::ffi::{OsStr, OsString}; use std::fs; use std::io; use std::path::{Path, PathBuf}; +use std::{env, vec}; use tempdir::TempDir; struct TestFixture { @@ -126,6 +128,50 @@ fn test_which() { } #[test] +#[cfg(all(unix, feature = "regex"))] +fn test_which_re_in_with_matches() { + let f = TestFixture::new(); + f.mk_bin("a/bin_0", "").unwrap(); + f.mk_bin("b/bin_1", "").unwrap(); + let re = Regex::new(r"bin_\d").unwrap(); + + let result: Vec<PathBuf> = which::which_re_in(re, Some(f.paths)) + .unwrap() + .into_iter() + .collect(); + + let temp = f.tempdir; + + assert_eq!( + result, + vec![temp.path().join("a/bin_0"), temp.path().join("b/bin_1")] + ) +} + +#[test] +#[cfg(all(unix, feature = "regex"))] +fn test_which_re_in_without_matches() { + let f = TestFixture::new(); + let re = Regex::new(r"bi[^n]").unwrap(); + + let result: Vec<PathBuf> = which::which_re_in(re, Some(f.paths)) + .unwrap() + .into_iter() + .collect(); + + assert_eq!(result, Vec::<PathBuf>::new()) +} + +#[test] +#[cfg(all(unix, feature = "regex"))] +fn test_which_re_accepts_owned_and_borrow() { + which::which_re(Regex::new(r".").unwrap()); + which::which_re(&Regex::new(r".").unwrap()); + which::which_re_in(Regex::new(r".").unwrap(), Some("pth")); + which::which_re_in(&Regex::new(r".").unwrap(), Some("pth")); +} + +#[test] #[cfg(unix)] fn test_which_extension() { let f = TestFixture::new(); |