$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; } function need_to_regen_any(array $files, array $deps, $debug = false) { $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"; } return need_to_regen($earliest_file, $deps, $debug); } function fnmatch_patterns($file, array $fnmatch_patterns) { 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($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 = _($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($src, $dst, $dir_perms = 0777, $excludes = array()) { recurse_copy($src, $dst, $dir_perms, COPY_MODE_BUILTIN, false, $excludes); } function ensure_copy_file_if_differs($src_file, $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($src, $dst, $dir_perms = 0777, $excludes = array()) { recurse_copy($src, $dst, $dir_perms, COPY_MODE_BUILTIN, true, $excludes); } function ensure_hardlink($src, $dst, $dir_perms = 0777, $excludes = array()) { recurse_copy($src, $dst, $dir_perms, COPY_MODE_HARDLINK, true, $excludes); } function ensure_duplicate($src, $dst, $dir_perms = 0777) { recurse_copy($src, $dst, $dir_perms, COPY_MODE_SYSTEM); } function recurse_copy($src, $dst, $dir_perms = 0777, $copy_mode = COPY_MODE_BUILTIN, $mtime_check = false, $excludes = 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); 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, $copy_mode, $mtime_check); } } } closedir($dir); } function shell($cmd, &$out=null) { shell_try($cmd, $ret, $out); if($ret != 0) throw new Exception("Shell execution error(exit code $ret)"); } function shell_try($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($cmd, $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($cmd, &$ret, &$out) { //TODO: do we really need to redirect error stream? $proc = popen("$cmd 2>&1", 'r'); $log = ''; //TODO: how can this be? if(is_string($proc)) { $log = $proc; _log($log, 1); } else { while($logline = fgets($proc)) { $log .= $logline; _log($logline, 1); } } $out = explode("\n", $log); $ret = pclose($proc); } function _ensure_copy_file($src, $dst, $copy_mode = COPY_MODE_BUILTIN, $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($src, $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($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); msg_dbg("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; 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($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($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"); shell(_( "cd $GAME_ROOT/client && " . "$xcode_filter xcodebuild -workspace %XCODE_WKSPACE% -scheme $scheme -configuration $config -sdk $sdk SYMROOT=$GAME_ROOT/build/client" )); } function git_get_info() { global $GAME_ROOT; 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 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() { 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 git_try_commit($files, $msg) { try { exec("git add $files && git commit -m \"$msg\" && git pull && git push"); } catch(Exception $e) { echo "Failed to commit changes. Apparently, nothing changed\n"; } } 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; shell("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"; 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($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($search_pattern, $replace_pattern, $from_file, $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() { $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($task, array $worker_args) { 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, $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(), $noted_warnings = 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(); $warnings_list = array(); $throw_fn = function($msg) use (&$warnings_list) { if(count($warnings_list) > 0) $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)) break; if($matches_any_fn($buffer, $noted_warnings)) $warnings_list[] = $buffer; if($matches_any_fn($buffer, $error_matches)) { if($matches_any_fn($buffer, $ignored_errors)) echo "Error in log file IGNORED\n"; else $throw_fn("Error condition in log file detected"); } } 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) { if(is_win()) { $cmd = "powershell Get-Process -Id $pid"; } else { $cmd = "ps -p $pid"; } exec($cmd, $_, $ret); return $ret; } function which_dir($bin) { return realpath(dirname(which_path($bin))); } function which_path($bin) { if(is_win()) { shell("where $bin", $out); return trim($out[0]); } else { shell("which $bin", $out); return trim($out[0]); } } function arg_exists($args, $needle) { $strict = true; return in_array($needle, $args, $strict); }