$cur) { if(($mode == 1 && !$cur->isDir()) || ($mode == 2 && $cur->isDir())) { if(!$only_extensions) $files[] = $filename; else { $flen = strlen($filename); foreach($only_extensions as $ext) { if(substr_compare($filename, $ext, $flen-strlen($ext)) === 0) $files[] = $filename; } } } } } return $files; } function normalize_path(string $path, ?bool $unix = null/*null means try to guess*/) : string { if(is_null($unix)) $unix = !is_win(); $path = str_replace('\\', '/', $path); $path = preg_replace('/\/+/', '/', $path); $parts = explode('/', $path); $absolutes = array(); foreach($parts as $part) { if('.' == $part) continue; if('..' == $part) array_pop($absolutes); else $absolutes[] = $part; } $res = implode($unix ? '/' : '\\', $absolutes); return $res; } function normalize_sed_path(string $path) : string { $path = normalize_path($path); if(is_win()) $path = str_replace('\\', '\\\\', $path); return $path; } //NOTE: Escaping multibyte unicode chars as \uXXXX impacts decoding performance drastically //so we unescape them while encoding json function json_encode_unescaped(array $arr) : string { array_walk_recursive($arr, function (&$item, $key) { if (is_string($item)) $item = mb_encode_numericentity($item, array (0x80, 0xffff, 0, 0xffff), 'UTF-8'); }); return mb_decode_numericentity(json_encode($arr), array (0x80, 0xffff, 0, 0xffff), 'UTF-8'); } function json_make_pretty(string $json) : string { return prettyJSON($json); } function need_to_regen(string $file, array $deps, bool $debug = false) : bool { if(!is_file($file)) { if($debug) echo "! $file\n"; return true; } $fmtime = filemtime($file); foreach($deps as $dep) { if($dep && is_file($dep) && (filemtime($dep) > $fmtime)) { if($debug) echo "$dep > $file\n"; return true; } } return false; } function need_to_regen_any(array $files, array $deps, bool $debug = false) : bool { $earliest_file = null; $earliest_time = 2e32; foreach($files as $file) { if(!is_file($file)) return true; $time = filemtime($file); if($time < $earliest_time) { $earliest_file = $file; $earliest_time = $time; } } if($debug) { $date = date(DATE_RFC2822, $earliest_time); echo "need_to_regen_any, earliest file: $earliest_file ($date)\n"; } if($earliest_file === null) return true; return need_to_regen($earliest_file, $deps, $debug); } function fnmatch_patterns(string $file, array $fnmatch_patterns) : bool { foreach($fnmatch_patterns as $pattern) { if(fnmatch($pattern, $file)) { return true; } } return false; } //force mode: //0 - none, //1 - force write always, //2 - write only if content differs //3 - write only if content differs, don't even touch if they are same function gen_file(string $tpl_file, string $result_file, $deps = array(), $force = 0, int $perms = 0640) { $deps[] = $tpl_file; if($force > 0 || need_to_regen($result_file, $deps)) { $txt = file_get_contents($tpl_file); if($txt === false) throw new Exception("Bad template settings file $tpl_file"); //replacing %(FOO)% alike entries with taskman config values $out = _($txt); if(($force == 2 || $force == 3) && is_file($result_file)) { $prev = file_get_contents($result_file); if($prev === false) throw new Exception("Could not read file $result_file"); //if contents is similar no need to write it if(strcmp($prev, $out) === 0) { if($force == 2) touch($result_file); return; } } ensure_write($result_file, $out); chmod($result_file, $perms); } } function ensure_read($file) { $c = file_get_contents($file); if($c === false) throw new Exception("Could not read file '$file'"); return $c; } function ensure_write($dst, $txt, $dir_perms = 0777, $flags = 0) { $dir = dirname($dst); if(!is_dir($dir)) mkdir($dir, $dir_perms, true); msg_dbg("> $dst ...\n"); if(file_put_contents($dst, $txt, $flags) === false) throw new Exception("Could not write to '$dst'"); } function ensure_write_if_differs($dst, $txt, $dir_perms = 0777, $flags = 0) { if(is_file($dst) && file_get_contents($dst) == $txt) return; ensure_write($dst, $txt, $dir_perms, $flags); } const COPY_MODE_BUILTIN = 1; const COPY_MODE_SYSTEM = 2; const COPY_MODE_HARDLINK = 3; function ensure_copy(string $src, string $dst, int $dir_perms = 0777, array $excludes = array(), array $fnmatches = array()) { recurse_copy($src, $dst, $dir_perms, COPY_MODE_BUILTIN, false, $excludes, $fnmatches); } function ensure_copy_file_if_differs(string $src_file, string $dst_file, $dir_perms = 0777) { if(!is_file($dst_file) || filesize($src_file) != filesize($dst_file) || crc32_file($src_file) !== crc32_file($dst_file)) ensure_copy($src_file, $dst_file, $dir_perms); } function ensure_sync(string $src, string $dst, int $dir_perms = 0777, array $excludes = array(), array $fnmatches = array()) { recurse_copy($src, $dst, $dir_perms, COPY_MODE_BUILTIN, true, $excludes, $fnmatches); } function ensure_hardlink(string $src, string $dst, int $dir_perms = 0777, array $excludes = array(), array $fnmatches = array()) { recurse_copy($src, $dst, $dir_perms, COPY_MODE_HARDLINK, true, $excludes, $fnmatches); } function ensure_duplicate(string $src, string $dst, int $dir_perms = 0777) { recurse_copy($src, $dst, $dir_perms, COPY_MODE_SYSTEM); } function recurse_copy( string $src, string $dst, int $dir_perms = 0777, int $copy_mode = COPY_MODE_BUILTIN, bool $mtime_check = false, //regex expressions which check full paths array $excludes = array(), //fnmatch expressions which check file names only array $fnmatches = array() ) { msg_dbg("copying $src => $dst ...\n"); if(!is_file($src) && !is_dir($src)) throw new Exception("Bad file or dir '$src'"); foreach($excludes as $exclude_pattern) { if(preg_match("~$exclude_pattern~", $src)) return; } if(!is_dir($src)) { _ensure_copy_file($src, $dst, $copy_mode, $mtime_check); return; } $dir = opendir($src); ensure_mkdir($dst, $dir_perms); while(false !== ($file = readdir($dir))) { if(($file != '.' ) && ($file != '..')) { if(is_dir($src . '/' . $file)) { recurse_copy( $src . '/' . $file, $dst . '/' . $file, $dir_perms, $copy_mode, $mtime_check, $excludes, $fnmatches ); } else { $excluded = false; foreach($excludes as $exclude_pattern) $excluded = $excluded || (bool)preg_match("~$exclude_pattern~", $src . '/' . $file); $fnmatched = sizeof($fnmatches) == 0; foreach($fnmatches as $fnmatch) $fnmatched = $fnmatched || fnmatch($fnmatch, $file); if($excluded || !$fnmatched) continue; _ensure_copy_file($src . '/' . $file, $dst . '/' . $file, $copy_mode, $mtime_check); } } } closedir($dir); } function shell(string $cmd, &$out = null) { shell_try($cmd, $ret, $out); if($ret != 0) throw new Exception("Shell execution error(exit code $ret)"); } function shell_try(string $cmd, &$ret = null, &$out = null) { msg(" shell: $cmd\n"); msg(" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); if(func_num_args() < 3) system($cmd, $ret); else _execute_proc_cmd($cmd, $ret, $out); } function shell_get(string $cmd, bool $as_string = true) { exec($cmd, $out, $code); if($code !== 0) throw new Exception("Error($code) executing shell cmd '$cmd'"); return $as_string ? implode("", $out) : $out; } function _execute_proc_cmd(string $cmd, &$ret, &$out) { //TODO: do we really need to redirect error stream? $proc = popen("$cmd 2>&1", 'r'); $log = ''; if(!is_resource($proc)) { $log = ""; _log($log, 1); } else { while($logline = fgets($proc)) { $log .= $logline; _log($logline, 1); } } $out = explode("\n", $log); $ret = pclose($proc); } function _ensure_copy_file(string $src, string $dst, int $copy_mode = COPY_MODE_BUILTIN, bool $mtime_check = false) { if($mtime_check && file_exists($dst) && filemtime($src) <= filemtime($dst)) return; msg_dbg("copy ($copy_mode): $src => $dst\n"); ensure_mkdir(dirname($dst)); if($copy_mode == COPY_MODE_SYSTEM) shell("cp -a $src $dst"); else if($copy_mode == COPY_MODE_BUILTIN) { if(!copy($src, $dst)) throw new Exception("Could not copy '$src' to '$dst'"); } else if($copy_mode == COPY_MODE_HARDLINK) { if(!link($src, $dst)) throw new Exception("Could make a hard link '$src' to '$dst'"); } else throw new Exception("Unrecognized copy mode $copy_mode"); } function ensure_identical(string $src, string $dst, $excludes = array()) { msg_dbg("deleting files missing in $src from $dst ...\n"); if(!is_dir($dst) && !is_file($dst)) throw new Exception("dst '$dst' must be a valid file or dir!"); foreach($excludes as $exclude_pattern) { if(preg_match("~$exclude_pattern~", $src)) return; } $dir = opendir($dst); while(false !== ($file = readdir($dir))) { if(($file != '.' ) && ($file != '..')) { $src_file = $src . '/' . $file; $dst_file = $dst . '/' . $file; if(is_dir($src_file)) ensure_identical($src_file, $dst_file, $excludes); else { $excluded = false; foreach($excludes as $exclude_pattern) { $excluded = $excluded || (bool)preg_match("~$exclude_pattern~", $src_file); } if($excluded) continue; if(is_file($dst_file) && !is_file($src_file)) ensure_rm($dst_file); } } } closedir($dir); } function ensure_symlink(string $src, string $dst, int $dir_perms = 0777) { if(!is_file($src) && !is_dir($src)) throw new Exception("Bad file or dir '$src'"); $dir = dirname($dst); if(!is_dir($dir)) mkdir($dir, $dir_perms, true); ensure_rm($dst); msg_dbg("symlinking $src -> $dst \n"); if(!symlink($src, $dst)) throw new Exception("Could not create symlink"); } function ensure_rm(string $what) { if(is_dir($what) && !is_link($what)) rrmdir($what); else if(is_file($what) || is_link($what)) unlink($what); } function ensure_mkdir(string $dir, int $perms = 0775) { if(is_dir($dir)) return; msg_dbg("mkdir $dir\n"); if(!mkdir($dir, $perms, true)) throw new Exception("Could not create dir '$dir'"); msg_dbg("chmod " . decoct($perms) . " $dir\n"); if(!chmod($dir, $perms)) throw new Exception("Could not chmod " . decoct($perms) . " dir '$dir'"); } function ensure_var_dir(string $dir) { ensure_mkdir($dir, 0777); $items = fmatch("$dir/*"); foreach($items as $item) { $perms = is_dir($item) ? 0770 : 0660; msg_dbg("chmod " . decoct($perms) . " $item\n"); if(!chmod($item, $perms)) throw new Exception("Could not chmod " . decoct($perms) . " '$item'"); } } function compare_files_timestamp(string $file_a, string $file_b) : int { $file_a_timestamp = file_exists($file_a) ? filemtime($file_a) : 0; $file_b_timestamp = file_exists($file_b) ? filemtime($file_b) : 0; if($file_a_timestamp >= $file_b_timestamp) return 1; else return -1; } function fmatch(string $path) : array { $res = glob($path); if(!is_array($res)) return array(); return $res; } function rrmdir(string $dir, bool $remove_top_dir = true) { if(is_dir($dir)) { $objects = scandir($dir); foreach($objects as $object) { if($object != "." && $object != "..") { if(is_dir($dir."/".$object)) rrmdir($dir."/".$object); else unlink($dir."/".$object); } } } if($remove_top_dir) { if(is_link($dir)) unlink($dir); else if(is_dir($dir)) rmdir($dir); } } define("GIT_INFO_REV_HASH" , 1 << 0); define("GIT_INFO_BRANCH" , 1 << 1); define("GIT_INFO_REV_NUMBER", 1 << 2); define("GIT_INFO_ALL" , ~0); function git_get_info($info = GIT_INFO_ALL) : array { global $GAME_ROOT; $rev_hash = ""; $branch = ""; $revision_number = 0; if(!is_dir("$GAME_ROOT/.git")) throw new Exception("Not a Git repository"); if($info & GIT_INFO_REV_HASH) { $out = array(); exec("git rev-parse HEAD", $out); $rev_hash = trim($out[0]); if(!$rev_hash) throw new Exception("Error getting git revision hash"); } if($info & GIT_INFO_BRANCH) { $out = array(); exec("git rev-parse --abbrev-ref HEAD", $out); $branch = trim($out[0]); if(!$branch) throw new Exception("Error getting git branch"); } if($info & GIT_INFO_REV_NUMBER) { $out = array(); exec("git rev-list HEAD --count", $out); $revision_number = (int)$out[0]; if(!$revision_number) throw new Exception("Error getting git revision number"); } return array($rev_hash, $branch, $revision_number); } function git_get_rev_hash() : string { list($rev_hash, $_, $__) = git_get_info(GIT_INFO_REV_HASH); return $rev_hash; } function git_get_branch() : string { list($_, $branch, $__) = git_get_info(GIT_INFO_BRANCH); return $branch; } function git_get_rev_number() : string { list($_, $__, $rev_number) = git_get_info(GIT_INFO_REV_NUMBER); return $rev_number; } function git_try_commit(string $paths, string $msg) { try { exec("git add $paths && git commit -m \"$msg\" && git pull && git push"); } catch(Exception $e) { echo "Failed to commit changes. Apparently, nothing changed\n"; } } function check_and_decode_jzon(string $json) : array { try { $arr = jzon_parse($json); return array(0, "", $arr); } catch(Exception $e) { return array(1, $e->getMessage(), array()); } } function make_file_md5($file, $md5_file) { if(!need_to_regen($md5_file, array($file))) return file_get_contents($md5_file); $md5 = md5_file($file); ensure_write($md5_file, $md5); return $md5; } function make_dir_md5($dir, $md5_file) { $files = scan_files_rec(array($dir)); if(!need_to_regen($md5_file, $files)) return; $md5s = array(); foreach($files as $file) $md5s[] = md5_file($file); $md5 = md5(implode('', $md5s)); ensure_write($md5_file, $md5); } function file_put_contents_atomic(string $filename, string $content, int $mode = 0644) { $temp = tempnam(dirname($filename), 'atomic'); if(!($f = @fopen($temp, 'wb'))) throw new Exception("Error writing temporary file '$temp'"); fwrite($f, $content); fclose($f); if(!@rename($temp, $filename)) { @unlink($filename); @rename($temp, $filename); } chmod($filename, $mode); } function gmgetdate($ts = null) : array { $k = array('seconds','minutes','hours','mday', 'wday','mon','year','yday','weekday','month',0); return array_combine($k, explode(":", gmdate('s:i:G:j:w:n:Y:z:l:F:U',is_null($ts)?time():$ts))); } function prettyJSON(string $json) : string { $result = ''; $level = 0; $prev_char = ''; $in_quotes = false; $ends_line_level = NULL; $json_length = strlen($json); for($i = 0; $i < $json_length; $i++) { $char = $json[$i]; $new_line_level = NULL; $post = ""; if($ends_line_level !== NULL) { $new_line_level = $ends_line_level; $ends_line_level = NULL; } if($char === '"' && $prev_char != '\\') { $in_quotes = !$in_quotes; } else if(!$in_quotes) { switch($char) { case '}': case ']': $level--; $ends_line_level = NULL; $new_line_level = $level; break; case '{': case '[': $level++; case ',': $ends_line_level = $level; break; case ':': $post = " "; break; case " ": case "\t": case "\n": case "\r": $char = ""; $ends_line_level = $new_line_level; $new_line_level = NULL; break; } } if($new_line_level !== NULL) { $result .= "\n".str_repeat(" ", $new_line_level); } $result .= $char.$post; $prev_char = $char; } $result = str_replace('"<%', '<%', $result); $result = str_replace('%>"', '%>', $result); $result = str_replace('\"', '"', $result); $result = str_replace('\\\\', '\\', $result); $result = str_replace('\/', '/', $result); $result = str_replace('"{', '{', $result); $result = str_replace('}"', '}', $result); return $result."\n"; } function crc32_file($file) { $hash = hash_file('crc32b', $file); $array = unpack('N', pack('H*', $hash)); return $array[1]; } function run_shell_in_project_dir($cmd) { global $GAME_ROOT; shell("cd $GAME_ROOT && $cmd", $out); return $out[0]; } function boolstr(bool $b) : string { return $b ? 'true' : 'false'; } function create_js_template_literal(string $str) : string { $result = str_replace('\\', '\\\\', $str); // replace \ -> \\ $result = str_replace('`', '\\`', $result); // replace ` -> \` $result = str_replace('$', '\\$', $result); // replace $ -> \$ return "`$result`"; } function create_go_string_literal(string $str) : string { $result = str_replace('\\', '\\\\', $str); // replace \ -> \\ $result = str_replace('"', '\\"', $result); // replace " -> \" $result = str_replace("\n", '\\n', $result); // replace new_line -> \n return '"' . $result . '"'; } function convertFileFromDos2UnixFormat(string $file) { $file_tmp = $file.".tmp"; if(is_win()) replace_text_in_file("\\r", "", $file, $file_tmp); else shell("sed \$'s/\\r\$//' $file > $file_tmp"); ensure_copy($file_tmp, $file); ensure_rm($file_tmp); } function removeCarriageReturn(string $file) { $file_tmp = $file.".tmp"; if(is_win()) replace_text_in_file("\\\\r", "", $file, $file_tmp); else shell("sed 's/\\\\r//' $file > $file_tmp"); ensure_copy($file_tmp, $file); ensure_rm($file_tmp); } function replace_text_in_file(string $search_pattern, string $replace_pattern, string $from_file, string $to_file) { $file_content = ensure_read($from_file); $res = str_replace($search_pattern, $replace_pattern, $file_content); ensure_write($to_file, $res); } function gamectl_get_props_as_php_code() : string { $props = props(); $props_str = " $v) $props_str .= "set('$k', " . var_export($v, true). ");\n"; return $props_str; } function run_background_proc($bin, array $args = array(), $redirect_out = '', $redirect_err = '') { if(is_win()) { //TODO: migrate to background jobs $cmd = "powershell.exe Start-Process $bin -NoNewWindow"; if($args) { $cmd .= ' -ArgumentList '; foreach($args as $arg) $cmd .= "'$arg',"; $cmd = rtrim($cmd, ','); } if($redirect_out) $cmd .= " -RedirectStandardOutput '$redirect_out'"; if($redirect_err) $cmd .= " -RedirectStandardError '$redirect_err'"; pclose(popen($cmd, 'r')); } else { $cmd = $bin; foreach($args as $arg) $cmd .= ' ' . escapeshellarg($arg); if($redirect_out) $cmd .= ' > ' . escapeshellarg($redirect_out); if($redirect_err) $cmd .= ' 2> ' . escapeshellarg($redirect_err); $cmd .= ' &'; exec($cmd, $_, $ret); if($ret !== 0) throw new Exception("Error starting worker: $cmd ($ret)"); } } function run_background_gamectl_workers(string $task, array $worker_args) : array { global $GAME_ROOT; $results = array(); $workers = array(); foreach($worker_args as $idx => $args) { $uid = uniqid(); $in_file = $GAME_ROOT . "/build/tmp/in_$uid.work"; $out_file = $GAME_ROOT . "/build/tmp/out_$uid.work"; $log_file = $GAME_ROOT . "/build/tmp/log_$uid.work"; $err_file = $GAME_ROOT . "/build/tmp/err_$uid.work"; $workers[] = array($in_file, $out_file, $log_file, $err_file); ensure_write($in_file, serialize($args)); $cwd = getcwd(); chdir($GAME_ROOT); run_background_proc( is_win() ? 'gamectl.bat' : './gamectl', array('-b', $task, $in_file, $out_file, $err_file), $log_file, $err_file ); chdir($cwd); } try { $log_handles = array(); while(sizeof($results) < sizeof($workers)) { sleep(1); clearstatcache(); foreach($workers as $idx => $worker) { if(isset($results[$idx])) continue; list($in_file, $out_file, $log_file, $err_file) = $worker; if(!isset($log_handles[$idx]) && is_file($log_file)) $log_handles[$idx] = fopen($log_file, 'r'); if(isset($log_handles[$idx])) { while(($buffer = fgets($log_handles[$idx])) !== false) echo $buffer; $pos = ftell($log_handles[$idx]); fclose($log_handles[$idx]); $log_handles[$idx] = fopen($log_file, "r"); fseek($log_handles[$idx], $pos); } if(is_file($err_file) && filesize($err_file) > 0) throw new Exception("Error in worker $idx:\n" . file_get_contents($err_file)); if(is_file($out_file)) $results[$idx] = @unserialize(ensure_read($out_file)); } } } finally { foreach($workers as $item) { list($in_file, $out_file, $log_file, $err_file) = $item; @ensure_rm($in_file); @ensure_rm($out_file); @ensure_rm($log_file); @ensure_rm($err_file); } } return $results; } function kb(string $data) : string { return kb_len(strlen($data)); } function kb_len(int $len) : string { return round($len/1024,2) . "kb"; } function gen_uuid_v4() : string { $UUID_LENGTH_BYTES = 16; $data = random_bytes($UUID_LENGTH_BYTES); $data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100 $data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10 return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)); } function watch_running_process($pid, string $log_file, $exit_matches = array(), $error_matches = array(), bool $verbose = true, $break_after_sec = -1, $ignored_errors = array(), $noted_warnings = array()) { $matches_any_fn = function($buffer, array $matches) { foreach($matches as $match) { $match_idx = strpos($buffer, $match); if($match_idx !== false) return $match_idx; } return false; }; $h = fopen($log_file, "r"); $start = time(); $warnings_list = array(); $throw_fn = function($msg) use (&$warnings_list) { if(count($warnings_list) > 0) // @phpstan-ignore-line $msg .= "\n\nPossible causes:\n" . implode("\n", $warnings_list) . "\n"; throw new Exception($msg); }; $process_gone = false; while(true) { $buffer = fgets($h); if($buffer !== false) { if($verbose) echo $buffer; //log success condition if($matches_any_fn($buffer, $exit_matches) !== false) break; if($matches_any_fn($buffer, $noted_warnings) !== false) $warnings_list[] = $buffer; $buffer_error_idx = $matches_any_fn($buffer, $error_matches); if($buffer_error_idx !== false) { if($matches_any_fn($buffer, $ignored_errors) !== false) echo "Error in log file IGNORED\n"; else $throw_fn("Error condition in log file '$log_file' detected:\n****\n" . trim(substr($buffer, $buffer_error_idx, 300)) . "\n****\n" ); } } else if($process_gone) $throw_fn("Process with id: '$pid' not found"); else { usleep(200000); $pos = ftell($h); fclose($h); $h = fopen($log_file, "r"); fseek($h, $pos); //check if process still exists if(check_process($pid) !== 0) { $process_gone = true; continue; } } if($break_after_sec > 0 && (time() - $start) > $break_after_sec) break; } fclose($h); } function check_process($pid) : int { if(is_win()) { $cmd = "powershell Get-Process -Id $pid"; } else { $cmd = "ps -p $pid"; } exec($cmd, $_, $ret); return $ret; } function which_dir(string $bin) : string { return realpath(dirname(which_path($bin))); } function which_path(string $bin) : string { if(is_win()) { shell("where $bin", $out); return trim($out[0]); } else { shell("which $bin", $out); return trim($out[0]); } } function arg_exists(array $args, string $needle) : bool { $strict = true; return in_array($needle, $args, $strict); } if(!function_exists('str_ends_with')) { function str_ends_with($haystack, $needle) { // search forward starting from end minus needle length characters return $needle === "" || (($temp = strlen($haystack) - strlen($needle)) >= 0 && strpos($haystack, $needle, $temp) !== false); } } function arg_opt(array &$args, $opt, $default, $conv_fn = null) { foreach($args as $idx => $arg) { if(strpos($arg, $opt) === 0) { $arg = substr($arg, strlen($opt)); unset($args[$idx]); return $conv_fn === null ? $arg : $conv_fn($arg); } } return $default; } function arg_opt_check_no_trailing(array $args) { foreach($args as $arg) { if(preg_match('~(--\w+)=.*~', $arg, $m)) throw new Exception("Unknown option '{$m[1]}'"); } } function are_you_sure() { if(!are_you_sure_ask()) { echo "exiting then\n"; exit(); } } function are_you_sure_ask() : bool { echo "Are you sure you want to proceed?(type YES): "; $resp = trim(fread(STDIN, 10)); return $resp == "YES"; } function names_hash_changed(string $crc_file, array $names) : bool { $ctx = hash_init('crc32'); foreach($names as $name) hash_update($ctx, $name); $names_crc = hash_final($ctx, true); $changed = !file_exists($crc_file) || ensure_read($crc_file) != $names_crc; ensure_write($crc_file, $names_crc); return $changed; } function is_apple_silicon() { $arch = php_uname('m'); // Check if the machine type contains 'arm' (ARM architecture) if (strpos($arch, 'arm') !== false) { return true; } // If running on x86_64, check for Apple Silicon using sysctl // (because we might get 'x86_64' when running under Rosetta) if ($arch === 'x86_64') { $sysctl = shell_exec('sysctl -n machdep.cpu.brand_string'); return strpos($sysctl, 'Apple') !== false; } return false; }