file = $refl->getFileName(); $this->line = $refl->getStartLine(); $this->func = $func; $this->name = $name; $this->props = $props; $this->file_changes = $file_changes; } function getName() : string { return $this->name; } function getFile() : string { return $this->file; } function getLine() : int { return $this->line; } function getFunc() : \Closure { return $this->func; } function getArgs() : array { return $this->args; } function getAliases() : array { if($this->_aliases === null) $this->_aliases = $this->_parseAliases(); return $this->_aliases; } function _parseAliases() : array { $alias = $this->getPropOr("alias", ""); if(is_array($alias)) return $alias; else if($alias && is_string($alias)) return explode(",", $alias); else return array(); } /** * @return artefact\TaskmanArtefact[] */ function getArtefacts() : array { if($this->_artefacts === null) $this->_artefacts = $this->_parseArtefacts(); return $this->_artefacts; } /** * @return artefact\TaskmanArtefact[] */ function _parseArtefacts() : array { $artefacts = array(); $specs = $this->getPropOr("artefacts", array()); if(is_callable($specs)) $specs = $specs(); if(is_array($specs)) { foreach($specs as $dst => $src_spec) $artefacts[] = new artefact\TaskmanArtefact($dst, $src_spec); } return $artefacts; } function getFileChanges() : ?TaskmanFileChanges { return $this->file_changes; } function isIncrementalBuild() : bool { return $this->file_changes != null; } function run($args = array()) { global $TASKMAN_CURRENT_TASK; global $TASKMAN_STACK; global $TASKMAN_NO_DEPS; global $TASKMAN_START_TIME; $args_str = serialize($args); if((isset($this->has_run[$args_str]) && $this->has_run[$args_str]) || $this->is_running) return; $task_result = null; try { if($this->getArtefacts() && !$this->_checkIfArtefactsStale()) { $this->has_run[$args_str] = true; return; } $this->is_running = true; $this->args = $args; $TASKMAN_STACK[] = $this; $level = count($TASKMAN_STACK)-1; msg_sys("***** ".str_repeat('-', $level)."task '" . $this->getName() . "' start *****\n"); if(!$TASKMAN_NO_DEPS) { run_many($this->before_deps); run_many($this->getDeps()); } $TASKMAN_CURRENT_TASK = $this; $bench = microtime(true); $task_result = call_user_func_array($this->func, array($this->args, $this)); array_pop($TASKMAN_STACK); if(!$TASKMAN_NO_DEPS) run_many($this->after_deps); msg_sys("***** ".str_repeat('-', $level)."task '" . $this->getName() . "' done(" . round(microtime(true)-$bench,2) . '/' .round(microtime(true)-$TASKMAN_START_TIME,2) . " sec.) *****\n"); $this->has_run[$args_str] = true; $this->is_running = false; } catch(Exception $e) { $error_handler = $GLOBALS['TASKMAN_ERROR_HANDLER']; if($error_handler) $error_handler($e); else throw $e; } return $task_result; } function addBeforeDep($task) { $this->before_deps[] = $task; } function addAfterDep($task) { $this->after_deps[] = $task; } function getDeps() : array { if($this->deps === null) $this->deps = $this->_parseDeps(); return $this->deps; } private function _parseDeps() : array { $deps = $this->getPropOr("deps", ""); if(is_array($deps)) return $deps; else if($deps && is_string($deps)) return internal\_parse_taskstr($deps); return array(); } function getPropOr(string $name, mixed $def) : mixed { return isset($this->props[$name]) ? $this->props[$name] : $def; } function getProp(string $name) : mixed { return $this->getPropOr($name, null); } function hasProp(string $name) : bool { return isset($this->props[$name]); } function getProps() : array { return $this->props; } private function _checkIfArtefactsStale() : bool { $file_changes = $this->getFileChanges(); $stale_found = false; foreach($this->getArtefacts() as $artefact) { $artefact->initSources($file_changes); if(!$stale_found && $artefact->checkNewerSources($file_changes)) $stale_found = true; if($artefact->isStale()) { msg_sys("Task '{$this->name}' artefact '{$artefact->getPath()}' (sources at ".implode(',', $artefact->getNewerSourcesIndices()).") is stale\n"); } } return $stale_found; } function findAnyStaleArtefact() : ?artefact\TaskmanArtefact { foreach($this->getArtefacts() as $artefact) { if($artefact->isStale()) return $artefact; } return null; } } class TaskmanFileChanges { private $changed = array(); private $removed = array(); static function parse(string $json_or_file) { if($json_or_file[0] == '[') $json = $json_or_file; else $json = file_get_contents($json_or_file); $decoded = json_decode($json, true); if(!is_array($decoded)) throw new Exception('Bad json'); $changed = array(); $removed = array(); $base_dir = dirname($_SERVER['PHP_SELF']); foreach($decoded as $items) { if(count($items) != 2) throw new Exception('Bad entry'); list($status, $file) = $items; if(strlen($file) > 0) { if(DIRECTORY_SEPARATOR == '/') { if($file[0] != '/') $file = $base_dir . DIRECTORY_SEPARATOR . $file; } else if(strlen($file) > 1 && $file[1] != ':') $file = $base_dir . DIRECTORY_SEPARATOR . $file; } $file = artefact\normalize_path($file); if($status == 'C') $changed[$file] = true; else if($status == 'R') $removed[$file] = true; else throw new Exception('Unknown status: ' . $status); } return new TaskmanFileChanges($changed, $removed); } //NOTE: these are actually maps: file => true function __construct(array $changed, array $removed) { $this->changed = $changed; $this->removed = $removed; } function isEmpty() : bool { return count($this->changed) == 0 && count($this->removed) == 0; } function matchDirectory(string $dir) : array { $dir = rtrim($dir, '/\\'); $dir .= DIRECTORY_SEPARATOR; $filtered = []; foreach($this->changed as $path => $_) if(strpos($path, $dir) === 0) $filtered[] = $path; foreach($this->removed as $path => $_) if(strpos($path, $dir) === 0) $filtered[] = $path; return $filtered; } function matchFiles(iterable $files) : array { $filtered = []; foreach($files as $file) { if(isset($this->changed[$file]) || isset($this->removed[$file])) $filtered[] = $file; } return $filtered; } }