Repository: php/pie (php)

Issue:
Add check for ZTS compatibility
The PIE design doc specifies:

> * The package will be checked to see if it is compatible with ZTS mode. If using
>   a `--with-php-config` with ZTS
>   enabled, but the extension specifies `"support-zts": false` in
>   its `composer.json` manifest, the installation will
>   be halted with an error explanation.

This check doesn't currently happen, but should be added.

Pull request description:
Check ZTS and NTS flags when resolving package
Fixes #35 

Patch:
diff --git a/composer.lock b/composer.lock
index 2872f4c..5bec76a 100644
--- a/composer.lock
+++ b/composer.lock
@@ -8,16 +8,16 @@
     "packages": [
         {
             "name": "composer/ca-bundle",
-            "version": "1.5.1",
+            "version": "1.5.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/composer/ca-bundle.git",
-                "reference": "063d9aa8696582f5a41dffbbaf3c81024f0a604a"
+                "reference": "48a792895a2b7a6ee65dd5442c299d7b835b6137"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/ca-bundle/zipball/063d9aa8696582f5a41dffbbaf3c81024f0a604a",
-                "reference": "063d9aa8696582f5a41dffbbaf3c81024f0a604a",
+                "url": "https://api.github.com/repos/composer/ca-bundle/zipball/48a792895a2b7a6ee65dd5442c299d7b835b6137",
+                "reference": "48a792895a2b7a6ee65dd5442c299d7b835b6137",
                 "shasum": ""
             },
             "require": {
@@ -27,8 +27,8 @@
             },
             "require-dev": {
                 "phpstan/phpstan": "^1.10",
+                "phpunit/phpunit": "^8 || ^9",
                 "psr/log": "^1.0 || ^2.0 || ^3.0",
-                "symfony/phpunit-bridge": "^4.2 || ^5",
                 "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0"
             },
             "type": "library",
@@ -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.1"
+                "source": "https://github.com/composer/ca-bundle/tree/1.5.2"
             },
             "funding": [
                 {
@@ -80,7 +80,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-07-08T15:28:20+00:00"
+            "time": "2024-09-25T07:49:53+00:00"
         },
         {
             "name": "composer/class-map-generator",
@@ -161,12 +161,12 @@
             "source": {
                 "type": "git",
                 "url": "https://github.com/composer/composer.git",
-                "reference": "bb8387e5a0680769be7a1e4a37f5057dbe135b28"
+                "reference": "dfee3dec57fe7459e89abb75a1f855109cec2bdd"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/composer/zipball/bb8387e5a0680769be7a1e4a37f5057dbe135b28",
-                "reference": "bb8387e5a0680769be7a1e4a37f5057dbe135b28",
+                "url": "https://api.github.com/repos/composer/composer/zipball/dfee3dec57fe7459e89abb75a1f855109cec2bdd",
+                "reference": "dfee3dec57fe7459e89abb75a1f855109cec2bdd",
                 "shasum": ""
             },
             "require": {
@@ -268,7 +268,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-09-17T14:14:47+00:00"
+            "time": "2024-09-25T08:12:58+00:00"
         },
         {
             "name": "composer/metadata-minifier",
@@ -420,24 +420,24 @@
         },
         {
             "name": "composer/semver",
-            "version": "3.4.2",
+            "version": "3.4.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/composer/semver.git",
-                "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6"
+                "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/semver/zipball/c51258e759afdb17f1fd1fe83bc12baaef6309d6",
-                "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6",
+                "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12",
+                "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12",
                 "shasum": ""
             },
             "require": {
                 "php": "^5.3.2 || ^7.0 || ^8.0"
             },
             "require-dev": {
-                "phpstan/phpstan": "^1.4",
-                "symfony/phpunit-bridge": "^4.2 || ^5"
+                "phpstan/phpstan": "^1.11",
+                "symfony/phpunit-bridge": "^3 || ^7"
             },
             "type": "library",
             "extra": {
@@ -481,7 +481,7 @@
             "support": {
                 "irc": "ircs://irc.libera.chat:6697/composer",
                 "issues": "https://github.com/composer/semver/issues",
-                "source": "https://github.com/composer/semver/tree/3.4.2"
+                "source": "https://github.com/composer/semver/tree/3.4.3"
             },
             "funding": [
                 {
@@ -497,7 +497,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-07-12T11:35:52+00:00"
+            "time": "2024-09-19T14:15:21+00:00"
         },
         {
             "name": "composer/spdx-licenses",
@@ -1740,16 +1740,16 @@
         },
         {
             "name": "symfony/console",
-            "version": "v6.4.11",
+            "version": "v6.4.12",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
-                "reference": "42686880adaacdad1835ee8fc2a9ec5b7bd63998"
+                "reference": "72d080eb9edf80e36c19be61f72c98ed8273b765"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/42686880adaacdad1835ee8fc2a9ec5b7bd63998",
-                "reference": "42686880adaacdad1835ee8fc2a9ec5b7bd63998",
+                "url": "https://api.github.com/repos/symfony/console/zipball/72d080eb9edf80e36c19be61f72c98ed8273b765",
+                "reference": "72d080eb9edf80e36c19be61f72c98ed8273b765",
                 "shasum": ""
             },
             "require": {
@@ -1814,7 +1814,7 @@
                 "terminal"
             ],
             "support": {
-                "source": "https://github.com/symfony/console/tree/v6.4.11"
+                "source": "https://github.com/symfony/console/tree/v6.4.12"
             },
             "funding": [
                 {
@@ -1830,7 +1830,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-08-15T22:48:29+00:00"
+            "time": "2024-09-20T08:15:52+00:00"
         },
         {
             "name": "symfony/deprecation-contracts",
@@ -1901,16 +1901,16 @@
         },
         {
             "name": "symfony/filesystem",
-            "version": "v6.4.9",
+            "version": "v6.4.12",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/filesystem.git",
-                "reference": "b51ef8059159330b74a4d52f68e671033c0fe463"
+                "reference": "f810e3cbdf7fdc35983968523d09f349fa9ada12"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/filesystem/zipball/b51ef8059159330b74a4d52f68e671033c0fe463",
-                "reference": "b51ef8059159330b74a4d52f68e671033c0fe463",
+                "url": "https://api.github.com/repos/symfony/filesystem/zipball/f810e3cbdf7fdc35983968523d09f349fa9ada12",
+                "reference": "f810e3cbdf7fdc35983968523d09f349fa9ada12",
                 "shasum": ""
             },
             "require": {
@@ -1947,7 +1947,7 @@
             "description": "Provides basic utilities for the filesystem",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/filesystem/tree/v6.4.9"
+                "source": "https://github.com/symfony/filesystem/tree/v6.4.12"
             },
             "funding": [
                 {
@@ -1963,7 +1963,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-06-28T09:49:33+00:00"
+            "time": "2024-09-16T16:01:33+00:00"
         },
         {
             "name": "symfony/finder",
@@ -2581,16 +2581,16 @@
         },
         {
             "name": "symfony/process",
-            "version": "v6.4.8",
+            "version": "v6.4.12",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/process.git",
-                "reference": "8d92dd79149f29e89ee0f480254db595f6a6a2c5"
+                "reference": "3f94e5f13ff58df371a7ead461b6e8068900fbb3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/process/zipball/8d92dd79149f29e89ee0f480254db595f6a6a2c5",
-                "reference": "8d92dd79149f29e89ee0f480254db595f6a6a2c5",
+                "url": "https://api.github.com/repos/symfony/process/zipball/3f94e5f13ff58df371a7ead461b6e8068900fbb3",
+                "reference": "3f94e5f13ff58df371a7ead461b6e8068900fbb3",
                 "shasum": ""
             },
             "require": {
@@ -2622,7 +2622,7 @@
             "description": "Executes commands in sub-processes",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/process/tree/v6.4.8"
+                "source": "https://github.com/symfony/process/tree/v6.4.12"
             },
             "funding": [
                 {
@@ -2638,7 +2638,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-05-31T14:49:08+00:00"
+            "time": "2024-09-17T12:47:12+00:00"
         },
         {
             "name": "symfony/service-contracts",
@@ -2725,16 +2725,16 @@
         },
         {
             "name": "symfony/string",
-            "version": "v6.4.11",
+            "version": "v6.4.12",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/string.git",
-                "reference": "5bc3eb632cf9c8dbfd6529d89be9950d1518883b"
+                "reference": "f8a1ccebd0997e16112dfecfd74220b78e5b284b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/string/zipball/5bc3eb632cf9c8dbfd6529d89be9950d1518883b",
-                "reference": "5bc3eb632cf9c8dbfd6529d89be9950d1518883b",
+                "url": "https://api.github.com/repos/symfony/string/zipball/f8a1ccebd0997e16112dfecfd74220b78e5b284b",
+                "reference": "f8a1ccebd0997e16112dfecfd74220b78e5b284b",
                 "shasum": ""
             },
             "require": {
@@ -2791,7 +2791,7 @@
                 "utf8"
             ],
             "support": {
-                "source": "https://github.com/symfony/string/tree/v6.4.11"
+                "source": "https://github.com/symfony/string/tree/v6.4.12"
             },
             "funding": [
                 {
@@ -2807,7 +2807,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-08-12T09:55:28+00:00"
+            "time": "2024-09-20T08:15:52+00:00"
         },
         {
             "name": "webmozart/assert",
@@ -3998,16 +3998,16 @@
         },
         {
             "name": "phpstan/phpdoc-parser",
-            "version": "1.30.1",
+            "version": "1.31.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpstan/phpdoc-parser.git",
-                "reference": "51b95ec8670af41009e2b2b56873bad96682413e"
+                "reference": "249f15fb843bf240cf058372dad29e100cee6c17"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/51b95ec8670af41009e2b2b56873bad96682413e",
-                "reference": "51b95ec8670af41009e2b2b56873bad96682413e",
+                "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/249f15fb843bf240cf058372dad29e100cee6c17",
+                "reference": "249f15fb843bf240cf058372dad29e100cee6c17",
                 "shasum": ""
             },
             "require": {
@@ -4039,9 +4039,9 @@
             "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/1.30.1"
+                "source": "https://github.com/phpstan/phpdoc-parser/tree/1.31.0"
             },
-            "time": "2024-09-07T20:13:05+00:00"
+            "time": "2024-09-22T11:32:18+00:00"
         },
         {
             "name": "phpunit/php-code-coverage",
@@ -4366,16 +4366,16 @@
         },
         {
             "name": "phpunit/phpunit",
-            "version": "10.5.34",
+            "version": "10.5.35",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "3c69d315bdf79080c8e115b69d1961c6905b0e18"
+                "reference": "7ac8b4e63f456046dcb4c9787da9382831a1874b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3c69d315bdf79080c8e115b69d1961c6905b0e18",
-                "reference": "3c69d315bdf79080c8e115b69d1961c6905b0e18",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/7ac8b4e63f456046dcb4c9787da9382831a1874b",
+                "reference": "7ac8b4e63f456046dcb4c9787da9382831a1874b",
                 "shasum": ""
             },
             "require": {
@@ -4447,7 +4447,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.34"
+                "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.35"
             },
             "funding": [
                 {
@@ -4463,7 +4463,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-09-13T05:19:38+00:00"
+            "time": "2024-09-19T10:52:21+00:00"
         },
         {
             "name": "psalm/plugin-phpunit",
@@ -5576,16 +5576,16 @@
         },
         {
             "name": "squizlabs/php_codesniffer",
-            "version": "3.10.2",
+            "version": "3.10.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
-                "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017"
+                "reference": "62d32998e820bddc40f99f8251958aed187a5c9c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/86e5f5dd9a840c46810ebe5ff1885581c42a3017",
-                "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017",
+                "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/62d32998e820bddc40f99f8251958aed187a5c9c",
+                "reference": "62d32998e820bddc40f99f8251958aed187a5c9c",
                 "shasum": ""
             },
             "require": {
@@ -5652,7 +5652,7 @@
                     "type": "open_collective"
                 }
             ],
-            "time": "2024-07-21T23:26:44+00:00"
+            "time": "2024-09-18T10:38:58+00:00"
         },
         {
             "name": "theseer/tokenizer",
diff --git a/docs/extension-maintainers.md b/docs/extension-maintainers.md
index 61f9ebf..cbd9241 100644
--- a/docs/extension-maintainers.md
+++ b/docs/extension-maintainers.md
@@ -57,9 +57,16 @@ An example of `extension-name` being used:
 `priority` is currently not used, but will form part of the `ini` filename to
 control ordering of extensions, if the target platform uses it.
 
-#### `support-zts`
+#### `support-zts` and `support-nts`
 
-Indicates whether the extension supports Zend Thread-Safe (ZTS) mode or not.
+Indicates whether the extension supports Zend Thread-Safe (ZTS) and non-Thread-
+Safe (NTS) modes. Both these flags default to `true` if not specified, but if
+your extension does not support either mode, it should be specified, and will
+mean the extension will not be installable on the target platform.
+
+Theoretically, it is possible to specify `false` for both `support-zts` and
+`support-nts`, but this will mean your package cannot be installed anywhere, so
+is not advisable.
 
 #### `configure-options`
 
diff --git a/src/DependencyResolver/IncompatibleThreadSafetyMode.php b/src/DependencyResolver/IncompatibleThreadSafetyMode.php
new file mode 100644
index 0000000..6990efa
--- /dev/null
+++ b/src/DependencyResolver/IncompatibleThreadSafetyMode.php
@@ -0,0 +1,20 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Php\Pie\DependencyResolver;
+
+use RuntimeException;
+
+class IncompatibleThreadSafetyMode extends RuntimeException
+{
+    public static function ztsExtensionOnNtsPlatform(): self
+    {
+        return new self('This extension does not support being installed on a non-Thread Safe PHP installation');
+    }
+
+    public static function ntsExtensionOnZtsPlatform(): self
+    {
+        return new self('This extension does not support being installed on a Thread Safe PHP installation');
+    }
+}
diff --git a/src/DependencyResolver/Package.php b/src/DependencyResolver/Package.php
index 30d203b..58d9038 100644
--- a/src/DependencyResolver/Package.php
+++ b/src/DependencyResolver/Package.php
@@ -29,6 +29,8 @@ final class Package
         public readonly array $configureOptions,
         public readonly string|null $notificationUrl,
         public readonly string $notificationVersion,
+        public readonly bool $supportZts,
+        public readonly bool $supportNts,
     ) {
     }
 
@@ -43,6 +45,14 @@ final class Package
             )
             : [];
 
+        $supportZts = $phpExtOptions !== null && array_key_exists('support-zts', $phpExtOptions)
+            ? $phpExtOptions['support-zts']
+            : true;
+
+        $supportNts = $phpExtOptions !== null && array_key_exists('support-nts', $phpExtOptions)
+            ? $phpExtOptions['support-nts']
+            : true;
+
         return new self(
             ExtensionType::tryFrom($completePackage->getType()) ?? ExtensionType::PhpModule,
             ExtensionName::determineFromComposerPackage($completePackage),
@@ -52,6 +62,8 @@ final class Package
             $configureOptions,
             $completePackage->getNotificationUrl(),
             $completePackage->getVersion(),
+            $supportZts,
+            $supportNts,
         );
     }
 
diff --git a/src/DependencyResolver/ResolveDependencyWithComposer.php b/src/DependencyResolver/ResolveDependencyWithComposer.php
index e6e4996..3b6f10d 100644
--- a/src/DependencyResolver/ResolveDependencyWithComposer.php
+++ b/src/DependencyResolver/ResolveDependencyWithComposer.php
@@ -12,6 +12,7 @@ use Composer\Repository\RepositorySet;
 use Php\Pie\ExtensionType;
 use Php\Pie\Platform\TargetPhp\ResolveTargetPhpToPlatformRepository;
 use Php\Pie\Platform\TargetPlatform;
+use Php\Pie\Platform\ThreadSafetyMode;
 
 use function preg_match;
 
@@ -61,6 +62,21 @@ final class ResolveDependencyWithComposer implements DependencyResolver
             throw UnableToResolveRequirement::toPhpOrZendExtension($package, $packageName, $requestedVersion);
         }
 
-        return Package::fromComposerCompletePackage($package);
+        $package = Package::fromComposerCompletePackage($package);
+
+        $this->assertCompatibleThreadSafetyMode($targetPlatform->threadSafety, $package);
+
+        return $package;
+    }
+
+    private function assertCompatibleThreadSafetyMode(ThreadSafetyMode $threadSafetyMode, Package $resolvedPackage): void
+    {
+        if ($threadSafetyMode === ThreadSafetyMode::NonThreadSafe && ! $resolvedPackage->supportNts) {
+            throw IncompatibleThreadSafetyMode::ztsExtensionOnNtsPlatform();
+        }
+
+        if ($threadSafetyMode === ThreadSafetyMode::ThreadSafe && ! $resolvedPackage->supportZts) {
+            throw IncompatibleThreadSafetyMode::ntsExtensionOnZtsPlatform();
+        }
     }
 }


Test patch:
diff --git a/test/integration/Building/UnixBuildTest.php b/test/integration/Building/UnixBuildTest.php
index 08e6b58..3099084 100644
--- a/test/integration/Building/UnixBuildTest.php
+++ b/test/integration/Building/UnixBuildTest.php
@@ -41,6 +41,8 @@ final class UnixBuildTest extends TestCase
                 [ConfigureOption::fromComposerJsonDefinition(['name' => 'enable-pie_test_ext'])],
                 null,
                 '0.1.0.0',
+                true,
+                true,
             ),
             self::TEST_EXTENSION_PATH,
         );
