diff --git a/bin/pie b/bin/pie index cbff1ac..6d548bb 100755 --- a/bin/pie +++ b/bin/pie @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Php\Pie; +use Php\Pie\Command\BuildCommand; use Php\Pie\Command\DownloadCommand; use Symfony\Component\Console\Application; use Symfony\Component\Console\CommandLoader\ContainerCommandLoader; @@ -21,6 +22,7 @@ $application->setCommandLoader(new ContainerCommandLoader( $container, [ 'download' => DownloadCommand::class, + 'build' => BuildCommand::class, ] )); $application->run($container->get(InputInterface::class), $container->get(OutputInterface::class)); diff --git a/src/Building/Build.php b/src/Building/Build.php new file mode 100644 index 0000000..14fd88c --- /dev/null +++ b/src/Building/Build.php @@ -0,0 +1,21 @@ + $configureOptions */ + public function __invoke( + DownloadedPackage $downloadedPackage, + TargetPlatform $targetPlatform, + array $configureOptions, + OutputInterface $output, + ): void; +} diff --git a/src/Building/UnixBuild.php b/src/Building/UnixBuild.php new file mode 100644 index 0000000..61c42c5 --- /dev/null +++ b/src/Building/UnixBuild.php @@ -0,0 +1,76 @@ +phpize($downloadedPackage); + $output->writeln('phpize complete.'); + + $phpConfigPath = $targetPlatform->phpBinaryPath->phpConfigPath(); + if ($phpConfigPath !== null) { + $configureOptions[] = '--with-php-config=' . $phpConfigPath; + } + + $this->configure($downloadedPackage, $configureOptions); + $optionsOutput = count($configureOptions) ? ' with options: ' . implode(' ', $configureOptions) : '.'; + $output->writeln('Configure complete' . $optionsOutput); + + $this->make($downloadedPackage); + + $expectedSoFile = $downloadedPackage->extractedSourcePath . '/modules/' . $downloadedPackage->package->extensionName->name() . '.so'; + + if (! file_exists($expectedSoFile)) { + $output->writeln(sprintf( + 'Build complete, but expected %s does not exist - however, this may be normal if this extension outputs the .so file in a different location.', + $expectedSoFile, + )); + + return; + } + + $output->writeln(sprintf( + 'Build complete: %s', + $expectedSoFile, + )); + } + + private function phpize(DownloadedPackage $downloadedPackage): void + { + (new Process(['phpize'], $downloadedPackage->extractedSourcePath)) + ->mustRun(); + } + + /** @param list $configureOptions */ + private function configure(DownloadedPackage $downloadedPackage, array $configureOptions = []): void + { + (new Process(['./configure', ...$configureOptions], $downloadedPackage->extractedSourcePath)) + ->mustRun(); + } + + private function make(DownloadedPackage $downloadedPackage): void + { + (new Process(['make'], $downloadedPackage->extractedSourcePath)) + ->mustRun(); + } +} diff --git a/src/Building/WindowsBuild.php b/src/Building/WindowsBuild.php new file mode 100644 index 0000000..3e743e3 --- /dev/null +++ b/src/Building/WindowsBuild.php @@ -0,0 +1,23 @@ +writeln('Nothing to do on Windows.'); + } +} diff --git a/src/Command/BuildCommand.php b/src/Command/BuildCommand.php new file mode 100644 index 0000000..2cc0377 --- /dev/null +++ b/src/Command/BuildCommand.php @@ -0,0 +1,58 @@ +dependencyResolver, + $targetPlatform, + $requestedNameAndVersionPair, + $this->downloadAndExtract, + $output, + ); + + CommandHelper::bindConfigureOptionsFromPackage($this, $downloadedPackage->package, $input); + + $configureOptionsValues = CommandHelper::processConfigureOptionsFromInput($downloadedPackage->package, $input); + + ($this->build)($downloadedPackage, $targetPlatform, $configureOptionsValues, $output); + + return Command::SUCCESS; + } +} diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php new file mode 100644 index 0000000..9cdd83c --- /dev/null +++ b/src/Command/CommandHelper.php @@ -0,0 +1,210 @@ +addArgument( + self::ARG_REQUESTED_PACKAGE_AND_VERSION, + InputArgument::REQUIRED, + 'The extension name and version constraint to use, in the format {ext-name}{?:{?version-constraint}{?@stability}}, for example `xdebug/xdebug:^3.4@alpha`, `xdebug/xdebug:@alpha`, `xdebug/xdebug:^3.4`, etc.', + ); + $command->addOption( + self::OPTION_WITH_PHP_CONFIG, + null, + InputOption::VALUE_OPTIONAL, + 'The path to the `php-config` binary to find the target PHP platform on ' . OperatingSystem::NonWindows->asFriendlyName() . ', e.g. --' . self::OPTION_WITH_PHP_CONFIG . '=/usr/bin/php-config7.4', + ); + $command->addOption( + self::OPTION_WITH_PHP_PATH, + null, + InputOption::VALUE_OPTIONAL, + 'The path to the `php` binary to use as the target PHP platform on ' . OperatingSystem::Windows->asFriendlyName() . ', e.g. --' . self::OPTION_WITH_PHP_PATH . '=C:\usr\php7.4.33\php.exe', + ); + + /** + * Allows additional options for the `./configure` command to be passed here. + * Note, this means you probably need to call {@see self::validateInput()} to validate the input manually... + */ + $command->ignoreValidationErrors(); + } + + public static function validateInput(InputInterface $input, Command $command): void + { + $input->bind($command->getDefinition()); + } + + public static function determineTargetPlatformFromInputs(InputInterface $input, OutputInterface $output): TargetPlatform + { + $phpBinaryPath = PhpBinaryPath::fromCurrentProcess(); + + /** @var mixed $withPhpConfig */ + $withPhpConfig = $input->getOption(self::OPTION_WITH_PHP_CONFIG); + $specifiedWithPhpConfig = is_string($withPhpConfig) && $withPhpConfig !== ''; + /** @var mixed $withPhpPath */ + $withPhpPath = $input->getOption(self::OPTION_WITH_PHP_PATH); + $specifiedWithPhpPath = is_string($withPhpPath) && $withPhpPath !== ''; + + if (Platform::isWindows() && $specifiedWithPhpConfig) { + throw new InvalidArgumentException('The --with-php-config=/path/to/php-config cannot be used on Windows, use --with-php-path=/path/to/php instead.'); + } + + if (! Platform::isWindows() && $specifiedWithPhpPath && ! $specifiedWithPhpConfig) { + throw new InvalidArgumentException('The --with-php-path=/path/to/php cannot be used on non-Windows, use --with-php-config=/path/to/php-config instead.'); + } + + if ($specifiedWithPhpConfig) { + $phpBinaryPath = PhpBinaryPath::fromPhpConfigExecutable($withPhpConfig); + } + + if ($specifiedWithPhpPath) { + $phpBinaryPath = PhpBinaryPath::fromPhpBinaryPath($withPhpPath); + } + + $targetPlatform = TargetPlatform::fromPhpBinaryPath($phpBinaryPath); + + $output->writeln(sprintf('You are running PHP %s', PHP_VERSION)); + $output->writeln(sprintf( + 'Target PHP installation: %s %s%s, on %s %s (from %s)', + $phpBinaryPath->version(), + $targetPlatform->threadSafety->asShort(), + strtolower($targetPlatform->windowsCompiler !== null ? ', ' . $targetPlatform->windowsCompiler->name : ''), + $targetPlatform->operatingSystem->asFriendlyName(), + $targetPlatform->architecture->name, + $phpBinaryPath->phpBinaryPath, + )); + + return $targetPlatform; + } + + /** @return RequestedNameAndVersionPair */ + public static function requestedNameAndVersionPair(InputInterface $input): array + { + $requestedPackageString = $input->getArgument(self::ARG_REQUESTED_PACKAGE_AND_VERSION); + + if (! is_string($requestedPackageString) || $requestedPackageString === '') { + throw new InvalidArgumentException('No package was requested for installation'); + } + + $nameAndVersionPairs = (new VersionParser()) + ->parseNameVersionPairs([$requestedPackageString]); + $requestedNameAndVersionPair = reset($nameAndVersionPairs); + + if (! is_array($requestedNameAndVersionPair)) { + throw new InvalidArgumentException('Failed to parse the name/version pair'); + } + + if (! array_key_exists('version', $requestedNameAndVersionPair)) { + $requestedNameAndVersionPair['version'] = null; + } + + Assert::stringNotEmpty($requestedNameAndVersionPair['name']); + Assert::nullOrStringNotEmpty($requestedNameAndVersionPair['version']); + + return $requestedNameAndVersionPair; + } + + /** @param RequestedNameAndVersionPair $requestedNameAndVersionPair */ + public static function downloadPackage( + DependencyResolver $dependencyResolver, + TargetPlatform $targetPlatform, + array $requestedNameAndVersionPair, + DownloadAndExtract $downloadAndExtract, + OutputInterface $output, + ): DownloadedPackage { + $package = ($dependencyResolver)( + $targetPlatform, + $requestedNameAndVersionPair['name'], + $requestedNameAndVersionPair['version'], + ); + + $output->writeln(sprintf('Found package: %s which provides %s', $package->prettyNameAndVersion(), $package->extensionName->nameWithExtPrefix())); + + return ($downloadAndExtract)($targetPlatform, $package); + } + + public static function bindConfigureOptionsFromPackage(Command $command, Package $package, InputInterface $input): void + { + foreach ($package->configureOptions as $configureOption) { + $command->addOption( + $configureOption->name, + null, + $configureOption->needsValue ? InputOption::VALUE_REQUIRED : InputOption::VALUE_NONE, + $configureOption->description, + ); + } + + self::validateInput($input, $command); + } + + /** @return list */ + public static function processConfigureOptionsFromInput(Package $package, InputInterface $input): array + { + $configureOptionsValues = []; + foreach ($package->configureOptions as $configureOption) { + if (! $input->hasOption($configureOption->name)) { + continue; + } + + $value = $input->getOption($configureOption->name); + + if ($configureOption->needsValue) { + if (is_string($value) && $value !== '') { + $configureOptionsValues[] = '--' . $configureOption->name . '=' . escapeshellarg($value); + } + + continue; + } + + Assert::boolean($value); + if ($value !== true) { + continue; + } + + $configureOptionsValues[] = '--' . $configureOption->name; + } + + return $configureOptionsValues; + } +} diff --git a/src/Command/DownloadCommand.php b/src/Command/DownloadCommand.php index 9611d6a..9c28f60 100644 --- a/src/Command/DownloadCommand.php +++ b/src/Command/DownloadCommand.php @@ -4,29 +4,14 @@ namespace Php\Pie\Command; -use Composer\Package\Version\VersionParser; -use InvalidArgumentException; use Php\Pie\DependencyResolver\DependencyResolver; use Php\Pie\Downloading\DownloadAndExtract; -use Php\Pie\Platform\OperatingSystem; -use Php\Pie\Platform\TargetPhp\PhpBinaryPath; -use Php\Pie\Platform\TargetPlatform; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Webmozart\Assert\Assert; -use function array_key_exists; -use function is_array; -use function is_string; -use function reset; use function sprintf; -use function strtolower; - -use const PHP_VERSION; #[AsCommand( name: 'download', @@ -34,10 +19,6 @@ )] final class DownloadCommand extends Command { - private const ARG_REQUESTED_PACKAGE_AND_VERSION = 'requested-package-and-version'; - private const OPTION_WITH_PHP_CONFIG = 'with-php-config'; - private const OPTION_WITH_PHP_PATH = 'with-php-path'; - public function __construct( private readonly DependencyResolver $dependencyResolver, private readonly DownloadAndExtract $downloadAndExtract, @@ -49,66 +30,25 @@ public function configure(): void { parent::configure(); - $this->addArgument( - self::ARG_REQUESTED_PACKAGE_AND_VERSION, - InputArgument::REQUIRED, - 'The extension name and version constraint to use, in the format {ext-name}{?:{?version-constraint}{?@stability}}, for example `xdebug/xdebug:^3.4@alpha`, `xdebug/xdebug:@alpha`, `xdebug/xdebug:^3.4`, etc.', - ); - $this->addOption( - self::OPTION_WITH_PHP_CONFIG, - null, - InputOption::VALUE_OPTIONAL, - 'The path to the `php-config` binary to find the target PHP platform on ' . OperatingSystem::NonWindows->asFriendlyName() . ', e.g. --' . self::OPTION_WITH_PHP_CONFIG . '=/usr/bin/php-config7.4', - ); - $this->addOption( - self::OPTION_WITH_PHP_PATH, - null, - InputOption::VALUE_OPTIONAL, - 'The path to the `php` binary to use as the target PHP platform on ' . OperatingSystem::Windows->asFriendlyName() . ', e.g. --' . self::OPTION_WITH_PHP_PATH . '=C:\usr\php7.4.33\php.exe', - ); + CommandHelper::configureOptions($this); } public function execute(InputInterface $input, OutputInterface $output): int { - $phpBinaryPath = PhpBinaryPath::fromCurrentProcess(); - - /** @var mixed $withPhpConfig */ - $withPhpConfig = $input->getOption(self::OPTION_WITH_PHP_CONFIG); - if (is_string($withPhpConfig) && $withPhpConfig !== '') { - $phpBinaryPath = PhpBinaryPath::fromPhpConfigExecutable($withPhpConfig); - } + CommandHelper::validateInput($input, $this); - /** @var mixed $withPhpPath */ - $withPhpPath = $input->getOption(self::OPTION_WITH_PHP_PATH); - if (is_string($withPhpPath) && $withPhpPath !== '') { - $phpBinaryPath = PhpBinaryPath::fromPhpBinaryPath($withPhpPath); - } + $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $output); - $targetPlatform = TargetPlatform::fromPhpBinaryPath($phpBinaryPath); + $requestedNameAndVersionPair = CommandHelper::requestedNameAndVersionPair($input); - $output->writeln(sprintf('You are running PHP %s', PHP_VERSION)); - $output->writeln(sprintf( - 'Target PHP installation: %s %s%s, on %s %s (from %s)', - $phpBinaryPath->version(), - $targetPlatform->threadSafety->asShort(), - strtolower($targetPlatform->windowsCompiler !== null ? ', ' . $targetPlatform->windowsCompiler->name : ''), - $targetPlatform->operatingSystem->asFriendlyName(), - $targetPlatform->architecture->name, - $phpBinaryPath->phpBinaryPath, - )); - - $requestedNameAndVersionPair = $this->requestedNameAndVersionPair($input); - - $package = ($this->dependencyResolver)( + $downloadedPackage = CommandHelper::downloadPackage( + $this->dependencyResolver, $targetPlatform, - $requestedNameAndVersionPair['name'], - $requestedNameAndVersionPair['version'], + $requestedNameAndVersionPair, + $this->downloadAndExtract, + $output, ); - $output->writeln(sprintf('Found package: %s which provides %s', $package->prettyNameAndVersion(), $package->extensionName->nameWithExtPrefix())); - - $downloadedPackage = ($this->downloadAndExtract)($targetPlatform, $package); - $output->writeln(sprintf( 'Extracted %s source to: %s', $downloadedPackage->package->prettyNameAndVersion(), @@ -117,31 +57,4 @@ public function execute(InputInterface $input, OutputInterface $output): int return Command::SUCCESS; } - - /** @return array{name: non-empty-string, version: non-empty-string|null} */ - private function requestedNameAndVersionPair(InputInterface $input): array - { - $requestedPackageString = $input->getArgument(self::ARG_REQUESTED_PACKAGE_AND_VERSION); - - if (! is_string($requestedPackageString) || $requestedPackageString === '') { - throw new InvalidArgumentException('No package was requested for installation'); - } - - $nameAndVersionPairs = (new VersionParser()) - ->parseNameVersionPairs([$requestedPackageString]); - $requestedNameAndVersionPair = reset($nameAndVersionPairs); - - if (! is_array($requestedNameAndVersionPair)) { - throw new InvalidArgumentException('Failed to parse the name/version pair'); - } - - if (! array_key_exists('version', $requestedNameAndVersionPair)) { - $requestedNameAndVersionPair['version'] = null; - } - - Assert::stringNotEmpty($requestedNameAndVersionPair['name']); - Assert::nullOrStringNotEmpty($requestedNameAndVersionPair['version']); - - return $requestedNameAndVersionPair; - } } diff --git a/src/ConfigureOption.php b/src/ConfigureOption.php new file mode 100644 index 0000000..12657a5 --- /dev/null +++ b/src/ConfigureOption.php @@ -0,0 +1,50 @@ + $configureOptionDefinition */ + public static function fromComposerJsonDefinition(array $configureOptionDefinition): self + { + Assert::keyExists($configureOptionDefinition, 'name'); + Assert::stringNotEmpty($configureOptionDefinition['name']); + + $needsValue = false; + if (array_key_exists('needs-value', $configureOptionDefinition)) { + Assert::boolean($configureOptionDefinition['needs-value']); + $needsValue = $configureOptionDefinition['needs-value']; + } + + $description = ''; + if (array_key_exists('description', $configureOptionDefinition)) { + Assert::string($configureOptionDefinition['description']); + $description = $configureOptionDefinition['description']; + } + + return new self( + $configureOptionDefinition['name'], + $needsValue, + $description, + ); + } +} diff --git a/src/Container.php b/src/Container.php index c8e8c8f..bf50bc4 100644 --- a/src/Container.php +++ b/src/Container.php @@ -16,6 +16,10 @@ use GuzzleHttp\ClientInterface; use GuzzleHttp\RequestOptions; use Illuminate\Container\Container as IlluminateContainer; +use Php\Pie\Building\Build; +use Php\Pie\Building\UnixBuild; +use Php\Pie\Building\WindowsBuild; +use Php\Pie\Command\BuildCommand; use Php\Pie\Command\DownloadCommand; use Php\Pie\DependencyResolver\DependencyResolver; use Php\Pie\DependencyResolver\ResolveDependencyWithComposer; @@ -44,6 +48,7 @@ public static function factory(): ContainerInterface $container->instance(OutputInterface::class, new ConsoleOutput()); $container->singleton(DownloadCommand::class); + $container->singleton(BuildCommand::class); $container->singleton(IOInterface::class, static function (ContainerInterface $container): IOInterface { return new ConsoleIO( @@ -110,6 +115,17 @@ static function (ContainerInterface $container): DownloadAndExtract { }, ); + $container->singleton( + Build::class, + static function (ContainerInterface $container): Build { + if (Platform::isWindows()) { + return $container->get(WindowsBuild::class); + } + + return $container->get(UnixBuild::class); + }, + ); + return $container; } } diff --git a/src/DependencyResolver/Package.php b/src/DependencyResolver/Package.php index 80f5a6d..41446fb 100644 --- a/src/DependencyResolver/Package.php +++ b/src/DependencyResolver/Package.php @@ -5,8 +5,12 @@ namespace Php\Pie\DependencyResolver; use Composer\Package\CompletePackageInterface; +use Php\Pie\ConfigureOption; use Php\Pie\ExtensionName; +use function array_key_exists; +use function array_map; + /** * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks * @@ -17,21 +21,33 @@ final class Package public const TYPE_PHP_MODULE = 'php-ext'; public const TYPE_ZEND_EXTENSION = 'php-ext-zend'; + /** @param list $configureOptions */ public function __construct( public readonly ExtensionName $extensionName, public readonly string $name, public readonly string $version, public readonly string|null $downloadUrl, + public readonly array $configureOptions, ) { } public static function fromComposerCompletePackage(CompletePackageInterface $completePackage): self { + $phpExtOptions = $completePackage->getPhpExt(); + + $configureOptions = $phpExtOptions !== null && array_key_exists('configure-options', $phpExtOptions) + ? array_map( + static fn (array $configureOption): ConfigureOption => ConfigureOption::fromComposerJsonDefinition($configureOption), + $phpExtOptions['configure-options'], + ) + : []; + return new self( ExtensionName::determineFromComposerPackage($completePackage), $completePackage->getPrettyName(), $completePackage->getPrettyVersion(), $completePackage->getDistUrl(), + $configureOptions, ); } diff --git a/src/Platform/TargetPhp/PhpBinaryPath.php b/src/Platform/TargetPhp/PhpBinaryPath.php index 0c28d06..025cc8d 100644 --- a/src/Platform/TargetPhp/PhpBinaryPath.php +++ b/src/Platform/TargetPhp/PhpBinaryPath.php @@ -23,9 +23,14 @@ */ class PhpBinaryPath { - /** @param non-empty-string $phpBinaryPath */ - private function __construct(readonly string $phpBinaryPath) - { + /** + * @param non-empty-string $phpBinaryPath + * @param non-empty-string|null $phpConfigPath + */ + private function __construct( + public readonly string $phpBinaryPath, + private readonly string|null $phpConfigPath, + ) { // @todo https://github.com/php/pie/issues/12 - we could verify that the given $phpBinaryPath really is a PHP install } @@ -160,6 +165,18 @@ public function phpinfo(): string return $phpInfo; } + /** + * This will only be set if {@see self::fromPhpConfigExecutable()} is used to create this {@see self}, otherwise + * will return `null`. + * + * @return non-empty-string|null + */ + public function phpConfigPath(): string|null + { + return $this->phpConfigPath; + } + + /** @param non-empty-string $phpConfig */ public static function fromPhpConfigExecutable(string $phpConfig): self { $phpExecutable = trim((new Process([$phpConfig, '--php-binary'])) @@ -167,13 +184,13 @@ public static function fromPhpConfigExecutable(string $phpConfig): self ->getOutput()); Assert::stringNotEmpty($phpExecutable, 'Could not find path to PHP executable.'); - return new self($phpExecutable); + return new self($phpExecutable, $phpConfig); } /** @param non-empty-string $phpBinary */ public static function fromPhpBinaryPath(string $phpBinary): self { - return new self($phpBinary); + return new self($phpBinary, null); } public static function fromCurrentProcess(): self @@ -181,6 +198,6 @@ public static function fromCurrentProcess(): self $phpExecutable = trim((string) (new PhpExecutableFinder())->find()); Assert::stringNotEmpty($phpExecutable, 'Could not find path to PHP executable.'); - return new self($phpExecutable); + return new self($phpExecutable, null); } } diff --git a/test/assets/pie_test_ext/.gitignore b/test/assets/pie_test_ext/.gitignore new file mode 100644 index 0000000..c37d5e7 --- /dev/null +++ b/test/assets/pie_test_ext/.gitignore @@ -0,0 +1,44 @@ +*.lo +*.la +.libs +acinclude.m4 +aclocal.m4 +autom4te.cache +build +config.guess +config.h +config.h.in +config.h.in~ +config.log +config.nice +config.status +config.sub +configure +configure~ +configure.ac +configure.in +include +install-sh +libtool +ltmain.sh +Makefile +Makefile.fragments +Makefile.global +Makefile.objects +missing +mkinstalldirs +modules +php_test_results_*.txt +phpt.* +run-test-info.php +run-tests.php +tests/**/*.diff +tests/**/*.out +tests/**/*.php +tests/**/*.exp +tests/**/*.log +tests/**/*.sh +tests/**/*.db +tests/**/*.mem +tmp-php.ini +pie_test_ext.dep diff --git a/test/assets/pie_test_ext/config.m4 b/test/assets/pie_test_ext/config.m4 new file mode 100644 index 0000000..3deaa4f --- /dev/null +++ b/test/assets/pie_test_ext/config.m4 @@ -0,0 +1,14 @@ +PHP_ARG_ENABLE([pie_test_ext], + [whether to enable pie_test_ext support], + [AS_HELP_STRING([--enable-pie_test_ext], + [Enable pie_test_ext support])], + [no]) + +if test "$PHP_PIE_TEST_EXT" != "no"; then + AC_DEFINE(HAVE_PIE_TEST_EXT, 1, [ Have pie_test_ext support ]) + + PHP_NEW_EXTENSION([pie_test_ext], + [pie_test_ext.c], + [$ext_shared],, + [-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1]) +fi diff --git a/test/assets/pie_test_ext/php_pie_test_ext.h b/test/assets/pie_test_ext/php_pie_test_ext.h new file mode 100644 index 0000000..40dae60 --- /dev/null +++ b/test/assets/pie_test_ext/php_pie_test_ext.h @@ -0,0 +1,15 @@ +/* pie_test_ext extension for PHP (c) 2024 PHP Foundation */ + +#ifndef PHP_PIE_TEST_EXT_H +# define PHP_PIE_TEST_EXT_H + +extern zend_module_entry pie_test_ext_module_entry; +# define phpext_pie_test_ext_ptr &pie_test_ext_module_entry + +# define PHP_PIE_TEST_EXT_VERSION "0.1.0" + +# if defined(ZTS) && defined(COMPILE_DL_PIE_TEST_EXT) +ZEND_TSRMLS_CACHE_EXTERN() +# endif + +#endif /* PHP_PIE_TEST_EXT_H */ diff --git a/test/assets/pie_test_ext/pie_test_ext.c b/test/assets/pie_test_ext/pie_test_ext.c new file mode 100644 index 0000000..c9ef788 --- /dev/null +++ b/test/assets/pie_test_ext/pie_test_ext.c @@ -0,0 +1,68 @@ +/* pie_test_ext extension for PHP (c) 2024 PHP Foundation */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "php.h" +#include "ext/standard/info.h" +#include "php_pie_test_ext.h" +#include "pie_test_ext_arginfo.h" + +/* For compatibility with older PHP versions */ +#ifndef ZEND_PARSE_PARAMETERS_NONE +#define ZEND_PARSE_PARAMETERS_NONE() \ + ZEND_PARSE_PARAMETERS_START(0, 0) \ + ZEND_PARSE_PARAMETERS_END() +#endif + +/* {{{ void test1() */ +PHP_FUNCTION(test1) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + php_printf("The extension %s is loaded and working!\r\n", "pie_test_ext"); +} +/* }}} */ + +/* {{{ PHP_RINIT_FUNCTION */ +PHP_RINIT_FUNCTION(pie_test_ext) +{ +#if defined(ZTS) && defined(COMPILE_DL_PIE_TEST_EXT) + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MINFO_FUNCTION */ +PHP_MINFO_FUNCTION(pie_test_ext) +{ + php_info_print_table_start(); + php_info_print_table_row(2, "pie_test_ext support", "enabled"); + php_info_print_table_end(); +} +/* }}} */ + +/* {{{ pie_test_ext_module_entry */ +zend_module_entry pie_test_ext_module_entry = { + STANDARD_MODULE_HEADER, + "pie_test_ext", /* Extension name */ + ext_functions, /* zend_function_entry */ + NULL, /* PHP_MINIT - Module initialization */ + NULL, /* PHP_MSHUTDOWN - Module shutdown */ + PHP_RINIT(pie_test_ext), /* PHP_RINIT - Request initialization */ + NULL, /* PHP_RSHUTDOWN - Request shutdown */ + PHP_MINFO(pie_test_ext), /* PHP_MINFO - Module info */ + PHP_PIE_TEST_EXT_VERSION, /* Version */ + STANDARD_MODULE_PROPERTIES +}; +/* }}} */ + +#ifdef COMPILE_DL_PIE_TEST_EXT +# ifdef ZTS +ZEND_TSRMLS_CACHE_DEFINE() +# endif +ZEND_GET_MODULE(pie_test_ext) +#endif diff --git a/test/assets/pie_test_ext/pie_test_ext.stub.php b/test/assets/pie_test_ext/pie_test_ext.stub.php new file mode 100644 index 0000000..fa91253 --- /dev/null +++ b/test/assets/pie_test_ext/pie_test_ext.stub.php @@ -0,0 +1,8 @@ + +--EXPECT-- +The extension "pie_test_ext" is available diff --git a/test/assets/pie_test_ext/tests/002.phpt b/test/assets/pie_test_ext/tests/002.phpt new file mode 100644 index 0000000..4f037c4 --- /dev/null +++ b/test/assets/pie_test_ext/tests/002.phpt @@ -0,0 +1,13 @@ +--TEST-- +test1() Basic test +--EXTENSIONS-- +pie_test_ext +--FILE-- + +--EXPECT-- +The extension pie_test_ext is loaded and working! +NULL diff --git a/test/integration/Building/UnixBuildTest.php b/test/integration/Building/UnixBuildTest.php new file mode 100644 index 0000000..08fe182 --- /dev/null +++ b/test/integration/Building/UnixBuildTest.php @@ -0,0 +1,69 @@ + 'enable-pie_test_ext'])], + ), + self::TEST_EXTENSION_PATH, + ); + + $unixBuilder = new UnixBuild(); + $unixBuilder->__invoke( + $downloadedPackage, + TargetPlatform::fromPhpBinaryPath(PhpBinaryPath::fromCurrentProcess()), + ['--enable-pie_test_ext'], + $output, + ); + + $outputString = $output->fetch(); + + self::assertStringContainsString('phpize complete.', $outputString); + self::assertStringContainsString('Configure complete with options: --enable-pie_test_ext', $outputString); + self::assertStringContainsString('Build complete:', $outputString); + self::assertStringContainsString('pie_test_ext.so', $outputString); + + self::assertSame( + 0, + (new Process(['make', 'test'], $downloadedPackage->extractedSourcePath)) + ->mustRun() + ->getExitCode(), + ); + + (new Process(['make', 'clean'], $downloadedPackage->extractedSourcePath))->mustRun(); + (new Process(['phpize', '--clean'], $downloadedPackage->extractedSourcePath))->mustRun(); + } +} diff --git a/test/integration/Command/BuildCommandTest.php b/test/integration/Command/BuildCommandTest.php new file mode 100644 index 0000000..98a80d5 --- /dev/null +++ b/test/integration/Command/BuildCommandTest.php @@ -0,0 +1,53 @@ +commandTester = new CommandTester(Container::factory()->get(BuildCommand::class)); + } + + public function testBuildCommandWillBuildTheExtension(): void + { + if (PHP_VERSION_ID < 80300 || PHP_VERSION_ID >= 80400) { + self::markTestSkipped('This test can only run on PHP 8.3 - you are running ' . PHP_VERSION); + } + + $this->commandTester->execute(['requested-package-and-version' => self::TEST_PACKAGE]); + + $this->commandTester->assertCommandIsSuccessful(); + + $outputString = $this->commandTester->getDisplay(); + + self::assertStringContainsString('Found package: asgrim/example-pie-extension:1.0.1 which provides ext-example_pie_extension', $outputString); + + if (Platform::isWindows()) { + self::assertStringContainsString('Nothing to do on Windows.', $outputString); + + return; + } + + self::assertStringContainsString('phpize complete.', $outputString); + self::assertStringContainsString('Configure complete.', $outputString); + self::assertStringContainsString('Build complete:', $outputString); + } +} diff --git a/test/integration/Command/DownloadCommandTest.php b/test/integration/Command/DownloadCommandTest.php index 7297ed4..80e8804 100644 --- a/test/integration/Command/DownloadCommandTest.php +++ b/test/integration/Command/DownloadCommandTest.php @@ -4,6 +4,7 @@ namespace Php\PieIntegrationTest\Command; +use Composer\Util\Platform; use Php\Pie\Command\DownloadCommand; use Php\Pie\Container; use Php\Pie\DependencyResolver\UnableToResolveRequirement; @@ -45,7 +46,7 @@ public static function validVersionsList(): array [self::TEST_PACKAGE . ':1.0.1-alpha.3@alpha', self::TEST_PACKAGE . ':1.0.1-alpha.3'], [self::TEST_PACKAGE . ':*', self::TEST_PACKAGE . ':1.0.1'], [self::TEST_PACKAGE . ':~1.0.0@alpha', self::TEST_PACKAGE . ':1.0.1'], - [self::TEST_PACKAGE . ':^1.1.0@alpha', self::TEST_PACKAGE . ':1.1.0-alpha.1'], + [self::TEST_PACKAGE . ':^1.1.0@alpha', self::TEST_PACKAGE . ':1.1.0-alpha.4'], [self::TEST_PACKAGE . ':~1.0.0', self::TEST_PACKAGE . ':1.0.1'], // @todo https://github.com/php/pie/issues/13 - in theory, these could work, on NonWindows at least // [self::TEST_PACKAGE . ':dev-main', self::TEST_PACKAGE . ':???'], @@ -77,6 +78,10 @@ public function testDownloadCommandWillDownloadCompatibleExtension(string $reque #[DataProvider('validVersionsList')] public function testDownloadingWithPhpConfig(string $requestedVersion, string $expectedVersion): void { + if (! Platform::isWindows()) { + self::markTestSkipped('This test can only run on Windows'); + } + // @todo This test makes an assumption you're using `ppa:ondrej/php` to have multiple PHP versions. This allows // us to test scenarios where you run with PHP 8.1 but want to install to a PHP 8.3 instance, for example. // However, this test isn't very portable, and won't run in CI, so we could do with improving this later. @@ -101,10 +106,11 @@ public function testDownloadingWithPhpConfig(string $requestedVersion, string $e #[DataProvider('validVersionsList')] public function testDownloadingWithPhpPath(string $requestedVersion, string $expectedVersion): void { - // @todo This test makes an assumption you're using `ppa:ondrej/php` to have multiple PHP versions. This allows - // us to test scenarios where you run with PHP 8.1 but want to install to a PHP 8.3 instance, for example. - // However, this test isn't very portable, and won't run in CI, so we could do with improving this later. - $phpBinaryPath = '/usr/bin/php8.3'; + if (! Platform::isWindows()) { + self::markTestSkipped('This test can only run on Windows'); + } + + $phpBinaryPath = 'C:\php-8.3.6\php.exe'; if (! file_exists($phpBinaryPath) || ! is_executable($phpBinaryPath)) { self::markTestSkipped('This test can only run where "' . $phpBinaryPath . '" exists and is executable, to target PHP 8.3'); diff --git a/test/unit/Command/CommandHelperTest.php b/test/unit/Command/CommandHelperTest.php new file mode 100644 index 0000000..3bf0a2b --- /dev/null +++ b/test/unit/Command/CommandHelperTest.php @@ -0,0 +1,206 @@ + + * + * @psalm-suppress PossiblyUnusedMethod https://github.com/psalm/psalm-plugin-phpunit/issues/131 + */ + public static function validPackageAndVersions(): array + { + $packages = [ + ['php/test-pie-ext', 'php/test-pie-ext', null], + ['php/test-pie-ext:^1.2', 'php/test-pie-ext', '^1.2'], + ['php/test-pie-ext:@alpha', 'php/test-pie-ext', '@alpha'], + ['php/test-pie-ext:~1.2.1', 'php/test-pie-ext', '~1.2.1'], + ['php/test-pie-ext:*', 'php/test-pie-ext', '*'], + ['php/test-pie-ext:1.2.3', 'php/test-pie-ext', '1.2.3'], + ]; + + return array_combine( + array_map(static fn (array $data) => $data[0], $packages), + $packages, + ); + } + + #[DataProvider('validPackageAndVersions')] + public function testRequestedNameAndVersionPair(string $requestedPackageAndVersion, string $expectedPackage, string|null $expectedVersion): void + { + $input = $this->createMock(InputInterface::class); + + $input->expects(self::once()) + ->method('getArgument') + ->with('requested-package-and-version') + ->willReturn($requestedPackageAndVersion); + + self::assertSame( + [ + 'name' => $expectedPackage, + 'version' => $expectedVersion, + ], + CommandHelper::requestedNameAndVersionPair($input), + ); + } + + public function testInvalidRequestedNameAndVersionPairThrowsExceptionWhenNoPackageProvided(): void + { + $input = $this->createMock(InputInterface::class); + + $input->expects(self::once()) + ->method('getArgument') + ->with('requested-package-and-version') + ->willReturn(null); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('No package was requested for installation'); + CommandHelper::requestedNameAndVersionPair($input); + } + + public function testDownloadPackage(): void + { + $dependencyResolver = $this->createMock(DependencyResolver::class); + $targetPlatform = TargetPlatform::fromPhpBinaryPath(PhpBinaryPath::fromCurrentProcess()); + $requestedNameAndVersionPair = ['name' => 'php/test-pie-ext', 'version' => '^1.2']; + $downloadAndExtract = $this->createMock(DownloadAndExtract::class); + $output = $this->createMock(OutputInterface::class); + + $dependencyResolver->expects(self::once()) + ->method('__invoke') + ->with( + $targetPlatform, + ) + ->willReturn($package = new Package( + ExtensionName::normaliseFromString('test_pie_ext'), + 'php/test-pie-ext', + '1.2.3', + 'https://test-uri/', + [], + )); + + $downloadAndExtract->expects(self::once()) + ->method('__invoke') + ->with( + $targetPlatform, + $package, + ) + ->willReturn($downloadedPackage = DownloadedPackage::fromPackageAndExtractedPath( + $package, + '/foo/bar', + )); + + $output->expects(self::once()) + ->method('writeln') + ->with(self::stringContains('Found package: php/test-pie-ext:1.2.3 which provides ext-test_pie_ext')); + + self::assertSame($downloadedPackage, CommandHelper::downloadPackage( + $dependencyResolver, + $targetPlatform, + $requestedNameAndVersionPair, + $downloadAndExtract, + $output, + )); + } + + public function testBindingConfigurationOptionsFromPackage(): void + { + self::markTestIncomplete(__METHOD__); + } + + public function testProcessingConfigureOptionsFromInput(): void + { + $package = new Package( + ExtensionName::normaliseFromString('lolz'), + 'foo/bar', + '1.0.0', + null, + [ + ConfigureOption::fromComposerJsonDefinition([ + 'name' => 'with-stuff', + 'needs-value' => true, + ]), + ConfigureOption::fromComposerJsonDefinition(['name' => 'enable-thing']), + ], + ); + $inputDefinition = new InputDefinition(); + $inputDefinition->addOption(new InputOption('with-stuff', null, InputOption::VALUE_REQUIRED)); + $inputDefinition->addOption(new InputOption('enable-thing', null, InputOption::VALUE_NONE)); + + $input = new ArrayInput(['--with-stuff' => 'lolz', '--enable-thing' => true], $inputDefinition); + + $options = CommandHelper::processConfigureOptionsFromInput($package, $input); + + self::assertSame( + [ + '--with-stuff=' . escapeshellarg('lolz'), + '--enable-thing', + ], + $options, + ); + } + + public function testWindowsMachinesCannotUseWithPhpConfigOption(): void + { + if (! Platform::isWindows()) { + self::markTestSkipped('This test can only run on Windows'); + } + + $command = new Command(); + $input = new ArrayInput(['--with-php-config' => 'C:\path\to\php-config']); + $output = new NullOutput(); + CommandHelper::configureOptions($command); + CommandHelper::validateInput($input, $command); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The --with-php-config=/path/to/php-config cannot be used on Windows, use --with-php-path=/path/to/php instead.'); + CommandHelper::determineTargetPlatformFromInputs($input, $output); + } + + public function testNonWindowsMachinesCannotUseWithPhpPathOption(): void + { + if (Platform::isWindows()) { + self::markTestSkipped('This test can only run on non-Windows'); + } + + $command = new Command(); + $input = new ArrayInput(['--with-php-path' => '/usr/bin/php']); + $output = new NullOutput(); + CommandHelper::configureOptions($command); + CommandHelper::validateInput($input, $command); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The --with-php-path=/path/to/php cannot be used on non-Windows, use --with-php-config=/path/to/php-config instead.'); + CommandHelper::determineTargetPlatformFromInputs($input, $output); + } +} diff --git a/test/unit/ConfigureOptionTest.php b/test/unit/ConfigureOptionTest.php new file mode 100644 index 0000000..b35c56c --- /dev/null +++ b/test/unit/ConfigureOptionTest.php @@ -0,0 +1,117 @@ +, + * expectedName: string, + * expectedNeedsValue: bool, + * expectedDescription: string, + * } + * > + * + * @psalm-suppress PossiblyUnusedMethod https://github.com/psalm/psalm-plugin-phpunit/issues/131 + */ + public static function composerJsonDefinitions(): array + { + return [ + 'minimal' => [ + 'definition' => ['name' => 'foo'], + 'expectedName' => 'foo', + 'expectedNeedsValue' => false, + 'expectedDescription' => '', + ], + 'full' => [ + 'definition' => [ + 'name' => 'foo', + 'needs-value' => false, + 'description' => 'Some option', + ], + 'expectedName' => 'foo', + 'expectedNeedsValue' => false, + 'expectedDescription' => 'Some option', + ], + 'needs-value' => [ + 'definition' => [ + 'name' => 'foo', + 'needs-value' => true, + 'description' => 'Some option', + ], + 'expectedName' => 'foo', + 'expectedNeedsValue' => true, + 'expectedDescription' => 'Some option', + ], + 'empty-description-allowed' => [ + 'definition' => [ + 'name' => 'foo', + 'needs-value' => false, + 'description' => '', + ], + 'expectedName' => 'foo', + 'expectedNeedsValue' => false, + 'expectedDescription' => '', + ], + ]; + } + + /** @param array $definition */ + #[DataProvider('composerJsonDefinitions')] + public function testCanBeConstructedFromValidComposerJsonDefinition(array $definition, string $expectedName, bool $expectedNeedsValue, string $expectedDescription): void + { + $configureOption = ConfigureOption::fromComposerJsonDefinition($definition); + + self::assertSame($expectedName, $configureOption->name); + self::assertSame($expectedNeedsValue, $configureOption->needsValue); + self::assertSame($expectedDescription, $configureOption->description); + } + + /** + * @return array< + * non-empty-string, + * array{ + * definition: array + * } + * > + * + * @psalm-suppress PossiblyUnusedMethod https://github.com/psalm/psalm-plugin-phpunit/issues/131 + */ + public static function invalidComposerJsonDefinitions(): array + { + return [ + 'no-keys' => [ + 'definition' => [], + ], + 'empty-name' => [ + 'definition' => ['name' => ''], + ], + 'needs-value-invalid-type' => [ + 'definition' => ['name' => 'foo', 'needs-value' => 'true'], + ], + 'description-invalid-type' => [ + 'definition' => ['name' => 'foo', 'description' => 123], + ], + ]; + } + + /** @param array $definition */ + #[DataProvider('invalidComposerJsonDefinitions')] + public function testInvalidComposerJsonDefinitionsAreRejected(array $definition): void + { + $this->expectException(InvalidArgumentException::class); + ConfigureOption::fromComposerJsonDefinition($definition); + } +} diff --git a/test/unit/Downloading/AddAuthenticationHeaderTest.php b/test/unit/Downloading/AddAuthenticationHeaderTest.php index 854773c..6465c31 100644 --- a/test/unit/Downloading/AddAuthenticationHeaderTest.php +++ b/test/unit/Downloading/AddAuthenticationHeaderTest.php @@ -32,7 +32,13 @@ public function testAuthorizationHeaderIsAdded(): void $requestWithAuthHeader = (new AddAuthenticationHeader())->withAuthHeaderFromComposer( $request, - new Package(ExtensionName::normaliseFromString('foo'), 'foo/bar', '1.2.3', $downloadUrl), + new Package( + ExtensionName::normaliseFromString('foo'), + 'foo/bar', + '1.2.3', + $downloadUrl, + [], + ), $authHelper, ); @@ -48,7 +54,13 @@ public function testExceptionIsThrownWhenPackageDoesNotHaveDownloadUrl(): void $authHelper = $this->createMock(AuthHelper::class); $addAuthenticationHeader = new AddAuthenticationHeader(); - $package = new Package(ExtensionName::normaliseFromString('foo'), 'foo/bar', '1.2.3', null); + $package = new Package( + ExtensionName::normaliseFromString('foo'), + 'foo/bar', + '1.2.3', + null, + [], + ); $this->expectException(RuntimeException::class); $this->expectExceptionMessage('The package foo/bar does not have a download URL'); diff --git a/test/unit/Downloading/DownloadedPackageTest.php b/test/unit/Downloading/DownloadedPackageTest.php index 5ead146..078dd17 100644 --- a/test/unit/Downloading/DownloadedPackageTest.php +++ b/test/unit/Downloading/DownloadedPackageTest.php @@ -17,7 +17,13 @@ final class DownloadedPackageTest extends TestCase { public function testFromPackageAndExtractedPath(): void { - $package = new Package(ExtensionName::normaliseFromString('foo'), 'foo/bar', '1.2.3', null); + $package = new Package( + ExtensionName::normaliseFromString('foo'), + 'foo/bar', + '1.2.3', + null, + [], + ); $extractedSourcePath = uniqid('/path/to/downloaded/package', true); diff --git a/test/unit/Downloading/Exception/CouldNotFindReleaseAssetTest.php b/test/unit/Downloading/Exception/CouldNotFindReleaseAssetTest.php index b03a335..5e5e494 100644 --- a/test/unit/Downloading/Exception/CouldNotFindReleaseAssetTest.php +++ b/test/unit/Downloading/Exception/CouldNotFindReleaseAssetTest.php @@ -20,7 +20,13 @@ final class CouldNotFindReleaseAssetTest extends TestCase { public function testForPackage(): void { - $package = new Package(ExtensionName::normaliseFromString('foo'), 'foo/bar', '1.2.3', null); + $package = new Package( + ExtensionName::normaliseFromString('foo'), + 'foo/bar', + '1.2.3', + null, + [], + ); $exception = CouldNotFindReleaseAsset::forPackage($package, ['something.zip', 'something2.zip']); @@ -29,7 +35,13 @@ public function testForPackage(): void public function testForPackageWithMissingTag(): void { - $package = new Package(ExtensionName::normaliseFromString('foo'), 'foo/bar', '1.2.3', null); + $package = new Package( + ExtensionName::normaliseFromString('foo'), + 'foo/bar', + '1.2.3', + null, + [], + ); $exception = CouldNotFindReleaseAsset::forPackageWithMissingTag($package); diff --git a/test/unit/Downloading/GithubPackageReleaseAssetsTest.php b/test/unit/Downloading/GithubPackageReleaseAssetsTest.php index 7c7efd4..b37d1e5 100644 --- a/test/unit/Downloading/GithubPackageReleaseAssetsTest.php +++ b/test/unit/Downloading/GithubPackageReleaseAssetsTest.php @@ -66,7 +66,13 @@ public function testUrlIsReturnedWhenFindingWindowsDownloadUrl(): void $guzzleMockClient = new Client(['handler' => HandlerStack::create($mockHandler)]); - $package = new Package(ExtensionName::normaliseFromString('foo'), 'asgrim/example-pie-extension', '1.2.3', 'https://test-uri/' . uniqid('downloadUrl', true)); + $package = new Package( + ExtensionName::normaliseFromString('foo'), + 'asgrim/example-pie-extension', + '1.2.3', + 'https://test-uri/' . uniqid('downloadUrl', true), + [], + ); $releaseAssets = new GithubPackageReleaseAssets($authHelper, $guzzleMockClient, 'https://test-github-api-base-url.thephp.foundation'); @@ -111,7 +117,13 @@ public function testUrlIsReturnedWhenFindingWindowsDownloadUrlWithCompilerAndThr $guzzleMockClient = new Client(['handler' => HandlerStack::create($mockHandler)]); - $package = new Package(ExtensionName::normaliseFromString('foo'), 'asgrim/example-pie-extension', '1.2.3', 'https://test-uri/' . uniqid('downloadUrl', true)); + $package = new Package( + ExtensionName::normaliseFromString('foo'), + 'asgrim/example-pie-extension', + '1.2.3', + 'https://test-uri/' . uniqid('downloadUrl', true), + [], + ); $releaseAssets = new GithubPackageReleaseAssets($authHelper, $guzzleMockClient, 'https://test-github-api-base-url.thephp.foundation'); @@ -142,7 +154,13 @@ public function testFindWindowsDownloadUrlForPackageThrowsExceptionWhenAssetNotF $guzzleMockClient = new Client(['handler' => HandlerStack::create($mockHandler)]); - $package = new Package(ExtensionName::normaliseFromString('foo'), 'asgrim/example-pie-extension', '1.2.3', 'https://test-uri/' . uniqid('downloadUrl', true)); + $package = new Package( + ExtensionName::normaliseFromString('foo'), + 'asgrim/example-pie-extension', + '1.2.3', + 'https://test-uri/' . uniqid('downloadUrl', true), + [], + ); $releaseAssets = new GithubPackageReleaseAssets($authHelper, $guzzleMockClient, 'https://test-github-api-base-url.thephp.foundation'); diff --git a/test/unit/Downloading/UnixDownloadAndExtractTest.php b/test/unit/Downloading/UnixDownloadAndExtractTest.php index f0bc4da..c75ab9c 100644 --- a/test/unit/Downloading/UnixDownloadAndExtractTest.php +++ b/test/unit/Downloading/UnixDownloadAndExtractTest.php @@ -59,7 +59,13 @@ public function testInvoke(): void ->willReturn($extractedPath); $downloadUrl = 'https://test-uri/' . uniqid('downloadUrl', true); - $requestedPackage = new Package(ExtensionName::normaliseFromString('foo'), 'foo/bar', '1.2.3', $downloadUrl); + $requestedPackage = new Package( + ExtensionName::normaliseFromString('foo'), + 'foo/bar', + '1.2.3', + $downloadUrl, + [], + ); $downloadedPackage = $unixDownloadAndExtract->__invoke($targetPlatform, $requestedPackage); diff --git a/test/unit/Downloading/WindowsDownloadAndExtractTest.php b/test/unit/Downloading/WindowsDownloadAndExtractTest.php index 448bdd5..e413204 100644 --- a/test/unit/Downloading/WindowsDownloadAndExtractTest.php +++ b/test/unit/Downloading/WindowsDownloadAndExtractTest.php @@ -74,7 +74,13 @@ public function testInvoke(): void ) ->willReturn($extractedPath); - $requestedPackage = new Package(ExtensionName::normaliseFromString('foo'), 'foo/bar', '1.2.3', 'https://test-uri/' . uniqid('downloadUrl', true)); + $requestedPackage = new Package( + ExtensionName::normaliseFromString('foo'), + 'foo/bar', + '1.2.3', + 'https://test-uri/' . uniqid('downloadUrl', true), + [], + ); $downloadedPackage = $windowsDownloadAndExtract->__invoke($targetPlatform, $requestedPackage); diff --git a/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php b/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php index 44ba903..03f8f62 100644 --- a/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php +++ b/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php @@ -33,10 +33,13 @@ final class PhpBinaryPathTest extends TestCase { public function testVersionFromCurrentProcess(): void { + $phpBinary = PhpBinaryPath::fromCurrentProcess(); + self::assertSame( sprintf('%s.%s.%s', PHP_MAJOR_VERSION, PHP_MINOR_VERSION, PHP_RELEASE_VERSION), - PhpBinaryPath::fromCurrentProcess()->version(), + $phpBinary->version(), ); + self::assertNull($phpBinary->phpConfigPath()); } public function testFromPhpConfigExecutable(): void @@ -45,7 +48,7 @@ public function testFromPhpConfigExecutable(): void $exitCode = $process->run(); $phpConfigExecutable = trim($process->getOutput()); - if ($exitCode !== 0 || ! file_exists($phpConfigExecutable) || ! is_executable($phpConfigExecutable)) { + if ($exitCode !== 0 || ! file_exists($phpConfigExecutable) || ! is_executable($phpConfigExecutable) || $phpConfigExecutable === '') { self::markTestSkipped('Needs php-config in path to run this test'); } @@ -59,6 +62,8 @@ public function testFromPhpConfigExecutable(): void sprintf('%s.%s.%s', PHP_MAJOR_VERSION, PHP_MINOR_VERSION, PHP_RELEASE_VERSION), $phpBinary->version(), ); + + self::assertSame($phpConfigExecutable, $phpBinary->phpConfigPath()); } public function testExtensions(): void