diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index db5e37343..92d78be7b 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -16,6 +16,7 @@ use Composer\Composer; use Composer\DependencyResolver\Request; use Composer\Installer; use Composer\IO\IOInterface; +use Composer\Package\Loader\RootPackageLoader; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Package\Version\VersionParser; @@ -140,8 +141,9 @@ EOT } } - $rootRequires = $composer->getPackage()->getRequires(); - $rootDevRequires = $composer->getPackage()->getDevRequires(); + $rootPackage = $composer->getPackage(); + $rootRequires = $rootPackage->getRequires(); + $rootDevRequires = $rootPackage->getDevRequires(); foreach ($reqs as $package => $constraint) { if (isset($rootRequires[$package])) { $rootRequires[$package] = $this->appendConstraintToLink($rootRequires[$package], $constraint); @@ -151,8 +153,10 @@ EOT throw new \UnexpectedValueException('Only root package requirements can receive temporary constraints and '.$package.' is not one'); } } - $composer->getPackage()->setRequires($rootRequires); - $composer->getPackage()->setDevRequires($rootDevRequires); + $rootPackage->setRequires($rootRequires); + $rootPackage->setDevRequires($rootDevRequires); + $rootPackage->setReferences(RootPackageLoader::extractReferences($reqs, $rootPackage->getReferences())); + $rootPackage->setStabilityFlags(RootPackageLoader::extractStabilityFlags($reqs, $rootPackage->getMinimumStability(), $rootPackage->getStabilityFlags())); if ($input->getOption('interactive')) { $packages = $this->getPackagesInteractively($io, $input, $output, $composer, $packages); diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index a5c1ced0a..ec1e5c1a5 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -158,151 +158,157 @@ class EventDispatcher $this->pushEvent($event); - $returnMax = 0; - foreach ($listeners as $callable) { - $return = 0; - $this->ensureBinDirIsInPath(); + try { + $returnMax = 0; + foreach ($listeners as $callable) { + $return = 0; + $this->ensureBinDirIsInPath(); - if (!is_string($callable)) { - if (!is_callable($callable)) { - $className = is_object($callable[0]) ? get_class($callable[0]) : $callable[0]; + if (!is_string($callable)) { + if (!is_callable($callable)) { + $className = is_object($callable[0]) ? get_class($callable[0]) : $callable[0]; - throw new \RuntimeException('Subscriber '.$className.'::'.$callable[1].' for event '.$event->getName().' is not callable, make sure the function is defined and public'); - } - if (is_array($callable) && (is_string($callable[0]) || is_object($callable[0])) && is_string($callable[1])) { - $this->io->writeError(sprintf('> %s: %s', $event->getName(), (is_object($callable[0]) ? get_class($callable[0]) : $callable[0]).'->'.$callable[1]), true, IOInterface::VERBOSE); - } - $return = false === call_user_func($callable, $event) ? 1 : 0; - } elseif ($this->isComposerScript($callable)) { - $this->io->writeError(sprintf('> %s: %s', $event->getName(), $callable), true, IOInterface::VERBOSE); + throw new \RuntimeException('Subscriber '.$className.'::'.$callable[1].' for event '.$event->getName().' is not callable, make sure the function is defined and public'); + } + if (is_array($callable) && (is_string($callable[0]) || is_object($callable[0])) && is_string($callable[1])) { + $this->io->writeError(sprintf('> %s: %s', $event->getName(), (is_object($callable[0]) ? get_class($callable[0]) : $callable[0]).'->'.$callable[1]), true, IOInterface::VERBOSE); + } + $return = false === call_user_func($callable, $event) ? 1 : 0; + } elseif ($this->isComposerScript($callable)) { + $this->io->writeError(sprintf('> %s: %s', $event->getName(), $callable), true, IOInterface::VERBOSE); - $script = explode(' ', substr($callable, 1)); - $scriptName = $script[0]; - unset($script[0]); + $script = explode(' ', substr($callable, 1)); + $scriptName = $script[0]; + unset($script[0]); + + $args = array_merge($script, $event->getArguments()); + $flags = $event->getFlags(); + if (strpos($callable, '@composer ') === 0) { + $exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(getenv('COMPOSER_BINARY')) . ' ' . implode(' ', $args); + if (0 !== ($exitCode = $this->executeTty($exec))) { + $this->io->writeError(sprintf('Script %s handling the %s event returned with error code '.$exitCode.'', $callable, $event->getName()), true, IOInterface::QUIET); + + throw new ScriptExecutionException('Error Output: '.$this->process->getErrorOutput(), $exitCode); + } + } else { + if (!$this->getListeners(new Event($scriptName))) { + $this->io->writeError(sprintf('You made a reference to a non-existent script %s', $callable), true, IOInterface::QUIET); + } + + try { + /** @var InstallerEvent $event */ + $scriptEvent = new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags); + $scriptEvent->setOriginatingEvent($event); + $return = $this->dispatch($scriptName, $scriptEvent); + } catch (ScriptExecutionException $e) { + $this->io->writeError(sprintf('Script %s was called via %s', $callable, $event->getName()), true, IOInterface::QUIET); + throw $e; + } + } + } elseif ($this->isPhpScript($callable)) { + $className = substr($callable, 0, strpos($callable, '::')); + $methodName = substr($callable, strpos($callable, '::') + 2); + + if (!class_exists($className)) { + $this->io->writeError('Class '.$className.' is not autoloadable, can not call '.$event->getName().' script', true, IOInterface::QUIET); + continue; + } + if (!is_callable($callable)) { + $this->io->writeError('Method '.$callable.' is not callable, can not call '.$event->getName().' script', true, IOInterface::QUIET); + continue; + } + + try { + $return = false === $this->executeEventPhpScript($className, $methodName, $event) ? 1 : 0; + } catch (\Exception $e) { + $message = "Script %s handling the %s event terminated with an exception"; + $this->io->writeError(''.sprintf($message, $callable, $event->getName()).'', true, IOInterface::QUIET); + throw $e; + } + } else { + $args = implode(' ', array_map(array('Composer\Util\ProcessExecutor', 'escape'), $event->getArguments())); + $exec = $callable . ($args === '' ? '' : ' '.$args); + if ($this->io->isVerbose()) { + $this->io->writeError(sprintf('> %s: %s', $event->getName(), $exec)); + } elseif ($event->getName() !== '__exec_command') { + // do not output the command being run when using `composer exec` as it is fairly obvious the user is running it + $this->io->writeError(sprintf('> %s', $exec)); + } + + $possibleLocalBinaries = $this->composer->getPackage()->getBinaries(); + if ($possibleLocalBinaries) { + foreach ($possibleLocalBinaries as $localExec) { + if (preg_match('{\b'.preg_quote($callable).'$}', $localExec)) { + $caller = BinaryInstaller::determineBinaryCaller($localExec); + $exec = preg_replace('{^'.preg_quote($callable).'}', $caller . ' ' . $localExec, $exec); + break; + } + } + } + + if (strpos($exec, '@putenv ') === 0) { + putenv(substr($exec, 8)); + list($var, $value) = explode('=', substr($exec, 8), 2); + $_SERVER[$var] = $value; + + continue; + } + if (strpos($exec, '@php ') === 0) { + $pathAndArgs = substr($exec, 5); + if (Platform::isWindows()) { + $pathAndArgs = preg_replace_callback('{^\S+}', function ($path) { + return str_replace('/', '\\', $path[0]); + }, $pathAndArgs); + } + // match somename (not in quote, and not a qualified path) and if it is not a valid path from CWD then try to find it + // in $PATH. This allows support for `@php foo` where foo is a binary name found in PATH but not an actual relative path + preg_match('{^[^\'"\s/\\\\]+}', $pathAndArgs, $match); + if (!file_exists($match[0])) { + $finder = new ExecutableFinder; + if ($pathToExec = $finder->find($match[0])) { + $pathAndArgs = $pathToExec . substr($pathAndArgs, strlen($match[0])); + } + } + $exec = $this->getPhpExecCommand() . ' ' . $pathAndArgs; + } else { + $finder = new PhpExecutableFinder(); + $phpPath = $finder->find(false); + if ($phpPath) { + $_SERVER['PHP_BINARY'] = $phpPath; + putenv('PHP_BINARY=' . $_SERVER['PHP_BINARY']); + } + + if (Platform::isWindows()) { + $exec = preg_replace_callback('{^\S+}', function ($path) { + return str_replace('/', '\\', $path[0]); + }, $exec); + } + } + + // if composer is being executed, make sure it runs the expected composer from current path + // resolution, even if bin-dir contains composer too because the project requires composer/composer + // see https://github.com/composer/composer/issues/8748 + if (strpos($exec, 'composer ') === 0) { + $exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(getenv('COMPOSER_BINARY')) . substr($exec, 8); + } - $args = array_merge($script, $event->getArguments()); - $flags = $event->getFlags(); - if (strpos($callable, '@composer ') === 0) { - $exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(getenv('COMPOSER_BINARY')) . ' ' . implode(' ', $args); if (0 !== ($exitCode = $this->executeTty($exec))) { $this->io->writeError(sprintf('Script %s handling the %s event returned with error code '.$exitCode.'', $callable, $event->getName()), true, IOInterface::QUIET); throw new ScriptExecutionException('Error Output: '.$this->process->getErrorOutput(), $exitCode); } - } else { - if (!$this->getListeners(new Event($scriptName))) { - $this->io->writeError(sprintf('You made a reference to a non-existent script %s', $callable), true, IOInterface::QUIET); - } - - try { - /** @var InstallerEvent $event */ - $scriptEvent = new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags); - $scriptEvent->setOriginatingEvent($event); - $return = $this->dispatch($scriptName, $scriptEvent); - } catch (ScriptExecutionException $e) { - $this->io->writeError(sprintf('Script %s was called via %s', $callable, $event->getName()), true, IOInterface::QUIET); - throw $e; - } - } - } elseif ($this->isPhpScript($callable)) { - $className = substr($callable, 0, strpos($callable, '::')); - $methodName = substr($callable, strpos($callable, '::') + 2); - - if (!class_exists($className)) { - $this->io->writeError('Class '.$className.' is not autoloadable, can not call '.$event->getName().' script', true, IOInterface::QUIET); - continue; - } - if (!is_callable($callable)) { - $this->io->writeError('Method '.$callable.' is not callable, can not call '.$event->getName().' script', true, IOInterface::QUIET); - continue; } - try { - $return = false === $this->executeEventPhpScript($className, $methodName, $event) ? 1 : 0; - } catch (\Exception $e) { - $message = "Script %s handling the %s event terminated with an exception"; - $this->io->writeError(''.sprintf($message, $callable, $event->getName()).'', true, IOInterface::QUIET); - throw $e; - } - } else { - $args = implode(' ', array_map(array('Composer\Util\ProcessExecutor', 'escape'), $event->getArguments())); - $exec = $callable . ($args === '' ? '' : ' '.$args); - if ($this->io->isVerbose()) { - $this->io->writeError(sprintf('> %s: %s', $event->getName(), $exec)); - } elseif ($event->getName() !== '__exec_command') { - // do not output the command being run when using `composer exec` as it is fairly obvious the user is running it - $this->io->writeError(sprintf('> %s', $exec)); - } + $returnMax = max($returnMax, $return); - $possibleLocalBinaries = $this->composer->getPackage()->getBinaries(); - if ($possibleLocalBinaries) { - foreach ($possibleLocalBinaries as $localExec) { - if (preg_match('{\b'.preg_quote($callable).'$}', $localExec)) { - $caller = BinaryInstaller::determineBinaryCaller($localExec); - $exec = preg_replace('{^'.preg_quote($callable).'}', $caller . ' ' . $localExec, $exec); - break; - } - } - } - - if (strpos($exec, '@putenv ') === 0) { - putenv(substr($exec, 8)); - list($var, $value) = explode('=', substr($exec, 8), 2); - $_SERVER[$var] = $value; - - continue; - } - if (strpos($exec, '@php ') === 0) { - $pathAndArgs = substr($exec, 5); - if (Platform::isWindows()) { - $pathAndArgs = preg_replace_callback('{^\S+}', function ($path) { - return str_replace('/', '\\', $path[0]); - }, $pathAndArgs); - } - // match somename (not in quote, and not a qualified path) and if it is not a valid path from CWD then try to find it - // in $PATH. This allows support for `@php foo` where foo is a binary name found in PATH but not an actual relative path - preg_match('{^[^\'"\s/\\\\]+}', $pathAndArgs, $match); - if (!file_exists($match[0])) { - $finder = new ExecutableFinder; - if ($pathToExec = $finder->find($match[0])) { - $pathAndArgs = $pathToExec . substr($pathAndArgs, strlen($match[0])); - } - } - $exec = $this->getPhpExecCommand() . ' ' . $pathAndArgs; - } else { - $finder = new PhpExecutableFinder(); - $phpPath = $finder->find(false); - if ($phpPath) { - $_SERVER['PHP_BINARY'] = $phpPath; - putenv('PHP_BINARY=' . $_SERVER['PHP_BINARY']); - } - - if (Platform::isWindows()) { - $exec = preg_replace_callback('{^\S+}', function ($path) { - return str_replace('/', '\\', $path[0]); - }, $exec); - } - } - - // if composer is being executed, make sure it runs the expected composer from current path - // resolution, even if bin-dir contains composer too because the project requires composer/composer - // see https://github.com/composer/composer/issues/8748 - if (strpos($exec, 'composer ') === 0) { - $exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(getenv('COMPOSER_BINARY')) . substr($exec, 8); - } - - if (0 !== ($exitCode = $this->executeTty($exec))) { - $this->io->writeError(sprintf('Script %s handling the %s event returned with error code '.$exitCode.'', $callable, $event->getName()), true, IOInterface::QUIET); - - throw new ScriptExecutionException('Error Output: '.$this->process->getErrorOutput(), $exitCode); + if ($event->isPropagationStopped()) { + break; } } + } catch (\Exception $e) { + $this->popEvent(); - $returnMax = max($returnMax, $return); - - if ($event->isPropagationStopped()) { - break; - } + throw $e; } $this->popEvent(); diff --git a/src/Composer/Package/Loader/RootPackageLoader.php b/src/Composer/Package/Loader/RootPackageLoader.php index 76e8f29f4..0e59d1750 100644 --- a/src/Composer/Package/Loader/RootPackageLoader.php +++ b/src/Composer/Package/Loader/RootPackageLoader.php @@ -144,8 +144,8 @@ class RootPackageLoader extends ArrayLoader $links[$link->getTarget()] = $link->getConstraint()->getPrettyString(); } $aliases = $this->extractAliases($links, $aliases); - $stabilityFlags = $this->extractStabilityFlags($links, $stabilityFlags, $realPackage->getMinimumStability()); - $references = $this->extractReferences($links, $references); + $stabilityFlags = self::extractStabilityFlags($links, $realPackage->getMinimumStability(), $stabilityFlags); + $references = self::extractReferences($links, $references); if (isset($links[$config['name']])) { throw new \RuntimeException(sprintf('Root package \'%s\' cannot require itself in its composer.json' . PHP_EOL . @@ -203,7 +203,10 @@ class RootPackageLoader extends ArrayLoader return $aliases; } - private function extractStabilityFlags(array $requires, array $stabilityFlags, $minimumStability) + /** + * @internal + */ + public static function extractStabilityFlags(array $requires, $minimumStability, array $stabilityFlags) { $stabilities = BasePackage::$stabilities; $minimumStability = $stabilities[$minimumStability]; @@ -256,7 +259,10 @@ class RootPackageLoader extends ArrayLoader return $stabilityFlags; } - private function extractReferences(array $requires, array $references) + /** + * @internal + */ + public static function extractReferences(array $requires, array $references) { foreach ($requires as $reqName => $reqVersion) { $reqVersion = preg_replace('{^([^,\s@]+) as .+$}', '$1', $reqVersion);