$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($path, $unix=null/*null means try to guess*/) { 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($path) { $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($arr) { 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($json) { return prettyJSON($json); } function need_to_regen($file, array $deps, $debug = false) { if(!is_file($file)) { if($debug) echo "! $file\n"; return true; } $fmtime = filemtime($file); foreach($deps as $dep) { if(is_file($dep) && (filemtime($dep) > $fmtime)) { if($debug) echo "$dep > $file\n"; return true; } } return false; } //force mode: 0 - none, 1 - force write always, 2 - write only if content differs function gen_file($tpl_file, $result_file, $deps = array(), $force = 0, $perms = 0640) { global $GAME_ROOT; $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 = taskman_str($txt); if($force == 2 && 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) { 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); taskman_dmsg("> $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); } function ensure_copy($src, $dst, $dir_perms = 0777, $excludes = array()) { recurse_copy($src, $dst, $dir_perms, false, false, $excludes); } function ensure_sync($src, $dst, $dir_perms = 0777, $excludes = array()) { recurse_copy($src, $dst, $dir_perms, false, true, $excludes); } function ensure_duplicate($src, $dst, $dir_perms = 0777) { recurse_copy($src, $dst, $dir_perms, true); } function recurse_copy($src, $dst, $dir_perms = 0777, $sys_copy = false, $mtime_check = false, $excludes = array()) { taskman_dmsg("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, $sys_copy, $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, $sys_copy, $mtime_check, $excludes); else { $excluded = false; foreach($excludes as $exclude_pattern) { $excluded = $excluded || (bool)preg_match("~$exclude_pattern~", $src . '/' . $file); } if($excluded) continue; _ensure_copy_file($src . '/' . $file, $dst . '/' . $file, $sys_copy, $mtime_check); } } } closedir($dir); } function _ensure_copy_file($src, $dst, $sys_copy = false, $mtime_check = false) { if($mtime_check && file_exists($dst) && filemtime($src) <= filemtime($dst)) return; taskman_dmsg("$src => $dst\n"); ensure_mkdir(dirname($dst)); if($sys_copy) taskman_shell_ensure("cp -a $src $dst"); else { if(!copy($src, $dst)) throw new Exception("Could not copy '$src' to '$dst'"); } } function ensure_symlink($src, $dst, $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); taskman_dmsg("symlinking $src -> $dst \n"); if(!symlink($src, $dst)) throw new Exception("Could not create symlink"); } function ensure_rm($what) { if(is_dir($what) && !is_link($what)) rrmdir($what); else if(is_file($what) || is_link($what)) unlink($what); } function ensure_mkdir($dir, $perms = 0775) { if(is_dir($dir)) return; taskman_dmsg("mkdir $dir\n"); if(!mkdir($dir, $perms, true)) throw new Exception("Could not create dir '$dir'"); taskman_dmsg("chmod " . decoct($perms) . " $dir\n"); if(!chmod($dir, $perms)) throw new Exception("Could not chmod " . decoct($perms) . " dir '$dir'"); } function ensure_var_dir($dir) { ensure_mkdir($dir, 0777); $items = fmatch("$dir/*"); foreach($items as $item) { $perms = is_dir($item) ? 0770 : 0660; taskman_dmsg("chmod " . decoct($perms) . " $item\n"); if(!chmod($item, $perms)) throw new Exception("Could not chmod " . decoct($perms) . " '$item'"); } } function compare_files_timestamp($file_a, $file_b) { $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($pat) { $res = glob($pat); if(!is_array($res)) return array(); return $res; } function rrmdir($dir, $remove_top_dir = true) { if(is_dir($dir)) { $objects = scandir($dir); foreach($objects as $object) { if($object != "." && $object != "..") { if(filetype($dir."/".$object) == "dir") rrmdir($dir."/".$object); else unlink($dir."/".$object); } } } if($remove_top_dir) { if(is_link($dir)) unlink($dir); else if(is_dir($dir)) rmdir($dir); } } function run_apple_script($script) { $vm = popen("osascript", "w"); fwrite($vm, $script); // run script will always exit with status 1 pclose($vm); } function client_xcode_build($scheme, $config = 'Debug', $sdk = 'iphonesimulator4.3') { global $GAME_ROOT; $xcode_filter = ". $GAME_ROOT/utils/xcodefilter.sh"; ensure_mkdir("$GAME_ROOT/build/client"); taskman_shell_ensure(taskman_str( "cd $GAME_ROOT/client && " . "$xcode_filter xcodebuild -workspace %XCODE_WKSPACE% -scheme $scheme -configuration $config -sdk $sdk SYMROOT=$GAME_ROOT/build/client" )); } function task_git_info() { list($rev_hash, $branch, $revision_number) = git_get_info(); $info = "==============GIT INFO==============\n" . "$rev_hash - hash\n" . "$branch - branch\n" . "$revision_number - revision\n"; echo $info; } //returns cached array(rev_hash, branch), //returns null in case of failure function git_get_info($use_cache = true) { global $GAME_ROOT; static $cached; if($use_cache && $cached) return $cached; if(!is_dir("$GAME_ROOT/.git")) throw new Exception("Not a Git repository"); $out = array(); exec("git rev-parse HEAD", $out); $rev_hash = trim($out[0]); if(!$rev_hash) throw new Exception("Error getting git revision hash"); $out = array(); exec("git rev-parse --abbrev-ref HEAD", $out); $branch = trim($out[0]); if(!$branch) throw new Exception("Error getting git branch"); $out = array(); exec("git branch -r", $out); $revision_number = 0; foreach ($out as $item) { if(strpos($item, "HEAD") !== false) continue; $count = array(); exec("git rev-list $item --count", $count); $revision_number += (int)$count[0]; } if(!$revision_number) throw new Exception("Error getting git revision number"); $cached = array($rev_hash, $branch, $revision_number); return $cached; } function git_get_rev_hash() { list($rev_hash, $_, $__) = git_get_info(); return $rev_hash; } function git_get_branch() { list($_, $branch, $__) = git_get_info(); return $branch; } function git_get_rev_number() { list($_, $__, $rev_number) = git_get_info(); return $rev_number; } function decode_json_ensure($json, $assoc = false) { $res = json_decode($json, $assoc); if($res === null) { $parser = new JsonParser(); $err = $parser->lint($json); echo $json; throw $err; } return $res; } function convert_js_to_msgpack($file, $bin_file) { $bin_file = "$file.lib"; if(need_to_regen($bin_file, array($file))) { echo "Converting $file -> $bin_file...\n"; $data = decode_json_ensure(file_get_contents($file)); $msg = config_msgpack_pack($data); ensure_write($bin_file, $msg); } return true; } function check_and_decode_json($json) { try { $arr = decode_json_ensure($json, true); return array(0, "", $arr); } catch(Exception $e) { return array(1, $e->getMessage(), array()); } } function check_and_decode_jzon($json) { 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($filename, $content, $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) { $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($json) { $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; taskman_shell_ensure("cd $GAME_ROOT && $cmd", $out); return $out[0]; } function boolstr($b) { return $b ? 'true' : 'false'; } function create_js_template_literal($str) { $result = str_replace('\\', '\\\\', $str); // replace \ -> \\ $result = str_replace('`', '\\`', $result); // replace ` -> \` $result = str_replace('$', '\\$', $result); // replace $ -> \$ return "`$result`"; } function create_go_string_literal($str) { $result = str_replace('\\', '\\\\', $str); // replace \ -> \\ $result = str_replace('"', '\\"', $result); // replace " -> \" $result = str_replace("\n", '\\n', $result); // replace new_line -> \n return '"' . $result . '"'; } function convertFileFromDos2UnixFormat($file) { $file_tmp = $file.".tmp"; taskman_shell_ensure("sed \$'s/\\r\$//' $file > $file_tmp"); ensure_copy($file_tmp, $file); ensure_rm($file_tmp); } function removeCarriageReturn($file) { $file_tmp = $file.".tmp"; taskman_shell_ensure("sed 's/\\\\r//' $file > $file_tmp"); ensure_copy($file_tmp, $file); ensure_rm($file_tmp); } function gamectl_get_props_as_php_code() { $props = taskman_getprops(); $props_str = " $v) $props_str .= "taskman_propset('$k', " . var_export($v, true). ");\n"; return $props_str; } class ProcWorker { public $start_time; public $id; public $in_file; public $out_file; public $log_file; public $err_file; public $progress_file; private $script; private $last_progress; function __construct($id, $script, $data) { global $GAME_ROOT; $this->start_time = microtime(true); $this->id = $id; $tmp_dir = "$GAME_ROOT/build/tmp"; $this->in_file = normalize_path("$tmp_dir/proc_$id.in"); $this->out_file = normalize_path("$tmp_dir/proc_$id.out"); $this->log_file = normalize_path("$tmp_dir/proc_$id.log"); $this->err_file = normalize_path("$tmp_dir/proc_$id.err"); $this->progress_file = normalize_path("$tmp_dir/proc_$id.prg"); $this->last_progress = null; $this->script = normalize_path($script); ensure_write($this->in_file, $data); ensure_rm($this->out_file); ensure_rm($this->log_file); ensure_rm($this->err_file); ensure_rm($this->progress_file); run_background_proc("php {$this->script} {$this->in_file} {$this->out_file} {$this->progress_file} {$this->err_file} >{$this->log_file}"); } function getProgress() { if(!is_file($this->progress_file)) return false; $progress = file_get_contents($this->progress_file); //NOTE: if progress hasn't changed don't report it if($progress === $this->last_progress) return false; $this->last_progress = $progress; return $progress; } function isDone() { return file_exists($this->out_file); } //1 - all done, 2 - error, 0 - not ready yet function check(&$out, $unlink_result = true) { if(file_exists($this->out_file)) { $out = file_get_contents($this->out_file); //NOTE: since it may take a lot of space if($unlink_result) unlink($this->out_file); return 1; } else if(file_exists($this->err_file)) { $out = file_get_contents($this->err_file); if($out) { if(file_exists($this->log_file)) $out = file_get_contents($this->log_file) . $out; return 2; } else return 0; } else return 0; } } function run_background_proc($cmd) { if(is_win()) { $wshell = new COM("WScript.Shell"); $ret = $wshell->Run($cmd, 0, false); if($ret !== 0) throw new Exception("Error starting worker: $cmd"); } else { exec("$cmd &", $output, $ret); if($ret !== 0) throw new Exception("Error starting worker: $cmd"); } } function run_background_gamectl_workers($task, array $worker_args) { $results = array(); $workers = array(); foreach($worker_args as $idx => $args) { $uid = uniqid(); $in_file = __DIR__ . "/../build/tmp/in_$uid.work"; $out_file = __DIR__ . "/../build/tmp/out_$uid.work"; $log_file = __DIR__ . "/../build/tmp/log_$uid.work"; $err_file = __DIR__ . "/../build/tmp/err_$uid.work"; $workers[] = array($in_file, $out_file, $log_file, $err_file); ensure_write($in_file, serialize($args)); $proc_cmd = "gamectl -b $task " . escapeshellarg($in_file) . ' ' . escapeshellarg($out_file) . ' ' . escapeshellarg($err_file) . ' > ' . escapeshellarg($log_file); if(!is_win()) $proc_cmd = "./$proc_cmd 2> " . escapeshellarg($err_file); $cwd = getcwd(); chdir(__DIR__ . '/../'); run_background_proc($proc_cmd); 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, $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($str) { return kb_len(strlen($str)); } function kb_len($len) { return round($len/1024,2) . "kb"; } function gen_uuid_v4() { $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, $log_file, $exit_matches = array(), $error_matches = array(), $verbose = true, $break_after_sec = -1, $ignored_errors = array()) { $matches_any_fn = function($buffer, array $matches) { foreach($matches as $match) { if(strpos($buffer, $match) !== false) return true; } return false; }; $h = fopen($log_file, "r"); $start = time(); while(true) { $buffer = fgets($h); if($buffer !== false) { if($verbose) echo $buffer; //log success condition if($matches_any_fn($buffer, $exit_matches)) break; if($matches_any_fn($buffer, $error_matches)) { if($matches_any_fn($buffer, $ignored_errors)) echo "Error in log file IGNORED\n"; else throw new Exception("Error condition in log file detected"); } } else { //wait 2 seconds usleep(200000); $pos = ftell($h); fclose($h); $h = fopen($log_file, "r"); fseek($h, $pos); //check if process still exists /*exec("ps aux | grep '/Applications/Unity/Hub/Editor/2019.4.19f1/Unity.app/Contents/MacOS/Unity' | grep -v grep | awk '{ print $2 }'", $out, $ret); if(count($out) > 0 && $ret === 0) { throw new Exception("Process with id: '$out[0]' not found"); }*/ } if($break_after_sec > 0 && (time() - $start) > $break_after_sec) break; } fclose($h); }