From f87f32de16aa2a62aaa20c200fe3ff59265c47a4 Mon Sep 17 00:00:00 2001 From: gpb Date: Wed, 18 Feb 2026 23:57:11 +0800 Subject: [PATCH 1/2] Fix Windows extension install from downloads.php.net - Generate asset name variants for "x64" arch (not just "x86_64") and versions without "v" prefix - Add Architecture::windowsName() for conventional Windows arch labels - Fall back to downloads.php.net/~windows/pecl/releases/ when GitHub release has no matching Windows binary - Support simple DLL naming (e.g. "php_apcu.dll") used in downloads.php.net zips --- .../GithubPackageReleaseAssets.php | 70 +++++++++-- src/Platform/Architecture.php | 16 +++ src/Platform/WindowsExtensionAssetName.php | 76 +++++++---- .../GithubPackageReleaseAssetsTest.php | 119 +++++++++++++++++- test/unit/Platform/ArchitectureTest.php | 16 +++ .../WindowsExtensionAssetNameTest.php | 86 +++++++++++++ 6 files changed, 352 insertions(+), 31 deletions(-) diff --git a/src/Downloading/GithubPackageReleaseAssets.php b/src/Downloading/GithubPackageReleaseAssets.php index ad1bd13a..76c99627 100644 --- a/src/Downloading/GithubPackageReleaseAssets.php +++ b/src/Downloading/GithubPackageReleaseAssets.php @@ -12,6 +12,7 @@ use function array_map; use function in_array; +use function ltrim; use function strtolower; /** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */ @@ -34,15 +35,28 @@ public function findMatchingReleaseAssetUrl( DownloadUrlMethod $downloadUrlMethod, array $possibleReleaseAssetNames, ): string { - $releaseAsset = $this->selectMatchingReleaseAsset( - $targetPlatform, - $package, - $this->getReleaseAssetsForPackage($package, $httpDownloader, $downloadUrlMethod), - $downloadUrlMethod, - $possibleReleaseAssetNames, - ); + try { + $releaseAsset = $this->selectMatchingReleaseAsset( + $targetPlatform, + $package, + $this->getReleaseAssetsForPackage($package, $httpDownloader, $downloadUrlMethod), + $downloadUrlMethod, + $possibleReleaseAssetNames, + ); + + return $releaseAsset['browser_download_url']; + } catch (Exception\CouldNotFindReleaseAsset $githubException) { + // GitHub release had no matching asset — try downloads.php.net as a fallback + // for Windows binaries, since many PECL extensions publish prebuilt DLLs there. + if ($downloadUrlMethod === DownloadUrlMethod::WindowsBinaryDownload) { + $fallbackUrl = $this->tryPhpNetWindowsDownload($package, $httpDownloader, $possibleReleaseAssetNames); + if ($fallbackUrl !== null) { + return $fallbackUrl; + } + } - return $releaseAsset['browser_download_url']; + throw $githubException; + } } /** @link https://github.com/squizlabs/PHP_CodeSniffer/issues/3734 */ @@ -114,4 +128,44 @@ static function (array $asset): array { $decodedResponse['assets'], ); } + + /** + * Fallback: attempt to find a prebuilt Windows extension archive on + * downloads.php.net, which hosts PECL binaries that may not be attached + * to GitHub releases. + * + * URL pattern: https://downloads.php.net/~windows/pecl/releases/{ext}/{version}/{asset} + * + * @param non-empty-list $possibleReleaseAssetNames + * + * @return non-empty-string|null + */ + private function tryPhpNetWindowsDownload( + Package $package, + HttpDownloader $httpDownloader, + array $possibleReleaseAssetNames, + ): string|null { + $extName = $package->extensionName()->name(); + $versionWithoutV = ltrim($package->version(), 'vV'); + + foreach ($possibleReleaseAssetNames as $assetName) { + $url = 'https://downloads.php.net/~windows/pecl/releases/' + . $extName . '/' . $versionWithoutV . '/' . $assetName; + + try { + $response = $httpDownloader->get($url, [ + 'http' => ['method' => 'HEAD'], + ]); + + if ($response->getStatusCode() === 200) { + return $url; + } + } catch (TransportException) { + // Asset not found at this URL, try next variant + continue; + } + } + + return null; + } } diff --git a/src/Platform/Architecture.php b/src/Platform/Architecture.php index 75229135..5e3acf0a 100644 --- a/src/Platform/Architecture.php +++ b/src/Platform/Architecture.php @@ -24,4 +24,20 @@ public static function parseArchitecture(string $architecture): self default => self::x86, }; } + + /** + * Returns the conventional Windows architecture label, which may differ + * from the PHP enum case name (e.g. "x64" instead of "x86_64"). + * Used when matching asset filenames from sources like downloads.php.net. + * + * @return non-empty-string + */ + public function windowsName(): string + { + return match ($this) { + self::x86_64 => 'x64', + self::arm64 => 'arm64', + self::x86 => 'x86', + }; + } } diff --git a/src/Platform/WindowsExtensionAssetName.php b/src/Platform/WindowsExtensionAssetName.php index 5e836df3..ab4dd882 100644 --- a/src/Platform/WindowsExtensionAssetName.php +++ b/src/Platform/WindowsExtensionAssetName.php @@ -9,8 +9,11 @@ use Php\Pie\Downloading\Exception\CouldNotFindReleaseAsset; use RuntimeException; +use function array_unique; +use function array_values; use function file_exists; use function implode; +use function ltrim; use function sprintf; use function strtolower; @@ -31,29 +34,48 @@ private static function assetNames(TargetPlatform $targetPlatform, Package $pack /** * During development, we swapped compiler/ts around. It is fairly trivial to support both, so we can check * both formats pretty easily, just to avoid confusion for package maintainers... + * + * Additionally, some distributions (notably downloads.php.net) use the shorter Windows architecture + * label "x64" instead of "x86_64", and version strings without the "v" prefix (e.g. "5.1.28" instead + * of "v5.1.28"). We generate variants covering all combinations to match either convention. */ - return [ - strtolower(sprintf( - 'php_%s-%s-%s-%s-%s-%s.%s', - $package->extensionName()->name(), - $package->version(), - $targetPlatform->phpBinaryPath->majorMinorVersion(), - $targetPlatform->threadSafety->asShort(), - strtolower($targetPlatform->windowsCompiler->name), - $targetPlatform->architecture->name, - $fileExtension, - )), - strtolower(sprintf( - 'php_%s-%s-%s-%s-%s-%s.%s', - $package->extensionName()->name(), - $package->version(), - $targetPlatform->phpBinaryPath->majorMinorVersion(), - strtolower($targetPlatform->windowsCompiler->name), - $targetPlatform->threadSafety->asShort(), - $targetPlatform->architecture->name, - $fileExtension, - )), - ]; + $version = $package->version(); + $versionNoV = ltrim($version, 'vV'); + $versions = array_unique([$version, $versionNoV]); + $architectures = array_unique([ + $targetPlatform->architecture->name, + $targetPlatform->architecture->windowsName(), + ]); + + $names = []; + foreach ($versions as $ver) { + foreach ($architectures as $arch) { + // Format: {ts}-{compiler} (e.g. ts-vs17) + $names[] = strtolower(sprintf( + 'php_%s-%s-%s-%s-%s-%s.%s', + $package->extensionName()->name(), + $ver, + $targetPlatform->phpBinaryPath->majorMinorVersion(), + $targetPlatform->threadSafety->asShort(), + strtolower($targetPlatform->windowsCompiler->name), + $arch, + $fileExtension, + )); + // Format: {compiler}-{ts} (e.g. vs17-ts) — legacy/swapped ordering + $names[] = strtolower(sprintf( + 'php_%s-%s-%s-%s-%s-%s.%s', + $package->extensionName()->name(), + $ver, + $targetPlatform->phpBinaryPath->majorMinorVersion(), + strtolower($targetPlatform->windowsCompiler->name), + $targetPlatform->threadSafety->asShort(), + $arch, + $fileExtension, + )); + } + } + + return array_values(array_unique($names)); } /** @return non-empty-list */ @@ -79,6 +101,16 @@ public static function determineDllName(TargetPlatform $targetPlatform, Download } } + // Zips from downloads.php.net use a simple naming convention (e.g. "php_apcu.dll") + // without version/platform suffixes, so check for that as a fallback. + $simpleDllName = 'php_' . $package->package->extensionName()->name() . '.dll'; + $fullSimpleDllName = $package->extractedSourcePath . '/' . $simpleDllName; + if (file_exists($fullSimpleDllName)) { + return $fullSimpleDllName; + } + + $possibleDllNames[] = $simpleDllName; + throw new RuntimeException('Unable to find DLL for package, checked: ' . implode(', ', $possibleDllNames)); } } diff --git a/test/unit/Downloading/GithubPackageReleaseAssetsTest.php b/test/unit/Downloading/GithubPackageReleaseAssetsTest.php index 4002a366..225c0637 100644 --- a/test/unit/Downloading/GithubPackageReleaseAssetsTest.php +++ b/test/unit/Downloading/GithubPackageReleaseAssetsTest.php @@ -25,6 +25,7 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; +use function str_contains; use function uniqid; #[CoversClass(GithubPackageReleaseAssets::class)] @@ -179,7 +180,7 @@ public function testFindWindowsDownloadUrlForPackageThrowsExceptionWhenAssetNotF $httpDownloader = $this->createMock(HttpDownloader::class); $httpDownloader - ->expects(self::once()) + ->expects(self::atLeastOnce()) ->method('get') ->willThrowException($e); @@ -206,4 +207,120 @@ public function testFindWindowsDownloadUrlForPackageThrowsExceptionWhenAssetNotF ), ); } + + public function testFallsBackToPhpNetWhenGithubReleaseHasNoMatchingAsset(): void + { + $phpBinaryPath = $this->createMock(PhpBinaryPath::class); + $phpBinaryPath->expects(self::any()) + ->method('majorMinorVersion') + ->willReturn('8.5'); + + $targetPlatform = new TargetPlatform( + OperatingSystem::Windows, + OperatingSystemFamily::Windows, + $phpBinaryPath, + Architecture::x86_64, + ThreadSafetyMode::ThreadSafe, + 1, + WindowsCompiler::VS17, + ); + + // GitHub release exists but has no matching Windows asset + $githubResponse = $this->createMock(Response::class); + $githubResponse + ->method('decodeJson') + ->willReturn(['assets' => []]); + + // downloads.php.net HEAD response succeeds + $phpNetResponse = $this->createMock(Response::class); + $phpNetResponse + ->method('getStatusCode') + ->willReturn(200); + + $httpDownloader = $this->createMock(HttpDownloader::class); + $httpDownloader + ->method('get') + ->willReturnCallback(static function (string $url) use ($githubResponse, $phpNetResponse): Response { + if (str_contains($url, 'github')) { + return $githubResponse; + } + + // The fallback should hit downloads.php.net + self::assertStringStartsWith('https://downloads.php.net/~windows/pecl/releases/apcu/5.1.28/', $url); + + return $phpNetResponse; + }); + + $package = new Package( + $this->createMock(CompletePackageInterface::class), + ExtensionType::PhpModule, + ExtensionName::normaliseFromString('apcu'), + 'apcu/apcu', + 'v5.1.28', + 'https://test-uri/' . uniqid('downloadUrl', true), + ); + + $releaseAssets = new GithubPackageReleaseAssets('https://test-github-api-base-url.thephp.foundation'); + + $url = $releaseAssets->findMatchingReleaseAssetUrl( + $targetPlatform, + $package, + $httpDownloader, + DownloadUrlMethod::WindowsBinaryDownload, + WindowsExtensionAssetName::zipNames($targetPlatform, $package), + ); + + self::assertStringStartsWith('https://downloads.php.net/~windows/pecl/releases/apcu/5.1.28/', $url); + } + + public function testPhpNetFallbackIsNotAttemptedForNonWindowsDownloadMethods(): void + { + $phpBinaryPath = $this->createMock(PhpBinaryPath::class); + $phpBinaryPath->expects(self::any()) + ->method('majorMinorVersion') + ->willReturn('8.5'); + + $targetPlatform = new TargetPlatform( + OperatingSystem::NonWindows, + OperatingSystemFamily::Linux, + $phpBinaryPath, + Architecture::x86_64, + ThreadSafetyMode::ThreadSafe, + 1, + null, + ); + + // GitHub release exists but has no matching asset + $githubResponse = $this->createMock(Response::class); + $githubResponse + ->method('decodeJson') + ->willReturn(['assets' => []]); + + // Only one HTTP call should be made (to GitHub) — no fallback to downloads.php.net + $httpDownloader = $this->createMock(HttpDownloader::class); + $httpDownloader + ->expects(self::once()) + ->method('get') + ->willReturn($githubResponse); + + $package = new Package( + $this->createMock(CompletePackageInterface::class), + ExtensionType::PhpModule, + ExtensionName::normaliseFromString('foo'), + 'asgrim/example-pie-extension', + 'v1.2.3', + 'https://test-uri/' . uniqid('downloadUrl', true), + ); + + $releaseAssets = new GithubPackageReleaseAssets('https://test-github-api-base-url.thephp.foundation'); + + $this->expectException(CouldNotFindReleaseAsset::class); + $releaseAssets->findMatchingReleaseAssetUrl( + $targetPlatform, + $package, + $httpDownloader, + DownloadUrlMethod::PrePackagedSourceDownload, + ['foo-v1.2.3.tgz'], + ); + } } diff --git a/test/unit/Platform/ArchitectureTest.php b/test/unit/Platform/ArchitectureTest.php index 1b45aa82..62f83a00 100644 --- a/test/unit/Platform/ArchitectureTest.php +++ b/test/unit/Platform/ArchitectureTest.php @@ -32,4 +32,20 @@ public function testParseArchitecture(string $architectureString, Architecture $ { self::assertSame($expectedArchitecture, Architecture::parseArchitecture($architectureString)); } + + /** @return array */ + public static function windowsNameProvider(): array + { + return [ + 'x86_64 => x64' => [Architecture::x86_64, 'x64'], + 'arm64 => arm64' => [Architecture::arm64, 'arm64'], + 'x86 => x86' => [Architecture::x86, 'x86'], + ]; + } + + #[DataProvider('windowsNameProvider')] + public function testWindowsName(Architecture $architecture, string $expectedWindowsName): void + { + self::assertSame($expectedWindowsName, $architecture->windowsName()); + } } diff --git a/test/unit/Platform/WindowsExtensionAssetNameTest.php b/test/unit/Platform/WindowsExtensionAssetNameTest.php index 446b8a3d..3b12b1b4 100644 --- a/test/unit/Platform/WindowsExtensionAssetNameTest.php +++ b/test/unit/Platform/WindowsExtensionAssetNameTest.php @@ -6,6 +6,7 @@ use Composer\Package\CompletePackageInterface; use Php\Pie\DependencyResolver\Package; +use Php\Pie\Downloading\DownloadedPackage; use Php\Pie\ExtensionName; use Php\Pie\ExtensionType; use Php\Pie\Platform\Architecture; @@ -18,6 +19,12 @@ use Php\Pie\Platform\WindowsExtensionAssetName; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; +use RuntimeException; + +use function file_put_contents; +use function mkdir; +use function sys_get_temp_dir; +use function uniqid; #[CoversClass(WindowsExtensionAssetName::class)] final class WindowsExtensionAssetNameTest extends TestCase @@ -58,6 +65,8 @@ public function testZipNames(): void [ 'php_foo-1.2.3-' . $this->phpVersion . '-ts-vc14-x86_64.zip', 'php_foo-1.2.3-' . $this->phpVersion . '-vc14-ts-x86_64.zip', + 'php_foo-1.2.3-' . $this->phpVersion . '-ts-vc14-x64.zip', + 'php_foo-1.2.3-' . $this->phpVersion . '-vc14-ts-x64.zip', ], WindowsExtensionAssetName::zipNames($this->platform, $this->package), ); @@ -69,8 +78,85 @@ public function testDllNames(): void [ 'php_foo-1.2.3-' . $this->phpVersion . '-ts-vc14-x86_64.dll', 'php_foo-1.2.3-' . $this->phpVersion . '-vc14-ts-x86_64.dll', + 'php_foo-1.2.3-' . $this->phpVersion . '-ts-vc14-x64.dll', + 'php_foo-1.2.3-' . $this->phpVersion . '-vc14-ts-x64.dll', ], WindowsExtensionAssetName::dllNames($this->platform, $this->package), ); } + + public function testVersionWithVPrefixGeneratesVariantsWithAndWithoutPrefix(): void + { + $packageWithV = new Package( + $this->createMock(CompletePackageInterface::class), + ExtensionType::PhpModule, + ExtensionName::normaliseFromString('foo'), + 'phpf/foo', + 'v1.2.3', + null, + ); + + $names = WindowsExtensionAssetName::zipNames($this->platform, $packageWithV); + + // Should contain both "v1.2.3" and "1.2.3" variants + self::assertContains('php_foo-v1.2.3-' . $this->phpVersion . '-ts-vc14-x86_64.zip', $names); + self::assertContains('php_foo-1.2.3-' . $this->phpVersion . '-ts-vc14-x86_64.zip', $names); + self::assertContains('php_foo-v1.2.3-' . $this->phpVersion . '-ts-vc14-x64.zip', $names); + self::assertContains('php_foo-1.2.3-' . $this->phpVersion . '-ts-vc14-x64.zip', $names); + } + + public function testX86ArchitectureDoesNotDuplicate(): void + { + $x86Platform = new TargetPlatform( + OperatingSystem::Windows, + OperatingSystemFamily::Windows, + PhpBinaryPath::fromCurrentProcess(), + Architecture::x86, + ThreadSafetyMode::ThreadSafe, + 1, + WindowsCompiler::VC14, + ); + + $names = WindowsExtensionAssetName::zipNames($x86Platform, $this->package); + + // x86 has the same enum name and windowsName(), so no arch duplicates + self::assertSame( + [ + 'php_foo-1.2.3-' . $this->phpVersion . '-ts-vc14-x86.zip', + 'php_foo-1.2.3-' . $this->phpVersion . '-vc14-ts-x86.zip', + ], + $names, + ); + } + + public function testDetermineDllNameFallsBackToSimpleName(): void + { + // Create a temp directory with only a simple-named DLL (as downloads.php.net provides) + $tempDir = sys_get_temp_dir() . '/' . uniqid('pie_test_', true); + mkdir($tempDir, 0777, true); + file_put_contents($tempDir . '/php_foo.dll', 'fake dll content'); + + $downloadedPackage = DownloadedPackage::fromPackageAndExtractedPath( + $this->package, + $tempDir, + ); + + $result = WindowsExtensionAssetName::determineDllName($this->platform, $downloadedPackage); + self::assertStringEndsWith('php_foo.dll', $result); + } + + public function testDetermineDllNameThrowsWhenNoDllFound(): void + { + $tempDir = sys_get_temp_dir() . '/' . uniqid('pie_test_empty_', true); + mkdir($tempDir, 0777, true); + + $downloadedPackage = DownloadedPackage::fromPackageAndExtractedPath( + $this->package, + $tempDir, + ); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('php_foo.dll'); + WindowsExtensionAssetName::determineDllName($this->platform, $downloadedPackage); + } } From 827cefb192cec70ea35361adcad2a40074956c4f Mon Sep 17 00:00:00 2001 From: gpb Date: Thu, 19 Feb 2026 03:18:57 +0800 Subject: [PATCH 2/2] fix some differences in $ARCH names across platforms --- src/Platform/Architecture.php | 17 +++++----- src/Platform/PrePackagedBinaryAssetName.php | 31 ++++++++++--------- src/Platform/WindowsExtensionAssetName.php | 9 ++---- .../Downloading/DownloadUrlMethodTest.php | 8 +++++ test/unit/Platform/ArchitectureTest.php | 17 +++++----- .../PrePackagedBinaryAssetNameTest.php | 10 ++++++ .../WindowsExtensionAssetNameTest.php | 2 +- 7 files changed, 57 insertions(+), 37 deletions(-) diff --git a/src/Platform/Architecture.php b/src/Platform/Architecture.php index 5e3acf0a..aefde4c9 100644 --- a/src/Platform/Architecture.php +++ b/src/Platform/Architecture.php @@ -26,18 +26,19 @@ public static function parseArchitecture(string $architecture): self } /** - * Returns the conventional Windows architecture label, which may differ - * from the PHP enum case name (e.g. "x64" instead of "x86_64"). - * Used when matching asset filenames from sources like downloads.php.net. + * Returns all known name variants for this architecture, with the + * canonical (enum case) name first. Used when matching asset filenames + * that may use platform-specific conventions (e.g. "x64" on Windows, + * "aarch64" on Linux). * - * @return non-empty-string + * @return non-empty-list */ - public function windowsName(): string + public function allNames(): array { return match ($this) { - self::x86_64 => 'x64', - self::arm64 => 'arm64', - self::x86 => 'x86', + self::x86_64 => ['x86_64', 'x64'], + self::arm64 => ['arm64', 'aarch64'], + self::x86 => ['x86'], }; } } diff --git a/src/Platform/PrePackagedBinaryAssetName.php b/src/Platform/PrePackagedBinaryAssetName.php index eda3f947..6181344f 100644 --- a/src/Platform/PrePackagedBinaryAssetName.php +++ b/src/Platform/PrePackagedBinaryAssetName.php @@ -21,51 +21,54 @@ private function __construct() /** @return non-empty-list */ public static function packageNames(TargetPlatform $targetPlatform, Package $package): array { - return array_values(array_unique([ - strtolower(sprintf( + $names = []; + foreach ($targetPlatform->architecture->allNames() as $arch) { + $names[] = strtolower(sprintf( 'php_%s-%s_php%s-%s-%s-%s%s%s.zip', $package->extensionName()->name(), $package->version(), $targetPlatform->phpBinaryPath->majorMinorVersion(), - $targetPlatform->architecture->name, + $arch, $targetPlatform->operatingSystemFamily->value, $targetPlatform->libcFlavour()->value, $targetPlatform->phpBinaryPath->debugMode() === DebugBuild::Debug ? '-debug' : '', $targetPlatform->threadSafety === ThreadSafetyMode::ThreadSafe ? '-zts' : '', - )), - strtolower(sprintf( + )); + $names[] = strtolower(sprintf( 'php_%s-%s_php%s-%s-%s-%s%s%s.tgz', $package->extensionName()->name(), $package->version(), $targetPlatform->phpBinaryPath->majorMinorVersion(), - $targetPlatform->architecture->name, + $arch, $targetPlatform->operatingSystemFamily->value, $targetPlatform->libcFlavour()->value, $targetPlatform->phpBinaryPath->debugMode() === DebugBuild::Debug ? '-debug' : '', $targetPlatform->threadSafety === ThreadSafetyMode::ThreadSafe ? '-zts' : '', - )), - strtolower(sprintf( + )); + $names[] = strtolower(sprintf( 'php_%s-%s_php%s-%s-%s-%s%s%s.zip', $package->extensionName()->name(), $package->version(), $targetPlatform->phpBinaryPath->majorMinorVersion(), - $targetPlatform->architecture->name, + $arch, $targetPlatform->operatingSystemFamily->value, $targetPlatform->libcFlavour()->value, $targetPlatform->phpBinaryPath->debugMode() === DebugBuild::Debug ? '-debug' : '', $targetPlatform->threadSafety === ThreadSafetyMode::ThreadSafe ? '-zts' : '-nts', - )), - strtolower(sprintf( + )); + $names[] = strtolower(sprintf( 'php_%s-%s_php%s-%s-%s-%s%s%s.tgz', $package->extensionName()->name(), $package->version(), $targetPlatform->phpBinaryPath->majorMinorVersion(), - $targetPlatform->architecture->name, + $arch, $targetPlatform->operatingSystemFamily->value, $targetPlatform->libcFlavour()->value, $targetPlatform->phpBinaryPath->debugMode() === DebugBuild::Debug ? '-debug' : '', $targetPlatform->threadSafety === ThreadSafetyMode::ThreadSafe ? '-zts' : '-nts', - )), - ])); + )); + } + + return array_values(array_unique($names)); } } diff --git a/src/Platform/WindowsExtensionAssetName.php b/src/Platform/WindowsExtensionAssetName.php index ab4dd882..e80b5bc1 100644 --- a/src/Platform/WindowsExtensionAssetName.php +++ b/src/Platform/WindowsExtensionAssetName.php @@ -35,17 +35,14 @@ private static function assetNames(TargetPlatform $targetPlatform, Package $pack * During development, we swapped compiler/ts around. It is fairly trivial to support both, so we can check * both formats pretty easily, just to avoid confusion for package maintainers... * - * Additionally, some distributions (notably downloads.php.net) use the shorter Windows architecture - * label "x64" instead of "x86_64", and version strings without the "v" prefix (e.g. "5.1.28" instead + * Additionally, some distributions (notably downloads.php.net) use alternative architecture labels + * (e.g. "x64" instead of "x86_64"), and version strings without the "v" prefix (e.g. "5.1.28" instead * of "v5.1.28"). We generate variants covering all combinations to match either convention. */ $version = $package->version(); $versionNoV = ltrim($version, 'vV'); $versions = array_unique([$version, $versionNoV]); - $architectures = array_unique([ - $targetPlatform->architecture->name, - $targetPlatform->architecture->windowsName(), - ]); + $architectures = $targetPlatform->architecture->allNames(); $names = []; foreach ($versions as $ver) { diff --git a/test/unit/Downloading/DownloadUrlMethodTest.php b/test/unit/Downloading/DownloadUrlMethodTest.php index b1113b9d..a38572e9 100644 --- a/test/unit/Downloading/DownloadUrlMethodTest.php +++ b/test/unit/Downloading/DownloadUrlMethodTest.php @@ -65,6 +65,8 @@ public function testWindowsPackages(): void [ 'php_foo-1.2.3-8.1-nts-vc15-x86_64.zip', 'php_foo-1.2.3-8.1-vc15-nts-x86_64.zip', + 'php_foo-1.2.3-8.1-nts-vc15-x64.zip', + 'php_foo-1.2.3-8.1-vc15-nts-x64.zip', ], $downloadUrlMethod->possibleAssetNames($package, $targetPlatform), ); @@ -146,6 +148,8 @@ public function testPrePackagedBinaryDownloads(): void [ 'php_bar-1.2.3_php8.3-x86_64-linux-glibc-debug-zts.zip', 'php_bar-1.2.3_php8.3-x86_64-linux-glibc-debug-zts.tgz', + 'php_bar-1.2.3_php8.3-x64-linux-glibc-debug-zts.zip', + 'php_bar-1.2.3_php8.3-x64-linux-glibc-debug-zts.tgz', ], $downloadUrlMethod->possibleAssetNames($package, $targetPlatform), ); @@ -222,6 +226,10 @@ public function testMultipleDownloadUrlMethods(): void 'php_bar-1.2.3_php8.3-x86_64-linux-glibc-debug.tgz', 'php_bar-1.2.3_php8.3-x86_64-linux-glibc-debug-nts.zip', 'php_bar-1.2.3_php8.3-x86_64-linux-glibc-debug-nts.tgz', + 'php_bar-1.2.3_php8.3-x64-linux-glibc-debug.zip', + 'php_bar-1.2.3_php8.3-x64-linux-glibc-debug.tgz', + 'php_bar-1.2.3_php8.3-x64-linux-glibc-debug-nts.zip', + 'php_bar-1.2.3_php8.3-x64-linux-glibc-debug-nts.tgz', ], $firstMethod->possibleAssetNames($package, $targetPlatform), ); diff --git a/test/unit/Platform/ArchitectureTest.php b/test/unit/Platform/ArchitectureTest.php index 62f83a00..469eba50 100644 --- a/test/unit/Platform/ArchitectureTest.php +++ b/test/unit/Platform/ArchitectureTest.php @@ -33,19 +33,20 @@ public function testParseArchitecture(string $architectureString, Architecture $ self::assertSame($expectedArchitecture, Architecture::parseArchitecture($architectureString)); } - /** @return array */ - public static function windowsNameProvider(): array + /** @return array}> */ + public static function allNamesProvider(): array { return [ - 'x86_64 => x64' => [Architecture::x86_64, 'x64'], - 'arm64 => arm64' => [Architecture::arm64, 'arm64'], - 'x86 => x86' => [Architecture::x86, 'x86'], + 'x86_64' => [Architecture::x86_64, ['x86_64', 'x64']], + 'arm64' => [Architecture::arm64, ['arm64', 'aarch64']], + 'x86' => [Architecture::x86, ['x86']], ]; } - #[DataProvider('windowsNameProvider')] - public function testWindowsName(Architecture $architecture, string $expectedWindowsName): void + /** @param non-empty-list $expectedNames */ + #[DataProvider('allNamesProvider')] + public function testAllNames(Architecture $architecture, array $expectedNames): void { - self::assertSame($expectedWindowsName, $architecture->windowsName()); + self::assertSame($expectedNames, $architecture->allNames()); } } diff --git a/test/unit/Platform/PrePackagedBinaryAssetNameTest.php b/test/unit/Platform/PrePackagedBinaryAssetNameTest.php index 022312c6..4822380f 100644 --- a/test/unit/Platform/PrePackagedBinaryAssetNameTest.php +++ b/test/unit/Platform/PrePackagedBinaryAssetNameTest.php @@ -45,6 +45,10 @@ public function testPackageNamesNts(): void 'php_foobar-1.2.3_php8.2-x86_64-linux-' . $libc->value . '.tgz', 'php_foobar-1.2.3_php8.2-x86_64-linux-' . $libc->value . '-nts.zip', 'php_foobar-1.2.3_php8.2-x86_64-linux-' . $libc->value . '-nts.tgz', + 'php_foobar-1.2.3_php8.2-x64-linux-' . $libc->value . '.zip', + 'php_foobar-1.2.3_php8.2-x64-linux-' . $libc->value . '.tgz', + 'php_foobar-1.2.3_php8.2-x64-linux-' . $libc->value . '-nts.zip', + 'php_foobar-1.2.3_php8.2-x64-linux-' . $libc->value . '-nts.tgz', ], PrePackagedBinaryAssetName::packageNames( $targetPlatform, @@ -81,6 +85,8 @@ public function testPackageNamesZts(): void [ 'php_foobar-1.2.3_php8.3-x86_64-linux-' . $libc->value . '-zts.zip', 'php_foobar-1.2.3_php8.3-x86_64-linux-' . $libc->value . '-zts.tgz', + 'php_foobar-1.2.3_php8.3-x64-linux-' . $libc->value . '-zts.zip', + 'php_foobar-1.2.3_php8.3-x64-linux-' . $libc->value . '-zts.tgz', ], PrePackagedBinaryAssetName::packageNames( $targetPlatform, @@ -119,6 +125,10 @@ public function testPackageNamesDebug(): void 'php_foobar-1.2.3_php8.4-arm64-darwin-' . $libc->value . '-debug.tgz', 'php_foobar-1.2.3_php8.4-arm64-darwin-' . $libc->value . '-debug-nts.zip', 'php_foobar-1.2.3_php8.4-arm64-darwin-' . $libc->value . '-debug-nts.tgz', + 'php_foobar-1.2.3_php8.4-aarch64-darwin-' . $libc->value . '-debug.zip', + 'php_foobar-1.2.3_php8.4-aarch64-darwin-' . $libc->value . '-debug.tgz', + 'php_foobar-1.2.3_php8.4-aarch64-darwin-' . $libc->value . '-debug-nts.zip', + 'php_foobar-1.2.3_php8.4-aarch64-darwin-' . $libc->value . '-debug-nts.tgz', ], PrePackagedBinaryAssetName::packageNames( $targetPlatform, diff --git a/test/unit/Platform/WindowsExtensionAssetNameTest.php b/test/unit/Platform/WindowsExtensionAssetNameTest.php index 3b12b1b4..92da3772 100644 --- a/test/unit/Platform/WindowsExtensionAssetNameTest.php +++ b/test/unit/Platform/WindowsExtensionAssetNameTest.php @@ -119,7 +119,7 @@ public function testX86ArchitectureDoesNotDuplicate(): void $names = WindowsExtensionAssetName::zipNames($x86Platform, $this->package); - // x86 has the same enum name and windowsName(), so no arch duplicates + // x86 has only one name in allNames(), so no arch duplicates self::assertSame( [ 'php_foo-1.2.3-' . $this->phpVersion . '-ts-vc14-x86.zip',