Skip to content

Commit

Permalink
Merge pull request #173 from asgrim/cleanup-vendor-after-install
Browse files Browse the repository at this point in the history
Cleanup vendor after install
  • Loading branch information
asgrim authored Jan 9, 2025
2 parents a53b255 + 4ae2f35 commit b6e014b
Show file tree
Hide file tree
Showing 11 changed files with 427 additions and 5 deletions.
3 changes: 3 additions & 0 deletions src/ComposerIntegration/ComposerIntegrationHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class ComposerIntegrationHandler
public function __construct(
private readonly ContainerInterface $container,
private readonly QuieterConsoleIO $arrayCollectionIo,
private readonly VendorCleanup $vendorCleanup,
) {
}

Expand Down Expand Up @@ -86,5 +87,7 @@ public function __invoke(

throw ComposerRunFailed::fromExitCode($resultCode);
}

($this->vendorCleanup)($composer);
}
}
1 change: 1 addition & 0 deletions src/ComposerIntegration/PieComposerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public static function createPieComposer(
);

OverrideWindowsUrlInstallListener::selfRegister($composer, $io, $container, $composerRequest);
RemoveUnrelatedInstallOperations::selfRegister($composer, $composerRequest);

$composer->getConfig()->merge(['config' => ['__PIE_REQUEST__' => $composerRequest]]);
$io->loadConfiguration($composer->getConfig());
Expand Down
14 changes: 9 additions & 5 deletions src/ComposerIntegration/PiePackageInstaller.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Util\Filesystem;
use Php\Pie\ExtensionType;
use Symfony\Component\Console\Output\OutputInterface;

use function sprintf;