diff --git a/test/integration/Installing/UnixInstallTest.php b/test/integration/Installing/UnixInstallTest.php
index efd9eb3..58c24f4 100644
--- a/test/integration/Installing/UnixInstallTest.php
+++ b/test/integration/Installing/UnixInstallTest.php
@@ -48,6 +48,8 @@ final class UnixInstallTest extends TestCase
                 [ConfigureOption::fromComposerJsonDefinition(['name' => 'enable-pie_test_ext'])],
                 null,
                 '0.1.0.0',
+                true,
+                true,
             ),
             self::TEST_EXTENSION_PATH,
         );
diff --git a/test/integration/Installing/WindowsInstallTest.php b/test/integration/Installing/WindowsInstallTest.php
index 9703a84..f895ea2 100644
--- a/test/integration/Installing/WindowsInstallTest.php
+++ b/test/integration/Installing/WindowsInstallTest.php
@@ -54,6 +54,8 @@ final class WindowsInstallTest extends TestCase
                 [],
                 null,
                 '1.2.3.0',
+                true,
+                true,
             ),
             self::TEST_EXTENSION_PATH,
         );
diff --git a/test/unit/Command/CommandHelperTest.php b/test/unit/Command/CommandHelperTest.php
index db3ae9c..f748d2d 100644
--- a/test/unit/Command/CommandHelperTest.php
+++ b/test/unit/Command/CommandHelperTest.php
@@ -111,6 +111,8 @@ final class CommandHelperTest extends TestCase
                 [],
                 null,
                 '1.2.3.0',
