1use std::path::Path;
2
3use anyhow::{Context, Result};
4use tempfile::TempDir;
5use tracing::info;
6
7use crate::codegen::{generate_build_rs, generate_cargo_toml, generate_lib_rs};
8use crate::config::BuildConfig;
9
10pub fn build(cfg: &BuildConfig, output_dir: &str) -> Result<()> {
11 let mut resolved = cfg.clone();
13 if let Some(path) = &resolved.build.folk_ext_path {
14 let abs = std::fs::canonicalize(path)
15 .with_context(|| format!("resolve folk_ext_path: {path}"))?;
16 resolved.build.folk_ext_path = Some(abs.display().to_string());
17 }
18 for plugin in &mut resolved.plugin {
19 if let Some(path) = &plugin.path {
20 let abs = std::fs::canonicalize(path)
21 .with_context(|| format!("resolve plugin path: {path}"))?;
22 plugin.path = Some(abs.display().to_string());
23 }
24 }
25
26 let tmp = TempDir::new().context("create temp dir")?;
27 let tmp_path = tmp.path();
28
29 std::fs::create_dir_all(tmp_path.join("src"))?;
30 std::fs::write(tmp_path.join("Cargo.toml"), generate_cargo_toml(&resolved))?;
31 std::fs::write(tmp_path.join("src/lib.rs"), generate_lib_rs(&resolved))?;
32 std::fs::write(tmp_path.join("build.rs"), generate_build_rs())?;
33
34 info!(dir = %tmp_path.display(), "compiling PHP extension");
35
36 let status = std::process::Command::new("cargo")
37 .args(["build", "--release"])
38 .current_dir(tmp_path)
39 .status()
40 .context("run cargo")?;
41
42 if !status.success() {
43 anyhow::bail!("cargo build failed with status {status}");
44 }
45
46 let lib_name = &cfg.build.output;
47 let ext = if cfg!(target_os = "macos") {
48 "dylib"
49 } else {
50 "so"
51 };
52
53 let src = tmp_path
54 .join("target/release")
55 .join(format!("lib{lib_name}.{ext}"));
56 let dst = Path::new(output_dir).join(format!("{lib_name}.{ext}"));
57
58 std::fs::copy(&src, &dst)
59 .with_context(|| format!("copy {} → {}", src.display(), dst.display()))?;
60
61 info!(output = %dst.display(), "extension ready");
62 Ok(())
63}