folk-runtime-embed 0.1.17

Embedded PHP runtime for Folk — PHP interpreter runs in-process via FFI
Documentation
use std::env;
use std::process::Command;

fn main() {
    // Use php-config to discover paths
    let php_config = env::var("PHP_CONFIG").unwrap_or_else(|_| "php-config".to_string());

    let includes = run_php_config(&php_config, "--includes");
    let ldflags = run_php_config(&php_config, "--ldflags");
    let prefix = run_php_config(&php_config, "--prefix");
    let extension_dir = run_php_config(&php_config, "--extension-dir");

    // Parse include paths for cc
    for flag in includes.split_whitespace() {
        if let Some(path) = flag.strip_prefix("-I") {
            println!("cargo:include={path}");
        }
    }

    // Link to libphp (embed SAPI)
    // php-config --libs gives us the dependent libs PHP needs
    let libs = run_php_config(&php_config, "--libs");
    for flag in libs.split_whitespace() {
        if let Some(lib) = flag.strip_prefix("-l") {
            println!("cargo:rustc-link-lib={lib}");
        }
    }

    // Parse ldflags for library search paths
    for flag in ldflags.split_whitespace() {
        if let Some(path) = flag.strip_prefix("-L") {
            println!("cargo:rustc-link-search=native={path}");
        }
    }

    // The embed SAPI library is typically at {prefix}/lib/libphp.so
    println!("cargo:rustc-link-search=native={prefix}/lib");
    println!("cargo:rustc-link-lib=php");

    // Compile our C helper (for zend_try/zend_catch which use setjmp/longjmp macros)
    let mut build = cc::Build::new();
    build.file("src/php_embed_helper.c");

    // Add PHP include paths
    for flag in includes.split_whitespace() {
        if let Some(path) = flag.strip_prefix("-I") {
            build.include(path);
        }
    }

    // Detect ZTS (thread-safe) build via php-config --configure-options
    let configure_opts = run_php_config(&php_config, "--configure-options");
    if configure_opts.contains("--enable-zts") {
        build.define("ZTS", "1");
        println!("cargo:rustc-cfg=php_zts");
    }

    build.compile("php_embed_helper");

    // Tell cargo to re-run if PHP changes
    println!("cargo:rerun-if-changed=src/php_embed_helper.c");
    println!("cargo:rerun-if-changed=build.rs");
    println!("cargo:rerun-if-env-changed=PHP_CONFIG");

    // Export extension dir for runtime
    println!("cargo:rustc-env=PHP_EXTENSION_DIR={extension_dir}");
}

fn run_php_config(php_config: &str, flag: &str) -> String {
    let output = Command::new(php_config)
        .arg(flag)
        .output()
        .unwrap_or_else(|e| {
            panic!("Failed to run `{php_config} {flag}`: {e}\nIs php-dev installed?")
        });

    assert!(
        output.status.success(),
        "`{php_config} {flag}` failed: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    String::from_utf8(output.stdout)
        .expect("php-config output is not UTF-8")
        .trim()
        .to_string()
}