object $handler]
*/
private $handlers;
public function __construct(Composer $composer, IOInterface $io)
{
$this->constructComposerIo($composer, $io);
$this->handlers = [
'export' => new ExportHandler(),
'php-eval' => new PhpEvalHandler(),
'php-method' => new PhpMethodHandler(),
'php-script' => new PhpScriptHandler(),
'sh' => new ComposerScriptHandler(),
'putenv' => new ComposerScriptHandler(),
'php' => new ComposerScriptHandler(),
'composer' => new ComposerScriptHandler(),
];
ksort($this->handlers);
if (Task::HANDLERS !== implode('|', array_keys($this->handlers))) {
throw new \RuntimeException("Task type list is out-of-date. Validation may not work.");
}
}
/**
* Run the items in the $taskList, as per policy.
*
* @param \Civi\CompilePlugin\TaskList $taskList
*/
public function runDefault(TaskList $taskList, $isDryRun = false)
{
$allowedTasks = $taskList->getByFilters($this->getWhitelistRules());
$blockedTasks = array_filter($taskList->getAll(), function ($task) use ($allowedTasks) {
return !isset($allowedTasks[$task->id]);
});
$mode = $this->getMode();
if ($mode === 'prompt' && empty($blockedTasks)) {
// If whitelist covers all extant tasks, then no need for user-interaction.
$mode = 'all';
}
switch ($mode) {
case 'all':
$this->run($taskList->getAll(), $isDryRun);
break;
case 'none':
$this->io->write(
"ERROR: Automatic compilation is disabled. These packages have compilation tasks which have not been executed:"
);
$this->io->write(TaskUIHelper::formatTaskSummary($taskList->getAll()));
$this->io->writeError(sprintf("Skipped %d compilation task(s)", count($taskList->getAll())));
$this->io->writeError("You may run skipped tasks with \"composer compile --all\"");
break;
case 'whitelist':
$disabledCount = $taskList->disable($blockedTasks);
$this->run($taskList->getAll(), $isDryRun);
if ($disabledCount) {
$this->io->writeError(sprintf('WARNING: %d task(s) were omitted due to whitelist policy', $disabledCount));
}
$this->io->writeError("You may run skipped tasks with \"composer compile --all\"");
break;
case 'prompt':
$choice = $this->askApproveTasks($blockedTasks);
if ($choice === 'a') {
$this->addWhitelistRules(array_map(function ($task) {
return $task->packageName;
}, $blockedTasks));
}
switch ($choice) {
case 'y':
case 'a':
$this->run($taskList->getAll(), $isDryRun);
break;
case 'n':
$this->io->writeError(sprintf("Skipped %d compilation task(s)", count($blockedTasks)));
$this->io->writeError("You may run skipped tasks with \"composer compile --all\"");
break;
}
}
}
/**
* Execute a list of compilation tasks.
*
* @param Task[] $tasks
* @param bool $isDryRun
*/
public function run(array $tasks, $isDryRun = false)
{
$io = $this->io;
$dryRunText = $isDryRun ? '(DRY-RUN) ' : '';
if (empty($tasks)) {
return;
}
switch ($this->getPassthruMode()) {
case 'never':
case 'error':
$io->write('Compiling additional files (For full details, use verbose "-v" mode.)');
break;
case 'always':
$io->write('Compiling additional files');
break;
}
$tasks = $this->sortTasks($tasks);
foreach ($tasks as $task) {
/** @var \Civi\CompilePlugin\Task $task */
$package = ($this->composer->getPackage()->getName() === $task->packageName)
? $this->composer->getPackage()
: $this->composer->getRepositoryManager()->getLocalRepository()->findPackage($task->packageName, '*');
$event = new CompileTaskEvent(CompileEvents::PRE_COMPILE_TASK, $this->composer, $this->io, $package, $task, $isDryRun);
$dispatcher = $this->composer->getEventDispatcher();
$dispatcher->dispatch(CompileEvents::PRE_COMPILE_TASK, $event);
if (!$task->active) {
$io->write(
$dryRunText . 'Skip: ' . ($task->title),
true,
IOInterface::VERBOSE
);
continue;
}
$io->write($dryRunText . 'Compile: ' . ($task->title));
if (!$isDryRun) {
$this->runTask($task, $package);
}
$event = new CompileTaskEvent(CompileEvents::POST_COMPILE_TASK, $this->composer, $this->io, $package, $task, $isDryRun);
$this->composer->getEventDispatcher()->dispatch(CompileEvents::POST_COMPILE_TASK, $event);
}
}
protected function runTask(Task $task, PackageInterface $package)
{
$orig = [
'pwd' => getcwd(),
'env' => EnvHelper::getAll(),
];
$passthruPolicyFilter = new PassthruPolicyFilter(
$this->io,
$this->getPassthruMode(),
function ($message) {
if ($this->io->isVerbose()) {
return true;
}
return preg_match(';^;', $message);
}
);
try {
chdir($task->pwd);
TaskTransfer::export($task);
foreach ($task->getParsedRun() as $run) {
if (!isset($this->handlers[$run['type']])) {
throw new \InvalidArgumentException("Unrecognized prefix: @" . $run['type']);
}
$isDryRun = false;
$e = new CompileTaskEvent('compile-task-' . $task->id, $this->composer, $passthruPolicyFilter, $package, $task, $isDryRun);
$this->handlers[$run['type']]->runTask($e, $run['type'], $run['code']);
}
} finally {
TaskTransfer::cleanup();
chdir($orig['pwd']);
EnvHelper::setAll($orig['env']);
}
}
/**
* @param Task[] $tasks
* @return Task[]
*/
public function sortTasks($tasks)
{
usort($tasks, function ($a, $b) {
$fields = ['weight', 'packageWeight', 'naturalWeight'];
foreach ($fields as $field) {
if ($a->{$field} > $b->{$field}) {
return 1;
} elseif ($a->{$field} < $b->{$field}) {
return -1;
}
}
return 0;
});
return $tasks;
}
/**
* Determine whether compilation is enabled.
*
* @return string
* One of:
* - 'all': Automatically run all compilation tasks
* - 'whitelist': Automatically compile anything on the whitelist, and
* reject everything else.
* - 'prompt': Automatically compile anything on the whitelist, and
* prompt for everything else.
* - 'none': Do not compile automatically.
*/
public function getMode()
{
$aliases = [
'0' => 'none',
'1' => 'all',
'off' => 'none',
'on' => 'all',
];
$mode = getenv('COMPOSER_COMPILE');
if ($mode === '' || $mode === false || $mode === null) {
$extra = $this->composer->getPackage()->getExtra();
$mode = $extra['compile-mode'] ?? '';
}
if ($mode === '' || $mode === false || $mode === null) {
$mode = 'prompt';
}
$mode = strtolower($mode);
if (isset($aliases[$mode])) {
$mode = $aliases[$mode];
}
$options = ['all', 'prompt', 'whitelist', 'none'];
if (in_array($mode, $options)) {
return $mode;
} else {
throw new \InvalidArgumentException(
"The compilation policy (COMPOSER_COMPILE or extra.compile-mode) is invalid. Valid options are \"" . implode('", "', $options) . "\"."
);
}
}
/**
* @return string
*/
public function getPassthruMode()
{
$passthru = getenv('COMPOSER_COMPILE_PASSTHRU');
if ($passthru === '' || $passthru === false || $passthru === null) {
$extra = $this->composer->getPackage()->getExtra();
$passthru = $extra['compile-passthru'] ?? '';
}
if ($passthru === '' || $passthru === false || $passthru === null) {
$passthru = 'error';
}
return $passthru;
}
/**
* Get a list of packages that are trusted to do compilation.
*
* @return array
* Ex: ['foo/bar', 'civicrm/*']
*/
protected function getWhitelistRules()
{
$rules = $this->composer->getPackage()->getExtra()['compile-whitelist'] ?? [];
// The root package is an ex-officio member of the whitelist.
$root = $this->composer->getPackage();
if (!in_array($root->getName(), $rules)) {
$rules[] = $root->getName();
}
return $rules;
}
/**
* Update list of packages that are trusted to do compilation.
*
* @param array $newRules
* Ex: ['foo/bar', 'civicrm/*']
*/
protected function addWhitelistRules($newRules)
{
$oldRules = $this->composer->getPackage()->getExtra()['compile-whitelist'] ?? [];
$rules = array_unique(array_merge($oldRules, $newRules));
sort($rules);
$this->composer->getConfig()->getConfigSource()->addProperty('extra.compile-whitelist', $rules);
}
/**
* @param Task[] $blockedTasks
* List of tasks that we cannot currently run.
* @return string
* Returns 'y' or 'n' or 'a'
* @throws \Exception
*/
protected function askApproveTasks($blockedTasks)
{
if (!$this->io->isInteractive()) {
throw new \Exception(
"Cannot prompt for compilation preferences. Please update COMPOSER_COMPILE, extra.compile-mode, or extra.compile-whitelist."
);
}
$blockedTasks = $this->sortTasks($blockedTasks);
$choice = null;
do {
if ($choice === null) {
$this->io->write("");
$this->io->write(sprintf("The following packages have new compilation tasks:"));
$this->io->write(TaskUIHelper::formatTaskSummary($blockedTasks));
}
$actions = implode(', ', [
'[y]es',
'[a]lways',
'[n]o',
'[l]ist',
'[h]elp'
]);
$choice = $this->io->askAndValidate(
'Allow these packages to compile? (' . $actions . ') ',
function ($x) {
$x = strtolower($x);
return in_array($x, ['y', 'n', 'a', 'h', 'l']) ? $x : null;
}
);
if ($choice === 'h') {
$this->io->write("\n" . file_get_contents(__DIR__ . '/messages/prompt-help.txt'));
}
if ($choice === 'l') {
$this->io->write(TaskUIHelper::formatTaskTable($blockedTasks, ['packageName', 'title', 'action']));
}
} while (!in_array($choice, ['y', 'n', 'a']));
return $choice;
}
}
__halt_compiler();----SIGNATURE:----0C6cD2vZ7qZutXCGEQ2xvhFvmAblzk4CwlKpyfavj3H3wqdLiX5i6WE/oNn0BjqttQFGCSckNspEej+75CzKA539tj2Wq7PHlByKFxJZD5KkAdmRXwl699sbMhE1aqEFH9jM+ApqOLYNPxCFTO/gCy+RvpaegwhwpuHV6eVGg8YW/mHUpsv3Y8rRn9UwC4jTOTHIvVLNVelUJ7qAoKYJqnPEr4T8eXSqYK3O4i8pZvqDLndT8V6znA1tbTadVEtGF6t5K49B07W4Hu4zGq6IClm4W157SN3UpeGR6BmWOYFGAoYtAK32/w58lWy5J2hpaAQM/g+sV+yhspCKns8Qs4ibPo6Juk/39f7EFwFgVtkcZ7aZGGMYTBA/fm1+uyZANOGS88yoB2O+3Au566SBNZQj85U6vb3qzjQFEa8fqeg8WzuWRuWMYDNeQHxpWVI7CX/20D5UunvocvO8ZlJewhJpLwQfOjJI1U+4U4hAr+FDEtr18AVVAcArvVa7ZKOp/6MSmsmlaHxHv+RAY45ksvq0JhbHnFeWVQ6vNthO5g7AyXnkMp8qvK2qjpF5si4Bz0HKarEd/ADhTl7ohOiquWFmMVDQBUMZrkSNogNYo/KNHanzEqaJlqvyXrC0wpe8PXSXitNs/5t958eHo+z4ipF33Y3LiLISanuQUMaW/4U=----ATTACHMENT:----NDk1MzQ0NzQ3MjY0NzkwMyAyMDc5MzQxMzQ3NTA4ODY4IDU4ODM0Njg2NDE4NzkyODI=