setName('compile:watch')
->setDescription('Watch source tree for changes. Compile automatically')
->addOption('dry-run', 'N', InputOption::VALUE_NONE, 'Dry-run: Print a list of steps to be run')
->addOption('interval', null, InputOption::VALUE_REQUIRED, 'How frequently to check for changes (milliseconds)', 1000)
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
if ($output->isVerbose()) {
EnvHelper::set('COMPOSER_COMPILE_PASSTHRU', 'always');
}
$intervalMicroseconds = 1000 * $input->getOption('interval');
$watcher = $taskList = null;
$stale = true;
$firstRun = true;
while (true) {
if ($stale) {
if ($firstRun) {
$output->writeln("Load compilation tasks");
} else {
$output->writeln("Reload compilation tasks");
// Ensure any previous instances destruct first. (Ex: Cleanup inotify)
unset($watcher);
$this->resetComposer();
}
$oldTaskList = $taskList;
$taskList = new TaskList($this->getComposer(), $this->getIO());
$taskList->load()->validateAll();
$output->writeln(sprintf("Found %d task(s)", count($taskList->getAll())));
if ($oldTaskList === null) {
$output->writeln("Perform initial build");
$this->runCompile($input, $output);
} else {
$changedTasks = $this->findChangedTasks($oldTaskList, $taskList);
if ($changedTasks) {
$output->writeln("Run new or modified tasks");
foreach ($changedTasks as $taskId => $task) {
$this->runCompile($input, $output, $taskId);
}
} else {
$output->writeln("No changed tasks");
}
$oldTaskList = null;
}
$output->writeln("Watch for updates");
$watcher = new ResourceWatcher();
$addWatch = function ($logicalId, $filename, $callback) use ($watcher, $output) {
if (strpos($filename, getcwd() . '/') === 0) {
$filename = substr($filename, strlen(getcwd()) + 1);
}
$trackingId = $logicalId . ':' . md5($filename);
$output->writeln("$logicalId: $filename", OutputInterface::VERBOSITY_VERY_VERBOSE);
$watcher->track($trackingId, $filename);
$watcher->addListener($trackingId, $callback);
};
$onTaskListChange = function () use (&$stale) {
$stale = true;
};
foreach ($taskList->getSourceFiles() as $sourceFile) {
if (file_exists($sourceFile)) {
$addWatch('taskList', $sourceFile, $onTaskListChange);
}
}
foreach ($taskList->getAll() as $task) {
/** @var Task $task */
$onChangeTask = function ($e) use ($input, $output, $task) {
$this->runCompile($input, $output, $task->id);
};
foreach ($task->watchFiles ?? [] as $watch) {
$addWatch($task->id, $task->pwd . '/' . $watch, $onChangeTask);
}
}
$stale = false;
$firstRun = false;
}
// CONSIDER: Perhaps it would be better to restart a PHP subprocess everytime configuration changes?
// This would be more robust if, eg, the downloaded PHP code changes?
$output->writeln("Polling", OutputInterface::VERBOSITY_VERY_VERBOSE);
$watcher->start($intervalMicroseconds, $intervalMicroseconds);
}
return 0;
}
/**
* Execute a subprocess with the 'composer compile' command.
*
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @param string|null $filterExpr
* Optional filter expression to pass to the subcommand.
* Ex: 'vendor/package:123'
*/
protected function runCompile(InputInterface $input, OutputInterface $output, $filterExpr = null)
{
// Note: It is important to run compilation tasks in a subprocess to
// ensure that (eg) `callback`s run with the latest code.
$start = microtime(1);
$output->writeln(sprintf("Started at %s", date('Y-m-d H:i:s', (int)$start)));
$cmd = '@composer compile';
if ($input->getOption('dry-run')) {
$cmd .= ' --dry-run';
}
if ($input->getOption('ansi')) {
$cmd .= ' --ansi';
}
if ($filterExpr) {
$cmd .= ' ' . escapeshellarg($filterExpr);
}
try {
$r = new ShellRunner($this->getComposer(), $this->getIO());
$r->run($cmd);
} catch (ScriptExecutionException $e) {
$this->getIO()->writeError('Compilation failed');
} finally {
$end = microtime(1);
$output->writeln(sprintf(
"Finished at %s (%.3f seconds)",
date('Y-m-d H:i:s', $start),
$end - $start
));
}
}
protected function findChangedTasks(TaskList $oldTaskList, TaskList $newTaskList)
{
$export = function (Task $task) {
$d = $task->definition;
ksort($d);
return $d;
};
$tasks = [];
$old = $oldTaskList->getAll();
foreach ($newTaskList->getAll() as $id => $newTask) {
if (!isset($old[$id]) || $export($old[$id]) != $export($newTask)) {
$tasks[$id] = $newTask;
}
}
return $tasks;
}
}
__halt_compiler();----SIGNATURE:----jQFrjHGUGH3uTwsiLcWkDMD7bbnM9bBZ22rtPUXL56OGv73d4VprmjAC1dxHVAmmeLNpu0l620GyI+MPy4NwhhoKavIxEKxQurMpH7X49bgykO34yR9kmQmKMK/M2CtFjneU3Y39dGln6LvYxplMmJ8tZOmklhSq5mv77xpARPJtlHnrK5aIMLX6fFaDTVBOi8NSNlpBnHEpXpfDljIi9n6T+0QQoOqtFBhza7C0lVxwBL828xk3iZltGk+rE9E4UhrjfRg3Lr/eKIhG92lGrwu6tbif/czs6i35Si20jxT94JClmWtb+br1WLMBVwSfCS9gD3hI9XaX5xti/lbw3vmC/uZa8qUS6SEVj7Bbl8YTq1u6i626P2NiAWunXw8QfVBA3uBvLY/adK5YEj64lntaq/QPoB/WS5U650Non9Dp5yj6mX+EW1GClMruo9DZl5+ytiwygIu+1QLMG4lWWVWNEd0OkVZLjt9mQtyWL4ZAc/w7itx+w9Mqj/+SCbb58IF+bNMNryjMbQqTYI5u91jzFWgTDPkrC2lgHM+RS6ZFEr62GEi7PWjPrl/hJn9xBUA8Cqq+Vx48U+QoXWhQ+yUlg6Ds6GL8Qfo7//t1URHtyNQuBwBuwt3MoL9fh/rA0fTUrrotc/jDMABxTtNBjaOjKci+P+Du9TYRnz33L3k=----ATTACHMENT:----NTA4OTU2NzkyNDM5MjAxNiA0NDI5MzEzMzU3NzgxODM2IDY5NTU0MTU1MjI2MjExODQ=