Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bin/pie
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ declare(strict_types=1);
namespace Php\Pie;

use Php\Pie\Command\BuildCommand;
use Php\Pie\Command\CheckBuildToolsCommand;
use Php\Pie\Command\DownloadCommand;
use Php\Pie\Command\InfoCommand;
use Php\Pie\Command\InstallCommand;
Expand Down Expand Up @@ -59,6 +60,7 @@ $application->setCommandLoader(new ContainerCommandLoader(
'self-update' => SelfUpdateCommand::class,
'self-verify' => SelfVerifyCommand::class,
'install-extensions-for-project' => InstallExtensionsForProjectCommand::class,
'check-build-tools' => CheckBuildToolsCommand::class,
],
));

Expand Down
109 changes: 109 additions & 0 deletions src/Command/CheckBuildToolsCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Composer\IO\IOInterface;
use Php\Pie\Platform\OperatingSystem;
use Php\Pie\Platform\PackageManager;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\SelfManage\BuildTools\CheckAllBuildTools;
use Php\Pie\Util\Emoji;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

use function sprintf;

#[AsCommand(
name: 'check-build-tools',
description: 'Check that all build tools required to compile PHP extensions are installed.',
)]
final class CheckBuildToolsCommand extends Command
{
public function __construct(
private readonly CheckAllBuildTools $checkAllBuildTools,
private readonly PackageManager|null $packageManager,
private readonly IOInterface $io,
) {
parent::__construct();
}

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

CommandHelper::configurePhpConfigOptions($this);
CommandHelper::configureBuildToolsCheckOptions($this);
}

public function execute(InputInterface $input, OutputInterface $output): int
{
$targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $this->io);

if ($targetPlatform->operatingSystem === OperatingSystem::Windows) {
$this->io->writeError('Build tools are not required on Windows systems!');

return 0;
}

$statuses = $this->checkAllBuildTools->statuses($targetPlatform, $this->packageManager);

$this->io->write("\n" . '<options=bold,underscore>Build tools typically required to build extensions:</>');

$anyMissing = false;
foreach ($statuses as $status) {
if ($status->found) {
$this->io->write(sprintf(' %s %s', Emoji::GREEN_CHECKMARK, $status->toolNames));
continue;
}

$anyMissing = true;
$this->io->write(sprintf(
' %s %s%s',
Emoji::CROSS,
$status->toolNames,
$status->packageName !== null ? sprintf(' (installs with package: %s)', $status->packageName) : '',
));
}

if (! $anyMissing) {
$this->io->write(sprintf("\n" . '<info>%s All build tools are installed.</info>', Emoji::GREEN_CHECKMARK));

return Command::SUCCESS;
}

$this->io->write('');

$this->checkAllBuildTools->check(
$this->io,
$this->packageManager,
$targetPlatform,
CommandHelper::autoInstallBuildTools($input),
);

// Check again things got installed as expected
if (! $this->allToolsFound($targetPlatform)) {
$this->io->writeError(sprintf("\n" . '<error>%s Some build tools are still missing.</error>', Emoji::CROSS));

return Command::FAILURE;
}

$this->io->write(sprintf("\n" . '<info>%s All build tools are now installed.</info>', Emoji::GREEN_CHECKMARK));

return Command::SUCCESS;
}

private function allToolsFound(TargetPlatform $targetPlatform): bool
{
foreach ($this->checkAllBuildTools->statuses($targetPlatform, $this->packageManager) as $status) {
if (! $status->found) {
return false;
}
}

return true;
}
}
17 changes: 11 additions & 6 deletions src/Command/CommandHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,16 @@ public static function configurePhpConfigOptions(Command $command): void
);
}

public static function configureBuildToolsCheckOptions(Command $command): void
{
$command->addOption(
self::OPTION_AUTO_INSTALL_BUILD_TOOLS,
null,
InputOption::VALUE_NONE,
'If build tools are missing, automatically install them, instead of prompting.',
);
}

