build.rs

 1use std::{io::Write, path::Path};
 2use wasmtime::{Config, Engine};
 3
 4fn main() {
 5    let base = Path::new("../../plugins");
 6
 7    // Find all files and folders that don't change when rebuilt
 8    let crates = std::fs::read_dir(base).expect("Could not find plugin directory");
 9    for dir in crates {
10        let path = dir.unwrap().path();
11        let name = path.file_name().and_then(|x| x.to_str());
12        let is_dir = path.is_dir();
13        if is_dir && name != Some("target") && name != Some("bin") {
14            println!("cargo:rerun-if-changed={}", path.display());
15        }
16    }
17
18    // Clear out and recreate the plugin bin directory
19    let _ = std::fs::remove_dir_all(base.join("bin"));
20    let _ =
21        std::fs::create_dir_all(base.join("bin")).expect("Could not make plugins bin directory");
22
23    // Compile the plugins using the same profile as the current Zed build
24    let (profile_flags, profile_target) = match std::env::var("PROFILE").unwrap().as_str() {
25        "debug" => (&[][..], "debug"),
26        "release" => (&["--release"][..], "release"),
27        unknown => panic!("unknown profile `{}`", unknown),
28    };
29
30    // Get the target architecture for pre-cross-compilation of plugins
31    let target_triple = std::env::var("TARGET").unwrap().to_string();
32
33    // Invoke cargo to build the plugins
34    let build_successful = std::process::Command::new("cargo")
35        .args([
36            "build",
37            "--target",
38            "wasm32-wasi",
39            "--manifest-path",
40            base.join("Cargo.toml").to_str().unwrap(),
41        ])
42        .args(profile_flags)
43        .status()
44        .expect("Could not build plugins")
45        .success();
46    assert!(build_successful);
47
48    // Find all compiled binaries
49    let engine = create_default_engine(&target_triple);
50    let binaries = std::fs::read_dir(base.join("target/wasm32-wasi").join(profile_target))
51        .expect("Could not find compiled plugins in target");
52
53    // Copy and precompile all compiled plugins we can find
54    for file in binaries {
55        let is_wasm = || {
56            let path = file.ok()?.path();
57            if path.extension()? == "wasm" {
58                Some(path)
59            } else {
60                None
61            }
62        };
63
64        if let Some(path) = is_wasm() {
65            let out_path = base.join("bin").join(path.file_name().unwrap());
66            std::fs::copy(&path, &out_path).expect("Could not copy compiled plugin to bin");
67            precompile(&out_path, &engine, &target_triple);
68        }
69    }
70}
71
72/// Creates an engine with the default configuration.
73/// N.B. This must create an engine with the same config as the one
74/// in `plugin_runtime/src/plugin.rs`.
75fn create_default_engine(target_triple: &str) -> Engine {
76    let mut config = Config::default();
77    config
78        .target(target_triple)
79        .expect(&format!("Could not set target to `{}`", target_triple));
80    config.async_support(true);
81    config.consume_fuel(true);
82    Engine::new(&config).expect("Could not create precompilation engine")
83}
84
85fn precompile(path: &Path, engine: &Engine, target_triple: &str) {
86    let bytes = std::fs::read(path).expect("Could not read wasm module");
87    let compiled = engine
88        .precompile_module(&bytes)
89        .expect("Could not precompile module");
90    let out_path = path.parent().unwrap().join(&format!(
91        "{}.{}",
92        path.file_name().unwrap().to_string_lossy(),
93        target_triple,
94    ));
95    let mut out_file = std::fs::File::create(out_path)
96        .expect("Could not create output file for precompiled module");
97    out_file.write_all(&compiled).unwrap();
98}