Compare commits

...

1 Commits

Author SHA1 Message Date
Shivam Mathur e1bf52e07a Install requested macOS extension caches alongside PHP 2026-09-24 21:59:54 +05:30
6 changed files with 222 additions and 1 deletions
+117
View File
@@ -0,0 +1,117 @@
import {extensionPacks} from '../src/extensions';
import {execFileSync} from 'node:child_process';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
describe('Optional macOS extension archives', () => {
it('installs packs concurrently and preserves successful packs when another fails', () => {
const directory = fs.mkdtempSync(
path.join(os.tmpdir(), 'setup-php-packs-')
);
const cache = path.join(directory, 'cache');
fs.mkdirSync(cache);
try {
for (const name of ['imagick', 'mongodb', 'memcached']) {
fs.writeFileSync(path.join(cache, name + '.json'), '{}');
}
fs.writeFileSync(
path.join(cache, 'install-extensions.cjs'),
`const fs=require('node:fs'), path=require('node:path');
const [,directory,name]=process.argv.slice(2);
fs.writeFileSync(path.join(directory,name+'.started'),'yes');
setTimeout(()=>process.exit(2),1500);
setInterval(()=>{
if (fs.readdirSync(directory).filter(file=>file.endsWith('.started')).length!==3) return;
fs.writeFileSync(path.join(directory,name+'.env'),'SASL_PATH=/cache/'+name+'\\n');
console.log('completed '+name);
process.exit(name==='mongodb'?1:0);
},10);
`
);
const output = execFileSync(
'bash',
[
'-c',
'. "$CACHE_SCRIPT"; add_env() { printf "env %s %s\\n" "$1" "$2"; }; (exit 0) & extension_cache_pid=$!; finish_extension_cache_downloads; test "$extension_cache_memcached_dependencies" = "igbinary msgpack"'
],
{
encoding: 'utf8',
env: {
...process.env,
CACHE_SCRIPT: path.resolve(
'src/scripts/extensions/darwin_cache.sh'
),
SETUP_PHP_NODE: process.execPath,
SETUP_PHP_EXTENSION_PACKS: 'imagick mongodb memcached',
extension_cache_dir: cache,
verbose: ''
}
}
);
expect(output).toContain('completed imagick');
expect(output).toContain('completed memcached');
expect(output).toContain('mongodb cache unavailable');
expect(output).toContain('env SASL_PATH /cache/imagick');
expect(output).not.toContain('env SASL_PATH /cache/mongodb');
expect(fs.existsSync(cache)).toBe(false);
} finally {
fs.rmSync(directory, {recursive: true, force: true});
}
});
it('selects each requested pack once', async () => {
expect(
await extensionPacks(
'imagick, mongodb, memcached, imagick, redis',
'8.4',
'darwin'
)
).toEqual(['imagick', 'mongodb', 'memcached']);
});
it.each(['', 'redis, yaml', ':imagick', 'imagick, :imagick'])(
'does not download unused or disabled packs: %s',
async input => {
expect(await extensionPacks(input, '8.4', 'darwin')).toEqual([]);
}
);
it.each(['imagick-3.8.1', 'mongodb-beta', 'memcached-github.com/a/b@main'])(
'preserves explicit version/source installation: %s',
async input => {
expect(await extensionPacks(input, '8.4', 'darwin')).toEqual([]);
}
);
it('selects optional packs for PHP 5.6 through 8.7', async () => {
for (const version of [
'5.6',
'7.0',
'7.1',
'7.2',
'7.3',
'7.4',
'8.0',
'8.1',
'8.2',
'8.3',
'8.4',
'8.5',
'8.6',
'8.7'
]) {
expect(
await extensionPacks('imagick, mongodb, memcached', version, 'darwin')
).toEqual(['imagick', 'mongodb', 'memcached']);
}
});
it('keeps unsupported PHP and other platforms on their existing paths', async () => {
for (const version of ['5.4', '5.5', '7.5', '8.8', '9.0']) {
expect(await extensionPacks('imagick', version, 'darwin')).toEqual([]);
}
for (const os of ['linux', 'win32']) {
expect(await extensionPacks('imagick', '8.4', os)).toEqual([]);
}
});
});
+1 -1
View File
File diff suppressed because one or more lines are too long
+13
View File
@@ -1,5 +1,18 @@
import * as utils from './utils';
/** Select only unversioned, requested macOS extension caches. */
export async function extensionPacks(
extension_csv: string,
version: string,
os: string
): Promise<string[]> {
if (os !== 'darwin' || !/^(5\.6|7\.[0-4]|8\.[0-7])$/.test(version)) return [];
const requested = await utils.extensionArray(extension_csv);
return ['imagick', 'mongodb', 'memcached'].filter(
name => requested.includes(name) && !requested.includes(':' + name)
);
}
/**
* Install and enable extensions for darwin
*
+7
View File
@@ -31,6 +31,13 @@ export async function getScript(os: string): Promise<string> {
const ini_file: string = await utils.parseIniFile(
await utils.getInput('ini-file', false)
);
const packs = await extensions.extensionPacks(extension_csv, version, os);
if (packs.length) {
process.env['SETUP_PHP_EXTENSION_PACKS'] = packs.join(' ');
process.env['SETUP_PHP_NODE'] = process.execPath;
} else {
delete process.env['SETUP_PHP_EXTENSION_PACKS'];
}
let script = await utils.joins('.', script_path, `'${version}'`, ini_file);
if (extension_csv) {
script += await extensions.addExtension(extension_csv, version, os);
+9
View File
@@ -77,6 +77,9 @@ add_brew_extension() {
prefix=$2
expected_version=${3:-}
extension="$(get_extension_from_formula "$formula")"
if [ "$extension" = memcached ] && [ -n "${extension_cache_memcached_dependencies:-}" ]; then
enable_extension_cache_dependencies "$extension"
fi
enable_extension "$extension" "$prefix"
if check_extension "$extension" && { [ -z "$expected_version" ] || check_extension_version "$extension" "$expected_version"; }; then
add_log "${tick:?}" "$extension" "Enabled"
@@ -337,4 +340,10 @@ export HOMEBREW_NO_INSTALL_FROM_API=1
configure_brew
read_env
self_hosted_setup
if [ -n "${SETUP_PHP_EXTENSION_PACKS:-}" ]; then
# shellcheck source=.
. "$scripts/extensions/darwin_cache.sh"
start_extension_cache_downloads
fi
setup_php
if [ -n "${SETUP_PHP_EXTENSION_PACKS:-}" ]; then finish_extension_cache_downloads; fi
+75
View File
@@ -0,0 +1,75 @@
# Optional macOS extension archives. Downloads overlap PHP installation; the
# PHP-only path never sources this file or contacts the extension cache.
start_extension_cache_downloads() {
extension_cache_dir=$(mktemp -d "${RUNNER_TEMP:-/tmp}/setup-php-extensions.XXXXXX") || return 0
(
cache_installer="$extension_cache_dir/install-extensions.cjs"
cache_primary="https://github.com/shivammathur/php-darwin/releases/download/extensions"
cache_mirror="https://artifacts.php-darwin.setup-php.com/extensions"
curl -fsSL --retry 0 --connect-timeout 3 --max-time 3 --max-filesize 2000000 \
"$cache_primary/install-extensions.cjs" -o "$cache_installer" ||
curl -fsSL --retry 0 --connect-timeout 3 --max-time 10 --max-filesize 2000000 \
"$cache_mirror/install-extensions.cjs" -o "$cache_installer" || exit 0
IFS=' ' read -r -a cache_extensions <<< "$SETUP_PHP_EXTENSION_PACKS"
"$SETUP_PHP_NODE" "$cache_installer" prefetch "$extension_cache_dir" \
"$version" "$debug" "$ts" "$(uname -m)" "${cache_extensions[@]}"
) > "$extension_cache_dir/download.log" 2>&1 &
extension_cache_pid=$!
}
finish_extension_cache_downloads() {
[ -n "${extension_cache_pid:-}" ] || return 0
wait "$extension_cache_pid" || true
extension_cache_pid=
if [[ "${verbose:-${VERBOSE:-}}" =~ ^(true|v{1,3})$ ]]; then
cat "$extension_cache_dir/download.log"
fi
if [ ! -s "$extension_cache_dir/install-extensions.cjs" ]; then
rm -rf "$extension_cache_dir"
extension_cache_dir=
return 0
fi
local extension variable value index
local cache_install_names=() cache_install_pids=()
for extension in $SETUP_PHP_EXTENSION_PACKS; do
[ -s "$extension_cache_dir/$extension.json" ] || continue
# Packs own separate directories and module names. Extract and validate them
# concurrently, then import their environments only after each one succeeds.
"$SETUP_PHP_NODE" "$extension_cache_dir/install-extensions.cjs" install "$extension_cache_dir" "$extension" \
> "$extension_cache_dir/$extension.install.log" 2>&1 &
cache_install_names+=("$extension")
cache_install_pids+=("$!")
done
for index in "${!cache_install_pids[@]}"; do
extension=${cache_install_names[$index]}
if wait "${cache_install_pids[$index]}"; then
cat "$extension_cache_dir/$extension.install.log"
while IFS='=' read -r variable value; do
case "$variable" in
MAGICK_CONFIGURE_PATH|MAGICK_CODER_MODULE_PATH|MAGICK_FILTER_MODULE_PATH|SASL_PATH)
add_env "$variable" "$value"
;;
esac
done < "$extension_cache_dir/$extension.env"
# Store the dependency names in shell variables; the download directory
# can be removed before the action enables its requested extensions.
if [ "$extension" = memcached ]; then
extension_cache_memcached_dependencies='igbinary msgpack'
fi
else
cat "$extension_cache_dir/$extension.install.log"
printf 'setup-php: %s cache unavailable for this PHP; using the existing installer\n' "$extension"
fi
done
rm -rf "$extension_cache_dir"
extension_cache_dir=
}
enable_extension_cache_dependencies() {
if [ "$1" = memcached ] && [ -n "${extension_cache_memcached_dependencies:-}" ]; then
local dependency
for dependency in $extension_cache_memcached_dependencies; do
enable_extension "$dependency" extension
done
fi
}