+                true,
+                true,
             ));
 
         $downloadAndExtract->expects(self::once())
@@ -159,6 +161,8 @@ final class CommandHelperTest extends TestCase
             ],
             null,
             '1.0.0.0',
+            true,
+            true,
         );
         $inputDefinition = new InputDefinition();
         $inputDefinition->addOption(new InputOption('with-stuff', null, InputOption::VALUE_REQUIRED));
diff --git a/test/unit/DependencyResolver/ResolveDependencyWithComposerTest.php b/test/unit/DependencyResolver/ResolveDependencyWithComposerTest.php
index e4d0201..8dd70a1 100644
--- a/test/unit/DependencyResolver/ResolveDependencyWithComposerTest.php
+++ b/test/unit/DependencyResolver/ResolveDependencyWithComposerTest.php
@@ -6,9 +6,12 @@ namespace Php\PieUnitTest\DependencyResolver;
 
 use Composer\Composer;
 use Composer\IO\NullIO;
+use Composer\Package\CompletePackage;
+use Composer\Repository\ArrayRepository;
 use Composer\Repository\CompositeRepository;
 use Composer\Repository\RepositoryFactory;
 use Composer\Repository\RepositoryManager;
+use Php\Pie\DependencyResolver\IncompatibleThreadSafetyMode;
 use Php\Pie\DependencyResolver\ResolveDependencyWithComposer;
 use Php\Pie\DependencyResolver\UnableToResolveRequirement;
 use Php\Pie\Platform\Architecture;
