Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 4 additions & 1 deletion e2e/backend/test_go_install_slow
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ assert_fail go

cat >>.mise.toml <<EOF
[tools]
go = "prefix:1.22"
go = "prefix:1.24"
"go:github.com/golangci/golangci-lint/cmd/golangci-lint" = "latest"
[settings]
experimental = true
Expand All @@ -31,6 +31,9 @@ assert "mise x go:github.com/go-task/task/v3/cmd/task@3.34.1 -- task --version"
# See https://github.com/jdx/mise/discussions/6737
assert "mise x go:github.com/jdx/go-example@e16a340 -- go-example" "hello world"

# Deep sub-module returns no versions from `go list -versions`, so we must keep @latest.
assert_contains "mise x go:github.com/go-kratos/kratos/cmd/kratos/v2@latest -- bash -c 'kratos --help 2>&1'" "Kratos: An elegant toolkit for Go microservices."

assert_contains "mise x go:github.com/golang-migrate/migrate/v4/cmd/migrate[tags=postgres]@4.18.2 -- bash -c 'migrate --help 2>&1'" "postgres"

# Required to properly cleanup as go installs read-only sources
Expand Down
122 changes: 92 additions & 30 deletions src/backend/go.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ impl Backend for GoBackend {
timeout::run_with_timeout_async(
async || {
let tool_name = self.tool_name();

// First try the exact tool path. If this succeeds but returns no versions,
// treat that as authoritative so installs can continue with `@latest`
// instead of resolving to a parent module version.
if let Some(versions) = self.fetch_go_module_versions(config, &tool_name).await? {
return Ok(versions);
}

let parts = tool_name.split('/').collect::<Vec<_>>();
let module_root_index = if parts[0] == "github.com" {
// Try likely module root index first
Expand All @@ -75,32 +83,13 @@ impl Backend for GoBackend {

for i in indices {
let mod_path = parts[..=i].join("/");
let res = cmd!(
"go",
"list",
"-mod=readonly",
"-m",
"-versions",
"-json",
mod_path
)
.full_env(self.dependency_env(config).await?)
.read();
if let Ok(raw) = res {
let res = serde_json::from_str::<GoModInfo>(&raw);
if let Ok(mod_info) = res {
// remove the leading v from the versions
let versions = mod_info
.versions
.into_iter()
.map(|v| VersionInfo {
version: v.trim_start_matches('v').to_string(),
..Default::default()
})
.collect();
return Ok(versions);
}
};
if mod_path == tool_name {
continue;
}
if let Some(versions) = self.fetch_go_module_versions(config, &mod_path).await?
{
return Ok(versions);
}
}

Ok(vec![])
Expand All @@ -113,7 +102,7 @@ impl Backend for GoBackend {
async fn install_version_(
&self,
ctx: &InstallContext,
tv: ToolVersion,
mut tv: ToolVersion,
) -> eyre::Result<ToolVersion> {
// Check if go is available
self.warn_if_dependency_missing(
Expand All @@ -125,6 +114,20 @@ impl Backend for GoBackend {
)
.await;

// Some deep modules return no Versions from `go list -versions`.
// If the original request was `latest`, force `@latest` install for
// those modules instead of using a parent module's resolved version.
let mut install_version = tv.version.clone();
if tv.request.version() == "latest" && tv.version != "latest"
&& let Some(versions) = self
.fetch_go_module_versions(&ctx.config, &self.tool_name())
.await?
&& versions.is_empty()
{
install_version = "latest".to_string();
tv.version = install_version.clone();
}
Comment thread
roele marked this conversation as resolved.
Outdated

let opts = self.ba.opts();

let install = async |v| {
Expand All @@ -142,17 +145,17 @@ impl Backend for GoBackend {
};

// try "v" prefix if the version starts with semver
let use_v = regex!(r"^\d+\.\d+\.\d+").is_match(&tv.version);
let use_v = regex!(r"^\d+\.\d+\.\d+").is_match(&install_version);

if use_v {
if install(format!("v{}", tv.version)).await.is_err() {
if install(format!("v{}", install_version)).await.is_err() {
warn!("Failed to install, trying again without added 'v' prefix");
} else {
return Ok(tv);
}
}

install(tv.version.clone()).await?;
install(install_version).await?;

Ok(tv)
}
Expand Down Expand Up @@ -183,10 +186,69 @@ impl GoBackend {
pub fn from_arg(ba: BackendArg) -> Self {
Self { ba: Arc::new(ba) }
}

async fn fetch_go_module_versions(
&self,
config: &Arc<Config>,
mod_path: &str,
) -> eyre::Result<Option<Vec<VersionInfo>>> {
let raw = match cmd!(
"go",
"list",
"-mod=readonly",
"-m",
"-versions",
"-json",
mod_path
)
.full_env(self.dependency_env(config).await?)
.read()
{
Ok(raw) => raw,
Err(_) => return Ok(None),
};

let mod_info = match serde_json::from_str::<GoModInfo>(&raw) {
Ok(info) => info,
Err(_) => return Ok(None),
};

// remove the leading v from the versions
let versions = mod_info
.versions
.into_iter()
.map(|v| VersionInfo {
version: v.trim_start_matches('v').to_string(),
..Default::default()
})
.collect();

Ok(Some(versions))
}
}

#[derive(Debug, serde::Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct GoModInfo {
#[serde(default)]
versions: Vec<String>,
}

#[cfg(test)]
mod tests {
use super::GoModInfo;

#[test]
fn parse_go_mod_info_without_versions() {
let raw = r#"{"Path":"github.com/go-kratos/kratos/cmd/kratos/v2"}"#;
let info: GoModInfo = serde_json::from_str(raw).unwrap();
assert!(info.versions.is_empty());
}

#[test]
fn parse_go_mod_info_with_versions() {
let raw = r#"{"Path":"example.com/mod","Versions":["v1.0.0","v1.1.0"]}"#;
let info: GoModInfo = serde_json::from_str(raw).unwrap();
assert_eq!(info.versions, vec!["v1.0.0", "v1.1.0"]);
}
}
Loading