Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Sys trait for swapping out system #109

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
name: Compile
strategy:
matrix:
target: [x86_64-unknown-linux-musl, wasm32-wasi]
target: [x86_64-unknown-linux-musl, wasm32-wasi, wasm32-unknown-unknown]
runs-on: ubuntu-latest
steps:
- name: Setup | Checkout
Expand All @@ -61,7 +61,18 @@ jobs:
target: ${{ matrix.target }}

- name: Build | Check
run: cargo check --workspace --target ${{ matrix.target }}
if: ${{ matrix.target != 'wasm32-unknown-unknown' }}
run: |
cargo check --workspace --target ${{ matrix.target }}
cargo check --workspace --target ${{ matrix.target }} --features regex
cargo check --workspace --target ${{ matrix.target }} --no-default-features
cargo check --workspace --target ${{ matrix.target }} --no-default-features --features regex

- name: Check wasm32-unknown-unknown
if: ${{ matrix.target == 'wasm32-unknown-unknown' }}
run: |
cargo check --workspace --target ${{ matrix.target }} --no-default-features
cargo check --workspace --target ${{ matrix.target }} --no-default-features --features regex

# Run tests on Linux, macOS, and Windows
# On both Rust stable and Rust nightly
Expand Down
8 changes: 5 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,24 @@ categories = ["os", "filesystem"]
keywords = ["which", "which-rs", "unix", "command"]

[features]
default = ["real-sys"]
regex = ["dep:regex"]
tracing = ["dep:tracing"]
real-sys = ["env_home", "rustix", "winsafe"]

[dependencies]
either = "1.9.0"
regex = { version = "1.10.2", optional = true }
tracing = { version = "0.1.40", default-features = false, optional = true }

[target.'cfg(any(windows, unix, target_os = "redox"))'.dependencies]
env_home = "0.1.0"
env_home = { version = "0.1.0", optional = true }

[target.'cfg(any(unix, target_os = "wasi", target_os = "redox"))'.dependencies]
rustix = { version = "0.38.30", default-features = false, features = ["fs", "std"] }
rustix = { version = "0.38.30", default-features = false, features = ["fs", "std"], optional = true }

[target.'cfg(windows)'.dependencies]
winsafe = { version = "0.0.19", features = ["kernel"] }
winsafe = { version = "0.0.19", features = ["kernel"], optional = true }

[dev-dependencies]
tempfile = "3.9.0"
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@ A Rust equivalent of Unix command "which". Locate installed executable in cross

### A note on WebAssembly

