#!/bin/php
<?php
try {
    $VERSION = pw_build_meson_version();
    $PW_BUILD_MESON_GENERATOR_PATH = realpath($argv[0] ?? "") ?: "";
    if (isset($argv[1]) && $argv[1] === "--generate-stub") {
        generate_pwphpbytecode_stub_cli($argv);
        exit(0);
    }
    if (isset($argv[1]) && file_exists($argv[1])) {
        parse_args($argv);
        echo "Starting pw-build-meson v" . $VERSION . " for directory " . $argv[1] . "...\r\n";
        chdir($argv[1]);
        echo "\tEntered root directory: " . realpath(".") . "\r\n";
        if(tree_needs_legacy_pwphpbytecode(".")){
            load_dependencies();
        }
        scandir4pw();
        chdir("..");
    } else {
        echo "Usage: pw-build-meson directory [phpslot abiver]\r\n";
        echo "--no-doxygen: skips generation of manpages\r\n";
        exit(1);
    }
}
catch(Exception|Error $ex){
    echo $ex->getMessage();
    echo "\r\n";
    echo $ex->getTraceAsString();
    echo "\r\n";
    exit(1);
}

function pw_build_meson_version(){
    $version = "__PW_BUILD_MESON_VERSION__";
    $placeholder = "__PW_BUILD_MESON_" . "VERSION__";
    if($version !== "" && $version !== $placeholder){
        return($version);
    }

    $env_version = getenv("PW_BUILD_MESON_VERSION");
    if($env_version !== false && $env_version !== ""){
        return($env_version);
    }

    return("unknown");
}

function parse_args($argv){
    global $_DOXYGEN;
    $_DOXYGEN=true;
    if(in_array("--no-doxygen", $argv)){
        $_DOXYGEN=false;
    }
}

function load_dependencies(){
    global $_PHP_SLOT,$_ABI_VER,$_SERVER;
    if(isset($_SERVER["argv"][3])) {
        $_ABI_VER = $_SERVER["argv"][3];
        $_PHP_SLOT = $_SERVER["argv"][2];
        return;
    }

    $files = scandir("/lib64");
    foreach($files as $file){
        if(str_starts_with($file,"libpwphpbytecode-") && str_ends_with($file,".so")){
            $exp = explode("-",$file);
            $exp2 = explode(".",$exp[2]);
            if(isset($_ABI_VER) && $_ABI_VER>$exp2[0]) continue;
            if(isset($_PHP_SLOT) && $_PHP_SLOT>$exp[1]) continue;
            $_ABI_VER=$exp2[0];
            $_PHP_SLOT=$exp[1];
        }
    }
    echo "\tAutomatically detected PHP Slot: ".$_PHP_SLOT." and ABIVER: ".$_ABI_VER."\r\n";
}

function tree_needs_legacy_pwphpbytecode($dir){
    $meson_pw = $dir . "/meson.build.pw";
    if(file_exists($meson_pw) && pw_params_need_legacy_pwphpbytecode(file($meson_pw))){
        return(true);
    }

    $opendir = opendir($dir);
    while($file = readdir($opendir)){
        if($file=="." || $file=="..") continue;
        $path = $dir . "/" . $file;
        if(is_dir($path) && tree_needs_legacy_pwphpbytecode($path)){
            closedir($opendir);
            return(true);
        }
    }
    closedir($opendir);
    return(false);
}


function scandir4pw(){
    $opendir=opendir(".");
    $subdirs=array();
    while($file=readdir($opendir)){
        if($file=="." || $file=="..") continue;
        if(is_dir($file)){
            $subdirs[]=$file;
        }
    }
    closedir($opendir);

    // Ordering...
    if(file_exists("meson.build")){ // if not exists, we dont have ordering informations
        $relevant_lines=array();
        $load = file("meson.build");
        foreach($load as $line){
            $line = trim($line);
            if(str_starts_with($line,"subdir")){
                $relevant_lines[]=$line;
            }
        }

        $subdirs_old=$subdirs;
        $subdirs=array();
        foreach($relevant_lines as $line){
            foreach($subdirs_old as $subdir_o){
                if(str_contains($line,$subdir_o)){
                    if(in_array($subdir_o,$subdirs))continue;
                    $subdirs[]=$subdir_o;
                }
            }
        }
        foreach($subdirs_old as $subdir_o){
            if(!in_array($subdir_o,$subdirs)){
                $subdirs[]=$subdir_o;
            }
        }
    }


    foreach($subdirs as $subdir) {
        echo "\tEntering " . realpath($subdir) . "\r\n";
        chdir($subdir);
        scandir4pw();
        chdir("..");
        echo "\tLeaving " . realpath($subdir) . "\r\n";
    }


    if(file_exists("meson.build.pw")){
        echo "\tProcessing ".realpath("meson.build.pw")."\r\n";
        process_pw("meson.build.pw");
    }
}

function load_pwphpwrapper_suffix_fine(){
    $ret = "_".load_pwphpwrapper_suffix();
    $ret = str_replace(".","_",$ret);
    $ret = str_replace("-","_",$ret);
    return($ret);
}
function load_pwphpwrapper_suffix(){
    global $_PHP_SLOT,$_ABI_VER;
    return($_PHP_SLOT."-".$_ABI_VER);
}
function pw_build_meson_collect_wildcard_snapshots($params){
    foreach($params as $param){
        $param=trim($param);
        if(empty($param))continue;
        if(str_starts_with($param, "#"))continue;
        $param = explode("=", $param, 2);
        $param_key = trim($param[0]);
        if(!is_phpef_rule_key($param_key))continue;
        $value = $param[1] ?? "";
        $tmp = explode("->", $value, 2);
        if(count($tmp) !== 2)continue;
        phpef_parse_sources(trim($tmp[0]));
    }
}

function process_pw($file){
    global $_PHP_SLOT,$_ABI_VER,$_DOXYGEN,$PW_BUILD_MESON_WILDCARD_SNAPSHOTS,$PW_BUILD_MESON_PHPEF_RUNTIME_POLICY;
    $params = file($file);
    phpef_validate_runtime_policy_before_rules($params);
    $PW_BUILD_MESON_PHPEF_RUNTIME_POLICY = phpef_parse_runtime_policy($params);
    $PW_BUILD_MESON_WILDCARD_SNAPSHOTS = array();
    pw_build_meson_collect_wildcard_snapshots($params);
    $mesonfile=pw_build_meson_generated_header($file);

    foreach(scan_for_subs() as $subdir){
        $mesonfile.="subdir('".$subdir."')\n";
    }
    if(pw_params_need_legacy_pwphpbytecode($params)){
        $mesonfile.="pwphpbytecode_dependency = dependency('pwphpbytecode-".$_PHP_SLOT."-".$_ABI_VER."')\n";
        $mesonfile.="pwphpbytecode_dependency_raw = dependency('pwphpbytecode_raw-".$_PHP_SLOT."-".$_ABI_VER."')\n";
    }

    // First Loop -> Generate Dependencies
    $done_embedfile=false;
    $done_embedfilestrip=false;
    $done_cat=false;
    $done_makeheaders=false;
    $done_doxygen=false;
    $done_etcautoupdate=false;
    $done_pccphpext=false;
    $done_echo=false;
    $done_valaphp=false;
    foreach($params as $param){
        $param=trim($param);
        if(empty($param))continue;
        if(str_starts_with($param, "#")){
            //its a comment
            continue;
        }
        $param = explode("=",$param,2);
        $param_key = trim($param[0]);
        if(is_phpef_runtime_policy_key($param_key)){
            continue;
        }
        if(is_valaphp_rule_key($param_key)){
            if(!$done_valaphp){
                $mesonfile.=valaphp_meson_prelude();
                $done_valaphp=true;
            }
        }
        elseif(is_phpef_rule_key($param_key)){
            // PHPEF rules are descriptor-driven; add_phpef_bin() emits the concrete tool find_program() calls it needs.
        }
        elseif(trim($param[0])=="lib"){
            if(!$done_cat){
                $mesonfile.="cat = find_program('cat')\n";
                $done_cat=true;
            }
        }
        elseif(trim($param[0])=="fpmext"){
            if(!$done_pccphpext){
                $mesonfile.="phpfpmextbuilder = find_program('pcc_phpextensionbuilder')\n";
                $done_pccphpext=true;
            }
            if(!$done_echo){
                $mesonfile.="echo = find_program('echo')\n";
                $done_echo=true;
            }
        }
        elseif(trim($param[0])=="bin" || trim($param[0])=="bin-jit" || str_starts_with(trim($param[0]), "bin(")|| str_starts_with(trim($param[0]), "bin-jit(")){
            if(!$done_embedfile){
                $mesonfile.="mycompiler = find_program('pcc_embedfile_bytecode_".$_PHP_SLOT."')\n";
                $done_embedfile=true;
            }
        }
        elseif(trim($param[0])=="strip" || str_starts_with(trim($param[0]), "strip(")){
            if(!$done_embedfilestrip){
                $mesonfile.="mycompiler_strip = find_program('pcc_stripfile')\n";
                $done_embedfilestrip=true;
            }
        }
        elseif(str_starts_with(trim($param[0]), "etc(")){
            if(!$done_embedfilestrip){
                $mesonfile.="mycompiler_strip = find_program('pcc_stripfile')\n";
                $done_embedfilestrip=true;
            }
            if (!$done_embedfile) {
                $mesonfile .= "mycompiler = find_program('pcc_embedfile_bytecode_".$_PHP_SLOT."')\n";
                $done_embedfile = true;
            }
            if(!$done_etcautoupdate){
                $mesonfile.="mycompiler_etcstub = find_program('etc-autoupdate_createstub')\n";
                $done_etcautoupdate=true;
            }
        }
        elseif(trim($param[0])=="class" || trim($param[0])=="class-jit"){
            if (!$done_embedfile) {
                $mesonfile .= "mycompiler = find_program('pcc_embedfile_bytecode_".$_PHP_SLOT."')\n";
                $done_embedfile = true;
            }
            if (!$done_makeheaders) {
                $mesonfile .= "makeheaders = find_program('makeheaders')\n";
                $done_makeheaders = true;
            }

            if(!$done_doxygen && $_DOXYGEN){
                // Build Doxyfile
                $mesonfile.="doxyconf_data = configuration_data()\n";
                $mesonfile.="doxyconf_data.set('SOURCECODE',meson.current_source_dir())\n";
                $mesonfile.="doxyconf_data.set('OUTPUTDIR',meson.current_build_dir())\n";
                $mesonfile.= "doxyconfig = configure_file(input:'/usr/share/pw-build-meson/Doxyfile',\n";
                $mesonfile.="output: 'Doxyfile',\n";
                $mesonfile.="configuration: doxyconf_data)\n";
                $mesonfile.="doxygen = find_program('doxygen')\n";
                $mesonfile.="doxygen_output_dir='man'\n";
                $mesonfile.="complete_man = custom_target(\n";
                $mesonfile.="   doxygen_output_dir,\n";
                $mesonfile.="   input: doxyconfig,\n";
                $mesonfile.="   output: doxygen_output_dir,\n";
                $mesonfile.="   build_always_stale: true,\n";
                $mesonfile.="   command: [doxygen,'-q','@INPUT@'],\n";
                $mesonfile.=")\n";
                $done_doxygen=true;
            }
            if(!$done_cat){
                $mesonfile.="cat = find_program('cat')\n";
                $done_cat=true;
            }
        }
    }

    // Second Loop -> Build code
    foreach($params as $param){
        $param=trim($param);
        if(empty($param))continue;
        if(str_starts_with($param, "#")){
            //its a comment
            continue;
        }
        $param = explode("=",$param,2);
        $param_key = trim($param[0]);
        if(is_phpef_runtime_policy_key($param_key)){
            continue;
        }
        if(is_valaphp_rule_key($param_key)){
            $mesonfile.=add_valaphp_rule($param);
        }
        elseif(is_phpef_rule_key($param_key)){
            $mesonfile.=add_phpef_rule($param);
        }
        elseif(trim($param[0])=="lib"){
            $mesonfile.=add_lib($param);
        }
        elseif(trim($param[0])=="fpmext"){
            $mesonfile.=add_phpfpm($param);
        }
        elseif(trim($param[0])=="bin" || trim($param[0])=="bin-jit" || str_starts_with(trim($param[0]), "bin(") || str_starts_with(trim($param[0]), "bin-jit(")){
            $mesonfile.=add_bin($param);
        }
        elseif(trim($param[0])=="strip" || str_starts_with(trim($param[0]), "strip(")){
            $mesonfile.=add_strip($param);
        }
        elseif(trim($param[0])=="class" || trim($param[0])=="class-jit"){
            $mesonfile.=add_class($param);
        }
        elseif(str_starts_with(trim($param[0]), "etc(")){
            $mesonfile.=add_etc($param);
        }
        else{
            echo "pw-build-meson2: "."Unknown param ".$param[0]."\r\n";
            exit(1);
        }
    }


    if($done_doxygen){
        // Use Doxyfile
    }

    $fp = fopen("meson.build","w");
    fwrite($fp,$mesonfile);
    fclose($fp);
    echo "\tmeson.build written!\r\n";
}

