mirror of
https://github.com/zed-industries/zed.git
synced 2026-10-19 11:04:06 +00:00
extension: Bump wasi-sdk to version 34 (#63816)
# Objective Bumps `wasi-sdk` to v34 that is based on LLVM 23.1 which includes [a compile time and memory usage fix](https://github.com/llvm/llvm-project/pull/181755) for tree-sitter grammars. It speeds up every grammar's compile time and reduces compile memory usage unlocking big grammars that were impossible to compile before to be submitted to Zed extensions. Drive-by added additional wasi-sdk targets for the rest of the supported architectures (by wasi-sdk), if riscv is unwanted I can remove it but it should not create any problems (I think). Also noticed the `download-wasi-sdk` script had `OS=mingw` which wasn't even in the artifact name on v25. ## Testing Compiled, ran and compiled my [big grammar](https://github.com/usagi-coffee/tree-sitter-abl) on it, that wouldn't compile at all pre-llvm 23. ## Self-Review Checklist: - [X] I've reviewed my own diff for quality, security, and reliability --- Release Notes: - Fixed compiling dev extensions with big Tree-sitter grammars. - Fixed compiling dev extensions on Windows ARM64. - Added wasi-sdk installation invalidation. --------- Co-authored-by: Kirill Bulatov <kirill@zed.dev>
This commit is contained in:
parent
e9d2934edf
commit
a3e93fffaa
2 changed files with 261 additions and 226 deletions
|
|
@ -2,7 +2,7 @@ use crate::{
|
|||
ExtensionLibraryKind, ExtensionManifest, GrammarManifestEntry, parse_wasm_extension_version,
|
||||
};
|
||||
use ::fs::Fs;
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use anyhow::{Context as _, Result};
|
||||
use futures::{
|
||||
FutureExt, StreamExt,
|
||||
channel::oneshot::{self, Sender},
|
||||
|
|
@ -28,20 +28,19 @@ use wasmparser::Parser;
|
|||
/// Currently, we compile with Rust's `wasm32-wasip2` target, which works with WASI `preview2` and the component model.
|
||||
const RUST_TARGET: &str = "wasm32-wasip2";
|
||||
|
||||
/// Compiling Tree-sitter parsers from C to WASM requires Clang 17, and a WASM build of libc
|
||||
/// and clang's runtime library. The `wasi-sdk` provides these binaries.
|
||||
///
|
||||
/// Once Clang 17 and its wasm target are available via system package managers, we won't need
|
||||
/// to download this.
|
||||
const WASI_SDK_URL: &str = "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/";
|
||||
const WASI_SDK_ASSET_NAME: Option<&str> = cfg_select! {
|
||||
all(target_os = "macos", target_arch = "x86_64") => Some("wasi-sdk-25.0-x86_64-macos.tar.gz"),
|
||||
all(target_os = "macos", target_arch = "aarch64") => Some("wasi-sdk-25.0-arm64-macos.tar.gz"),
|
||||
all(target_os = "linux", target_arch = "x86_64") => Some("wasi-sdk-25.0-x86_64-linux.tar.gz"),
|
||||
all(target_os = "linux", target_arch = "aarch64") => Some("wasi-sdk-25.0-arm64-linux.tar.gz"),
|
||||
all(target_os = "freebsd", target_arch = "x86_64") => Some("wasi-sdk-25.0-x86_64-linux.tar.gz"),
|
||||
all(target_os = "freebsd", target_arch = "aarch64") => Some("wasi-sdk-25.0-arm64-linux.tar.gz"),
|
||||
all(target_os = "windows", target_arch = "x86_64") => Some("wasi-sdk-25.0-x86_64-windows.tar.gz"),
|
||||
/// Compiling Tree-sitter parsers from C to WebAssembly requires a compiler,
|
||||
/// a WASI sysroot, and compiler runtime libraries, provided by `wasi-sdk`.
|
||||
const WASI_SDK_VERSION: &str = "34";
|
||||
const WASI_SDK_URL: &str = "https://github.com/WebAssembly/wasi-sdk/releases/download/";
|
||||
const WASI_SDK_PLATFORM: Option<&str> = cfg_select! {
|
||||
all(target_os = "macos", target_arch = "x86_64") => Some("x86_64-macos"),
|
||||
all(target_os = "macos", target_arch = "aarch64") => Some("arm64-macos"),
|
||||
all(target_os = "linux", target_arch = "x86_64") => Some("x86_64-linux"),
|
||||
all(target_os = "linux", target_arch = "aarch64") => Some("arm64-linux"),
|
||||
all(target_os = "freebsd", target_arch = "x86_64") => Some("x86_64-linux"),
|
||||
all(target_os = "freebsd", target_arch = "aarch64") => Some("arm64-linux"),
|
||||
all(target_os = "windows", target_arch = "x86_64") => Some("x86_64-windows"),
|
||||
all(target_os = "windows", target_arch = "aarch64") => Some("arm64-windows"),
|
||||
_ => None
|
||||
};
|
||||
|
||||
|
|
@ -100,16 +99,14 @@ impl ExtensionBuilder {
|
|||
|
||||
populate_defaults(extension_manifest, extension_dir, fs.clone()).await?;
|
||||
|
||||
if extension_dir.is_relative() {
|
||||
bail!(
|
||||
"extension dir {} is not an absolute path",
|
||||
extension_dir.display()
|
||||
);
|
||||
}
|
||||
anyhow::ensure!(
|
||||
extension_dir.is_absolute(),
|
||||
"extension dir {extension_dir:?} is not an absolute path"
|
||||
);
|
||||
|
||||
fs.create_dir(&self.cache_dir)
|
||||
.await
|
||||
.context("failed to create cache dir")?;
|
||||
.context("creating cache directory")?;
|
||||
|
||||
let (tx, mut rx) = oneshot::channel();
|
||||
|
||||
|
|
@ -129,12 +126,12 @@ impl ExtensionBuilder {
|
|||
let rust_compilation_task =
|
||||
(extension_manifest.lib.kind == Some(ExtensionLibraryKind::Rust)).then(|| {
|
||||
async {
|
||||
log::info!("compiling Rust extension {}", extension_dir.display());
|
||||
log::info!("compiling Rust extension {extension_dir:?}");
|
||||
self.compile_rust_extension(extension_dir, extension_manifest, tx, &options)
|
||||
.await
|
||||
.context("failed to compile Rust extension")?;
|
||||
.context("compiling Rust extension")?;
|
||||
|
||||
log::info!("compiled Rust extension {}", extension_dir.display());
|
||||
log::info!("compiled Rust extension {extension_dir:?}");
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
|
|
@ -147,21 +144,15 @@ impl ExtensionBuilder {
|
|||
.map(|((grammar_name, grammar_metadata), clang_path_task)| {
|
||||
async move {
|
||||
let snake_cased_grammar_name = grammar_name.to_snake_case();
|
||||
if grammar_name.as_ref() != snake_cased_grammar_name.as_str() {
|
||||
bail!(
|
||||
"grammar name '{grammar_name}' must be \
|
||||
written in snake_case: {snake_cased_grammar_name}"
|
||||
);
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"compiling grammar {grammar_name} for extension {}",
|
||||
extension_dir.display()
|
||||
anyhow::ensure!(
|
||||
grammar_name.as_ref() == snake_cased_grammar_name.as_str(),
|
||||
"grammar name '{grammar_name}' must be \
|
||||
written in snake_case: {snake_cased_grammar_name}"
|
||||
);
|
||||
|
||||
let clang_path = clang_path_task
|
||||
.await
|
||||
.context("Failed to resolve clang path")?;
|
||||
log::info!("compiling grammar {grammar_name} for extension {extension_dir:?}");
|
||||
|
||||
let clang_path = clang_path_task.await.context("resolving clang path")?;
|
||||
|
||||
self.compile_grammar(
|
||||
extension_dir,
|
||||
|
|
@ -170,11 +161,8 @@ impl ExtensionBuilder {
|
|||
&clang_path,
|
||||
)
|
||||
.await
|
||||
.with_context(|| format!("failed to compile grammar '{grammar_name}'"))?;
|
||||
log::info!(
|
||||
"compiled grammar {grammar_name} for extension {}",
|
||||
extension_dir.display()
|
||||
);
|
||||
.with_context(|| format!("compiling grammar '{grammar_name}'"))?;
|
||||
log::info!("compiled grammar {grammar_name} for extension {extension_dir:?}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -204,8 +192,7 @@ impl ExtensionBuilder {
|
|||
}
|
||||
|
||||
log::info!(
|
||||
"finished compiling extension {} in {time:.2}s",
|
||||
extension_dir.display(),
|
||||
"finished compiling extension {extension_dir:?} in {time:.2}s",
|
||||
time = start.elapsed().as_secs_f64(),
|
||||
);
|
||||
Ok(())
|
||||
|
|
@ -223,10 +210,7 @@ impl ExtensionBuilder {
|
|||
let cargo_toml_content = fs::read_to_string(extension_dir.join("Cargo.toml"))?;
|
||||
let cargo_toml: CargoToml = toml::from_str(&cargo_toml_content)?;
|
||||
|
||||
log::info!(
|
||||
"compiling Rust crate for extension {}",
|
||||
extension_dir.display()
|
||||
);
|
||||
log::info!("compiling Rust crate for extension {extension_dir:?}");
|
||||
let output = util::command::new_command("cargo")
|
||||
.args(["build", "--target", RUST_TARGET])
|
||||
.args(options.release.then_some("--release"))
|
||||
|
|
@ -237,19 +221,15 @@ impl ExtensionBuilder {
|
|||
.current_dir(extension_dir)
|
||||
.output()
|
||||
.await
|
||||
.context("failed to run `cargo`")?;
|
||||
if !output.status.success() {
|
||||
bail!(
|
||||
"failed to build extension {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"compiled Rust crate for extension {}",
|
||||
extension_dir.display()
|
||||
.context("running `cargo`")?;
|
||||
anyhow::ensure!(
|
||||
output.status.success(),
|
||||
"failed to build extension {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
log::info!("compiled Rust crate for extension {extension_dir:?}");
|
||||
|
||||
let mut wasm_path = PathBuf::from(extension_dir);
|
||||
wasm_path.extend([
|
||||
"target",
|
||||
|
|
@ -263,17 +243,14 @@ impl ExtensionBuilder {
|
|||
]);
|
||||
wasm_path.set_extension("wasm");
|
||||
|
||||
log::info!(
|
||||
"encoding wasm component for extension {}",
|
||||
extension_dir.display()
|
||||
);
|
||||
log::info!("encoding wasm component for extension {extension_dir:?}");
|
||||
|
||||
let component_bytes = fs::read(&wasm_path)
|
||||
.with_context(|| format!("failed to read output module `{}`", wasm_path.display()))?;
|
||||
let component_bytes =
|
||||
fs::read(&wasm_path).with_context(|| format!("reading output module {wasm_path:?}"))?;
|
||||
|
||||
let component_bytes = self
|
||||
.strip_custom_sections(&component_bytes)
|
||||
.context("failed to strip debug sections from wasm component")?;
|
||||
.context("stripping debug sections from wasm component")?;
|
||||
|
||||
let wasm_extension_api_version =
|
||||
parse_wasm_extension_version(&manifest.id, &component_bytes)
|
||||
|
|
@ -283,14 +260,9 @@ impl ExtensionBuilder {
|
|||
.map_err(|_| anyhow::anyhow!("Failed to send API version"))?;
|
||||
|
||||
let extension_file = extension_dir.join("extension.wasm");
|
||||
fs::write(extension_file.clone(), &component_bytes)
|
||||
.context("failed to write extension.wasm")?;
|
||||
fs::write(extension_file.clone(), &component_bytes).context("writing extension.wasm")?;
|
||||
|
||||
log::info!(
|
||||
"extension {} written to {}",
|
||||
extension_dir.display(),
|
||||
extension_file.display()
|
||||
);
|
||||
log::info!("extension {extension_dir:?} written to {extension_file:?}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -345,15 +317,13 @@ impl ExtensionBuilder {
|
|||
.args(scanner_path.exists().then_some(scanner_path))
|
||||
.output()
|
||||
.await
|
||||
.context("failed to run clang")?;
|
||||
.context("running clang")?;
|
||||
|
||||
if !clang_output.status.success() {
|
||||
bail!(
|
||||
"failed to compile {} parser with clang: {}",
|
||||
grammar_name,
|
||||
String::from_utf8_lossy(&clang_output.stderr),
|
||||
);
|
||||
}
|
||||
anyhow::ensure!(
|
||||
clang_output.status.success(),
|
||||
"failed to compile {grammar_name} parser with clang: {}",
|
||||
String::from_utf8_lossy(&clang_output.stderr),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -372,28 +342,22 @@ impl ExtensionBuilder {
|
|||
.await?;
|
||||
let has_remote = remotes_output.status.success()
|
||||
&& String::from_utf8_lossy(&remotes_output.stdout).trim() == url;
|
||||
if !has_remote {
|
||||
bail!(
|
||||
"grammar directory '{}' already exists, but is not a git clone of '{}'",
|
||||
directory.display(),
|
||||
url
|
||||
);
|
||||
}
|
||||
anyhow::ensure!(
|
||||
has_remote,
|
||||
"grammar directory {directory:?} already exists, but is not a git clone of '{url}'"
|
||||
);
|
||||
} else {
|
||||
fs::create_dir_all(directory).with_context(|| {
|
||||
format!("failed to create grammar directory {}", directory.display(),)
|
||||
})?;
|
||||
fs::create_dir_all(directory)
|
||||
.with_context(|| format!("creating grammar directory {directory:?}"))?;
|
||||
let init_output = util::command::new_command("git")
|
||||
.arg("init")
|
||||
.current_dir(directory)
|
||||
.output()
|
||||
.await?;
|
||||
if !init_output.status.success() {
|
||||
bail!(
|
||||
"failed to run `git init` in directory '{}'",
|
||||
directory.display()
|
||||
);
|
||||
}
|
||||
anyhow::ensure!(
|
||||
init_output.status.success(),
|
||||
"failed to run `git init` in directory {directory:?}"
|
||||
);
|
||||
|
||||
let remote_add_output = util::command::new_command("git")
|
||||
.arg("--git-dir")
|
||||
|
|
@ -401,13 +365,11 @@ impl ExtensionBuilder {
|
|||
.args(["remote", "add", "origin", url])
|
||||
.output()
|
||||
.await
|
||||
.context("failed to execute `git remote add`")?;
|
||||
if !remote_add_output.status.success() {
|
||||
bail!(
|
||||
"failed to add remote {url} for git repository {}",
|
||||
git_dir.display()
|
||||
);
|
||||
}
|
||||
.context("executing `git remote add`")?;
|
||||
anyhow::ensure!(
|
||||
remote_add_output.status.success(),
|
||||
"failed to add remote {url} for git repository {git_dir:?}"
|
||||
);
|
||||
}
|
||||
|
||||
let fetch_output = util::command::new_command("git")
|
||||
|
|
@ -416,7 +378,7 @@ impl ExtensionBuilder {
|
|||
.args(["fetch", "--depth", "1", "origin", rev])
|
||||
.output()
|
||||
.await
|
||||
.context("failed to execute `git fetch`")?;
|
||||
.context("executing `git fetch`")?;
|
||||
|
||||
let checkout_output = util::command::new_command("git")
|
||||
.arg("--git-dir")
|
||||
|
|
@ -425,19 +387,14 @@ impl ExtensionBuilder {
|
|||
.current_dir(directory)
|
||||
.output()
|
||||
.await
|
||||
.context("failed to execute `git checkout`")?;
|
||||
.context("executing `git checkout`")?;
|
||||
if !checkout_output.status.success() {
|
||||
if !fetch_output.status.success() {
|
||||
bail!(
|
||||
"failed to fetch revision {} in directory '{}'",
|
||||
rev,
|
||||
directory.display()
|
||||
);
|
||||
}
|
||||
bail!(
|
||||
"failed to checkout revision {} in directory '{}': {}",
|
||||
rev,
|
||||
directory.display(),
|
||||
anyhow::ensure!(
|
||||
fetch_output.status.success(),
|
||||
"failed to fetch revision {rev} in directory {directory:?}"
|
||||
);
|
||||
anyhow::bail!(
|
||||
"failed to checkout revision {rev} in directory {directory:?}: {}",
|
||||
String::from_utf8_lossy(&checkout_output.stderr)
|
||||
);
|
||||
}
|
||||
|
|
@ -450,27 +407,25 @@ impl ExtensionBuilder {
|
|||
.args(["--print", "target-libdir", "--target", RUST_TARGET])
|
||||
.output()
|
||||
.await
|
||||
.context("failed to run rustc")?;
|
||||
if !rustc_output.status.success() {
|
||||
bail!(
|
||||
"failed to retrieve the `{RUST_TARGET}` target libdir: {}",
|
||||
String::from_utf8_lossy(&rustc_output.stderr)
|
||||
);
|
||||
}
|
||||
.context("running rustc")?;
|
||||
anyhow::ensure!(
|
||||
rustc_output.status.success(),
|
||||
"failed to retrieve the `{RUST_TARGET}` target libdir: {}",
|
||||
String::from_utf8_lossy(&rustc_output.stderr)
|
||||
);
|
||||
|
||||
let target_libdir = PathBuf::from(String::from_utf8(rustc_output.stdout)?.trim());
|
||||
if target_libdir.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if which::which("rustup").is_err() {
|
||||
bail!(
|
||||
"the `{RUST_TARGET}` target is not installed, and `rustup` is not available to \
|
||||
install it. Add the target to your Rust toolchain (e.g. `targets = \
|
||||
[\"{RUST_TARGET}\"]` for Nix rust-overlay/fenix toolchains) or install it via \
|
||||
your package manager"
|
||||
);
|
||||
}
|
||||
anyhow::ensure!(
|
||||
which::which("rustup").is_ok(),
|
||||
"the `{RUST_TARGET}` target is not installed, and `rustup` is not available to \
|
||||
install it. Add the target to your Rust toolchain (e.g. `targets = \
|
||||
[\"{RUST_TARGET}\"]` for Nix rust-overlay/fenix toolchains) or install it via \
|
||||
your package manager"
|
||||
);
|
||||
|
||||
let output = util::command::new_command("rustup")
|
||||
.args(["target", "add", RUST_TARGET])
|
||||
|
|
@ -478,13 +433,12 @@ impl ExtensionBuilder {
|
|||
.stdout(Stdio::inherit())
|
||||
.output()
|
||||
.await
|
||||
.context("failed to run `rustup target add`")?;
|
||||
if !output.status.success() {
|
||||
bail!(
|
||||
"failed to install the `{RUST_TARGET}` target: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
.context("running `rustup target add`")?;
|
||||
anyhow::ensure!(
|
||||
output.status.success(),
|
||||
"failed to install the `{RUST_TARGET}` target: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -502,42 +456,69 @@ impl ExtensionBuilder {
|
|||
);
|
||||
}
|
||||
|
||||
let url = if let Some(asset_name) = WASI_SDK_ASSET_NAME {
|
||||
format!("{WASI_SDK_URL}{asset_name}")
|
||||
} else {
|
||||
bail!("wasi-sdk is not available for platform {}", env::consts::OS);
|
||||
};
|
||||
|
||||
let wasi_sdk_dir = self.cache_dir.join("wasi-sdk");
|
||||
let clang_path = wasi_sdk_clang_path(&wasi_sdk_dir);
|
||||
|
||||
log::info!("downloading wasi-sdk to {}", wasi_sdk_dir.display());
|
||||
|
||||
if fs::metadata(&clang_path).is_ok_and(|metadata| metadata.is_file()) {
|
||||
return Ok(clang_path);
|
||||
match self
|
||||
.update_wasi_sdk_if_needed(&wasi_sdk_dir, &clang_path)
|
||||
.await
|
||||
{
|
||||
Ok(()) => Ok(clang_path),
|
||||
Err(error) => {
|
||||
if fs::metadata(&clang_path).is_ok_and(|metadata| metadata.is_file()) {
|
||||
let installed_version = installed_wasi_sdk_version(&wasi_sdk_dir)
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
log::warn!(
|
||||
"failed to install wasi-sdk {WASI_SDK_VERSION}.0, using cached wasi-sdk {installed_version} from {wasi_sdk_dir:?}: {error:#}"
|
||||
);
|
||||
Ok(clang_path)
|
||||
} else {
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_wasi_sdk_if_needed(
|
||||
&self,
|
||||
wasi_sdk_dir: &Path,
|
||||
clang_path: &Path,
|
||||
) -> Result<()> {
|
||||
if installed_wasi_sdk_version(wasi_sdk_dir) == Some(format!("{WASI_SDK_VERSION}.0"))
|
||||
&& fs::metadata(clang_path).is_ok_and(|metadata| metadata.is_file())
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let platform = WASI_SDK_PLATFORM.with_context(|| {
|
||||
format!("wasi-sdk is not available for platform {}", env::consts::OS)
|
||||
})?;
|
||||
let url = format!(
|
||||
"{WASI_SDK_URL}wasi-sdk-{WASI_SDK_VERSION}/wasi-sdk-{WASI_SDK_VERSION}.0-{platform}.tar.gz"
|
||||
);
|
||||
|
||||
log::info!("downloading wasi-sdk to {wasi_sdk_dir:?}");
|
||||
|
||||
let tar_out_dir = self.cache_dir.join("wasi-sdk-temp");
|
||||
|
||||
fs::remove_dir_all(&wasi_sdk_dir).ok();
|
||||
fs::remove_dir_all(&tar_out_dir).ok();
|
||||
fs::create_dir_all(&tar_out_dir).context("failed to create extraction directory")?;
|
||||
fs::create_dir_all(&tar_out_dir).context("creating extraction directory")?;
|
||||
|
||||
let mut response = self.http.get(&url, AsyncBody::default(), true).await?;
|
||||
|
||||
// Write the response to a temporary file
|
||||
let tar_gz_path = self.cache_dir.join("wasi-sdk.tar.gz");
|
||||
let tar_gz_file =
|
||||
fs::File::create(&tar_gz_path).context("failed to create temporary tar.gz file")?;
|
||||
fs::File::create(&tar_gz_path).context("creating temporary tar.gz file")?;
|
||||
let response_body = response.body_mut();
|
||||
|
||||
let mut async_file = io::AllowStdIo::new(tar_gz_file);
|
||||
io::copy(response_body, &mut async_file)
|
||||
.await
|
||||
.context("failed to stream response to file")?;
|
||||
.context("streaming response to file")?;
|
||||
drop(async_file);
|
||||
|
||||
log::info!("un-tarring wasi-sdk to {}", tar_out_dir.display());
|
||||
log::info!("un-tarring wasi-sdk to {tar_out_dir:?}");
|
||||
|
||||
// Shell out to tar to extract the archive
|
||||
let tar_output = util::command::new_command("tar")
|
||||
|
|
@ -547,14 +528,13 @@ impl ExtensionBuilder {
|
|||
.arg(&tar_out_dir)
|
||||
.output()
|
||||
.await
|
||||
.context("failed to run tar")?;
|
||||
.context("running tar")?;
|
||||
|
||||
if !tar_output.status.success() {
|
||||
bail!(
|
||||
"failed to extract wasi-sdk archive: {}",
|
||||
String::from_utf8_lossy(&tar_output.stderr)
|
||||
);
|
||||
}
|
||||
anyhow::ensure!(
|
||||
tar_output.status.success(),
|
||||
"failed to extract wasi-sdk archive: {}",
|
||||
String::from_utf8_lossy(&tar_output.stderr)
|
||||
);
|
||||
|
||||
log::info!("finished downloading wasi-sdk");
|
||||
|
||||
|
|
@ -564,12 +544,17 @@ impl ExtensionBuilder {
|
|||
let inner_dir = fs::read_dir(&tar_out_dir)?
|
||||
.next()
|
||||
.context("no content")?
|
||||
.context("failed to read contents of extracted wasi archive directory")?
|
||||
.context("reading contents of extracted wasi archive directory")?
|
||||
.path();
|
||||
fs::rename(&inner_dir, &wasi_sdk_dir).context("failed to move extracted wasi dir")?;
|
||||
match fs::remove_dir_all(wasi_sdk_dir) {
|
||||
Ok(()) => {}
|
||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
|
||||
Err(error) => return Err(error).context("removing outdated wasi-sdk"),
|
||||
}
|
||||
fs::rename(&inner_dir, wasi_sdk_dir).context("moving extracted wasi directory")?;
|
||||
fs::remove_dir_all(&tar_out_dir).ok();
|
||||
|
||||
Ok(clang_path)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// This was adapted from:
|
||||
|
|
@ -665,7 +650,7 @@ async fn populate_defaults(
|
|||
let mut language_dir_entries = fs
|
||||
.read_dir(&languages_dir)
|
||||
.await
|
||||
.context("failed to list languages dir")?;
|
||||
.context("listing languages directory")?;
|
||||
|
||||
while let Some(language_dir) = language_dir_entries.next().await {
|
||||
let language_dir = language_dir?;
|
||||
|
|
@ -686,7 +671,7 @@ async fn populate_defaults(
|
|||
let mut theme_dir_entries = fs
|
||||
.read_dir(&themes_dir)
|
||||
.await
|
||||
.context("failed to list themes dir")?;
|
||||
.context("listing themes directory")?;
|
||||
|
||||
while let Some(theme_path) = theme_dir_entries.next().await {
|
||||
let theme_path = theme_path?;
|
||||
|
|
@ -705,7 +690,7 @@ async fn populate_defaults(
|
|||
let mut icon_theme_dir_entries = fs
|
||||
.read_dir(&icon_themes_dir)
|
||||
.await
|
||||
.context("failed to list icon themes dir")?;
|
||||
.context("listing icon themes directory")?;
|
||||
|
||||
while let Some(icon_theme_path) = icon_theme_dir_entries.next().await {
|
||||
let icon_theme_path = icon_theme_path?;
|
||||
|
|
@ -734,7 +719,7 @@ async fn populate_defaults(
|
|||
let mut grammar_dir_entries = fs
|
||||
.read_dir(&grammars_dir)
|
||||
.await
|
||||
.context("failed to list grammars dir")?;
|
||||
.context("listing grammars directory")?;
|
||||
|
||||
while let Some(grammar_path) = grammar_dir_entries.next().await {
|
||||
let grammar_path = grammar_path?;
|
||||
|
|
@ -801,6 +786,20 @@ fn wasi_sdk_clang_path(wasi_sdk_dir: &Path) -> PathBuf {
|
|||
clang_path
|
||||
}
|
||||
|
||||
fn installed_wasi_sdk_version(wasi_sdk_dir: &Path) -> Option<String> {
|
||||
let version_path = wasi_sdk_dir.join("VERSION");
|
||||
let contents = match fs::read_to_string(&version_path) {
|
||||
Ok(contents) => contents,
|
||||
Err(error) => {
|
||||
if error.kind() != std::io::ErrorKind::NotFound {
|
||||
log::warn!("failed to read wasi-sdk version from {version_path:?}: {error}");
|
||||
}
|
||||
return None;
|
||||
}
|
||||
};
|
||||
contents.lines().next().map(str::to_string)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{
|
||||
|
|
@ -815,7 +814,7 @@ mod tests {
|
|||
|
||||
use crate::{
|
||||
ExtensionManifest, ExtensionSnippets,
|
||||
extension_builder::{file_newer_than_deps, populate_defaults},
|
||||
extension_builder::{file_newer_than_deps, installed_wasi_sdk_version, populate_defaults},
|
||||
};
|
||||
|
||||
#[test]
|
||||
|
|
@ -856,6 +855,29 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_installed_wasi_sdk_version() {
|
||||
let tmpdir = tempfile::tempdir().unwrap();
|
||||
let version_path = tmpdir.path().join("VERSION");
|
||||
|
||||
assert_eq!(installed_wasi_sdk_version(tmpdir.path()), None);
|
||||
|
||||
std::fs::write(&version_path, "").unwrap();
|
||||
assert_eq!(installed_wasi_sdk_version(tmpdir.path()), None);
|
||||
|
||||
std::fs::write(&version_path, "25.0\nwasi-libc: 574b88da4815\n").unwrap();
|
||||
assert_eq!(
|
||||
installed_wasi_sdk_version(tmpdir.path()),
|
||||
Some("25.0".to_string())
|
||||
);
|
||||
|
||||
std::fs::write(&version_path, "34.0\r\nwasi-libc: 2e6fb9d8ee0c\r\n").unwrap();
|
||||
assert_eq!(
|
||||
installed_wasi_sdk_version(tmpdir.path()),
|
||||
Some("34.0".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_snippet_location_is_kept(cx: &mut TestAppContext) {
|
||||
let fs = fs::FakeFs::new(cx.executor());
|
||||
|
|
|
|||
|
|
@ -1,61 +1,74 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Check if ./target/wasi-sdk exists
|
||||
if [ ! -d "./target/wasi-sdk" ]; then
|
||||
echo "WASI SDK not found, downloading v25..."
|
||||
WASI_SDK_VERSION="34"
|
||||
WASI_SDK_DIR="./target/wasi-sdk"
|
||||
|
||||
# Determine OS and architecture
|
||||
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
|
||||
ARCH=$(uname -m)
|
||||
|
||||
# Map architecture names to WASI SDK format
|
||||
case $ARCH in
|
||||
x86_64)
|
||||
ARCH="x86_64"
|
||||
;;
|
||||
arm64|aarch64)
|
||||
ARCH="arm64"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported architecture: $ARCH"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Map OS names to WASI SDK format
|
||||
case $OS in
|
||||
darwin)
|
||||
OS="macos"
|
||||
;;
|
||||
linux)
|
||||
OS="linux"
|
||||
;;
|
||||
mingw*|msys*|cygwin*)
|
||||
OS="mingw"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported OS: $OS"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Construct download URL
|
||||
WASI_SDK_VERSION="25"
|
||||
WASI_SDK_URL="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_VERSION}/wasi-sdk-${WASI_SDK_VERSION}.0-${ARCH}-${OS}.tar.gz"
|
||||
|
||||
echo "Downloading from: $WASI_SDK_URL"
|
||||
|
||||
# Create target directory if it doesn't exist
|
||||
mkdir -p ./target
|
||||
|
||||
# Download and extract
|
||||
curl -fL "$WASI_SDK_URL" | tar -xz -C ./target
|
||||
|
||||
# Rename the extracted directory to wasi-sdk
|
||||
mv "./target/wasi-sdk-${WASI_SDK_VERSION}.0-${ARCH}-${OS}" "./target/wasi-sdk"
|
||||
|
||||
echo "WASI SDK v25 installed successfully"
|
||||
else
|
||||
echo "WASI SDK already exists at ./target/wasi-sdk"
|
||||
installed_version=""
|
||||
if [ -f "${WASI_SDK_DIR}/VERSION" ]; then
|
||||
installed_version=$(head -n 1 "${WASI_SDK_DIR}/VERSION" | tr -d '\r')
|
||||
fi
|
||||
|
||||
if [ "${installed_version}" = "${WASI_SDK_VERSION}.0" ]; then
|
||||
echo "WASI SDK v${WASI_SDK_VERSION} already exists at ${WASI_SDK_DIR}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -d "${WASI_SDK_DIR}" ]; then
|
||||
echo "WASI SDK at ${WASI_SDK_DIR} is version '${installed_version}', replacing with v${WASI_SDK_VERSION}..."
|
||||
else
|
||||
echo "WASI SDK not found, downloading v${WASI_SDK_VERSION}..."
|
||||
fi
|
||||
|
||||
# Determine OS and architecture
|
||||
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
|
||||
ARCH=$(uname -m)
|
||||
|
||||
# Map architecture names to WASI SDK format
|
||||
case $ARCH in
|
||||
x86_64)
|
||||
ARCH="x86_64"
|
||||
;;
|
||||
arm64|aarch64)
|
||||
ARCH="arm64"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported architecture: $ARCH"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Map OS names to WASI SDK format
|
||||
case $OS in
|
||||
darwin)
|
||||
OS="macos"
|
||||
;;
|
||||
linux)
|
||||
OS="linux"
|
||||
;;
|
||||
mingw*|msys*|cygwin*)
|
||||
OS="windows"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported OS: $OS"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Construct download URL
|
||||
WASI_SDK_URL="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_VERSION}/wasi-sdk-${WASI_SDK_VERSION}.0-${ARCH}-${OS}.tar.gz"
|
||||
|
||||
echo "Downloading from: $WASI_SDK_URL"
|
||||
|
||||
# Create target directory if it doesn't exist
|
||||
mkdir -p ./target
|
||||
|
||||
# Download and extract
|
||||
rm -rf "./target/wasi-sdk-${WASI_SDK_VERSION}.0-${ARCH}-${OS}"
|
||||
curl -fL "$WASI_SDK_URL" | tar -xz -C ./target
|
||||
|
||||
# Rename the extracted directory to wasi-sdk
|
||||
rm -rf "${WASI_SDK_DIR}"
|
||||
mv "./target/wasi-sdk-${WASI_SDK_VERSION}.0-${ARCH}-${OS}" "${WASI_SDK_DIR}"
|
||||
|
||||
echo "WASI SDK v${WASI_SDK_VERSION} installed successfully"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue