diff --git a/composer.json b/composer.json
index aedd38d9..9bb4f3d6 100644
--- a/composer.json
+++ b/composer.json
@@ -34,20 +34,20 @@
"fidry/cpu-core-counter": "^1.3.0",
"illuminate/container": "^10.49.0",
"psr/container": "^2.0.2",
- "symfony/console": "^6.4.27",
- "symfony/event-dispatcher": "^6.4.25",
- "symfony/process": "^6.4.26",
+ "symfony/console": "^6.4.32",
+ "symfony/event-dispatcher": "^6.4.32",
+ "symfony/process": "^6.4.33",
"thephpf/attestation": "^0.0.5",
"webmozart/assert": "^1.12.1"
},
"require-dev": {
"ext-openssl": "*",
- "behat/behat": "^3.27.0",
+ "behat/behat": "^3.29.0",
"bnf/phpstan-psr-container": "^1.1",
"doctrine/coding-standard": "^14.0.0",
- "phpstan/phpstan": "^2.1.32",
+ "phpstan/phpstan": "^2.1.38",
"phpstan/phpstan-webmozart-assert": "^2.0",
- "phpunit/phpunit": "^10.5.58"
+ "phpunit/phpunit": "^10.5.63"
},
"replace": {
"symfony/polyfill-php81": "*",
diff --git a/composer.lock b/composer.lock
index 48278fed..654bc2d6 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,20 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "a0a5418f392e9a86fd3ad48a5be15549",
+ "content-hash": "6392877b0b53e6d18d3d156173dcbba3",
"packages": [
{
"name": "composer/ca-bundle",
- "version": "1.5.9",
+ "version": "1.5.10",
"source": {
"type": "git",
"url": "https://github.com/composer/ca-bundle.git",
- "reference": "1905981ee626e6f852448b7aaa978f8666c5bc54"
+ "reference": "961a5e4056dd2e4a2eedcac7576075947c28bf63"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/ca-bundle/zipball/1905981ee626e6f852448b7aaa978f8666c5bc54",
- "reference": "1905981ee626e6f852448b7aaa978f8666c5bc54",
+ "url": "https://api.github.com/repos/composer/ca-bundle/zipball/961a5e4056dd2e4a2eedcac7576075947c28bf63",
+ "reference": "961a5e4056dd2e4a2eedcac7576075947c28bf63",
"shasum": ""
},
"require": {
@@ -64,7 +64,7 @@
"support": {
"irc": "irc://irc.freenode.org/composer",
"issues": "https://github.com/composer/ca-bundle/issues",
- "source": "https://github.com/composer/ca-bundle/tree/1.5.9"
+ "source": "https://github.com/composer/ca-bundle/tree/1.5.10"
},
"funding": [
{
@@ -76,20 +76,20 @@
"type": "github"
}
],
- "time": "2025-11-06T11:46:17+00:00"
+ "time": "2025-12-08T15:06:51+00:00"
},
{
"name": "composer/class-map-generator",
- "version": "1.7.0",
+ "version": "1.7.1",
"source": {
"type": "git",
"url": "https://github.com/composer/class-map-generator.git",
- "reference": "2373419b7709815ed323ebf18c3c72d03ff4a8a6"
+ "reference": "8f5fa3cc214230e71f54924bd0197a3bcc705eb1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/class-map-generator/zipball/2373419b7709815ed323ebf18c3c72d03ff4a8a6",
- "reference": "2373419b7709815ed323ebf18c3c72d03ff4a8a6",
+ "url": "https://api.github.com/repos/composer/class-map-generator/zipball/8f5fa3cc214230e71f54924bd0197a3bcc705eb1",
+ "reference": "8f5fa3cc214230e71f54924bd0197a3bcc705eb1",
"shasum": ""
},
"require": {
@@ -133,7 +133,7 @@
],
"support": {
"issues": "https://github.com/composer/class-map-generator/issues",
- "source": "https://github.com/composer/class-map-generator/tree/1.7.0"
+ "source": "https://github.com/composer/class-map-generator/tree/1.7.1"
},
"funding": [
{
@@ -145,7 +145,7 @@
"type": "github"
}
],
- "time": "2025-11-19T10:41:15+00:00"
+ "time": "2025-12-29T13:15:25+00:00"
},
{
"name": "composer/composer",
@@ -793,21 +793,21 @@
},
{
"name": "justinrainbow/json-schema",
- "version": "6.6.1",
+ "version": "6.6.4",
"source": {
"type": "git",
"url": "https://github.com/jsonrainbow/json-schema.git",
- "reference": "fd8e5c6b1badb998844ad34ce0abcd71a0aeb396"
+ "reference": "2eeb75d21cf73211335888e7f5e6fd7440723ec7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/fd8e5c6b1badb998844ad34ce0abcd71a0aeb396",
- "reference": "fd8e5c6b1badb998844ad34ce0abcd71a0aeb396",
+ "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/2eeb75d21cf73211335888e7f5e6fd7440723ec7",
+ "reference": "2eeb75d21cf73211335888e7f5e6fd7440723ec7",
"shasum": ""
},
"require": {
"ext-json": "*",
- "marc-mabe/php-enum": "^4.0",
+ "marc-mabe/php-enum": "^4.4",
"php": "^7.2 || ^8.0"
},
"require-dev": {
@@ -862,9 +862,9 @@
],
"support": {
"issues": "https://github.com/jsonrainbow/json-schema/issues",
- "source": "https://github.com/jsonrainbow/json-schema/tree/6.6.1"
+ "source": "https://github.com/jsonrainbow/json-schema/tree/6.6.4"
},
- "time": "2025-11-07T18:30:29+00:00"
+ "time": "2025-12-19T15:01:32+00:00"
},
{
"name": "marc-mabe/php-enum",
@@ -1391,16 +1391,16 @@
},
{
"name": "symfony/console",
- "version": "v6.4.27",
+ "version": "v6.4.32",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "13d3176cf8ad8ced24202844e9f95af11e2959fc"
+ "reference": "0bc2199c6c1f05276b05956f1ddc63f6d7eb5fc3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/13d3176cf8ad8ced24202844e9f95af11e2959fc",
- "reference": "13d3176cf8ad8ced24202844e9f95af11e2959fc",
+ "url": "https://api.github.com/repos/symfony/console/zipball/0bc2199c6c1f05276b05956f1ddc63f6d7eb5fc3",
+ "reference": "0bc2199c6c1f05276b05956f1ddc63f6d7eb5fc3",
"shasum": ""
},
"require": {
@@ -1465,7 +1465,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v6.4.27"
+ "source": "https://github.com/symfony/console/tree/v6.4.32"
},
"funding": [
{
@@ -1485,7 +1485,7 @@
"type": "tidelift"
}
],
- "time": "2025-10-06T10:25:16+00:00"
+ "time": "2026-01-13T08:45:59+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -1556,16 +1556,16 @@
},
{
"name": "symfony/event-dispatcher",
- "version": "v6.4.25",
+ "version": "v6.4.32",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
- "reference": "b0cf3162020603587363f0551cd3be43958611ff"
+ "reference": "99d7e101826e6610606b9433248f80c1997cd20b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b0cf3162020603587363f0551cd3be43958611ff",
- "reference": "b0cf3162020603587363f0551cd3be43958611ff",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/99d7e101826e6610606b9433248f80c1997cd20b",
+ "reference": "99d7e101826e6610606b9433248f80c1997cd20b",
"shasum": ""
},
"require": {
@@ -1616,7 +1616,7 @@
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.25"
+ "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.32"
},
"funding": [
{
@@ -1636,7 +1636,7 @@
"type": "tidelift"
}
],
- "time": "2025-08-13T09:41:44+00:00"
+ "time": "2026-01-05T11:13:48+00:00"
},
{
"name": "symfony/event-dispatcher-contracts",
@@ -1716,16 +1716,16 @@
},
{
"name": "symfony/filesystem",
- "version": "v6.4.24",
+ "version": "v6.4.30",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
- "reference": "75ae2edb7cdcc0c53766c30b0a2512b8df574bd8"
+ "reference": "441c6b69f7222aadae7cbf5df588496d5ee37789"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/filesystem/zipball/75ae2edb7cdcc0c53766c30b0a2512b8df574bd8",
- "reference": "75ae2edb7cdcc0c53766c30b0a2512b8df574bd8",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/441c6b69f7222aadae7cbf5df588496d5ee37789",
+ "reference": "441c6b69f7222aadae7cbf5df588496d5ee37789",
"shasum": ""
},
"require": {
@@ -1762,7 +1762,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/filesystem/tree/v6.4.24"
+ "source": "https://github.com/symfony/filesystem/tree/v6.4.30"
},
"funding": [
{
@@ -1782,20 +1782,20 @@
"type": "tidelift"
}
],
- "time": "2025-07-10T08:14:14+00:00"
+ "time": "2025-11-26T14:43:45+00:00"
},
{
"name": "symfony/finder",
- "version": "v6.4.27",
+ "version": "v6.4.33",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "a1b6aa435d2fba50793b994a839c32b6064f063b"
+ "reference": "24965ca011dac87431729640feef8bcf7b5523e0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/a1b6aa435d2fba50793b994a839c32b6064f063b",
- "reference": "a1b6aa435d2fba50793b994a839c32b6064f063b",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/24965ca011dac87431729640feef8bcf7b5523e0",
+ "reference": "24965ca011dac87431729640feef8bcf7b5523e0",
"shasum": ""
},
"require": {
@@ -1830,7 +1830,7 @@
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/finder/tree/v6.4.27"
+ "source": "https://github.com/symfony/finder/tree/v6.4.33"
},
"funding": [
{
@@ -1850,7 +1850,7 @@
"type": "tidelift"
}
],
- "time": "2025-10-15T18:32:00+00:00"
+ "time": "2026-01-26T13:03:48+00:00"
},
{
"name": "symfony/polyfill-ctype",
@@ -2269,16 +2269,16 @@
},
{
"name": "symfony/process",
- "version": "v6.4.26",
+ "version": "v6.4.33",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "48bad913268c8cafabbf7034b39c8bb24fbc5ab8"
+ "reference": "c46e854e79b52d07666e43924a20cb6dc546644e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/48bad913268c8cafabbf7034b39c8bb24fbc5ab8",
- "reference": "48bad913268c8cafabbf7034b39c8bb24fbc5ab8",
+ "url": "https://api.github.com/repos/symfony/process/zipball/c46e854e79b52d07666e43924a20cb6dc546644e",
+ "reference": "c46e854e79b52d07666e43924a20cb6dc546644e",
"shasum": ""
},
"require": {
@@ -2310,7 +2310,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/process/tree/v6.4.26"
+ "source": "https://github.com/symfony/process/tree/v6.4.33"
},
"funding": [
{
@@ -2330,7 +2330,7 @@
"type": "tidelift"
}
],
- "time": "2025-09-11T09:57:09+00:00"
+ "time": "2026-01-23T16:02:12+00:00"
},
{
"name": "symfony/service-contracts",
@@ -2421,16 +2421,16 @@
},
{
"name": "symfony/string",
- "version": "v6.4.26",
+ "version": "v6.4.30",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "5621f039a71a11c87c106c1c598bdcd04a19aeea"
+ "reference": "50590a057841fa6bf69d12eceffce3465b9e32cb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/5621f039a71a11c87c106c1c598bdcd04a19aeea",
- "reference": "5621f039a71a11c87c106c1c598bdcd04a19aeea",
+ "url": "https://api.github.com/repos/symfony/string/zipball/50590a057841fa6bf69d12eceffce3465b9e32cb",
+ "reference": "50590a057841fa6bf69d12eceffce3465b9e32cb",
"shasum": ""
},
"require": {
@@ -2486,7 +2486,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v6.4.26"
+ "source": "https://github.com/symfony/string/tree/v6.4.30"
},
"funding": [
{
@@ -2506,7 +2506,7 @@
"type": "tidelift"
}
],
- "time": "2025-09-11T14:32:46+00:00"
+ "time": "2025-11-21T18:03:05+00:00"
},
{
"name": "thephpf/attestation",
@@ -2632,16 +2632,16 @@
"packages-dev": [
{
"name": "behat/behat",
- "version": "v3.27.0",
+ "version": "v3.29.0",
"source": {
"type": "git",
"url": "https://github.com/Behat/Behat.git",
- "reference": "3282ad774358e4eaf533855e9a1f48559894d1b5"
+ "reference": "51bdf81639a14645c5d2c06926f4aa37d204921b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Behat/Behat/zipball/3282ad774358e4eaf533855e9a1f48559894d1b5",
- "reference": "3282ad774358e4eaf533855e9a1f48559894d1b5",
+ "url": "https://api.github.com/repos/Behat/Behat/zipball/51bdf81639a14645c5d2c06926f4aa37d204921b",
+ "reference": "51bdf81639a14645c5d2c06926f4aa37d204921b",
"shasum": ""
},
"require": {
@@ -2660,8 +2660,8 @@
"symfony/yaml": "^5.4 || ^6.4 || ^7.0"
},
"require-dev": {
- "friendsofphp/php-cs-fixer": "^3.68",
"opis/json-schema": "^2.5",
+ "php-cs-fixer/shim": "^3.89",
"phpstan/phpstan": "^2.0",
"phpunit/phpunit": "^9.6",
"rector/rector": "2.1.7",
@@ -2677,11 +2677,6 @@
"bin/behat"
],
"type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.x-dev"
- }
- },
"autoload": {
"psr-4": {
"Behat\\Hook\\": "src/Behat/Hook/",
@@ -2721,22 +2716,36 @@
],
"support": {
"issues": "https://github.com/Behat/Behat/issues",
- "source": "https://github.com/Behat/Behat/tree/v3.27.0"
+ "source": "https://github.com/Behat/Behat/tree/v3.29.0"
},
- "time": "2025-11-23T12:12:41+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/acoulton",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/carlos-granados",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/stof",
+ "type": "github"
+ }
+ ],
+ "time": "2025-12-11T09:51:30+00:00"
},
{
"name": "behat/gherkin",
- "version": "v4.15.0",
+ "version": "v4.16.1",
"source": {
"type": "git",
"url": "https://github.com/Behat/Gherkin.git",
- "reference": "05a7459283e8e6af0d46ec25b8bb5960ca3cfa7b"
+ "reference": "e26037937dfd48528746764dd870bc5d0836665f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Behat/Gherkin/zipball/05a7459283e8e6af0d46ec25b8bb5960ca3cfa7b",
- "reference": "05a7459283e8e6af0d46ec25b8bb5960ca3cfa7b",
+ "url": "https://api.github.com/repos/Behat/Gherkin/zipball/e26037937dfd48528746764dd870bc5d0836665f",
+ "reference": "e26037937dfd48528746764dd870bc5d0836665f",
"shasum": ""
},
"require": {
@@ -2744,7 +2753,7 @@
"php": ">=8.1 <8.6"
},
"require-dev": {
- "cucumber/gherkin-monorepo": "dev-gherkin-v36.0.0",
+ "cucumber/gherkin-monorepo": "dev-gherkin-v37.0.0",
"friendsofphp/php-cs-fixer": "^3.77",
"mikey179/vfsstream": "^1.6",
"phpstan/extension-installer": "^1",
@@ -2790,9 +2799,23 @@
],
"support": {
"issues": "https://github.com/Behat/Gherkin/issues",
- "source": "https://github.com/Behat/Gherkin/tree/v4.15.0"
+ "source": "https://github.com/Behat/Gherkin/tree/v4.16.1"
},
- "time": "2025-11-05T15:34:04+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/acoulton",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/carlos-granados",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/stof",
+ "type": "github"
+ }
+ ],
+ "time": "2025-12-08T16:12:58+00:00"
},
{
"name": "bnf/phpstan-psr-container",
@@ -3065,16 +3088,16 @@
},
{
"name": "nikic/php-parser",
- "version": "v5.6.2",
+ "version": "v5.7.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "3a454ca033b9e06b63282ce19562e892747449bb"
+ "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb",
- "reference": "3a454ca033b9e06b63282ce19562e892747449bb",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82",
+ "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82",
"shasum": ""
},
"require": {
@@ -3117,9 +3140,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0"
},
- "time": "2025-10-21T19:32:17+00:00"
+ "time": "2025-12-06T11:56:16+00:00"
},
{
"name": "phar-io/manifest",
@@ -3241,16 +3264,16 @@
},
{
"name": "phpstan/phpdoc-parser",
- "version": "2.3.0",
+ "version": "2.3.2",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git",
- "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495"
+ "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/1e0cd5370df5dd2e556a36b9c62f62e555870495",
- "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495",
+ "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a",
+ "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a",
"shasum": ""
},
"require": {
@@ -3282,17 +3305,17 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
- "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.0"
+ "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2"
},
- "time": "2025-08-30T15:50:23+00:00"
+ "time": "2026-01-25T14:56:51+00:00"
},
{
"name": "phpstan/phpstan",
- "version": "2.1.32",
+ "version": "2.1.38",
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e126cad1e30a99b137b8ed75a85a676450ebb227",
- "reference": "e126cad1e30a99b137b8ed75a85a676450ebb227",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dfaf1f530e1663aa167bc3e52197adb221582629",
+ "reference": "dfaf1f530e1663aa167bc3e52197adb221582629",
"shasum": ""
},
"require": {
@@ -3337,7 +3360,7 @@
"type": "github"
}
],
- "time": "2025-11-11T15:18:17+00:00"
+ "time": "2026-01-30T17:12:46+00:00"
},
{
"name": "phpstan/phpstan-webmozart-assert",
@@ -3713,16 +3736,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "10.5.58",
+ "version": "10.5.63",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "e24fb46da450d8e6a5788670513c1af1424f16ca"
+ "reference": "33198268dad71e926626b618f3ec3966661e4d90"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e24fb46da450d8e6a5788670513c1af1424f16ca",
- "reference": "e24fb46da450d8e6a5788670513c1af1424f16ca",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90",
+ "reference": "33198268dad71e926626b618f3ec3966661e4d90",
"shasum": ""
},
"require": {
@@ -3743,7 +3766,7 @@
"phpunit/php-timer": "^6.0.0",
"sebastian/cli-parser": "^2.0.1",
"sebastian/code-unit": "^2.0.0",
- "sebastian/comparator": "^5.0.4",
+ "sebastian/comparator": "^5.0.5",
"sebastian/diff": "^5.1.1",
"sebastian/environment": "^6.1.0",
"sebastian/exporter": "^5.1.4",
@@ -3794,7 +3817,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.58"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.63"
},
"funding": [
{
@@ -3818,7 +3841,7 @@
"type": "tidelift"
}
],
- "time": "2025-09-28T12:04:46+00:00"
+ "time": "2026-01-27T05:48:37+00:00"
},
{
"name": "sebastian/cli-parser",
@@ -3990,16 +4013,16 @@
},
{
"name": "sebastian/comparator",
- "version": "5.0.4",
+ "version": "5.0.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
- "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e"
+ "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e8e53097718d2b53cfb2aa859b06a41abf58c62e",
- "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d",
+ "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d",
"shasum": ""
},
"require": {
@@ -4055,7 +4078,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
"security": "https://github.com/sebastianbergmann/comparator/security/policy",
- "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.4"
+ "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5"
},
"funding": [
{
@@ -4075,7 +4098,7 @@
"type": "tidelift"
}
],
- "time": "2025-09-07T05:25:07+00:00"
+ "time": "2026-01-24T09:25:16+00:00"
},
{
"name": "sebastian/complexity",
@@ -4775,32 +4798,32 @@
},
{
"name": "slevomat/coding-standard",
- "version": "8.25.1",
+ "version": "8.27.1",
"source": {
"type": "git",
"url": "https://github.com/slevomat/coding-standard.git",
- "reference": "4caa5ec5a30b84b2305e80159c710d437f40cc40"
+ "reference": "29bdaee8b65e7ed2b8e702b01852edba8bae1769"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/4caa5ec5a30b84b2305e80159c710d437f40cc40",
- "reference": "4caa5ec5a30b84b2305e80159c710d437f40cc40",
+ "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/29bdaee8b65e7ed2b8e702b01852edba8bae1769",
+ "reference": "29bdaee8b65e7ed2b8e702b01852edba8bae1769",
"shasum": ""
},
"require": {
- "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.2.0",
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.2.0",
"php": "^7.4 || ^8.0",
- "phpstan/phpdoc-parser": "^2.3.0",
+ "phpstan/phpdoc-parser": "^2.3.1",
"squizlabs/php_codesniffer": "^4.0.1"
},
"require-dev": {
- "phing/phing": "3.0.1|3.1.0",
+ "phing/phing": "3.0.1|3.1.1",
"php-parallel-lint/php-parallel-lint": "1.4.0",
- "phpstan/phpstan": "2.1.32",
+ "phpstan/phpstan": "2.1.37",
"phpstan/phpstan-deprecation-rules": "2.0.3",
- "phpstan/phpstan-phpunit": "2.0.8",
+ "phpstan/phpstan-phpunit": "2.0.12",
"phpstan/phpstan-strict-rules": "2.0.7",
- "phpunit/phpunit": "9.6.8|10.5.48|11.4.4|11.5.36|12.4.4"
+ "phpunit/phpunit": "9.6.31|10.5.60|11.4.4|11.5.49|12.5.7"
},
"type": "phpcodesniffer-standard",
"extra": {
@@ -4824,7 +4847,7 @@
],
"support": {
"issues": "https://github.com/slevomat/coding-standard/issues",
- "source": "https://github.com/slevomat/coding-standard/tree/8.25.1"
+ "source": "https://github.com/slevomat/coding-standard/tree/8.27.1"
},
"funding": [
{
@@ -4836,7 +4859,7 @@
"type": "tidelift"
}
],
- "time": "2025-11-25T18:01:43+00:00"
+ "time": "2026-01-25T15:57:07+00:00"
},
{
"name": "squizlabs/php_codesniffer",
@@ -4919,16 +4942,16 @@
},
{
"name": "symfony/config",
- "version": "v6.4.28",
+ "version": "v6.4.32",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
- "reference": "15947c18ef3ddb0b2f4ec936b9e90e2520979f62"
+ "reference": "d445badf0ad2c2a492e38c0378c39997a56ef97b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/config/zipball/15947c18ef3ddb0b2f4ec936b9e90e2520979f62",
- "reference": "15947c18ef3ddb0b2f4ec936b9e90e2520979f62",
+ "url": "https://api.github.com/repos/symfony/config/zipball/d445badf0ad2c2a492e38c0378c39997a56ef97b",
+ "reference": "d445badf0ad2c2a492e38c0378c39997a56ef97b",
"shasum": ""
},
"require": {
@@ -4974,7 +4997,7 @@
"description": "Helps you find, load, combine, autofill and validate configuration values of any kind",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/config/tree/v6.4.28"
+ "source": "https://github.com/symfony/config/tree/v6.4.32"
},
"funding": [
{
@@ -4994,20 +5017,20 @@
"type": "tidelift"
}
],
- "time": "2025-11-01T19:52:02+00:00"
+ "time": "2026-01-13T08:40:30+00:00"
},
{
"name": "symfony/dependency-injection",
- "version": "v6.4.26",
+ "version": "v6.4.32",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
- "reference": "5f311eaf0b321f8ec640f6bae12da43a14026898"
+ "reference": "b17882e933c4c606620247b6708ab53aa3b88753"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/5f311eaf0b321f8ec640f6bae12da43a14026898",
- "reference": "5f311eaf0b321f8ec640f6bae12da43a14026898",
+ "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/b17882e933c4c606620247b6708ab53aa3b88753",
+ "reference": "b17882e933c4c606620247b6708ab53aa3b88753",
"shasum": ""
},
"require": {
@@ -5059,7 +5082,7 @@
"description": "Allows you to standardize and centralize the way objects are constructed in your application",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/dependency-injection/tree/v6.4.26"
+ "source": "https://github.com/symfony/dependency-injection/tree/v6.4.32"
},
"funding": [
{
@@ -5079,20 +5102,20 @@
"type": "tidelift"
}
],
- "time": "2025-09-11T09:57:09+00:00"
+ "time": "2026-01-23T10:54:33+00:00"
},
{
"name": "symfony/translation",
- "version": "v6.4.26",
+ "version": "v6.4.32",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
- "reference": "c8559fe25c7ee7aa9d28f228903a46db008156a4"
+ "reference": "d6cc8e2fdd484f2f41d25938b0e8e3915de3cfbc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation/zipball/c8559fe25c7ee7aa9d28f228903a46db008156a4",
- "reference": "c8559fe25c7ee7aa9d28f228903a46db008156a4",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/d6cc8e2fdd484f2f41d25938b0e8e3915de3cfbc",
+ "reference": "d6cc8e2fdd484f2f41d25938b0e8e3915de3cfbc",
"shasum": ""
},
"require": {
@@ -5158,7 +5181,7 @@
"description": "Provides tools to internationalize your application",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/translation/tree/v6.4.26"
+ "source": "https://github.com/symfony/translation/tree/v6.4.32"
},
"funding": [
{
@@ -5178,7 +5201,7 @@
"type": "tidelift"
}
],
- "time": "2025-09-05T18:17:25+00:00"
+ "time": "2026-01-12T19:15:33+00:00"
},
{
"name": "symfony/translation-contracts",
@@ -5345,16 +5368,16 @@
},
{
"name": "symfony/yaml",
- "version": "v6.4.26",
+ "version": "v6.4.30",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "0fc8b966fd0dcaab544ae59bfc3a433f048c17b0"
+ "reference": "8207ae83da19ee3748d6d4f567b4d9a7c656e331"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/0fc8b966fd0dcaab544ae59bfc3a433f048c17b0",
- "reference": "0fc8b966fd0dcaab544ae59bfc3a433f048c17b0",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/8207ae83da19ee3748d6d4f567b4d9a7c656e331",
+ "reference": "8207ae83da19ee3748d6d4f567b4d9a7c656e331",
"shasum": ""
},
"require": {
@@ -5397,7 +5420,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/yaml/tree/v6.4.26"
+ "source": "https://github.com/symfony/yaml/tree/v6.4.30"
},
"funding": [
{
@@ -5417,7 +5440,7 @@
"type": "tidelift"
}
],
- "time": "2025-09-26T15:07:38+00:00"
+ "time": "2025-12-02T11:50:18+00:00"
},
{
"name": "theseer/tokenizer",
diff --git a/docs/extension-maintainers.md b/docs/extension-maintainers.md
index 8d279c63..f2e2e1af 100644
--- a/docs/extension-maintainers.md
+++ b/docs/extension-maintainers.md
@@ -228,18 +228,103 @@ The `build-path` may contain some templated values which are replaced:
##### `download-url-method`
The `download-url-method` directive allows extension maintainers to
-change the behaviour of downloading the source package.
-
- * Setting this to `composer-default`, which is the default value if not
- specified, will use the default behaviour implemented by Composer, which is
- to use the standard ZIP archive from the GitHub API (or other source control
- system).
- * Using `pre-packaged-source` will locate a source code package in the release
- assets list based matching one of the following naming conventions:
- * `php_{ExtensionName}-{Version}-src.tgz` (e.g. `php_myext-1.20.1-src.tgz`)
- * `php_{ExtensionName}-{Version}-src.zip` (e.g. `php_myext-1.20.1-src.zip`)
- * `{ExtensionName}-{Version}.tgz` (this is intended for backwards
- compatibility with PECL packages)
+change the behaviour of downloading the source package. This should be defined
+as a list of supported methods, but for backwards compatibility a single
+string may be used.
+
+The possible values are:
+
+ - `composer-default`
+ - `pre-packaged-source`
+ - `pre-packaged-binary`
+
+The default value, if nothing is specified is `["composer-default"]`.
+
+###### composer-default
+
+Setting this to `composer-default`, which is the default value if nothing is
+specified, will use the default behaviour implemented by Composer, which is
+to use the standard ZIP archive from the GitHub API (or other source control
+system). PIE will then build and install the extension from source.
+
+###### pre-packaged-source
+
+Using `pre-packaged-source` will locate a source code package in the release
+assets list based matching one of the following naming conventions:
+
+* `php_{ExtensionName}-{Version}-src.tgz` (e.g. `php_myext-1.20.1-src.tgz`)
+* `php_{ExtensionName}-{Version}-src.zip` (e.g. `php_myext-1.20.1-src.zip`)
+* `{ExtensionName}-{Version}.tgz` (this is intended for backwards
+ compatibility with PECL packages)
+
+This is useful for scenarios where you might need additional dependencies
+pulled into the source build, which would not be available if you downloaded
+the ZIP archive from your repository. For example, if your extension uses Git
+Submodules to include third party libraries statically in the build.
+
+###### pre-packaged-binary
+
+> [!CAUTION]
+> If your extension depends on dynamically linked libraries, it is **not
+> recommended** to use `pre-packaged-binary` option, as the correct version,
+> or at least compatible linked libraries may not be available on the end
+> user's system. Use with caution!
+
+Using `pre-packaged-binary` will attempt to locate a Zip (or TGZ) archive in
+the release assets list based on matching one of the following naming
+conventions:
+
+ * `php_{ExtensionName}-{Version}_php{PhpVersion}-{Arch}-{OS}-{Libc}-{Debug}-{TSMode}.{Format}`
+
+The replacements are:
+
+ * `{ExtensionName}` the name of your extension, e.g. `xdebug` (hint: this
+ is not your Composer package name!)
+ * `{PhpVersion}` the major and minor version of PHP, e.g. `8.5`
+ * `{Version}` the version of your extension, e.g. `1.20.1`
+ * `{Arch}` the architecture of the binary, one of `x86`, `x86_64`, `arm64`
+ * `{OS}` the operating system, one of `windows`, `darwin`, `linux`, `bsd`, `solaris`, `unknown`
+ * `{Libc}` the libc flavour, one of `glibc`, `musl`, `bsdlibc`
+ * `{Debug}` the debug mode, one of `debug`, `nodebug` (or omitted)
+ * `{TSMode}` the thread safety mode, one of `zts`, `nts` (or omitted)
+ * `{Format}` the archive format, one of `zip`, `tgz` - note that ZIP is
+ preferred as it means there are fewer dependencies for the end user
+
+> [!TIP]
+> In order to generate pre-built binaries for PIE, you could use the
+> [php/pie-ext-binary-builder](https://github.com/php/pie-ext-binary-builder)
+> GitHub Action. This will build and name the assets correctly for you.
+
+Some examples of valid asset names:
+
+ * `php_xdebug-4.1_php8.4-x86_64-linux-glibc.zip` (or `php_xdebug-4.1_php8.4-x86_64-glibc-nts.zip`)
+ * `php_xdebug-4.1_php8.4-x86_64-linux-musl.zip` (or `php_xdebug-4.1_php8.4-x86_64-musl-nts.zip`)
+ * `php_xdebug-4.1_php8.4-arm64-linux-glibc.zip` (or `php_xdebug-4.1_php8.4-arm64-glibc-nts.zip`)
+ * `php_xdebug-4.1_php8.4-arm64-linux-musl.zip` (or `php_xdebug-4.1_php8.4-arm64-musl-nts.zip`)
+ * `php_xdebug-4.1_php8.4-x86_64-linux-glibc-zts.zip`
+ * `php_xdebug-4.1_php8.4-x86_64-linux-musl-zts.zip`
+ * `php_xdebug-4.1_php8.4-arm64-linux-glibc-zts.zip`
+ * `php_xdebug-4.1_php8.4-arm64-linux-musl-zts.zip`
+ * `php_xdebug-4.1_php8.4-x86_64-linux-glibc-debug.zip`
+ * `php_xdebug-4.1_php8.4-x86_64-linux-musl-debug.zip`
+ * `php_xdebug-4.1_php8.4-arm64-linux-glibc-debug.zip`
+ * `php_xdebug-4.1_php8.4-arm64-linux-musl-debug.zip`
+
+It is recommended that `pre-packaged-binary` is combined with `composer-default`
+as a fallback mechanism, if a particular combination is supported, but not
+pre-packaged on the release, e.g. `"download-url-method": ["pre-packaged-binary", "composer-default"]`.
+PIE will try to find a pre-packaged binary asset first, but if it cannot
+find an appropriate binary, it will download the source code and build it
+in the traditional manner.
+
+```json
+{
+ "name": "myvendor/myext",
+ "php-ext": {
+ "download-url-method": ["pre-packaged-binary", "composer-default"]
+ }
+}
+```
##### `os-families` restrictions
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index 24d19231..73debb0b 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -175,31 +175,31 @@ parameters:
path: src/DependencyResolver/Package.php
-
- message: '#^@readonly property Php\\Pie\\DependencyResolver\\Package\:\:\$downloadUrlMethod is assigned outside of the constructor\.$#'
+ message: '#^@readonly property Php\\Pie\\DependencyResolver\\Package\:\:\$incompatibleOsFamilies is assigned outside of the constructor\.$#'
identifier: property.readOnlyByPhpDocAssignNotInConstructor
count: 1
path: src/DependencyResolver/Package.php
-
- message: '#^@readonly property Php\\Pie\\DependencyResolver\\Package\:\:\$incompatibleOsFamilies is assigned outside of the constructor\.$#'
+ message: '#^@readonly property Php\\Pie\\DependencyResolver\\Package\:\:\$priority is assigned outside of the constructor\.$#'
identifier: property.readOnlyByPhpDocAssignNotInConstructor
count: 1
path: src/DependencyResolver/Package.php
-
- message: '#^@readonly property Php\\Pie\\DependencyResolver\\Package\:\:\$priority is assigned outside of the constructor\.$#'
+ message: '#^@readonly property Php\\Pie\\DependencyResolver\\Package\:\:\$supportNts is assigned outside of the constructor\.$#'
identifier: property.readOnlyByPhpDocAssignNotInConstructor
count: 1
path: src/DependencyResolver/Package.php
-
- message: '#^@readonly property Php\\Pie\\DependencyResolver\\Package\:\:\$supportNts is assigned outside of the constructor\.$#'
+ message: '#^@readonly property Php\\Pie\\DependencyResolver\\Package\:\:\$supportZts is assigned outside of the constructor\.$#'
identifier: property.readOnlyByPhpDocAssignNotInConstructor
count: 1
path: src/DependencyResolver/Package.php
-
- message: '#^@readonly property Php\\Pie\\DependencyResolver\\Package\:\:\$supportZts is assigned outside of the constructor\.$#'
+ message: '#^@readonly property Php\\Pie\\DependencyResolver\\Package\:\:\$supportedDownloadUrlMethods is assigned outside of the constructor\.$#'
identifier: property.readOnlyByPhpDocAssignNotInConstructor
count: 1
path: src/DependencyResolver/Package.php
diff --git a/resources/composer-json-php-ext-schema.json b/resources/composer-json-php-ext-schema.json
index 55ac0820..9095708b 100644
--- a/resources/composer-json-php-ext-schema.json
+++ b/resources/composer-json-php-ext-schema.json
@@ -41,10 +41,28 @@
"default": null
},
"download-url-method": {
- "type": "string",
- "description": "If specified, this technique will be used to override the URL that PIE uses to download the asset. The default, if not specified, is composer-default.",
- "enum": ["composer-default", "pre-packaged-source"],
- "example": "composer-default"
+ "oneOf": [
+ {
+ "type": "string",
+ "description": "If specified, this technique will be used to override the URL that PIE uses to download the asset. The default, if not specified, is composer-default.",
+ "deprecated": true,
+ "enum": ["composer-default", "pre-packaged-source", "pre-packaged-binary"],
+ "example": "composer-default",
+ "default": "composer-default"
+ },
+ {
+ "type": "array",
+ "description": "Multiple techniques can be specified, in which case PIE will try each in turn until one succeeds. The first technique that succeeds will be used.",
+ "items": {
+ "type": "string",
+ "description": "If specified, this technique will be used to override the URL that PIE uses to download the asset. The default, if not specified, is composer-default.",
+ "enum": ["composer-default", "pre-packaged-source", "pre-packaged-binary"],
+ "example": ["pre-packaged-binary", "composer-default"]
+ },
+ "minItems": 1,
+ "default": ["composer-default"]
+ }
+ ]
},
"os-families": {
"type": "array",
diff --git a/src/Building/ExtensionBinaryNotFound.php b/src/Building/ExtensionBinaryNotFound.php
index 1074361b..9974ee98 100644
--- a/src/Building/ExtensionBinaryNotFound.php
+++ b/src/Building/ExtensionBinaryNotFound.php
@@ -10,6 +10,14 @@
class ExtensionBinaryNotFound extends RuntimeException
{
+ public static function fromPrePackagedBinary(string $expectedBinaryName): self
+ {
+ return new self(sprintf(
+ 'Expected pre-packaged binary does not exist: %s',
+ $expectedBinaryName,
+ ));
+ }
+
public static function fromExpectedBinary(string $expectedBinaryName): self
{
return new self(sprintf(
diff --git a/src/Building/UnixBuild.php b/src/Building/UnixBuild.php
index ef05661e..26699e98 100644
--- a/src/Building/UnixBuild.php
+++ b/src/Building/UnixBuild.php
@@ -5,8 +5,10 @@
namespace Php\Pie\Building;
use Composer\IO\IOInterface;
+use LogicException;
use Php\Pie\ComposerIntegration\BundledPhpExtensionsRepository;
use Php\Pie\Downloading\DownloadedPackage;
+use Php\Pie\Downloading\DownloadUrlMethod;
use Php\Pie\File\BinaryFile;
use Php\Pie\Platform\TargetPhp\PhpizePath;
use Php\Pie\Platform\TargetPlatform;
@@ -33,6 +35,46 @@ public function __invoke(
array $configureOptions,
IOInterface $io,
PhpizePath|null $phpizePath,
+ ): BinaryFile {
+ $selectedDownloadMethod = DownloadUrlMethod::fromDownloadedPackage($downloadedPackage);
+ switch ($selectedDownloadMethod) {
+ case DownloadUrlMethod::PrePackagedBinary:
+ return $this->prePackagedBinary($downloadedPackage, $io);
+
+ case DownloadUrlMethod::ComposerDefaultDownload:
+ case DownloadUrlMethod::PrePackagedSourceDownload:
+ return $this->buildFromSource($downloadedPackage, $targetPlatform, $configureOptions, $io, $phpizePath);
+
+ default:
+ throw new LogicException('Unsupported download method: ' . $selectedDownloadMethod->value);
+ }
+ }
+
+ private function prePackagedBinary(
+ DownloadedPackage $downloadedPackage,
+ IOInterface $io,
+ ): BinaryFile {
+ $expectedSoFile = $downloadedPackage->extractedSourcePath . '/' . $downloadedPackage->package->extensionName()->name() . '.so';
+
+ if (! file_exists($expectedSoFile)) {
+ throw ExtensionBinaryNotFound::fromPrePackagedBinary($expectedSoFile);
+ }
+
+ $io->write(sprintf(
+ 'Pre-packaged binary found: %s',
+ $expectedSoFile,
+ ));
+
+ return BinaryFile::fromFileWithSha256Checksum($expectedSoFile);
+ }
+
+ /** @param list $configureOptions */
+ private function buildFromSource(
+ DownloadedPackage $downloadedPackage,
+ TargetPlatform $targetPlatform,
+ array $configureOptions,
+ IOInterface $io,
+ PhpizePath|null $phpizePath,
): BinaryFile {
$outputCallback = null;
if ($io->isVerbose()) {
diff --git a/src/ComposerIntegration/InstallAndBuildProcess.php b/src/ComposerIntegration/InstallAndBuildProcess.php
index b036b24c..d64a4d79 100644
--- a/src/ComposerIntegration/InstallAndBuildProcess.php
+++ b/src/ComposerIntegration/InstallAndBuildProcess.php
@@ -48,6 +48,7 @@ public function __invoke(
$composerPackage,
);
+ $builtBinaryFile = null;
if ($composerRequest->operation->shouldBuild()) {
$builtBinaryFile = ($this->pieBuild)(
$downloadedPackage,
@@ -75,6 +76,7 @@ public function __invoke(
($this->pieInstall)(
$downloadedPackage,
$composerRequest->targetPlatform,
+ $builtBinaryFile,
$io,
$composerRequest->attemptToSetupIniFile,
),
diff --git a/src/ComposerIntegration/Listeners/CouldNotDetermineDownloadUrlMethod.php b/src/ComposerIntegration/Listeners/CouldNotDetermineDownloadUrlMethod.php
new file mode 100644
index 00000000..a5782c56
--- /dev/null
+++ b/src/ComposerIntegration/Listeners/CouldNotDetermineDownloadUrlMethod.php
@@ -0,0 +1,42 @@
+ $downloadMethods
+ * @param array $failureReasons
+ */
+ public static function fromDownloadUrlMethods(Package $piePackage, array $downloadMethods, array $failureReasons): self
+ {
+ $message = sprintf('Could not download %s', $piePackage->name());
+
+ if (count($downloadMethods) === 1) {
+ $first = array_key_first($downloadMethods);
+ $message .= sprintf(' using %s method: %s', $downloadMethods[$first]->value, $failureReasons[$downloadMethods[$first]->value] ?? '(unknown failure)');
+
+ return new self($message);
+ }
+
+ $message .= ' using the following methods:' . PHP_EOL;
+
+ foreach ($downloadMethods as $downloadMethod) {
+ $message .= sprintf(' - %s: %s%s', $downloadMethod->value, $failureReasons[$downloadMethod->value] ?? '(unknown failure)', PHP_EOL);
+ }
+
+ return new self($message);
+ }
+}
diff --git a/src/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListener.php b/src/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListener.php
index 12118fb9..cf7b6e5a 100644
--- a/src/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListener.php
+++ b/src/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListener.php
@@ -17,6 +17,7 @@
use Php\Pie\Downloading\DownloadUrlMethod;
use Php\Pie\Downloading\PackageReleaseAssets;
use Psr\Container\ContainerInterface;
+use Throwable;
use function array_walk;
use function pathinfo;
@@ -69,38 +70,70 @@ function (OperationInterface $operation): void {
return;
}
- $piePackage = Package::fromComposerCompletePackage($composerPackage);
- $targetPlatform = $this->composerRequest->targetPlatform;
- $downloadUrlMethod = DownloadUrlMethod::fromPackage($piePackage, $targetPlatform);
-
- // Exit early if we should just use Composer's normal download
- if ($downloadUrlMethod === DownloadUrlMethod::ComposerDefaultDownload) {
- return;
- }
-
- $possibleAssetNames = $downloadUrlMethod->possibleAssetNames($piePackage, $targetPlatform);
- if ($possibleAssetNames === null) {
- return;
+ $piePackage = Package::fromComposerCompletePackage($composerPackage);
+ $targetPlatform = $this->composerRequest->targetPlatform;
+ $downloadUrlMethods = DownloadUrlMethod::possibleDownloadUrlMethodsForPackage($piePackage, $targetPlatform);
+
+ $selectedDownloadUrlMethod = null;
+ $downloadMethodFailures = [];
+
+ foreach ($downloadUrlMethods as $downloadUrlMethod) {
+ $this->io->write('Trying to download using: ' . $downloadUrlMethod->value, verbosity: IOInterface::VERY_VERBOSE);
+
+ // Exit early if we should just use Composer's normal download
+ if ($downloadUrlMethod === DownloadUrlMethod::ComposerDefaultDownload) {
+ $selectedDownloadUrlMethod = $downloadUrlMethod;
+ break;
+ }
+
+ try {
+ $possibleAssetNames = $downloadUrlMethod->possibleAssetNames($piePackage, $targetPlatform);
+ } catch (Throwable $t) {
+ $downloadMethodFailures[$downloadUrlMethod->value] = $t->getMessage();
+ $this->io->write('Failed fetching asset names [' . $downloadUrlMethod->value . ']: ' . $t->getMessage(), verbosity: IOInterface::VERBOSE);
+ continue;
+ }
+
+ if ($possibleAssetNames === null) {
+ $downloadMethodFailures[$downloadUrlMethod->value] = 'No asset names';
+ $this->io->write('Failed fetching asset names [' . $downloadUrlMethod->value . ']: No asset names', verbosity: IOInterface::VERBOSE);
+ continue;
+ }
+
+ // @todo https://github.com/php/pie/issues/138 will need to depend on the repo type (GH/GL/BB/etc.)
+ $packageReleaseAssets = $this->container->get(PackageReleaseAssets::class);
+
+ try {
+ $url = $packageReleaseAssets->findMatchingReleaseAssetUrl(
+ $targetPlatform,
+ $piePackage,
+ new HttpDownloader($this->io, $this->composer->getConfig()),
+ $downloadUrlMethod,
+ $possibleAssetNames,
+ );
+ } catch (Throwable $t) {
+ $downloadMethodFailures[$downloadUrlMethod->value] = $t->getMessage();
+ $this->io->write('Failed locating asset [' . $downloadUrlMethod->value . ']: ' . $t->getMessage(), verbosity: IOInterface::VERBOSE);
+ continue;
+ }
+
+ $this->composerRequest->pieOutput->write('Found prebuilt archive: ' . $url);
+ $composerPackage->setDistUrl($url);
+
+ if (pathinfo($url, PATHINFO_EXTENSION) === 'tgz') {
+ $composerPackage->setDistType('tar');
+ }
+
+ $selectedDownloadUrlMethod = $downloadUrlMethod;
+ break;
}
- // @todo https://github.com/php/pie/issues/138 will need to depend on the repo type (GH/GL/BB/etc.)
- $packageReleaseAssets = $this->container->get(PackageReleaseAssets::class);
-
- $url = $packageReleaseAssets->findMatchingReleaseAssetUrl(
- $targetPlatform,
- $piePackage,
- new HttpDownloader($this->io, $this->composer->getConfig()),
- $possibleAssetNames,
- );
-
- $this->composerRequest->pieOutput->write('Found prebuilt archive: ' . $url);
- $composerPackage->setDistUrl($url);
-
- if (pathinfo($url, PATHINFO_EXTENSION) !== 'tgz') {
- return;
+ if ($selectedDownloadUrlMethod === null) {
+ throw CouldNotDetermineDownloadUrlMethod::fromDownloadUrlMethods($piePackage, $downloadUrlMethods, $downloadMethodFailures);
}
- $composerPackage->setDistType('tar');
+ $selectedDownloadUrlMethod->writeToComposerPackage($composerPackage);
+ $this->io->write('Selected download URL method: ' . $selectedDownloadUrlMethod->value . '', verbosity: IOInterface::VERBOSE);
},
);
}
diff --git a/src/DependencyResolver/Package.php b/src/DependencyResolver/Package.php
index eb54d5d8..154c01ec 100644
--- a/src/DependencyResolver/Package.php
+++ b/src/DependencyResolver/Package.php
@@ -39,10 +39,11 @@ final class Package
/** @var non-empty-list|null */
private array|null $compatibleOsFamilies = null;
/** @var non-empty-list|null */
- private array|null $incompatibleOsFamilies = null;
- private bool $supportZts = true;
- private bool $supportNts = true;
- private DownloadUrlMethod|null $downloadUrlMethod = null;
+ private array|null $incompatibleOsFamilies = null;
+ private bool $supportZts = true;
+ private bool $supportNts = true;
+ /** @var non-empty-list|null */
+ private array|null $supportedDownloadUrlMethods = null;
public function __construct(
private readonly CompletePackageInterface $composerPackage,
@@ -91,17 +92,15 @@ public static function fromComposerCompletePackage(CompletePackageInterface $com
$package->priority = $phpExtOptions['priority'] ?? 80;
if ($phpExtOptions !== null && array_key_exists('download-url-method', $phpExtOptions)) {
- /** @var string|list $method */
- $method = $phpExtOptions['download-url-method'];
- if (is_array($method)) {
- if (count($method) === 1) {
- $method = $method[0];
- } else {
- $method = DownloadUrlMethod::ComposerDefaultDownload->value;
- }
+ /** @var string|list $extOptionValue */
+ $extOptionValue = $phpExtOptions['download-url-method'];
+ $methods = is_array($extOptionValue) ? $extOptionValue : [$extOptionValue];
+ if (count($methods) > 0) {
+ $package->supportedDownloadUrlMethods = array_map(
+ static fn (string $method): DownloadUrlMethod => DownloadUrlMethod::from($method),
+ $methods,
+ );
}
-
- $package->downloadUrlMethod = DownloadUrlMethod::tryFrom($method);
}
return $package;
@@ -231,8 +230,9 @@ public function supportNts(): bool
return $this->supportNts;
}
- public function downloadUrlMethod(): DownloadUrlMethod|null
+ /** @return non-empty-list|null */
+ public function supportedDownloadUrlMethods(): array|null
{
- return $this->downloadUrlMethod;
+ return $this->supportedDownloadUrlMethods;
}
}
diff --git a/src/Downloading/DownloadUrlMethod.php b/src/Downloading/DownloadUrlMethod.php
index 6a5a95d8..4b1d99d8 100644
--- a/src/Downloading/DownloadUrlMethod.php
+++ b/src/Downloading/DownloadUrlMethod.php
@@ -4,18 +4,29 @@
namespace Php\Pie\Downloading;
+use Composer\Package\CompletePackageInterface;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\Platform\OperatingSystem;
+use Php\Pie\Platform\PrePackagedBinaryAssetName;
use Php\Pie\Platform\PrePackagedSourceAssetName;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Platform\WindowsExtensionAssetName;
+use function array_key_exists;
+use function array_merge;
+use function assert;
+use function is_string;
+use function method_exists;
+
/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
enum DownloadUrlMethod: string
{
+ private const COMPOSER_PACKAGE_EXTRA_KEY = 'download-url-method';
+
case ComposerDefaultDownload = 'composer-default';
case WindowsBinaryDownload = 'windows-binary';
case PrePackagedSourceDownload = 'pre-packaged-source';
+ case PrePackagedBinary = 'pre-packaged-binary';
/** @return non-empty-list|null */
public function possibleAssetNames(Package $package, TargetPlatform $targetPlatform): array|null
@@ -24,28 +35,45 @@ public function possibleAssetNames(Package $package, TargetPlatform $targetPlatf
self::WindowsBinaryDownload => WindowsExtensionAssetName::zipNames($targetPlatform, $package),
self::PrePackagedSourceDownload => PrePackagedSourceAssetName::packageNames($package),
self::ComposerDefaultDownload => null,
+ self::PrePackagedBinary => PrePackagedBinaryAssetName::packageNames($targetPlatform, $package),
};
}
- public static function fromPackage(Package $package, TargetPlatform $targetPlatform): self
+ public static function fromDownloadedPackage(DownloadedPackage $downloadedPackage): self
+ {
+ return self::fromComposerPackage($downloadedPackage->package->composerPackage());
+ }
+
+ public static function fromComposerPackage(CompletePackageInterface $completePackage): self
+ {
+ $extra = $completePackage->getExtra();
+
+ return self::from(array_key_exists(self::COMPOSER_PACKAGE_EXTRA_KEY, $extra) && is_string($extra[self::COMPOSER_PACKAGE_EXTRA_KEY]) ? $extra[self::COMPOSER_PACKAGE_EXTRA_KEY] : '');
+ }
+
+ public function writeToComposerPackage(CompletePackageInterface $composerPackage): void
+ {
+ assert(method_exists($composerPackage, 'setExtra'));
+
+ $composerPackage->setExtra(array_merge($composerPackage->getExtra(), [self::COMPOSER_PACKAGE_EXTRA_KEY => $this->value]));
+ }
+
+ /** @return non-empty-list */
+ public static function possibleDownloadUrlMethodsForPackage(Package $package, TargetPlatform $targetPlatform): array
{
/**
* PIE does not support building on Windows (yet, at least). Maintainers
* should provide pre-built Windows binaries.
*/
if ($targetPlatform->operatingSystem === OperatingSystem::Windows) {
- return self::WindowsBinaryDownload;
+ return [self::WindowsBinaryDownload];
}
- /**
- * Some packages pre-package source code (e.g. mongodb) as there are
- * external dependencies in Git submodules that otherwise aren't
- * included in GitHub/Gitlab/etc "dist" downloads
- */
- if ($package->downloadUrlMethod() === DownloadUrlMethod::PrePackagedSourceDownload) {
- return self::PrePackagedSourceDownload;
+ $configuredSupportedMethods = $package->supportedDownloadUrlMethods();
+ if ($configuredSupportedMethods === null) {
+ return [self::ComposerDefaultDownload];
}
- return self::ComposerDefaultDownload;
+ return $configuredSupportedMethods;
}
}
diff --git a/src/Downloading/Exception/CouldNotFindReleaseAsset.php b/src/Downloading/Exception/CouldNotFindReleaseAsset.php
index 815086bd..e52d0101 100644
--- a/src/Downloading/Exception/CouldNotFindReleaseAsset.php
+++ b/src/Downloading/Exception/CouldNotFindReleaseAsset.php
@@ -17,10 +17,8 @@
class CouldNotFindReleaseAsset extends RuntimeException
{
/** @param non-empty-list $expectedAssetNames */
- public static function forPackage(TargetPlatform $targetPlatform, Package $package, array $expectedAssetNames): self
+ public static function forPackage(TargetPlatform $targetPlatform, Package $package, DownloadUrlMethod $downloadUrlMethod, array $expectedAssetNames): self
{
- $downloadUrlMethod = DownloadUrlMethod::fromPackage($package, $targetPlatform);
-
if ($downloadUrlMethod === DownloadUrlMethod::WindowsBinaryDownload) {
return new self(sprintf(
'Windows archive with prebuilt extension for %s was not attached on release %s - looked for one of "%s"',
@@ -37,10 +35,10 @@ public static function forPackage(TargetPlatform $targetPlatform, Package $packa
));
}
- public static function forPackageWithMissingTag(Package $package): self
+ public static function forPackageWithMissingTag(Package $package, DownloadUrlMethod $downloadUrlMethod): self
{
if (
- $package->downloadUrlMethod() === DownloadUrlMethod::PrePackagedSourceDownload
+ $downloadUrlMethod === DownloadUrlMethod::PrePackagedSourceDownload
&& $package->composerPackage()->isDev()
) {
return new self(sprintf(
diff --git a/src/Downloading/GithubPackageReleaseAssets.php b/src/Downloading/GithubPackageReleaseAssets.php
index 04c7aed9..ad1bd13a 100644
--- a/src/Downloading/GithubPackageReleaseAssets.php
+++ b/src/Downloading/GithubPackageReleaseAssets.php
@@ -31,12 +31,14 @@ public function findMatchingReleaseAssetUrl(
TargetPlatform $targetPlatform,
Package $package,
HttpDownloader $httpDownloader,
+ DownloadUrlMethod $downloadUrlMethod,
array $possibleReleaseAssetNames,
): string {
$releaseAsset = $this->selectMatchingReleaseAsset(
$targetPlatform,
$package,
- $this->getReleaseAssetsForPackage($package, $httpDownloader),
+ $this->getReleaseAssetsForPackage($package, $httpDownloader, $downloadUrlMethod),
+ $downloadUrlMethod,
$possibleReleaseAssetNames,
);
@@ -56,6 +58,7 @@ private function selectMatchingReleaseAsset(
TargetPlatform $targetPlatform,
Package $package,
array $releaseAssets,
+ DownloadUrlMethod $downloadUrlMethod,
array $possibleReleaseAssetNames,
): array {
foreach ($releaseAssets as $releaseAsset) {
@@ -64,13 +67,14 @@ private function selectMatchingReleaseAsset(
}
}
- throw Exception\CouldNotFindReleaseAsset::forPackage($targetPlatform, $package, $possibleReleaseAssetNames);
+ throw Exception\CouldNotFindReleaseAsset::forPackage($targetPlatform, $package, $downloadUrlMethod, $possibleReleaseAssetNames);
}
/** @return list */
private function getReleaseAssetsForPackage(
Package $package,
HttpDownloader $httpDownloader,
+ DownloadUrlMethod $downloadUrlMethod,
): array {
Assert::notNull($package->downloadUrl());
@@ -88,7 +92,7 @@ private function getReleaseAssetsForPackage(
} catch (TransportException $t) {
/** @link https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#get-a-release-by-tag-name */
if ($t->getStatusCode() === 404) {
- throw Exception\CouldNotFindReleaseAsset::forPackageWithMissingTag($package);
+ throw Exception\CouldNotFindReleaseAsset::forPackageWithMissingTag($package, $downloadUrlMethod);
}
throw $t;
diff --git a/src/Downloading/PackageReleaseAssets.php b/src/Downloading/PackageReleaseAssets.php
index 87a903f2..622bebdb 100644
--- a/src/Downloading/PackageReleaseAssets.php
+++ b/src/Downloading/PackageReleaseAssets.php
@@ -20,6 +20,7 @@ public function findMatchingReleaseAssetUrl(
TargetPlatform $targetPlatform,
Package $package,
HttpDownloader $httpDownloader,
+ DownloadUrlMethod $downloadUrlMethod,
array $possibleReleaseAssetNames,
): string;
}
diff --git a/src/Installing/Install.php b/src/Installing/Install.php
index ebdda216..81724d0a 100644
--- a/src/Installing/Install.php
+++ b/src/Installing/Install.php
@@ -19,6 +19,7 @@ interface Install
public function __invoke(
DownloadedPackage $downloadedPackage,
TargetPlatform $targetPlatform,
+ BinaryFile|null $builtBinaryFile,
IOInterface $io,
bool $attemptToSetupIniFile,
): BinaryFile;
diff --git a/src/Installing/UnixInstall.php b/src/Installing/UnixInstall.php
index 02e32489..a58c14db 100644
--- a/src/Installing/UnixInstall.php
+++ b/src/Installing/UnixInstall.php
@@ -6,14 +6,18 @@
use Composer\IO\IOInterface;
use Php\Pie\Downloading\DownloadedPackage;
+use Php\Pie\Downloading\DownloadUrlMethod;
use Php\Pie\File\BinaryFile;
use Php\Pie\File\Sudo;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Util\Process;
use RuntimeException;
+use Webmozart\Assert\Assert;
-use function array_unshift;
+use function array_map;
+use function array_merge;
use function file_exists;
+use function implode;
use function is_writable;
use function sprintf;
@@ -29,6 +33,7 @@ public function __construct(private readonly SetupIniFile $setupIniFile)
public function __invoke(
DownloadedPackage $downloadedPackage,
TargetPlatform $targetPlatform,
+ BinaryFile|null $builtBinaryFile,
IOInterface $io,
bool $attemptToSetupIniFile,
): BinaryFile {
@@ -41,7 +46,30 @@ public function __invoke(
$sharedObjectName,
);
- $makeInstallCommand = ['make', 'install'];
+ $installCommands = [];
+ switch (DownloadUrlMethod::fromDownloadedPackage($downloadedPackage)) {
+ case DownloadUrlMethod::PrePackagedBinary:
+ Assert::notNull($builtBinaryFile);
+
+ if (file_exists($expectedSharedObjectLocation)) {
+ $installCommands[] = [
+ 'rm',
+ '-v',
+ $expectedSharedObjectLocation,
+ ];
+ }
+
+ $installCommands[] = [
+ 'cp',
+ '-v',
+ $builtBinaryFile->filePath,
+ $targetExtensionPath,
+ ];
+ break;
+
+ default:
+ $installCommands[] = ['make', 'install'];
+ }
// If the target directory isn't writable, or a .so file already exists and isn't writable, try to use sudo
if (
@@ -55,16 +83,20 @@ public function __invoke(
'Cannot write to %s, so using sudo to elevate privileges.',
$targetExtensionPath,
));
- array_unshift($makeInstallCommand, Sudo::find());
+ $installCommands = array_map(static fn (array $command) => array_merge(['sudo'], $command), $installCommands);
}
- $makeInstallOutput = Process::run(
- $makeInstallCommand,
- $downloadedPackage->extractedSourcePath,
- self::MAKE_INSTALL_TIMEOUT_SECS,
- );
+ $io->write(sprintf('Install commands are: %s', implode(', ', array_map(static fn (array $command) => implode(' ', $command), $installCommands))), verbosity: IOInterface::VERY_VERBOSE);
- $io->write($makeInstallOutput, verbosity: IOInterface::VERY_VERBOSE);
+ foreach ($installCommands as $installCommand) {
+ $makeInstallOutput = Process::run(
+ $installCommand,
+ $downloadedPackage->extractedSourcePath,
+ self::MAKE_INSTALL_TIMEOUT_SECS,
+ );
+
+ $io->write($makeInstallOutput, verbosity: IOInterface::VERY_VERBOSE);
+ }
if (! file_exists($expectedSharedObjectLocation)) {
throw new RuntimeException('Install failed, ' . $expectedSharedObjectLocation . ' was not installed.');
diff --git a/src/Installing/WindowsInstall.php b/src/Installing/WindowsInstall.php
index 97eae97e..e674a561 100644
--- a/src/Installing/WindowsInstall.php
+++ b/src/Installing/WindowsInstall.php
@@ -37,6 +37,7 @@ public function __construct(private readonly SetupIniFile $setupIniFile)
public function __invoke(
DownloadedPackage $downloadedPackage,
TargetPlatform $targetPlatform,
+ BinaryFile|null $builtBinaryFile,
IOInterface $io,
bool $attemptToSetupIniFile,
): BinaryFile {
diff --git a/src/Platform/DebugBuild.php b/src/Platform/DebugBuild.php
new file mode 100644
index 00000000..691dd50d
--- /dev/null
+++ b/src/Platform/DebugBuild.php
@@ -0,0 +1,12 @@
+find('otool');
+ if ($otool !== null) {
+ return self::Bsd;
+ }
+
+ $lddPath = $executableFinder->find('ldd');
+ $lsPath = $executableFinder->find('ls');
+
+ if ($lddPath === null || $lsPath === null) {
+ return self::Gnu;
+ }
+
+ try {
+ $linkResult = Process::run([$lddPath, $lsPath]);
+ } catch (Throwable) {
+ return self::Gnu;
+ }
+
+ return str_contains($linkResult, 'musl') ? self::Musl : self::Gnu;
+ }
+}
diff --git a/src/Platform/PrePackagedBinaryAssetName.php b/src/Platform/PrePackagedBinaryAssetName.php
new file mode 100644
index 00000000..eda3f947
--- /dev/null
+++ b/src/Platform/PrePackagedBinaryAssetName.php
@@ -0,0 +1,71 @@
+ */
+ public static function packageNames(TargetPlatform $targetPlatform, Package $package): array
+ {
+ return array_values(array_unique([
+ strtolower(sprintf(
+ 'php_%s-%s_php%s-%s-%s-%s%s%s.zip',
+ $package->extensionName()->name(),
+ $package->version(),
+ $targetPlatform->phpBinaryPath->majorMinorVersion(),
+ $targetPlatform->architecture->name,
+ $targetPlatform->operatingSystemFamily->value,
+ $targetPlatform->libcFlavour()->value,
+ $targetPlatform->phpBinaryPath->debugMode() === DebugBuild::Debug ? '-debug' : '',
+ $targetPlatform->threadSafety === ThreadSafetyMode::ThreadSafe ? '-zts' : '',
+ )),
+ strtolower(sprintf(
+ 'php_%s-%s_php%s-%s-%s-%s%s%s.tgz',
+ $package->extensionName()->name(),
+ $package->version(),
+ $targetPlatform->phpBinaryPath->majorMinorVersion(),
+ $targetPlatform->architecture->name,
+ $targetPlatform->operatingSystemFamily->value,
+ $targetPlatform->libcFlavour()->value,
+ $targetPlatform->phpBinaryPath->debugMode() === DebugBuild::Debug ? '-debug' : '',
+ $targetPlatform->threadSafety === ThreadSafetyMode::ThreadSafe ? '-zts' : '',
+ )),
+ strtolower(sprintf(
+ 'php_%s-%s_php%s-%s-%s-%s%s%s.zip',
+ $package->extensionName()->name(),
+ $package->version(),
+ $targetPlatform->phpBinaryPath->majorMinorVersion(),
+ $targetPlatform->architecture->name,
+ $targetPlatform->operatingSystemFamily->value,
+ $targetPlatform->libcFlavour()->value,
+ $targetPlatform->phpBinaryPath->debugMode() === DebugBuild::Debug ? '-debug' : '',
+ $targetPlatform->threadSafety === ThreadSafetyMode::ThreadSafe ? '-zts' : '-nts',
+ )),
+ strtolower(sprintf(
+ 'php_%s-%s_php%s-%s-%s-%s%s%s.tgz',
+ $package->extensionName()->name(),
+ $package->version(),
+ $targetPlatform->phpBinaryPath->majorMinorVersion(),
+ $targetPlatform->architecture->name,
+ $targetPlatform->operatingSystemFamily->value,
+ $targetPlatform->libcFlavour()->value,
+ $targetPlatform->phpBinaryPath->debugMode() === DebugBuild::Debug ? '-debug' : '',
+ $targetPlatform->threadSafety === ThreadSafetyMode::ThreadSafe ? '-zts' : '-nts',
+ )),
+ ]));
+ }
+}
diff --git a/src/Platform/TargetPhp/PhpBinaryPath.php b/src/Platform/TargetPhp/PhpBinaryPath.php
index c6abc55f..32108921 100644
--- a/src/Platform/TargetPhp/PhpBinaryPath.php
+++ b/src/Platform/TargetPhp/PhpBinaryPath.php
@@ -9,6 +9,7 @@
use Composer\Util\Platform;
use Php\Pie\ExtensionName;
use Php\Pie\Platform\Architecture;
+use Php\Pie\Platform\DebugBuild;
use Php\Pie\Platform\OperatingSystem;
use Php\Pie\Platform\OperatingSystemFamily;
use Php\Pie\Util\Process;
@@ -89,6 +90,18 @@ public function phpApiVersion(): string
throw new RuntimeException('Failed to find PHP API version...');
}
+ public function debugMode(): DebugBuild
+ {
+ if (
+ preg_match('/Debug Build([ =>\t]*)(.*)/', $this->phpinfo(), $m)
+ && $m[2] !== ''
+ ) {
+ return $m[2] === 'yes' ? DebugBuild::Debug : DebugBuild::NoDebug;
+ }
+
+ throw new RuntimeException('Failed to find PHP API version...');
+ }
+
/** @return non-empty-string */
public function extensionPath(): string
{
diff --git a/src/Platform/TargetPlatform.php b/src/Platform/TargetPlatform.php
index c6cf5f62..9a50542c 100644
--- a/src/Platform/TargetPlatform.php
+++ b/src/Platform/TargetPlatform.php
@@ -21,6 +21,8 @@
*/
class TargetPlatform
{
+ private static LibcFlavour $libcFlavour;
+
public function __construct(
public readonly OperatingSystem $operatingSystem,
public readonly OperatingSystemFamily $operatingSystemFamily,
@@ -32,6 +34,15 @@ public function __construct(
) {
}
+ public function libcFlavour(): LibcFlavour
+ {
+ if (! isset(self::$libcFlavour)) {
+ self::$libcFlavour = LibcFlavour::detect();
+ }
+
+ return self::$libcFlavour;
+ }
+
public static function isRunningAsRoot(): bool
{
return function_exists('posix_getuid') && posix_getuid() === 0;
diff --git a/test/assets/fake-ldd/bsdlibc/otool b/test/assets/fake-ldd/bsdlibc/otool
new file mode 100755
index 00000000..69ef7881
--- /dev/null
+++ b/test/assets/fake-ldd/bsdlibc/otool
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+echo "/bin/ls:
+ /usr/lib/libutil.dylib (compatibility version 1.0.0, current version 1.0.0)
+ /usr/lib/libncurses.5.4.dylib (compatibility version 5.4.0, current version 5.4.0)
+ /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1356.0.0)"
diff --git a/test/assets/fake-ldd/glibc/ldd b/test/assets/fake-ldd/glibc/ldd
new file mode 100755
index 00000000..0a6f9719
--- /dev/null
+++ b/test/assets/fake-ldd/glibc/ldd
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+echo " linux-vdso.so.1 (0x000078fecccd6000)
+ libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x000078feccc57000)
+ libcap.so.2 => /lib/x86_64-linux-gnu/libcap.so.2 (0x000078feccc49000)
+ libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x000078fecca00000)
+ libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0 (0x000078fecc941000)
+ /lib64/ld-linux-x86-64.so.2 (0x000078fecccd8000)"
diff --git a/test/assets/fake-ldd/musl/ldd b/test/assets/fake-ldd/musl/ldd
new file mode 100755
index 00000000..1824d33d
--- /dev/null
+++ b/test/assets/fake-ldd/musl/ldd
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+
+echo " /lib/ld-musl-x86_64.so.1 (0x7146f93b1000)
+ libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7146f93b1000)"
diff --git a/test/assets/pre-packaged-binary-examples/invalid/wrong-name.so b/test/assets/pre-packaged-binary-examples/invalid/wrong-name.so
new file mode 100644
index 00000000..e69de29b
diff --git a/test/assets/pre-packaged-binary-examples/valid/pie_test_ext.so b/test/assets/pre-packaged-binary-examples/valid/pie_test_ext.so
new file mode 100644
index 00000000..e69de29b
diff --git a/test/behaviour/CliContext.php b/test/behaviour/CliContext.php
index 05f95df4..c39b00bf 100644
--- a/test/behaviour/CliContext.php
+++ b/test/behaviour/CliContext.php
@@ -17,6 +17,7 @@
use function copy;
use function realpath;
use function sprintf;
+use function str_contains;
class CliContext implements Context
{
@@ -119,6 +120,13 @@ public function theExtensionShouldHaveBeenBuilt(): void
return;
}
+ if (str_contains($this->output, 'Found prebuilt archive')) {
+ Assert::contains($this->output, 'Found prebuilt archive');
+ Assert::contains($this->output, 'Pre-packaged binary found');
+
+ return;
+ }
+
Assert::contains($this->output, 'phpize complete.');
Assert::contains($this->output, 'Configure complete');
Assert::contains($this->output, 'Build complete:');
@@ -141,6 +149,13 @@ public function theExtensionShouldHaveBeenBuiltWithOptions(): void
return;
}
+ if (str_contains($this->output, 'Found prebuilt archive')) {
+ Assert::contains($this->output, 'Found prebuilt archive');
+ Assert::contains($this->output, 'Pre-packaged binary found');
+
+ return;
+ }
+
Assert::contains($this->output, 'phpize complete.');
Assert::contains($this->output, 'Configure complete with options: --with-hello-name=sup');
Assert::contains($this->output, 'Build complete:');
diff --git a/test/integration/Building/UnixBuildTest.php b/test/integration/Building/UnixBuildTest.php
index 73d1c80f..bcf221e0 100644
--- a/test/integration/Building/UnixBuildTest.php
+++ b/test/integration/Building/UnixBuildTest.php
@@ -11,6 +11,7 @@
use Php\Pie\Building\UnixBuild;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\Downloading\DownloadedPackage;
+use Php\Pie\Downloading\DownloadUrlMethod;
use Php\Pie\ExtensionName;
use Php\Pie\ExtensionType;
use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
@@ -25,9 +26,12 @@
#[CoversClass(UnixBuild::class)]
final class UnixBuildTest extends TestCase
{
- private const TEST_EXTENSION_PATH = __DIR__ . '/../../assets/pie_test_ext';
+ private const COMPOSER_PACKAGE_EXTRA_KEY = 'download-url-method';
+ private const TEST_EXTENSION_PATH = __DIR__ . '/../../assets/pie_test_ext';
+ private const TEST_PREBUILT_PATH_VALID = __DIR__ . '/../../assets/pre-packaged-binary-examples/valid';
+ private const TEST_PREBUILT_PATH_INVALID = __DIR__ . '/../../assets/pre-packaged-binary-examples/invalid';
- public function testUnixBuildCanBuildExtension(): void
+ public function testUnixSourceBuildCanBuildExtension(): void
{
if (Platform::isWindows()) {
self::markTestSkipped('Unix build test cannot be run on Windows');
@@ -35,9 +39,14 @@ public function testUnixBuildCanBuildExtension(): void
$output = new BufferIO();
+ $composerPackage = $this->createMock(CompletePackageInterface::class);
+ $composerPackage
+ ->method('getExtra')
+ ->willReturn([self::COMPOSER_PACKAGE_EXTRA_KEY => DownloadUrlMethod::ComposerDefaultDownload->value]);
+
$downloadedPackage = DownloadedPackage::fromPackageAndExtractedPath(
new Package(
- $this->createMock(CompletePackageInterface::class),
+ $composerPackage,
ExtensionType::PhpModule,
ExtensionName::normaliseFromString('pie_test_ext'),
'pie_test_ext',
@@ -76,7 +85,7 @@ public function testUnixBuildCanBuildExtension(): void
(new Process(['phpize', '--clean'], $downloadedPackage->extractedSourcePath))->mustRun();
}
- public function testUnixBuildWillThrowExceptionWhenExpectedBinaryNameMismatches(): void
+ public function testUnixSourceBuildWillThrowExceptionWhenExpectedBinaryNameMismatches(): void
{
if (Platform::isWindows()) {
self::markTestSkipped('Unix build test cannot be run on Windows');
@@ -84,9 +93,14 @@ public function testUnixBuildWillThrowExceptionWhenExpectedBinaryNameMismatches(
$output = new BufferIO();
+ $composerPackage = $this->createMock(CompletePackageInterface::class);
+ $composerPackage
+ ->method('getExtra')
+ ->willReturn([self::COMPOSER_PACKAGE_EXTRA_KEY => DownloadUrlMethod::ComposerDefaultDownload->value]);
+
$downloadedPackage = DownloadedPackage::fromPackageAndExtractedPath(
new Package(
- $this->createMock(CompletePackageInterface::class),
+ $composerPackage,
ExtensionType::PhpModule,
ExtensionName::normaliseFromString('mismatched_name'),
'pie_test_ext',
@@ -113,7 +127,7 @@ public function testUnixBuildWillThrowExceptionWhenExpectedBinaryNameMismatches(
}
}
- public function testUnixBuildCanBuildExtensionWithBuildPath(): void
+ public function testUnixSourceBuildCanBuildExtensionWithBuildPath(): void
{
if (Platform::isWindows()) {
self::markTestSkipped('Unix build test cannot be run on Windows');
@@ -126,6 +140,9 @@ public function testUnixBuildCanBuildExtensionWithBuildPath(): void
$composerPackage->method('getPrettyVersion')->willReturn('0.1.0');
$composerPackage->method('getType')->willReturn('php-ext');
$composerPackage->method('getPhpExt')->willReturn(['build-path' => 'pie_test_ext']);
+ $composerPackage
+ ->method('getExtra')
+ ->willReturn([self::COMPOSER_PACKAGE_EXTRA_KEY => DownloadUrlMethod::ComposerDefaultDownload->value]);
$downloadedPackage = DownloadedPackage::fromPackageAndExtractedPath(
Package::fromComposerCompletePackage($composerPackage),
@@ -161,6 +178,77 @@ public function testUnixBuildCanBuildExtensionWithBuildPath(): void
(new Process(['phpize', '--clean'], $downloadedPackage->extractedSourcePath))->mustRun();
}
+ public function testUnixBinaryBuildThrowsErrorWhenBinaryFileNotFound(): void
+ {
+ if (Platform::isWindows()) {
+ self::markTestSkipped('Unix build test cannot be run on Windows');
+ }
+
+ $output = new BufferIO();
+
+ $composerPackage = $this->createMock(CompletePackageInterface::class);
+ $composerPackage->method('getPrettyName')->willReturn('myvendor/pie_test_ext');
+ $composerPackage->method('getPrettyVersion')->willReturn('0.1.0');
+ $composerPackage->method('getType')->willReturn('php-ext');
+ $composerPackage
+ ->method('getExtra')
+ ->willReturn([self::COMPOSER_PACKAGE_EXTRA_KEY => DownloadUrlMethod::PrePackagedBinary->value]);
+
+ $downloadedPackage = DownloadedPackage::fromPackageAndExtractedPath(
+ Package::fromComposerCompletePackage($composerPackage),
+ self::TEST_PREBUILT_PATH_INVALID,
+ );
+
+ $targetPlatform = TargetPlatform::fromPhpBinaryPath(PhpBinaryPath::fromCurrentProcess(), null);
+ $unixBuilder = new UnixBuild();
+
+ $this->expectException(ExtensionBinaryNotFound::class);
+ $this->expectExceptionMessage('Expected pre-packaged binary does not exist');
+ $unixBuilder->__invoke(
+ $downloadedPackage,
+ $targetPlatform,
+ ['--enable-pie_test_ext'],
+ $output,
+ null,
+ );
+ }
+
+ public function testUnixBinaryBuildReturnsBinaryFile(): void
+ {
+ if (Platform::isWindows()) {
+ self::markTestSkipped('Unix build test cannot be run on Windows');
+ }
+
+ $output = new BufferIO();
+
+ $composerPackage = $this->createMock(CompletePackageInterface::class);
+ $composerPackage->method('getPrettyName')->willReturn('myvendor/pie_test_ext');
+ $composerPackage->method('getPrettyVersion')->willReturn('0.1.0');
+ $composerPackage->method('getType')->willReturn('php-ext');
+ $composerPackage
+ ->method('getExtra')
+ ->willReturn([self::COMPOSER_PACKAGE_EXTRA_KEY => DownloadUrlMethod::PrePackagedBinary->value]);
+
+ $downloadedPackage = DownloadedPackage::fromPackageAndExtractedPath(
+ Package::fromComposerCompletePackage($composerPackage),
+ self::TEST_PREBUILT_PATH_VALID,
+ );
+
+ $targetPlatform = TargetPlatform::fromPhpBinaryPath(PhpBinaryPath::fromCurrentProcess(), null);
+ $unixBuilder = new UnixBuild();
+
+ $binaryFile = $unixBuilder->__invoke(
+ $downloadedPackage,
+ $targetPlatform,
+ ['--enable-pie_test_ext'],
+ $output,
+ null,
+ );
+
+ self::assertSame('e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', $binaryFile->checksum);
+ self::assertStringEndsWith('pre-packaged-binary-examples/valid/pie_test_ext.so', $binaryFile->filePath);
+ }
+
public function testCleanupDoesNotCleanWhenConfigureIsMissing(): void
{
if (Platform::isWindows()) {
@@ -172,9 +260,14 @@ public function testCleanupDoesNotCleanWhenConfigureIsMissing(): void
$output = new BufferIO(verbosity: OutputInterface::VERBOSITY_VERBOSE);
+ $composerPackage = $this->createMock(CompletePackageInterface::class);
+ $composerPackage
+ ->method('getExtra')
+ ->willReturn([self::COMPOSER_PACKAGE_EXTRA_KEY => DownloadUrlMethod::ComposerDefaultDownload->value]);
+
$downloadedPackage = DownloadedPackage::fromPackageAndExtractedPath(
new Package(
- $this->createMock(CompletePackageInterface::class),
+ $composerPackage,
ExtensionType::PhpModule,
ExtensionName::normaliseFromString('pie_test_ext'),
'pie_test_ext',
@@ -209,9 +302,14 @@ public function testVerboseOutputShowsCleanupMessages(): void
$output = new BufferIO(verbosity: OutputInterface::VERBOSITY_VERBOSE);
+ $composerPackage = $this->createMock(CompletePackageInterface::class);
+ $composerPackage
+ ->method('getExtra')
+ ->willReturn([self::COMPOSER_PACKAGE_EXTRA_KEY => DownloadUrlMethod::ComposerDefaultDownload->value]);
+
$downloadedPackage = DownloadedPackage::fromPackageAndExtractedPath(
new Package(
- $this->createMock(CompletePackageInterface::class),
+ $composerPackage,
ExtensionType::PhpModule,
ExtensionName::normaliseFromString('pie_test_ext'),
'pie_test_ext',
diff --git a/test/integration/Command/BuildCommandTest.php b/test/integration/Command/BuildCommandTest.php
index 07123e58..342e81ee 100644
--- a/test/integration/Command/BuildCommandTest.php
+++ b/test/integration/Command/BuildCommandTest.php
@@ -10,6 +10,8 @@
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
+use function str_contains;
+
#[CoversClass(BuildCommand::class)]
class BuildCommandTest extends TestCase
{
@@ -36,6 +38,13 @@ public function testBuildCommandWillBuildTheExtension(): void
return;
}
+ if (str_contains($outputString, 'Found prebuilt archive')) {
+ self::assertStringContainsString('Found prebuilt archive', $outputString);
+ self::assertStringContainsString('Pre-packaged binary found', $outputString);
+
+ return;
+ }
+
self::assertStringContainsString('phpize complete.', $outputString);
self::assertStringContainsString('Configure complete', $outputString);
self::assertStringContainsString('Build complete:', $outputString);
diff --git a/test/integration/Downloading/GithubPackageReleaseAssetsTest.php b/test/integration/Downloading/GithubPackageReleaseAssetsTest.php
index cd9cb4b3..f26b0ef7 100644
--- a/test/integration/Downloading/GithubPackageReleaseAssetsTest.php
+++ b/test/integration/Downloading/GithubPackageReleaseAssetsTest.php
@@ -9,6 +9,7 @@
use Composer\Package\CompletePackageInterface;
use Composer\Util\HttpDownloader;
use Php\Pie\DependencyResolver\Package;
+use Php\Pie\Downloading\DownloadUrlMethod;
use Php\Pie\Downloading\GithubPackageReleaseAssets;
use Php\Pie\ExtensionName;
use Php\Pie\ExtensionType;
@@ -65,6 +66,7 @@ public function testDeterminingReleaseAssetUrlForWindows(): void
$targetPlatform,
$package,
new HttpDownloader($io, $config),
+ DownloadUrlMethod::WindowsBinaryDownload,
WindowsExtensionAssetName::zipNames(
$targetPlatform,
$package,
diff --git a/test/integration/Installing/UnixInstallTest.php b/test/integration/Installing/UnixInstallTest.php
index cae63683..f80e21d8 100644
--- a/test/integration/Installing/UnixInstallTest.php
+++ b/test/integration/Installing/UnixInstallTest.php
@@ -10,8 +10,10 @@
use Php\Pie\Building\UnixBuild;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\Downloading\DownloadedPackage;
+use Php\Pie\Downloading\DownloadUrlMethod;
use Php\Pie\ExtensionName;
use Php\Pie\ExtensionType;
+use Php\Pie\File\BinaryFile;
use Php\Pie\Installing\Ini\PickBestSetupIniApproach;
use Php\Pie\Installing\SetupIniFile;
use Php\Pie\Installing\UnixInstall;
@@ -30,11 +32,18 @@
use function file_exists;
use function is_executable;
use function is_writable;
+use function mkdir;
+use function rename;
+use function unlink;
+
+use const DIRECTORY_SEPARATOR;
#[CoversClass(UnixInstall::class)]
final class UnixInstallTest extends TestCase
{
- private const TEST_EXTENSION_PATH = __DIR__ . '/../../assets/pie_test_ext';
+ private const COMPOSER_PACKAGE_EXTRA_KEY = 'download-url-method';
+ private const TEST_EXTENSION_PATH = __DIR__ . '/../../assets/pie_test_ext';
+ private const TEST_PREBUILT_PATH = __DIR__ . '/../../assets/pre-packaged-binary-examples/install';
/** @return array */
public static function phpPathProvider(): array
@@ -68,7 +77,7 @@ public static function phpPathProvider(): array
}
#[DataProvider('phpPathProvider')]
- public function testUnixInstallCanInstallExtension(string $phpConfig): void
+ public function testUnixInstallCanInstallExtensionBuiltFromSource(string $phpConfig): void
{
assert($phpConfig !== '');
if (Platform::isWindows()) {
@@ -79,9 +88,14 @@ public function testUnixInstallCanInstallExtension(string $phpConfig): void
$targetPlatform = TargetPlatform::fromPhpBinaryPath(PhpBinaryPath::fromPhpConfigExecutable($phpConfig), null);
$extensionPath = $targetPlatform->phpBinaryPath->extensionPath();
+ $composerPackage = $this->createMock(CompletePackageInterface::class);
+ $composerPackage
+ ->method('getExtra')
+ ->willReturn([self::COMPOSER_PACKAGE_EXTRA_KEY => DownloadUrlMethod::ComposerDefaultDownload->value]);
+
$downloadedPackage = DownloadedPackage::fromPackageAndExtractedPath(
new Package(
- $this->createMock(CompletePackageInterface::class),
+ $composerPackage,
ExtensionType::PhpModule,
ExtensionName::normaliseFromString('pie_test_ext'),
'pie_test_ext',
@@ -91,7 +105,7 @@ public function testUnixInstallCanInstallExtension(string $phpConfig): void
self::TEST_EXTENSION_PATH,
);
- (new UnixBuild())->__invoke(
+ $built = (new UnixBuild())->__invoke(
$downloadedPackage,
$targetPlatform,
['--enable-pie_test_ext'],
@@ -102,6 +116,7 @@ public function testUnixInstallCanInstallExtension(string $phpConfig): void
$installedSharedObject = (new UnixInstall(new SetupIniFile(new PickBestSetupIniApproach([]))))->__invoke(
$downloadedPackage,
$targetPlatform,
+ $built,
$output,
true,
);
@@ -122,4 +137,89 @@ public function testUnixInstallCanInstallExtension(string $phpConfig): void
(new Process(['make', 'clean'], $downloadedPackage->extractedSourcePath))->mustRun();
(new Process(['phpize', '--clean'], $downloadedPackage->extractedSourcePath))->mustRun();
}
+
+ #[DataProvider('phpPathProvider')]
+ public function testUnixInstallCanInstallPrePackagedBinary(string $phpConfig): void
+ {
+ assert($phpConfig !== '');
+ if (Platform::isWindows()) {
+ self::markTestSkipped('Unix build test cannot be run on Windows');
+ }
+
+ $output = new BufferIO();
+ $targetPlatform = TargetPlatform::fromPhpBinaryPath(PhpBinaryPath::fromPhpConfigExecutable($phpConfig), null);
+ $extensionPath = $targetPlatform->phpBinaryPath->extensionPath();
+
+ // First build it (otherwise the test assets would need to have a binary for every test platform...)
+ $composerPackage = $this->createMock(CompletePackageInterface::class);
+ $composerPackage
+ ->method('getExtra')
+ ->willReturn([self::COMPOSER_PACKAGE_EXTRA_KEY => DownloadUrlMethod::ComposerDefaultDownload->value]);
+
+ $built = (new UnixBuild())->__invoke(
+ DownloadedPackage::fromPackageAndExtractedPath(
+ new Package(
+ $composerPackage,
+ ExtensionType::PhpModule,
+ ExtensionName::normaliseFromString('pie_test_ext'),
+ 'pie_test_ext',
+ '0.1.0',
+ null,
+ ),
+ self::TEST_EXTENSION_PATH,
+ ),
+ $targetPlatform,
+ ['--enable-pie_test_ext'],
+ $output,
+ null,
+ );
+
+ /**
+ * Move the built .so into a new path; this simulates a pre-packaged binary, which would not have Makefile etc
+ * so this ensures we're not accidentally relying on any build mechanism (`make install` or otherwise)
+ */
+ mkdir(self::TEST_PREBUILT_PATH, 0777, true);
+ $prebuiltBinaryFilePath = self::TEST_PREBUILT_PATH . DIRECTORY_SEPARATOR . 'pie_test_ext.so';
+ rename($built->filePath, $prebuiltBinaryFilePath);
+
+ $prebuiltBinaryFile = BinaryFile::fromFileWithSha256Checksum($prebuiltBinaryFilePath);
+
+ $composerPackage = $this->createMock(CompletePackageInterface::class);
+ $composerPackage
+ ->method('getExtra')
+ ->willReturn([self::COMPOSER_PACKAGE_EXTRA_KEY => DownloadUrlMethod::PrePackagedBinary->value]);
+
+ $installedSharedObject = (new UnixInstall(new SetupIniFile(new PickBestSetupIniApproach([]))))->__invoke(
+ DownloadedPackage::fromPackageAndExtractedPath(
+ new Package(
+ $composerPackage,
+ ExtensionType::PhpModule,
+ ExtensionName::normaliseFromString('pie_test_ext'),
+ 'pie_test_ext',
+ '0.1.0',
+ null,
+ ),
+ self::TEST_PREBUILT_PATH,
+ ),
+ $targetPlatform,
+ $prebuiltBinaryFile,
+ $output,
+ true,
+ );
+ $outputString = $output->getOutput();
+
+ self::assertStringContainsString('Install complete: ' . $extensionPath . '/pie_test_ext.so', $outputString);
+ self::assertStringContainsString('You must now add "extension=pie_test_ext" to your php.ini', $outputString);
+
+ self::assertSame($extensionPath . '/pie_test_ext.so', $installedSharedObject->filePath);
+ self::assertFileExists($installedSharedObject->filePath);
+
+ $rmCommand = ['rm', $installedSharedObject->filePath];
+ if (! is_writable($installedSharedObject->filePath)) {
+ array_unshift($rmCommand, 'sudo');
+ }
+
+ (new Process($rmCommand))->mustRun();
+ unlink($prebuiltBinaryFile->filePath);
+ }
}
diff --git a/test/integration/Installing/WindowsInstallTest.php b/test/integration/Installing/WindowsInstallTest.php
index 086fe026..a4142da6 100644
--- a/test/integration/Installing/WindowsInstallTest.php
+++ b/test/integration/Installing/WindowsInstallTest.php
@@ -71,7 +71,7 @@ public function testWindowsInstallCanInstallExtension(): void
$installer = new WindowsInstall(new SetupIniFile(new PickBestSetupIniApproach([])));
- $installedDll = $installer->__invoke($downloadedPackage, $targetPlatform, $output, true);
+ $installedDll = $installer->__invoke($downloadedPackage, $targetPlatform, null, $output, true);
self::assertSame($extensionPath . '\php_pie_test_ext.dll', $installedDll->filePath);
$outputString = $output->getOutput();
diff --git a/test/unit/Building/ExtensionBinaryNotFoundTest.php b/test/unit/Building/ExtensionBinaryNotFoundTest.php
new file mode 100644
index 00000000..f2996ae9
--- /dev/null
+++ b/test/unit/Building/ExtensionBinaryNotFoundTest.php
@@ -0,0 +1,29 @@
+getMessage(),
+ );
+ }
+
+ public function testFromExpectedBinary(): void
+ {
+ self::assertSame(
+ 'Build complete, but expected /foo/bar does not exist.',
+ ExtensionBinaryNotFound::fromExpectedBinary('/foo/bar')->getMessage(),
+ );
+ }
+}
diff --git a/test/unit/ComposerIntegration/Listeners/CouldNotDetermineDownloadUrlMethodTest.php b/test/unit/ComposerIntegration/Listeners/CouldNotDetermineDownloadUrlMethodTest.php
new file mode 100644
index 00000000..7b0b0fce
--- /dev/null
+++ b/test/unit/ComposerIntegration/Listeners/CouldNotDetermineDownloadUrlMethodTest.php
@@ -0,0 +1,73 @@
+createMock(CompletePackageInterface::class),
+ ExtensionType::PhpModule,
+ ExtensionName::normaliseFromString('foo'),
+ 'foo/foo',
+ '1.2.3',
+ null,
+ );
+
+ $e = CouldNotDetermineDownloadUrlMethod::fromDownloadUrlMethods(
+ $package,
+ [DownloadUrlMethod::PrePackagedBinary],
+ [DownloadUrlMethod::PrePackagedBinary->value => 'A bad thing happened downloading the binary'],
+ );
+
+ self::assertSame(
+ 'Could not download foo/foo using pre-packaged-binary method: A bad thing happened downloading the binary',
+ $e->getMessage(),
+ );
+ }
+
+ public function testMultipleDownloadUrlMethods(): void
+ {
+ $package = new Package(
+ $this->createMock(CompletePackageInterface::class),
+ ExtensionType::PhpModule,
+ ExtensionName::normaliseFromString('foo'),
+ 'foo/foo',
+ '1.2.3',
+ null,
+ );
+
+ $e = CouldNotDetermineDownloadUrlMethod::fromDownloadUrlMethods(
+ $package,
+ [
+ DownloadUrlMethod::PrePackagedBinary,
+ DownloadUrlMethod::PrePackagedSourceDownload,
+ ],
+ [
+ DownloadUrlMethod::PrePackagedBinary->value => 'A bad thing happened downloading the binary',
+ DownloadUrlMethod::PrePackagedSourceDownload->value => 'Another bad thing happened downloading the source',
+ ],
+ );
+
+ self::assertSame(
+ 'Could not download foo/foo using the following methods:
+ - pre-packaged-binary: A bad thing happened downloading the binary
+ - pre-packaged-source: Another bad thing happened downloading the source
+',
+ $e->getMessage(),
+ );
+ }
+}
diff --git a/test/unit/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListenerTest.php b/test/unit/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListenerTest.php
index 5a356b8e..657a2aab 100644
--- a/test/unit/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListenerTest.php
+++ b/test/unit/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListenerTest.php
@@ -12,10 +12,13 @@
use Composer\IO\IOInterface;
use Composer\Package\CompletePackage;
use Composer\Package\Package;
+use Php\Pie\ComposerIntegration\Listeners\CouldNotDetermineDownloadUrlMethod;
use Php\Pie\ComposerIntegration\Listeners\OverrideDownloadUrlInstallListener;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\PieOperation;
use Php\Pie\DependencyResolver\RequestedPackageAndVersion;
+use Php\Pie\Downloading\DownloadUrlMethod;
+use Php\Pie\Downloading\Exception\CouldNotFindReleaseAsset;
use Php\Pie\Downloading\PackageReleaseAssets;
use Php\Pie\Platform\Architecture;
use Php\Pie\Platform\OperatingSystem;
@@ -256,6 +259,7 @@ public function testWindowsUrlInstallerDoesNotRunOnNonWindows(): void
'https://example.com/git-archive-zip-url',
$composerPackage->getDistUrl(),
);
+ self::assertSame(DownloadUrlMethod::ComposerDefaultDownload, DownloadUrlMethod::fromComposerPackage($composerPackage));
}
public function testDistUrlIsUpdatedForWindowsInstallers(): void
@@ -310,6 +314,7 @@ public function testDistUrlIsUpdatedForWindowsInstallers(): void
'https://example.com/windows-download-url',
$composerPackage->getDistUrl(),
);
+ self::assertSame(DownloadUrlMethod::WindowsBinaryDownload, DownloadUrlMethod::fromComposerPackage($composerPackage));
}
public function testDistUrlIsUpdatedForPrePackagedTgzSource(): void
@@ -369,6 +374,187 @@ public function testDistUrlIsUpdatedForPrePackagedTgzSource(): void
'https://example.com/pre-packaged-source-download-url.tgz',
$composerPackage->getDistUrl(),
);
+ self::assertSame(DownloadUrlMethod::PrePackagedSourceDownload, DownloadUrlMethod::fromComposerPackage($composerPackage));
self::assertSame('tar', $composerPackage->getDistType());
}
+
+ public function testDistUrlIsUpdatedForPrePackagedTgzBinaryWhenBinaryIsFound(): void
+ {
+ $composerPackage = new CompletePackage('foo/bar', '1.2.3.0', '1.2.3');
+ $composerPackage->setDistType('zip');
+ $composerPackage->setDistUrl('https://example.com/git-archive-zip-url');
+ $composerPackage->setPhpExt([
+ 'extension-name' => 'foobar',
+ 'download-url-method' => ['pre-packaged-binary', 'composer-default'],
+ ]);
+
+ $installerEvent = new InstallerEvent(
+ InstallerEvents::PRE_OPERATIONS_EXEC,
+ $this->composer,
+ $this->io,
+ false,
+ true,
+ new Transaction([], [$composerPackage]),
+ );
+
+ $packageReleaseAssets = $this->createMock(PackageReleaseAssets::class);
+ $packageReleaseAssets
+ ->expects(self::once())
+ ->method('findMatchingReleaseAssetUrl')
+ ->willReturn('https://example.com/pre-packaged-binary-download-url.tgz');
+
+ $this->container
+ ->method('get')
+ ->with(PackageReleaseAssets::class)
+ ->willReturn($packageReleaseAssets);
+
+ (new OverrideDownloadUrlInstallListener(
+ $this->composer,
+ $this->io,
+ $this->container,
+ new PieComposerRequest(
+ $this->createMock(IOInterface::class),
+ new TargetPlatform(
+ OperatingSystem::NonWindows,
+ OperatingSystemFamily::Linux,
+ PhpBinaryPath::fromCurrentProcess(),
+ Architecture::x86_64,
+ ThreadSafetyMode::NonThreadSafe,
+ 1,
+ WindowsCompiler::VC15,
+ ),
+ new RequestedPackageAndVersion('foo/bar', '^1.1'),
+ PieOperation::Install,
+ [],
+ null,
+ false,
+ ),
+ ))($installerEvent);
+
+ self::assertSame(
+ 'https://example.com/pre-packaged-binary-download-url.tgz',
+ $composerPackage->getDistUrl(),
+ );
+ self::assertSame(DownloadUrlMethod::PrePackagedBinary, DownloadUrlMethod::fromComposerPackage($composerPackage));
+ self::assertSame('tar', $composerPackage->getDistType());
+ }
+
+ public function testDistUrlIsUpdatedForPrePackagedTgzBinaryWhenBinaryIsNotFound(): void
+ {
+ $composerPackage = new CompletePackage('foo/bar', '1.2.3.0', '1.2.3');
+ $composerPackage->setDistType('zip');
+ $composerPackage->setDistUrl('https://example.com/git-archive-zip-url');
+ $composerPackage->setPhpExt([
+ 'extension-name' => 'foobar',
+ 'download-url-method' => ['pre-packaged-binary', 'composer-default'],
+ ]);
+
+ $installerEvent = new InstallerEvent(
+ InstallerEvents::PRE_OPERATIONS_EXEC,
+ $this->composer,
+ $this->io,
+ false,
+ true,
+ new Transaction([], [$composerPackage]),
+ );
+
+ $packageReleaseAssets = $this->createMock(PackageReleaseAssets::class);
+ $packageReleaseAssets
+ ->expects(self::once())
+ ->method('findMatchingReleaseAssetUrl')
+ ->willThrowException(new CouldNotFindReleaseAsset('nope not found'));
+
+ $this->container
+ ->method('get')
+ ->with(PackageReleaseAssets::class)
+ ->willReturn($packageReleaseAssets);
+
+ (new OverrideDownloadUrlInstallListener(
+ $this->composer,
+ $this->io,
+ $this->container,
+ new PieComposerRequest(
+ $this->createMock(IOInterface::class),
+ new TargetPlatform(
+ OperatingSystem::NonWindows,
+ OperatingSystemFamily::Linux,
+ PhpBinaryPath::fromCurrentProcess(),
+ Architecture::x86_64,
+ ThreadSafetyMode::NonThreadSafe,
+ 1,
+ WindowsCompiler::VC15,
+ ),
+ new RequestedPackageAndVersion('foo/bar', '^1.1'),
+ PieOperation::Install,
+ [],
+ null,
+ false,
+ ),
+ ))($installerEvent);
+
+ self::assertSame(
+ 'https://example.com/git-archive-zip-url',
+ $composerPackage->getDistUrl(),
+ );
+ self::assertSame(DownloadUrlMethod::ComposerDefaultDownload, DownloadUrlMethod::fromComposerPackage($composerPackage));
+ self::assertSame('zip', $composerPackage->getDistType());
+ }
+
+ public function testNoSelectedDownloadUrlMethodWillThrowException(): void
+ {
+ $composerPackage = new CompletePackage('foo/bar', '1.2.3.0', '1.2.3');
+ $composerPackage->setDistType('zip');
+ $composerPackage->setDistUrl('https://example.com/git-archive-zip-url');
+ $composerPackage->setPhpExt([
+ 'extension-name' => 'foobar',
+ 'download-url-method' => ['pre-packaged-binary'],
+ ]);
+
+ $installerEvent = new InstallerEvent(
+ InstallerEvents::PRE_OPERATIONS_EXEC,
+ $this->composer,
+ $this->io,
+ false,
+ true,
+ new Transaction([], [$composerPackage]),
+ );
+
+ $packageReleaseAssets = $this->createMock(PackageReleaseAssets::class);
+ $packageReleaseAssets
+ ->expects(self::once())
+ ->method('findMatchingReleaseAssetUrl')
+ ->willThrowException(new CouldNotFindReleaseAsset('nope not found'));
+
+ $this->container
+ ->method('get')
+ ->with(PackageReleaseAssets::class)
+ ->willReturn($packageReleaseAssets);
+
+ $listener = new OverrideDownloadUrlInstallListener(
+ $this->composer,
+ $this->io,
+ $this->container,
+ new PieComposerRequest(
+ $this->createMock(IOInterface::class),
+ new TargetPlatform(
+ OperatingSystem::NonWindows,
+ OperatingSystemFamily::Linux,
+ PhpBinaryPath::fromCurrentProcess(),
+ Architecture::x86_64,
+ ThreadSafetyMode::NonThreadSafe,
+ 1,
+ WindowsCompiler::VC15,
+ ),
+ new RequestedPackageAndVersion('foo/bar', '^1.1'),
+ PieOperation::Install,
+ [],
+ null,
+ false,
+ ),
+ );
+
+ $this->expectException(CouldNotDetermineDownloadUrlMethod::class);
+ $this->expectExceptionMessage('Could not download foo/bar using pre-packaged-binary method: nope not found');
+ $listener($installerEvent);
+ }
}
diff --git a/test/unit/DependencyResolver/PackageTest.php b/test/unit/DependencyResolver/PackageTest.php
index d048da9c..14576c3b 100644
--- a/test/unit/DependencyResolver/PackageTest.php
+++ b/test/unit/DependencyResolver/PackageTest.php
@@ -148,33 +148,32 @@ public function testFromComposerCompletePackageWithBuildPath(): void
self::assertSame('some/subdirectory/path/', $package->buildPath());
}
- public function testDownloadUrlMethodWithStringHasValidDownloadUrlMethod(): void
+ public function testFromComposerCompletePackageWithStringDownloadUrlMethod(): void
{
$composerCompletePackage = new CompletePackage('vendor/foo', '1.2.3.0', '1.2.3');
- $composerCompletePackage->setPhpExt(['download-url-method' => 'composer-default']);
+ $composerCompletePackage->setPhpExt(['download-url-method' => 'pre-packaged-binary']);
- $package = Package::fromComposerCompletePackage($composerCompletePackage);
-
- self::assertSame(DownloadUrlMethod::ComposerDefaultDownload, $package->downloadUrlMethod());
+ self::assertSame(
+ [DownloadUrlMethod::PrePackagedBinary],
+ Package::fromComposerCompletePackage($composerCompletePackage)->supportedDownloadUrlMethods(),
+ );
}
- public function testDownloadUrlMethodWithSingleItemListHasValidDownloadUrlMethod(): void
+ public function testFromComposerCompletePackageWithListDownloadUrlMethods(): void
{
$composerCompletePackage = new CompletePackage('vendor/foo', '1.2.3.0', '1.2.3');
- $composerCompletePackage->setPhpExt(['download-url-method' => ['composer-default']]);
+ $composerCompletePackage->setPhpExt(['download-url-method' => ['pre-packaged-binary', 'composer-default']]);
- $package = Package::fromComposerCompletePackage($composerCompletePackage);
-
- self::assertSame(DownloadUrlMethod::ComposerDefaultDownload, $package->downloadUrlMethod());
+ self::assertSame(
+ [DownloadUrlMethod::PrePackagedBinary, DownloadUrlMethod::ComposerDefaultDownload],
+ Package::fromComposerCompletePackage($composerCompletePackage)->supportedDownloadUrlMethods(),
+ );
}
- public function testDownloadUrlMethodWithMultiItemListIsNotYetSupported(): void
+ public function testFromComposerCompletePackageWithOmittedDownloadUrlMethod(): void
{
- $composerCompletePackage = new CompletePackage('vendor/foo', '1.2.3.0', '1.2.3');
- $composerCompletePackage->setPhpExt(['download-url-method' => ['pre-packaged-source', 'composer-default']]);
-
- $package = Package::fromComposerCompletePackage($composerCompletePackage);
-
- self::assertSame(DownloadUrlMethod::ComposerDefaultDownload, $package->downloadUrlMethod());
+ self::assertNull(Package::fromComposerCompletePackage(
+ new CompletePackage('vendor/foo', '1.2.3.0', '1.2.3'),
+ )->supportedDownloadUrlMethods());
}
}
diff --git a/test/unit/Downloading/DownloadUrlMethodTest.php b/test/unit/Downloading/DownloadUrlMethodTest.php
index 26712bec..b1113b9d 100644
--- a/test/unit/Downloading/DownloadUrlMethodTest.php
+++ b/test/unit/Downloading/DownloadUrlMethodTest.php
@@ -4,12 +4,15 @@
namespace Php\PieUnitTest\Downloading;
+use Composer\Package\CompletePackage;
use Composer\Package\CompletePackageInterface;
use Php\Pie\DependencyResolver\Package;
+use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\Downloading\DownloadUrlMethod;
use Php\Pie\ExtensionName;
use Php\Pie\ExtensionType;
use Php\Pie\Platform\Architecture;
+use Php\Pie\Platform\DebugBuild;
use Php\Pie\Platform\OperatingSystem;
use Php\Pie\Platform\OperatingSystemFamily;
use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
@@ -18,6 +21,9 @@
use Php\Pie\Platform\WindowsCompiler;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
+use ValueError;
+
+use function array_key_first;
#[CoversClass(DownloadUrlMethod::class)]
final class DownloadUrlMethodTest extends TestCase
@@ -48,7 +54,10 @@ public function testWindowsPackages(): void
WindowsCompiler::VC15,
);
- $downloadUrlMethod = DownloadUrlMethod::fromPackage($package, $targetPlatform);
+ $downloadUrlMethods = DownloadUrlMethod::possibleDownloadUrlMethodsForPackage($package, $targetPlatform);
+
+ self::assertCount(1, $downloadUrlMethods);
+ $downloadUrlMethod = $downloadUrlMethods[array_key_first($downloadUrlMethods)];
self::assertSame(DownloadUrlMethod::WindowsBinaryDownload, $downloadUrlMethod);
@@ -81,7 +90,10 @@ public function testPrePackagedSourceDownloads(): void
null,
);
- $downloadUrlMethod = DownloadUrlMethod::fromPackage($package, $targetPlatform);
+ $downloadUrlMethods = DownloadUrlMethod::possibleDownloadUrlMethodsForPackage($package, $targetPlatform);
+
+ self::assertCount(1, $downloadUrlMethods);
+ $downloadUrlMethod = $downloadUrlMethods[array_key_first($downloadUrlMethods)];
self::assertSame(DownloadUrlMethod::PrePackagedSourceDownload, $downloadUrlMethod);
@@ -95,6 +107,50 @@ public function testPrePackagedSourceDownloads(): void
);
}
+ public function testPrePackagedBinaryDownloads(): void
+ {
+ $composerPackage = $this->createMock(CompletePackageInterface::class);
+ $composerPackage->method('getPrettyName')->willReturn('foo/bar');
+ $composerPackage->method('getPrettyVersion')->willReturn('1.2.3');
+ $composerPackage->method('getType')->willReturn('php-ext');
+ $composerPackage->method('getPhpExt')->willReturn(['download-url-method' => ['pre-packaged-binary']]);
+
+ $package = Package::fromComposerCompletePackage($composerPackage);
+
+ $phpBinaryPath = $this->createMock(PhpBinaryPath::class);
+ $phpBinaryPath
+ ->method('majorMinorVersion')
+ ->willReturn('8.3');
+ $phpBinaryPath
+ ->method('debugMode')
+ ->willReturn(DebugBuild::Debug);
+
+ $targetPlatform = new TargetPlatform(
+ OperatingSystem::NonWindows,
+ OperatingSystemFamily::Linux,
+ $phpBinaryPath,
+ Architecture::x86_64,
+ ThreadSafetyMode::ThreadSafe,
+ 1,
+ null,
+ );
+
+ $downloadUrlMethods = DownloadUrlMethod::possibleDownloadUrlMethodsForPackage($package, $targetPlatform);
+
+ self::assertCount(1, $downloadUrlMethods);
+ $downloadUrlMethod = $downloadUrlMethods[array_key_first($downloadUrlMethods)];
+
+ self::assertSame(DownloadUrlMethod::PrePackagedBinary, $downloadUrlMethod);
+
+ self::assertSame(
+ [
+ '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',
+ ],
+ $downloadUrlMethod->possibleAssetNames($package, $targetPlatform),
+ );
+ }
+
public function testComposerDefaultDownload(): void
{
$package = new Package(
@@ -116,10 +172,108 @@ public function testComposerDefaultDownload(): void
null,
);
- $downloadUrlMethod = DownloadUrlMethod::fromPackage($package, $targetPlatform);
+ $downloadUrlMethods = DownloadUrlMethod::possibleDownloadUrlMethodsForPackage($package, $targetPlatform);
+
+ self::assertCount(1, $downloadUrlMethods);
+ $downloadUrlMethod = $downloadUrlMethods[array_key_first($downloadUrlMethods)];
self::assertSame(DownloadUrlMethod::ComposerDefaultDownload, $downloadUrlMethod);
self::assertNull($downloadUrlMethod->possibleAssetNames($package, $targetPlatform));
}
+
+ public function testMultipleDownloadUrlMethods(): void
+ {
+ $composerPackage = $this->createMock(CompletePackageInterface::class);
+ $composerPackage->method('getPrettyName')->willReturn('foo/bar');
+ $composerPackage->method('getPrettyVersion')->willReturn('1.2.3');
+ $composerPackage->method('getType')->willReturn('php-ext');
+ $composerPackage->method('getPhpExt')->willReturn(['download-url-method' => ['pre-packaged-binary', 'pre-packaged-source', 'composer-default']]);
+
+ $package = Package::fromComposerCompletePackage($composerPackage);
+
+ $phpBinaryPath = $this->createMock(PhpBinaryPath::class);
+ $phpBinaryPath
+ ->method('majorMinorVersion')
+ ->willReturn('8.3');
+ $phpBinaryPath
+ ->method('debugMode')
+ ->willReturn(DebugBuild::Debug);
+
+ $targetPlatform = new TargetPlatform(
+ OperatingSystem::NonWindows,
+ OperatingSystemFamily::Linux,
+ $phpBinaryPath,
+ Architecture::x86_64,
+ ThreadSafetyMode::NonThreadSafe,
+ 1,
+ null,
+ );
+
+ $downloadUrlMethods = DownloadUrlMethod::possibleDownloadUrlMethodsForPackage($package, $targetPlatform);
+
+ self::assertCount(3, $downloadUrlMethods);
+
+ $firstMethod = $downloadUrlMethods[0];
+ self::assertSame(DownloadUrlMethod::PrePackagedBinary, $firstMethod);
+ self::assertSame(
+ [
+ 'php_bar-1.2.3_php8.3-x86_64-linux-glibc-debug.zip',
+ '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',
+ ],
+ $firstMethod->possibleAssetNames($package, $targetPlatform),
+ );
+
+ $secondMethod = $downloadUrlMethods[1];
+ self::assertSame(DownloadUrlMethod::PrePackagedSourceDownload, $secondMethod);
+ self::assertSame(
+ [
+ 'php_bar-1.2.3-src.tgz',
+ 'php_bar-1.2.3-src.zip',
+ 'bar-1.2.3.tgz',
+ ],
+ $secondMethod->possibleAssetNames($package, $targetPlatform),
+ );
+
+ $thirdMethod = $downloadUrlMethods[2];
+ self::assertSame(DownloadUrlMethod::ComposerDefaultDownload, $thirdMethod);
+ self::assertNull($thirdMethod->possibleAssetNames($package, $targetPlatform));
+ }
+
+ public function testFromComposerPackageWhenPackageKeyWasDefined(): void
+ {
+ $composerPackage = new CompletePackage('foo/bar', '1.2.3.0', '1.2.3');
+ DownloadUrlMethod::PrePackagedBinary->writeToComposerPackage($composerPackage);
+ self::assertSame(DownloadUrlMethod::PrePackagedBinary, DownloadUrlMethod::fromComposerPackage($composerPackage));
+ }
+
+ public function testFromComposerPackageWhenPackageKeyWasNotDefined(): void
+ {
+ $composerPackage = new CompletePackage('foo/bar', '1.2.3.0', '1.2.3');
+
+ $this->expectException(ValueError::class);
+ DownloadUrlMethod::fromComposerPackage($composerPackage);
+ }
+
+ public function testFromDownloadedPackage(): void
+ {
+ $composerPackage = new CompletePackage('foo/bar', '1.2.3.0', '1.2.3');
+ DownloadUrlMethod::PrePackagedSourceDownload->writeToComposerPackage($composerPackage);
+
+ $downloaded = DownloadedPackage::fromPackageAndExtractedPath(
+ new Package(
+ $composerPackage,
+ ExtensionType::PhpModule,
+ ExtensionName::normaliseFromString('foo'),
+ 'foo/bar',
+ '1.2.3',
+ null,
+ ),
+ '/path/to/extracted/source',
+ );
+
+ self::assertSame(DownloadUrlMethod::PrePackagedSourceDownload, DownloadUrlMethod::fromDownloadedPackage($downloaded));
+ }
}
diff --git a/test/unit/Downloading/Exception/CouldNotFindReleaseAssetTest.php b/test/unit/Downloading/Exception/CouldNotFindReleaseAssetTest.php
index d7b153d3..32bd161c 100644
--- a/test/unit/Downloading/Exception/CouldNotFindReleaseAssetTest.php
+++ b/test/unit/Downloading/Exception/CouldNotFindReleaseAssetTest.php
@@ -6,6 +6,7 @@
use Composer\Package\CompletePackageInterface;
use Php\Pie\DependencyResolver\Package;
+use Php\Pie\Downloading\DownloadUrlMethod;
use Php\Pie\Downloading\Exception\CouldNotFindReleaseAsset;
use Php\Pie\ExtensionName;
use Php\Pie\ExtensionType;
@@ -44,6 +45,7 @@ public function testForPackageWithRegularPackage(): void
null,
),
$package,
+ DownloadUrlMethod::PrePackagedSourceDownload,
['something.zip', 'something2.zip'],
);
@@ -72,6 +74,7 @@ public function testForPackageWithWindowsPackage(): void
WindowsCompiler::VS17,
),
$package,
+ DownloadUrlMethod::WindowsBinaryDownload,
['something.zip', 'something2.zip'],
);
@@ -89,7 +92,7 @@ public function testForPackageWithMissingTag(): void
null,
);
- $exception = CouldNotFindReleaseAsset::forPackageWithMissingTag($package);
+ $exception = CouldNotFindReleaseAsset::forPackageWithMissingTag($package, DownloadUrlMethod::PrePackagedSourceDownload);
self::assertSame('Could not find release by tag name for foo/bar:1.2.3', $exception->getMessage());
}
diff --git a/test/unit/Downloading/GithubPackageReleaseAssetsTest.php b/test/unit/Downloading/GithubPackageReleaseAssetsTest.php
index eb2985fd..4002a366 100644
--- a/test/unit/Downloading/GithubPackageReleaseAssetsTest.php
+++ b/test/unit/Downloading/GithubPackageReleaseAssetsTest.php
@@ -9,6 +9,7 @@
use Composer\Util\Http\Response;
use Composer\Util\HttpDownloader;
use Php\Pie\DependencyResolver\Package;
+use Php\Pie\Downloading\DownloadUrlMethod;
use Php\Pie\Downloading\Exception\CouldNotFindReleaseAsset;
use Php\Pie\Downloading\GithubPackageReleaseAssets;
use Php\Pie\ExtensionName;
@@ -86,6 +87,7 @@ public function testUrlIsReturnedWhenFindingWindowsDownloadUrl(): void
$targetPlatform,
$package,
$httpDownloader,
+ DownloadUrlMethod::WindowsBinaryDownload,
WindowsExtensionAssetName::zipNames(
$targetPlatform,
$package,
@@ -151,6 +153,7 @@ public function testUrlIsReturnedWhenFindingWindowsDownloadUrlWithCompilerAndThr
$targetPlatform,
$package,
$httpDownloader,
+ DownloadUrlMethod::WindowsBinaryDownload,
WindowsExtensionAssetName::zipNames(
$targetPlatform,
$package,
@@ -196,6 +199,7 @@ public function testFindWindowsDownloadUrlForPackageThrowsExceptionWhenAssetNotF
$targetPlatform,
$package,
$httpDownloader,
+ DownloadUrlMethod::WindowsBinaryDownload,
WindowsExtensionAssetName::zipNames(
$targetPlatform,
$package,
diff --git a/test/unit/Platform/LibcFlavourTest.php b/test/unit/Platform/LibcFlavourTest.php
new file mode 100644
index 00000000..231462e8
--- /dev/null
+++ b/test/unit/Platform/LibcFlavourTest.php
@@ -0,0 +1,63 @@
+createMock(PhpBinaryPath::class);
+ $php->method('debugMode')->willReturn(DebugBuild::NoDebug);
+ $php->method('majorMinorVersion')->willReturn('8.2');
+
+ $targetPlatform = new TargetPlatform(
+ OperatingSystem::NonWindows,
+ OperatingSystemFamily::Linux,
+ $php,
+ Architecture::x86_64,
+ ThreadSafetyMode::NonThreadSafe,
+ 1,
+ null,
+ );
+
+ $libc = $targetPlatform->libcFlavour();
+ self::assertSame(
+ [
+ 'php_foobar-1.2.3_php8.2-x86_64-linux-' . $libc->value . '.zip',
+ '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',
+ ],
+ PrePackagedBinaryAssetName::packageNames(
+ $targetPlatform,
+ new Package(
+ $this->createMock(CompletePackageInterface::class),
+ ExtensionType::PhpModule,
+ ExtensionName::normaliseFromString('foobar'),
+ 'foo/bar',
+ '1.2.3',
+ null,
+ ),
+ ),
+ );
+ }
+
+ public function testPackageNamesZts(): void
+ {
+ $php = $this->createMock(PhpBinaryPath::class);
+ $php->method('debugMode')->willReturn(DebugBuild::NoDebug);
+ $php->method('majorMinorVersion')->willReturn('8.3');
+
+ $targetPlatform = new TargetPlatform(
+ OperatingSystem::NonWindows,
+ OperatingSystemFamily::Linux,
+ $php,
+ Architecture::x86_64,
+ ThreadSafetyMode::ThreadSafe,
+ 1,
+ null,
+ );
+
+ $libc = $targetPlatform->libcFlavour();
+ self::assertSame(
+ [
+ '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',
+ ],
+ PrePackagedBinaryAssetName::packageNames(
+ $targetPlatform,
+ new Package(
+ $this->createMock(CompletePackageInterface::class),
+ ExtensionType::PhpModule,
+ ExtensionName::normaliseFromString('foobar'),
+ 'foo/bar',
+ '1.2.3',
+ null,
+ ),
+ ),
+ );
+ }
+
+ public function testPackageNamesDebug(): void
+ {
+ $php = $this->createMock(PhpBinaryPath::class);
+ $php->method('debugMode')->willReturn(DebugBuild::Debug);
+ $php->method('majorMinorVersion')->willReturn('8.4');
+
+ $targetPlatform = new TargetPlatform(
+ OperatingSystem::NonWindows,
+ OperatingSystemFamily::Darwin,
+ $php,
+ Architecture::arm64,
+ ThreadSafetyMode::NonThreadSafe,
+ 1,
+ null,
+ );
+
+ $libc = $targetPlatform->libcFlavour();
+ self::assertSame(
+ [
+ 'php_foobar-1.2.3_php8.4-arm64-darwin-' . $libc->value . '-debug.zip',
+ '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',
+ ],
+ PrePackagedBinaryAssetName::packageNames(
+ $targetPlatform,
+ new Package(
+ $this->createMock(CompletePackageInterface::class),
+ ExtensionType::PhpModule,
+ ExtensionName::normaliseFromString('foobar'),
+ 'foo/bar',
+ '1.2.3',
+ null,
+ ),
+ ),
+ );
+ }
+}
diff --git a/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php b/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php
index 3bf6b74e..321ac7d8 100644
--- a/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php
+++ b/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php
@@ -8,6 +8,7 @@
use Composer\Util\Platform;
use Php\Pie\ExtensionName;
use Php\Pie\Platform\Architecture;
+use Php\Pie\Platform\DebugBuild;
use Php\Pie\Platform\OperatingSystem;
use Php\Pie\Platform\OperatingSystemFamily;
use Php\Pie\Platform\TargetPhp\Exception\ExtensionIsNotLoaded;
@@ -445,4 +446,26 @@ public function testBuildProviderNullWhenNotConfigured(): void
self::assertNull($phpBinary->buildProvider());
}
+
+ public function testDebugBuildModeReturnsDebugWhenYes(): void
+ {
+ $phpBinary = $this->createPartialMock(PhpBinaryPath::class, ['phpinfo']);
+
+ $phpBinary->expects(self::once())
+ ->method('phpinfo')
+ ->willReturn('Debug Build => no');
+
+ self::assertSame(DebugBuild::NoDebug, $phpBinary->debugMode());
+ }
+
+ public function testDebugBuildModeReturnsNoDebugWhenNo(): void
+ {
+ $phpBinary = $this->createPartialMock(PhpBinaryPath::class, ['phpinfo']);
+
+ $phpBinary->expects(self::once())
+ ->method('phpinfo')
+ ->willReturn('Debug Build => yes');
+
+ self::assertSame(DebugBuild::Debug, $phpBinary->debugMode());
+ }
}
diff --git a/test/unit/Platform/TargetPlatformTest.php b/test/unit/Platform/TargetPlatformTest.php
index 3753181a..48c79df3 100644
--- a/test/unit/Platform/TargetPlatformTest.php
+++ b/test/unit/Platform/TargetPlatformTest.php
@@ -107,4 +107,12 @@ public function testLinuxPlatform(): void
self::assertSame(ThreadSafetyMode::NonThreadSafe, $platform->threadSafety);
self::assertSame(Architecture::x86_64, $platform->architecture);
}
+
+ public function testLibcFlavourIsMemoized(): void
+ {
+ self::assertSame(
+ TargetPlatform::fromPhpBinaryPath(PhpBinaryPath::fromCurrentProcess(), null)->libcFlavour(),
+ TargetPlatform::fromPhpBinaryPath(PhpBinaryPath::fromCurrentProcess(), null)->libcFlavour(),
+ );
+ }
}