Compare commits

...

9 Commits

Author SHA1 Message Date
Pavel Shevaev 97e948d132 Artefact is considered stale if passed changed files match the spec (without additional stale check)
Publish PHP Package / docker (push) Successful in 3m28s Details
2025-04-29 14:11:51 +03:00
Pavel Shevaev ade0ef5373 Adding --bench option to show basic tasks bench info
Publish PHP Package / docker (push) Successful in 7s Details
2025-04-28 14:38:13 +03:00
Pavel Shevaev 058ab92420 array_splice possible fix
Publish PHP Package / docker (push) Successful in 6s Details
2025-04-28 14:28:29 +03:00
Pavel Shevaev 54cd3576d7 Adding initial support for tasks bench times; A bit improving similar tasks logic in case of task was not found; A bit cleaning internals;
Publish PHP Package / docker (push) Successful in 6s Details
2025-04-28 14:24:44 +03:00
Pavel Shevaev 66b3abf819 Adding proper directory path normalization for artefacts
Publish PHP Package / docker (push) Successful in 9s Details
2025-04-25 17:37:54 +03:00
Pavel Shevaev e5f15251d7 Improving lazy checks for stale artefacts; Adding artefacts stale check bench timings
Publish PHP Package / docker (push) Successful in 6s Details
2025-04-23 20:33:17 +03:00
Pavel Shevaev c0caa504a9 Adding malformed json sanitation for file change events
Publish PHP Package / docker (push) Successful in 6s Details
2025-04-21 17:27:38 +03:00
Pavel Shevaev 421d6580dc It is now possible to specify exact log level using new -l option; Adding debug logs for artefacts sources resolving
Publish PHP Package / docker (push) Successful in 6s Details
2025-04-18 15:27:33 +03:00
Pavel Shevaev dbf30f1231 A bit tweaking heuristics for changed files (ignoring garbage created status)
Publish PHP Package / docker (push) Successful in 6s Details
2025-04-18 14:49:23 +03:00
3 changed files with 157 additions and 61 deletions

View File

@ -43,6 +43,7 @@ class TaskmanArtefact
if(isset($this->sources_fn[$idx]))
{
$fn = $this->sources_fn[$idx];
\taskman\log(2, "Task '{$this->task->getName()}' artefact '{$this->path}' resolving sources at $idx\n");
$sources = $fn();
$this->sources[$idx] = $sources;
return $sources;
@ -69,6 +70,7 @@ class TaskmanArtefact
if(isset($this->sources_changed_fn[$idx]))
{
$fn = $this->sources_changed_fn[$idx];
\taskman\log(2, "Task '{$this->task->getName()}' artefact '{$this->path}' resolving changed sources at $idx\n");
$changed = $fn();
$this->sources_changed[$idx] = $changed;
return $changed;
@ -85,17 +87,40 @@ class TaskmanArtefact
function isSourcesAffected(int $idx) : bool
{
return isset($this->sources_affected[$idx]) && $this->sources_affected[$idx];
if(!isset($this->sources_affected[$idx]))
{
//NOTE: more conservative implementation which always checks for staleness
//$sources = $this->task->getFileChanges() != null ?
// //tries to match changed files with sources spec
// $this->getChangedSources($idx) :
// $this->getSources($idx);
//$this->sources_affected[$idx] = is_stale($this->getPath(), $sources);
//NOTE: if there any file changes we simply check if there are any changed sources,
// and if yes then we mark the artefact as affected
if($this->task->getFileChanges() != null)
$this->sources_affected[$idx] = count($this->getChangedSources($idx)) > 0;
else
$this->sources_affected[$idx] = is_stale($this->getPath(), $this->getSources($idx));
}
return $this->sources_affected[$idx];
}
function getAffectedSourcesIndices() : array
{
return array_keys($this->sources_affected);
$indices = array();
foreach($this->sources_affected as $idx => $is_affected)
{
if($is_affected)
$indices[] = $idx;
}
return $indices;
}
function isStale() : bool
{
return count($this->sources_affected) > 0;
return count($this->getAffectedSourcesIndices()) > 0;
}
function initSources()
@ -104,7 +129,7 @@ class TaskmanArtefact
$all_src_specs = $this->getSourcesSpec();
//let's process a convenience special case
if(count($all_src_specs) > 0 && !is_array($all_src_specs[0]))
if(count($all_src_specs) > 0 && !is_array($all_src_specs[0]) && !is_int($all_src_specs[0]))
$all_src_specs = [$all_src_specs];
foreach($all_src_specs as $src_idx => $src_spec)
@ -116,7 +141,10 @@ class TaskmanArtefact
$this->setSourcesFn($src_idx, function() use($src_spec) {
$dir2files = array();
foreach($src_spec[0] as $spec_dir)
{
$spec_dir = normalize_path($spec_dir);
$dir2files[$spec_dir] = scan_files([$spec_dir], $src_spec[1]);
}
return new TaskmanDirFiles($dir2files);
});
@ -126,6 +154,7 @@ class TaskmanArtefact
$changed = array();
foreach($src_spec[0] as $spec_dir)
{
$spec_dir = normalize_path($spec_dir);
$matches = $file_changes->matchDirectory($spec_dir, $src_spec[1]);
$changed[$spec_dir] = $matches;
}
@ -144,6 +173,18 @@ class TaskmanArtefact
);
}
}
//references another artefact by its index
else if(is_int($src_spec))
{
$src_artefact = $this->task->getArtefact($src_spec);
foreach($src_artefact->getSourcesSpec() as $src_idx => $_)
{
$this->setSourcesFn($src_idx, fn() => $src_artefact->getSources($src_idx));
if($file_changes != null)
$this->setSourcesChangedFn($src_idx, fn() => $src_artefact->getChangedSources($src_idx));
}
}
else
throw new Exception("Unknown artefact '{$this->getPath()}' source type" . gettype($src_spec));
}
@ -151,17 +192,13 @@ class TaskmanArtefact
function checkAffectedSources() : bool
{
$file_changes = $this->task->getFileChanges();
//let's check if any source is affected
foreach($this->getSourcesSpec() as $src_idx => $src_spec)
{
$sources = $file_changes != null ? $this->getChangedSources($src_idx) : $this->getSources($src_idx);
if(is_stale($this->getPath(), $sources))
{
$this->sources_affected[$src_idx] = true;
if($this->isSourcesAffected($src_idx))
return true;
}
}
return false;
}
}

