Skip to content

Commit ce9e462

Browse files
authored
Add go version to gopls cache key (#20922)
Closes #8071 Release Notes: - Changed the Go integration to check whether an existing `gopls` was compiled for the current `go` version. Previously we cached gopls (the go language server) as a file called `gopls_{GOPLS_VERSION}`. The go version that gopls was built with is crucial, so we need to cache the go version as well. It's actually super interesting and very clever; gopls uses go to parse the AST and do all the analyzation etc. Go exposes its internals in its standard lib (`go/parser`, `go/types`, ...), which gopls uses to analyze the user code. So if there is a new go release that contains new syntax/features/etc. (the libraries `go/parser`, `go/types`, ... change), we can rebuild the same version of `gopls` with the new version of go (with the updated `go/xxx` libraries) to support the new language features. We had some issues around that (e.g., range over integers introduced in go1.22, or custom iterators in go1.23) where we never updated gopls, because we were on the latest gopls version, but built with an old go version. After this PR gopls will be cached under the name `gopls_{GOPLS_VERSION}_go_{GO_VERSION}`. Most users do not see this issue anymore, because after #8188 we first check if we can find gopls in the PATH before downloading and caching gopls, but the issue still exists.
1 parent e58cdca commit ce9e462

File tree

1 file changed

+25
-12
lines changed
  • crates/languages/src

1 file changed

+25
-12
lines changed

crates/languages/src/go.rs

+25-12
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use std::{
1515
ffi::{OsStr, OsString},
1616
ops::Range,
1717
path::PathBuf,
18+
process::Output,
1819
str,
1920
sync::{
2021
atomic::{AtomicBool, Ordering::SeqCst},
@@ -35,8 +36,8 @@ impl GoLspAdapter {
3536
const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("gopls");
3637
}
3738

38-
static GOPLS_VERSION_REGEX: LazyLock<Regex> =
39-
LazyLock::new(|| Regex::new(r"\d+\.\d+\.\d+").expect("Failed to create GOPLS_VERSION_REGEX"));
39+
static VERSION_REGEX: LazyLock<Regex> =
40+
LazyLock::new(|| Regex::new(r"\d+\.\d+\.\d+").expect("Failed to create VERSION_REGEX"));
4041

4142
static GO_ESCAPE_SUBTEST_NAME_REGEX: LazyLock<Regex> = LazyLock::new(|| {
4243
Regex::new(r#"[.*+?^${}()|\[\]\\]"#).expect("Failed to create GO_ESCAPE_SUBTEST_NAME_REGEX")
@@ -111,11 +112,18 @@ impl super::LspAdapter for GoLspAdapter {
111112
container_dir: PathBuf,
112113
delegate: &dyn LspAdapterDelegate,
113114
) -> Result<LanguageServerBinary> {
115+
let go = delegate.which("go".as_ref()).await.unwrap_or("go".into());
116+
let go_version_output = util::command::new_smol_command(&go)
117+
.args(["version"])
118+
.output()
119+
.await
120+
.context("failed to get go version via `go version` command`")?;
121+
let go_version = parse_version_output(&go_version_output)?;
114122
let version = version.downcast::<Option<String>>().unwrap();
115123
let this = *self;
116124

117125
if let Some(version) = *version {
118-
let binary_path = container_dir.join(format!("gopls_{version}"));
126+
let binary_path = container_dir.join(format!("gopls_{version}_go_{go_version}"));
119127
if let Ok(metadata) = fs::metadata(&binary_path).await {
120128
if metadata.is_file() {
121129
remove_matching(&container_dir, |entry| {
@@ -139,8 +147,6 @@ impl super::LspAdapter for GoLspAdapter {
139147

140148
let gobin_dir = container_dir.join("gobin");
141149
fs::create_dir_all(&gobin_dir).await?;
142-
143-
let go = delegate.which("go".as_ref()).await.unwrap_or("go".into());
144150
let install_output = util::command::new_smol_command(go)
145151
.env("GO111MODULE", "on")
146152
.env("GOBIN", &gobin_dir)
@@ -164,13 +170,8 @@ impl super::LspAdapter for GoLspAdapter {
164170
.output()
165171
.await
166172
.context("failed to run installed gopls binary")?;
167-
let version_stdout = str::from_utf8(&version_output.stdout)
168-
.context("gopls version produced invalid utf8 output")?;
169-
let version = GOPLS_VERSION_REGEX
170-
.find(version_stdout)
171-
.with_context(|| format!("failed to parse golps version output '{version_stdout}'"))?
172-
.as_str();
173-
let binary_path = container_dir.join(format!("gopls_{version}"));
173+
let gopls_version = parse_version_output(&version_output)?;
174+
let binary_path = container_dir.join(format!("gopls_{gopls_version}_go_{go_version}"));
174175
fs::rename(&installed_binary_path, &binary_path).await?;
175176

176177
Ok(LanguageServerBinary {
@@ -366,6 +367,18 @@ impl super::LspAdapter for GoLspAdapter {
366367
}
367368
}
368369

370+
fn parse_version_output(output: &Output) -> Result<&str> {
371+
let version_stdout =
372+
str::from_utf8(&output.stdout).context("version command produced invalid utf8 output")?;
373+
374+
let version = VERSION_REGEX
375+
.find(version_stdout)
376+
.with_context(|| format!("failed to parse version output '{version_stdout}'"))?
377+
.as_str();
378+
379+
Ok(version)
380+
}
381+
369382
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
370383
maybe!(async {
371384
let mut last_binary_path = None;

0 commit comments

Comments
 (0)