Expand Down Expand Up @@ -39,11 +40,14 @@ public function install(InstalledRepositoryInterface $repo, PackageInterface $pa
$output = $this->composerRequest->pieOutput;

if ($this->composerRequest->requestedPackage->package !== $composerPackage->getName()) {
$output->writeln(sprintf(
'<error>Not using PIE to install %s as it was not the expected package %s</error>',
$composerPackage->getName(),
$this->composerRequest->requestedPackage->package,
));
$output->writeln(
sprintf(
'<comment>Skipping %s install request from Composer as it was not the expected PIE package %s</comment>',
$composerPackage->getName(),
$this->composerRequest->requestedPackage->package,
),
OutputInterface::VERBOSITY_VERY_VERBOSE,
);

return null;
}
Expand Down
90 changes: 90 additions & 0 deletions src/ComposerIntegration/RemoveUnrelatedInstallOperations.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Closure;
use Composer\Composer;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\DependencyResolver\Transaction;
use Composer\Installer\InstallerEvent;
use Composer\Installer\InstallerEvents;
use Symfony\Component\Console\Output\OutputInterface;

use function array_filter;
use function assert;
use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class RemoveUnrelatedInstallOperations
{
public function __construct(
private readonly PieComposerRequest $composerRequest,
) {
}

public static function selfRegister(
Composer $composer,
PieComposerRequest $composerRequest,
): void {
$composer
->getEventDispatcher()
->addListener(
InstallerEvents::PRE_OPERATIONS_EXEC,
new self($composerRequest),
);
}

/**
* @psalm-suppress InternalProperty
* @psalm-suppress InternalMethod
*/
public function __invoke(InstallerEvent $installerEvent): void
{
$pieOutput = $this->composerRequest->pieOutput;

$newOperations = array_filter(
$installerEvent->getTransaction()?->getOperations() ?? [],
function (OperationInterface $operation) use ($pieOutput): bool {
if (! $operation instanceof InstallOperation) {
$pieOutput->writeln(
sprintf(
'Unexpected operation during installer: %s',
$operation::class,
),
OutputInterface::VERBOSITY_VERY_VERBOSE,
);

return false;
}

$isRequestedPiePackage = $this->composerRequest->requestedPackage->package === $operation->getPackage()->getName();

if (! $isRequestedPiePackage) {
$pieOutput->writeln(
sprintf(
'Filtering package %s from install operations, as it was not the requested package',
$operation->getPackage()->getName(),
),
OutputInterface::VERBOSITY_VERY_VERBOSE,
);
}

return $isRequestedPiePackage;
},
);

$overrideOperations = Closure::Bind(
static function (Transaction $transaction) use ($newOperations): void {
/** @psalm-suppress InaccessibleProperty */
$transaction->operations = $newOperations;
},
null,
Transaction::class,
);
assert($overrideOperations !== null);
$overrideOperations($installerEvent->getTransaction());
}
}
88 changes: 88 additions & 0 deletions src/ComposerIntegration/VendorCleanup.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Composer;
use Composer\Util\Filesystem;
use Symfony\Component\Console\Output\OutputInterface;

use function array_filter;
use function array_walk;
use function in_array;
use function is_array;
use function scandir;
use function sprintf;

use const DIRECTORY_SEPARATOR;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class VendorCleanup
{
public function __construct(
private readonly OutputInterface $output,
private readonly Filesystem $filesystem,
) {
}

public function __invoke(Composer $composer): void
{
$vendorDir = (string) $composer->getConfig()->get('vendor-dir');
$vendorContents = scandir($vendorDir);

if (! is_array($vendorContents)) {
$this->output->writeln(
sprintf(
'<comment>Vendor directory (vendor-dir config) %s seemed invalid?/comment>',
$vendorDir,
),
OutputInterface::VERBOSITY_VERY_VERBOSE,
);

return;
}

$toRemove = array_filter(
$vendorContents,
static function (string $path): bool {
return ! in_array(
$path,
[
'.',
'..',
'autoload.php',
'composer',
],
);
},
);

array_walk(
$toRemove,
function (string $pathToRemove) use ($vendorDir): void {
$fullPathToRemove = $vendorDir . DIRECTORY_SEPARATOR . $pathToRemove;

$this->output->writeln(
sprintf(
'<comment>Removing: %s</comment>',
$fullPathToRemove,
),
OutputInterface::VERBOSITY_VERY_VERBOSE,
);

if ($this->filesystem->remove($fullPathToRemove)) {
return;
}

$this->output->writeln(
sprintf(
'<comment>Warning: failed to remove %s</comment>',
$fullPathToRemove,
),
OutputInterface::VERBOSITY_VERBOSE,
);
},
);
}
}
Empty file.
Empty file.
Empty file.
Empty file.
136 changes: 136 additions & 0 deletions test/unit/ComposerIntegration/RemoveUnrelatedInstallOperationsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<?php

declare(strict_types=1);

namespace Php\PieUnitTest\ComposerIntegration;

use Composer\Composer;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\DependencyResolver\Transaction;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Installer\InstallerEvent;
use Composer\Installer\InstallerEvents;
use Composer\IO\IOInterface;
use Composer\Package\CompletePackage;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\PieOperation;
use Php\Pie\ComposerIntegration\RemoveUnrelatedInstallOperations;
use Php\Pie\DependencyResolver\RequestedPackageAndVersion;
use Php\Pie\Platform\Architecture;
use Php\Pie\Platform\OperatingSystem;
use Php\Pie\Platform\OperatingSystemFamily;
use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Platform\ThreadSafetyMode;
use Php\Pie\Platform\WindowsCompiler;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Output\OutputInterface;

use function array_filter;
use function array_map;

#[CoversClass(RemoveUnrelatedInstallOperations::class)]
final class RemoveUnrelatedInstallOperationsTest extends TestCase
{
private Composer&MockObject $composer;

public function setUp(): void
{
parent::setUp();

$this->composer = $this->createMock(Composer::class);
}

public function testEventListenerRegistration(): void
{
$eventDispatcher = $this->createMock(EventDispatcher::class);
$eventDispatcher
->expects(self::once())
->method('addListener')
->with(
InstallerEvents::PRE_OPERATIONS_EXEC,
self::isInstanceOf(RemoveUnrelatedInstallOperations::class),
);

$this->composer
->expects(self::once())
->method('getEventDispatcher')
->willReturn($eventDispatcher);

RemoveUnrelatedInstallOperations::selfRegister(
$this->composer,
new PieComposerRequest(
$this->createMock(OutputInterface::class),
new TargetPlatform(
OperatingSystem::NonWindows,
OperatingSystemFamily::Linux,
PhpBinaryPath::fromCurrentProcess(),
Architecture::x86_64,
ThreadSafetyMode::NonThreadSafe,
1,
WindowsCompiler::VC15,
),
new RequestedPackageAndVersion('foo/bar', '^1.1'),
PieOperation::Install,
[],
null,
false,
),
);
}

/** @psalm-suppress InternalMethod */
public function testUnrelatedInstallOperationsAreRemoved(): void
{
$composerPackage1 = new CompletePackage('foo/bar', '1.2.3.0', '1.2.3');
$composerPackage2 = new CompletePackage('bat/baz', '3.4.5.0', '3.4.5');
$composerPackage3 = new CompletePackage('qux/quux', '5.6.7.0', '5.6.7');

/**
* @psalm-suppress InternalClass
* @psalm-suppress InternalMethod
*/
$installerEvent = new InstallerEvent(
InstallerEvents::PRE_OPERATIONS_EXEC,
$this->composer,
$this->createMock(IOInterface::class),
false,
true,
new Transaction([], [$composerPackage1, $composerPackage2, $composerPackage3]),
);

(new RemoveUnrelatedInstallOperations(
new PieComposerRequest(
$this->createMock(OutputInterface::class),
new TargetPlatform(
OperatingSystem::Windows,
OperatingSystemFamily::Linux,
PhpBinaryPath::fromCurrentProcess(),
Architecture::x86_64,
ThreadSafetyMode::NonThreadSafe,
1,
WindowsCompiler::VC15,
),
new RequestedPackageAndVersion('bat/baz', '^3.2'),
PieOperation::Install,
[],
null,
false,
),
))($installerEvent);

self::assertSame(
['bat/baz'],
array_map(
static fn (InstallOperation $operation): string => $operation->getPackage()->getName(),
array_filter(
$installerEvent->getTransaction()?->getOperations() ?? [],
static fn (OperationInterface $operation): bool => $operation instanceof InstallOperation,
),
),
);
}
}
Loading

0 comments on commit b6e014b

Please sign in to comment.