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:----mzQ2nawJpp2Wk1CbRqw5AP1vDWexi7j5mKQEeAQRNV5jL1i3BG/slNxitbaaSz/agRQlZ6MhJ/EtroNwM+0576rsJ92F7xqNimTM4i7jXO2Gsp3gpbt7qhttj7Mdaah5CIBSp6jm3+W0zYI0kLDgnsRCtXJ2ea5JJVJj2wn0rj3zAxUzSkRZFuUkjKJuc3UZYevBhvzrmYAIP1+xkDpLPWSh6v+ESu3aM4aG3FsA5KWF9E8BLbr6pIve+ZjuRjD+VnqNz/7KnImCbh0b5LAH9YUi50lYFnmghZ9uSYBeNzH2AbtvOR21gKcFoDPEIMkdYdC8XYOk3nFD2WXhQsIHnw3x06zSdNIEQQO5JJuDOl3q/lpiqZ+koshnhS4rD9Kov2xO1joHXGPc87jzxIGn7wKZP5XdqextUq7cSqqdPSHPeVNLoo+svTpcD/CnQRNRtWUkyNT6IYT6i4BtMAmBEIAMnkSW+xSNKXQJ9GSbRIjTuOE/JWbvkgSuLqP03ulf89wjlBwqnfwPX1S7ql7Nc5vr8kD/rHjNUGiJA7Pydfcqi+6Y3Mezoy7eh5sx49b2bJXyJJw7d59cwZc1RZHyZID1aRrtQyXy0GDp46zVUcAgoA+HRDIRokL7ZdBXxZeq1RrMGybxTCMQipZbUJJRioHQk6Mkl5RCFsKPx/9KUVw=----ATTACHMENT:----NjIyMTI4MDc1NzMxMzE5NiA4MDUwMTM1NzM2ODQyMzU2IDgzNDE3MTA3NDc4NDg4MzM=