diff --git a/.gitlab-ci/check.yml b/.gitlab-ci/check.yml index 4c44e416..6a23615f 100644 --- a/.gitlab-ci/check.yml +++ b/.gitlab-ci/check.yml @@ -8,7 +8,7 @@ pre-commit-checks: image: "rustlang/rust:nightly-slim" interruptible: true script: - - RUST_BACKTRACE=1 cargo run --manifest-path hooks/checks/Cargo.toml -- --verbose --force-install + - RUST_BACKTRACE=1 cargo run --manifest-path hooks/checks/Cargo.toml -- --verbose --force-install --cargo-binstall # Checks needing dependencies in the Flatpak runtime flatpak-checks: diff --git a/hooks/checks/src/main.rs b/hooks/checks/src/main.rs index d3fb7caf..93da17d7 100644 --- a/hooks/checks/src/main.rs +++ b/hooks/checks/src/main.rs @@ -10,8 +10,8 @@ use std::{ mod utils; use crate::utils::{ - CargoManifest, CheckDependency, CommandData, GitStagedFiles, InstallationCommand, - check_files_sorted, file_contains, load_files, print_error, visit_dir, + CargoInstallMethod, CargoManifest, CheckDependency, CommandData, GitStagedFiles, + InstallationCommand, check_files_sorted, file_contains, load_files, print_error, visit_dir, }; /// The path to the directory containing the workspace. @@ -71,6 +71,9 @@ impl ScriptCommand { "force-install" => { check_cmd.force_install = true; } + "cargo-binstall" => { + check_cmd.cargo_install_method = CargoInstallMethod::CargoBinstall; + } "verbose" => { VERBOSE.store(true, Ordering::Relaxed); } @@ -153,6 +156,8 @@ USAGE: {name} [OPTIONS] OPTIONS: -s, --git-staged Only check files staged to be committed -f, --force-install Install missing dependencies without asking + --cargo-binstall Use cargo-binstall instead of `cargo install` when installing + missing crate dependencies -v, --verbose Use verbose output --version Print the version of this script -h, --help Print this help and exit @@ -185,6 +190,8 @@ struct CheckCmd { staged_files: Option, /// Whether to install missing dependencies without asking. force_install: bool, + /// Which program to use to install crate dependencies. + cargo_install_method: CargoInstallMethod, } impl CheckCmd { @@ -216,7 +223,7 @@ impl CheckCmd { &["component", "add", "--toolchain", "nightly", "rustfmt"], )), } - .check(self.force_install)?; + .check(self.force_install, self.cargo_install_method)?; if let Some(staged_files) = &self.staged_files { let cmd = CommandData::new( @@ -267,7 +274,7 @@ impl CheckCmd { &["component", "add", "clippy"], )), } - .check(self.force_install)?; + .check(self.force_install, self.cargo_install_method)?; let manifest_path = format!("{WORKSPACE_DIR}/hooks/checks/Cargo.toml"); @@ -295,7 +302,7 @@ impl CheckCmd { version: CommandData::new("typos", &["--version"]), install: InstallationCommand::Cargo("typos-cli"), } - .check(self.force_install)?; + .check(self.force_install, self.cargo_install_method)?; let cmd = CommandData::new("typos", &["--color", "always"]).print_output(); @@ -322,7 +329,7 @@ impl CheckCmd { version: CommandData::new("cargo-machete", &["--version"]), install: InstallationCommand::Cargo("cargo-machete"), } - .check(self.force_install)?; + .check(self.force_install, self.cargo_install_method)?; let output = CommandData::new("cargo-machete", &["--with-metadata"]) .print_output() @@ -345,7 +352,7 @@ impl CheckCmd { version: CommandData::new("cargo", &["deny", "--version"]), install: InstallationCommand::Cargo("cargo-deny"), } - .check(self.force_install)?; + .check(self.force_install, self.cargo_install_method)?; let output = CommandData::new("cargo", &["deny", "check"]) .print_output() @@ -625,7 +632,7 @@ impl CheckCmd { version: CommandData::new("cargo", &["sort", "--version"]), install: InstallationCommand::Cargo("cargo-sort"), } - .check(self.force_install)?; + .check(self.force_install, self.cargo_install_method)?; let output = CommandData::new( "cargo", diff --git a/hooks/checks/src/utils.rs b/hooks/checks/src/utils.rs index 40061a84..bca1f65b 100644 --- a/hooks/checks/src/utils.rs +++ b/hooks/checks/src/utils.rs @@ -116,7 +116,11 @@ impl CheckDependency { /// Check whether the dependency is available. /// /// Returns `Ok` if the dependency was available or successfully installed. - pub(crate) fn check(self, force_install: bool) -> Result<(), ScriptError> { + pub(crate) fn check( + self, + force_install: bool, + cargo_method: CargoInstallMethod, + ) -> Result<(), ScriptError> { let Self { name, version, @@ -136,12 +140,14 @@ impl CheckDependency { } if !force_install { - self.ask_install()?; + self.ask_install(cargo_method)?; } println!("\x1B[1;92mInstalling\x1B[0m {name}…"); - if install.run()?.status.success() && version.run()?.status.success() { + if install.run(force_install, cargo_method)?.status.success() + && version.run()?.status.success() + { // The dependency was installed successfully. Ok(()) } else { @@ -152,7 +158,7 @@ impl CheckDependency { /// Ask the user whether we should try to install the dependency, if we are /// in a terminal. - fn ask_install(self) -> Result<(), ScriptError> { + fn ask_install(self, cargo_method: CargoInstallMethod) -> Result<(), ScriptError> { let name = self.name; let stdin = stdin(); @@ -163,7 +169,7 @@ impl CheckDependency { } println!("{name} is needed for this check, but it isn’t available\n"); - println!("y: Install {name} via {}", self.install.via()); + println!("y: Install {name} via {}", self.install.via(cargo_method)); println!("N: Don’t install {name} and abort checks\n"); let mut input = String::new(); @@ -313,24 +319,65 @@ pub(crate) enum InstallationCommand { impl InstallationCommand { /// The program used for the installation. - pub(crate) fn via(self) -> &'static str { + pub(crate) fn via(self, cargo_method: CargoInstallMethod) -> &'static str { match self { - Self::Cargo(_) => "cargo", + Self::Cargo(_) => cargo_method.name(), Self::Custom(cmd) => cmd.program, } } /// Run this command. - pub(crate) fn run(self) -> Result { + pub(crate) fn run( + self, + force_install: bool, + cargo_method: CargoInstallMethod, + ) -> Result { match self { - Self::Cargo(dep) => CommandData::new("cargo", &["install"]) - .print_output() - .run_with_args(&[dep]), + Self::Cargo(dep) => cargo_method.run(dep, force_install), Self::Custom(cmd) => cmd.print_output().run(), } } } +/// The method used to install crate dependencies. +#[derive(Clone, Copy, Default)] +pub(crate) enum CargoInstallMethod { + /// Use `cargo install`. + #[default] + Cargo, + /// Use `cargo-binstall`. + CargoBinstall, +} + +impl CargoInstallMethod { + /// The name of this method. + fn name(self) -> &'static str { + match self { + Self::Cargo => "cargo", + Self::CargoBinstall => "cargo-binstall", + } + } + + /// Run tis method for the given dependency. + fn run(self, dep: &str, force_install: bool) -> Result { + if matches!(self, Self::CargoBinstall) { + CheckDependency { + name: "cargo-binstall", + version: CommandData::new("cargo", &["binstall", "-V"]), + install: InstallationCommand::Cargo("cargo-binstall"), + } + .check(force_install, CargoInstallMethod::Cargo)?; + } + + let cmd = match self { + Self::Cargo => CommandData::new("cargo", &["install"]), + Self::CargoBinstall => CommandData::new("cargo", &["binstall"]), + }; + + cmd.print_output().run_with_args(&[dep]) + } +} + /// Visit the given directory recursively and apply the given function to files. pub(crate) fn visit_dir(dir: &Path, on_file: &mut dyn FnMut(PathBuf)) { let dir_entries = match fs::read_dir(dir) {