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    // and write it to disk to be used when embedding plugins
 32    let target_triple = std::env::var("TARGET").unwrap().to_string();
 33    println!("cargo:rerun-if-env-changed=TARGET");
 34
 35    // Invoke cargo to build the plugins
 36    let build_successful = std::process::Command::new("cargo")
 37        .args([
 38            "build",
 39            "--target",
 40            "wasm32-wasi",
 41            "--manifest-path",
 42            base.join("Cargo.toml").to_str().unwrap(),
 43        ])
 44        .args(profile_flags)
 45        .status()
 46        .expect("Could not build plugins")
 47        .success();
 48    assert!(build_successful);
 49
 50    // Find all compiled binaries
 51    let engine = create_default_engine(&target_triple);
 52    let binaries = std::fs::read_dir(base.join("target/wasm32-wasi").join(profile_target))
 53        .expect("Could not find compiled plugins in target");
 54
 55    // Copy and precompile all compiled plugins we can find
 56    for file in binaries {
 57        let is_wasm = || {
 58            let path = file.ok()?.path();
 59            if path.extension()? == "wasm" {
 60                Some(path)
 61            } else {
 62                None
 63            }
 64        };
 65
 66        if let Some(path) = is_wasm() {
 67            let out_path = base.join("bin").join(path.file_name().unwrap());
 68            std::fs::copy(&path, &out_path).expect("Could not copy compiled plugin to bin");
 69            precompile(&out_path, &engine, &target_triple);
 70        }
 71    }
 72}
 73
 74/// Creates an engine with the default configuration.
 75/// N.B. This must create an engine with the same config as the one
 76/// in `plugin_runtime/src/plugin.rs`.
 77fn create_default_engine(target_triple: &str) -> Engine {
 78    let mut config = Config::default();
 79    config
 80        .target(target_triple)
 81        .expect(&format!("Could not set target to `{}`", target_triple));
 82    config.async_support(true);
 83    config.consume_fuel(true);
 84    Engine::new(&config).expect("Could not create precompilation engine")
 85}
 86
 87fn precompile(path: &Path, engine: &Engine, target_triple: &str) {
 88    let bytes = std::fs::read(path).expect("Could not read wasm module");
 89    let compiled = engine
 90        .precompile_module(&bytes)
 91        .expect("Could not precompile module");
 92    let out_path = path.parent().unwrap().join(&format!(
 93        "{}.{}",
 94        path.file_name().unwrap().to_string_lossy(),
 95        target_triple,
 96    ));
 97    let mut out_file = std::fs::File::create(out_path)
 98        .expect("Could not create output file for precompiled module");
 99    out_file.write_all(&compiled).unwrap();
100}