Author: Shivam Mathur (shivammathur)
Date: 2026-10-13T19:22:22+05:30

Commit: 
https://github.com/php/web-downloads/commit/d8171aca08092c46104b9845388c24ef457b4798
Raw diff: 
https://github.com/php/web-downloads/commit/d8171aca08092c46104b9845388c24ef457b4798.diff

Allow publishing Winlibs artifacts for one architecture

Changed paths:
  M  .github/workflows/winlibs.yml
  M  API.md
  M  src/Console/Command/WinlibsCommand.php
  M  src/Http/Controllers/WinlibsController.php
  M  tests/Console/Command/WinlibsCommandTest.php
  M  tests/Http/Controllers/WinlibsControllerTest.php


Diff:

diff --git a/.github/workflows/winlibs.yml b/.github/workflows/winlibs.yml
index c10ccd3..39b0d48 100644
--- a/.github/workflows/winlibs.yml
+++ b/.github/workflows/winlibs.yml
@@ -35,6 +35,11 @@ on:
         options:
           - 'true'
           - 'false'
+      arch:
+        description: 'Artifact architecture to publish'
+        type: choice
+        default: all
+        options: [all, x64, x86, arm64]
 jobs:
   print-inputs:
     runs-on: ubuntu-latest
@@ -53,14 +58,26 @@ jobs:
           echo "| vs_version_targets | ${{ inputs.vs_version_targets }} |" >> 
$GITHUB_STEP_SUMMARY
           echo "| stability | ${{ inputs.stability }} |" >> 
$GITHUB_STEP_SUMMARY
           echo "| update_series | ${{ inputs.update_series }} |" >> 
$GITHUB_STEP_SUMMARY
+          echo "| arch | ${{ inputs.arch }} |" >> $GITHUB_STEP_SUMMARY
   test:
     runs-on: ubuntu-latest
     environment: downloads.php.net
     steps:
+      - name: Check architecture filtering support
+        if: inputs.arch != 'all'
+        run: |
+          response=$(curl --silent --show-error --location --request POST \
+            --header 'Authorization: Bearer ${{ secrets.AUTH_TOKEN }}' \
+            --data '{ "arch": "unsupported" }' --write-out '\n%{http_code}' \
+            https://downloads.php.net/api/winlibs)
+          if [[ "$response" != *'The arch field must match'* || "$response" != 
*$'\n400' ]]; then
+            echo 'The downloads server does not yet support filtering Winlibs 
artifacts by architecture.'
+            exit 1
+          fi
       - name: Upload
         run: |
-          curl \
+          curl --fail-with-body \
           --request POST \
           --location https://downloads.php.net/api/winlibs \
           --header 'Authorization: Bearer ${{ secrets.AUTH_TOKEN }}' \
-          --data '{ "library": "${{ inputs.library }}", "ref": "${{ inputs.ref 
}}", "type": "${{ inputs.type }}", "workflow_run_id": "${{ 
inputs.workflow_run_id }}", "php_versions": "${{ inputs.php_versions }}", 
"vs_version_targets": "${{ inputs.vs_version_targets }}", "stability": "${{ 
inputs.stability }}", "token": "${{ secrets.TOKEN }}", "update_series": "${{ 
inputs.update_series }}" }'
+          --data '{ "library": "${{ inputs.library }}", "ref": "${{ inputs.ref 
}}", "type": "${{ inputs.type }}", "workflow_run_id": "${{ 
inputs.workflow_run_id }}", "php_versions": "${{ inputs.php_versions }}", 
"vs_version_targets": "${{ inputs.vs_version_targets }}", "stability": "${{ 
inputs.stability }}", "token": "${{ secrets.TOKEN }}", "update_series": "${{ 
inputs.update_series }}", "arch": "${{ inputs.arch }}" }'
diff --git a/API.md b/API.md
index 7c28d40..9376c69 100644
--- a/API.md
+++ b/API.md
@@ -159,6 +159,7 @@ curl -i -X POST \
     - `vs_version_targets` (string, required): Comma-separated list matching 
`^(v[c|s]\d{2})(,v[c|s]\d{2})*$`.
     - `stability` (string, required): `stable`, `staging`, or both 