@@ -108,4 +111,88 @@ final class ResolveDependencyWithComposerTest extends TestCase
             $version,
         );
     }
+
+    public function testZtsOnlyPackageCannotBeInstalledOnNtsSystem(): void
+    {
+        $pkg = new CompletePackage('test-vendor/test-package', '1.0.0.0', '1.0.0');
+        $pkg->setType('php-ext');
+        $pkg->setPhpExt([
+            'extension-name' => 'testext',
+            'support-nts' => false,
+        ]);
+
+        $repoManager = $this->createMock(RepositoryManager::class);
+        $repoManager->method('getRepositories')
+            ->willReturn([new ArrayRepository([$pkg])]);
+
+        $this->composer = $this->createMock(Composer::class);
+        $this->composer->method('getRepositoryManager')
+            ->willReturn($repoManager);
+
+        $phpBinaryPath = $this->createMock(PhpBinaryPath::class);
+        $phpBinaryPath->expects(self::any())
+            ->method('version')
+            ->willReturn('8.3.0');
+
+        $targetPlatform = new TargetPlatform(
+            OperatingSystem::NonWindows,
+            $phpBinaryPath,
+            Architecture::x86_64,
+            ThreadSafetyMode::NonThreadSafe,
+            null,
+        );
+
+        $this->expectException(IncompatibleThreadSafetyMode::class);
+        $this->expectExceptionMessage('This extension does not support being installed on a non-Thread Safe PHP installation');
+        (new ResolveDependencyWithComposer(
+            $this->composer,
+            $this->resolveTargetPhpToPlatformRepository,
+        ))(
+            $targetPlatform,
+            'test-vendor/test-package',
+            '1.0.0',
+        );
+    }
+
+    public function testNtsOnlyPackageCannotBeInstalledOnZtsSystem(): void
+    {
+        $pkg = new CompletePackage('test-vendor/test-package', '1.0.0.0', '1.0.0');
+        $pkg->setType('php-ext');
+        $pkg->setPhpExt([
+            'extension-name' => 'testext',
+            'support-zts' => false,
+        ]);
+
+        $repoManager = $this->createMock(RepositoryManager::class);
+        $repoManager->method('getRepositories')
+            ->willReturn([new ArrayRepository([$pkg])]);
+
+        $this->composer = $this->createMock(Composer::class);
+        $this->composer->method('getRepositoryManager')
+            ->willReturn($repoManager);
+
+        $phpBinaryPath = $this->createMock(PhpBinaryPath::class);
+        $phpBinaryPath->expects(self::any())
+            ->method('version')
+            ->willReturn('8.3.0');
+
+        $targetPlatform = new TargetPlatform(
+            OperatingSystem::NonWindows,
+            $phpBinaryPath,
+            Architecture::x86_64,
+            ThreadSafetyMode::ThreadSafe,
+            null,
+        );
+
+        $this->expectException(IncompatibleThreadSafetyMode::class);
+        $this->expectExceptionMessage('This extension does not support being installed on a Thread Safe PHP installation');
+        (new ResolveDependencyWithComposer(
+            $this->composer,
+            $this->resolveTargetPhpToPlatformRepository,
+        ))(
+            $targetPlatform,
+            'test-vendor/test-package',
+            '1.0.0',
+        );
+    }
 }