function pw_build_meson_generated_header($source_file){
    global $PW_BUILD_MESON_WILDCARD_SNAPSHOTS;
    $marker = "# This file is automatically generated by dev-php/pw-build-meson, DO NOT EDIT! DONT COMMIT INTO SUBVERSION! - sourced by " . $source_file;
    $generator_path = pw_build_meson_generator_path();
    $generator_mtime = pw_build_meson_generator_mtime();
    $wildcard_manifest = pw_build_meson_wildcard_manifest_text($PW_BUILD_MESON_WILDCARD_SNAPSHOTS ?? array());

    $txt = "";
    $txt .= $marker . "\n";
    $txt .= "# pw-build-meson-generator-path: " . $generator_path . "\n";
    $txt .= "# pw-build-meson-generator-mtime: " . $generator_mtime . "\n";
    $txt .= "_pw_build_meson_source = meson.current_source_dir() / " . valaphp_meson_quote($source_file) . "\n";
    $txt .= "_pw_build_meson_generated = meson.current_source_dir() / 'meson.build'\n";
    $txt .= "_pw_build_meson_generator_path = " . valaphp_meson_quote($generator_path) . "\n";
    $txt .= "_pw_build_meson_expected_generator_mtime = " . $generator_mtime . "\n";
    $txt .= "if _pw_build_meson_generator_path != '' and _pw_build_meson_expected_generator_mtime > 0 and run_command('test', '-f', _pw_build_meson_generator_path, check: false).returncode() == 0\n";
    $txt .= "\tconfigure_file(input: _pw_build_meson_generator_path, output: 'pw-build-meson.watch', copy: true)\n";
    $txt .= "\t_pw_build_meson_current_generator_mtime = run_command('stat', '-c', '%Y', _pw_build_meson_generator_path, check: true).stdout().strip().to_int()\n";
    $txt .= "\tif _pw_build_meson_current_generator_mtime != _pw_build_meson_expected_generator_mtime\n";
    $txt .= "\t\t_pw_build_meson_marker = run_command('grep', '-F', " . valaphp_meson_quote($marker) . ", _pw_build_meson_generated, check: false)\n";
    $txt .= "\t\tif _pw_build_meson_marker.returncode() == 0\n";
    $txt .= "\t\t\trun_command('rm', '-f', _pw_build_meson_generated, check: true)\n";
    $txt .= "\t\t\terror('pw-build-meson generator mtime changed; removed generated meson.build. Run pw-build-meson before the next Meson configure/compile.')\n";
    $txt .= "\t\telse\n";
    $txt .= "\t\t\terror('pw-build-meson generator mtime changed, but meson.build has no pw-build-meson marker; refusing to remove it.')\n";
    $txt .= "\t\tendif\n";
    $txt .= "\tendif\n";
    $txt .= "endif\n";
    $txt .= "if run_command('test', '-f', _pw_build_meson_source, check: false).returncode() == 0\n";
    $txt .= "\tconfigure_file(input: " . valaphp_meson_quote($source_file) . ", output: 'meson.build.pw.watch', copy: true)\n";
    $txt .= "\t_pw_build_meson_source_mtime = run_command('stat', '-c', '%Y', _pw_build_meson_source, check: true).stdout().strip().to_int()\n";
    $txt .= "\t_pw_build_meson_generated_mtime = run_command('stat', '-c', '%Y', _pw_build_meson_generated, check: true).stdout().strip().to_int()\n";
    $txt .= "\tif _pw_build_meson_source_mtime > _pw_build_meson_generated_mtime\n";
    $txt .= "\t\t_pw_build_meson_marker = run_command('grep', '-F', " . valaphp_meson_quote($marker) . ", _pw_build_meson_generated, check: false)\n";
    $txt .= "\t\tif _pw_build_meson_marker.returncode() == 0\n";
    $txt .= "\t\t\trun_command('rm', '-f', _pw_build_meson_generated, check: true)\n";
    $txt .= "\t\t\terror('meson.build.pw is newer than generated meson.build; removed generated meson.build. Run pw-build-meson before the next Meson configure/compile.')\n";
    $txt .= "\t\telse\n";
    $txt .= "\t\t\terror('meson.build.pw is newer than meson.build, but meson.build has no pw-build-meson marker; refusing to remove it.')\n";
    $txt .= "\t\tendif\n";
    $txt .= "\tendif\n";
    $txt .= "endif\n";
    if($wildcard_manifest !== ""){
        foreach(explode("\n", trim($wildcard_manifest)) as $manifest_line){
            if($manifest_line !== ""){
                $txt .= "# pw-build-meson-wildcard: " . $manifest_line . "\n";
            }
        }
        foreach(($PW_BUILD_MESON_WILDCARD_SNAPSHOTS ?? array()) as $snapshot_index => $snapshot){
            foreach(($snapshot["matches"] ?? array()) as $match_index => $match){
                $watch_name = "pw-build-meson-wildcard-" . $snapshot_index . "-" . $match_index . ".watch";
                $txt .= "configure_file(input: " . valaphp_meson_quote($match) . ", output: " . valaphp_meson_quote($watch_name) . ", copy: true)\n";
            }
        }
    }
    $txt .= "if _pw_build_meson_generator_path != ''\n";
    $txt .= "\t_pw_build_meson_program = find_program(_pw_build_meson_generator_path, required: true)\n";
    $txt .= "else\n";
    $txt .= "\t_pw_build_meson_program = find_program('pw-build-meson', required: true)\n";
    $txt .= "endif\n";
    return($txt);
}

function pw_build_meson_wildcard_manifest_text($snapshots){
    $lines = array();
    foreach($snapshots as $snapshot){
        $pattern = $snapshot["pattern"] ?? "";
        foreach(($snapshot["matches"] ?? array()) as $match){
            $lines[] = $pattern . "	" . $match;
        }
    }
    if($lines === array()){
        return("");
    }
    sort($lines, SORT_NATURAL | SORT_FLAG_CASE);
    return(implode("\n", $lines) . "\n");
}

function pw_build_meson_generator_path(){
    global $PW_BUILD_MESON_GENERATOR_PATH;
    $path = $PW_BUILD_MESON_GENERATOR_PATH ?? "";
    if($path === "" || !file_exists($path)){
        return("");
    }
    return($path);
}

function pw_build_meson_generator_mtime(){
    $path = pw_build_meson_generator_path();
    if($path === ""){
        return(0);
    }
    $mtime = filemtime($path);
    if($mtime === false){
        return(0);
    }
    return($mtime);
}

function pw_param_key($param){
    $parts = explode("=", trim($param), 2);
    return(trim($parts[0] ?? ""));
}

function pw_params_need_legacy_pwphpbytecode($params){
    foreach($params as $param){
        $param = trim($param);
        if(empty($param) || str_starts_with($param, "#"))continue;
        $key = pw_param_key($param);
        if(
            $key === "lib" ||
            $key === "fpmext" ||
            $key === "bin" ||
            $key === "bin-jit" ||
            str_starts_with($key, "bin(") ||
            str_starts_with($key, "bin-jit(") ||
            $key === "class" ||
            $key === "class-jit" ||
            str_starts_with($key, "etc(")
        ){
            return(true);
        }
    }
    return(false);
}


function is_phpef_runtime_policy_key($key){
    $key = trim($key);
    return($key === "phpef-runtime-embed" || $key === "phpef-runtime-emed" || $key === "phpef-runtime-natives");
}

function phpef_parse_runtime_policy($params){
    $policy = array(
        "embed" => true,
        "natives" => "*",
        "defined-embed" => false,
        "defined-natives" => false,
    );
    foreach($params as $param){
        $param = trim($param);
        if($param === "" || str_starts_with($param, "#"))continue;
        $parts = explode("=", $param, 2);
        $key = trim($parts[0] ?? "");
        if(!is_phpef_runtime_policy_key($key))continue;
        $value = trim($parts[1] ?? "");
        if($key === "phpef-runtime-embed" || $key === "phpef-runtime-emed"){
            $policy["embed"] = phpef_bool_option($key, $value);
            $policy["defined-embed"] = true;
        }
        elseif($key === "phpef-runtime-natives"){
            $policy["natives"] = $value === "" ? "" : $value;
            $policy["defined-natives"] = true;
        }
    }
    return($policy);
}

function phpef_current_runtime_policy(){
    global $PW_BUILD_MESON_PHPEF_RUNTIME_POLICY;
    if(!is_array($PW_BUILD_MESON_PHPEF_RUNTIME_POLICY ?? null)){
        return(array("embed" => true, "natives" => "*", "defined-embed" => false, "defined-natives" => false));
    }
    return($PW_BUILD_MESON_PHPEF_RUNTIME_POLICY);
}

function phpef_runtime_policy_missing_keys($policy){
    $missing = array();
    if(!($policy["defined-embed"] ?? false)){
        $missing[] = "phpef-runtime-embed";
    }
    if(!($policy["defined-natives"] ?? false)){
        $missing[] = "phpef-runtime-natives";
    }
    return($missing);
}

function phpef_validate_runtime_policy_before_rules($params){
    $seen_policy = array(
        "defined-embed" => false,
        "defined-natives" => false,
        "natives" => "",
    );
    foreach($params as $line_number => $param){
        $param = trim($param);
        if($param === "" || str_starts_with($param, "#"))continue;
        $parts = explode("=", $param, 2);
        $key = trim($parts[0] ?? "");
        $value = trim($parts[1] ?? "");
        if(is_phpef_runtime_policy_key($key)){
            if($key === "phpef-runtime-embed" || $key === "phpef-runtime-emed"){
                phpef_bool_option($key, $value);
                $seen_policy["defined-embed"] = true;
            }
            elseif($key === "phpef-runtime-natives"){
                $seen_policy["defined-natives"] = true;
                $seen_policy["natives"] = $value;
            }
            continue;
        }
        if(is_phpef_rule_key($key)){
            $missing = phpef_runtime_policy_missing_keys($seen_policy);
            if($missing !== array()){
                echo "pw-build-meson: PHPEF runtime policy must be explicitly defined before " . phpef_rule_base_key($key) . " line " . ($line_number + 1) . ". Missing: " . join(", ", $missing) . "\r\n";
                echo "pw-build-meson: Add top-of-file lines like: phpef-runtime-embed = true|false and phpef-runtime-natives = standard,foo,bar (or * or empty for no native runtime). These must match the ebuild RDEPENDs.\r\n";
                exit(1);
            }
        }
    }
}

function phpef_native_runtime_packages_from_policy($policy){
    $raw = trim((string)($policy["natives"] ?? "*"));
    if($raw === "*"){
        return(null);
    }
    $packages = array();
    foreach(explode(",", $raw) as $name){
        $name = trim($name);
        if($name === "")continue;
        if(!str_starts_with($name, "phpef-runtime-native-")){
            $name = "phpef-runtime-native-" . $name;
        }
        if(!in_array($name, $packages, true)){
            $packages[] = $name;
        }
    }
    return($packages);
}

function is_phpef_rule_key($key){
    return(in_array(phpef_rule_base_key($key), array("ebin", "elib"), true));
}

function phpef_rule_base_key($key){
    $key = trim($key);
    if(str_contains($key, "(")){
        $parts = explode("(", $key, 2);
        $key = trim($parts[0]);
    }
    return($key);
}

function phpef_rule_install_dir($key){
    $key = trim($key);
    if(!str_contains($key, "(") || !str_contains($key, ")")){
        return("");
    }
    $parts = explode("(", $key, 2);
    $inner = explode(")", $parts[1], 2);
    return(trim($inner[0]));
}

function add_phpef_rule($param){
    global $_PHPEF_LOCAL_LIBRARIES;
    if(!isset($_PHPEF_LOCAL_LIBRARIES) || !is_array($_PHPEF_LOCAL_LIBRARIES)){
        $_PHPEF_LOCAL_LIBRARIES = array();
    }
    $key = phpef_rule_base_key($param[0]);
    if($key === "ebin"){
        $rule = parse_phpef_rule($param);
        $rule["target-kind"] = "executable";
        $external_sources = array();
        $external_stubs = array();
        $local_phpef_libs = array();
        foreach(($rule["phpef-libs"] ?? array()) as $phpef_lib){
            if(isset($_PHPEF_LOCAL_LIBRARIES[$phpef_lib])){
                $local_phpef_libs[] = $phpef_lib;
                foreach($_PHPEF_LOCAL_LIBRARIES[$phpef_lib] as $source){
                    if(!in_array($source, $external_sources, true)){
                        $external_sources[] = $source;
                    }
                }
            }
            else{
                $stub = phpef_library_stub_path($phpef_lib);
                if(!in_array($stub, $external_stubs, true)){
                    $external_stubs[] = $stub;
                }
            }
        }
        $rule["external-sources"] = $external_sources;
        $rule["external-stubs"] = $external_stubs;
        $rule["local-phpef-libs"] = array_values(array_unique($local_phpef_libs));
        return(add_phpef_bin($rule));
    }
    if($key === "elib"){
        $rule = parse_phpef_rule($param);
        $rule["target-kind"] = "library";
        $_PHPEF_LOCAL_LIBRARIES[$rule["dst"]] = $rule["sources"];
        return(add_phpef_bin($rule));
    }
    echo "pw-build-meson: Unknown PHPEF rule ".$key."
";
    exit(1);
}

function phpef_library_stub_path($library){
    $stub_dir = getenv("PHPEF_LIBSTUBS_DIR");
    if($stub_dir === false || $stub_dir === ""){
        $stub_dir = "/usr/share/phpef/libstubs";
    }
    return(rtrim($stub_dir, "/") . "/" . phpef_source_symbol_prefix((string)$library) . ".php");
}

function phpef_local_library_link_target_ref($library){
    $lib = phpef_source_symbol_prefix((string)$library);
    return(valaphp_identifier("phpef_" . $lib . "_" . $lib . "_compile_c_library"));
}

function parse_phpef_rule($param){
    if(!isset($param[1])){
        echo "pw-build-meson: Missing right side in PHPEF param:\r\n";
        print_r($param);
        exit(1);
    }

    $value = trim($param[1]);
    $options = phpef_parse_options($value, phpef_rule_install_dir($param[0]));
    $tmp = explode("->", $value, 2);
    if(count($tmp) !== 2){
        echo "pw-build-meson: Missing -> in PHPEF param:\r\n";
        print_r($param);
        exit(1);
    }
    $sources = phpef_parse_sources(trim($tmp[0]));
    $src = $sources[0] ?? "";
    $dst = trim($tmp[1]);
    $phpef_libs = array();
    if(preg_match('/^(.*?)\s*\(([^)]*)\)\s*$/', $dst, $match)){
        $dst = trim($match[1]);
        foreach(explode(",", $match[2]) as $lib){
            $lib = trim($lib);
            if($lib !== "" && !in_array($lib, $phpef_libs, true)){
                $phpef_libs[] = $lib;
            }
        }
    }
    if($src === "" || $dst === ""){
        echo "pw-build-meson: Missing source or destination in PHPEF param:\r\n";
        print_r($param);
        exit(1);
    }

    return(array(
        "src" => $src,
        "sources" => $sources,
        "support-sources" => array_slice($sources, 1),
        "dst" => $dst,
        "phpef-libs" => $phpef_libs,
        "options" => $options,
        "runtime-policy" => phpef_current_runtime_policy(),
    ));
}


function phpef_parse_sources($raw_sources){
    $raw_sources = trim($raw_sources);
    if($raw_sources === ""){
        return(array());
    }

    if(str_contains($raw_sources, "+")){
        $parts = explode("+", $raw_sources, 2);
        $entry = trim($parts[0]);
        $rest = trim($parts[1]);
        $sources = array();
        foreach(phpef_expand_source_token($entry) as $entry_source){
            if(!in_array($entry_source, $sources, true)){
                $sources[] = $entry_source;
            }
        }
        foreach(phpef_split_source_list($rest) as $source){
            if(!in_array($source, $sources, true)){
                $sources[] = $source;
            }
        }
        return($sources);
    }

    return(phpef_split_source_list($raw_sources));
}

function phpef_split_source_list($raw_sources){
    $sources = array();
    foreach(explode(",", $raw_sources) as $source){
        $source = trim($source);
        if($source === ""){
            continue;
        }
        foreach(phpef_expand_source_token($source) as $expanded_source){
            if(!in_array($expanded_source, $sources, true)){
                $sources[] = $expanded_source;
            }
        }
    }
    return($sources);
}

function phpef_expand_source_token($source){
    $source = trim($source);
    if($source === ""){
        return(array());
    }
    if(!phpef_source_token_has_wildcard($source)){
        return(array($source));
    }

    $matches = glob($source, GLOB_BRACE);
    if($matches === false || $matches === array()){
        echo "pw-build-meson: PHPEF source wildcard matched no files: " . $source . "\r\n";
        exit(1);
    }
    $matches = array_values(array_filter($matches, static fn($match) => is_file($match)));
    sort($matches, SORT_NATURAL | SORT_FLAG_CASE);
    if($matches === array()){
        echo "pw-build-meson: PHPEF source wildcard matched no files: " . $source . "\r\n";
        exit(1);
    }
    phpef_record_source_wildcard_snapshot($source, $matches);
    return($matches);
}