(comma-separated) matching `^(stable|staging)(,(stable|staging))?$`.
     - `update_series` (string, required): `true` or `false`.
+    - `arch` (string, optional): `all` (default), `x64`, `x86`, or `arm64`. 
Only matching artifacts are published and their series entries updated; other 
architectures remain unchanged.
     - `token` (string, required): GitHub token used to download artifacts.
 - Success: `200 OK`, empty body.
 - Errors:
diff --git a/src/Console/Command/WinlibsCommand.php 
b/src/Console/Command/WinlibsCommand.php
index 3be1677..e0360e0 100644
--- a/src/Console/Command/WinlibsCommand.php
+++ b/src/Console/Command/WinlibsCommand.php
@@ -58,6 +58,10 @@ public function handle(): int
                     $data = json_decode((string) 
file_get_contents($directoryPath . '/data.json'), true, 512, 
JSON_THROW_ON_ERROR);
                     $files = glob($directoryPath . '/*.zip');
                     $files = $this->parseFiles($files);
+                    $arch = $data['arch'] ?? 'all';
+                    if ($arch !== 'all') {
+                        $files = array_filter($files, static fn (array $file): 
bool => $file['arch'] === $arch);
+                    }
                     if (empty($files)) {
                         throw new Exception('No valid files found in ' . 
basename($directoryPath));
                     }
diff --git a/src/Http/Controllers/WinlibsController.php 
b/src/Http/Controllers/WinlibsController.php
index 56334a5..e7b359e 100644
--- a/src/Http/Controllers/WinlibsController.php
+++ b/src/Http/Controllers/WinlibsController.php
@@ -20,10 +20,11 @@ protected function validate(array $data): bool
             'vs_version_targets' => 
'required|string|regex:/^(v[c|s]\d{2})(,v[c|s]\d{2})*$/',
             'stability' => 
'required|string|regex:/^(stable|staging)(,(stable|staging))?$/',
             'update_series' => 'required|string|regex:/^(true|false)$/',
+            'arch' => 'required|string|regex:/\A(all|x64|x86|arm64)\z/',
             'token' => 'required|string',
         ]);
 
-        $validator->validate($data);
+        $validator->validate($data + ['arch' => 'all']);
 
         $valid = $validator->isValid;
 
diff --git a/tests/Console/Command/WinlibsCommandTest.php 
b/tests/Console/Command/WinlibsCommandTest.php
index ebcdba7..a7308af 100644
--- a/tests/Console/Command/WinlibsCommandTest.php
+++ b/tests/Console/Command/WinlibsCommandTest.php
@@ -481,6 +481,74 @@ public function 
testSortsPhpSeriesFileAfterAddingLibrary(): void
         );
     }
 