public static function configureDownloadBuildInstallOptions(Command $command, bool $withRequestedPackageAndVersion = true): void
{
if ($withRequestedPackageAndVersion) {
Expand Down Expand Up @@ -161,12 +171,7 @@ public static function configureDownloadBuildInstallOptions(Command $command, bo
'Select a PIE package for a given extension name, e.g. `--select=foo=myvendor/foo` to resolve the `ext-foo` extension to `myvendor/foo` PIE package.',
);

$command->addOption(
self::OPTION_AUTO_INSTALL_BUILD_TOOLS,
null,
InputOption::VALUE_NONE,
'If build tools are missing, automatically install them, instead of prompting.',
);
self::configureBuildToolsCheckOptions($command);
$command->addOption(
self::OPTION_SUPPRESS_BUILD_TOOLS_CHECK,
null,
Expand Down
2 changes: 2 additions & 0 deletions src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Php\Pie\Building\WindowsBuild;
use Php\Pie\Command\ArgvInput;
use Php\Pie\Command\BuildCommand;
use Php\Pie\Command\CheckBuildToolsCommand;
use Php\Pie\Command\DownloadCommand;
use Php\Pie\Command\InfoCommand;
use Php\Pie\Command\InstallCommand;
Expand Down Expand Up @@ -125,6 +126,7 @@ static function (ConsoleCommandEvent $event) use (&$displayedBanner, $io): void
$container->singleton(SelfUpdateCommand::class);
$container->singleton(SelfVerifyCommand::class);
$container->singleton(InstallExtensionsForProjectCommand::class);
$container->singleton(CheckBuildToolsCommand::class);

$container->singleton(IOInterface::class, static function (ContainerInterface $container): IOInterface {
return new ConsoleIO(
Expand Down
16 changes: 16 additions & 0 deletions src/SelfManage/BuildTools/BuildToolStatus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\BuildTools;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class BuildToolStatus
{
public function __construct(
public readonly string $toolNames,
public readonly bool $found,
public readonly string|null $packageName,
) {
}
}
38 changes: 26 additions & 12 deletions src/SelfManage/BuildTools/CheckAllBuildTools.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Php\Pie\Platform\TargetPlatform;
use Throwable;

use function array_map;
use function array_unique;
use function array_values;
use function count;
Expand Down Expand Up @@ -114,38 +115,51 @@ public function __construct(
) {
}

/** @return list<BuildToolStatus> */
public function statuses(TargetPlatform $targetPlatform, PackageManager|null $packageManager): array
{
return array_map(
static function (BinaryBuildToolFinder $buildTool) use ($targetPlatform, $packageManager): BuildToolStatus {
$found = $buildTool->check($targetPlatform);

return new BuildToolStatus(
$buildTool->toolNames(),
$found,
$found || $packageManager === null ? null : $buildTool->packageNameFor($packageManager, $targetPlatform),
);
},
$this->buildTools,
);
}

public function check(IOInterface $io, PackageManager|null $packageManager, TargetPlatform $targetPlatform, bool $autoInstallIfMissing): void
{
$io->write('<info>Checking if all build tools are installed.</info>', verbosity: IOInterface::VERBOSE);
/** @var list<string> $packagesToInstall */
$packagesToInstall = [];
$missingTools = [];
$allFound = true;

foreach ($this->buildTools as $buildTool) {
if ($buildTool->check($targetPlatform) !== false) {
$io->write('Build tool ' . $buildTool->toolNames() . ' is installed.', verbosity: IOInterface::VERY_VERBOSE);
foreach ($this->statuses($targetPlatform, $packageManager) as $status) {
if ($status->found) {
$io->write('Build tool ' . $status->toolNames . ' is installed.', verbosity: IOInterface::VERY_VERBOSE);
continue;
}

$allFound = false;
$missingTools[] = $buildTool->toolNames();
$missingTools[] = $status->toolNames;

if ($packageManager === null) {
continue;
}

$packageName = $buildTool->packageNameFor($packageManager, $targetPlatform);

if ($packageName === null) {
$io->writeError('<warning>Could not find package name for build tool ' . $buildTool->toolNames() . '.</warning>', verbosity: IOInterface::VERBOSE);
if ($status->packageName === null) {
$io->writeError('<warning>Could not find package name for build tool ' . $status->toolNames . '.</warning>', verbosity: IOInterface::VERBOSE);
continue;
}

$packagesToInstall[] = $packageName;
$packagesToInstall[] = $status->packageName;
}

if ($allFound) {
if (! count($missingTools)) {
$io->write('<info>All build tools found.</info>', verbosity: IOInterface::VERBOSE);

return;
Expand Down
53 changes: 53 additions & 0 deletions test/integration/Command/CheckBuildToolsCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

namespace Php\PieIntegrationTest\Command;

use Composer\Util\Platform;
use Php\Pie\Command\CheckBuildToolsCommand;
use Php\Pie\Container;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\RequiresOperatingSystemFamily;

#[CoversClass(CheckBuildToolsCommand::class)]
final class CheckBuildToolsCommandTest extends IsolatedWorkingDirectoryTestCase
{
private CommandTester $commandTester;

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

$this->commandTester = new CommandTester(Container::testFactory()->get(CheckBuildToolsCommand::class));
}

public function testCheckBuildToolsCommandListsRequiredToolsAndSucceeds(): void
{
if (Platform::isWindows()) {
self::markTestSkipped('This test can only run on non-Windows systems');
}

$this->commandTester->execute([]);

$this->commandTester->assertCommandIsSuccessful();

$outputString = $this->commandTester->getDisplay();
self::assertStringContainsString('Build tools typically required to build extensions:', $outputString);
self::assertStringContainsString('gcc', $outputString);
self::assertStringContainsString('make', $outputString);
self::assertStringContainsString('phpize', $outputString);
self::assertStringContainsString('All build tools are installed.', $outputString);
}

#[RequiresOperatingSystemFamily('Windows')]
public function testCheckBuildToolsCommandSkipsCheckOnWindows(): void
{
$this->commandTester->execute([]);

$this->commandTester->assertCommandIsSuccessful();

$outputString = $this->commandTester->getDisplay();
self::assertStringContainsString('Build tools are not required on Windows systems!', $outputString);
}
}
Loading
Loading