function phpef_record_source_wildcard_snapshot($pattern, $matches){
    global $PW_BUILD_MESON_WILDCARD_SNAPSHOTS;
    if(!isset($PW_BUILD_MESON_WILDCARD_SNAPSHOTS) || !is_array($PW_BUILD_MESON_WILDCARD_SNAPSHOTS)){
        $PW_BUILD_MESON_WILDCARD_SNAPSHOTS = array();
    }
    foreach($PW_BUILD_MESON_WILDCARD_SNAPSHOTS as $snapshot){
        if(($snapshot["pattern"] ?? "") === $pattern && ($snapshot["matches"] ?? array()) === $matches){
            return;
        }
    }
    $PW_BUILD_MESON_WILDCARD_SNAPSHOTS[] = array(
        "pattern" => $pattern,
        "matches" => $matches,
    );
}

function phpef_source_token_has_wildcard($source){
    return(strpbrk($source, "*?[") !== false);
}

function phpef_parse_options(&$value, $install_dir){
    $options = array(
        "roundtrip" => true,
        "typing-optimizer" => true,
        "library-optimizer" => true,
        "fastpath-runtime-optimizer" => true,
        "precompute-optimizer" => true,
        "stacktrace" => true,
        "install-dir" => $install_dir,
    );

    if(!preg_match('/\s*\[([^\]]*)\]\s*$/', $value, $match)){
        return($options);
    }

    $value = trim(substr($value, 0, -strlen($match[0])));
    foreach(explode(",", $match[1]) as $raw_option){
        $raw_option = trim($raw_option);
        if($raw_option === ""){
            continue;
        }
        $parts = explode("=", $raw_option, 2);
        $name = trim($parts[0]);
        $option_value = isset($parts[1]) ? trim($parts[1]) : "true";
        if($name === "roundtrip" || $name === "stacktrace" || $name === "typing-optimizer" || $name === "library-optimizer" || $name === "fastpath-runtime-optimizer" || $name === "precompute-optimizer"){
            $options[$name] = phpef_bool_option($name, $option_value);
        }
        elseif($name === "valair-optimizer-typing-zval2static"){
            $options["typing-optimizer"] = phpef_bool_option($name, $option_value);
        }
        elseif($name === "valair-optimizer-library-embed2native"){
            $options["library-optimizer"] = phpef_bool_option($name, $option_value);
        }
        elseif($name === "valair-optimizer-fastpath-runtime"){
            $options["fastpath-runtime-optimizer"] = phpef_bool_option($name, $option_value);
        }
        elseif($name === "valair-optimizer-precompute"){
            $options["precompute-optimizer"] = phpef_bool_option($name, $option_value);
        }
        elseif($name === "install" || $name === "install-dir"){
            echo "pw-build-meson: PHPEF option ".$name." is not supported; use ebin = ... for /usr/bin or ebin(/path) = ... for a custom install directory\r\n";
            exit(1);
        }
        else{
            echo "pw-build-meson: Unknown PHPEF option ".$name."\r\n";
            exit(1);
        }
    }

    return($options);
}

function phpef_bool_option($name, $value){
    $value = strtolower(trim($value));
    if(in_array($value, array("1", "true", "yes", "on"), true)){
        return(true);
    }
    if(in_array($value, array("0", "false", "no", "off"), true)){
        return(false);
    }
    echo "pw-build-meson: Invalid boolean for PHPEF option ".$name.": ".$value."\r\n";
    exit(1);
}

function phpef_valair_optimizer_enabled($rule, $pass){
    if($pass === "typing-zval2static"){
        return($rule["options"]["typing-optimizer"] ?? true);
    }
    if($pass === "library-embed2native"){
        return($rule["options"]["library-optimizer"] ?? true);
    }
    if($pass === "fastpath-runtime"){
        return($rule["options"]["fastpath-runtime-optimizer"] ?? true);
    }
    if($pass === "precompute"){
        return($rule["options"]["precompute-optimizer"] ?? true);
    }
    return(true);
}

function phpef_load_pipeline_descriptor($rule){
    $cmd = array("phpefc-get-build-pipeline");
    foreach(($rule["sources"] ?? array($rule["src"])) as $source){
        $cmd[] = $source;
    }
    $cmd[] = "--target-name";
    $cmd[] = $rule["dst"];
    $cmd[] = "--target-kind";
    $cmd[] = ($rule["target-kind"] ?? "executable");
    foreach(($rule["phpef-libs"] ?? array()) as $phpef_lib){
        $init_function = phpef_library_init_function($phpef_lib);
        $cmd[] = "--auto-init-function";
        $cmd[] = $init_function;
    }
    foreach(($rule["external-sources"] ?? array()) as $external_source){
        $cmd[] = "--external-source";
        $cmd[] = $external_source;
    }
    foreach(($rule["external-stubs"] ?? array()) as $external_stub){
        $cmd[] = "--external-stub";
        $cmd[] = $external_stub;
    }
    $runtime_policy = $rule["runtime-policy"] ?? phpef_current_runtime_policy();
    $cmd[] = "--phpef-runtime-embed";
    $cmd[] = !empty($runtime_policy["embed"]) ? "true" : "false";
    $cmd[] = "--phpef-runtime-natives";
    $cmd[] = (string)($runtime_policy["natives"] ?? "*");
    if(array_key_exists("meson-debug-build", $rule) && !(bool)$rule["meson-debug-build"]){
        $cmd[] = "--no-debug-section";
    }
    if(!empty($rule["options"]["stacktrace"])){
        $cmd[] = "--stacktrace-section";
    }
    if(!$rule["options"]["roundtrip"]){
        $cmd[] = "--no-roundtrip";
    }
    if(!phpef_valair_optimizer_enabled($rule, "typing-zval2static")){
        $cmd[] = "--no-valair-optimizer-typing-zval2static";
    }
    if(!phpef_valair_optimizer_enabled($rule, "library-embed2native")){
        $cmd[] = "--no-valair-optimizer-library-embed2native";
    }
    if(!phpef_valair_optimizer_enabled($rule, "fastpath-runtime")){
        $cmd[] = "--no-valair-optimizer-fastpath-runtime";
    }
    if(!phpef_valair_optimizer_enabled($rule, "precompute")){
        $cmd[] = "--no-valair-optimizer-precompute";
    }

    $shell_cmd = "";
    foreach($cmd as $part){
        $shell_cmd .= ($shell_cmd === "" ? "" : " ") . escapeshellarg($part);
    }
    $output = array();
    $exit_code = 1;
    exec($shell_cmd . " 2>&1", $output, $exit_code);
    if($exit_code !== 0){
        echo "pw-build-meson: phpefc-get-build-pipeline failed for ".$rule["src"]." -> ".$rule["dst"]."\r\n";
        echo join("\r\n", $output) . "\r\n";
        exit(1);
    }

    $json = join("\n", $output);
    $descriptor = json_decode($json, true);
    if(!is_array($descriptor) || ($descriptor["format"] ?? "") !== "phpefc-build-pipeline-v0"){
        echo "pw-build-meson: Invalid PHPEF pipeline descriptor for ".$rule["src"]."\r\n";
        exit(1);
    }
    return($descriptor);
}


function phpef_source_base($source){
    $base = basename($source);
    if(str_ends_with($base, ".php")){
        $base = substr($base, 0, -4);
    }
    $base = preg_replace('/[^A-Za-z0-9_]+/', '_', $base);
    $base = trim($base, '_');
    return($base !== "" ? $base : "source");
}

function phpef_source_symbol_prefix($source){
    $normalized = str_replace("\\", "/", $source);
    $normalized = ltrim($normalized, "./");
    $prefix = preg_replace("/[^A-Za-z0-9_]+/", "_", $normalized);
    $prefix = trim($prefix, "_");
    if($prefix === ""){
        $prefix = "source";
    }
    if(preg_match("/^[A-Za-z_]/", $prefix) !== 1){
        $prefix = "_" . $prefix;
    }
    return($prefix);
}

function phpef_library_init_function($target_name){
    return("phpef_library_" . phpef_source_symbol_prefix($target_name) . "_init");
}

function phpef_library_target_var($target_name){
    $lib = phpef_source_symbol_prefix((string)$target_name);
    return(valaphp_identifier("phpef_" . $lib . "_" . $lib . "_compile_c_library"));
}

function phpef_local_library_link_expr($library_names){
    $refs = array();
    foreach($library_names as $library_name){
        $library_name = trim((string)$library_name);
        if($library_name === ""){ continue; }
        $refs[] = phpef_library_target_var($library_name);
    }
    return("[" . join(", ", $refs) . "]");
}