+    #[DataProvider('architectureFilterProvider')]
+    public function testPublishesOnlySelectedArchitecture(string $type, string 
$arch): void
+    {
+        $job = $this->winlibsDirectory . '/lib';
+        $deps = $this->baseDirectory . ($type === 'php' ? '/php-sdk/deps' : 
'/pecl/deps');
+        mkdir($job, 0755, true);
+        mkdir($deps . '/series', 0755, true);
+        file_put_contents($job . '/data.json', json_encode([
+            'type' => $type, 'library' => 'lib', 'ref' => '1.0',
+            'vs_version_targets' => 'vs17', 'php_versions' => '8.4',
+            'stability' => 'staging', 'update_series' => 'true', 'arch' => 
$arch,
+        ]));
+        foreach (['x64', 'x86', 'arm64'] as $artifactArch) {
+            $filename = "lib-1.0-vs17-$artifactArch.zip";
+            $destination = $type === 'php' ? "$deps/vs17/$artifactArch" : 
$deps;
+            if (!is_dir($destination)) {
+                mkdir($destination, 0755, true);
+            }
+            file_put_contents("$destination/$filename", 'published');
+            file_put_contents("$job/$filename", 'rebuilt');
+            
file_put_contents("$deps/series/packages-8.4-vs17-$artifactArch-staging.txt", 
"lib-0.9-vs17-$artifactArch.zip");
+        }
+
+        $command = new WinlibsCommand();
+        $command->options = ['base-directory' => $this->baseDirectory, 
'builds-directory' => $this->buildsDirectory];
+        $this->assertSame(0, $command->handle());
+
+        foreach (['x64', 'x86', 'arm64'] as $artifactArch) {
+            $selected = $arch === 'all' || $artifactArch === $arch;
+            $destination = $type === 'php' ? "$deps/vs17/$artifactArch" : 
$deps;
+            
$this->assertStringEqualsFile("$destination/lib-1.0-vs17-$artifactArch.zip", 
$selected ? 'rebuilt' : 'published');
+            if ($type === 'php') {
+                $version = $selected ? '1.0' : '0.9';
+                
$this->assertStringEqualsFile("$deps/series/packages-8.4-vs17-$artifactArch-staging.txt",
 "lib-$version-vs17-$artifactArch.zip");
+            }
+        }
+        $this->assertDirectoryDoesNotExist($job);
+    }
+
+    public static function architectureFilterProvider(): array
+    {
+        return [
+            ['php', 'arm64'], ['php', 'x64'], ['php', 'x86'], ['php', 'all'],
+            ['pecl', 'arm64'],
+        ];
+    }
+
+    public function 
testMissingSelectedArchitectureDoesNotPublishOtherArtifacts(): void
+    {
+        $job = $this->winlibsDirectory . '/lib';
+        mkdir($job, 0755, true);
+        file_put_contents($job . '/data.json', json_encode([
+            'type' => 'php', 'library' => 'lib', 'ref' => '1.0',
+            'vs_version_targets' => 'vs17', 'php_versions' => '8.4',
+            'stability' => 'staging', 'update_series' => 'true', 'arch' => 
'arm64',
+        ]));
+        file_put_contents($job . '/lib-1.0-vs17-x64.zip', 'rebuilt');
+        $command = new WinlibsCommand();
+        $command->options = ['base-directory' => $this->baseDirectory, 
'builds-directory' => $this->buildsDirectory];
+        ob_start();
+        $result = $command->handle();
+        $output = ob_get_clean();
+        $this->assertSame(1, $result);
+        $this->assertStringContainsString('No valid files found', $output);
+        $this->assertDirectoryDoesNotExist($this->baseDirectory . '/php-sdk');
+        $this->assertFileExists($job . '/lib-1.0-vs17-x64.zip');
+    }
+
     public function testCommandHandlesMissingBaseDirectory(): void
     {
         $command = new WinlibsCommand();
diff --git a/tests/Http/Controllers/WinlibsControllerTest.php 
b/tests/Http/Controllers/WinlibsControllerTest.php
index f1ef347..5f70833 100644
--- a/tests/Http/Controllers/WinlibsControllerTest.php
+++ b/tests/Http/Controllers/WinlibsControllerTest.php
@@ -6,6 +6,7 @@
 use App\Http\Controllers\WinlibsController;
 use JsonException;
 use Override;
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 
 class MockWinlibsController extends WinlibsController {
@@ -31,6 +32,37 @@ public function handle(): void
 }
 
 class WinlibsControllerTest extends TestCase {
+    #[DataProvider('architectureProvider')]
+    public function testArchitectureValidation(array $override, bool 
$expected): void
+    {
+        $controller = new class extends WinlibsController {
+            public function validateRequest(array $data): bool { return 
parent::validate($data); }
+        };
+        $data = $override + [
+            'library' => 'zlib', 'ref' => '1.3.2', 'type' => 'php',
+            'workflow_run_id' => '123', 'php_versions' => '8.4',
+            'vs_version_targets' => 'vs17', 'stability' => 'staging',
+            'update_series' => 'true', 'token' => 'test',
+        ];
+        ob_start();
+        try {
+            $this->assertSame($expected, $controller->validateRequest($data));
+        } finally {
+            ob_end_clean();
+            http_response_code(200);
+        }
+    }
+
+    public static function architectureProvider(): array
+    {
+        return [
+            [[], true], [['arch' => 'all'], true], [['arch' => 'arm64'], true],
+            [['arch' => 'x64'], true], [['arch' => 'x86'], true],
+            [['arch' => ''], false], [['arch' => 'x64,arm64'], false],
+            [['arch' => "arm64\n"], false], [['arch' => null], false],
+        ];
+    }
+
     /**
      * @throws JsonException
      */

Reply via email to