Skip to content

Commit ca7ff03

Browse files
authored
Fix wasm build issues introduced by switching to wasi-sdk (tree-sitter#4407)
* Don't shell out for extracting tar.gz files * Avoid wasi-sdk adding dependency on libc.so * Clippy * Fix -nostdlib flag
1 parent d4d8ed3 commit ca7ff03

File tree

7 files changed

+93
-79
lines changed

7 files changed

+93
-79
lines changed

Cargo.lock

Lines changed: 27 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ ctrlc = { version = "3.4.6", features = ["termination"] }
116116
dialoguer = { version = "0.11.0", features = ["fuzzy-select"] }
117117
etcetera = "0.8.0"
118118
filetime = "0.2.25"
119+
flate2 = "1.0.28"
119120
fs4 = "0.12.0"
120121
git2 = "0.20.1"
121122
glob = "0.3.2"
@@ -140,6 +141,7 @@ serde_json = { version = "1.0.140", features = ["preserve_order"] }
140141
similar = "2.7.0"
141142
smallbitvec = "2.6.0"
142143
streaming-iterator = "0.1.9"
144+
tar = "0.4.40"
143145
tempfile = "3.19.1"
144146
thiserror = "2.0.12"
145147
tiny_http = "0.12.0"

cli/loader/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ default = ["tree-sitter-highlight", "tree-sitter-tags"]
2727
anyhow.workspace = true
2828
cc.workspace = true
2929
etcetera.workspace = true
30+
flate2.workspace = true
3031
fs4.workspace = true
3132
indoc.workspace = true
3233
libloading.workspace = true
@@ -36,6 +37,7 @@ regex.workspace = true
3637
semver.workspace = true
3738
serde.workspace = true
3839
serde_json.workspace = true
40+
tar.workspace = true
3941
tempfile.workspace = true
4042
url.workspace = true
4143
ureq = "3.0.11"

cli/loader/src/lib.rs

Lines changed: 62 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::sync::Mutex;
88
use std::{
99
collections::HashMap,
1010
env, fs,
11-
io::{BufRead, BufReader, Write},
11+
io::{BufRead, BufReader, Write as _},
1212
mem,
1313
path::{Path, PathBuf},
1414
process::Command,
@@ -20,6 +20,7 @@ use std::{
2020
use anyhow::Error;
2121
use anyhow::{anyhow, Context, Result};
2222
use etcetera::BaseStrategy as _;
23+
use flate2::read::GzDecoder;
2324
use fs4::fs_std::FileExt;
2425
use indoc::indoc;
2526
use libloading::{Library, Symbol};
@@ -976,31 +977,34 @@ impl Loader {
976977

977978
let output_name = "output.wasm";
978979
let mut command = Command::new(&clang_executable);
979-
command.current_dir(src_path);
980-
command.args([
980+
command.current_dir(src_path).args([
981981
"-o",
982982
output_name,
983983
"-fPIC",
984984
"-shared",
985985
"-Os",
986986
format!("-Wl,--export=tree_sitter_{language_name}").as_str(),
987+
"-Wl,--allow-undefined",
988+
"-Wl,--no-entry",
989+
"-nostdlib",
987990
"-fno-exceptions",
988991
"-fvisibility=hidden",
989992
"-I",
990993
".",
994+
"parser.c",
991995
]);
992996

993997
if let Some(scanner_filename) = scanner_filename {
994998
command.arg(scanner_filename);
995999
}
9961000

997-
command.arg("parser.c");
998-
let output = command
999-
.output()
1000-
.with_context(|| format!("Failed to run wasi-sdk clang command: {:?}", command))?;
1001+
let output = command.output().context("Failed to run wasi-sdk clang")?;
10011002

10021003
if !output.status.success() {
1003-
return Err(anyhow!("wasi-sdk clang command failed"));
1004+
return Err(anyhow!(
1005+
"wasi-sdk clang command failed: {}",
1006+
String::from_utf8_lossy(&output.stderr)
1007+
));
10041008
}
10051009

10061010
fs::rename(src_path.join(output_name), output_path)
@@ -1009,50 +1013,81 @@ impl Loader {
10091013
Ok(())
10101014
}
10111015

1016+
/// Extracts a tar.gz archive, stripping the first path component.
1017+
///
1018+
/// Similar to `tar -xzf <archive> --strip-components=1`
1019+
fn extract_tar_gz_with_strip(
1020+
&self,
1021+
archive_path: &Path,
1022+
destination: &Path,
1023+
) -> Result<(), Error> {
1024+
let archive_file = fs::File::open(archive_path).context("Failed to open archive")?;
1025+
let mut archive = tar::Archive::new(GzDecoder::new(archive_file));
1026+
for entry in archive
1027+
.entries()
1028+
.with_context(|| "Failed to read archive entries")?
1029+
{
1030+
let mut entry = entry?;
1031+
let path = entry.path()?;
1032+
let Some(first_component) = path.components().next() else {
1033+
continue;
1034+
};
1035+
let dest_path = destination.join(path.strip_prefix(first_component).unwrap());
1036+
if let Some(parent) = dest_path.parent() {
1037+
fs::create_dir_all(parent).with_context(|| {
1038+
format!("Failed to create directory at {}", parent.display())
1039+
})?;
1040+
}
1041+
entry
1042+
.unpack(&dest_path)
1043+
.with_context(|| format!("Failed to extract file to {}", dest_path.display()))?;
1044+
}
1045+
Ok(())
1046+
}
1047+
10121048
fn ensure_wasi_sdk_exists(&self) -> Result<PathBuf, Error> {
10131049
let cache_dir = etcetera::choose_base_strategy()?
10141050
.cache_dir()
10151051
.join("tree-sitter");
1016-
if !cache_dir.exists() {
1017-
fs::create_dir_all(&cache_dir)?;
1018-
}
1052+
fs::create_dir_all(&cache_dir)?;
10191053

10201054
let wasi_sdk_dir = cache_dir.join("wasi-sdk");
10211055
let clang_exe = if cfg!(windows) {
10221056
wasi_sdk_dir.join("bin").join("clang.exe")
10231057
} else {
10241058
wasi_sdk_dir.join("bin").join("clang")
10251059
};
1060+
10261061
if clang_exe.exists() {
10271062
return Ok(clang_exe);
10281063
}
10291064

1030-
if !wasi_sdk_dir.exists() {
1031-
fs::create_dir_all(&wasi_sdk_dir)?;
1032-
}
1065+
fs::create_dir_all(&wasi_sdk_dir)?;
10331066

1034-
let sdk_filename = if cfg!(target_os = "macos") {
1067+
let arch_os = if cfg!(target_os = "macos") {
10351068
if cfg!(target_arch = "aarch64") {
1036-
"wasi-sdk-25.0-arm64-macos.tar.gz"
1069+
"arm64-macos"
10371070
} else {
1038-
"wasi-sdk-25.0-x86_64-macos.tar.gz"
1071+
"x86_64-macos"
10391072
}
10401073
} else if cfg!(target_os = "windows") {
1041-
"wasi-sdk-25.0-x86_64-windows.tar.gz"
1074+
"x86_64-windows"
10421075
} else if cfg!(target_os = "linux") {
10431076
if cfg!(target_arch = "aarch64") {
1044-
"wasi-sdk-25.0-arm64-linux.tar.gz"
1077+
"arm64-linux"
10451078
} else {
1046-
"wasi-sdk-25.0-x86_64-linux.tar.gz"
1079+
"x86_64-linux"
10471080
}
10481081
} else {
10491082
return Err(anyhow!("Unsupported platform for wasi-sdk"));
10501083
};
10511084

1052-
let base_url = "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25";
1053-
let sdk_url = format!("{}/{}", base_url, sdk_filename);
1054-
eprintln!("Downloading wasi-sdk from {}...", sdk_url);
1085+
let sdk_filename = format!("wasi-sdk-25.0-{arch_os}.tar.gz");
1086+
let sdk_url = format!(
1087+
"https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/{sdk_filename}",
1088+
);
10551089

1090+
eprintln!("Downloading wasi-sdk from {sdk_url}...");
10561091
let temp_tar_path = cache_dir.join(sdk_filename);
10571092
let mut temp_file = fs::File::create(&temp_tar_path).with_context(|| {
10581093
format!(
@@ -1063,7 +1098,7 @@ impl Loader {
10631098

10641099
let response = ureq::get(&sdk_url)
10651100
.call()
1066-
.with_context(|| format!("Failed to download wasi-sdk from {}", sdk_url))?;
1101+
.with_context(|| format!("Failed to download wasi-sdk from {sdk_url}"))?;
10671102
if !response.status().is_success() {
10681103
return Err(anyhow::anyhow!(
10691104
"Failed to download wasi-sdk from {}",
@@ -1077,50 +1112,13 @@ impl Loader {
10771112
.flush()
10781113
.context("Failed to flush downloaded file")?;
10791114
eprintln!("Extracting wasi-sdk to {}...", wasi_sdk_dir.display());
1080-
1081-
#[cfg(unix)]
1082-
{
1083-
let status = Command::new("tar")
1084-
.args([
1085-
"-xzf",
1086-
temp_tar_path.to_str().unwrap(),
1087-
"-C",
1088-
wasi_sdk_dir.to_str().unwrap(),
1089-
"--strip-components=1",
1090-
])
1091-
.status()
1092-
.context("Failed to extract wasi-sdk archive with tar")?;
1093-
1094-
if !status.success() {
1095-
return Err(anyhow!("Failed to extract wasi-sdk archive with tar"));
1096-
}
1097-
}
1098-
1099-
#[cfg(windows)]
1100-
{
1101-
// On Windows, use PowerShell to extract the tar.gz file directly
1102-
let ps_command = format!(
1103-
"cd '{}'; tar -xzf '{}' --strip-components=1",
1104-
wasi_sdk_dir.to_str().unwrap(),
1105-
temp_tar_path.to_str().unwrap()
1106-
);
1107-
1108-
let status = Command::new("powershell")
1109-
.args(["-Command", &ps_command])
1110-
.status()
1111-
.context("Failed to extract wasi-sdk archive with PowerShell")?;
1112-
1113-
if !status.success() {
1114-
return Err(anyhow!(
1115-
"Failed to extract wasi-sdk archive with PowerShell"
1116-
));
1117-
}
1118-
}
1115+
self.extract_tar_gz_with_strip(&temp_tar_path, &wasi_sdk_dir)
1116+
.context("Failed to extract wasi-sdk archive")?;
11191117

11201118
fs::remove_file(temp_tar_path).ok();
11211119
if !clang_exe.exists() {
11221120
return Err(anyhow!(
1123-
"Failed to extract wasi-sdk correctly. Clang executable not found at {}",
1121+
"Failed to extract wasi-sdk correctly. Clang executable not found at expected location: {}",
11241122
clang_exe.display()
11251123
));
11261124
}

docs/src/cli/build.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,6 @@ will attempt to build the parser in the current working directory.
2020

2121
Compile the parser as a WASM module.
2222

23-
### `-d/--docker`
24-
25-
Use Docker or Podman to supply Emscripten. This removes the need to install Emscripten on your machine locally.
26-
Note that this flag is only available when compiling to WASM.
27-
2823
### `-o/--output`
2924

3025
Specify where to output the shared object file (native or WASM). This flag accepts either an absolute path or a relative

xtask/src/generate.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,6 @@ pub fn run_fixtures(args: &GenerateFixtures) -> Result<()> {
4242
&format!("target/release/tree-sitter-{grammar_name}.wasm"),
4343
grammar_dir.to_str().unwrap(),
4444
]);
45-
if args.docker {
46-
cmd.arg("--docker");
47-
}
4845
bail_on_err(
4946
&cmd.spawn()?.wait_with_output()?,
5047
&format!("Failed to regenerate {grammar_name} parser to wasm"),

xtask/src/main.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,6 @@ struct GenerateFixtures {
123123
/// Generates the parser to WASM
124124
#[arg(long, short)]
125125
wasm: bool,
126-
/// Run emscripten via docker even if it is installed locally.
127-
#[arg(long, short, requires = "wasm")]
128-
docker: bool,
129126
}
130127

131128
#[derive(Args)]

0 commit comments

Comments
 (0)