function phpef_meson_indent_block($text){
    $lines = explode("
", rtrim($text, "
"));
    $out = array();
    foreach($lines as $line){
        $out[] = $line === "" ? "" : "	" . $line;
    }
    return(join("
", $out) . "
");
}

function add_phpef_bin($rule){
    $descriptor = phpef_load_pipeline_descriptor($rule);
    $id = valaphp_identifier("phpef_" . $rule["dst"]);
    $txt = "";
    $tool_vars = array();
    $artifact_refs = array();
    foreach(($rule["sources"] ?? array($rule["src"])) as $source_index => $source){
        $source_var = $id . "_source_" . $source_index;
        $txt .= $source_var . " = files(" . valaphp_meson_quote($source) . ")\n";
        $artifact_refs[$source] = $source_var . "[0]";
    }
    if(($rule["target-kind"] ?? "executable") === "library"){
        $stub_tool_var = valaphp_identifier($id . "_tool_phpefc_generate_library_stub");
        $stub_target_var = valaphp_identifier($id . "_library_stub");
        $source_refs = array();
        foreach(($rule["sources"] ?? array($rule["src"])) as $source){ $source_refs[] = phpef_meson_input_ref($source, $artifact_refs); }
        $txt .= $stub_tool_var . " = find_program('phpefc-generate-library-stub', required: true)\n";
        $txt .= $stub_target_var . " = custom_target(\n";
        $txt .= "\t" . valaphp_meson_quote($rule["dst"] . ".php") . ",\n";
        $txt .= "\tinput: " . phpef_meson_ref_list($source_refs) . ",\n";
        $txt .= "\toutput: " . valaphp_meson_quote($rule["dst"] . ".php") . ",\n";
        $txt .= "\tcommand: [" . $stub_tool_var . ", '@INPUT@', '@OUTPUT@'],\n";
        $txt .= "\tinstall: true,\n";
        $txt .= "\tinstall_dir: join_paths(get_option('datadir'), 'phpef', 'libstubs'),\n";
        $txt .= "\tinstall_tag: 'devel',\n";
        $txt .= ")\n";
        $artifact_refs[phpef_library_stub_path($rule["dst"])] = $stub_target_var;
    }
    $dependency_vars = array();
    foreach(($descriptor["runtimeDependencies"] ?? array()) as $dep){
        $dep = (string) $dep;
        if($dep === "" || isset($dependency_vars[$dep])){ continue; }
        $dep_var = valaphp_identifier($id . "_dep_" . $dep);
        $txt .= $dep_var . " = dependency(" . valaphp_meson_quote($dep) . ", required: true)\n";
        $dependency_vars[$dep] = $dep_var;
    }
    $native_runtime_packages_var = "''";
    $native_runtime_dependencies_var = "[]";
    $native_runtime_pattern = (string)($descriptor["nativeRuntimeDependencyPattern"] ?? "");
    if($native_runtime_pattern !== ""){
        $native_runtime_packages_var = valaphp_identifier($id . "_native_runtime_packages");
        $native_runtime_dependencies_var = valaphp_identifier($id . "_native_runtime_dependencies");
        $runtime_policy = $rule["runtime-policy"] ?? phpef_current_runtime_policy();
        $allowed_native_packages = phpef_native_runtime_packages_from_policy($runtime_policy);
        if(is_array($allowed_native_packages)){
            $native_runtime_packages_literal = implode(" ", $allowed_native_packages);
            $txt .= $native_runtime_packages_var . " = " . valaphp_meson_quote($native_runtime_packages_literal) . "\n";
        }
        else{
            $native_regex = "^" . str_replace('\\*', '.+', preg_quote($native_runtime_pattern, '/')) . "$";
            $native_script = "pkg-config --list-all | awk '{print $1}' | grep -E " . escapeshellarg($native_regex) . " | grep -Ev -- '-uninstalled$' | sort | tr '\\n' ' ' || true";
            $txt .= $native_runtime_packages_var . " = run_command('sh', '-c', " . valaphp_meson_quote($native_script) . ", check: false).stdout().strip()\n";
        }
        $txt .= $native_runtime_dependencies_var . " = []\n";
        $txt .= "foreach native_runtime_package : " . $native_runtime_packages_var . ".split()\n";
        $txt .= "\tif native_runtime_package != ''\n";
        $txt .= "\t\t" . $native_runtime_dependencies_var . " += [dependency(native_runtime_package, required: true)]\n";
        $txt .= "\tendif\n";
        $txt .= "endforeach\n";
    }
    foreach(($descriptor["steps"] ?? array()) as $step){
        $role = $step["role"] ?? "";
        $step_id = valaphp_identifier($id . "_" . ($step["id"] ?? $role));
        $pass = $step["pass"] ?? "typing-zval2static";
        if($role === "valair_optimize" && !phpef_valair_optimizer_enabled($rule, $pass)){
            $inputs = $step["inputs"] ?? array();
            $outputs = $step["outputs"] ?? array();
            $input_ref = phpef_meson_input_ref($inputs[0] ?? "", $artifact_refs);
            foreach($outputs as $output){
                $artifact_refs[$output] = $input_ref;
            }
            continue;
        }
        if(isset($step["tool"]) && !isset($tool_vars[$step["tool"]])){
            $tool_var = valaphp_identifier($id . "_tool_" . $step["tool"]);
            $txt .= $tool_var . " = find_program(" . valaphp_meson_quote($step["tool"]) . ", required: true)\n";
            $tool_vars[$step["tool"]] = $tool_var;
        }

        if($role === "php_to_ast" || $role === "ast_to_phpir" || $role === "phpir_to_valair" || $role === "valair_to_vala" || $role === "ast_to_php_roundtrip"){
            $inputs = $step["inputs"] ?? array();
            $outputs = $step["outputs"] ?? array();
            $input_refs = array();
            foreach($inputs as $input){ $input_refs[] = phpef_meson_input_ref($input, $artifact_refs); }
            $txt .= $step_id . " = custom_target(\n";
            $txt .= "\t" . valaphp_meson_quote($outputs[0] ?? $step_id) . ",\n";
            $txt .= "\tinput: " . phpef_meson_ref_list($input_refs) . ",\n";
            $txt .= "\toutput: " . phpef_meson_output_list($outputs) . ",\n";
            $argv = array($tool_vars[$step["tool"]]);
            foreach(($step["argv"] ?? array('@INPUT@', '@OUTPUT@')) as $arg){ $argv[] = valaphp_meson_quote($arg); }
            $txt .= "\tcommand: [" . join(", ", $argv) . "],\n";
            if((string)($step["header"] ?? "") !== "" || (string)($step["vapi"] ?? "") !== ""){
                $vala_vapi_dir_var = valaphp_identifier($step_id . "_vala_vapi_dir");
                $api_include_dir_var = valaphp_identifier($step_id . "_api_include_dir");
                $txt .= "\tinstall: true,\n";
                $txt .= $vala_vapi_dir_var . " = join_paths(get_option(" . valaphp_meson_quote("datadir") . "), " . valaphp_meson_quote("vala") . ", " . valaphp_meson_quote("vapi") . ")\n";
                $txt .= $api_include_dir_var . " = join_paths(get_option(" . valaphp_meson_quote("includedir") . "), " . valaphp_meson_quote($rule["dst"]) . ")\n";
                $install_dirs = array();
                foreach($outputs as $output){
                    if($output === (string)($step["header"] ?? "")){ $install_dirs[] = $api_include_dir_var; }
                    elseif($output === (string)($step["vapi"] ?? "")){ $install_dirs[] = $vala_vapi_dir_var; }
                    else{ $install_dirs[] = "false"; }
                }
                $txt .= "\tinstall_dir: [" . join(", ", $install_dirs) . "],\n";
            }
            $txt .= ")\n";
            phpef_register_output_refs($artifact_refs, $outputs, $step_id);
        }
        elseif($role === "valair_optimize"){
            $inputs = $step["inputs"] ?? array();
            $input_refs = array();
            foreach($inputs as $input){ $input_refs[] = phpef_meson_input_ref($input, $artifact_refs); }
            $output = ($step["outputs"] ?? array())[0] ?? "";
            $txt .= $step_id . " = custom_target(\n";
            $txt .= "\t" . valaphp_meson_quote($output) . ",\n";
            $txt .= "\tinput: " . phpef_meson_ref_list($input_refs) . ",\n";
            $txt .= "\toutput: " . valaphp_meson_quote($output) . ",\n";
            if(isset($step["argv"]) && is_array($step["argv"])){
                $argv = array($tool_vars[$step["tool"]]);
                foreach($step["argv"] as $arg){
                    $argv[] = valaphp_meson_quote($arg);
                }
                $txt .= "\tcommand: [" . join(", ", $argv) . "],\n";
            }
            else{
                $txt .= "\tcommand: [" . $tool_vars[$step["tool"]] . ", '--pass', " . valaphp_meson_quote($pass) . ", '@INPUT@', '@OUTPUT@'],\n";
            }
            $txt .= ")\n";
            $artifact_refs[$output] = $step_id;
        }
        elseif($role === "verify_no_runtime_embed"){
            $inputs = $step["inputs"] ?? array();
            $outputs = $step["outputs"] ?? array();
            $input_ref = phpef_meson_input_ref($inputs[0] ?? "", $artifact_refs);
            $output = $outputs[0] ?? ($step_id . ".stamp");
            $check_script = 'set -e; bin="$1"; stamp="$2"; if command -v lddtree >/dev/null 2>&1; then deps=$(lddtree "$bin" 2>&1 || true); else deps=$(ldd "$bin" 2>&1 || true); fi; if printf "%s\n" "$deps" | grep -E "libphpef-runtime-embed\.so|phpef-runtime-embed" >/dev/null; then printf "%s\n" "ERROR: PHPEF target still depends on phpef-runtime-embed, but meson.build.pw has phpef-runtime-embed=false." >&2; printf "%s\n" "Not all required PHP functions are native yet. Set phpef-runtime-embed=true in meson.build.pw to allow embed fallback, or add native implementations/allowed native runtimes." >&2; printf "%s\n" "$deps" >&2; exit 1; fi; : > "$stamp"';
            $txt .= $step_id . " = custom_target(\n";
            $txt .= "\t" . valaphp_meson_quote($output) . ",\n";
            $txt .= "\tinput: " . $input_ref . ",\n";
            $txt .= "\toutput: " . valaphp_meson_quote($output) . ",\n";
            $txt .= "\tcommand: ['sh', '-c', " . valaphp_meson_quote($check_script) . ", 'sh', '@INPUT@', '@OUTPUT@'],\n";
            $txt .= "\tbuild_by_default: " . phpef_meson_bool((bool)($step["build_by_default"] ?? true)) . ",\n";
            $txt .= ")\n";
            $artifact_refs[$output] = $step_id;
        }
        elseif($role === "compile_vala_to_c"){
            $inputs = $step["inputs"] ?? array();
            $outputs = $step["outputs"] ?? array();
            $input_refs = array();
            foreach($inputs as $input){ $input_refs[] = phpef_meson_input_ref($input, $artifact_refs); }
            if((string)($step["header"] ?? "") !== "" || (string)($step["vapi"] ?? "") !== ""){
                $vala_vapi_dir_var = valaphp_identifier($step_id . "_vala_vapi_dir");
                $api_include_dir_var = valaphp_identifier($step_id . "_api_include_dir");
                $txt .= $vala_vapi_dir_var . " = join_paths(get_option(" . valaphp_meson_quote("datadir") . "), " . valaphp_meson_quote("vala") . ", " . valaphp_meson_quote("vapi") . ")\n";
                $txt .= $api_include_dir_var . " = join_paths(get_option(" . valaphp_meson_quote("includedir") . "), " . valaphp_meson_quote($rule["dst"]) . ")\n";
            }
            $txt .= $step_id . " = custom_target(\n";
            $txt .= "\t" . valaphp_meson_quote(($step["id"] ?? $step_id) . "-sources") . ",\n";
            $txt .= "\tinput: " . phpef_meson_ref_list($input_refs) . ",\n";
            $txt .= "\toutput: " . phpef_meson_output_list($outputs) . ",\n";
            $packages = array();
            $local_vapi_depends = array();
            foreach(($step["packages"] ?? ($descriptor["runtimeDependencies"] ?? array())) as $package){
                $package = (string)$package;
                if($package === "" || $package === "@NATIVE_RUNTIME_PACKAGES@"){ continue; }
                $packages[] = $package;
            }
            foreach(($rule["phpef-libs"] ?? array()) as $phpef_lib){
                $phpef_lib = trim((string)$phpef_lib);
                if($phpef_lib === ""){ continue; }
                $packages[] = $phpef_lib;
                if(in_array($phpef_lib, ($rule["local-phpef-libs"] ?? array()), true)){
                    $local_vapi_depends[] = phpef_library_target_var($phpef_lib);
                }
            }
            $packages = array_values(array_unique($packages));
            $package_args = array();
            foreach($packages as $package){ $package_args[] = valaphp_meson_quote($package); }
            $valac_script = 'set -e; valac="$1"; builddir="$2"; native_packages="$3"; header="$4"; vapi="$5"; shift 5; pkg_args=; vapidir_args="--vapidir $builddir"; add_pkg(){ pkg="$1"; if [ -z "$pkg" ]; then return; fi; pkg_args="$pkg_args --pkg $pkg"; vapidir=$(pkg-config --variable=vapidir "$pkg" 2>/dev/null || true); if [ -n "$vapidir" ]; then vapidir_args="$vapidir_args --vapidir $vapidir"; fi; }; while [ "$#" -gt 0 ] && [ "$1" != "--" ]; do add_pkg "$1"; shift; done; for pkg in $native_packages; do add_pkg "$pkg"; done; if [ "${1:-}" = "--" ]; then shift; fi; header_args=; vapi_args=; if [ -n "$header" ]; then header_args="--header $builddir/$header"; fi; if [ -n "$vapi" ]; then vapi_args="--vapi $builddir/$vapi"; fi; "$valac" --debug -C $header_args $vapi_args $pkg_args $vapidir_args --directory "$builddir" "$@"; if [ -n "$vapi" ]; then vapi_path="$builddir/$vapi"; pkg="${vapi%.vapi}"; tmp="$vapi_path.tmp"; : > "$tmp"; ccode=""; while IFS= read -r line; do case "$line" in /*) echo "$line" >> "$tmp" ;; "[CCode"*) ccode="$line" ;; "public static "*) case "$line" in *"phpef_library_${pkg}_init"*|*"phpuserfunc_${pkg}_"*) if [ -n "$ccode" ]; then echo "$ccode" >> "$tmp"; fi; echo "$line" >> "$tmp" ;; esac; ccode="" ;; *) ccode="" ;; esac; done < "$vapi_path"; mv "$tmp" "$vapi_path"; fi; for inpath in "$@"; do base=$(basename "$inpath" .vala); rel="${inpath%.vala}.c"; actual="$builddir/$rel"; final="$builddir/$base.c"; if [ -f "$actual" ] && [ "$actual" != "$final" ]; then mv "$actual" "$final"; fi; done';
            $txt .= "\tcommand: ['sh', '-c', " . valaphp_meson_quote($valac_script) . ", 'sh', " . $tool_vars[$step["tool"]] . ", meson.current_build_dir(), " . $native_runtime_packages_var . ", " . valaphp_meson_quote((string)($step["header"] ?? "")) . ", " . valaphp_meson_quote((string)($step["vapi"] ?? ""));
            foreach($package_args as $package_arg){ $txt .= ", " . $package_arg; }
            $txt .= ", '--', '@INPUT@'],\n";
            if(count($local_vapi_depends) > 0){
                $txt .= "\tdepends: [" . join(", ", array_values(array_unique($local_vapi_depends))) . "],\n";
            }
            if((string)($step["header"] ?? "") !== "" || (string)($step["vapi"] ?? "") !== ""){
                $install_dirs = array();
                foreach($outputs as $output){
                    if($output === (string)($step["header"] ?? "")){ $install_dirs[] = $api_include_dir_var; }
                    elseif($output === (string)($step["vapi"] ?? "")){ $install_dirs[] = $vala_vapi_dir_var; }
                    else{ $install_dirs[] = "false"; }
                }
                $txt .= "\tinstall: true,\n";
                $txt .= "\tinstall_dir: [" . join(", ", $install_dirs) . "],\n";
            }
            $txt .= ")\n";
            phpef_register_output_refs($artifact_refs, $outputs, $step_id);
        }
        elseif($role === "rewrite_c_source_lines" || $role === "generate_stacktrace_map") {
            $inputs = $step["inputs"] ?? array();
            $outputs = $step["outputs"] ?? array();
            $input_refs = array();
            foreach($inputs as $input){ $input_refs[] = phpef_meson_input_ref($input, $artifact_refs); }
            $txt .= $step_id . " = custom_target(\n";
            $txt .= "\t" . valaphp_meson_quote($outputs[0] ?? $step_id) . ",\n";
            $txt .= "\tinput: [" . join(", ", $input_refs) . "],\n";
            $txt .= "\toutput: " . phpef_meson_string_array($outputs) . ",\n";
            $argv = array($tool_vars[$step["tool"]]);
            foreach(($step["argv"] ?? array('@INPUT0@', '@OUTPUT0@', '@INPUT1@', '@OUTPUT1@')) as $arg){ $argv[] = valaphp_meson_quote($arg); }
            $txt .= "\tcommand: [" . join(", ", $argv) . "],\n";
            $txt .= ")\n";
            foreach($outputs as $idx => $output){ $artifact_refs[$output] = count($outputs) === 1 ? $step_id : $step_id . "[" . $idx . "]"; }
        }
        elseif($role === "compile_vala_library"){
            $inputs = $step["inputs"] ?? array();
            $input_refs = array();
            foreach($inputs as $input){ $input_refs[] = phpef_meson_input_ref($input, $artifact_refs); }
            $output = ($step["outputs"] ?? array())[0] ?? $rule["dst"];
            $deps = array();
            $packages = array();
            foreach(($step["dependencies"] ?? array()) as $dep){
                $dep = (string)$dep;
                $deps[] = $dependency_vars[$dep] ?? ("dependency(" . valaphp_meson_quote($dep) . ", required: true)");
                if($dep !== ""){ $packages[] = $dep; }
            }
            $deps_expr = "[" . join(", ", $deps) . "]";
            if($native_runtime_dependencies_var !== "[]"){
                $deps_expr .= " + " . $native_runtime_dependencies_var;
            }
            $allowed_native_packages = phpef_native_runtime_packages_from_policy($rule["runtime-policy"] ?? phpef_current_runtime_policy());
            if(is_array($allowed_native_packages)){
                foreach($allowed_native_packages as $native_package){
                    if($native_package !== ""){ $packages[] = $native_package; }
                }
            }
            $packages = array_values(array_unique($packages));
            $vala_vapi_dir_var = valaphp_identifier($step_id . "_vala_vapi_dir");
            $api_include_dir_var = valaphp_identifier($step_id . "_api_include_dir");
            $pkgconfig_var = valaphp_identifier($step_id . "_pkgconfig");
            $txt .= $vala_vapi_dir_var . " = join_paths(get_option('datadir'), 'vala', 'vapi')\n";
            $txt .= $api_include_dir_var . " = join_paths(get_option('includedir'), " . valaphp_meson_quote($output) . ")\n";
            $txt .= $pkgconfig_var . " = import('pkgconfig')\n";
            $library_install_dir = (($rule["options"]["install-dir"] ?? "") !== "") ? valaphp_meson_quote($rule["options"]["install-dir"]) : "true";
            $txt .= $step_id . " = shared_library(\n";
            $txt .= "\t" . valaphp_meson_quote($output) . ",\n";
            $txt .= "\t" . phpef_meson_ref_list($input_refs) . ",\n";
            $txt .= "\tdependencies: " . $deps_expr . ",\n";
            $txt .= "\tvala_header: " . valaphp_meson_quote($output . ".h") . ",\n";
            $txt .= "\tvala_vapi: " . valaphp_meson_quote($output . ".vapi") . ",\n";
            $txt .= "\tbuild_by_default: " . phpef_meson_bool((bool)($step["build_by_default"] ?? true)) . ",\n";
            $txt .= "\tinstall: true,\n";
            $txt .= "\tinstall_dir: [" . $library_install_dir . ", " . $api_include_dir_var . ", " . $vala_vapi_dir_var . "],\n";
            $txt .= ")\n";
            $deps_args = array();
            foreach($packages as $package){ $deps_args[] = valaphp_meson_quote($package); }
            $txt .= "custom_target(\n";
            $txt .= "\t" . valaphp_meson_quote($output . "-deps") . ",\n";
            $txt .= "\toutput: " . valaphp_meson_quote($output . ".deps") . ",\n";
            $txt .= "\tcommand: ['sh', '-c', " . valaphp_meson_quote('output="$1"; shift; for dep do echo "$dep"; done > "$output"') . ", 'sh', '@OUTPUT@'";
            foreach($deps_args as $deps_arg){ $txt .= ", " . $deps_arg; }
            $txt .= "],\n";
            $txt .= "\tinstall: true,\n";
            $txt .= "\tinstall_dir: " . $vala_vapi_dir_var . ",\n";
            $txt .= "\tinstall_tag: 'devel',\n";
            $txt .= ")\n";
            $txt .= $pkgconfig_var . ".generate(\n";
            $txt .= "\t" . $step_id . ",\n";
            $txt .= "\tname: " . valaphp_meson_quote($output) . ",\n";
            $txt .= "\tdescription: " . valaphp_meson_quote("PHPEF library " . $output) . ",\n";
            $txt .= "\tversion: meson.project_version(),\n";
            $txt .= "\trequires: " . phpef_meson_string_array($public_packages) . ",\n";
                $txt .= "\trequires_private: " . phpef_meson_string_array($private_packages) . ",\n";
            $txt .= "\tsubdirs: " . valaphp_meson_quote($output) . ",\n";
            $txt .= ")\n";
            $artifact_refs[$output] = $step_id;
        }
        elseif($role === "compile_c_library"){
            $inputs = $step["inputs"] ?? array();
            $input_refs = array();
            foreach($inputs as $input){ $input_refs[] = phpef_meson_input_ref($input, $artifact_refs); }
            $output = ($step["outputs"] ?? array())[0] ?? ($rule["dst"] . ".raw");
            $deps = array();
            foreach(($step["dependencies"] ?? array()) as $dep){
                $dep = (string)$dep;
                $deps[] = $dependency_vars[$dep] ?? ("dependency(" . valaphp_meson_quote($dep) . ", required: true)");
            }
            $deps_expr = "[" . join(", ", $deps) . "]";
            if($native_runtime_dependencies_var !== "[]"){
                $deps_expr .= " + " . $native_runtime_dependencies_var;
            }
            $export_map_var = valaphp_identifier($step_id . "_exports_map");
            $export_map_content = "PHPEF_LIBRARY { global: " . phpef_library_init_function($rule["dst"]) . "; phpuserfunc_" . phpef_source_symbol_prefix($rule["dst"]) . "_*; local: *; };";
            $txt .= $export_map_var . " = configure_file(\n";
            $txt .= "\toutput: " . valaphp_meson_quote($rule["dst"] . ".exports.map") . ",\n";
            $txt .= "\tcommand: ['sh', '-c', " . valaphp_meson_quote('printf "%s\\n" "$1" > "$2"') . ", 'sh', " . valaphp_meson_quote($export_map_content) . ", '@OUTPUT@'],\n";
            $txt .= ")\n";
            $link_args_expr = "[" . valaphp_meson_quote("-Wl,-soname,lib" . $rule["dst"] . ".so") . ", '-Wl,--version-script=' + " . $export_map_var . ".full_path()]";
            $txt .= $step_id . " = shared_library(\n";
            $txt .= "\t" . valaphp_meson_quote($output) . ",\n";
            $txt .= "\t" . phpef_meson_ref_list($input_refs) . ",\n";
            $txt .= "\tdependencies: " . $deps_expr . ",\n";
            $txt .= "\tlink_args: " . $link_args_expr . ",\n";
            $txt .= "\tlink_depends: " . $export_map_var . ",\n";
            $txt .= "\tbuild_by_default: " . phpef_meson_bool((bool)($step["build_by_default"] ?? false)) . ",\n";
            $txt .= "\tinstall: false,\n";
            $txt .= ")\n";
            $artifact_refs[$output] = $step_id;
        }
        elseif($role === "compile_c_executable" || $role === "compile_vala_executable"){
            $inputs = $step["inputs"] ?? array();
            $input_refs = array();
            foreach($inputs as $input){ $input_refs[] = phpef_meson_input_ref($input, $artifact_refs); }
            $output = ($step["outputs"] ?? array())[0] ?? ($role === "compile_c_executable" ? $rule["dst"] . ".raw" : $rule["dst"]);
            $deps = array();
            foreach(($step["dependencies"] ?? array()) as $dep){
                $dep = (string)$dep;
                $deps[] = $dependency_vars[$dep] ?? ("dependency(" . valaphp_meson_quote($dep) . ", required: true)");
            }
            $local_link_with = array();
            foreach(($rule["phpef-libs"] ?? array()) as $phpef_lib){
                $phpef_lib = trim((string)$phpef_lib);
                if($phpef_lib === ""){ continue; }
                if(in_array($phpef_lib, ($rule["local-phpef-libs"] ?? array()), true)){
                    $local_link_with[] = phpef_library_target_var($phpef_lib);
                }
                else{
                    $deps[] = "dependency(" . valaphp_meson_quote($phpef_lib) . ", required: true)";
                }
            }
            $deps_expr = "[" . join(", ", $deps) . "]";
            if($native_runtime_dependencies_var !== "[]"){
                $deps_expr .= " + " . $native_runtime_dependencies_var;
            }
            $txt .= $step_id . " = executable(\n";
            $txt .= "\t" . valaphp_meson_quote($output) . ",\n";
            $txt .= "\t" . phpef_meson_ref_list($input_refs) . ",\n";
            $txt .= "\tdependencies: " . $deps_expr . ",\n";
            if(count($local_link_with) > 0){
                $txt .= "\tlink_with: [" . join(", ", $local_link_with) . "],\n";
            }
            $txt .= "\tbuild_by_default: " . phpef_meson_bool((bool)($step["build_by_default"] ?? false)) . ",\n";
            if($role === "compile_vala_executable"){
                $txt .= "\tinstall: true,\n";
                if($rule["options"]["install-dir"] !== ""){
                    $txt .= "\tinstall_dir: " . valaphp_meson_quote($rule["options"]["install-dir"]) . ",\n";
                }
                else{
                    $txt .= "\tinstall_dir: get_option('bindir'),\n";
                }
            }
            $txt .= ")\n";
            $artifact_refs[$output] = $step_id;
        }
        elseif($role === "add_resource_sections" || $role === "add_debug_section" || $role === "add_metadata_sections") {
            $inputs = $step["inputs"] ?? array();
            $outputs = $step["outputs"] ?? array();
            $input_refs = array();
            foreach($inputs as $input){ $input_refs[] = phpef_meson_input_ref($input, $artifact_refs); }
            $txt .= $step_id . " = custom_target(\n";
            $txt .= "\t" . valaphp_meson_quote($outputs[0] ?? $rule["dst"]) . ",\n";
            $txt .= "\tinput: [" . join(", ", $input_refs) . "],\n";
            $txt .= "\toutput: " . phpef_meson_output_list($outputs) . ",\n";
            $txt .= "\tbuild_by_default: " . phpef_meson_bool((bool)($step["build_by_default"] ?? true)) . ",\n";
            $argv = array($tool_vars[$step["tool"]]);
            foreach(($step["argv"] ?? array('--add-section', '.debug_phpef=@INPUT1@', '--set-section-flags', '.debug_phpef=debug,readonly', '@INPUT0@', '@OUTPUT@')) as $arg){ $argv[] = valaphp_meson_quote($arg); }
            $txt .= "\tcommand: [" . join(", ", $argv) . "],\n";
            $install_output = ($role !== "add_resource_sections" || (bool)($step["build_by_default"] ?? true));
            $is_library_metadata = (($descriptor["options"]["targetKind"] ?? "") === "library");
            if($install_output){
                $txt .= "\tinstall: true,\n";
                $install_dir_expr = "";
                if($is_library_metadata){
                    if($rule["options"]["install-dir"] !== ""){
                        $install_dir_expr = valaphp_meson_quote($rule["options"]["install-dir"]);
                    }
                    else{
                        $install_dir_expr = "get_option('libdir')";
                    }
                }
                else{
                    if($rule["options"]["install-dir"] !== ""){
                        $install_dir_expr = valaphp_meson_quote($rule["options"]["install-dir"]);
                    }
                    else{
                        $install_dir_expr = "get_option('bindir')";
                    }
                }
                if($role === "add_resource_sections" && count($outputs) > 1){
                    $install_dirs = array($install_dir_expr);
                    for($output_idx = 1; $output_idx < count($outputs); $output_idx++){
                        $install_dirs[] = "false";
                    }
                    $txt .= "\tinstall_dir: [" . join(", ", $install_dirs) . "],\n";
                }
                else{
                    $txt .= "\tinstall_dir: " . $install_dir_expr . ",\n";
                }
            }
            $txt .= ")\n";
            foreach($outputs as $idx => $output){ $artifact_refs[$output] = count($outputs) === 1 ? $step_id : $step_id . "[" . $idx . "]"; }
            if($role !== "add_resource_sections" && $is_library_metadata){
                $output = $rule["dst"];
                $packages = array();
                foreach(($descriptor["runtimeDependencies"] ?? array()) as $dep){
                    $dep = (string)$dep;
                    if($dep !== ""){ $packages[] = $dep; }
                }
                $native_packages = phpef_native_runtime_packages_from_policy($rule["runtime-policy"] ?? phpef_current_runtime_policy());
                if(is_array($native_packages)){
                    foreach($native_packages as $dep){
                        if($dep !== ""){ $packages[] = $dep; }
                    }
                }
                $packages = array_values(array_unique($packages));
                $public_packages = array();
                $private_packages = array();
                foreach($packages as $package){
                    if($package === "phpef-runtime-core"){ $public_packages[] = $package; }
                    else{ $private_packages[] = $package; }
                }
                $vala_vapi_dir_var = valaphp_identifier($step_id . "_vala_vapi_dir");
                $api_include_dir_var = valaphp_identifier($step_id . "_api_include_dir");
                $pkgconfig_var = valaphp_identifier($step_id . "_pkgconfig");
                $txt .= $vala_vapi_dir_var . " = join_paths(get_option(" . valaphp_meson_quote("datadir") . "), " . valaphp_meson_quote("vala") . ", " . valaphp_meson_quote("vapi") . ")\n";
                $txt .= $api_include_dir_var . " = join_paths(get_option(" . valaphp_meson_quote("includedir") . "), " . valaphp_meson_quote($output) . ")\n";
                $deps_script = "output=\"\$1\"; shift; for dep do echo \"\$dep\"; done > \"\$output\"";
                $deps_args = array();
                foreach($packages as $package){ $deps_args[] = valaphp_meson_quote($package); }
                $txt .= "custom_target(\n";
                $txt .= "\t" . valaphp_meson_quote($output . "-deps") . ",\n";
                $txt .= "\toutput: " . valaphp_meson_quote($output . ".deps") . ",\n";
                $txt .= "\tcommand: [" . valaphp_meson_quote("sh") . ", " . valaphp_meson_quote("-c") . ", " . valaphp_meson_quote($deps_script) . ", " . valaphp_meson_quote("sh") . ", " . valaphp_meson_quote("@OUTPUT@");
                foreach($deps_args as $deps_arg){ $txt .= ", " . $deps_arg; }
                $txt .= "],\n";
                $txt .= "\tinstall: true,\n";
                $txt .= "\tinstall_dir: " . $vala_vapi_dir_var . ",\n";
                $txt .= "\tinstall_tag: " . valaphp_meson_quote("devel") . ",\n";
                $txt .= ")\n";
                $txt .= $pkgconfig_var . " = import(" . valaphp_meson_quote("pkgconfig") . ")\n";
                $txt .= $pkgconfig_var . ".generate(\n";
                $txt .= "\tname: " . valaphp_meson_quote($output) . ",\n";
                $txt .= "\tdescription: " . valaphp_meson_quote("PHPEF library " . $output) . ",\n";
                $txt .= "\tfilebase: " . valaphp_meson_quote($output) . ",\n";
                $txt .= "\tlibraries: " . phpef_meson_string_array(array("-l" . $output)) . ",\n";
                $txt .= "\tversion: meson.project_version(),\n";
                $txt .= "\trequires: " . phpef_meson_string_array($public_packages) . ",\n";
                $txt .= "\trequires_private: " . phpef_meson_string_array($private_packages) . ",\n";
                $txt .= "\tsubdirs: " . valaphp_meson_quote($output) . ",\n";
                $txt .= ")\n";
            }
        }
        else{
            echo "pw-build-meson: Unsupported PHPEF pipeline role ".$role."\r\n";
            exit(1);
        }
    }

    foreach(($descriptor["tests"] ?? array()) as $test){
        if(($rule["support-sources"] ?? array()) !== array() && ($test["role"] ?? "") === "roundtrip_compare"){
            continue;
        }
        if(($test["role"] ?? "") !== "roundtrip_compare"){
            echo "pw-build-meson: Unsupported PHPEF test role ".($test["role"] ?? "")."\r\n";
            exit(1);
        }
        $tool = $test["tool"] ?? "phpefc-compare-tokenized-files";
        if(!isset($tool_vars[$tool])){
            $tool_var = valaphp_identifier($id . "_tool_" . $tool);
            $txt .= $tool_var . " = find_program(" . valaphp_meson_quote($tool) . ", required: true)\n";
            $tool_vars[$tool] = $tool_var;
        }
        $args = array();
        foreach(($test["args"] ?? array()) as $arg){ $args[] = phpef_meson_arg_ref($arg, $artifact_refs); }
        $depends = array();
        foreach(($test["depends"] ?? array()) as $dep){ $depends[] = phpef_meson_input_ref($dep, $artifact_refs); }
        $txt .= "test(\n";
        $txt .= "\t" . valaphp_meson_quote($test["name"] ?? ($rule["dst"] . " roundtrip")) . ",\n";
        $txt .= "\t" . $tool_vars[$tool] . ",\n";
        $txt .= "\targs: [" . join(", ", $args) . "],\n";
        if(count($depends) > 0){ $txt .= "\tdepends: [" . join(", ", $depends) . "],\n"; }
        $txt .= "\ttimeout: " . intval($test["timeout"] ?? 60) . ",\n";
        $txt .= ")\n";
    }

    return($txt);
}

function phpef_meson_input_ref($artifact, $artifact_refs){
    if(isset($artifact_refs[$artifact])){ return($artifact_refs[$artifact]); }
    return("files(" . valaphp_meson_quote($artifact) . ")");
}

function phpef_meson_ref_list($refs){
    if(count($refs) === 1){
        return($refs[0]);
    }
    return("[" . join(", ", $refs) . "]");
}

function phpef_meson_output_list($outputs){
    if(count($outputs) === 1){
        return(valaphp_meson_quote($outputs[0]));
    }
    return(phpef_meson_string_array($outputs));
}

function phpef_register_output_refs(&$artifact_refs, $outputs, $step_id){
    foreach($outputs as $idx => $output){
        $artifact_refs[$output] = count($outputs) === 1 ? $step_id : $step_id . "[" . $idx . "]";
    }
}

function phpef_meson_arg_ref($artifact, $artifact_refs){
    if(isset($artifact_refs[$artifact])){
        return($artifact_refs[$artifact]);
    }
    return(valaphp_meson_quote($artifact));
}


function phpef_meson_string_array($values){
    $quoted = array();
    foreach($values as $value){ $quoted[] = valaphp_meson_quote($value); }
    return("[" . join(", ", $quoted) . "]");
}

function phpef_meson_bool($value){
    return($value ? "true" : "false");
}

function is_valaphp_rule_key($key){
    $base_key = valaphp_rule_base_key($key);
    return(in_array($base_key, array(
        "vlib",
        "vlib-native",
        "vlib-embed",
        "vlib-pwphp",
        "vbin",
        "vbin-native",
        "vbin-embed",
        "vbin-pwphp",
    )));
}

function valaphp_rule_base_key($key){
    $key = trim($key);
    if(str_contains($key, "(")){
        $parts = explode("(", $key, 2);
        $key = trim($parts[0]);
    }
    return($key);
}

function valaphp_rule_install_dir($key){
    $key = trim($key);
    if(!str_contains($key, "(") || !str_contains($key, ")")){
        return("");
    }
    $parts = explode("(", $key, 2);
    $inner = explode(")", $parts[1], 2);
    return(trim($inner[0]));
}

function valaphp_meson_prelude(){
    $txt = "";
    $txt .= "valaphp_php2vala = find_program('php2vala')\n";
    $txt .= "valaphp_pkg = import('pkgconfig')\n";
    $txt .= "valaphp_cc = meson.get_compiler('c')\n";
    $txt .= "valaphp_c_args = valaphp_cc.get_supported_arguments([\n";
    $txt .= "\t'-Wno-discarded-qualifiers',\n";
    $txt .= "\t'-Wno-unused-but-set-variable',\n";
    $txt .= "\t'-Wno-unused-parameter',\n";
    $txt .= "\t'-Wno-unused-variable',\n";
    $txt .= "])\n";
    $txt .= "valaphp_vapi_dir = join_paths(get_option('datadir'), 'vala', 'vapi')\n";
    $txt .= "if not is_variable('project_description')\n";
    $txt .= "\tproject_description = meson.project_name()\n";
    $txt .= "endif\n";
    return($txt);
}

function add_valaphp_rule($param){
    $key = valaphp_rule_base_key($param[0]);
    $rule = parse_valaphp_rule($param);
    if($key === "vlib" || str_starts_with($key, "vlib-")){
        return(add_valaphp_lib($rule));
    }
    if($key === "vbin" || str_starts_with($key, "vbin-")){
        return(add_valaphp_bin($rule));
    }
    echo "pw-build-meson: Unknown valaphp rule ".$key."\r\n";
    exit(1);
}

function parse_valaphp_rule($param){
    if(!isset($param[1])){
        echo "pw-build-meson: Missing right side in param:\r\n";
        print_r($param);
        exit(1);
    }

    $key = valaphp_rule_base_key($param[0]);
    $install_dir = valaphp_rule_install_dir($param[0]);
    $parts = explode("-", $key, 2);
    $kind = $parts[0] ?? "";
    $mode = $parts[1] ?? "both";
    if(!in_array($kind, array("vlib", "vbin")) || !in_array($mode, array("native", "embed", "pwphp", "both"))){
        echo "pw-build-meson: Invalid valaphp rule ".$key."\r\n";
        exit(1);
    }

    $tmp1 = explode("->", $param[1], 2);
    if(count($tmp1) !== 2){
        echo "pw-build-meson: Missing -> in param:\r\n";
        print_r($param);
        exit(1);
    }

    $src = trim($tmp1[0]);
    $dst_and_deps = trim($tmp1[1]);
    $options = valaphp_parse_options($dst_and_deps);
    $tmp2 = explode("(", $dst_and_deps, 2);
    $dst = trim($tmp2[0]);
    if(empty($src) || empty($dst)){
        echo "pw-build-meson: Missing source or destination in param:\r\n";
        print_r($param);
        exit(1);
    }

    $deps = array();
    if(isset($tmp2[1])){
        $tmp3 = explode(")", $tmp2[1], 2);
        foreach(explode(",", $tmp3[0]) as $dep){
            $dep = trim($dep);
            if(!empty($dep)){
                $deps[] = $dep;
            }
        }
    }

    $deps = valaphp_apply_default_dependencies($kind, $mode, $deps);

    return(array(
        "kind" => $kind,
        "mode" => $mode,
        "src" => $src,
        "dst" => $dst,
        "deps" => $deps,
        "optimizer" => $options["optimizer"],
        "install_dir" => $install_dir,
    ));
}

function valaphp_parse_options(&$value){
    $options = array(
        "optimizer" => 4,
    );

    if(!preg_match('/\s*\[([^\]]*)\]\s*$/', $value, $match)){
        return($options);
    }

    $value = trim(substr($value, 0, -strlen($match[0])));
    foreach(explode(",", $match[1]) as $raw_option){
        $raw_option = trim($raw_option);
        if($raw_option === ""){
            continue;
        }

        $parts = explode("=", $raw_option, 2);
        $name = trim($parts[0]);
        $option_value = isset($parts[1]) ? trim($parts[1]) : "";
        if($name !== "opt"){
            echo "pw-build-meson: Unknown valaphp option ".$name."\r\n";
            exit(1);
        }
        if(!preg_match('/^[1-4]$/', $option_value)){
            echo "pw-build-meson: Invalid valaphp optimizer level ".$option_value." (expected opt=1..4)\r\n";
            exit(1);
        }
        $options["optimizer"] = intval($option_value);
    }

    return($options);
}

function valaphp_apply_default_dependencies($kind, $mode, $deps){
    $defaults = array();
    if($kind === "vbin" || $kind === "vlib"){
        $defaults[] = "gee-0.8";
        $defaults[] = "gio-2.0";
        $defaults[] = "valaphp-runtime-core";
        if($mode === "native"){
            $defaults[] = "valaphp-runtime-std";
        } elseif($mode === "both"){
            $defaults[] = "valaphp-runtime-std";
            $defaults[] = "valaphp-runtime-embed";
        } else {
            $defaults[] = "valaphp-runtime-embed";
        }
    }

    if($mode === "pwphp"){
        $defaults[] = "valaphp-runtime-embed-pwphpbytecode";
    }

    foreach($defaults as $dep){
        if(!in_array($dep, $deps)){
            $deps[] = $dep;
        }
    }
    $normalized_deps = array();
    $for_binary = ($kind === "vbin");
    foreach($deps as $dep){
        $normalized_dep = valaphp_dependency_name($dep, $for_binary);
        if(!in_array($normalized_dep, $normalized_deps)){
            $normalized_deps[] = $normalized_dep;
        }
    }
    return($normalized_deps);
}

function valaphp_runtime_arg($mode){
    if($mode === "native"){
        return("--native-runtime");
    }
    if($mode === "both"){
        return("--both-runtimes");
    }
    return("--embed-runtime");
}

function valaphp_optimizer_command_lines($level){
    switch($level){
        case 1:
            return("\t\t'--no-optimize-local-vars',\n");
        case 2:
            return("\t\t'--optimize-local-vars',\n");
        case 3:
            return("\t\t'--optimize-file-func-vars',\n");
        case 4:
            return("\t\t'--optimize-cross-file-func-vars',\n");
    }

    echo "pw-build-meson: Invalid valaphp optimizer level ".$level." (expected 1..4)\r\n";
    exit(1);
}

function valaphp_identifier($value){
    $value = preg_replace('/[^A-Za-z0-9_]/', '_', $value);
    if($value === "" || preg_match('/^[0-9]/', $value)){
        $value = "valaphp_" . $value;
    }
    return($value);
}

function valaphp_meson_quote($value){
    return("'" . str_replace("'", "\\'", $value) . "'");
}

function valaphp_pkg_config_exists($package_name){
    static $cache = array();
    if(isset($cache[$package_name])){
        return($cache[$package_name]);
    }

    $output = array();
    $exit_code = 1;
    exec("pkg-config --exists " . escapeshellarg($package_name), $output, $exit_code);
    $cache[$package_name] = ($exit_code === 0);
    return($cache[$package_name]);
}

function valaphp_runtime_dependency_alias($dep){
    $dep = trim($dep);
    if($dep === "" || str_starts_with($dep, "valaphp-runtime-")){
        return($dep);
    }

    $runtime_dep = "valaphp-runtime-" . $dep;
    if(valaphp_pkg_config_exists($runtime_dep)){
        return($runtime_dep);
    }
    return($dep);
}

function valaphp_dependency_name($dep, $for_binary){
    $dep = trim($dep);
    if($for_binary && str_starts_with($dep, "lib")){
        return("valaphp-" . $dep);
    }
    return(valaphp_runtime_dependency_alias($dep));
}

function valaphp_dependency_expr($dep, $for_binary){
    $dep = trim($dep);
    if($for_binary && preg_match('/^pw[A-Za-z0-9_]*$/', $dep)){
        return("valaphp_cc.find_library(" . valaphp_meson_quote($dep . "-") . " + phpslot + '-' + abiver, required: true)");
    }
    return("dependency(" . valaphp_meson_quote(valaphp_dependency_name($dep, $for_binary)) . ")");
}

function valaphp_dependency_array($deps, $for_binary){
    $exprs = array();
    foreach($deps as $dep){
        $exprs[] = valaphp_dependency_expr($dep, $for_binary);
    }
    return("[" . join(", ", $exprs) . "]");
}

function valaphp_string_array($values, $for_binary = false){
    $exprs = array();
    foreach($values as $value){
        $exprs[] = valaphp_meson_quote(valaphp_dependency_name($value, $for_binary));
    }
    return("[" . join(", ", $exprs) . "]");
}

function add_valaphp_lib($rule){
    $id = valaphp_identifier($rule["dst"]);
    $vala_output = $id . ".vala";
    $stub_output = $rule["dst"] . ".stub.php";
    $deps = valaphp_dependency_array($rule["deps"], false);
    $dep_names = valaphp_string_array($rule["deps"]);
    $runtime_arg = valaphp_runtime_arg($rule["mode"]);
    $optimizer_args = valaphp_optimizer_command_lines($rule["optimizer"]);

    $txt = "";
    $txt .= $id . "_api_name = " . valaphp_meson_quote($rule["dst"]) . "\n";
    $txt .= $id . "_public_vapi_pkg_names = " . $dep_names . "\n";
    $txt .= $id . "_deps = " . $deps . "\n";
    $txt .= $id . "_include_dir = join_paths(get_option('includedir'), " . $id . "_api_name)\n";
    $txt .= $id . "_stub_dir = join_paths(get_option('datadir'), 'valaphp', " . $id . "_api_name)\n";
    $txt .= $id . "_generated_outputs = custom_target(\n";
    $txt .= "\t" . valaphp_meson_quote($rule["dst"] . "-vala") . ",\n";
    $txt .= "\tinput: " . valaphp_meson_quote($rule["src"]) . ",\n";
    $txt .= "\toutput: [" . valaphp_meson_quote($vala_output) . ", " . valaphp_meson_quote($stub_output) . "],\n";
    $txt .= "\tcommand: [\n";
    $txt .= "\t\tvalaphp_php2vala,\n";
    $txt .= "\t\t'--library',\n";
    $txt .= "\t\t'--php-stub-out',\n";
    $txt .= "\t\t'@OUTPUT1@',\n";
    $txt .= "\t\t" . valaphp_meson_quote($runtime_arg) . ",\n";
    $txt .= $optimizer_args;
    $txt .= "\t\t'@INPUT@',\n";
    $txt .= "\t\t'@OUTPUT0@',\n";
    $txt .= "\t],\n";
    $txt .= "\tinstall: true,\n";
    $txt .= "\tinstall_dir: [false, " . $id . "_stub_dir],\n";
    $txt .= ")\n";
    $txt .= $id . "_lib = library(\n";
    $txt .= "\t" . $id . "_api_name,\n";
    $txt .= "\t" . $id . "_generated_outputs,\n";
    $txt .= "\tdependencies: " . $id . "_deps,\n";
    $txt .= "\tc_args: valaphp_c_args,\n";
    $txt .= "\tvala_header: " . $id . "_api_name + '.h',\n";
    $txt .= "\tvala_vapi: " . $id . "_api_name + '.vapi',\n";
    $txt .= "\tinstall: true,\n";
    $txt .= "\tinstall_dir: [true, " . $id . "_include_dir, valaphp_vapi_dir],\n";
    $txt .= ")\n";
    $txt .= "custom_target(\n";
    $txt .= "\t" . $id . "_api_name + '-deps',\n";
    $txt .= "\toutput: " . $id . "_api_name + '.deps',\n";
    $txt .= "\tcommand: [\n";
    $txt .= "\t\t'sh',\n";
    $txt .= "\t\t'-c',\n";
    $txt .= "\t\t'output=\"\$1\"; shift; for dep do echo \"\$dep\"; done > \"\$output\"',\n";
    $txt .= "\t\t'sh',\n";
    $txt .= "\t\t'@OUTPUT@',\n";
    $txt .= "\t] + " . $id . "_public_vapi_pkg_names,\n";
    $txt .= "\tinstall: true,\n";
    $txt .= "\tinstall_dir: valaphp_vapi_dir,\n";
    $txt .= ")\n";
    $txt .= "valaphp_pkg.generate(\n";
    $txt .= "\t" . $id . "_lib,\n";
    $txt .= "\tname: " . $id . "_api_name,\n";
    $txt .= "\tdescription: project_description,\n";
    $txt .= "\tversion: meson.project_version(),\n";
    $txt .= "\trequires: " . $id . "_public_vapi_pkg_names,\n";
    $txt .= "\tsubdirs: " . $id . "_api_name,\n";
    $txt .= ")\n";
    return($txt);
}

function add_valaphp_bin($rule){
    $id = valaphp_identifier($rule["dst"]);
    $vala_output = $id . ".vala";
    $deps = valaphp_dependency_array($rule["deps"], true);
    $runtime_arg = valaphp_runtime_arg($rule["mode"]);
    $optimizer_args = valaphp_optimizer_command_lines($rule["optimizer"]);

    $txt = "";
    $txt .= $id . "_generated_output = custom_target(\n";
    $txt .= "\t" . valaphp_meson_quote($rule["dst"] . "-vala") . ",\n";
    $txt .= "\tinput: " . valaphp_meson_quote($rule["src"]) . ",\n";
    $txt .= "\toutput: " . valaphp_meson_quote($vala_output) . ",\n";
    $txt .= "\tcommand: [\n";
    $txt .= "\t\tvalaphp_php2vala,\n";
    $txt .= "\t\t" . valaphp_meson_quote($runtime_arg) . ",\n";
    $txt .= $optimizer_args;
    $txt .= "\t\t'@INPUT@',\n";
    $txt .= "\t\t'@OUTPUT@',\n";
    $txt .= "\t],\n";
    $txt .= ")\n";
    $txt .= $id . "_bin = executable(\n";
    $txt .= "\t" . valaphp_meson_quote($rule["dst"]) . ",\n";
    $txt .= "\t" . $id . "_generated_output,\n";
    $txt .= "\tdependencies: " . $deps . ",\n";
    $txt .= "\tc_args: valaphp_c_args,\n";
    $txt .= "\tinstall: true,\n";
    if($rule["install_dir"] !== ""){
        $txt .= "\tinstall_dir: " . valaphp_meson_quote($rule["install_dir"]) . ",\n";
    }
    $txt .= ")\n";
    return($txt);
}

function scan_for_subs(){
    $ret=array();
    $opendir=opendir(".");
    while($file=readdir($opendir)){
        if($file=="." || $file=="..") continue;
        if(file_exists($file."/meson.build") || file_exists($file."/meson.build.pw")){
            $ret[]=$file;
        }
    }
    closedir($opendir);
    return($ret);
}
function add_etc($param){
    // Catch filesystem target name of binary
    $tmp1 = explode("->",$param[1]);
    $tmp3 = explode("(",$tmp1[1]);
    $dst=trim($tmp3[0]);
    if(empty($dst)){
        echo "pw-build-meson: "."Missing destination in param:\r\n";
        print_r($param);
        exit(1);
    }

    // Catch etc-config-path
    $tmp1 = explode("(",$param[0]);
    $tmp2 = explode(")",$tmp1[1]);
    $etcfile = $tmp2[0];
    if(empty($etcfile)){
        echo "pw-build-meson: "."Missing etcfile in param:\r\n";
        print_r($param);
        exit(1);
    }

    // Build executable
    $path_to_calling="/usr/libexec/pw-etc-adjustments";
    $meson = add_bin(array("bin",$param[1]),$path_to_calling);

    // Build hook for etc-autoupdate
    $tmpsourcename=$dst.".etc-hook.meson-build".".php";

    $identifier=str_replace(".","_",$dst);
    $identifier=str_replace("-","_",$identifier);
    $meson.=$identifier."_etc_autoupdater_php = custom_target(\n";
    $meson.="\t'".$tmpsourcename."',\n";
    $meson.="\tcommand: [mycompiler_etcstub, '".$dst."', '".$etcfile."', '".$path_to_calling."/".$dst."'],\n";
    $meson.="\tcapture: true,\n";
    $meson.="\toutput: '".$tmpsourcename."',\n";
    $meson.=")\n";

    $meson.="strip_php_result_etc_autoupdate = custom_target(\n";
    $meson.="\t'".$dst."',\n";
    $meson.="\tinput: [".$identifier."_etc_autoupdater_php],\n";
    $meson.="\toutput: ['".$dst.".etc-hook.php'],\n";
    $meson.="\tcommand: [mycompiler_strip,'@INPUT@','@OUTPUT@'],\n";
    $meson.="\tinstall:true,\n";
    $meson.="\tinstall_mode: 'rw-r--r--',\n";
    $meson.="\tinstall_dir: '/etc/etc-autoupdate'\n";
    $meson.=")\n";


    return($meson);
}
function add_bin($param,$installdir=""){
    global $lib_mesonlib;
    $tmp1 = explode("->",$param[1]);
    $tmp3 = explode("(",$tmp1[1]);

    $src_specs = phpef_parse_sources(trim($tmp1[0]));
    $src=trim($src_specs[0] ?? "");
    $src_depend_files=array_slice($src_specs, 1);
    if(empty($src)){
        echo "pw-build-meson: "."Missing source in param:\r\n";
        print_r($param);
        exit(1);
    }
    $dst=trim($tmp3[0]);

    $jit=false;
    if(trim($param[0])=="bin-jit")$jit=true;

    // Catch installdir from param[0]
    if(empty($installdir) && str_contains($param[0],"(") && str_contains($param[0],")")) {
        $tmp4 = explode("(", $param[0]);
        $tmp2 = explode(")", $tmp4[1]);
        $installdir = $tmp2[0];
        if(trim($tmp4[0])=="bin-jit")$jit=true;
    }


    $libs=array();
    $elflibs=array();
    $mesonlibs=array();
    $mesonlibs_h=array();
    if(isset($tmp3[1])){
        $tmp2 = explode(")",$tmp3[1]);
        $libs = explode(",",$tmp2[0]);
    }
    $rule_name = trim($param[0]);
    if(str_contains($rule_name,"(")){
        $rule_name = trim(explode("(",$rule_name)[0]);
    }
    foreach($libs as $lib){
        $lib = trim($lib);
        if(empty($lib)){
            continue;
        }
        $exp = explode(":",$lib);
        $dep_name = trim($exp[0]);
        if(str_starts_with($dep_name,"valaphp")){
            echo "pw-build-meson: Invalid dependency '".$dep_name."' for ".$rule_name." rule '".$dst."': bin/bin-jit use pwphpbytecode and cannot link VALAPHP libraries. Use a vbin/vlib rule for VALAPHP dependencies.\r\n";
            exit(1);
        }
        if(is_array($lib_mesonlib) && in_array($dep_name,$lib_mesonlib)){
            $mesonlibs[]=$dep_name.load_pwphpwrapper_suffix_fine()."_lib";
            $mesonlibs_h[]=$dep_name."_h";
        }
        else{
            $elflibs[]="dependency('".trim($dep_name."-".load_pwphpwrapper_suffix())."')";
        }
    }

    $txt = "";

    $compiler="mycompiler";
    $jit_append="";
    if($jit)$jit_append="'--enable-jit'";

    if($src == "pw-build-meson.php" && $dst == "pw-build-meson"){
        $txt .= "exe_php_src = custom_target(\n";
        $txt .= "\t'pw-build-meson-versioned-php',\n";
        $txt .= "\tinput: files( '" . $src . "' ),\n";
        $txt .= "\toutput: 'pw-build-meson.versioned.php',\n";
        $txt .= "\tcommand: ['sh', '-c', 'sed \"s/__PW_BUILD_MESON_VERSION__/\$1/g\" \"\$2\" > \"\$3\"', 'sh', meson.project_version(), '@INPUT@', '@OUTPUT@'],\n";
        $txt .= ")\n";
    }
    else{
        $txt .= "exe_php_src = files( '" . $src . "' )\n";
    }
    if(count($src_depend_files) > 0){
        $quoted_depend_files = array();
        foreach($src_depend_files as $src_depend_file){
            $quoted_depend_files[] = valaphp_meson_quote($src_depend_file);
        }
        $txt .= "exe_php_depend_files = files(" . join(", ", $quoted_depend_files) . ")\n";
    }
    $txt .= "exe_c = ['".$src.".c']\n";
    $txt .= "exe_c_sources = custom_target(\n";
    $txt .= "\texe_c,\n";
    $txt .= "\tinput: exe_php_src,\n";
    if(count($src_depend_files) > 0){
        $txt .= "\tdepend_files: exe_php_depend_files,\n";
    }
    $txt .= "\toutput: [exe_c],\n";
    if (count($libs) > 0) {
        $txt .= "\tcommand: [".$compiler.",'@INPUT@','@OUTPUT@','" . join("','", $libs) . "',".$jit_append."]\n";
    } else {
        $txt .= "\tcommand: [".$compiler.",'@INPUT@','@OUTPUT@',".$jit_append."]\n";
    }
    $txt .= ")\n";
    $txt .= "exe_bin = executable('" . $dst . "',\n";
    $txt .= "\texe_c_sources,\n";
    $txt .= "\tdependencies: [pwphpbytecode_dependency, " . join(", ", $elflibs) . "],\n";
    if (count($mesonlibs) > 0) {
        $txt .= "\tlink_with: [" . join(", ", $mesonlibs) . "],\n";
        $txt .= "\tsources: [" . join(", ", $mesonlibs_h) . "],\n";
    }
    $txt .= "\tinstall:true,\n";
    if (!empty($installdir)) {
        $txt .= "\tinstall_dir: '" . $installdir . "'\n";
    }
    $txt .= ")\n";
    return($txt);
}
function add_strip($param,$installdir="",$exec=true){
    $tmp1 = explode("->",$param[1]);
    $tmp3 = explode("(",$tmp1[1]);

    $src=trim($tmp1[0]);
    $dst=trim($tmp3[0]);

    // Catch installdir from param[0]
    if(empty($installdir) && str_contains($param[0],"(") && str_contains($param[0],")")) {
        $tmp1 = explode("(", $param[0]);
        $tmp2 = explode(")", $tmp1[1]);
        $installdir = $tmp2[0];
        $exec=false;
    }

    $libs=array();
    if(isset($tmp3[1])){
        $tmp2 = explode(")",$tmp3[1]);
        $libs = explode(",",$tmp2[0]);
    }
    if(count($libs)>0){
        echo "pw-build-meson: "."Stripping not allows libs:\r\n";
        print_r($param);
        exit(1);
    }

    $txt="";
    $txt.="strip_php_sources = files( '".$src."' )\n";
    $txt.="strip_php_result = custom_target(\n";
    $txt.="\t'".$dst."',\n";
    $txt.="\tinput: [strip_php_sources],\n";
    $txt.="\toutput: ['".$dst."'],\n";
    $txt.="\tcommand: [mycompiler_strip,'@INPUT@','@OUTPUT@'],\n";
    $txt.="\tinstall:true,\n";
    if($exec){
        $txt.="\tinstall_mode: 'rwxr-xr-x',\n";
    }
    else{
        $txt.="\tinstall_mode: 'rw-r--r--',\n";
    }
    if(!empty($installdir)){
        $txt.="\tinstall_dir: '".$installdir."'\n";
    }
    else{
        $txt.="\tinstall_dir: '/usr/bin'\n";
    }
    $txt.=")\n";
    return($txt);
}

function add_lib($param){
    global $lib_elflibs,$lib_elflibs_raw,$lib_mesonlib,$lib_elflibs_intern,$class_pwphpbytecode_stub_files;
    $tmp1 = explode("->",$param[1]);
    $tmp3 = explode("(",$tmp1[1]);

    $srcs=trim($tmp1[0]);
    $dst=trim($tmp3[0]);
    $dst_withver=trim($tmp3[0]).load_pwphpwrapper_suffix_fine();
    $dst_withverfile=trim($tmp3[0])."-".load_pwphpwrapper_suffix();

    $libs=array();
    $elflibs=array();
    $elflibs_raw=array();
    $mesonlibs=array();
    if(isset($tmp3[1])){
        $tmp2 = explode(")",$tmp3[1]);
        $libs = explode(",",$tmp2[0]);
    }
    foreach($libs as $lib){
        echo "Dependencies not allowed on libs, add dependency directly to class!\r\n";
        exit(1);
    }

    $txt="";
    $srcs = explode(",",$srcs);
    foreach($srcs as $src){
        if(isset($lib_elflibs[$src])){
            foreach($lib_elflibs[$src] as $thislib){
                $elflibs[]=$thislib;
            }
            foreach($lib_elflibs_raw[$src] as $thislib){
                $elflibs_raw[]=$thislib;
            }
        }
        if(isset($lib_elflibs_intern[$src])){
            foreach($lib_elflibs_intern[$src] as $thislib){
                $mesonlibs[]=$thislib;
            }
        }
    }

    // Build library Default version
    $txt.=$dst_withver."_lib = shared_library('".$dst_withverfile."',\n";
    $txt.="\t[\n";
    foreach($srcs as $src){
        $txt.="\t\t".$src."_c_sources,\n";
    }
    $txt.="\t],\n";
    $txt.="\tdependencies: [pwphpbytecode_dependency, ".join(", ",$elflibs)."],\n";
    if(count($mesonlibs)>0){
        $txt.="\tlink_with: [".join(", ",$mesonlibs)."],\n";
    }
    $txt.="\tinstall:true,\n";
    $txt.=")\n";


    // Build header file
    $txt.="parts_of_global_header_file = [";
    foreach($srcs as $src){
        $txt.=$src."_h, ";
    }
    $txt.="]\n";
    $txt.=$dst."_h = custom_target(\n";
    $txt.="\t'".$dst.".h',\n";
    $txt.="\tcommand: [cat, '@INPUT@'],\n";
    $txt.="\tcapture: true,\n";
    $txt.="\tinput: parts_of_global_header_file,\n";
    $txt.="\toutput: '".$dst.".h',\n";
    $txt.="\tinstall_dir: get_option('includedir')+'/pwphp_".load_pwphpwrapper_suffix()."',\n";
    $txt.="\tinstall: true,\n";
    $txt.=")\n";

    // Build pkg-config
    $txt.="pkg = import('pkgconfig')\n";
    $txt.="pkg.generate(".$dst_withver."_lib, subdirs: 'pwphp_".load_pwphpwrapper_suffix()."')\n";

    foreach($srcs as $src){
        if(isset($class_pwphpbytecode_stub_files[$src])){
            $stub_spec = $class_pwphpbytecode_stub_files[$src];
            $stub_file = $stub_spec["output"];
            $stub_target = $src . "_pwphpbytecode_stub";
            $txt .= $stub_target . " = custom_target(\n";
            $txt .= "\t'" . $src . "-pwphpbytecode-stub',\n";
            $txt .= "\tinput: [" . $src . "_php_src],\n";
            $txt .= "\toutput: " . valaphp_meson_quote($stub_file) . ",\n";
            $txt .= "\tcommand: [_pw_build_meson_program, '--generate-stub', '@INPUT@', '@OUTPUT@', " . valaphp_meson_quote($stub_spec["class"]) . ", " . valaphp_meson_quote($dst_withverfile) . "],\n";
            $txt .= "\tinstall: true,\n";
            $txt .= "\tinstall_dir: get_option('datadir')+'/pwphpbytecode-stubs/" . $dst_withverfile . "'\n";
            $txt .= ")\n";
        }
    }

    if(true) { //TODO: should be special activatable - not always true (also non-raw should be special activatable!)
        // Build library RAW (for CLI/FPM) Version
        $dst_withver_raw=trim($tmp3[0])."_raw".load_pwphpwrapper_suffix_fine();
        $dst_withverfile_raw=trim($tmp3[0])."_raw-".load_pwphpwrapper_suffix();

        $txt .= $dst_withver_raw . "_lib = shared_library('" . $dst_withverfile_raw . "',\n";
        $txt .= "\t[\n";
        foreach ($srcs as $src) {
            $txt .= "\t\t" . $src . "_c_sources,\n";
        }
        $txt .= "\t],\n";
        $txt .= "\tdependencies: [pwphpbytecode_dependency_raw, " . join(", ", $elflibs_raw) . "],\n";
        if (count($mesonlibs) > 0) {
            $txt .= "\tlink_with: [" . join(", ", $mesonlibs) . "],\n";
        }
        $txt .= "\tinstall:true,\n";
        $txt .= ")\n";

        // Build pkg-config
        $txt.="pkg.generate(".$dst_withver_raw."_lib, subdirs: 'pwphp_".load_pwphpwrapper_suffix()."')\n";
    }

    // add for internal reference
    if(!is_array($lib_mesonlib)){
        $lib_mesonlib=array();
    }
    $lib_mesonlib[]=$dst;
    return($txt);
}

function add_class($param){
    global $lib_elflibs,$lib_elflibs_raw,$lib_elflibs_intern,$lib_mesonlib,$_DOXYGEN,$class_pwphpbytecode_stub_files;
    $tmp1 = explode("->",$param[1]);
    $tmp3 = explode("(",$tmp1[1]);

    $src=trim($tmp1[0]);
    $dst=trim($tmp3[0]);

    $jit=false;
    if(trim($param[0])=="class-jit")$jit=true;

    $libs=array();
    $mesonlibs_h=array();
    if(isset($tmp3[1])){
        $tmp2 = explode(")",$tmp3[1]);
        $libs = explode(",",$tmp2[0]);
    }
    $pcclibs=array();
    $headerfiles_otherclasses=array();
    foreach($libs as $lib){


        $exp = explode(":",$lib);
        if(isset($exp[1]) && !empty($exp[1])) {
            if(!isset($lib_elflibs)){
                $lib_elflibs[$dst]=array();
            }
            if(is_array($lib_mesonlib) && in_array($exp[0],$lib_mesonlib)){
                $lib_elflibs_intern[$dst][]=$exp[0].load_pwphpwrapper_suffix_fine()."_lib";
                $mesonlibs_h[]=$exp[0]."_h";
            }
            else{
                $lib_elflibs[$dst][]="dependency('".trim($exp[0]."-".load_pwphpwrapper_suffix())."')";
                $lib_elflibs_raw[$dst][]="dependency('".trim($exp[0]."_raw-".load_pwphpwrapper_suffix())."')";
            }
            $pcclibs[]=$lib;
        }
        else{
            $pcclibs[]=$exp[0].":".$exp[0];
            $headerfiles_otherclasses[]=$exp[0]."_h";
        }
    }

    $txt="";

    $jit_append="";
    if($jit)$jit_append="'--enable-jit'";
    $stub_file = pwphpbytecode_stub_output_name($dst);
    if(class_has_pwphpbytecode_stub_methods($src, $dst)){
        if(!isset($class_pwphpbytecode_stub_files) || !is_array($class_pwphpbytecode_stub_files)){
            $class_pwphpbytecode_stub_files=array();
        }
        $class_pwphpbytecode_stub_files[$dst] = [
            "class" => $dst,
            "output" => $stub_file,
        ];
    }


    // Build .c file
    $txt .= $dst."_php_src = files( '" . $src . "' )\n";
    $txt .= $dst."_c = ['".$dst.".c']\n";
    $txt.=$dst."_c_sources = custom_target(\n";
    $txt.="\t".$dst."_c,\n";
    $txt.="\tinput: [".$dst."_php_src],\n";
    $txt.="\toutput: [".$dst."_c],\n";
    if(count($libs)>0){
        $txt.="\tcommand: [mycompiler,'".$dst."','@INPUT@','@OUTPUT@','".join("','",$pcclibs)."','--is-lib',".$jit_append."],\n";
    }
    else{
        $txt.="\tcommand: [mycompiler,'".$dst."','@INPUT@','@OUTPUT@','--is-lib',".$jit_append."],\n";
    }
    if(count($headerfiles_otherclasses)>0){
        $txt.="\tdepends: [".join(", ",$headerfiles_otherclasses)."],\n";
    }
    if(count($mesonlibs_h)>0){
        $txt.="\tdepends: [".join(", ",$mesonlibs_h)."],\n";
    }
    $txt.=")\n";

    // Build .h file
    $txt.=$dst."_h = custom_target(\n";
    $txt.="\t'".$dst.".h',\n";
    $txt.="\tinput: [".$dst."_c_sources],\n";
    $txt.="\toutput: '".$dst.".h',\n";
    $txt.="\tcommand: [makeheaders,'@INPUT@']\n";
    $txt.=")\n";

    if($_DOXYGEN) {
        // Install Man-Page
        $txt .= "custom_target('manpage_" . $dst . "',\n";
        $txt .= "\tcapture: true,\n";
        $txt .= "\toutput: '" . $dst . "." . load_pwphpwrapper_suffix() . ".3',\n";
        $txt .= "\tdepends: [ complete_man ],\n";
        $txt .= "\tcommand: [ cat, meson.current_build_dir()+'/man/man3/" . $dst . ".3' ],\n";
        $txt .= "\tinstall_dir: get_option('mandir')+'/man3',\n";
        $txt .= "\tinstall: true,\n";
        $txt .= "\tbuild_always_stale: true,\n";
        $txt .= ")\n";
    }

    return($txt);
}

function generate_pwphpbytecode_stub_cli($argv){
    if(count($argv) < 6){
        fwrite(STDERR, "Usage: pw-build-meson --generate-stub <input.php> <output.stub.php> <ClassName> <PackageName>\n");
        exit(2);
    }

    $input = $argv[2];
    $output = $argv[3];
    $class_name = $argv[4];
    $package_name = $argv[5];

    $source = file_get_contents($input);
    if($source === false){
        fwrite(STDERR, "Could not read stub input: " . $input . "\n");
        exit(1);
    }

    $stub = build_pwphpbytecode_stub($source, $input, $class_name, $package_name);
    if($stub === ""){
        fwrite(STDERR, "No public static methods found for class " . $class_name . " in " . $input . "\n");
        exit(1);
    }

    if(file_put_contents($output, $stub) === false){
        fwrite(STDERR, "Could not write stub output: " . $output . "\n");
        exit(1);
    }
}

function pwphpbytecode_stub_output_name($class_name){
    return($class_name . "." . load_pwphpwrapper_suffix() . ".pwphpbytecode.stub.php");
}

function class_has_pwphpbytecode_stub_methods($src, $class_name){
    if(!file_exists($src)){
        return(false);
    }

    $source = file_get_contents($src);
    if($source === false){
        return(false);
    }

    return(count(extract_phpbytecode_stub_methods($source, $class_name)) > 0);
}

function build_pwphpbytecode_stub($source, $src_label, $class_name, $package_name){
    $methods = extract_phpbytecode_stub_methods($source, $class_name);
    if(count($methods) === 0){
        return("");
    }

    $stub = "<?php\n";
    $stub .= "/*\n";
    $stub .= " * Generated by pw-build-meson. Declarative pwphpbytecode stub only.\n";
    $stub .= " * Source: " . $src_label . "\n";
    $stub .= " */\n\n";
    $stub .= "/**\n";
    $stub .= " * @pwphpbytecode-loader php_load_class_" . $class_name . "\n";
    $stub .= " * @pwphpbytecode-package " . $package_name . "\n";
    $stub .= " */\n";
    $stub .= "class " . $class_name . " {\n";
    foreach($methods as $method){
        $stub .= format_php_stub_doc_comment($method["doc"] ?? "", $method["name"]);
        $stub .= "    public static function " . $method["name"] . "(" . $method["params"] . "): " . $method["return"] . " {}\n";
    }
    $stub .= "}\n";
    return($stub);
}

function extract_phpbytecode_stub_methods($source, $class_name){
    $tokens = token_get_all($source);
    $methods = array();
    $in_target_class = false;
    $class_depth = 0;
    $brace_depth = 0;
    $pending_class = false;
    $pending_static = false;
    $pending_visibility = "";
    $pending_doc_comment = "";

    for($i = 0; $i < count($tokens); $i++){
        $token = $tokens[$i];
        $id = is_array($token) ? $token[0] : null;
        $text = is_array($token) ? $token[1] : $token;

        if($text === "{"){
            $brace_depth++;
            if($pending_class){
                $in_target_class = true;
                $class_depth = $brace_depth;
                $pending_class = false;
            }
            continue;
        }
        if($text === "}"){
            if($in_target_class && $brace_depth === $class_depth){
                $in_target_class = false;
                $class_depth = 0;
            }
            $brace_depth--;
            if($brace_depth < 0)$brace_depth = 0;
            continue;
        }

        if($id === T_CLASS){
            $name = next_non_whitespace_token_text($tokens, $i + 1);
            if($name === $class_name){
                $pending_class = true;
            }
            continue;
        }

        if(!$in_target_class){
            continue;
        }

        if($id === T_DOC_COMMENT){
            $pending_doc_comment = $text;
            continue;
        }
        if($id === T_PUBLIC || $id === T_PROTECTED || $id === T_PRIVATE){
            $pending_visibility = token_name($id);
            continue;
        }
        if($id === T_STATIC){
            $pending_static = true;
            continue;
        }
        if($id === T_FUNCTION){
            $method_name = next_non_whitespace_token_text($tokens, $i + 1);
            if($pending_static && ($pending_visibility === "" || $pending_visibility === "T_PUBLIC") && is_identifier_text($method_name)){
                $params = extract_parenthesized_text_after_function_name($tokens, $i + 1);
                $return = extract_return_type_after_function_signature($tokens, $i + 1);
                $methods[] = array(
                    "name" => $method_name,
                    "params" => normalize_php_stub_params($params),
                    "return" => normalize_php_stub_return($return),
                    "doc" => $pending_doc_comment,
                );
            }
            $pending_static = false;
            $pending_visibility = "";
            $pending_doc_comment = "";
            continue;
        }
    }

    if(count($methods) === 0){
        $methods = extract_phpbytecode_stub_methods_regex($source);
    }

    return($methods);
}

function extract_phpbytecode_stub_methods_regex($source){
    $methods = array();
    if(!preg_match_all('/(?:public\s+static|static\s+public|static)\s+function\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(([^)]*)\)\s*(?::\s*([^{;]+))?/m', $source, $matches, PREG_SET_ORDER)){
        return($methods);
    }
    foreach($matches as $match){
        $methods[] = array(
            "name" => $match[1],
            "params" => normalize_php_stub_params($match[2] ?? ""),
            "return" => normalize_php_stub_return($match[3] ?? ""),
            "doc" => "",
        );
    }
    return($methods);
}

function format_php_stub_doc_comment($doc_comment, $method_name){
    $doc_comment = trim($doc_comment);
    if($doc_comment === ""){
        return("    /**\n     * pwphpbytecode stub for " . $method_name . "().\n     */\n");
    }

    $lines = preg_split('/\R/', $doc_comment);
    $txt = "";
    foreach($lines as $line){
        $txt .= "    " . trim($line) . "\n";
    }
    return($txt);
}

function next_non_whitespace_token_text($tokens, $start){
    for($i = $start; $i < count($tokens); $i++){
        $token = $tokens[$i];
        if(is_array($token) && ($token[0] === T_WHITESPACE || $token[0] === T_COMMENT || $token[0] === T_DOC_COMMENT)){
            continue;
        }
        return(is_array($token) ? $token[1] : $token);
    }
    return("");
}

function extract_parenthesized_text_after_function_name($tokens, $start){
    $seen_name = false;
    $depth = 0;
    $text = "";
    for($i = $start; $i < count($tokens); $i++){
        $token = $tokens[$i];
        $part = is_array($token) ? $token[1] : $token;
        if(!$seen_name){
            if(is_array($token) && $token[0] === T_STRING){
                $seen_name = true;
            }
            continue;
        }
        if($part === "("){
            $depth++;
            if($depth === 1)continue;
        }
        if($part === ")"){
            $depth--;
            if($depth === 0)return($text);
        }
        if($depth > 0){
            $text .= $part;
        }
    }
    return("");
}

function extract_return_type_after_function_signature($tokens, $start){
    $seen_name = false;
    $depth = 0;
    $seen_signature_end = false;
    $return = "";
    for($i = $start; $i < count($tokens); $i++){
        $token = $tokens[$i];
        $part = is_array($token) ? $token[1] : $token;
        if(!$seen_name){
            if(is_array($token) && $token[0] === T_STRING){
                $seen_name = true;
            }
            continue;
        }
        if(!$seen_signature_end){
            if($part === "(")$depth++;
            elseif($part === ")"){
                $depth--;
                if($depth === 0)$seen_signature_end = true;
            }
            continue;
        }
        if(is_array($token) && $token[0] === T_WHITESPACE){
            continue;
        }
        if($part === ":"){
            continue;
        }
        if($part === "{" || $part === ";"){
            break;
        }
        $return .= $part;
    }
    return(trim($return));
}

function normalize_php_stub_params($params){
    return(trim(preg_replace('/\s+/', ' ', $params)));
}

function normalize_php_stub_return($return){
    $return = trim($return);
    if($return === ""){
        return("mixed");
    }
    if($return === "self" || $return === "static" || $return === "parent"){
        return("mixed");
    }
    return($return);
}

function is_identifier_text($text){
    return(is_string($text) && preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $text));
}

function add_phpfpm($param){
    global $_PHP_SLOT;
    $tmp1 = explode("->",$param[1]);
    $tmp2 = explode(":",$tmp1[0]);

    $ext_name=trim($tmp1[1]);
    $ext_lib=trim($tmp2[0]);
    $ext_class=trim($tmp2[1]);

    $txt ="c_source_".$ext_name."_cfile = ['".$ext_name."c_source_phpfpmext.c']\n";
    $txt.="c_source_".$ext_name." = custom_target(\n";
    $txt.="\tc_source_".$ext_name."_cfile,\n";
    $txt.="\toutput: c_source_".$ext_name."_cfile,\n";
    $txt.="\tcommand: [phpfpmextbuilder, php_api_version, abi_implement_version, '".$ext_name."', meson.project_version(), ".$ext_class."_h, '".$ext_class."', '@OUTPUT@' ]\n";
    $txt.=")\n";

    # Find PHP-Config
    $txt.="phpconfig = find_program('/usr/lib64/php' + php_api_version + '/bin/php-config')\n";


    # Find PHP includes
    $txt.="phpconfig_includes_run = run_command(phpconfig, '--includes', check: true)\n";
    $txt.="phpconfig_includes = phpconfig_includes_run.stdout().strip().replace('-I','').split()\n";
    $txt.="php_dep_inc = include_directories([phpconfig_includes])\n";

    # Find PHP extension dir
    $txt.="phpconfig_extdir_run = run_command(phpconfig, '--extension-dir', check: true)\n";
    $txt.="phpconfig_extdir = phpconfig_extdir_run.stdout().strip()\n";

    # Plain library without linking (for usage in cli or fpm)
    $txt.="php_dependency_only_incs = declare_dependency(\n";
    $txt.="\tinclude_directories: [php_dep_inc,'..'],\n";
    $txt.=")\n";
    $txt.="phpextension_".$ext_name." = shared_library('phpfpm_".$ext_name."',\n";
    $txt.="\t[c_source_".$ext_name."],\n";
    $txt.="\tdependencies: [php_dependency_only_incs],\n";
    $txt.="\tinstall: true,\n";
    $txt.="\tinstall_dir: phpconfig_extdir,\n";
    $txt.="\tlink_args: [ '-Wl,--unresolved-symbols=ignore-all'],\n";
    $txt.="\tlink_with: [" . $ext_lib. "_raw".load_pwphpwrapper_suffix_fine()."_lib],\n";
    $txt.=")\n";

    # crate extension php.ini
    $txt.="c_source_".$ext_name."_ini = ['phpfpmext_".$ext_name.".ini']\n";
    $txt.="c_source_".$ext_name." = custom_target(\n";
    $txt.="\tc_source_".$ext_name."_ini,\n";
    $txt.="\toutput: c_source_".$ext_name."_ini,\n";
    $txt.="\tcommand: [echo,  'extension=libphpfpm_".$ext_name.".so'],\n";
    $txt.="\tinstall: true,\n";
    $txt.="\tcapture: true,\n";
    $txt.="\tinstall_dir: '/etc/php/fpm-php".$_PHP_SLOT."/ext-active',\n";
    $txt.=")\n";
    return($txt);
}
