28 class Patches implements PluginInterface, EventSubscriberInterface {
60 $this->eventDispatcher = $composer->getEventDispatcher();
61 $this->executor =
new ProcessExecutor($this->io);
62 $this->patches =
array();
70 ScriptEvents::PRE_INSTALL_CMD =>
"checkPatches",
71 ScriptEvents::PRE_UPDATE_CMD =>
"checkPatches",
72 PackageEvents::PRE_PACKAGE_INSTALL =>
"gatherPatches",
73 PackageEvents::PRE_PACKAGE_UPDATE =>
"gatherPatches",
74 PackageEvents::POST_PACKAGE_INSTALL =>
"postInstall",
75 PackageEvents::POST_PACKAGE_UPDATE =>
"postInstall",
89 $repositoryManager = $this->composer->getRepositoryManager();
90 $localRepository = $repositoryManager->getLocalRepository();
91 $installationManager = $this->composer->getInstallationManager();
92 $packages = $localRepository->getPackages();
95 if ($tmp_patches == FALSE) {
96 $this->io->write(
'<info>No patches supplied.</info>');
100 foreach ($packages as $package) {
101 $extra = $package->getExtra();
102 $patches = isset($extra[
'patches']) ? $extra[
'patches'] :
array();
103 $tmp_patches = array_merge_recursive($tmp_patches,
$patches);
107 foreach ($packages as $package) {
108 if (!($package instanceof AliasPackage)) {
109 $package_name = $package->getName();
110 $extra = $package->getExtra();
111 $has_patches = isset($tmp_patches[$package_name]);
112 $has_applied_patches = isset($extra[
'patches_applied']);
113 if (($has_patches && !$has_applied_patches)
114 || (!$has_patches && $has_applied_patches)
115 || ($has_patches && $has_applied_patches && $tmp_patches[$package_name] !== $extra[
'patches_applied'])) {
116 $uninstallOperation =
new UninstallOperation($package,
'Removing package so it can be re-installed and re-patched.');
117 $this->io->write(
'<info>Removing package ' . $package_name .
' so that it can be re-installed and re-patched.</info>');
118 $installationManager->uninstall($localRepository, $uninstallOperation);
137 if (isset($this->patches[
'_patchesGathered'])) {
146 if (empty($this->patches)) {
147 $this->io->write(
'<info>No patches supplied.</info>');
151 $operations = $event->getOperations();
152 $this->io->write(
'<info>Gathering patches for dependencies. This might take a minute.</info>');
153 foreach ($operations as $operation) {
154 if ($operation->getJobType() ==
'install' || $operation->getJobType() ==
'update') {
156 $extra = $package->getExtra();
157 if (isset($extra[
'patches'])) {
158 $this->patches = array_merge_recursive($this->patches, $extra[
'patches']);
164 if ($this->io->isVerbose()) {
165 foreach ($this->patches as $package =>
$patches) {
167 $this->io->write(
'<info>Found ' . $number .
' patches for ' . $package .
'.</info>');
173 $this->patches[
'_patchesGathered'] = TRUE;
183 $extra = $this->composer->getPackage()->getExtra();
184 if (isset($extra[
'patches'])) {
185 $this->io->write(
'<info>Gathering patches for root package.</info>');
190 elseif (isset($extra[
'patches-file'])) {
191 $this->io->write(
'<info>Gathering patches from patch file.</info>');
192 $patches = file_get_contents($extra[
'patches-file']);
194 $error = json_last_error();
197 case JSON_ERROR_DEPTH:
198 $msg =
' - Maximum stack depth exceeded';
200 case JSON_ERROR_STATE_MISMATCH:
201 $msg =
' - Underflow or the modes mismatch';
203 case JSON_ERROR_CTRL_CHAR:
204 $msg =
' - Unexpected control character found';
206 case JSON_ERROR_SYNTAX:
207 $msg =
' - Syntax error, malformed JSON';
209 case JSON_ERROR_UTF8:
210 $msg =
' - Malformed UTF-8 characters, possibly incorrectly encoded';
213 $msg =
' - Unknown error';
216 throw new \Exception(
'There was an error in the supplied patches file:' . $msg);
223 throw new \Exception(
'There was an error in the supplied patch file');
235 public function postInstall(PackageEvent $event) {
237 $operation = $event->getOperation();
240 $package_name = $package->getName();
242 if (!isset($this->patches[$package_name])) {
243 if ($this->io->isVerbose()) {
244 $this->io->write(
'<info>No patches found for ' . $package_name .
'.</info>');
248 $this->io->write(
' - Applying patches for <info>' . $package_name .
'</info>');
251 $manager = $event->getComposer()->getInstallationManager();
252 $install_path = $manager->getInstaller($package->getType())->getInstallPath($package);
255 $downloader =
new RemoteFilesystem($this->io, $this->composer->getConfig());
258 $localRepository = $this->composer->getRepositoryManager()->getLocalRepository();
259 $localPackage = $localRepository->findPackage($package_name, $package->getVersion());
260 $extra = $localPackage->getExtra();
261 $extra[
'patches_applied'] =
array();
264 $this->io->write(
' <info>' .
$url .
'</info> (<comment>' .
$description.
'</comment>)');
272 $this->io->write(
' <error>Could not apply patch! Skipping.</error>');
273 if (getenv(
'COMPOSER_EXIT_ON_PATCH_FAILURE')) {
274 throw new \Exception(
"Cannot apply patch $description ($url)!");
278 $localPackage->setExtra($extra);
280 $this->io->write(
'');
292 if ($operation instanceof InstallOperation) {
293 $package = $operation->getPackage();
295 elseif ($operation instanceof UpdateOperation) {
296 $package = $operation->getTargetPackage();
299 throw new \Exception(
'Unknown operation: ' . get_class($operation));
313 protected function getAndApplyPatch(RemoteFilesystem $downloader, $install_path, $patch_url) {
316 if (file_exists($patch_url)) {
324 $hostname = parse_url($patch_url, PHP_URL_HOST);
325 $downloader->copy($hostname, $patch_url,
$filename, FALSE);
333 $patch_levels =
array(
'-p1',
'-p0',
'-p2');
334 foreach ($patch_levels as $patch_level) {
335 $checked = $this->
executeCommand(
'cd %s && GIT_DIR=. git apply --check %s %s', $install_path, $patch_level,
$filename);
338 $patched = $this->
executeCommand(
'cd %s && GIT_DIR=. git apply %s %s', $install_path, $patch_level,
$filename);
346 foreach ($patch_levels as $patch_level) {
349 if ($patched = $this->
executeCommand(
"patch %s --no-backup-if-mismatch -d %s < %s", $patch_level, $install_path,
$filename)) {
356 if (isset($hostname)) {
362 throw new \Exception(
"Cannot apply patch $patch_url");
373 $extra = $this->composer->getPackage()->getExtra();
375 if (empty($extra[
'patches'])) {
378 return isset($extra[
'enable-patching']) ? $extra[
'enable-patching'] : FALSE;
392 $output =
"This file was automatically generated by Composer Patches (https://github.com/cweagans/composer-patches)\n";
393 $output .=
"Patches applied to this directory:\n\n";
398 file_put_contents($directory .
"/PATCHES.txt",
$output);
409 $args = func_get_args();
410 foreach ($args as
$index => $arg) {
412 $args[
$index] = escapeshellarg($arg);
417 $command = call_user_func_array(
'sprintf', $args);
419 if ($this->io->isVerbose()) {
420 $this->io->write(
'<comment>' . $command .
'</comment>');
423 if (
$type == Process::ERR) {
424 $io->write(
'<error>' .
$data .
'</error>');
427 $io->write(
'<comment>' .
$data .
'</comment>');
431 return ($this->executor->execute($command,
$output) == 0);
executeCommand($cmd)
Executes a shell command with escaping.
grabPatches()
Get the patches from root composer or external file.
checkPatches(Event $event)
Before running composer install,.
isPatchingEnabled()
Checks if the root package enables patching.
if(!is_dir( $entity_dir)) exit("Fatal Error ([A-Za-z0-9]+)\+" &#(? foreach( $entity_files as $file) $output
static getSubscribedEvents()
Returns an array of event names this subscriber wants to listen to.
getPackageFromOperation(OperationInterface $operation)
Get a Package object from an OperationInterface object.
activate(Composer $composer, IOInterface $io)
Apply plugin modifications to composer.
Create styles array
The data for the language used.
getAndApplyPatch(RemoteFilesystem $downloader, $install_path, $patch_url)
Apply a patch on code in the specified directory.
writePatchReport($patches, $directory)
Writes a patch report to the target directory.
gatherPatches(PackageEvent $event)
Gather patches from dependencies and store them for later use.