From 48a6f7b53fe374f89d0d272e226777b32e9b0240 Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Tue, 5 Nov 2024 20:00:43 -0600 Subject: [PATCH] fix: support multiple versions in lockfile (#2923)  Conflicts:  .mise.lock  .mise.toml  e2e/cli/test_use --- .mise.lock | 18 ++++++++++++ e2e/lockfile/test_lockfile_exec | 1 + e2e/lockfile/test_lockfile_install | 1 + e2e/lockfile/test_lockfile_use | 14 ++++++++++ settings.toml | 20 +++++++++++--- src/lockfile.rs | 44 +++++++++++++++++++++++++----- src/toolset/tool_request.rs | 2 +- 7 files changed, 88 insertions(+), 12 deletions(-) create mode 100644 .mise.lock diff --git a/.mise.lock b/.mise.lock new file mode 100644 index 000000000..4f8e70b0a --- /dev/null +++ b/.mise.lock @@ -0,0 +1,18 @@ +[tools] +actionlint = "1.7.4" +cargo-binstall = "1.10.10" +"cargo:cargo-edit" = "0.13.0" +"cargo:cargo-insta" = "1.41.1" +"cargo:cargo-show" = "0.6.0" +"cargo:git-cliff" = "2.6.1" +"cargo:usage-cli" = "1.1.1" +direnv = "latest" +jq = "1.7.1" +"npm:markdownlint-cli" = "0.42.0" +"npm:prettier" = "3.3.3" +"pipx:toml-sort" = "0.23.1" +python = "3.10.15" +ripgrep = "14.1.1" +shellcheck = "0.10.0" +shfmt = "3.10.0" +tiny = "2.1.0" diff --git a/e2e/lockfile/test_lockfile_exec b/e2e/lockfile/test_lockfile_exec index 998340740..dbc8a0215 100644 --- a/e2e/lockfile/test_lockfile_exec +++ b/e2e/lockfile/test_lockfile_exec @@ -3,6 +3,7 @@ export MISE_LOCKFILE=1 export MISE_EXPERIMENTAL=1 +touch .mise.lock mise install tiny@1.0.0 mise use tiny@1 mise install tiny@1.0.1 diff --git a/e2e/lockfile/test_lockfile_install b/e2e/lockfile/test_lockfile_install index 98e9a8de6..e441b0fb6 100644 --- a/e2e/lockfile/test_lockfile_install +++ b/e2e/lockfile/test_lockfile_install @@ -3,6 +3,7 @@ export MISE_LOCKFILE=1 export MISE_EXPERIMENTAL=1 +touch .mise.lock mise use tiny@1 cat <.mise.lock [tools] diff --git a/e2e/lockfile/test_lockfile_use b/e2e/lockfile/test_lockfile_use index 31e6a4c4e..df06b6e15 100644 --- a/e2e/lockfile/test_lockfile_use +++ b/e2e/lockfile/test_lockfile_use @@ -3,6 +3,7 @@ export MISE_LOCKFILE=1 export MISE_EXPERIMENTAL=1 +touch .mise.lock mise install tiny@1.0.0 mise use tiny@1 mise install tiny@1.0.1 @@ -30,3 +31,16 @@ assert "cat .mise.lock" '[tools] tiny = "3.1.0"' assert "mise ls tiny --json --current | jq -r '.[0].requested_version'" "3" assert "mise ls tiny --json --current | jq -r '.[0].version'" "3.1.0" + +mise use tiny@1 tiny@2 +assert "cat .mise.lock" '[tools] +tiny = [ + "1.0.0", + "2.1.0", +]' +mise uninstall --all tiny +mise install tiny +assert "mise ls tiny --json --current | jq -r '.[0].requested_version'" "1" +assert "mise ls tiny --json --current | jq -r '.[0].version'" "1.0.0" +assert "mise ls tiny --json --current | jq -r '.[1].requested_version'" "2" +assert "mise ls tiny --json --current | jq -r '.[1].version'" "2.1.0" diff --git a/settings.toml b/settings.toml index 826aed960..07a660b39 100644 --- a/settings.toml +++ b/settings.toml @@ -329,7 +329,7 @@ type = "Bool" default = false description = "Create and read lockfiles for tool versions." docs = """ -Create and read lockfiles for tool versions. This is useful when you'd like to have loose versions in mise.toml like this: +Read/update lockfiles for tool versions. This is useful when you'd like to have loose versions in mise.toml like this: ```toml [tools] @@ -337,9 +337,21 @@ node = "22" gh = "latest" ``` -But you'd like the versions installed to be consistent within a project. When this is enabled, mise will automatically -create mise.lock files next to mise.toml files containing pinned versions. -When installing tools, mise will reference this lockfile if it exists and this setting is enabled to resolve versions. +But you'd like the versions installed to be consistent within a project. When this is enabled, mise will update mise.lock +files next to mise.toml files containing pinned versions. When installing tools, mise will reference this lockfile if it exists and this setting is enabled to resolve versions. + +The lockfiles are not created automatically. To generate them, run the following (assuming the config file is `mise.toml`): + +```sh +touch mise.lock && mise install +``` + +The lockfile is named the same as the config file but with `.lock` instead of `.toml` as the extension, e.g.: + +- `mise.toml` -> `mise.lock` +- `.mise.toml` -> `.mise.lock` +- `mise.local.toml` -> `mise.local.lock` +- `.config/mise.toml` -> `.config/mise.lock` """ [log_level] diff --git a/src/lockfile.rs b/src/lockfile.rs index e96e25c48..f0a77e7f5 100644 --- a/src/lockfile.rs +++ b/src/lockfile.rs @@ -13,7 +13,7 @@ use std::sync::Mutex; #[derive(Debug, Default, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Lockfile { - tools: BTreeMap, + tools: BTreeMap, } impl Lockfile { @@ -76,6 +76,9 @@ pub fn update_lockfiles(new_versions: &[ToolVersion]) -> Result<()> { let empty = HashMap::new(); for config_path in lockfiles { let lockfile_path = config_path.with_extension("lock"); + if !lockfile_path.exists() { + continue; + } let tool_source = ToolSource::MiseToml(config_path.clone()); let tools = tools_by_source.get(&tool_source).unwrap_or(&empty); trace!( @@ -94,10 +97,19 @@ pub fn update_lockfiles(new_versions: &[ToolVersion]) -> Result<()> { .retain(|k, _| all_tool_names.contains(k) || SETTINGS.disable_tools.contains(k)); for (short, tvl) in tools { - for tv in &tvl.versions { - existing_lockfile - .tools - .insert(short.to_string(), tv.version.to_string()); + if tvl.versions.len() > 1 { + let versions = toml::Value::Array( + tvl.versions + .iter() + .map(|tv| tv.version.clone().into()) + .collect(), + ); + existing_lockfile.tools.insert(short.to_string(), versions); + } else { + existing_lockfile.tools.insert( + short.to_string(), + toml::Value::String(tvl.versions[0].version.clone()), + ); } } @@ -107,7 +119,7 @@ pub fn update_lockfiles(new_versions: &[ToolVersion]) -> Result<()> { Ok(()) } -pub fn get_locked_version(path: &Path, short: &str) -> Result> { +pub fn get_locked_version(path: &Path, short: &str, prefix: &str) -> Result> { static CACHE: Lazy>> = Lazy::new(Default::default); if !SETTINGS.lockfile { @@ -122,7 +134,25 @@ pub fn get_locked_version(path: &Path, short: &str) -> Result> { .unwrap_or_else(|err| handle_missing_lockfile(err, &lockfile_path)) }); - Ok(lockfile.tools.get(short).cloned()) + if let Some(tool) = lockfile.tools.get(short) { + // TODO: handle something like `mise use python@3 python@3.1` + match tool { + toml::Value::String(v) => { + if v.starts_with(prefix) { + Ok(Some(v.clone())) + } else { + Ok(None) + } + } + toml::Value::Array(v) => Ok(v + .iter() + .map(|v| v.as_str().unwrap().to_string()) + .find(|v| v.starts_with(prefix))), + _ => unimplemented!("unsupported lockfile format"), + } + } else { + Ok(None) + } } fn handle_missing_lockfile(err: Report, lockfile_path: &Path) -> Lockfile { diff --git a/src/toolset/tool_request.rs b/src/toolset/tool_request.rs index 5f8514f02..b24b0539b 100644 --- a/src/toolset/tool_request.rs +++ b/src/toolset/tool_request.rs @@ -206,7 +206,7 @@ impl ToolRequest { pub fn lockfile_resolve(&self) -> Result> { if let Some(path) = self.source().path() { - return lockfile::get_locked_version(path, &self.backend().short); + return lockfile::get_locked_version(path, &self.backend().short, &self.version()); } Ok(None) }