diff --git a/deny.toml b/deny.toml index 87517bb061..bbda73e1ea 100644 --- a/deny.toml +++ b/deny.toml @@ -71,6 +71,7 @@ feature-depth = 1 # output a note when they are encountered. ignore = [ { id = "RUSTSEC-2024-0370", reason = "subdependency cannot be updated" }, + { id = "RUSTSEC-2025-0007", reason = "subdependency cannot be updated" }, #"RUSTSEC-0000-0000", #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish diff --git a/docs/dev-tools/backends/ubi.md b/docs/dev-tools/backends/ubi.md index 2e751ecc17..45f291d507 100644 --- a/docs/dev-tools/backends/ubi.md +++ b/docs/dev-tools/backends/ubi.md @@ -46,6 +46,17 @@ use the `exe` option to specify the executable name: "ubi:cli/cli" = { version = "latest", exe = "gh" } # github's cli ``` +### `rename_exe` + +The `rename_exe` option allows you to specify the name of the executable once it has been extracted. + +use the `rename_exe` option to specify the target executable name: + +```toml +[tools] +"ubi:cli/cli" = { version = "latest", exe = "gh", rename_exe = "github" } # github's cli +``` + ### `matching` Set a string to match against the release filename when there are multiple files for your @@ -60,7 +71,7 @@ then this will be ignored. ### `extract_all` -Set to `true` to extract all files in the tarball instead of just the "bin". Not compatible with `exe`. +Set to `true` to extract all files in the tarball instead of just the "bin". Not compatible with `exe` nor `rename_exe`. ```toml [tools] diff --git a/docs/installing-mise.md b/docs/installing-mise.md index 545478ea35..775fbdc311 100644 --- a/docs/installing-mise.md +++ b/docs/installing-mise.md @@ -296,7 +296,7 @@ You will need to first create the parent directory if it does not exist. ::: ```powershell -echo '~/.local/bin/mise activate mise activate pwsh | Out-String | Invoke-Expression' >> $HOME\Documents\PowerShell\Microsoft.PowerShell_profile.ps1 +echo 'mise activate pwsh | Out-String | Invoke-Expression' >> $HOME\Documents\PowerShell\Microsoft.PowerShell_profile.ps1 ``` ### Nushell diff --git a/docs/lang/zig.md b/docs/lang/zig.md index fdb5c54ece..fd7ac097ee 100644 --- a/docs/lang/zig.md +++ b/docs/lang/zig.md @@ -14,7 +14,7 @@ The following installs zig and makes it the global default: ```sh mise use -g zig@0.13 # install zig 0.13.x mise use -g zig@latest # install latest zig release -mise use -g zig@ref:master # instaLL latest nightly from master +mise use -g zig@ref:master # install latest nightly from master mise use -g zig@ref:mach-latest # install latest nominated zig mise use -g zig@0.14.0-dev.2577+271452d22 # install dev version ``` diff --git a/e2e-win/shim.Tests.ps1 b/e2e-win/shim.Tests.ps1 new file mode 100644 index 0000000000..e70e200e91 --- /dev/null +++ b/e2e-win/shim.Tests.ps1 @@ -0,0 +1,55 @@ +Describe 'shim_mode' { + + BeforeAll { + function changeShimMode { + param ( + [string]$mode + ) + + mise settings windows_shim_mode $mode + mise reshim --force + } + + $shimPath = Join-Path -Path $env:MISE_DATA_DIR -ChildPath "shims" + } + + AfterAll { + mise settings unset windows_shim_mode + } + + It 'run on symlink' { + changeShimMode "symlink" + + mise x go@1.23.3 -- where go + mise x go@1.23.3 -- go version | Should -BeLike "go version go1.23.3 windows/*" + + (Get-Item -Path (Join-Path -Path $shimPath -ChildPath go.exe)).LinkType | Should -Be "SymbolicLink" + } + + It 'run on file' { + changeShimMode "file" + + mise x go@1.23.3 -- where go + mise x go@1.23.3 -- go version | Should -BeLike "go version go1.23.3 windows/*" + + (Get-Item -Path (Join-Path -Path $shimPath -ChildPath go.cmd)).LinkType | Should -Be $null + } + + It 'run on hardlink' { + mise settings windows_shim_mode "hardlink" + + # make mise is on same filesystem + $misePath = (Get-Command -Type Application mise -all | Select-Object -First 1).Source + $binPath = (Join-Path -Path $env:MISE_DATA_DIR -ChildPath "bin") + $newMisePath = (Join-Path -Path $binPath -ChildPath "mise.exe") + New-Item -ItemType Directory -Path $binPath -Force + Copy-Item $misePath $newMisePath + + &$newMisePath reshim --force + + &$newMisePath x go@1.23.3 -- where go + &$newMisePath x go@1.23.3 -- go version | Should -BeLike "go version go1.23.3 windows/*" + + (Get-Item -Path (Join-Path -Path $shimPath -ChildPath go.exe)).LinkType | Should -Be "HardLink" + } +} diff --git a/e2e/cli/test_outdated b/e2e/cli/test_outdated index 345b35f805..48c098f619 100644 --- a/e2e/cli/test_outdated +++ b/e2e/cli/test_outdated @@ -1,5 +1,8 @@ #!/usr/bin/env bash +assert "mise outdated --json" "{}" +assert "mise outdated" "" + echo 'dummy 1' >.tool-versions mise install dummy@1.0.0 diff --git a/e2e/cli/test_unuse b/e2e/cli/test_unuse index edd2f96b80..9f21fb78ec 100644 --- a/e2e/cli/test_unuse +++ b/e2e/cli/test_unuse @@ -1,8 +1,13 @@ #!/usr/bin/env bash assert "mise install dummy@1.0.0" +assert "ls $MISE_DATA_DIR/installs/dummy" "1 +1.0 +1.0.0 +latest" assert "mise rm dummy" assert_empty "mise ls" +assert_fail "ls $MISE_DATA_DIR/installs/dummy" assert "mise use dummy@1.0.0" assert_contains "mise ls dummy" "1.0.0" diff --git a/e2e/cli/test_use b/e2e/cli/test_use index c5d6a808d3..65553b7eb4 100644 --- a/e2e/cli/test_use +++ b/e2e/cli/test_use @@ -75,6 +75,13 @@ assert "cat ~/.config/mise/config.toml" '[tools] gh = "2"' rm -f ~/.config/mise/config.toml +export MISE_ENV=test +mise use -g dummy@1 +assert "cat ~/.config/mise/config.toml" '[tools] +dummy = "1"' +rm -f ~/.config/mise/config.toml +unset MISE_ENV + mise uninstall dummy --all mise use dummy@system assert "mise ls dummy" "dummy system ~/workdir/mise.toml system" diff --git a/registry.toml b/registry.toml index 3753e99ab8..5fd2f80ce2 100644 --- a/registry.toml +++ b/registry.toml @@ -842,6 +842,7 @@ gwvault.backends = [ "asdf:GoodwayGroup/asdf-gwvault" ] hadolint.backends = [ + "aqua:hadolint/hadolint", "ubi:hadolint/hadolint", "asdf:devlincashman/asdf-hadolint" ] @@ -982,7 +983,11 @@ k3kcli.backends = [ k3s.backends = ["asdf:mise-plugins/mise-k3s"] k3sup.backends = ["aqua:alexellis/k3sup", "asdf:cgroschupp/asdf-k3sup"] k6.backends = ["ubi:grafana/k6", "asdf:gr1m0h/asdf-k6"] -k9s.backends = ["ubi:derailed/k9s", "asdf:looztra/asdf-k9s"] +k9s.backends = [ + "aqua:derailed/k9s", + "ubi:derailed/k9s", + "asdf:looztra/asdf-k9s" +] kafka.backends = ["asdf:mise-plugins/mise-kafka"] kafkactl.backends = [ "aqua:deviceinsight/kafkactl", @@ -1615,7 +1620,11 @@ sonobuoy.backends = [ "ubi:vmware-tanzu/sonobuoy", "asdf:Nick-Triller/asdf-sonobuoy" ] -sops.backends = ["ubi:getsops/sops", "asdf:mise-plugins/mise-sops"] +sops.backends = [ + "aqua:getsops/sops", + "ubi:getsops/sops", + "asdf:mise-plugins/mise-sops" +] sopstool.backends = ["aqua:ibotta/sopstool", "asdf:elementalvoid/asdf-sopstool"] soracom.backends = ["ubi:soracom/soracom-cli", "asdf:gr1m0h/asdf-soracom"] sourcery.backends = ["asdf:mise-plugins/mise-sourcery"] diff --git a/schema/mise.json b/schema/mise.json index 617a7a3fe9..a1b1260747 100644 --- a/schema/mise.json +++ b/schema/mise.json @@ -969,6 +969,11 @@ "type": "string" } }, + "windows_shim_mode": { + "default": "file", + "description": "Shim file mode for Windows. Options: `file`, `hardlink`, `symlink`.", + "type": "string" + }, "yes": { "description": "This will automatically answer yes or no to prompts. This is useful for scripting.", "type": "boolean" diff --git a/settings.toml b/settings.toml index bab52156b9..d7809f2212 100644 --- a/settings.toml +++ b/settings.toml @@ -1159,6 +1159,17 @@ default = ["exe", "bat", "cmd", "com", "ps1", "vbs"] parse_env = "list_by_comma" description = "List of executable extensions for Windows. For example, `exe` for .exe files, `bat` for .bat files, and so on." +[windows_shim_mode] +env = "MISE_WINDOWS_SHIM_MODE" +type = "String" +default = "file" +description = "Shim file mode for Windows. Options: `file`, `hardlink`, `symlink`." +docs = """ +`file`: Creates a file with the content `mise exec`. +`hardlink`: Uses Windows NTFS Hardlink, required on same filesystems. Need run `mise reshim --force` after upgrade mise +`symlink`: Uses Windows NTFS SymbolicLink. Requires Windows Vista or later with admin privileges or enabling "Developer Mode" in Windows 10/11. +""" + [yes] env = "MISE_YES" type = "Bool" diff --git a/src/backend/ubi.rs b/src/backend/ubi.rs index 3e7cb8385e..0c542c2498 100644 --- a/src/backend/ubi.rs +++ b/src/backend/ubi.rs @@ -104,8 +104,13 @@ impl Backend for UbiBackend { if extract_all { builder = builder.extract_all(); - } else if let Some(exe) = opts.get("exe") { - builder = builder.exe(exe); + } else { + if let Some(exe) = opts.get("exe") { + builder = builder.exe(exe); + } + if let Some(rename_exe) = opts.get("rename_exe") { + builder = builder.rename_exe_to(rename_exe) + } } if let Some(matching) = opts.get("matching") { builder = builder.matching(matching); diff --git a/src/cli/outdated.rs b/src/cli/outdated.rs index f70b082f04..7c06d983bf 100644 --- a/src/cli/outdated.rs +++ b/src/cli/outdated.rs @@ -53,6 +53,19 @@ impl Outdated { ts.versions .retain(|_, tvl| tool_set.is_empty() || tool_set.contains(&tvl.backend)); let outdated = ts.list_outdated_versions(self.bump); + self.display(outdated)?; + Ok(()) + } + + fn display(&self, outdated: Vec) -> Result<()> { + match self.json { + true => self.display_json(outdated)?, + false => self.display_table(outdated)?, + } + Ok(()) + } + + fn display_table(&self, outdated: Vec) -> Result<()> { if outdated.is_empty() { info!("All tools are up to date"); if !self.bump { @@ -62,15 +75,8 @@ impl Outdated { "" ); } - } else if self.json { - self.display_json(outdated)?; - } else { - self.display(outdated)?; + return Ok(()); } - Ok(()) - } - - fn display(&self, outdated: Vec) -> Result<()> { let mut table = tabled::Table::new(outdated); if !self.bump { table.with(Remove::column(ByColumnName::new("bump"))); diff --git a/src/cli/unuse.rs b/src/cli/unuse.rs index 33918f2e57..00d7cec060 100644 --- a/src/cli/unuse.rs +++ b/src/cli/unuse.rs @@ -57,6 +57,7 @@ impl Unuse { if !self.no_prune { prune(self.installed_tool.iter().map(|ta| &ta.ba).collect(), false)?; + config::rebuild_shims_and_runtime_symlinks(&[])?; } Ok(()) diff --git a/src/cli/use.rs b/src/cli/use.rs index da31deff0c..1b886459bf 100644 --- a/src/cli/use.rs +++ b/src/cli/use.rs @@ -195,7 +195,9 @@ impl Use { fn get_config_file(&self) -> Result> { let cwd = env::current_dir()?; - let path = if let Some(p) = &self.path { + let path = if self.global { + MISE_GLOBAL_CONFIG_FILE.clone() + } else if let Some(p) = &self.path { let from_dir = config_file_from_dir(p).absolutize()?.to_path_buf(); if from_dir.starts_with(&cwd) { from_dir @@ -212,7 +214,7 @@ impl Use { } else if !env::MISE_ENV.is_empty() { let env = env::MISE_ENV.last().unwrap(); config_file_from_dir(&cwd.join(format!("mise.{env}.toml"))) - } else if self.global || env::in_home_dir() { + } else if env::in_home_dir() { MISE_GLOBAL_CONFIG_FILE.clone() } else { config_file_from_dir(&cwd) diff --git a/src/config/tracking.rs b/src/config/tracking.rs index cee9921eef..46609c99f4 100644 --- a/src/config/tracking.rs +++ b/src/config/tracking.rs @@ -26,11 +26,14 @@ impl Tracker { return Ok(output); } for path in read_dir(&*TRACKED_CONFIGS)? { - let path = path?.path(); - if !path.is_symlink() { + let mut path = path?.path(); + if path.is_symlink() { + path = fs::read_link(path)?; + } else if cfg!(target_os = "windows") { + path = PathBuf::from(fs::read_to_string(&path)?.trim()); + } else { continue; } - let path = fs::read_link(path)?; if path.exists() { output.push(path); } diff --git a/src/shims.rs b/src/shims.rs index ec57360632..495edd0d05 100644 --- a/src/shims.rs +++ b/src/shims.rs @@ -128,38 +128,59 @@ pub fn reshim(ts: &Toolset, force: bool) -> Result<()> { #[cfg(windows)] fn add_shim(mise_bin: &Path, symlink_path: &Path, shim: &str) -> Result<()> { - let shim = shim.trim_end_matches(".cmd"); - // write a shim file without extension for use in Git Bash/Cygwin - file::write( - symlink_path.with_extension(""), - formatdoc! {r#" + match SETTINGS.windows_shim_mode.as_ref() { + "file" => { + let shim = shim.trim_end_matches(".cmd"); + // write a shim file without extension for use in Git Bash/Cygwin + file::write( + symlink_path.with_extension(""), + formatdoc! {r#" #!/bin/bash exec mise x -- {shim} "$@" "#}, - ) - .wrap_err_with(|| { - eyre!( - "Failed to create symlink from {} to {}", - display_path(mise_bin), - display_path(symlink_path) - ) - })?; - file::write( - symlink_path.with_extension("cmd"), - formatdoc! {r#" + ) + .wrap_err_with(|| { + eyre!( + "Failed to create symlink from {} to {}", + display_path(mise_bin), + display_path(symlink_path) + ) + })?; + file::write( + symlink_path.with_extension("cmd"), + formatdoc! {r#" @echo off setlocal mise x -- {shim} %* "#}, - ) - .wrap_err_with(|| { - eyre!( - "Failed to create symlink from {} to {}", - display_path(mise_bin), - display_path(symlink_path) - ) - }) + ) + .wrap_err_with(|| { + eyre!( + "Failed to create symlink from {} to {}", + display_path(mise_bin), + display_path(symlink_path) + ) + }) + } + "hardlink" => fs::hard_link(mise_bin, symlink_path).wrap_err_with(|| { + eyre!( + "Failed to create hardlink from {} to {}", + display_path(mise_bin), + display_path(symlink_path) + ) + }), + "symlink" => { + std::os::windows::fs::symlink_file(mise_bin, symlink_path).wrap_err_with(|| { + eyre!( + "Failed to create symlink from {} to {}", + display_path(mise_bin), + display_path(symlink_path) + ) + }) + } + _ => panic!("Unknown shim mode"), + } } #[cfg(unix)] @@ -261,10 +282,18 @@ fn get_desired_shims(toolset: &Toolset) -> Result> { bins.into_iter() .flat_map(|b| { let p = PathBuf::from(&b); - vec![ - p.with_extension("").to_string_lossy().to_string(), - p.with_extension("cmd").to_string_lossy().to_string(), - ] + match SETTINGS.windows_shim_mode.as_ref() { + "hardlink" | "symlink" => { + vec![p.with_extension("exe").to_string_lossy().to_string()] + } + "file" => { + vec![ + p.with_extension("").to_string_lossy().to_string(), + p.with_extension("cmd").to_string_lossy().to_string(), + ] + } + _ => panic!("Unknown shim mode"), + } }) .collect() } else if cfg!(macos) {