From cd723fdfb70c70635ba05522c138f9bbbc6549ae Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Thu, 12 Feb 2026 09:12:12 +0000 Subject: [PATCH] Prefix INSTALL_ROOT env var for UnixInstall --- src/Installing/UnixInstall.php | 11 ++++++- src/Platform/TargetPhp/PhpBinaryPath.php | 8 ++++- src/Util/Process.php | 4 ++- .../Installing/UnixInstallTest.php | 24 ++++++++++++--- .../Platform/TargetPhp/PhpBinaryPathTest.php | 29 +++++++++++++++++++ 5 files changed, 69 insertions(+), 7 deletions(-) diff --git a/src/Installing/UnixInstall.php b/src/Installing/UnixInstall.php index a58c14db..0084655b 100644 --- a/src/Installing/UnixInstall.php +++ b/src/Installing/UnixInstall.php @@ -5,6 +5,7 @@ namespace Php\Pie\Installing; use Composer\IO\IOInterface; +use Composer\Util\Platform as ComposerPlatform; use Php\Pie\Downloading\DownloadedPackage; use Php\Pie\Downloading\DownloadUrlMethod; use Php\Pie\File\BinaryFile; @@ -37,7 +38,14 @@ public function __invoke( IOInterface $io, bool $attemptToSetupIniFile, ): BinaryFile { - $targetExtensionPath = $targetPlatform->phpBinaryPath->extensionPath(); + $env = []; + $installRoot = (string) ComposerPlatform::getEnv('INSTALL_ROOT'); + if ($installRoot !== '') { + $io->write(sprintf('Using INSTALL_ROOT=%s', $installRoot)); + $env['INSTALL_ROOT'] = $installRoot; + } + + $targetExtensionPath = $targetPlatform->phpBinaryPath->extensionPath($installRoot); $sharedObjectName = $downloadedPackage->package->extensionName()->name() . '.so'; $expectedSharedObjectLocation = sprintf( @@ -93,6 +101,7 @@ public function __invoke( $installCommand, $downloadedPackage->extractedSourcePath, self::MAKE_INSTALL_TIMEOUT_SECS, + env: $env, ); $io->write($makeInstallOutput, verbosity: IOInterface::VERY_VERBOSE); diff --git a/src/Platform/TargetPhp/PhpBinaryPath.php b/src/Platform/TargetPhp/PhpBinaryPath.php index 32108921..f0f231c9 100644 --- a/src/Platform/TargetPhp/PhpBinaryPath.php +++ b/src/Platform/TargetPhp/PhpBinaryPath.php @@ -30,9 +30,11 @@ use function in_array; use function is_dir; use function is_executable; +use function ltrim; use function mkdir; use function preg_match; use function preg_replace; +use function rtrim; use function sprintf; use function strtolower; use function trim; @@ -103,7 +105,7 @@ public function debugMode(): DebugBuild } /** @return non-empty-string */ - public function extensionPath(): string + public function extensionPath(string|null $prefixInstallRoot = null): string { $phpinfo = $this->phpinfo(); @@ -116,6 +118,10 @@ public function extensionPath(): string $extensionPath = trim($matches[1]); assert($extensionPath !== ''); + if (self::operatingSystem() !== OperatingSystem::Windows && $prefixInstallRoot !== null && $prefixInstallRoot !== '') { + $extensionPath = rtrim($prefixInstallRoot, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . ltrim($extensionPath, DIRECTORY_SEPARATOR); + } + if (file_exists($extensionPath) && is_dir($extensionPath)) { return $extensionPath; } diff --git a/src/Util/Process.php b/src/Util/Process.php index 329ce5f2..8e32df16 100644 --- a/src/Util/Process.php +++ b/src/Util/Process.php @@ -30,6 +30,7 @@ private function __construct() * * @param list $command * @param callable(SymfonyProcess::ERR|SymfonyProcess::OUT, string): void|null $outputCallback + * @param array|null $env * * @throws ProcessFailedException */ @@ -38,8 +39,9 @@ public static function run( string|null $workingDirectory = null, int|null $timeout = self::NO_TIMEOUT, callable|null $outputCallback = null, + array|null $env = null, ): string { - return trim((new SymfonyProcess($command, $workingDirectory, timeout: $timeout)) + return trim((new SymfonyProcess($command, $workingDirectory, $env, timeout: $timeout)) ->mustRun($outputCallback) ->getOutput()); } diff --git a/test/integration/Installing/UnixInstallTest.php b/test/integration/Installing/UnixInstallTest.php index f80e21d8..820146ed 100644 --- a/test/integration/Installing/UnixInstallTest.php +++ b/test/integration/Installing/UnixInstallTest.php @@ -6,6 +6,7 @@ use Composer\IO\BufferIO; use Composer\Package\CompletePackageInterface; +use Composer\Util\Filesystem; use Composer\Util\Platform; use Php\Pie\Building\UnixBuild; use Php\Pie\DependencyResolver\Package; @@ -30,11 +31,13 @@ use function array_unshift; use function assert; use function file_exists; +use function getenv; use function is_executable; use function is_writable; use function mkdir; +use function putenv; use function rename; -use function unlink; +use function uniqid; use const DIRECTORY_SEPARATOR; @@ -79,6 +82,10 @@ public static function phpPathProvider(): array #[DataProvider('phpPathProvider')] public function testUnixInstallCanInstallExtensionBuiltFromSource(string $phpConfig): void { + $installRoot = '/tmp/' . uniqid('pie-test-install-root-', true); + $oldInstallRoot = getenv('INSTALL_ROOT'); + putenv('INSTALL_ROOT=' . $installRoot); + assert($phpConfig !== ''); if (Platform::isWindows()) { self::markTestSkipped('Unix build test cannot be run on Windows'); @@ -86,7 +93,7 @@ public function testUnixInstallCanInstallExtensionBuiltFromSource(string $phpCon $output = new BufferIO(); $targetPlatform = TargetPlatform::fromPhpBinaryPath(PhpBinaryPath::fromPhpConfigExecutable($phpConfig), null); - $extensionPath = $targetPlatform->phpBinaryPath->extensionPath(); + $extensionPath = $targetPlatform->phpBinaryPath->extensionPath($installRoot); $composerPackage = $this->createMock(CompletePackageInterface::class); $composerPackage @@ -136,11 +143,17 @@ public function testUnixInstallCanInstallExtensionBuiltFromSource(string $phpCon (new Process($rmCommand))->mustRun(); (new Process(['make', 'clean'], $downloadedPackage->extractedSourcePath))->mustRun(); (new Process(['phpize', '--clean'], $downloadedPackage->extractedSourcePath))->mustRun(); + (new Filesystem())->remove($installRoot); + putenv('INSTALL_ROOT=' . $oldInstallRoot); } #[DataProvider('phpPathProvider')] public function testUnixInstallCanInstallPrePackagedBinary(string $phpConfig): void { + $installRoot = '/tmp/' . uniqid('pie-test-install-root-', true); + $oldInstallRoot = getenv('INSTALL_ROOT'); + putenv('INSTALL_ROOT=' . $installRoot); + assert($phpConfig !== ''); if (Platform::isWindows()) { self::markTestSkipped('Unix build test cannot be run on Windows'); @@ -148,7 +161,8 @@ public function testUnixInstallCanInstallPrePackagedBinary(string $phpConfig): v $output = new BufferIO(); $targetPlatform = TargetPlatform::fromPhpBinaryPath(PhpBinaryPath::fromPhpConfigExecutable($phpConfig), null); - $extensionPath = $targetPlatform->phpBinaryPath->extensionPath(); + $extensionPath = $installRoot . $targetPlatform->phpBinaryPath->extensionPath(); + mkdir($extensionPath, 0777, true); // First build it (otherwise the test assets would need to have a binary for every test platform...) $composerPackage = $this->createMock(CompletePackageInterface::class); @@ -220,6 +234,8 @@ public function testUnixInstallCanInstallPrePackagedBinary(string $phpConfig): v } (new Process($rmCommand))->mustRun(); - unlink($prebuiltBinaryFile->filePath); + (new Filesystem())->remove($prebuiltBinaryFile->filePath); + (new Filesystem())->remove($installRoot); + putenv('INSTALL_ROOT=' . $oldInstallRoot); } } diff --git a/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php b/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php index 321ac7d8..eeeda371 100644 --- a/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php +++ b/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php @@ -276,6 +276,22 @@ public function testExtensionPathOnLinuxThatAlreadyExists(): void ); } + #[RequiresOperatingSystemFamily('Linux')] + public function testExtensionPathWithInstallRootPrefixOnLinuxThatAlreadyExists(): void + { + $installRoot = '/tmp/' . uniqid('pie-test-install-root-existing-', true); + $phpBinary = PhpBinaryPath::fromCurrentProcess(); + + $expectedExtensionDir = $installRoot . ini_get('extension_dir'); + mkdir($expectedExtensionDir, 0777, true); + self::assertDirectoryExists($expectedExtensionDir); + + self::assertSame( + $expectedExtensionDir, + $phpBinary->extensionPath($installRoot), + ); + } + #[RequiresOperatingSystemFamily('Windows')] public function testExtensionPathOnWindows(): void { @@ -336,6 +352,19 @@ public function testExtensionPathIsImplicitlyCreated(): void self::assertDirectoryExists($configuredExtensionPath); } + #[RequiresOperatingSystemFamily('Linux')] + public function testExtensionPathWithInstallRootPrefixIsImplicitlyCreated(): void + { + $installRoot = '/tmp/' . uniqid('pie-test-install-root-not-existing-', true); + $phpBinary = PhpBinaryPath::fromCurrentProcess(); + + $expectedExtensionDir = $installRoot . ini_get('extension_dir'); + self::assertDirectoryDoesNotExist($expectedExtensionDir); + + self::assertSame($expectedExtensionDir, $phpBinary->extensionPath($installRoot)); + self::assertDirectoryExists($expectedExtensionDir); + } + /** @return array */ public static function phpPathProvider(): array {