diff --git a/test/unit/Downloading/AddAuthenticationHeaderTest.php b/test/unit/Downloading/AddAuthenticationHeaderTest.php
index 4ba1e22..ecf3241 100644
--- a/test/unit/Downloading/AddAuthenticationHeaderTest.php
+++ b/test/unit/Downloading/AddAuthenticationHeaderTest.php
@@ -42,6 +42,8 @@ final class AddAuthenticationHeaderTest extends TestCase
                 [],
                 null,
                 '1.2.3.0',
+                true,
+                true,
             ),
             $authHelper,
         );
@@ -67,6 +69,8 @@ final class AddAuthenticationHeaderTest extends TestCase
             [],
             null,
             '1.2.3.0',
+            true,
+            true,
         );
 
         $this->expectException(RuntimeException::class);
diff --git a/test/unit/Downloading/DownloadedPackageTest.php b/test/unit/Downloading/DownloadedPackageTest.php
index de2ade8..f685e72 100644
--- a/test/unit/Downloading/DownloadedPackageTest.php
+++ b/test/unit/Downloading/DownloadedPackageTest.php
@@ -27,6 +27,8 @@ final class DownloadedPackageTest extends TestCase
             [],
             null,
             '1.2.3.0',
+            true,
+            true,
         );
 
         $extractedSourcePath = uniqid('/path/to/downloaded/package', true);
