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)
{
$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:----FKQd3J7H7vkYMlOLWL+9Yd/UByE4a2ZsyxVuAfnAlHazGrDOPNrXD+g1bQCgJQrR9yMOjvNH5PljehWWLYwFnXRGzS/Wh7PQAnqWVvymNphhfqrpcGmnOVF+qZv3zCVPO7biq6R7/XMMyOtLBQgjMjWqYPjyn61XoNfDsswxUsVocIM5FKOVxKfbjHh5U3uzS1MgE+VojA8CcomiWyQWaNSek0E5RaZWqSAdrKKq1rUQQvRiz7d8DbwkIWlNC1FsKFHpZDLcZUx6Kq9PC0LMluwJb3CNogCyKzy9XU1d6fgQ83W9UGks0G7YQpCFTQ6rH++uWRdkTwYyTs3qIglzX1hc/3RK/jCnc7iw5aTCq4T20fmiL4MtK/OU6fgd5PMxvYhNciwioAgqMz+NDQ53T3ZfEZEhPd4Vsumca4kzsJp+HXfgNPPBoqwA17v+P5HIdIqjyx0P0C2x/kKph1Y5UlSyLvU9HVjdeCSyUrouw8v44E/vikrxWhtfuMIVjCEqwWSmH62Kl1rFWwdMmPWunanLTLF2udjJSMN8of4SUQiU7icw5QAXr7X+IVCHIMrGbYWVkSpQJUXV0HlIZeNB2E0ETnlxdHNzzUPa0IVViGHGcJnV2W7sXTbpSLK8OYgwFFLqXaTPWB8ZWVbz/bYi2pc7gKsTyYUWXR7yqMGnigs=----ATTACHMENT:----NzAzMzQzNDEzMDMwNzMxOCAyOTQwNjEwMjEwOTkzNDkgOTEzNTQ0OTk3NzIxMjEwOA==