From cec806cdcc2485a654fc07fddef64b9a8fdcc584 Mon Sep 17 00:00:00 2001 From: Peter Ullrich Date: Tue, 11 Nov 2025 20:48:44 +0100 Subject: [PATCH 1/4] Download latest version into checksum dir instead of version dir --- Cargo.lock | 84 ++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/language_servers/expert.rs | 50 ++++++++++++++------ 3 files changed, 120 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d35d2e3..d8f3823 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,12 +14,66 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "hashbrown" version = "0.15.2" @@ -64,6 +118,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + [[package]] name = "log" version = "0.4.26" @@ -138,6 +198,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "smallvec" version = "1.14.0" @@ -164,6 +235,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -182,6 +259,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasm-encoder" version = "0.201.0" @@ -314,6 +397,7 @@ dependencies = [ name = "zed_elixir" version = "0.2.3" dependencies = [ + "sha2", "zed_extension_api", ] diff --git a/Cargo.toml b/Cargo.toml index 4e3f073..795db92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,5 @@ path = "src/elixir.rs" crate-type = ["cdylib"] [dependencies] +sha2 = "0.10.9" zed_extension_api = "0.2.0" diff --git a/src/language_servers/expert.rs b/src/language_servers/expert.rs index fa69bfa..a1402b4 100644 --- a/src/language_servers/expert.rs +++ b/src/language_servers/expert.rs @@ -5,6 +5,8 @@ use zed_extension_api::lsp::{Completion, CompletionKind, Symbol, SymbolKind}; use zed_extension_api::settings::LspSettings; use zed_extension_api::{self as zed, CodeLabel, CodeLabelSpan, Result}; +use sha2::{Digest, Sha256}; + use crate::language_servers::util; pub struct ExpertBinary { @@ -98,27 +100,45 @@ impl Expert { .find(|asset| asset.name == asset_name) .ok_or_else(|| format!("no asset found matching {:?}", asset_name))?; - let version_dir = format!("{}-{}", Self::LANGUAGE_SERVER_ID, release.version); - fs::create_dir_all(&version_dir).map_err(|e| format!("failed to create directory: {e}"))?; + // Download to temporary location first so that we can generate the SHA256 checksum + let temp_file_path = format!("{}/temporary-download", Self::LANGUAGE_SERVER_ID); + + zed::set_language_server_installation_status( + language_server_id, + &zed::LanguageServerInstallationStatus::Downloading, + ); + + zed::download_file( + &asset.download_url, + &temp_file_path, + zed::DownloadedFileType::Uncompressed, + ) + .map_err(|e| format!("failed to download file: {e}"))?; + + // Calculate checksum of downloaded file + let file_contents = fs::read(&temp_file_path) + .map_err(|e| format!("failed to read downloaded file: {e}"))?; + + let checksum = format!("{:x}", Sha256::digest(&file_contents)); + + // Create directory with checksum + let checksum_dir = format!("{}-{}", Self::LANGUAGE_SERVER_ID, checksum); + fs::create_dir_all(&checksum_dir) + .map_err(|e| format!("failed to create directory: {e}"))?; - let binary_path = format!("{version_dir}/expert"); + let binary_path = format!("{checksum_dir}/expert"); if !fs::metadata(&binary_path).map_or(false, |stat| stat.is_file()) { - zed::set_language_server_installation_status( - language_server_id, - &zed::LanguageServerInstallationStatus::Downloading, - ); - - zed::download_file( - &asset.download_url, - &binary_path, - zed::DownloadedFileType::Uncompressed, - ) - .map_err(|e| format!("failed to download file: {e}"))?; + // Move from temp location to final location + fs::rename(&temp_file_path, &binary_path) + .map_err(|e| format!("failed to move file: {e}"))?; zed::make_file_executable(&binary_path)?; - util::remove_outdated_versions(Self::LANGUAGE_SERVER_ID, &version_dir)?; + util::remove_outdated_versions(Self::LANGUAGE_SERVER_ID, &checksum_dir)?; + } else { + // Clean up temp file if binary already exists + let _ = fs::remove_file(&temp_file_path); } self.cached_binary_path = Some(binary_path.clone()); From a3f24d9fd23386973aa2ab313b632edb7804a0a0 Mon Sep 17 00:00:00 2001 From: Peter Ullrich Date: Tue, 11 Nov 2025 21:02:28 +0100 Subject: [PATCH 2/4] Remove tmp folder --- src/language_servers/expert.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/language_servers/expert.rs b/src/language_servers/expert.rs index a1402b4..ebb2e4d 100644 --- a/src/language_servers/expert.rs +++ b/src/language_servers/expert.rs @@ -101,7 +101,9 @@ impl Expert { .ok_or_else(|| format!("no asset found matching {:?}", asset_name))?; // Download to temporary location first so that we can generate the SHA256 checksum - let temp_file_path = format!("{}/temporary-download", Self::LANGUAGE_SERVER_ID); + let tmp_dir = format!("{}-tmp", Self::LANGUAGE_SERVER_ID); + fs::create_dir_all(&tmp_dir).map_err(|e| format!("failed to create directory: {e}"))?; + let temp_file_path = format!("{tmp_dir}/temporary-download"); zed::set_language_server_installation_status( language_server_id, @@ -136,11 +138,11 @@ impl Expert { zed::make_file_executable(&binary_path)?; util::remove_outdated_versions(Self::LANGUAGE_SERVER_ID, &checksum_dir)?; - } else { - // Clean up temp file if binary already exists - let _ = fs::remove_file(&temp_file_path); } + // Clean up temp file + fs::remove_dir_all(&tmp_dir).ok(); + self.cached_binary_path = Some(binary_path.clone()); Ok(ExpertBinary { path: binary_path, From 4f2a731e2ab08d87fe11f5ef62c393e4ab694f6a Mon Sep 17 00:00:00 2001 From: Peter Ullrich Date: Wed, 12 Nov 2025 11:05:06 +0100 Subject: [PATCH 3/4] Fetch checksum from checksums txt file instead --- Cargo.lock | 84 ---------------------------------- Cargo.toml | 1 - src/language_servers/expert.rs | 64 ++++++++++++++------------ 3 files changed, 35 insertions(+), 114 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d8f3823..d35d2e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,66 +14,12 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" -[[package]] -name = "generic-array" -version = "0.14.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" -dependencies = [ - "typenum", - "version_check", -] - [[package]] name = "hashbrown" version = "0.15.2" @@ -118,12 +64,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" -[[package]] -name = "libc" -version = "0.2.177" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" - [[package]] name = "log" version = "0.4.26" @@ -198,17 +138,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "smallvec" version = "1.14.0" @@ -235,12 +164,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "typenum" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - [[package]] name = "unicode-ident" version = "1.0.18" @@ -259,12 +182,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - [[package]] name = "wasm-encoder" version = "0.201.0" @@ -397,7 +314,6 @@ dependencies = [ name = "zed_elixir" version = "0.2.3" dependencies = [ - "sha2", "zed_extension_api", ] diff --git a/Cargo.toml b/Cargo.toml index 795db92..4e3f073 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,5 +10,4 @@ path = "src/elixir.rs" crate-type = ["cdylib"] [dependencies] -sha2 = "0.10.9" zed_extension_api = "0.2.0" diff --git a/src/language_servers/expert.rs b/src/language_servers/expert.rs index ebb2e4d..74ee69f 100644 --- a/src/language_servers/expert.rs +++ b/src/language_servers/expert.rs @@ -5,8 +5,6 @@ use zed_extension_api::lsp::{Completion, CompletionKind, Symbol, SymbolKind}; use zed_extension_api::settings::LspSettings; use zed_extension_api::{self as zed, CodeLabel, CodeLabelSpan, Result}; -use sha2::{Digest, Sha256}; - use crate::language_servers::util; pub struct ExpertBinary { @@ -100,49 +98,57 @@ impl Expert { .find(|asset| asset.name == asset_name) .ok_or_else(|| format!("no asset found matching {:?}", asset_name))?; - // Download to temporary location first so that we can generate the SHA256 checksum - let tmp_dir = format!("{}-tmp", Self::LANGUAGE_SERVER_ID); - fs::create_dir_all(&tmp_dir).map_err(|e| format!("failed to create directory: {e}"))?; - let temp_file_path = format!("{tmp_dir}/temporary-download"); + let checksum_asset = release + .assets + .iter() + .find(|asset| asset.name == "expert_checksums.txt") + .ok_or_else(|| format!("no checksums file found in release"))?; - zed::set_language_server_installation_status( - language_server_id, - &zed::LanguageServerInstallationStatus::Downloading, - ); + let checksums_dir = format!("{}-checksums", Self::LANGUAGE_SERVER_ID); + fs::create_dir_all(&checksums_dir) + .map_err(|e| format!("failed to create directory: {e}"))?; + + let checksums_path = format!("{checksums_dir}/expert_checksums.txt"); zed::download_file( - &asset.download_url, - &temp_file_path, + &checksum_asset.download_url, + &checksums_path, zed::DownloadedFileType::Uncompressed, ) - .map_err(|e| format!("failed to download file: {e}"))?; + .map_err(|e| format!("failed to download checksums file: {e}"))?; - // Calculate checksum of downloaded file - let file_contents = fs::read(&temp_file_path) - .map_err(|e| format!("failed to read downloaded file: {e}"))?; + let checksums_content = fs::read_to_string(&checksums_path) + .map_err(|e| format!("failed to read checksums file: {e}"))?; - let checksum = format!("{:x}", Sha256::digest(&file_contents)); + let checksum = checksums_content + .lines() + .find(|line| line.ends_with(&asset_name)) + .and_then(|line| line.split_whitespace().next()) + .ok_or_else(|| format!("checksum not found for {}", asset_name))?; - // Create directory with checksum - let checksum_dir = format!("{}-{}", Self::LANGUAGE_SERVER_ID, checksum); - fs::create_dir_all(&checksum_dir) - .map_err(|e| format!("failed to create directory: {e}"))?; + let expert_dir = format!("{}-{}", Self::LANGUAGE_SERVER_ID, checksum); + fs::create_dir_all(&expert_dir).map_err(|e| format!("failed to create directory: {e}"))?; - let binary_path = format!("{checksum_dir}/expert"); + let binary_path = format!("{expert_dir}/expert"); if !fs::metadata(&binary_path).map_or(false, |stat| stat.is_file()) { - // Move from temp location to final location - fs::rename(&temp_file_path, &binary_path) - .map_err(|e| format!("failed to move file: {e}"))?; + zed::set_language_server_installation_status( + language_server_id, + &zed::LanguageServerInstallationStatus::Downloading, + ); + + zed::download_file( + &asset.download_url, + &binary_path, + zed::DownloadedFileType::Uncompressed, + ) + .map_err(|e| format!("failed to download file: {e}"))?; zed::make_file_executable(&binary_path)?; - util::remove_outdated_versions(Self::LANGUAGE_SERVER_ID, &checksum_dir)?; + util::remove_outdated_versions(Self::LANGUAGE_SERVER_ID, &expert_dir)?; } - // Clean up temp file - fs::remove_dir_all(&tmp_dir).ok(); - self.cached_binary_path = Some(binary_path.clone()); Ok(ExpertBinary { path: binary_path, From ba1a2214fd9e96a2af5a94a6ca2c3e00abbf605d Mon Sep 17 00:00:00 2001 From: Peter Ullrich Date: Mon, 17 Nov 2025 09:50:07 +0100 Subject: [PATCH 4/4] Truncate checksum and clean up checksum folder --- src/language_servers/expert.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/language_servers/expert.rs b/src/language_servers/expert.rs index 74ee69f..d04f569 100644 --- a/src/language_servers/expert.rs +++ b/src/language_servers/expert.rs @@ -120,13 +120,19 @@ impl Expert { let checksums_content = fs::read_to_string(&checksums_path) .map_err(|e| format!("failed to read checksums file: {e}"))?; - let checksum = checksums_content + fs::remove_dir_all(&checksums_dir) + .map_err(|e| format!("failed to remove checksums directory: {e}"))?; + + let truncated_checksum = checksums_content .lines() .find(|line| line.ends_with(&asset_name)) .and_then(|line| line.split_whitespace().next()) - .ok_or_else(|| format!("checksum not found for {}", asset_name))?; + .ok_or_else(|| format!("checksum not found for {}", asset_name))? + .chars() + .take(8) + .collect::(); - let expert_dir = format!("{}-{}", Self::LANGUAGE_SERVER_ID, checksum); + let expert_dir = format!("{}-{}", Self::LANGUAGE_SERVER_ID, truncated_checksum); fs::create_dir_all(&expert_dir).map_err(|e| format!("failed to create directory: {e}"))?; let binary_path = format!("{expert_dir}/expert");