diff --git a/test/unit/Downloading/Exception/CouldNotFindReleaseAssetTest.php b/test/unit/Downloading/Exception/CouldNotFindReleaseAssetTest.php
index ef8fe60..f492b6f 100644
--- a/test/unit/Downloading/Exception/CouldNotFindReleaseAssetTest.php
+++ b/test/unit/Downloading/Exception/CouldNotFindReleaseAssetTest.php
@@ -30,6 +30,8 @@ final class CouldNotFindReleaseAssetTest extends TestCase
             [],
             null,
             '1.2.3.0',
+            true,
+            true,
         );
 
         $exception = CouldNotFindReleaseAsset::forPackage($package, ['something.zip', 'something2.zip']);
@@ -48,6 +50,8 @@ final class CouldNotFindReleaseAssetTest extends TestCase
             [],
             null,
             '1.2.3.0',
+            true,
+            true,
         );
 
         $exception = CouldNotFindReleaseAsset::forPackageWithMissingTag($package);
diff --git a/test/unit/Downloading/GithubPackageReleaseAssetsTest.php b/test/unit/Downloading/GithubPackageReleaseAssetsTest.php
index 51b548d..d65db25 100644
--- a/test/unit/Downloading/GithubPackageReleaseAssetsTest.php
+++ b/test/unit/Downloading/GithubPackageReleaseAssetsTest.php
@@ -76,6 +76,8 @@ final class GithubPackageReleaseAssetsTest extends TestCase
             [],
             null,
             '1.2.3.0',
