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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
version: 2

updates:
- package-ecosystem: composer
directory: /
target-branch: develop
schedule:
interval: weekly
day: monday
time: '08:00'
timezone: Africa/Lusaka
open-pull-requests-limit: 5

- package-ecosystem: github-actions
directory: /
target-branch: develop
schedule:
interval: weekly
day: monday
time: '08:00'
timezone: Africa/Lusaka
open-pull-requests-limit: 5
36 changes: 36 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
name: CI

on:
workflow_dispatch:
push:
branches:
- main
- develop
pull_request:
branches:
- main
- develop

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

env:
COMPOSER_NO_INTERACTION: '1'

jobs:
test:
name: PHP 8.4 / ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
Expand All @@ -30,6 +45,14 @@ jobs:
tools: composer:v2
coverage: none

- name: Cache Composer downloads
uses: actions/cache@v4
with:
path: ~/.composer/cache/files

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Cache Composer's actual download directory

With Composer 2 on the hosted runners, composer config cache-files-dir --absolute resolves to platform-specific locations such as ~/.cache/composer/files on Linux, not ~/.composer/cache/files. Consequently, this path remains absent and actions/cache saves nothing, so every CI matrix job continues downloading all dependencies. Resolve the directory from Composer in a preceding step and cache that output instead.

Useful? React with 👍 / 👎.

key: composer-${{ runner.os }}-php-8.4-${{ hashFiles('composer.lock') }}
restore-keys: |
composer-${{ runner.os }}-php-8.4-

- name: Validate Composer metadata
run: composer validate --strict --no-check-publish

Expand All @@ -50,3 +73,16 @@ jobs:

- name: Smoke-test Stage 33 Slice 2 through the production PHAR
run: composer smoke:phar:stage33-slice2

- name: Upload PHAR smoke artifact
if: runner.os == 'Linux'
uses: actions/upload-artifact@v4
with:
name: baton-php-phar-${{ github.sha }}
path: |
build/baton.phar
build/baton.phar.sha256
build/baton-dependencies.json
build/LICENSES/
if-no-files-found: error
retention-days: 7
8 changes: 8 additions & 0 deletions .github/workflows/private-runtime.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ name: Private PHP runtime
on:
workflow_dispatch:
pull_request:
branches:
- main
- develop
paths:
- '.github/workflows/private-runtime.yml'
- 'composer.json'
Expand All @@ -14,6 +17,7 @@ on:
- 'tests/Distribution/**'
push:
branches:
- main
- develop
paths:
- '.github/workflows/private-runtime.yml'
Expand All @@ -25,6 +29,10 @@ on:
- 'templates/**'
- 'tests/Distribution/**'

concurrency:
group: private-runtime-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

Expand Down
79 changes: 60 additions & 19 deletions packaging/php-runtime/build.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,6 @@

$builderPath = installBuilder($assetPath, $workDirectory, str_starts_with($target, 'windows-'));
$extensionArgument = implode(',', $extensions);
$sourceArguments = [];
foreach ($sources as $name => $source) {
$sourceArguments[] = "--custom-url={$name}:{$source['url']}";
}
if (isset($options['prepare'])) {
run([
$builderPath,
Expand All @@ -91,15 +87,29 @@
], $workDirectory);
}

run([
$builderPath,
'download',
"--for-extensions={$extensionArgument}",
"--with-php={$spec['php']['version']}",
'--without-suggestions',
...$sourceArguments,
'--no-interaction',
], $workDirectory);
$downloaded = false;
$sourceUrlAttempts = sourceUrlAttempts($sources);
foreach ($sourceUrlAttempts as $index => $sourceArguments) {
$exitCode = runForExitCode([
$builderPath,
'download',
"--for-extensions={$extensionArgument}",
"--with-php={$spec['php']['version']}",
'--without-suggestions',
...$sourceArguments,
'--no-interaction',
], $workDirectory);
if ($exitCode === 0) {
$downloaded = true;
break;
}
if (isset($sourceUrlAttempts[$index + 1])) {
fwrite(STDERR, "Source download failed; retrying with fallback URLs.\n");
}
}
if (!$downloaded) {
fail('Runtime source download failed for every pinned URL set.');
}

foreach ($sources as $name => $source) {
verifyDownloadedSource($workDirectory . '/downloads', $name, $source['sha256']);
Expand Down Expand Up @@ -177,8 +187,8 @@
fwrite(STDOUT, "Runtime manifest: {$manifestPath}" . PHP_EOL);

/**
* @param array<string, array{url: string, sha256: string, runtime: bool, targets?: list<string>}> $sources
* @return array<string, array{url: string, sha256: string, runtime: bool, targets?: list<string>}>
* @param array<string, array{url: string, fallbackUrls?: list<string>, sha256: string, runtime: bool, targets?: list<string>}> $sources
* @return array<string, array{url: string, fallbackUrls?: list<string>, sha256: string, runtime: bool, targets?: list<string>}>
*/
function sourcesForTarget(array $sources, string $target): array
{
Expand All @@ -189,6 +199,31 @@ function sourcesForTarget(array $sources, string $target): array
);
}

