Skip to content

Commit ec199c9

Browse files
committed
707: add --no-dev flag for pie install for PHP projects
1 parent b2864c9 commit ec199c9

6 files changed

Lines changed: 184 additions & 4 deletions

File tree

docs/usage.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,16 @@ pie install \
459459
> The `--allow-non-interactive-project-install` will no longer work. You must
460460
> provide package selections from PIE 1.5 onwards.
461461
462+
### Excluding require-dev extensions
463+
464+
By default, PIE checks extensions declared in both `require` and
465+
`require-dev`. To skip extensions that are only declared in `require-dev`
466+
(for example, `ext-xdebug` in a production build), pass `--no-dev`:
467+
468+
```bash
469+
pie install --no-dev
470+
```
471+
462472
## Install extensions from pie.lock
463473

464474
If you have an existing `pie.json` and `pie.lock` for a given PHP install,

src/Command/CommandHelper.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ final class CommandHelper
7373
public const OPTION_WITH_PHP_PATH = 'with-php-path';
7474
public const OPTION_WITH_PHPIZE_PATH = 'with-phpize-path';
7575
public const OPTION_ALLOW_NON_INTERACTIVE_PROJECT_INSTALL = 'allow-non-interactive-project-install';
76+
public const OPTION_NO_DEV = 'no-dev';
7677
private const OPTION_PACKAGE_SELECTION = 'select';
7778
private const OPTION_WORKING_DIRECTORY = 'working-dir';
7879
private const OPTION_MAKE_PARALLEL_JOBS = 'make-parallel-jobs';
@@ -180,6 +181,13 @@ public static function configureDownloadBuildInstallOptions(Command $command, bo
180181
'Deprecated and ignored. Will emit a warning if used.',
181182
);
182183