+            true,
+            true,
         );
 
         $releaseAssets = new GithubPackageReleaseAssets($authHelper, $guzzleMockClient, 'https://test-github-api-base-url.thephp.foundation');
@@ -130,6 +132,8 @@ final class GithubPackageReleaseAssetsTest extends TestCase
             [],
             null,
             '1.2.3.0',
+            true,
+            true,
         );
 
         $releaseAssets = new GithubPackageReleaseAssets($authHelper, $guzzleMockClient, 'https://test-github-api-base-url.thephp.foundation');
@@ -170,6 +174,8 @@ final class GithubPackageReleaseAssetsTest extends TestCase
             [],
             null,
             '1.2.3.0',
+            true,
+            true,
         );
 
         $releaseAssets = new GithubPackageReleaseAssets($authHelper, $guzzleMockClient, 'https://test-github-api-base-url.thephp.foundation');
diff --git a/test/unit/Downloading/UnixDownloadAndExtractTest.php b/test/unit/Downloading/UnixDownloadAndExtractTest.php
index d650f0a..5b5a5eb 100644
--- a/test/unit/Downloading/UnixDownloadAndExtractTest.php
+++ b/test/unit/Downloading/UnixDownloadAndExtractTest.php
@@ -69,6 +69,8 @@ final class UnixDownloadAndExtractTest extends TestCase
             [],
             null,
             '1.2.3.0',
+            true,
+            true,
         );
 
         $downloadedPackage = $unixDownloadAndExtract->__invoke($targetPlatform, $requestedPackage);
diff --git a/test/unit/Downloading/WindowsDownloadAndExtractTest.php b/test/unit/Downloading/WindowsDownloadAndExtractTest.php
index d266fb9..1b2ac22 100644
--- a/test/unit/Downloading/WindowsDownloadAndExtractTest.php
+++ b/test/unit/Downloading/WindowsDownloadAndExtractTest.php
@@ -84,6 +84,8 @@ final class WindowsDownloadAndExtractTest extends TestCase
             [],
             null,
             '1.2.3.0',
+            true,
+            true,
         );
 
         $downloadedPackage = $windowsDownloadAndExtract->__invoke($targetPlatform, $requestedPackage);
diff --git a/test/unit/Installing/InstallNotification/SendInstallNotificationUsingGuzzleTest.php b/test/unit/Installing/InstallNotification/SendInstallNotificationUsingGuzzleTest.php
index 97858ac..425c804 100644
--- a/test/unit/Installing/InstallNotification/SendInstallNotificationUsingGuzzleTest.php
+++ b/test/unit/Installing/InstallNotification/SendInstallNotificationUsingGuzzleTest.php
@@ -69,6 +69,8 @@ final class SendInstallNotificationUsingGuzzleTest extends TestCase
                 [],
                 $notificationUrl,
                 '1.2.3.0',
+                true,
+                true,
             ),
             '/path/to/extracted',
         );
diff --git a/test/unit/Platform/WindowsExtensionAssetNameTest.php b/test/unit/Platform/WindowsExtensionAssetNameTest.php
index 6bcc448..06cee21 100644
--- a/test/unit/Platform/WindowsExtensionAssetNameTest.php
+++ b/test/unit/Platform/WindowsExtensionAssetNameTest.php
@@ -47,6 +47,8 @@ final class WindowsExtensionAssetNameTest extends TestCase
             [],
             null,
             '1.2.3.0',
+            true,
+            true,
         );
     }