/**
* @param array<string, array{url: string, fallbackUrls?: list<string>}> $sources
* @return list<list<string>>
*/
function sourceUrlAttempts(array $sources): array
{
$attemptCount = 1;
foreach ($sources as $source) {
$attemptCount = max($attemptCount, 1 + count($source['fallbackUrls'] ?? []));
}

$attempts = [];
for ($attempt = 0; $attempt < $attemptCount; $attempt++) {
$arguments = [];
foreach ($sources as $name => $source) {
$urls = [$source['url'], ...($source['fallbackUrls'] ?? [])];
$url = $urls[min($attempt, count($urls) - 1)];
$arguments[] = "--custom-url={$name}:{$url}";
}
$attempts[] = $arguments;
}

return $attempts;
}

/** @param list<string> $arguments
* @return array<string, string|true>
*/
Expand Down Expand Up @@ -407,6 +442,15 @@ function installBuilder(string $assetPath, string $workDirectory, bool $windows)

/** @param list<string> $command */
function run(array $command, string $workingDirectory): void
{
$exitCode = runForExitCode($command, $workingDirectory);
if ($exitCode !== 0) {
fail("Process exited with status {$exitCode}: {$command[0]}");
}
}

/** @param list<string> $command */
function runForExitCode(array $command, string $workingDirectory): int
{
fwrite(STDOUT, '> ' . implode(' ', array_map(
static fn (string $argument): string => escapeshellarg($argument),
Expand All @@ -416,10 +460,7 @@ function run(array $command, string $workingDirectory): void
if (!is_resource($process)) {
fail("Could not start process: {$command[0]}");
}
$exitCode = proc_close($process);
if ($exitCode !== 0) {
fail("Process exited with status {$exitCode}: {$command[0]}");
}
return proc_close($process);
}

function copyFile(string $source, string $destination): void
Expand Down
5 changes: 4 additions & 1 deletion packaging/php-runtime/spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@
"runtime": true
},
"libiconv": {
"url": "https://mirrors.kernel.org/gnu/libiconv/libiconv-1.19.tar.gz",
"url": "https://ftp.gnu.org/gnu/libiconv/libiconv-1.19.tar.gz",
"fallbackUrls": [
"https://mirrors.kernel.org/gnu/libiconv/libiconv-1.19.tar.gz"
],
"sha256": "88dd96a8c0464eca144fc791ae60cd31cd8ee78321e67397e25fc095c4a19aa6",
"runtime": true,
"targets": [
Expand Down
20 changes: 13 additions & 7 deletions tests/Distribution/RuntimeSpecTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public function testRuntimeInputsAndSupportedTargetsArePinned(): void
* assets: array<string, array{url: string, sha256: string, spcTarget: string}>
* },
* extensions: array{common: list<string>, unix: list<string>},
* sources: array<string, array{url: string, sha256: string, runtime: bool, targets?: list<string>}>,
* sources: array<string, array{url: string, fallbackUrls?: list<string>, sha256: string, runtime: bool, targets?: list<string>}>,
* capabilities: list<string>
* } $spec
*/
Expand Down Expand Up @@ -66,11 +66,13 @@ public function testRuntimeInputsAndSupportedTargetsArePinned(): void
array_keys($spec['sources']),
);
foreach ($spec['sources'] as $source) {
self::assertPinnedUrlAndHash($source['url'], $source['sha256']);
self::assertMatchesRegularExpression(
'/\.(?:tar\.(?:gz|xz)|tgz|zip)$/',
parse_url($source['url'], PHP_URL_PATH) ?: '',
);
foreach ([$source['url'], ...($source['fallbackUrls'] ?? [])] as $url) {
self::assertPinnedUrlAndHash($url, $source['sha256']);
self::assertMatchesRegularExpression(
'/\.(?:tar\.(?:gz|xz)|tgz|zip)$/',
parse_url($url, PHP_URL_PATH) ?: '',
);
}
}
self::assertTrue($spec['sources']['zlib']['runtime']);
self::assertFalse($spec['sources']['micro']['runtime']);
Expand All @@ -79,9 +81,13 @@ public function testRuntimeInputsAndSupportedTargetsArePinned(): void
self::assertTrue($spec['sources']['libiconv']['runtime']);
self::assertTrue($spec['sources']['libiconv-win']['runtime']);
self::assertStringStartsWith(
'https://mirrors.kernel.org/gnu/',
'https://ftp.gnu.org/gnu/',
$spec['sources']['libiconv']['url'],
);
self::assertSame(
['https://mirrors.kernel.org/gnu/libiconv/libiconv-1.19.tar.gz'],
$spec['sources']['libiconv']['fallbackUrls'] ?? null,
);
self::assertSame(
['linux-x86_64', 'linux-aarch64', 'macos-x86_64', 'macos-aarch64'],
$spec['sources']['libiconv']['targets'] ?? null,
Expand Down
Loading