@@ -8,7 +8,7 @@ use std::sync::Mutex;
88use 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::{
2020use anyhow:: Error ;
2121use anyhow:: { anyhow, Context , Result } ;
2222use etcetera:: BaseStrategy as _;
23+ use flate2:: read:: GzDecoder ;
2324use fs4:: fs_std:: FileExt ;
2425use indoc:: indoc;
2526use 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 }
0 commit comments