This project aims to support WebAssembly with the [wasi](https://wasi.dev/) extension. This extension is a requirement. `which` is a library for exploring a filesystem, and
WebAssembly without wasi does not have a filesystem. `which` cannot do anything useful without this extension. Issues and PRs relating to
`wasm32-unknown-unknown` and `wasm64-unknown-unknown` will not be resolved or merged. All `wasm32-wasi*` targets are officially supported.
This project aims to support WebAssembly with the [WASI](https://wasi.dev/) extension. All `wasm32-wasi*` targets are officially supported.

If you need to add a conditional dependency on `which` for this reason please refer to [the relevant cargo documentation for platform specific dependencies.](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#platform-specific-dependencies)
If you need to add a conditional dependency on `which` please refer to [the relevant cargo documentation for platform specific dependencies.](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#platform-specific-dependencies)

Here's an example of how to conditionally add `which`. You should tweak this to your needs.

Expand All @@ -26,6 +24,8 @@ Here's an example of how to conditionally add `which`. You should tweak this to
which = "7.0.0"
```

Note that non-WASI environments have no access to the system. Using this in that situation requires disabling the default features of this crate and providing a custom `which::sys::Sys` implementation to `which::WhichConfig`.

## Examples

1) To find which rustc executable binary is using.
Expand Down
48 changes: 48 additions & 0 deletions clippy.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
disallowed-methods = [
{ path = "std::env::current_dir", reason = "System operations should be done using Sys trait" },
{ path = "std::path::Path::canonicalize", reason = "System operations should be done using Sys trait" },
{ path = "std::path::Path::is_dir", reason = "System operations should be done using Sys trait" },
{ path = "std::path::Path::is_file", reason = "System operations should be done using Sys trait" },
{ path = "std::path::Path::is_symlink", reason = "System operations should be done using Sys trait" },
{ path = "std::path::Path::metadata", reason = "System operations should be done using Sys trait" },
{ path = "std::path::Path::read_dir", reason = "System operations should be done using Sys trait" },
{ path = "std::path::Path::read_link", reason = "System operations should be done using Sys trait" },
{ path = "std::path::Path::symlink_metadata", reason = "System operations should be done using Sys trait" },
{ path = "std::path::Path::try_exists", reason = "System operations should be done using Sys trait" },
{ path = "std::path::PathBuf::exists", reason = "System operations should be done using Sys trait" },
{ path = "std::path::PathBuf::canonicalize", reason = "System operations should be done using Sys trait" },
{ path = "std::path::PathBuf::is_dir", reason = "System operations should be done using Sys trait" },
{ path = "std::path::PathBuf::is_file", reason = "System operations should be done using Sys trait" },
{ path = "std::path::PathBuf::is_symlink", reason = "System operations should be done using Sys trait" },
{ path = "std::path::PathBuf::metadata", reason = "System operations should be done using Sys trait" },
{ path = "std::path::PathBuf::read_dir", reason = "System operations should be done using Sys trait" },
{ path = "std::path::PathBuf::read_link", reason = "System operations should be done using Sys trait" },
{ path = "std::path::PathBuf::symlink_metadata", reason = "System operations should be done using Sys trait" },
{ path = "std::path::PathBuf::try_exists", reason = "System operations should be done using Sys trait" },
{ path = "std::env::set_current_dir", reason = "System operations should be done using Sys trait" },
{ path = "std::env::split_paths", reason = "System operations should be done using Sys trait" },
{ path = "std::env::temp_dir", reason = "System operations should be done using Sys trait" },
{ path = "std::env::var", reason = "System operations should be done using Sys trait" },
{ path = "std::env::var_os", reason = "System operations should be done using Sys trait" },
{ path = "std::fs::canonicalize", reason = "System operations should be done using Sys trait" },
{ path = "std::fs::copy", reason = "System operations should be done using Sys trait" },
{ path = "std::fs::create_dir_all", reason = "System operations should be done using Sys trait" },
{ path = "std::fs::create_dir", reason = "System operations should be done using Sys trait" },
{ path = "std::fs::DirBuilder::new", reason = "System operations should be done using Sys trait" },
{ path = "std::fs::hard_link", reason = "System operations should be done using Sys trait" },
{ path = "std::fs::metadata", reason = "System operations should be done using Sys trait" },
{ path = "std::fs::OpenOptions::new", reason = "System operations should be done using Sys trait" },
{ path = "std::fs::read_dir", reason = "System operations should be done using Sys trait" },
{ path = "std::fs::read_link", reason = "System operations should be done using Sys trait" },
{ path = "std::fs::read_to_string", reason = "System operations should be done using Sys trait" },
{ path = "std::fs::read", reason = "System operations should be done using Sys trait" },
{ path = "std::fs::remove_dir_all", reason = "System operations should be done using Sys trait" },
{ path = "std::fs::remove_dir", reason = "System operations should be done using Sys trait" },
{ path = "std::fs::remove_file", reason = "System operations should be done using Sys trait" },
{ path = "std::fs::rename", reason = "System operations should be done using Sys trait" },
{ path = "std::fs::set_permissions", reason = "System operations should be done using Sys trait" },
{ path = "std::fs::symlink_metadata", reason = "System operations should be done using Sys trait" },
{ path = "std::fs::write", reason = "System operations should be done using Sys trait" },
{ path = "std::path::Path::canonicalize", reason = "System operations should be done using Sys trait" },
{ path = "std::path::Path::exists", reason = "System operations should be done using Sys trait" },
]
170 changes: 71 additions & 99 deletions src/checker.rs
Original file line number Diff line number Diff line change
@@ -1,139 +1,111 @@
use crate::finder::Checker;
use crate::sys::Sys;
use crate::sys::SysMetadata;
use crate::{NonFatalError, NonFatalErrorHandler};
use std::fs;
use std::path::Path;

pub struct ExecutableChecker;
pub struct ExecutableChecker<TSys: Sys> {
sys: TSys,
}

impl ExecutableChecker {
pub fn new() -> ExecutableChecker {
ExecutableChecker
impl<TSys: Sys> ExecutableChecker<TSys> {
pub fn new(sys: TSys) -> Self {
Self { sys }
}
}

impl Checker for ExecutableChecker {
#[cfg(any(unix, target_os = "wasi", target_os = "redox"))]
impl<TSys: Sys> Checker for ExecutableChecker<TSys> {
fn is_valid<F: NonFatalErrorHandler>(
&self,
path: &Path,
nonfatal_error_handler: &mut F,
) -> bool {
use std::io;

use rustix::fs as rfs;
let ret = rfs::access(path, rfs::Access::EXEC_OK)
.map_err(|e| {
nonfatal_error_handler.handle(NonFatalError::Io(io::Error::from_raw_os_error(
e.raw_os_error(),
)))
})
.is_ok();
#[cfg(feature = "tracing")]
tracing::trace!("{} EXEC_OK = {ret}", path.display());
ret
}

#[cfg(windows)]
fn is_valid<F: NonFatalErrorHandler>(
&self,
_path: &Path,
_nonfatal_error_handler: &mut F,
) -> bool {
true
if self.sys.is_windows() && path.extension().is_some() {
true
} else {
let ret = self
.sys
.is_valid_executable(path)
.map_err(|e| nonfatal_error_handler.handle(NonFatalError::Io(e)))
.unwrap_or(false);
#[cfg(feature = "tracing")]
tracing::trace!("{} EXEC_OK = {ret}", path.display());
ret
}
}
}

pub struct ExistedChecker;

impl ExistedChecker {
pub fn new() -> ExistedChecker {
ExistedChecker
}
pub struct ExistedChecker<TSys: Sys> {
sys: TSys,
}

impl Checker for ExistedChecker {
#[cfg(target_os = "windows")]
fn is_valid<F: NonFatalErrorHandler>(
&self,
path: &Path,
nonfatal_error_handler: &mut F,
) -> bool {
let ret = fs::symlink_metadata(path)
.map(|metadata| {
let file_type = metadata.file_type();
#[cfg(feature = "tracing")]
tracing::trace!(
"{} is_file() = {}, is_symlink() = {}",
path.display(),
file_type.is_file(),
file_type.is_symlink()
);
file_type.is_file() || file_type.is_symlink()
})
.map_err(|e| {
nonfatal_error_handler.handle(NonFatalError::Io(e));
})
.unwrap_or(false)
&& (path.extension().is_some() || matches_arch(path, nonfatal_error_handler));
#[cfg(feature = "tracing")]
tracing::trace!(
"{} has_extension = {}, ExistedChecker::is_valid() = {ret}",
path.display(),
path.extension().is_some()
);
ret
impl<TSys: Sys> ExistedChecker<TSys> {
pub fn new(sys: TSys) -> Self {
Self { sys }
}
}

#[cfg(not(target_os = "windows"))]
impl<TSys: Sys> Checker for ExistedChecker<TSys> {
fn is_valid<F: NonFatalErrorHandler>(
&self,
path: &Path,
nonfatal_error_handler: &mut F,
) -> bool {
let ret = fs::metadata(path).map(|metadata| metadata.is_file());
#[cfg(feature = "tracing")]
tracing::trace!("{} is_file() = {ret:?}", path.display());
match ret {
Ok(ret) => ret,
Err(e) => {
nonfatal_error_handler.handle(NonFatalError::Io(e));
false
if self.sys.is_windows() {
let ret = self
.sys
.symlink_metadata(path)
.map(|metadata| {
#[cfg(feature = "tracing")]
tracing::trace!(
"{} is_file() = {}, is_symlink() = {}",
path.display(),
metadata.is_file(),
metadata.is_symlink()
);
metadata.is_file() || metadata.is_symlink()
})
.map_err(|e| {
nonfatal_error_handler.handle(NonFatalError::Io(e));
})
.unwrap_or(false);
#[cfg(feature = "tracing")]
tracing::trace!(
"{} has_extension = {}, ExistedChecker::is_valid() = {ret}",
path.display(),
path.extension().is_some()
);
ret
} else {
let ret = self.sys.metadata(path).map(|metadata| metadata.is_file());
#[cfg(feature = "tracing")]
tracing::trace!("{} is_file() = {ret:?}", path.display());
match ret {
Ok(ret) => ret,
Err(e) => {
nonfatal_error_handler.handle(NonFatalError::Io(e));
false
}
}
}
}
}

#[cfg(target_os = "windows")]
fn matches_arch<F: NonFatalErrorHandler>(path: &Path, nonfatal_error_handler: &mut F) -> bool {
use std::io;

let ret = winsafe::GetBinaryType(&path.display().to_string())
.map_err(|e| {
nonfatal_error_handler.handle(NonFatalError::Io(io::Error::from_raw_os_error(
e.raw() as i32
)))
})
.is_ok();
#[cfg(feature = "tracing")]
tracing::trace!("{} matches_arch() = {ret}", path.display());
ret
}

pub struct CompositeChecker {
existed_checker: ExistedChecker,
executable_checker: ExecutableChecker,
pub struct CompositeChecker<TSys: Sys> {
existed_checker: ExistedChecker<TSys>,
executable_checker: ExecutableChecker<TSys>,
}

impl CompositeChecker {
pub fn new() -> CompositeChecker {
impl<TSys: Sys> CompositeChecker<TSys> {
pub fn new(sys: TSys) -> Self {
CompositeChecker {
executable_checker: ExecutableChecker::new(),
existed_checker: ExistedChecker::new(),
executable_checker: ExecutableChecker::new(sys.clone()),
existed_checker: ExistedChecker::new(sys),
}
}
}

impl Checker for CompositeChecker {
impl<TSys: Sys> Checker for CompositeChecker<TSys> {
fn is_valid<F: NonFatalErrorHandler>(
&self,
path: &Path,
Expand Down
Loading
Loading