Skip to content

Commit

Permalink
bug #869 Fix detecting new packages before installing their recipes (…
Browse files Browse the repository at this point in the history
…nicolas-grekas)

This PR was merged into the 1.x branch.

Discussion
----------

Fix detecting new packages before installing their recipes

Fix #851

Tracking the transactional changeset from composer.lock leads to the linked issue.
Instead, this tracks the changeset by computing it from symfony.lock.

Requires the `InstallerEvents::PRE_OPERATIONS_EXEC` event, which is Composer 2-only.

Commits
-------

07631cf Fix detecting new packages before installing their recipes
  • Loading branch information
nicolas-grekas committed Feb 16, 2022
2 parents 5092ea7 + 07631cf commit ed1626b
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 11 deletions.
51 changes: 42 additions & 9 deletions src/Flex.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Pool;
use Composer\DependencyResolver\Transaction;
use Composer\Downloader\FileDownloader;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\Factory;
Expand All @@ -35,6 +36,7 @@
use Composer\Package\BasePackage;
use Composer\Package\Comparer\Comparer;
use Composer\Package\Locker;
use Composer\Package\Package;
use Composer\Package\PackageInterface;
use Composer\Plugin\PluginEvents;
use Composer\Plugin\PluginInterface;
Expand All @@ -45,6 +47,7 @@
use Composer\Repository\RepositoryManager;
use Composer\Script\Event;
use Composer\Script\ScriptEvents;
use Composer\Semver\VersionParser;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Flex\Event\UpdateEvent;
Expand Down Expand Up @@ -341,11 +344,34 @@ public function configureProject(Event $event)

public function record(PackageEvent $event)
{
if ($this->shouldRecordOperation($event)) {
if ($this->shouldRecordOperation($event->getOperation(), $event->isDevMode(), $event->getComposer())) {
$this->operations[] = $event->getOperation();
}
}

public function recordOperations(InstallerEvent $event)
{
if (!$event->isExecutingOperations()) {
return;
}

$versionParser = new VersionParser();
$packages = [];
foreach ($this->lock->all() as $name => $info) {
$packages[] = new Package($name, $versionParser->normalize($info['version']), $info['version']);
}

$transation = \Closure::bind(function () use ($packages, $event) {
return new Transaction($packages, $event->getTransaction()->resultPackageMap);
}, null, Transaction::class)();

foreach ($transation->getOperations() as $operation) {
if ($this->shouldRecordOperation($operation, $event->isDevMode(), $event->getComposer())) {
$this->operations[] = $operation;
}
}
}

public function update(Event $event, $operations = [])
{
if ($operations) {
Expand Down Expand Up @@ -854,18 +880,21 @@ private function formatOrigin(Recipe $recipe): string
return sprintf('<info>%s</> (<comment>>=%s</>): From %s', $matches[1], $matches[2], 'auto-generated recipe' === $matches[3] ? '<comment>'.$matches[3].'</>' : $matches[3]);
}

private function shouldRecordOperation(PackageEvent $event): bool
private function shouldRecordOperation(OperationInterface $operation, bool $isDevMode, Composer $composer = null): bool
{
$operation = $event->getOperation();
if ($this->dryRun) {
return false;
}

if ($operation instanceof UpdateOperation) {
$package = $operation->getTargetPackage();
} else {
$package = $operation->getPackage();
}

// when Composer runs with --no-dev, ignore uninstall operations on packages from require-dev
if (!$event->isDevMode() && $operation instanceof UninstallOperation) {
foreach ($event->getComposer()->getLocker()->getLockData()['packages-dev'] as $p) {
if (!$isDevMode && $operation instanceof UninstallOperation) {
foreach (($composer ?? $this->composer)->getLocker()->getLockData()['packages-dev'] as $p) {
if ($package->getName() === $p['name']) {
return false;
}
Expand Down Expand Up @@ -993,9 +1022,6 @@ public static function getSubscribedEvents(): array
}

$events = [
PackageEvents::POST_PACKAGE_INSTALL => 'record',
PackageEvents::POST_PACKAGE_UPDATE => [['record'], ['enableThanksReminder']],
PackageEvents::POST_PACKAGE_UNINSTALL => 'record',
ScriptEvents::POST_CREATE_PROJECT_CMD => 'configureProject',
ScriptEvents::POST_INSTALL_CMD => 'install',
ScriptEvents::PRE_UPDATE_CMD => 'configureInstaller',
Expand All @@ -1005,14 +1031,21 @@ public static function getSubscribedEvents(): array

if (version_compare('2.0.0', PluginInterface::PLUGIN_API_VERSION, '>')) {
$events += [
PackageEvents::POST_PACKAGE_INSTALL => 'record',
PackageEvents::POST_PACKAGE_UPDATE => [['record'], ['enableThanksReminder']],
PackageEvents::POST_PACKAGE_UNINSTALL => 'record',
InstallerEvents::PRE_DEPENDENCIES_SOLVING => [['populateProvidersCacheDir', \PHP_INT_MAX]],
InstallerEvents::POST_DEPENDENCIES_SOLVING => [['populateFilesCacheDir', \PHP_INT_MAX]],
PackageEvents::PRE_PACKAGE_INSTALL => [['populateFilesCacheDir', ~\PHP_INT_MAX]],
PackageEvents::PRE_PACKAGE_UPDATE => [['populateFilesCacheDir', ~\PHP_INT_MAX]],
PluginEvents::PRE_FILE_DOWNLOAD => 'onFileDownload',
];
} else {
$events += [PluginEvents::PRE_POOL_CREATE => 'truncatePackages'];
$events += [
PackageEvents::POST_PACKAGE_UPDATE => 'enableThanksReminder',
InstallerEvents::PRE_OPERATIONS_EXEC => 'recordOperations',
PluginEvents::PRE_POOL_CREATE => 'truncatePackages',
];
}

return $events;
Expand Down
8 changes: 7 additions & 1 deletion tests/Command/DumpEnvCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
namespace Symfony\Flex\Tests\Command;

use Composer\Config;
use Composer\Console\Application;
use Composer\IO\NullIO;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Flex\Command\DumpEnvCommand;
use Symfony\Flex\Options;
Expand Down Expand Up @@ -210,6 +211,11 @@ private function createCommandDumpEnv(array $options = [])
);

$application = new Application();

\Closure::bind(function () {
$this->io = new NullIO();
}, $application, $application)();

$application->add($command);
$command = $application->find('dump-env');

Expand Down
2 changes: 1 addition & 1 deletion tests/Command/UpdateRecipesCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@

namespace Symfony\Flex\Tests\Command;

use Composer\Console\Application;
use Composer\Factory;
use Composer\IO\BufferIO;
use Composer\Plugin\PluginInterface;
use Composer\Util\Platform;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Process\Process;
Expand Down
1 change: 1 addition & 0 deletions tests/FlexTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ private function mockPackageEvent(Package $package): PackageEvent
{
$event = $this->getMockBuilder(PackageEvent::class, ['getOperation'])->disableOriginalConstructor()->getMock();
$event->expects($this->any())->method('getOperation')->willReturn(new InstallOperation($package));
$event->expects($this->any())->method('isDevMode')->willReturn(true);

return $event;
}
Expand Down

0 comments on commit ed1626b

Please sign in to comment.