fix: support multiple versions in lockfile (#2923)

 Conflicts:
	.mise.lock
	.mise.toml
	e2e/cli/test_use
This commit is contained in:
jdx 2024-11-05 20:00:43 -06:00 committed by GitHub
parent 17a1c501cf
commit c6e795dc07
7 changed files with 88 additions and 12 deletions

18
.mise.lock Normal file
View File

@ -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"

View File

@ -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

View File

@ -3,6 +3,7 @@
export MISE_LOCKFILE=1
export MISE_EXPERIMENTAL=1
touch .mise.lock
mise use tiny@1
cat <<EOF >.mise.lock
[tools]

View File

@ -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"

View File

@ -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]

View File

@ -13,7 +13,7 @@ use std::sync::Mutex;
#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Lockfile {
tools: BTreeMap<String, String>,
tools: BTreeMap<String, toml::Value>,
}
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<Option<String>> {
pub fn get_locked_version(path: &Path, short: &str, prefix: &str) -> Result<Option<String>> {
static CACHE: Lazy<Mutex<HashMap<PathBuf, Lockfile>>> = Lazy::new(Default::default);
if !SETTINGS.lockfile {
@ -122,7 +134,25 @@ pub fn get_locked_version(path: &Path, short: &str) -> Result<Option<String>> {
.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 {

View File

@ -206,7 +206,7 @@ impl ToolRequest {
pub fn lockfile_resolve(&self) -> Result<Option<String>> {
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)
}