View File

@ -18,7 +18,7 @@ function _default_usage($script_name = "<taskman-script>")
echo " -- pass all options verbatim after this mark\n";
}
function _collect_tasks()
function _collect_tasks() : array
{
global $TASKMAN_TASKS;
global $TASKMAN_TASK_ALIASES;
@ -59,14 +59,14 @@ function _collect_tasks()
}
}
_validate_tasks();
_validate_tasks($TASKMAN_TASKS);
return $TASKMAN_TASKS;
}
function _validate_tasks()
function _validate_tasks(array $tasks)
{
global $TASKMAN_TASKS;
foreach($TASKMAN_TASKS as $task)
foreach($tasks as $task)
{
try
{
@ -88,7 +88,7 @@ function _validate_tasks()
}
}
function _get_task_candidates()
function _get_task_candidates() : array
{
global $TASKMAN_CLOSURES;
@ -105,7 +105,7 @@ function _get_task_candidates()
return $cands;
}
function _resolve_callable_prop($name)
function _resolve_callable_prop(string $name)
{
$prop = $GLOBALS['TASKMAN_PROP_' . $name];
if(!($prop instanceof \Closure))
@ -114,25 +114,24 @@ function _resolve_callable_prop($name)
$GLOBALS['TASKMAN_PROP_' . $name] = $value;
}
function _isset_task($task)
function _isset_task(string $task) : bool
{
global $TASKMAN_TASKS;
global $TASKMAN_TASK_ALIASES;
return isset($TASKMAN_TASKS[$task]) || isset($TASKMAN_TASK_ALIASES[$task]);
}
function _get_hints($task)
function _get_hints(string $task) : array
{
global $TASKMAN_TASKS;
global $TASKMAN_TASK_ALIASES;
$tasks = array_merge(array_keys($TASKMAN_TASKS), array_keys($TASKMAN_TASK_ALIASES));
$found = array_filter($tasks, function($v) use($task) { return strpos($v, $task) === 0; });
$found = array_merge($found, array_filter($tasks, function($v) use($task) { $pos = strpos($v, $task); return $pos !== false && $pos > 0; }));
$found = array_filter($tasks, function($v) use($task) { similar_text($v, $task, $p); return $p > 70; });
return $found;
}
//e.g: run,build,zip
function _parse_taskstr($str)
function _parse_taskstr(string $str) : array
{
$task_spec = array();
$items = explode(',', $str);
@ -172,6 +171,7 @@ function _process_argv(array &$argv)
global $TASKMAN_BATCH;
global $TASKMAN_NO_DEPS;
global $TASKMAN_FILE_CHANGES;
global $TASKMAN_SHOW_BENCH;
$filtered = array();
$process_defs = false;
@ -186,10 +186,6 @@ function _process_argv(array &$argv)
$filtered[] = $argv[$j];
break;
}
else if($v == '-D')
{
$process_defs = true;
}
else if($v == '-V')
{
$TASKMAN_LOG_LEVEL = 2;
@ -202,10 +198,22 @@ function _process_argv(array &$argv)
{
$TASKMAN_LOG_LEVEL = -1;
}
else if($v == '-l')
{
if(!isset($argv[$i+1]))
throw new \taskman\TaskmanException("Log level is missing");
$TASKMAN_LOG_LEVEL = intval($argv[$i+1]);
++$i;
}
else if($v == '-O')
{
$TASKMAN_NO_DEPS = true;
}
else if($v == '--bench')
{
$TASKMAN_SHOW_BENCH = true;
}
else if($v == '-c')
{
if(!isset($argv[$i+1]))
@ -222,6 +230,10 @@ function _process_argv(array &$argv)
++$i;
}
else if($v == '-D')
{
$process_defs = true;
}
else if($process_defs)
{
$eq_pos = strpos($v, '=');
@ -282,3 +294,31 @@ function _extract_lines_from_file(string $file_path) : array
return $lines;
}
function _show_bench(array $tasks, int $limit = 5)
{
if(!$tasks)
return;
$times = array();
foreach($tasks as $name => $task)
{
if($task->hasRun())
$times[$name] = round($task->getRunTime(), 2);
}
uasort($times, fn($a, $b) => $b <=> $a);
if(count($times) > $limit)
array_splice($times, $limit);
if(!$times)
return;
\taskman\log(1, "Top ".count($times)." time consuming tasks:\n");
$n = 0;
foreach($times as $name => $time)
{
++$n;
\taskman\log(1, "$n) Task '$name': $time sec\n");
}
}

View File

@ -2,6 +2,7 @@
namespace taskman;
use Exception;
//TODO: get rid of global variables
$GLOBALS['TASKMAN_TASKS'] = array();
$GLOBALS['TASKMAN_CLOSURES'] = array();
$GLOBALS['TASKMAN_STACK'] = array();
@ -15,6 +16,7 @@ $GLOBALS['TASKMAN_LOGGER'] = '\taskman\internal\_default_logger';
$GLOBALS['TASKMAN_ERROR_HANDLER'] = null;
$GLOBALS['TASKMAN_START_TIME'] = 0;
$GLOBALS['TASKMAN_FILES_CHANGES'] = null;
$GLOBALS['TASKMAN_SHOW_BENCH'] = false;
include_once(__DIR__ . '/internal.inc.php');
include_once(__DIR__ . '/util.inc.php');
@ -39,7 +41,11 @@ class TaskmanTask
private $props = array();
private bool $is_running = false;
//contains statuses of runs in the following format:
// serialized_args => 1|2
// 1 - artefacts are not stale, 2 - full run
private array $has_run = array();
private float $run_time = 0;
private ?array $deps = null;
private array $before_deps = array();
@ -151,6 +157,16 @@ class TaskmanTask
return $this->file_changes != null;
}
function hasRun() : bool
{
return count($this->has_run) > 0;
}
function getRunTime() : float
{
return $this->run_time;
}
function run($args = array())
{
global $TASKMAN_CURRENT_TASK;
@ -158,9 +174,9 @@ class TaskmanTask
global $TASKMAN_NO_DEPS;
global $TASKMAN_START_TIME;
$args_str = serialize($args);
$args_hash = serialize($args);
if((isset($this->has_run[$args_str]) && $this->has_run[$args_str]) ||
if((isset($this->has_run[$args_hash]) && $this->has_run[$args_hash]) ||
$this->is_running)
return;
@ -168,10 +184,20 @@ class TaskmanTask
try
{
if($this->getArtefacts() && !$this->_checkIfArtefactsStale())
$level = count($TASKMAN_STACK);
if($this->getArtefacts())
{
$this->has_run[$args_str] = true;
return;
$bench = microtime(true);
$stale_found = $this->_checkIfArtefactsStale();
$this->run_time += (microtime(true) - $bench);
log(0, "***** ".str_repeat('-', $level)."task '" . $this->getName() . "' artefacts check done(" .
round($this->run_time,2) . '/' .round(microtime(true)-$TASKMAN_START_TIME,2) . " sec.) *****\n");
if(!$stale_found)
{
$this->has_run[$args_hash] = 1;
return;
}
}
$this->is_running = true;
@ -179,8 +205,6 @@ class TaskmanTask
$TASKMAN_STACK[] = $this;
$level = count($TASKMAN_STACK)-1;
log(0, "***** ".str_repeat('-', $level)."task '" . $this->getName() . "' start *****\n");
if(!$TASKMAN_NO_DEPS)
@ -199,10 +223,11 @@ class TaskmanTask
if(!$TASKMAN_NO_DEPS)
run_many($this->after_deps);
$this->run_time += (microtime(true) - $bench);
log(0, "***** ".str_repeat('-', $level)."task '" . $this->getName() . "' done(" .
round(microtime(true)-$bench,2) . '/' .round(microtime(true)-$TASKMAN_START_TIME,2) . " sec.) *****\n");
round($this->run_time,2) . '/' .round(microtime(true)-$TASKMAN_START_TIME,2) . " sec.) *****\n");
$this->has_run[$args_str] = true;
$this->has_run[$args_hash] = 2;
$this->is_running = false;
}
catch(Exception $e)
@ -313,6 +338,8 @@ class TaskmanFileChanges
{
$lines = internal\_extract_lines_from_file($json_or_file);
$json = '[' . implode(',', $lines) . ']';
//NOTE: adding some sanitization for potentially malformed json
$json = str_replace(["\\", "\r\n", "\n"], ["/", "", ""], $json);
}
$decoded = json_decode($json, true);
@ -322,6 +349,8 @@ class TaskmanFileChanges
$changed = array();
$base_dir = dirname($_SERVER['PHP_SELF']);
//for debug
//var_dump($decoded);
foreach($decoded as $items)
{
@ -345,7 +374,11 @@ class TaskmanFileChanges
if($status == 'Changed')
$changed[$file] = self::Changed;
else if($status == 'Created')
$changed[$file] = self::Created;
{
//let's status for files which were modified and then created
if(isset($changed[$file]) && $changed[$file] !== self::Changed)
$changed[$file] = self::Created;
}
else if($status == 'Renamed')
$changed[$file] = self::Renamed;
else if($status == 'Deleted')
@ -451,11 +484,11 @@ function main(
$GLOBALS['TASKMAN_SCRIPT'] = array_shift($argv);
internal\_collect_tasks();
$task_objs = internal\_collect_tasks();
$always_tasks = array();
$default_task = null;
foreach(get_tasks() as $task_obj)
foreach($task_objs as $task_obj)
{
if($task_obj->hasProp('always'))
array_unshift($always_tasks, $task_obj);
@ -479,30 +512,13 @@ function main(
if(count($tasks) == 1 && !internal\_isset_task($tasks[0]))
{
$pattern = $tasks[0];
if($pattern[0] == '~')
{
$pattern = substr($pattern, 1, strlen($pattern) - 1);
$is_similar = true;
}
elseif(substr($pattern, -1, 1) == '~')
{
$pattern = substr($pattern, 0, strlen($pattern) - 1);
$is_similar = true;
}
else
$is_similar = false;
$hints = internal\_get_hints($pattern);
if($is_similar && count($hints) == 1)
$tasks = $hints;
else
{
$similars = '';
if($hints)
$similars .= "\nSimilar tasks: " . implode(', ', $hints) . ".";
$similars = '';
if($hints)
$similars .= "\nSimilar tasks: " . implode(', ', $hints) . ".";
throw new Exception("Task '{$tasks[0]}' not found. $similars");
}
throw new Exception("Task '{$tasks[0]}' not found. $similars");
}
run_many($tasks, $argv);
@ -510,6 +526,9 @@ function main(
else if($default_task)
run($default_task, $argv);
if($GLOBALS['TASKMAN_SHOW_BENCH'])
internal\_show_bench($task_objs);
log(0, "***** All done (".round(microtime(true)-$GLOBALS['TASKMAN_START_TIME'],2)." sec.) *****\n");
}