184+
$command->addOption(
185+
self::OPTION_NO_DEV,
186+
null,
187+
InputOption::VALUE_NONE,
188+
'When checking a project for required extensions, exclude any extensions declared in the root package\'s require-dev.',
189+
);
190+
183191
$command->addOption(
184192
self::OPTION_PACKAGE_SELECTION,
185193
null,
@@ -305,6 +313,11 @@ public static function determineForceInstallingPackageVersion(InputInterface $in
305313
return $input->hasOption(self::OPTION_FORCE) && $input->getOption(self::OPTION_FORCE);
306314
}
307315

316+
public static function noDev(InputInterface $input): bool
317+
{
318+
return $input->hasOption(self::OPTION_NO_DEV) && $input->getOption(self::OPTION_NO_DEV);
319+
}
320+
308321
/** @return list<DownloadUrlMethod> */
309322
public static function determineSuppressedDownloadUrlMethods(InputInterface $input): array
310323
{

src/Command/InstallExtensionsForProjectCommand.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,10 @@ private function handlePhpProject(InputInterface $input, RootPackageInterface $r
124124
getcwd(),
125125
));
126126

127-
$extensionsRequired = $this->determineExtensionsRequired->forProject($this->composerFactoryForProject->composer($this->io));
127+
$extensionsRequired = $this->determineExtensionsRequired->forProject(
128+
$this->composerFactoryForProject->composer($this->io),
129+
CommandHelper::noDev($input),
130+
);
128131

129132
$pieComposer = PieComposerFactory::createPieComposer(
130133
$this->container,

src/Installing/InstallForPhpProject/DetermineExtensionsRequired.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public static function linkFilter(Link $link): bool
3131
}
3232

3333
/** @return array<string, Link> */
34-
public function forProject(Composer $composer): array
34+
public function forProject(Composer $composer, bool $noDev = false): array
3535
{
3636
$requires = [];
3737
$removeDevPackages = [];
@@ -44,8 +44,10 @@ public function forProject(Composer $composer): array
4444
$removeDevPackages = $installedRepo->getDevPackageNames();
4545
}
4646

47-
foreach (array_filter($composer->getPackage()->getDevRequires(), [self::class, 'linkFilter']) as $require => $link) {
48-
$requires[$require] = $link;
47+
if (! $noDev) {
48+
foreach (array_filter($composer->getPackage()->getDevRequires(), [self::class, 'linkFilter']) as $require => $link) {
49+
$requires[$require] = $link;
50+
}
4951
}
5052

5153
$installedRepo = new InstalledRepository([$installedRepo, new RootPackageRepository(clone $composer->getPackage())]);

test/integration/Command/InstallExtensionsForProjectCommandTest.php

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,79 @@ public function testInstallingExtensionsForPhpProject(): void
157157
self::assertStringContainsString('requires: ext-foobar:^1.2 🚫 Missing', $outputString);
158158
}
159159

160+
public function testInstallingExtensionsForPhpProjectIncludesDevRequiresByDefault(): void
161+
{
162+
$rootPackage = new RootPackage('my/project', '1.2.3.0', '1.2.3');
163+
$rootPackage->setRequires([
164+
'ext-standard' => new Link('my/project', 'ext-standard', new Constraint('=', '*'), Link::TYPE_REQUIRE, '*'),
165+
]);
166+
$rootPackage->setDevRequires([
167+
'ext-xdebug' => new Link('my/project', 'ext-xdebug', new Constraint('=', '*'), Link::TYPE_DEV_REQUIRE, '*'),
168+
]);
169+
$this->composerFactoryForProject->method('rootPackage')->willReturn($rootPackage);
170+
171+
$installedRepository = new InstalledArrayRepository([$rootPackage]);
172+
173+
$repositoryManager = $this->createMock(RepositoryManager::class);
174+
$repositoryManager->method('getLocalRepository')->willReturn($installedRepository);
175+
176+
$composer = $this->createMock(Composer::class);
177+
$composer->method('getPackage')->willReturn($rootPackage);
178+
$composer->method('getRepositoryManager')->willReturn($repositoryManager);
179+
180+
$this->composerFactoryForProject->method('composer')->willReturn($composer);
181+
182+
$this->installedPiePackages->method('allPiePackages')->willReturn(new PiePackageList([]));
183+
184+
$this->commandTester->execute(
185+
[],
186+
['verbosity' => BufferedOutput::VERBOSITY_VERY_VERBOSE],
187+
);
188+
189+
$outputString = $this->commandTester->getDisplay();
190+
191+
self::assertStringContainsString('Checking extensions for your project my/project', $outputString);
192+
self::assertStringContainsString('requires: ext-standard:* ✅ Already installed', $outputString);
193+
self::assertStringContainsString('ext-xdebug:* 🚫 Missing', $outputString);
194+
}
195+
196+
public function testInstallingExtensionsForPhpProjectExcludesDevRequiresWhenNoDevOptionSet(): void
197+
{
198+
$rootPackage = new RootPackage('my/project', '1.2.3.0', '1.2.3');
199+
$rootPackage->setRequires([
200+
'ext-standard' => new Link('my/project', 'ext-standard', new Constraint('=', '*'), Link::TYPE_REQUIRE, '*'),
201+
]);
202+
$rootPackage->setDevRequires([
203+
'ext-xdebug' => new Link('my/project', 'ext-xdebug', new Constraint('=', '*'), Link::TYPE_DEV_REQUIRE, '*'),
204+
]);
205+
$this->composerFactoryForProject->method('rootPackage')->willReturn($rootPackage);
206+
207+
$installedRepository = new InstalledArrayRepository([$rootPackage]);
208+
209+
$repositoryManager = $this->createMock(RepositoryManager::class);
210+
$repositoryManager->method('getLocalRepository')->willReturn($installedRepository);
211+
212+
$composer = $this->createMock(Composer::class);
213+
$composer->method('getPackage')->willReturn($rootPackage);
214+
$composer->method('getRepositoryManager')->willReturn($repositoryManager);
215+
216+
$this->composerFactoryForProject->method('composer')->willReturn($composer);
217+
218+
$this->installedPiePackages->method('allPiePackages')->willReturn(new PiePackageList([]));
219+
220+
$this->commandTester->execute(
221+
['--no-dev' => true],
222+
['verbosity' => BufferedOutput::VERBOSITY_VERY_VERBOSE],
223+
);
224+
225+
$outputString = $this->commandTester->getDisplay();
226+
227+
$this->commandTester->assertCommandIsSuccessful($outputString);
228+
self::assertStringContainsString('Checking extensions for your project my/project', $outputString);
229+
self::assertStringContainsString('requires: ext-standard:* ✅ Already installed', $outputString);
230+
self::assertStringNotContainsString('ext-xdebug', $outputString);
231+
}
232+
160233
public function testInstallingExtensionsForPhpProjectWithMultipleMatches(): void
161234
{
162235
$rootPackage = new RootPackage('my/project', '1.2.3.0', '1.2.3');
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Php\PieUnitTest\Installing\InstallForPhpProject;
6+
7+
use Composer\Composer;
8+
use Composer\Package\CompletePackage;
9+
use Composer\Package\Link;
10+
use Composer\Package\RootPackage;
11+
use Composer\Repository\InstalledArrayRepository;
12+
use Composer\Repository\RepositoryManager;
13+
use Composer\Semver\Constraint\Constraint;
14+
use Php\Pie\Installing\InstallForPhpProject\DetermineExtensionsRequired;
15+
use PHPUnit\Framework\Attributes\CoversClass;
16+
use PHPUnit\Framework\TestCase;
17+
18+
#[CoversClass(DetermineExtensionsRequired::class)]
19+
final class DetermineExtensionsRequiredTest extends TestCase
20+
{
21+
private function composerFor(RootPackage $rootPackage, InstalledArrayRepository $installedRepository): Composer
22+
{
23+
$repositoryManager = $this->createMock(RepositoryManager::class);
24+
$repositoryManager->method('getLocalRepository')->willReturn($installedRepository);
25+
26+
$composer = $this->createMock(Composer::class);
27+
$composer->method('getPackage')->willReturn($rootPackage);
28+
$composer->method('getRepositoryManager')->willReturn($repositoryManager);
29+
30+
return $composer;
31+
}
32+
33+
public function testForProjectIncludesDevRequiresByDefault(): void
34+
{
35+
$rootPackage = new RootPackage('my/project', '1.2.3.0', '1.2.3');
36+
$rootPackage->setRequires(['ext-redis' => new Link('my/project', 'ext-redis', new Constraint('=', '*'), Link::TYPE_REQUIRE, '*')]);
37+
$rootPackage->setDevRequires(['ext-xdebug' => new Link('my/project', 'ext-xdebug', new Constraint('=', '*'), Link::TYPE_DEV_REQUIRE, '*')]);
38+
39+
$composer = $this->composerFor($rootPackage, new InstalledArrayRepository([$rootPackage]));
40+
41+
$requires = (new DetermineExtensionsRequired())->forProject($composer);
42+
43+
self::assertArrayHasKey('ext-redis', $requires);
44+
self::assertArrayHasKey('ext-xdebug', $requires);
45+
}
46+
47+
public function testForProjectExcludesRootDevRequiresWhenNoDevIsTrue(): void
48+
{
49+
$rootPackage = new RootPackage('my/project', '1.2.3.0', '1.2.3');
50+
$rootPackage->setRequires(['ext-redis' => new Link('my/project', 'ext-redis', new Constraint('=', '*'), Link::TYPE_REQUIRE, '*')]);
51+
$rootPackage->setDevRequires(['ext-xdebug' => new Link('my/project', 'ext-xdebug', new Constraint('=', '*'), Link::TYPE_DEV_REQUIRE, '*')]);
52+
53+
$composer = $this->composerFor($rootPackage, new InstalledArrayRepository([$rootPackage]));
54+
55+
$requires = (new DetermineExtensionsRequired())->forProject($composer, true);
56+
57+
self::assertArrayHasKey('ext-redis', $requires);
58+
self::assertArrayNotHasKey('ext-xdebug', $requires);
59+
}
60+
61+
public function testForProjectStillIncludesRequiresFromNonDevInstalledPackagesWhenNoDevIsTrue(): void
62+
{
63+
$rootPackage = new RootPackage('my/project', '1.2.3.0', '1.2.3');
64+
$rootPackage->setDevRequires(['ext-xdebug' => new Link('my/project', 'ext-xdebug', new Constraint('=', '*'), Link::TYPE_DEV_REQUIRE, '*')]);
65+
66+
$dependencyPackage = new CompletePackage('vendor/some-lib', '1.0.0.0', '1.0.0');
67+
$dependencyPackage->setRequires(['ext-mbstring' => new Link('vendor/some-lib', 'ext-mbstring', new Constraint('=', '*'), Link::TYPE_REQUIRE, '*')]);
68+
69+
$installedRepository = new InstalledArrayRepository([$rootPackage, $dependencyPackage]);
70+
$installedRepository->setDevPackageNames([]);
71+
72+
$composer = $this->composerFor($rootPackage, $installedRepository);
73+
74+
$requires = (new DetermineExtensionsRequired())->forProject($composer, true);
75+
76+
self::assertArrayHasKey('ext-mbstring', $requires);
77+
self::assertArrayNotHasKey('ext-xdebug', $requires);
78+
}
79+
}

0 commit comments

Comments
 (0)