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 https://github.com/zed-industries/zed/pull/8188 we first check if we can find gopls in the PATH before downloading and caching gopls, but the issue still exists.
This commit is contained in:
parent
e58cdca044
commit
ce9e4629be
1 changed files with 25 additions and 12 deletions
|
@ -15,6 +15,7 @@ use std::{
|
|||
ffi::{OsStr, OsString},
|
||||
ops::Range,
|
||||
path::PathBuf,
|
||||
process::Output,
|
||||
str,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering::SeqCst},
|
||||
|
@ -35,8 +36,8 @@ impl GoLspAdapter {
|
|||
const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("gopls");
|
||||
}
|
||||
|
||||
static GOPLS_VERSION_REGEX: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"\d+\.\d+\.\d+").expect("Failed to create GOPLS_VERSION_REGEX"));
|
||||
static VERSION_REGEX: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"\d+\.\d+\.\d+").expect("Failed to create VERSION_REGEX"));
|
||||
|
||||
static GO_ESCAPE_SUBTEST_NAME_REGEX: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r#"[.*+?^${}()|\[\]\\]"#).expect("Failed to create GO_ESCAPE_SUBTEST_NAME_REGEX")
|
||||
|
@ -111,11 +112,18 @@ impl super::LspAdapter for GoLspAdapter {
|
|||
container_dir: PathBuf,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let go = delegate.which("go".as_ref()).await.unwrap_or("go".into());
|
||||
let go_version_output = util::command::new_smol_command(&go)
|
||||
.args(["version"])
|
||||
.output()
|
||||
.await
|
||||
.context("failed to get go version via `go version` command`")?;
|
||||
let go_version = parse_version_output(&go_version_output)?;
|
||||
let version = version.downcast::<Option<String>>().unwrap();
|
||||
let this = *self;
|
||||
|
||||
if let Some(version) = *version {
|
||||
let binary_path = container_dir.join(format!("gopls_{version}"));
|
||||
let binary_path = container_dir.join(format!("gopls_{version}_go_{go_version}"));
|
||||
if let Ok(metadata) = fs::metadata(&binary_path).await {
|
||||
if metadata.is_file() {
|
||||
remove_matching(&container_dir, |entry| {
|
||||
|
@ -139,8 +147,6 @@ impl super::LspAdapter for GoLspAdapter {
|
|||
|
||||
let gobin_dir = container_dir.join("gobin");
|
||||
fs::create_dir_all(&gobin_dir).await?;
|
||||
|
||||
let go = delegate.which("go".as_ref()).await.unwrap_or("go".into());
|
||||
let install_output = util::command::new_smol_command(go)
|
||||
.env("GO111MODULE", "on")
|
||||
.env("GOBIN", &gobin_dir)
|
||||
|
@ -164,13 +170,8 @@ impl super::LspAdapter for GoLspAdapter {
|
|||
.output()
|
||||
.await
|
||||
.context("failed to run installed gopls binary")?;
|
||||
let version_stdout = str::from_utf8(&version_output.stdout)
|
||||
.context("gopls version produced invalid utf8 output")?;
|
||||
let version = GOPLS_VERSION_REGEX
|
||||
.find(version_stdout)
|
||||
.with_context(|| format!("failed to parse golps version output '{version_stdout}'"))?
|
||||
.as_str();
|
||||
let binary_path = container_dir.join(format!("gopls_{version}"));
|
||||
let gopls_version = parse_version_output(&version_output)?;
|
||||
let binary_path = container_dir.join(format!("gopls_{gopls_version}_go_{go_version}"));
|
||||
fs::rename(&installed_binary_path, &binary_path).await?;
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
|
@ -366,6 +367,18 @@ impl super::LspAdapter for GoLspAdapter {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_version_output(output: &Output) -> Result<&str> {
|
||||
let version_stdout =
|
||||
str::from_utf8(&output.stdout).context("version command produced invalid utf8 output")?;
|
||||
|
||||
let version = VERSION_REGEX
|
||||
.find(version_stdout)
|
||||
.with_context(|| format!("failed to parse version output '{version_stdout}'"))?
|
||||
.as_str();
|
||||
|
||||
Ok(version)
|
||||
}
|
||||
|
||||
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||
maybe!(async {
|
||||
let mut last_binary_path = None;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue