"html", "search-pattern" => "/^([a-zA-Z]+)\.(html|php|json)$/", "build-target" => "dev|rel", "path" => "./", "local-path" => "./shared/html/" ], [ /* shared javascript files (development mode only) */ "type" => "js", "search-pattern" => "/.*\.js$/", "search-exclude" => "/(.*\/)?workers\/.*/", "build-target" => "dev", "path" => "js/", "local-path" => "./shared/js/" ], [ /* shared javascript mapping files (development mode only) */ "type" => "js", "search-pattern" => "/.*\.(js.map|ts)$/", "search-exclude" => "/(.*\/)?workers\/.*/", "build-target" => "dev", "path" => "js/", "local-path" => "./shared/js/", "req-parm" => ["-js-map"] ], [ /* shared generated worker codec */ "type" => "js", "search-pattern" => "/(WorkerCodec.js|WorkerPOW.js)$/", "build-target" => "dev|rel", "path" => "js/workers/", "local-path" => "./shared/js/workers/" ], [ /* shared developer single css files */ "type" => "css", "search-pattern" => "/.*\.css$/", "build-target" => "dev", "path" => "css/", "local-path" => "./shared/css/" ], [ /* shared release css files */ "type" => "css", "search-pattern" => "/.*\.css$/", "build-target" => "rel", "path" => "css/", "local-path" => "./shared/generated/" ], [ /* shared release css files */ "type" => "css", "search-pattern" => "/.*\.css$/", "build-target" => "rel", "path" => "css/loader/", "local-path" => "./shared/css/loader/" ], [ /* shared release css files */ "type" => "css", "search-pattern" => "/.*\.css$/", "build-target" => "dev|rel", "path" => "css/theme/", "local-path" => "./shared/css/theme/" ], [ /* shared sound files */ "type" => "wav", "search-pattern" => "/.*\.wav$/", "build-target" => "dev|rel", "path" => "audio/", "local-path" => "./shared/audio/" ], [ /* shared data sound files */ "type" => "json", "search-pattern" => "/.*\.json/", "build-target" => "dev|rel", "path" => "audio/", "local-path" => "./shared/audio/" ], [ /* shared image files */ "type" => "img", "search-pattern" => "/.*\.(svg|png)/", "build-target" => "dev|rel", "path" => "img/", "local-path" => "./shared/img/" ], [ /* generated assembly files */ "type" => "wasm", "search-pattern" => "/.*\.(wasm)/", "build-target" => "dev|rel", "path" => "wasm/", "local-path" => "./asm/generated/" ], [ /* generated assembly javascript files */ "type" => "js", "search-pattern" => "/.*\.(js)/", "build-target" => "dev|rel", "path" => "wasm/", "local-path" => "./asm/generated/" ], [ /* own webassembly files */ "type" => "wasm", "search-pattern" => "/.*\.(wasm)/", "build-target" => "dev|rel", "path" => "wat/", "local-path" => "./shared/wat/" ], [ /* translations */ "type" => "i18n", "search-pattern" => "/.*\.(translation|json)/", "build-target" => "dev|rel", "path" => "i18n/", "local-path" => "./shared/i18n/" ] ]; $APP_FILE_LIST_SHARED_VENDORS = [ [ "type" => "js", "search-pattern" => "/.*\.js$/", "build-target" => "dev|rel", "path" => "vendor/", "local-path" => "./vendor/" ], [ "type" => "css", "search-pattern" => "/.*\.css$/", "build-target" => "dev|rel", "path" => "vendor/", "local-path" => "./vendor/" ] ]; $APP_FILE_LIST_CLIENT_SOURCE = [ [ /* client css files */ "client-only" => true, "type" => "css", "search-pattern" => "/.*\.css$/", "build-target" => "dev|rel", "path" => "css/", "local-path" => "./client/css/" ], [ /* client js files */ "client-only" => true, "type" => "js", "search-pattern" => "/.*\.js/", "build-target" => "dev", "path" => "js/", "local-path" => "./client/js/" ], /* release specific */ [ /* web merged javascript files (shared inclusive) */ "client-only" => true, "type" => "js", "search-pattern" => "/.*\.js$/", "build-target" => "rel", "path" => "js/", "local-path" => "./client/generated/" ], [ /* Add the shared generated files. Exclude the shared file because we're including it already */ "client-only" => true, "type" => "js", "search-pattern" => "/.*\.js$/", "search-exclude" => "/shared\.js(.map)?$/", "build-target" => "rel", "path" => "js/", "local-path" => "./shared/generated/" ], ]; $APP_FILE_LIST_WEB_SOURCE = [ [ /* web javascript files (development mode only) */ "web-only" => true, "type" => "js", "search-pattern" => "/.*\.js$/", "build-target" => "dev", "path" => "js/", "local-path" => "./web/js/" ], [ /* web merged javascript files (shared inclusive) */ "web-only" => true, "type" => "js", "search-pattern" => "/.*\.js$/", "build-target" => "rel", "path" => "js/", "local-path" => "./web/generated/" ], [ /* Add the shared generated files. Exclude the shared file because we're including it already */ "web-only" => true, "type" => "js", "search-pattern" => "/.*\.js$/", "search-exclude" => "/shared\.js(.map)?$/", "build-target" => "rel", "path" => "js/", "local-path" => "./shared/generated/" ], [ /* web css files */ "web-only" => true, "type" => "css", "search-pattern" => "/.*\.css$/", "build-target" => "dev|rel", "path" => "css/", "local-path" => "./web/css/" ], [ /* web html files */ "web-only" => true, "type" => "html", "search-pattern" => "/.*\.(php|html)/", "build-target" => "dev|rel", "path" => "./", "local-path" => "./web/html/" ] ]; $APP_FILE_LIST_WEB_TEASPEAK = [ /* special web.teaspeak.de only auth files */ [ /* login page and api */ "web-only" => true, "type" => "html", "search-pattern" => "/.*\.(php|html)/", "build-target" => "dev|rel", "search-depth" => 1, "path" => "./", "local-path" => "./auth/", "req-parm" => ["-xf"] ], [ /* javascript */ "web-only" => true, "type" => "js", "search-pattern" => "/.*\.js$/", "build-target" => "dev|rel", "path" => "js/", "local-path" => "./auth/js/", "req-parm" => ["-xf"] ], [ /* web css files */ "web-only" => true, "type" => "css", "search-pattern" => "/.*\.css$/", "build-target" => "dev|rel", "path" => "css/", "local-path" => "./auth/css/", "req-parm" => ["-xf"] ], [ /* certificates */ "web-only" => true, "type" => "pem", "search-pattern" => "/.*\.pem$/", "build-target" => "dev|rel", "path" => "certs/", "local-path" => "./auth/certs/", "req-parm" => ["-xf"] ] ]; $APP_FILE_LIST = array_merge( $APP_FILE_LIST_SHARED_SOURCE, $APP_FILE_LIST_SHARED_VENDORS, $APP_FILE_LIST_CLIENT_SOURCE, $APP_FILE_LIST_WEB_SOURCE, $APP_FILE_LIST_WEB_TEASPEAK ); function systemify_path($path) { return str_replace("/", DIRECTORY_SEPARATOR, $path); } function join_path(...$paths) { $result_path = ""; foreach ($paths as $path) { if(strlen($result_path) > 0) $result_path .= DIRECTORY_SEPARATOR . $path; else $result_path = $path; } return $result_path; } function create_directories(&$error, $path, $dry_run = false) { if(strpos(PHP_OS, "Linux") !== false) { $command = "mkdir -p " . $path; } else if(strpos(PHP_OS, "WINNT") !== false) { $command = "mkdir " . $path; /* default path tree */ } else { $error = "unsupported system"; return false; } echo $command . PHP_EOL; if(!$dry_run) { exec($command, $error, $state); if($state) { $error = "Command execution results in " . $state . ": " . implode(' ', $error); return false; } } return true; } function delete_directories(&$error, $path, $dry_run = false) { if(!file_exists($path)) return true; if(strpos(PHP_OS, "Linux") !== false) { $command = "rm -r " . $path; } else if(strpos(PHP_OS, "WINNT") !== false) { $command = "rm -r " . $path; } else { $error = "unsupported system"; return false; } echo $command . PHP_EOL; if(!$dry_run) { $state = 0; exec($command, $output, $state); if($state !== 0) { $error = "Command execution results in " . $state . ": " . implode(' ', $output); return false; } } return true; } function create_link(&$error, $source, $target, $dry_run = false) { if(strpos(PHP_OS, "Linux") !== false) { $command = "ln -s " . $source . " " . $target; } else if(strpos(PHP_OS, "WINNT") !== false) { $command = "mklink " . (is_dir($target) ? "/D " : "") . " " . $target . " " . $source; } else { $error = "unsupported system"; return false; } echo $command . PHP_EOL; if(!$dry_run) { $state = 0; exec($command, $output, $state); if($state !== 0) { $error = "Command execution results in " . $state . ": " . implode(' ', $output); return false; } } return true; } function list_dir($base_dir, $match = null, $depth = -1, &$results = array(), $dir = "") { if($depth == 0) return $results; if(!is_dir($base_dir . $dir)) { echo "Skipping directory " . $base_dir . $dir . PHP_EOL; return $results; } $files = scandir($base_dir . $dir); foreach($files as $key => $value){ $path = $base_dir.$dir.DIRECTORY_SEPARATOR.$value; if(!is_dir($path)) { if(!$match || preg_match($match, ($dir ? $dir.DIRECTORY_SEPARATOR : "").$value)) $results[] = ($dir ? $dir.DIRECTORY_SEPARATOR : "").$value; } else if($value != "." && $value != "..") { list_dir($base_dir, $match, $depth - 1, $results, ($dir ? $dir.DIRECTORY_SEPARATOR : "").$value); } } return $results; } class AppFile { public $type; public $name; public $target_path; /* relative path to the target file viewed from the file root */ public $local_path; /* absolute path to local file */ public $hash; } function find_files($flag = 0b11, $local_path_prefix = "." . DIRECTORY_SEPARATOR, $type = "dev", $args = []) { //TODO Use cache here! global $APP_FILE_LIST; $result = []; foreach ($APP_FILE_LIST as $entry) { if(isset($entry["web-only"]) && $entry["web-only"] && ($flag & 0b01) == 0) continue; if(isset($entry["client-only"]) && $entry["client-only"] && ($flag & 0b10) == 0) continue; if(isset($entry["build-target"]) && array_search($type, explode("|", $entry["build-target"])) === false) continue; if(isset($entry["req-parm"])) { if(!is_array($entry["req-parm"])) $entry["req-parm"] = [$entry["req-parm"]]; $valid = true; foreach($entry["req-parm"] as $parm) { if(array_search($parm, $args) === false) $valid = false; } if(!$valid) continue; } $entries = list_dir( systemify_path($local_path_prefix . $entry["local-path"]), $entry["search-pattern"], isset($entry["search-depth"]) ? $entry["search-depth"] : -1 ); foreach ($entries as $f_entry) { if(isset($entry["search-exclude"]) && preg_match($entry["search-exclude"], $f_entry)) continue; $file = new AppFile; $f_info = pathinfo($f_entry); $file->target_path = systemify_path($entry["path"]) . DIRECTORY_SEPARATOR . $f_info["dirname"] . DIRECTORY_SEPARATOR; $file->local_path = getcwd() . DIRECTORY_SEPARATOR . systemify_path($entry["local-path"]) . DIRECTORY_SEPARATOR . $f_info["dirname"] . DIRECTORY_SEPARATOR; $file->name = $f_info["basename"]; $file->type = $entry["type"]; $file->hash = sha1_file($file->local_path . DIRECTORY_SEPARATOR . $file->name); if(strlen($file->hash) > 0) { foreach ($result as $e) if($e->hash == $file->hash) goto ignore; } array_push($result, $file); ignore: } } return $result; } if(isset($_SERVER["argv"])) { //Executed by command line $supported = false; if(strpos(PHP_OS, "Linux") !== false) { $supported = true; } else if(strpos(PHP_OS, "WIN") !== false) { $supported = true; } if(!$supported) { error_log("Invalid operating system (" . PHP_OS . ")! Help tool only available under linux!"); exit(1); } if(count($_SERVER["argv"]) < 2) { error_log("Invalid parameters!"); goto help; } if($_SERVER["argv"][1] == "help") { help: echo "php " . $_SERVER["argv"][0] . " " . PHP_EOL; echo " generate [-xf]" . PHP_EOL; echo " list [-xf]" . PHP_EOL; exit(1); } else if($_SERVER["argv"][1] == "generate" || $_SERVER["argv"][1] == "list") { $state = 0; $dry_run = $_SERVER["argv"][1] == "list"; $flagset = 0b00; $environment = ""; $type = "dev"; if($_SERVER["argv"][3] == "dev" || $_SERVER["argv"][3] == "development") { if ($_SERVER["argv"][2] == "web") { $flagset = 0b01; $environment = join_path("web", "environment", "development"); } else if ($_SERVER["argv"][2] == "client") { $flagset = 0b10; $environment = join_path("client-api", "environment", "ui-files", "raw"); } else { error_log("Invalid type!"); goto help; } } else if($_SERVER["argv"][3] == "rel" || $_SERVER["argv"][3] == "release") { $type = "rel"; if ($_SERVER["argv"][2] == "web") { $flagset = 0b01; $environment = join_path("web", "environment", "release"); } else if ($_SERVER["argv"][2] == "client") { $flagset = 0b10; $environment = join_path("client-api", "environment", "ui-files", "raw"); } else { error_log("Invalid type!"); goto help; } } else { error_log("Invalid type!"); goto help; } { if(!$dry_run) { if(delete_directories($error, $environment) === false) goto handle_error; if(create_directories($error, $environment) === false) goto handle_error; } $files = find_files($flagset, "." . DIRECTORY_SEPARATOR, $type, array_slice($_SERVER["argv"], 4)); $original_path = realpath("."); if(!chdir($environment)) { error_log("Failed to enter directory " . $environment . "!"); exit(1); } /** @var AppFile $file */ foreach($files as $file) { if(!$dry_run && !is_dir($file->target_path) && strlen($file->target_path) > 0) { if(create_directories($error, $file->target_path, $dry_run) === false) goto handle_error; } if(create_link($output, $file->local_path . $file->name, $file->target_path . $file->name, $dry_run) === false) goto handle_error; } if(!chdir($original_path)) { error_log("Failed to reset directory!"); exit(1); } echo "Generated!" . PHP_EOL; } if(!$dry_run) { exec("." . DIRECTORY_SEPARATOR . "scripts" . DIRECTORY_SEPARATOR . "git_index.sh sort-tag", $output, $state); file_put_contents($environment . DIRECTORY_SEPARATOR . "version", $output); if ($_SERVER["argv"][2] == "client") { if(!chdir("client-api" . DIRECTORY_SEPARATOR . "environment")) { error_log("Failed to enter directory client-api/environment!"); exit(1); } if(!is_dir("versions" . DIRECTORY_SEPARATOR . "beta")) { exec($command = "mkdir -p versions/beta", $output, $state); if($state) goto handle_error; } if(!is_dir("versions/stable")) { exec($command = "mkdir -p versions/beta", $output, $state); if($state) goto handle_error; } exec($command = "ln -s ../api.php ./", $output, $state); $state = 0; //Dont handle an error here! if($state) goto handle_error; } } exit(0); handle_error: error_log("Command execution failed!"); error_log("Error message: " . $error); exit(1); } }