wasmer_capi_examples_runner/
lib.rs

1#[cfg(test)]
2use std::error::Error;
3
4#[cfg(test)]
5static INCLUDE_REGEX: &str = "#include \"(.*)\"";
6
7#[derive(Default)]
8pub struct RemoveTestsOnDrop {}
9
10impl Drop for RemoveTestsOnDrop {
11    fn drop(&mut self) {
12        let manifest_dir = env!("CARGO_MANIFEST_DIR");
13        for entry in std::fs::read_dir(manifest_dir).unwrap() {
14            let entry = entry.unwrap();
15            let path = entry.path();
16            let extension = path.extension().and_then(|s| s.to_str());
17            if extension == Some("obj") || extension == Some("exe") || extension == Some("o") {
18                println!("removing {}", path.display());
19                let _ = std::fs::remove_file(&path);
20            }
21        }
22        if let Some(parent) = std::path::Path::new(&manifest_dir).parent() {
23            for entry in std::fs::read_dir(parent).unwrap() {
24                let entry = entry.unwrap();
25                let path = entry.path();
26                let extension = path.extension().and_then(|s| s.to_str());
27                if extension == Some("obj") || extension == Some("exe") || extension == Some("o") {
28                    println!("removing {}", path.display());
29                    let _ = std::fs::remove_file(path);
30                }
31            }
32        }
33    }
34}
35
36fn make_package() {
37    let wasmer_root_dir = find_wasmer_base_dir();
38    let _ = std::fs::create_dir_all(format!("{wasmer_root_dir}/package/lib"));
39    let _ = std::fs::create_dir_all(format!("{wasmer_root_dir}/package/include"));
40    let _ = std::fs::copy(
41        format!("{wasmer_root_dir}/lib/c-api/tests/wasm.h"),
42        format!("{wasmer_root_dir}/package/include/wasm.h"),
43    );
44    let _ = std::fs::copy(
45        format!("{wasmer_root_dir}/lib/c-api/tests/wasmer.h"),
46        format!("{wasmer_root_dir}/package/include/wasmer.h"),
47    );
48    #[cfg(target_os = "windows")]
49    let _ = std::fs::copy(
50        &format!("{wasmer_root_dir}/target/release/wasmer.dll"),
51        &format!("{wasmer_root_dir}/package/lib"),
52    );
53    #[cfg(target_os = "windows")]
54    let _ = std::fs::copy(
55        &format!("{wasmer_root_dir}/target/release/wasmer.dll.lib"),
56        &format!("{wasmer_root_dir}/package/lib"),
57    );
58    #[cfg(not(target_os = "windows"))]
59    let _ = std::fs::copy(
60        format!("{wasmer_root_dir}/target/release/libwasmer.so"),
61        format!("{wasmer_root_dir}/package/lib"),
62    );
63    #[cfg(not(target_os = "windows"))]
64    let _ = std::fs::copy(
65        format!("{wasmer_root_dir}/target/release/libwasmer.lib"),
66        format!("{wasmer_root_dir}/package/lib"),
67    );
68    println!("copying done (make package)");
69}
70
71#[derive(Debug)]
72pub struct Config {
73    pub wasmer_dir: String,
74    pub root_dir: String,
75}
76
77impl Config {
78    pub fn get() -> Config {
79        let mut config = Config {
80            wasmer_dir: std::env::var("WASMER_DIR").unwrap_or_default(),
81            root_dir: std::env::var("ROOT_DIR").unwrap_or_default(),
82        };
83
84        // resolve the path until the /wasmer root directory
85        let manifest_dir = env!("CARGO_MANIFEST_DIR");
86        let wasmer_base_dir = find_wasmer_base_dir();
87
88        if config.wasmer_dir.is_empty() {
89            println!("manifest dir = {manifest_dir}, wasmer root dir = {wasmer_base_dir}");
90            config.wasmer_dir = wasmer_base_dir.clone() + "/package";
91            if !std::path::Path::new(&config.wasmer_dir).exists() {
92                println!("running make build-capi...");
93                // run make build-capi
94                let mut cmd = std::process::Command::new("make");
95                cmd.arg("build-capi");
96                cmd.current_dir(&wasmer_base_dir);
97                let result = cmd.output();
98                println!("make build-capi: {result:#?}");
99
100                println!("running make package-capi...");
101                // run make package
102                let mut cmd = std::process::Command::new("make");
103                cmd.arg("package-capi");
104                cmd.current_dir(&wasmer_base_dir);
105                let result = cmd.output();
106                make_package();
107                println!("make package: {result:#?}");
108
109                println!("list {}", config.wasmer_dir);
110                match std::fs::read_dir(&config.wasmer_dir) {
111                    Ok(o) => {
112                        for entry in o {
113                            let entry = entry.unwrap();
114                            let path = entry.path();
115                            println!("    {:?}", path.file_name());
116                        }
117                    }
118                    Err(e) => {
119                        println!("error in reading config.wasmer_dir: {e}");
120                    }
121                };
122            }
123        }
124        if config.root_dir.is_empty() {
125            config.root_dir = wasmer_base_dir + "/lib/c-api/examples";
126        }
127
128        config
129    }
130}
131
132fn find_wasmer_base_dir() -> String {
133    let wasmer_base_dir = env!("CARGO_MANIFEST_DIR");
134    let mut path2 = wasmer_base_dir.split("wasmer").collect::<Vec<_>>();
135    path2.pop();
136    let mut wasmer_base_dir = path2.join("wasmer");
137
138    if wasmer_base_dir.contains("wasmer/lib/c-api") {
139        wasmer_base_dir = wasmer_base_dir
140            .split("wasmer/lib/c-api")
141            .next()
142            .unwrap()
143            .to_string()
144            + "wasmer";
145    } else if wasmer_base_dir.contains("wasmer\\lib\\c-api") {
146        wasmer_base_dir = wasmer_base_dir
147            .split("wasmer\\lib\\c-api")
148            .next()
149            .unwrap()
150            .to_string()
151            + "wasmer";
152    }
153
154    wasmer_base_dir
155}
156
157#[cfg(test)]
158pub const TESTS: &[&str] = &[
159    "deprecated-header",
160    "early-exit",
161    "instance",
162    "imports-exports",
163    "exports-function",
164    "exports-global",
165    "memory",
166    "memory2",
167    "features",
168    "wasi",
169];
170
171#[test]
172fn test_run() {
173    let _drop = RemoveTestsOnDrop::default();
174    let config = Config::get();
175    println!("config: {config:#?}");
176
177    let manifest_dir = env!("CARGO_MANIFEST_DIR");
178    let host = target_lexicon::HOST.to_string();
179    let target = &host;
180
181    let wasmer_dll_dir = format!("{}/lib", config.wasmer_dir);
182    let libwasmer_so_path = format!("{}/lib/libwasmer.so", config.wasmer_dir);
183    let path = std::env::var("PATH").unwrap_or_default();
184    let newpath = format!("{wasmer_dll_dir};{path}");
185    let exe_dir = match std::path::Path::new(&manifest_dir).parent() {
186        Some(parent) => format!("{}", parent.display()),
187        None => manifest_dir.to_string(),
188    };
189
190    for test in TESTS.iter() {
191        let manifest_dir_parent = std::path::Path::new(&manifest_dir);
192        let manifest_dir_parent = manifest_dir_parent.parent().unwrap();
193        let c_file_path = manifest_dir_parent.join(format!("{test}.c"));
194
195        if target.contains("msvc") {
196            let mut build = cc::Build::new();
197            let build = build
198                .cargo_metadata(false)
199                .warnings(true)
200                .static_crt(true)
201                .extra_warnings(true)
202                .warnings_into_errors(false)
203                .debug(true)
204                .host(&host)
205                .target(target)
206                .opt_level(1);
207
208            let compiler = build.try_get_compiler().unwrap();
209            let mut command = compiler.to_command();
210
211            command.arg(format!("{}", c_file_path.display()));
212            if !config.wasmer_dir.is_empty() {
213                command.arg("/I");
214                command.arg(format!("{}/include/", config.wasmer_dir));
215                let mut log = String::new();
216                fixup_symlinks(
217                    &[
218                        format!("{}/include", config.wasmer_dir),
219                        config.root_dir.to_string(),
220                    ],
221                    &mut log,
222                    &config.root_dir,
223                )
224                .unwrap_or_else(|_| panic!("failed to fix symlinks: {log}"));
225                println!("{log}");
226            }
227
228            let exe_outpath = manifest_dir_parent.join(format!("{test}.exe"));
229            let exe_outpath = format!("{}", exe_outpath.display());
230            println!("compiling exe to {exe_outpath}");
231
232            command.arg(format!("/Fo:{}/", manifest_dir_parent.display()));
233            command.arg("/link");
234            if !config.wasmer_dir.is_empty() {
235                command.arg(format!("/LIBPATH:{}/lib", config.wasmer_dir));
236                command.arg(format!("{}/lib/wasmer.dll.lib", config.wasmer_dir));
237            }
238            command.arg(format!("/OUT:{exe_outpath}"));
239
240            // read vcvars into file, append command, then execute the bat
241
242            println!("compiling WINDOWS {test}: {command:?}");
243
244            let vcvars_bat_path = find_vcvarsall(&compiler).expect("no vcvarsall.bat");
245            let vcvars_bat_path_parent = std::path::Path::new(&vcvars_bat_path).parent().unwrap();
246            let _vcvars_modified_output = vcvars_bat_path_parent.join("compile-windows.bat");
247            let vcvars_bat_file = std::fs::read_to_string(&vcvars_bat_path).unwrap();
248            let batch_formatted = format!("{}\\", vcvars_bat_path_parent.display());
249            let vcvars_bat_file = vcvars_bat_file
250                .replace("%~dp0", &batch_formatted.replace('\\', "\\\\"))
251                .replace("\"%1\"", "\"x64\"");
252            let vcvars_modified = format!("{vcvars_bat_file}\r\n{command:?}");
253            let path = std::path::Path::new(&manifest_dir).join("compile-windows.bat");
254            println!("outputting batch to {}", path.display());
255            std::fs::write(&path, vcvars_modified).unwrap();
256
257            // print_wasmer_root_to_stdout(&config);
258
259            let mut vcvars = std::process::Command::new("cmd");
260            vcvars.arg("/C");
261            vcvars.arg(&path);
262            vcvars.arg("x64");
263            vcvars.current_dir(vcvars_bat_path_parent);
264
265            // compile
266            let output = vcvars
267                .output()
268                .map_err(|e| format!("failed to compile {command:#?}: {e}"))
269                .unwrap();
270            if !output.status.success() {
271                println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
272                println!("stdout: {}", String::from_utf8_lossy(&output.stderr));
273                // print_wasmer_root_to_stdout(&config);
274                panic!("failed to compile {test}");
275            }
276
277            if !std::path::Path::new(&exe_outpath).exists() {
278                panic!("error: {exe_outpath} does not exist");
279            }
280            if !std::path::Path::new(&wasmer_dll_dir)
281                .join("wasmer.dll")
282                .exists()
283            {
284                panic!("error: {wasmer_dll_dir} has no wasmer.dll");
285            }
286            // execute
287            let mut command = std::process::Command::new(&exe_outpath);
288            command.env("PATH", &newpath);
289            command.current_dir(&exe_dir);
290            println!("executing {test}: {command:?}");
291            println!("setting current dir = {exe_dir}");
292            let output = command
293                .output()
294                .unwrap_or_else(|_| panic!("failed to run {command:#?}"));
295            if !output.status.success() {
296                println!("{output:#?}");
297                println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
298                println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
299                // print_wasmer_root_to_stdout(&config);
300                panic!("failed to execute {test}");
301            }
302        } else {
303            let compiler_cmd = match std::process::Command::new("cc").output() {
304                Ok(_) => "cc",
305                Err(_) => "gcc",
306            };
307
308            let mut command = std::process::Command::new(compiler_cmd);
309
310            if !config.wasmer_dir.is_empty() {
311                command.arg("-I");
312                command.arg(format!("{}/include", config.wasmer_dir));
313                let mut log = String::new();
314                fixup_symlinks(
315                    &[
316                        format!("{}/include", config.wasmer_dir),
317                        config.root_dir.to_string(),
318                    ],
319                    &mut log,
320                    &config.root_dir,
321                )
322                .unwrap_or_else(|_| panic!("failed to fix symlinks: {log}"));
323            }
324            command.arg(&c_file_path);
325            if !config.wasmer_dir.is_empty() {
326                command.arg("-L");
327                command.arg(format!("{}/lib/", config.wasmer_dir));
328                command.arg("-lwasmer");
329                command.arg(format!("-Wl,-rpath,{}/lib/", config.wasmer_dir));
330            }
331            command.arg("-o");
332            command.arg(format!("{manifest_dir}/../{test}"));
333
334            // cc -g -IC:/Users/felix/Development/wasmer/lib/c-api/examples/../tests
335            //       -IC:/Users/felix/Development/wasmer/package/include
336            //       -c -o deprecated-header.o deprecated-header.c
337
338            /*
339                cc -I /home/runner/work/wasmer/wasmer/lib/c-api/tests
340                -I" "/home/runner/work/wasmer/wasmer/package/include"
341                "/home/runner/work/wasmer/wasmer/lib/c-api/tests/wasmer-c-api-test-runner/../wasm-c-api/example/callback.c"
342                "-L/home/runner/work/wasmer/wasmer/package/lib"
343                "-lwasmer"
344                "-o" "/home/runner/work/wasmer/wasmer/lib/c-api/tests/wasmer-c-api-test-runner/../wasm-c-api/example/callback"
345            */
346
347            println!("compiling LINUX {command:#?}");
348            // compile
349            let output = command
350                .output()
351                .map_err(|e| format!("failed to compile {command:#?}: {e}"))
352                .unwrap();
353            if !output.status.success() {
354                println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
355                println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
356                // print_wasmer_root_to_stdout(&config);
357                panic!("failed to compile {test}: {command:#?}");
358            }
359
360            // execute
361            let mut command = std::process::Command::new(format!("{manifest_dir}/../{test}"));
362            command.env("LD_PRELOAD", &libwasmer_so_path);
363            command.current_dir(&exe_dir);
364            println!("execute: {command:#?}");
365            let output = command
366                .output()
367                .unwrap_or_else(|_| panic!("failed to run {command:#?}"));
368            if !output.status.success() {
369                println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
370                println!("stdout: {}", String::from_utf8_lossy(&output.stderr));
371                // print_wasmer_root_to_stdout(&config);
372                panic!("failed to execute {test} executable");
373            }
374        }
375    }
376}
377
378// #[cfg(test)]
379// fn print_wasmer_root_to_stdout(config: &Config) {
380//     println!("print_wasmer_root_to_stdout");
381
382//     use walkdir::WalkDir;
383
384//     for entry in WalkDir::new(&config.wasmer_dir)
385//         .into_iter()
386//         .filter_map(Result::ok)
387//     {
388//         let f_name = String::from(entry.path().canonicalize().unwrap().to_string_lossy());
389//         println!("{f_name}");
390//     }
391
392//     for entry in WalkDir::new(&config.root_dir)
393//         .into_iter()
394//         .filter_map(Result::ok)
395//     {
396//         let f_name = String::from(entry.path().canonicalize().unwrap().to_string_lossy());
397//         println!("{f_name}");
398//     }
399
400//     println!("printed");
401// }
402
403#[cfg(test)]
404fn fixup_symlinks(
405    include_paths: &[String],
406    log: &mut String,
407    root_dir: &str,
408) -> Result<(), Box<dyn Error>> {
409    let source = std::path::Path::new(root_dir)
410        .join("lib")
411        .join("c-api")
412        .join("tests")
413        .join("wasm-c-api")
414        .join("include")
415        .join("wasm.h");
416    let target = std::path::Path::new(root_dir)
417        .join("lib")
418        .join("c-api")
419        .join("tests")
420        .join("wasm.h");
421    println!("copying {} -> {}", source.display(), target.display());
422    let _ = std::fs::copy(source, target);
423
424    log.push_str(&format!("include paths: {include_paths:?}"));
425    for i in include_paths {
426        let i = i.replacen("-I", "", 1);
427        let mut paths_headers = Vec::new();
428        let readdir = match std::fs::read_dir(&i) {
429            Ok(o) => o,
430            Err(_) => continue,
431        };
432        for entry in readdir {
433            let entry = entry?;
434            let path = entry.path();
435            let path_display = format!("{}", path.display());
436            if path_display.ends_with('h') {
437                paths_headers.push(path_display);
438            }
439        }
440        fixup_symlinks_inner(&paths_headers, log)?;
441    }
442
443    Ok(())
444}
445
446#[cfg(test)]
447fn fixup_symlinks_inner(include_paths: &[String], log: &mut String) -> Result<(), Box<dyn Error>> {
448    log.push_str(&format!("fixup symlinks: {include_paths:#?}"));
449    let regex = regex::Regex::new(INCLUDE_REGEX).unwrap();
450    for path in include_paths.iter() {
451        let file = match std::fs::read_to_string(path) {
452            Ok(o) => o,
453            _ => continue,
454        };
455        let lines_3 = file.lines().take(3).collect::<Vec<_>>();
456        log.push_str(&format!("first 3 lines of {path:?}: {lines_3:#?}\n"));
457
458        let parent = std::path::Path::new(&path).parent().unwrap();
459        if let Ok(symlink) = std::fs::read_to_string(parent.join(&file)) {
460            log.push_str(&format!("symlinking {path:?}\n"));
461            std::fs::write(path, symlink)?;
462        }
463
464        // follow #include directives and recurse
465        let filepaths = regex
466            .captures_iter(&file)
467            .map(|c| c[1].to_string())
468            .collect::<Vec<_>>();
469        log.push_str(&format!("regex captures: ({path:?}): {filepaths:#?}\n"));
470        let joined_filepaths = filepaths
471            .iter()
472            .map(|s| {
473                let path = parent.join(s);
474                format!("{}", path.display())
475            })
476            .collect::<Vec<_>>();
477        fixup_symlinks_inner(&joined_filepaths, log)?;
478    }
479    Ok(())
480}
481
482#[cfg(test)]
483fn find_vcvarsall(compiler: &cc::Tool) -> Option<String> {
484    if !compiler.is_like_msvc() {
485        return None;
486    }
487
488    let path = compiler.path();
489    let path = format!("{}", path.display());
490    let split = path.split("VC").next()?;
491
492    Some(format!("{split}VC\\Auxiliary\\Build\\vcvarsall.bat"))
493}