aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/shirabe-php-rpc/php/worker.php134
-rw-r--r--crates/shirabe-php-rpc/src/lib.rs522
-rw-r--r--crates/shirabe/src/command/base_dependency_command.rs4
-rw-r--r--crates/shirabe/src/command/check_platform_reqs_command.rs2
-rw-r--r--crates/shirabe/src/command/completion_trait.rs6
-rw-r--r--crates/shirabe/src/command/create_project_command.rs2
-rw-r--r--crates/shirabe/src/command/diagnose_command.rs2
-rw-r--r--crates/shirabe/src/command/init_command.rs2
-rw-r--r--crates/shirabe/src/command/package_discovery_trait.rs2
-rw-r--r--crates/shirabe/src/command/require_command.rs8
-rw-r--r--crates/shirabe/src/command/search_command.rs2
-rw-r--r--crates/shirabe/src/command/show_command.rs8
-rw-r--r--crates/shirabe/src/command/suggests_command.rs4
-rw-r--r--crates/shirabe/src/dependency_resolver/problem.rs2
-rw-r--r--crates/shirabe/src/installer.rs2
-rw-r--r--crates/shirabe/src/platform.rs6
-rw-r--r--crates/shirabe/src/platform/runtime.rs237
-rw-r--r--crates/shirabe/src/repository/platform_repository.rs251
-rw-r--r--crates/shirabe/tests/package/version/version_selector_test.rs8
-rw-r--r--crates/shirabe/tests/platform/main.rs1
-rw-r--r--crates/shirabe/tests/platform/runtime_test.rs28
-rw-r--r--crates/shirabe/tests/repository/platform_repository_test.rs306
22 files changed, 778 insertions, 761 deletions
diff --git a/crates/shirabe-php-rpc/php/worker.php b/crates/shirabe-php-rpc/php/worker.php
index 15b38770..578c30d0 100644
--- a/crates/shirabe-php-rpc/php/worker.php
+++ b/crates/shirabe-php-rpc/php/worker.php
@@ -455,6 +455,77 @@ ShirabeRpcRuntime::$stubAutoloader = static function (string $class): void {
};
spl_autoload_register(ShirabeRpcRuntime::$stubAutoloader, true, true);
+/**
+ * Port of Composer\Platform\Runtime, whose runtime queries feed the `platform` payload. Its
+ * parseHtmlExtensionInfo() has no counterpart here: the worker is always the CLI SAPI, so
+ * getExtensionInfo() never takes the branch that reformats phpinfo()'s HTML output.
+ */
+final class ShirabePlatformRuntime
+{
+ /** The constants the `platform` payload reports, named ltrim($class.'::'.$constant, ':'). */
+ public const CONSTANTS = [
+ 'PHP_VERSION',
+ 'PHP_DEBUG',
+ 'PHP_ZTS',
+ 'PHP_INT_SIZE',
+ 'AF_INET6',
+ 'GD_VERSION',
+ 'GMP_VERSION',
+ 'ICONV_VERSION',
+ 'INTL_ICU_VERSION',
+ 'LIBXML_DOTTED_VERSION',
+ 'MB_ONIGURUMA_VERSION',
+ 'OPENSSL_VERSION_TEXT',
+ 'PCRE_VERSION',
+ 'PGSQL_LIBPQ_VERSION',
+ 'RD_KAFKA_VERSION',
+ 'SODIUM_LIBRARY_VERSION',
+ 'LIBXSLT_DOTTED_VERSION',
+ 'ZipArchive::LIBZIP_VERSION',
+ 'ZLIB_VERSION',
+ ];
+
+ /** The extensions whose info() output the `platform` payload reports when they are loaded. */
+ public const EXTENSION_INFO = [
+ 'amqp',
+ 'bz2',
+ 'curl',
+ 'date',
+ 'fileinfo',
+ 'gd',
+ 'intl',
+ 'ldap',
+ 'mbstring',
+ 'memcached',
+ 'mongodb',
+ 'mysqlnd',
+ 'pcre',
+ 'pdo_mysql',
+ 'pdo_pgsql',
+ 'pdo_sqlite',
+ 'pgsql',
+ 'pq',
+ 'sqlite3',
+ 'ssh2',
+ 'xsl',
+ 'yaml',
+ 'zlib',
+ ];
+
+ /** The classes the `platform` payload reports the existence of. */
+ public const CLASSES = ['ResourceBundle', 'IntlChar'];
+
+ public static function getExtensionInfo(string $extension): string
+ {
+ $reflector = new ReflectionExtension($extension);
+
+ ob_start();
+ $reflector->info();
+
+ return (string) ob_get_clean();
+ }
+}
+
// Port of Composer\XdebugHandler\XdebugHandler::setXdebugDetails(), which the diagnose payload
// reports as `xdebug_active`.
$xdebug_active = static function (): bool {
@@ -491,9 +562,6 @@ $xdebug_active = static function (): bool {
ShirabeRpcRuntime::$dispatch = [
'constant' => static fn($args) => defined($args[0]) ? constant($args[0]) : null,
- 'inet_pton' => static fn($args) => @inet_pton($args[0]),
- 'curl_version' => static fn($args) => function_exists('curl_version') ? (curl_version()['version'] ?? null) : null,
- 'get_loaded_extensions' => static fn($args) => get_loaded_extensions(),
'get_all_ini_files' => static function ($args) {
$paths = [(string) php_ini_loaded_file()];
$scanned = php_ini_scanned_files();
@@ -502,14 +570,60 @@ ShirabeRpcRuntime::$dispatch = [
}
return $paths;
},
- 'extension_info' => static function ($args) {
- if (!extension_loaded($args[0])) {
- return '';
+ 'platform' => static function ($args) {
+ $extensions = get_loaded_extensions();
+
+ $extension_versions = [];
+ foreach ($extensions as $extension) {
+ $version = phpversion($extension);
+ $extension_versions[$extension] = $version !== false ? $version : '0';
}
- $re = new ReflectionExtension($args[0]);
- ob_start();
- $re->info();
- return (string) ob_get_clean();
+
+ $extension_info = [];
+ foreach (ShirabePlatformRuntime::EXTENSION_INFO as $extension) {
+ if (in_array($extension, $extensions, true)) {
+ $extension_info[$extension] = ShirabePlatformRuntime::getExtensionInfo($extension);
+ }
+ }
+
+ // Only the defined constants carry a value; `constant_names` tells the Rust side which
+ // names were looked up, so a name it reads but this list omits is an error rather than a
+ // silently undefined constant.
+ $constants = [];
+ foreach (ShirabePlatformRuntime::CONSTANTS as $constant) {
+ if (defined($constant)) {
+ $constants[$constant] = constant($constant);
+ }
+ }
+
+ $classes = [];
+ foreach (ShirabePlatformRuntime::CLASSES as $class) {
+ $classes[$class] = class_exists($class, false);
+ }
+
+ // The values below stand in for the PHP objects and calls PlatformRepository reaches
+ // through Composer\Platform\Runtime::invoke()/construct(), reduced to the entries it reads.
+ $resource_bundle = null;
+ if ($classes['ResourceBundle']) {
+ $bundle = ResourceBundle::create('root', 'ICUDATA', false);
+ if ($bundle !== null) {
+ $resource_bundle = ['Version' => $bundle->get('Version')];
+ }
+ }
+
+ return [
+ 'extensions' => $extensions,
+ 'extension_versions' => $extension_versions,
+ 'extension_info' => $extension_info,
+ 'constant_names' => ShirabePlatformRuntime::CONSTANTS,
+ 'constants' => $constants,
+ 'classes' => $classes,
+ 'curl_version' => extension_loaded('curl') ? curl_version() : null,
+ 'inet_pton_ipv6' => @inet_pton('::'),
+ 'resource_bundle' => $resource_bundle,
+ 'intl_char_unicode_version' => $classes['IntlChar'] ? IntlChar::getUnicodeVersion() : null,
+ 'imagick' => extension_loaded('imagick') ? (new Imagick())->getVersion() : null,
+ ];
},
'diagnose' => static function ($args) use ($xdebug_active) {
$extensions = [];
diff --git a/crates/shirabe-php-rpc/src/lib.rs b/crates/shirabe-php-rpc/src/lib.rs
index 69790695..e2f5fd1e 100644
--- a/crates/shirabe-php-rpc/src/lib.rs
+++ b/crates/shirabe-php-rpc/src/lib.rs
@@ -30,49 +30,11 @@ pub fn get_php_binary() -> String {
}
}
-/// PHP `defined($name)`.
-pub fn has_constant(name: &str) -> bool {
- match call("defined", name) {
- PhpMixed::Bool(b) => b,
- other => panic!("PHP RPC: `defined` did not return a bool: {other:?}"),
- }
-}
-
/// PHP `constant($name)`.
-pub fn get_constant(name: &str) -> PhpMixed {
+fn get_constant(name: &str) -> PhpMixed {
call("constant", name)
}
-/// PHP `class_exists($name)`, with autoloading, as the runtime sees it.
-pub fn class_exists(name: &str) -> bool {
- match call("class_exists", name) {
- PhpMixed::Bool(exists) => exists,
- other => panic!("PHP RPC: `class_exists` returned an unexpected value: {other:?}"),
- }
-}
-
-/// PHP `inet_pton($address)`.
-pub fn inet_pton(address: &str) -> PhpMixed {
- call("inet_pton", address)
-}
-
-/// PHP `curl_version()['version']`.
-pub fn curl_version() -> Option<String> {
- match call("curl_version", "") {
- PhpMixed::String(s) => Some(s),
- PhpMixed::Null => None,
- other => panic!("PHP RPC: `curl_version` returned an unexpected value: {other:?}"),
- }
-}
-
-/// PHP `(new \ReflectionExtension($name))->info()` output.
-pub fn get_extension_info(name: &str) -> String {
- match call("extension_info", name) {
- PhpMixed::String(s) => s,
- other => panic!("PHP RPC: `extension_info` did not return a string: {other:?}"),
- }
-}
-
/// `curl_version()`, together with the `CURL_*` constants the `diagnose` command consults. Every
/// `Option` field is `None` when the corresponding array key or constant is absent.
#[derive(Debug)]
@@ -89,7 +51,7 @@ pub struct Curl {
}
/// Everything the `diagnose` command needs to know about the PHP runtime, fetched in a single
-/// round trip because the command would otherwise probe the same runtime dozens of times.
+/// round trip because the command would otherwise query the same runtime dozens of times.
#[derive(Debug)]
pub struct Diagnostics {
pub php_version: String,
@@ -118,28 +80,28 @@ pub struct Diagnostics {
}
impl Diagnostics {
- /// PHP `extension_loaded($name)`. Only the extensions the worker probes can be asked about;
+ /// PHP `extension_loaded($name)`. Only the extensions the worker reports can be asked about;
/// any other name is a bug in the caller, not a missing extension.
pub fn extension_loaded(&self, name: &str) -> bool {
*self.extensions.get(name).unwrap_or_else(|| {
- panic!("PHP RPC: extension `{name}` is not probed by the diagnose payload")
+ panic!("PHP RPC: extension `{name}` is not reported by the diagnose payload")
})
}
- /// PHP `function_exists($name)`. See [`Diagnostics::extension_loaded`] for the fixed probe set.
+ /// PHP `function_exists($name)`. See [`Diagnostics::extension_loaded`] for the fixed set of names.
pub fn function_exists(&self, name: &str) -> bool {
*self.functions.get(name).unwrap_or_else(|| {
- panic!("PHP RPC: function `{name}` is not probed by the diagnose payload")
+ panic!("PHP RPC: function `{name}` is not reported by the diagnose payload")
})
}
/// PHP `ini_get($option)`, with PHP's `false` (no such setting) mapped to `None`. See
- /// [`Diagnostics::extension_loaded`] for the fixed probe set.
+ /// [`Diagnostics::extension_loaded`] for the fixed set of names.
pub fn ini_get(&self, option: &str) -> Option<&str> {
self.ini_settings
.get(option)
.unwrap_or_else(|| {
- panic!("PHP RPC: ini setting `{option}` is not probed by the diagnose payload")
+ panic!("PHP RPC: ini setting `{option}` is not reported by the diagnose payload")
})
.as_deref()
}
@@ -155,123 +117,416 @@ pub fn get_diagnostics() -> &'static Diagnostics {
let payload = payload
.as_array()
.unwrap_or_else(|| panic!("PHP RPC: `diagnose` did not return an array: {payload:?}"));
+ let payload = &Payload {
+ what: "diagnose",
+ map: payload,
+ };
Diagnostics {
- php_version: string_field(payload, "php_version"),
- php_version_id: int_field(payload, "php_version_id"),
- php_binary: nullable_string_field(payload, "php_binary"),
- openssl_version_text: nullable_string_field(payload, "openssl_version_text"),
- openssl_version_number: int_field(payload, "openssl_version_number"),
- has_hhvm_version: bool_field(payload, "has_hhvm_version"),
- has_php_windows_version_build: bool_field(payload, "has_php_windows_version_build"),
- xdebug_active: bool_field(payload, "xdebug_active"),
- ioncube_loader_iversion: int_field(payload, "ioncube_loader_iversion"),
- ioncube_loader_version: string_field(payload, "ioncube_loader_version"),
- phpinfo_general: string_field(payload, "phpinfo_general"),
+ php_version: payload.string("php_version"),
+ php_version_id: payload.int("php_version_id"),
+ php_binary: payload.nullable_string("php_binary"),
+ openssl_version_text: payload.nullable_string("openssl_version_text"),
+ openssl_version_number: payload.int("openssl_version_number"),
+ has_hhvm_version: payload.bool("has_hhvm_version"),
+ has_php_windows_version_build: payload.bool("has_php_windows_version_build"),
+ xdebug_active: payload.bool("xdebug_active"),
+ ioncube_loader_iversion: payload.int("ioncube_loader_iversion"),
+ ioncube_loader_version: payload.string("ioncube_loader_version"),
+ phpinfo_general: payload.string("phpinfo_general"),
curl: curl_field(payload, "curl"),
- extensions: map_field(payload, "extensions")
+ extensions: payload.bool_map("extensions"),
+ functions: payload.bool_map("functions"),
+ ini_settings: payload
+ .entries("ini")
+ .map(|(name, value)| (name.clone(), as_nullable_string(value, "ini", name)))
+ .collect(),
+ }
+ })
+}
+
+/// The constants the `platform` payload carries, in the worker's order.
+const PLATFORM_CONSTANTS: &[&str] = &[
+ "PHP_VERSION",
+ "PHP_DEBUG",
+ "PHP_ZTS",
+ "PHP_INT_SIZE",
+ "AF_INET6",
+ "GD_VERSION",
+ "GMP_VERSION",
+ "ICONV_VERSION",
+ "INTL_ICU_VERSION",
+ "LIBXML_DOTTED_VERSION",
+ "MB_ONIGURUMA_VERSION",
+ "OPENSSL_VERSION_TEXT",
+ "PCRE_VERSION",
+ "PGSQL_LIBPQ_VERSION",
+ "RD_KAFKA_VERSION",
+ "SODIUM_LIBRARY_VERSION",
+ "LIBXSLT_DOTTED_VERSION",
+ "ZipArchive::LIBZIP_VERSION",
+ "ZLIB_VERSION",
+];
+
+/// The classes the `platform` payload carries the existence of.
+const PLATFORM_CLASSES: &[&str] = &["ResourceBundle", "IntlChar"];
+
+/// Everything `PlatformRepository` needs to know about the PHP runtime, fetched in a single round
+/// trip because it would otherwise query the same runtime once per extension and constant.
+///
+/// The fields standing in for PHP objects (`resource_bundle`, `imagick`) carry the entries the
+/// consumer reads off them rather than the object itself.
+#[derive(Debug, Clone)]
+pub struct PlatformInfo {
+ extensions: Vec<String>,
+ extension_versions: IndexMap<String, String>,
+ extension_info: IndexMap<String, String>,
+ /// Keyed as `ltrim($class.'::'.$constant, ':')`; `None` for a reported but undefined constant.
+ constants: IndexMap<String, Option<PhpMixed>>,
+ classes: IndexMap<String, bool>,
+ /// `curl_version()`, or null when the curl extension is not loaded.
+ pub curl_version: PhpMixed,
+ /// `@inet_pton('::')`.
+ pub inet_pton_ipv6: PhpMixed,
+ /// `['Version' => ResourceBundle::create('root', 'ICUDATA', false)->get('Version')]`, or null
+ /// when the class is absent or the bundle cannot be opened.
+ pub resource_bundle: PhpMixed,
+ /// `IntlChar::getUnicodeVersion()`, or null when the class is absent.
+ pub intl_char_unicode_version: PhpMixed,
+ /// `(new Imagick())->getVersion()`, or null when the extension is not loaded.
+ pub imagick: PhpMixed,
+}
+
+impl Default for PlatformInfo {
+ /// A runtime with no extensions loaded, no classes defined and every reported constant
+ /// undefined.
+ fn default() -> Self {
+ PlatformInfo {
+ extensions: Vec::new(),
+ extension_versions: IndexMap::new(),
+ extension_info: IndexMap::new(),
+ constants: PLATFORM_CONSTANTS
.iter()
- .map(|(name, value)| (name.clone(), as_bool(value, name)))
+ .map(|name| ((*name).to_string(), None))
.collect(),
- functions: map_field(payload, "functions")
+ classes: PLATFORM_CLASSES
.iter()
- .map(|(name, value)| (name.clone(), as_bool(value, name)))
+ .map(|name| ((*name).to_string(), false))
.collect(),
- ini_settings: map_field(payload, "ini")
+ curl_version: PhpMixed::Null,
+ inet_pton_ipv6: PhpMixed::Null,
+ resource_bundle: PhpMixed::Null,
+ intl_char_unicode_version: PhpMixed::Null,
+ imagick: PhpMixed::Null,
+ }
+ }
+}
+
+impl PlatformInfo {
+ /// PHP `get_loaded_extensions()`.
+ pub fn get_extensions(&self) -> &[String] {
+ &self.extensions
+ }
+
+ /// PHP `phpversion($extension)`, with PHP's `false` mapped to `'0'`.
+ pub fn get_extension_version(&self, extension: &str) -> &str {
+ self.extension_versions
+ .get(extension)
+ .unwrap_or_else(|| {
+ panic!("PHP RPC: extension `{extension}` is not loaded in the platform payload")
+ })
+ .as_str()
+ }
+
+ /// PHP `(new \ReflectionExtension($extension))->info()` output. Only the extensions the worker
+ /// reports can be asked about; any other name is a bug in the caller.
+ pub fn get_extension_info(&self, extension: &str) -> &str {
+ self.extension_info
+ .get(extension)
+ .unwrap_or_else(|| {
+ panic!("PHP RPC: extension `{extension}` info is not in the platform payload")
+ })
+ .as_str()
+ }
+
+ /// PHP `defined(ltrim($class.'::'.$constant, ':'))`. Only the constants the worker reports can
+ /// be asked about; any other name is a bug in the caller, not an undefined constant.
+ pub fn has_constant(&self, constant_name: &str, class: Option<&str>) -> bool {
+ self.constant(constant_name, class).is_some()
+ }
+
+ /// PHP `constant(ltrim($class.'::'.$constant, ':'))`, answering null for an undefined
+ /// constant. See [`PlatformInfo::has_constant`] for the fixed set of names.
+ pub fn get_constant(&self, constant_name: &str, class: Option<&str>) -> PhpMixed {
+ self.constant(constant_name, class)
+ .cloned()
+ .unwrap_or(PhpMixed::Null)
+ }
+
+ /// PHP `class_exists($class, false)`. See [`PlatformInfo::has_constant`] for the fixed set of
+ /// names.
+ pub fn has_class(&self, class: &str) -> bool {
+ *self.classes.get(class).unwrap_or_else(|| {
+ panic!("PHP RPC: class `{class}` is not reported by the platform payload")
+ })
+ }
+
+ /// For testing only: reports `extensions` as loaded, each at `version`.
+ pub fn __set_extensions(&mut self, extensions: Vec<String>, version: &str) {
+ self.extension_versions = extensions
+ .iter()
+ .map(|name| (name.clone(), version.to_string()))
+ .collect();
+ self.extensions = extensions;
+ }
+
+ /// For testing only: reports `info` as the `ReflectionExtension::info()` output of `extension`.
+ pub fn __set_extension_info(&mut self, extension: &str, info: &str) {
+ self.extension_info
+ .insert(extension.to_string(), info.to_string());
+ }
+
+ /// For testing only: reports the constant as defined with `value`. Panics on a constant the
+ /// worker does not report, so a test cannot describe a runtime the worker cannot report.
+ pub fn __set_constant(&mut self, constant_name: &str, class: Option<&str>, value: PhpMixed) {
+ let key = Self::constant_key(constant_name, class);
+ let entry = self.constants.get_mut(&key).unwrap_or_else(|| {
+ panic!("PHP RPC: constant `{key}` is not reported by the platform payload")
+ });
+ *entry = Some(value);
+ }
+
+ /// For testing only: reports the class as defined. See [`PlatformInfo::__set_constant`].
+ pub fn __set_class(&mut self, class: &str) {
+ let entry = self.classes.get_mut(class).unwrap_or_else(|| {
+ panic!("PHP RPC: class `{class}` is not reported by the platform payload")
+ });
+ *entry = true;
+ }
+
+ fn constant(&self, constant_name: &str, class: Option<&str>) -> Option<&PhpMixed> {
+ let key = Self::constant_key(constant_name, class);
+ self.constants
+ .get(&key)
+ .unwrap_or_else(|| {
+ panic!("PHP RPC: constant `{key}` is not reported by the platform payload")
+ })
+ .as_ref()
+ }
+
+ fn constant_key(constant_name: &str, class: Option<&str>) -> String {
+ match class {
+ Some(class) => format!("{class}::{constant_name}"),
+ None => constant_name.to_string(),
+ }
+ }
+}
+
+static PLATFORM_INFO: OnceLock<PlatformInfo> = OnceLock::new();
+
+/// PHP runtime information for `PlatformRepository`. The worker is queried once per process;
+/// subsequent calls reuse the cached payload.
+pub fn get_platform_info() -> &'static PlatformInfo {
+ PLATFORM_INFO.get_or_init(|| {
+ let payload = call("platform", "");
+ let payload = payload
+ .as_array()
+ .unwrap_or_else(|| panic!("PHP RPC: `platform` did not return an array: {payload:?}"));
+ let payload = &Payload {
+ what: "platform",
+ map: payload,
+ };
+
+ let constant_names = payload.string_list("constant_names");
+ assert_eq!(
+ constant_names
.iter()
- .map(|(name, value)| (name.clone(), as_nullable_string(value, name)))
+ .map(String::as_str)
+ .collect::<Vec<_>>(),
+ PLATFORM_CONSTANTS,
+ "PHP RPC: the worker reports different constants than the platform payload declares"
+ );
+ let constants = payload.map("constants");
+ let classes = payload.bool_map("classes");
+ assert_eq!(
+ classes.keys().map(String::as_str).collect::<Vec<_>>(),
+ PLATFORM_CLASSES,
+ "PHP RPC: the worker reports different classes than the platform payload declares"
+ );
+
+ PlatformInfo {
+ extensions: payload.string_list("extensions"),
+ extension_versions: payload.string_map("extension_versions"),
+ extension_info: payload.string_map("extension_info"),
+ constants: constant_names
+ .into_iter()
+ .map(|name| {
+ let value = constants.and_then(|map| map.get(&name)).cloned();
+ (name, value)
+ })
.collect(),
+ classes,
+ curl_version: payload.mixed("curl_version"),
+ inet_pton_ipv6: payload.mixed("inet_pton_ipv6"),
+ resource_bundle: payload.mixed("resource_bundle"),
+ intl_char_unicode_version: payload.mixed("intl_char_unicode_version"),
+ imagick: payload.mixed("imagick"),
}
})
}
-fn field<'a>(payload: &'a IndexMap<String, PhpMixed>, key: &str) -> &'a PhpMixed {
- payload
- .get(key)
- .unwrap_or_else(|| panic!("PHP RPC: `diagnose` payload has no `{key}` entry"))
+/// One worker answer, named after the dispatch entry that produced it so a decoding failure says
+/// which payload was malformed.
+struct Payload<'a> {
+ what: &'static str,
+ map: &'a IndexMap<String, PhpMixed>,
}
-fn string_field(payload: &IndexMap<String, PhpMixed>, key: &str) -> String {
- match field(payload, key) {
- PhpMixed::String(s) => s.clone(),
- other => panic!("PHP RPC: `diagnose` payload entry `{key}` is not a string: {other:?}"),
+impl Payload<'_> {
+ fn field(&self, key: &str) -> &PhpMixed {
+ self.map
+ .get(key)
+ .unwrap_or_else(|| panic!("PHP RPC: `{}` payload has no `{key}` entry", self.what))
}
-}
-fn nullable_string_field(payload: &IndexMap<String, PhpMixed>, key: &str) -> Option<String> {
- as_nullable_string(field(payload, key), key)
-}
+ fn mixed(&self, key: &str) -> PhpMixed {
+ self.field(key).clone()
+ }
-fn int_field(payload: &IndexMap<String, PhpMixed>, key: &str) -> i64 {
- match field(payload, key) {
- PhpMixed::Int(n) => *n,
- other => panic!("PHP RPC: `diagnose` payload entry `{key}` is not an int: {other:?}"),
+ fn string(&self, key: &str) -> String {
+ match self.field(key) {
+ PhpMixed::String(s) => s.clone(),
+ other => panic!(
+ "PHP RPC: `{}` payload entry `{key}` is not a string: {other:?}",
+ self.what
+ ),
+ }
}
-}
-fn bool_field(payload: &IndexMap<String, PhpMixed>, key: &str) -> bool {
- as_bool(field(payload, key), key)
-}
+ fn nullable_string(&self, key: &str) -> Option<String> {
+ as_nullable_string(self.field(key), self.what, key)
+ }
-fn map_field<'a>(
- payload: &'a IndexMap<String, PhpMixed>,
- key: &str,
-) -> &'a IndexMap<String, PhpMixed> {
- match field(payload, key) {
- PhpMixed::Array(map) => map,
- other => panic!("PHP RPC: `diagnose` payload entry `{key}` is not an array: {other:?}"),
+ fn int(&self, key: &str) -> i64 {
+ match self.field(key) {
+ PhpMixed::Int(n) => *n,
+ other => panic!(
+ "PHP RPC: `{}` payload entry `{key}` is not an int: {other:?}",
+ self.what
+ ),
+ }
+ }
+
+ fn bool(&self, key: &str) -> bool {
+ as_bool(self.field(key), self.what, key)
+ }
+
+ /// `None` for PHP's empty array, which carries no key type and so decodes as an empty list.
+ fn map(&self, key: &str) -> Option<&IndexMap<String, PhpMixed>> {
+ match self.field(key) {
+ PhpMixed::Array(map) => Some(map),
+ PhpMixed::List(items) if items.is_empty() => None,
+ other => panic!(
+ "PHP RPC: `{}` payload entry `{key}` is not an array: {other:?}",
+ self.what
+ ),
+ }
+ }
+
+ fn entries(&self, key: &str) -> impl Iterator<Item = (&String, &PhpMixed)> {
+ self.map(key).into_iter().flatten()
+ }
+
+ fn bool_map(&self, key: &str) -> IndexMap<String, bool> {
+ self.entries(key)
+ .map(|(name, value)| (name.clone(), as_bool(value, self.what, name)))
+ .collect()
+ }
+
+ fn string_map(&self, key: &str) -> IndexMap<String, String> {
+ self.entries(key)
+ .map(|(name, value)| match value {
+ PhpMixed::String(s) => (name.clone(), s.clone()),
+ other => panic!(
+ "PHP RPC: `{}` payload entry `{key}[{name}]` is not a string: {other:?}",
+ self.what
+ ),
+ })
+ .collect()
+ }
+
+ fn string_list(&self, key: &str) -> Vec<String> {
+ match self.field(key) {
+ PhpMixed::List(items) => items
+ .iter()
+ .map(|item| match item {
+ PhpMixed::String(s) => s.clone(),
+ other => panic!(
+ "PHP RPC: `{}` payload entry `{key}` has a non-string element: {other:?}",
+ self.what
+ ),
+ })
+ .collect(),
+ other => panic!(
+ "PHP RPC: `{}` payload entry `{key}` is not a list: {other:?}",
+ self.what
+ ),
+ }
}
}
-fn as_bool(value: &PhpMixed, key: &str) -> bool {
+fn as_bool(value: &PhpMixed, what: &str, key: &str) -> bool {
match value {
PhpMixed::Bool(b) => *b,
- other => panic!("PHP RPC: `diagnose` payload entry `{key}` is not a bool: {other:?}"),
+ other => panic!("PHP RPC: `{what}` payload entry `{key}` is not a bool: {other:?}"),
+ }
+}
+
+fn as_nullable_string(value: &PhpMixed, what: &str, key: &str) -> Option<String> {
+ match value {
+ PhpMixed::String(s) => Some(s.clone()),
+ PhpMixed::Null => None,
+ other => {
+ panic!("PHP RPC: `{what}` payload entry `{key}` is not a string or null: {other:?}")
+ }
}
}
-fn nullable_int_field(payload: &IndexMap<String, PhpMixed>, key: &str) -> Option<i64> {
- match field(payload, key) {
+fn nullable_int_field(payload: &Payload, key: &str) -> Option<i64> {
+ let what = payload.what;
+ match payload.field(key) {
PhpMixed::Int(n) => Some(*n),
PhpMixed::Null => None,
other => {
- panic!("PHP RPC: `diagnose` payload entry `{key}` is not an int or null: {other:?}")
+ panic!("PHP RPC: `{what}` payload entry `{key}` is not an int or null: {other:?}")
}
}
}
-fn curl_field(payload: &IndexMap<String, PhpMixed>, key: &str) -> Option<Curl> {
- let curl = match field(payload, key) {
+fn curl_field(payload: &Payload, key: &str) -> Option<Curl> {
+ let what = payload.what;
+ let curl = match payload.field(key) {
PhpMixed::Null => return None,
PhpMixed::Array(map) => map,
other => {
- panic!("PHP RPC: `diagnose` payload entry `{key}` is not an array or null: {other:?}")
+ panic!("PHP RPC: `{what}` payload entry `{key}` is not an array or null: {other:?}")
}
};
+ let curl = &Payload { what, map: curl };
Some(Curl {
- version: string_field(curl, "version"),
- libz_version: nullable_string_field(curl, "libz_version"),
- brotli_version: nullable_string_field(curl, "brotli_version"),
- ssl_version: nullable_string_field(curl, "ssl_version"),
+ version: curl.string("version"),
+ libz_version: curl.nullable_string("libz_version"),
+ brotli_version: curl.nullable_string("brotli_version"),
+ ssl_version: curl.nullable_string("ssl_version"),
features: nullable_int_field(curl, "features"),
version_zstd: nullable_int_field(curl, "version_zstd"),
version_http2: nullable_int_field(curl, "version_http2"),
- has_http_version_2_0: bool_field(curl, "has_http_version_2_0"),
+ has_http_version_2_0: curl.bool("has_http_version_2_0"),
version_http3: nullable_int_field(curl, "version_http3"),
})
}
-fn as_nullable_string(value: &PhpMixed, key: &str) -> Option<String> {
- match value {
- PhpMixed::String(s) => Some(s.clone()),
- PhpMixed::Null => None,
- other => {
- panic!("PHP RPC: `diagnose` payload entry `{key}` is not a string or null: {other:?}")
- }
- }
-}
-
/// PHP `phpversion($extension)`.
pub fn phpversion(extension: &str) -> Option<String> {
match call("phpversion", extension) {
@@ -281,11 +536,6 @@ pub fn phpversion(extension: &str) -> Option<String> {
}
}
-/// PHP `get_loaded_extensions()`.
-pub fn get_loaded_extensions() -> Vec<String> {
- string_list(call("get_loaded_extensions", ""), "get_loaded_extensions")
-}
-
/// `Composer\XdebugHandler\XdebugHandler::getAllIniFiles()` (minus the `self::$name` branch,
/// which is unreachable since this port never constructs an XdebugHandler): `[(string)
/// php_ini_loaded_file()]` merged with the trimmed, comma-split `php_ini_scanned_files()` list
@@ -851,12 +1101,6 @@ mod tests {
return;
}
- let extensions = get_loaded_extensions();
- assert!(
- extensions.iter().any(|extension| extension == "Core"),
- "expected the Core extension among {extensions:?}",
- );
-
// XdebugHandler::getAllIniFiles() always yields at least one entry, which is the empty
// string when no php.ini is loaded.
assert!(!get_all_ini_files().is_empty());
@@ -910,20 +1154,38 @@ mod tests {
}
#[test]
- fn queries_constants_when_php_available() {
+ fn queries_platform_info_when_php_available() {
if PhpExecutableFinder::new().find(false).is_none() {
// No PHP in this environment; the worker cannot start.
return;
}
- assert!(has_constant("PHP_VERSION"));
- assert!(!has_constant("SHIRABE_DOES_NOT_EXIST_XYZ"));
+ let platform_info = get_platform_info();
- assert_eq!(get_constant("PHP_INT_SIZE"), PhpMixed::Int(8));
- assert_eq!(get_constant("SHIRABE_DOES_NOT_EXIST_XYZ"), PhpMixed::Null);
- match get_constant("PHP_VERSION") {
+ let extensions = platform_info.get_extensions();
+ assert!(
+ extensions.iter().any(|extension| extension == "Core"),
+ "expected the Core extension among {extensions:?}",
+ );
+ assert_eq!(
+ platform_info.get_extension_version("Core"),
+ get_php_version()
+ );
+
+ assert!(platform_info.has_constant("PHP_VERSION", None));
+ assert_eq!(
+ platform_info.get_constant("PHP_INT_SIZE", None),
+ PhpMixed::Int(8)
+ );
+ match platform_info.get_constant("PHP_VERSION", None) {
PhpMixed::String(s) => assert!(!s.is_empty(), "expected a non-empty PHP_VERSION"),
other => panic!("expected a string, got {other:?}"),
}
}
+
+ #[test]
+ #[should_panic(expected = "is not reported by the platform payload")]
+ fn platform_info_rejects_an_unreported_constant() {
+ PlatformInfo::default().has_constant("SHIRABE_DOES_NOT_EXIST_XYZ", None);
+ }
}
diff --git a/crates/shirabe/src/command/base_dependency_command.rs b/crates/shirabe/src/command/base_dependency_command.rs
index c4399f34..46cc654f 100644
--- a/crates/shirabe/src/command/base_dependency_command.rs
+++ b/crates/shirabe/src/command/base_dependency_command.rs
@@ -78,7 +78,7 @@ pub trait BaseDependencyCommand: BaseCommand {
.map(|(k, v)| (k, PhpMixed::String(v)))
.collect();
repos.push(crate::repository::RepositoryInterfaceHandle::new(
- PlatformRepository::new(vec![], platform_overrides)?,
+ PlatformRepository::new(vec![], platform_overrides, None, None)?,
));
} else {
let repository_manager = composer.get_repository_manager().clone();
@@ -109,7 +109,7 @@ pub trait BaseDependencyCommand: BaseCommand {
.into_iter()
.collect();
repos.push(crate::repository::RepositoryInterfaceHandle::new(
- PlatformRepository::new(vec![], platform_overrides)?,
+ PlatformRepository::new(vec![], platform_overrides, None, None)?,
));
}
diff --git a/crates/shirabe/src/command/check_platform_reqs_command.rs b/crates/shirabe/src/command/check_platform_reqs_command.rs
index 903e0c4c..4e105e69 100644
--- a/crates/shirabe/src/command/check_platform_reqs_command.rs
+++ b/crates/shirabe/src/command/check_platform_reqs_command.rs
@@ -290,7 +290,7 @@ impl Command for CheckPlatformReqsCommand {
requires_sorted.sort_by(|a, b| a.0.cmp(&b.0));
installed_repo.add_repository(crate::repository::RepositoryInterfaceHandle::new(
- PlatformRepository::new(vec![], indexmap::IndexMap::new())?,
+ PlatformRepository::new(vec![], indexmap::IndexMap::new(), None, None)?,
));
let installed_repo_with_platform = installed_repo;
diff --git a/crates/shirabe/src/command/completion_trait.rs b/crates/shirabe/src/command/completion_trait.rs
index bdd42dff..67ab23be 100644
--- a/crates/shirabe/src/command/completion_trait.rs
+++ b/crates/shirabe/src/command/completion_trait.rs
@@ -80,14 +80,14 @@ pub trait CompletionTrait: BaseCommand {
.into_iter()
.map(|(k, v)| (k, PhpMixed::String(v)))
.collect();
- PlatformRepository::new(vec![], overrides)?
+ PlatformRepository::new(vec![], overrides, None, None)?
} else {
let platform_cfg = composer.get_config().borrow().get("platform");
let overrides: IndexMap<String, PhpMixed> = platform_cfg
.as_array()
.map(|m| m.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
.unwrap_or_default();
- PlatformRepository::new(vec![], overrides)?
+ PlatformRepository::new(vec![], overrides, None, None)?
};
if input.get_completion_value().is_empty() {
// to reduce noise, when no text is yet entered we list only two entries for ext- and lib- prefixes
@@ -286,7 +286,7 @@ pub trait CompletionTrait: BaseCommand {
.as_array()
.map(|m| m.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
.unwrap_or_default();
- let mut repos = PlatformRepository::new(vec![], overrides)?;
+ let mut repos = PlatformRepository::new(vec![], overrides, None, None)?;
let pattern =
base_package::package_name_to_regexp(&format!("{}*", input.get_completion_value()));
diff --git a/crates/shirabe/src/command/create_project_command.rs b/crates/shirabe/src/command/create_project_command.rs
index dcf0e0f8..aa1b79a8 100644
--- a/crates/shirabe/src/command/create_project_command.rs
+++ b/crates/shirabe/src/command/create_project_command.rs
@@ -684,6 +684,8 @@ impl CreateProjectCommand {
.collect(),
_ => indexmap::IndexMap::new(),
},
+ None,
+ None,
)?;
// find the latest version if there are multiple
diff --git a/crates/shirabe/src/command/diagnose_command.rs b/crates/shirabe/src/command/diagnose_command.rs
index 8483eb56..65240e51 100644
--- a/crates/shirabe/src/command/diagnose_command.rs
+++ b/crates/shirabe/src/command/diagnose_command.rs
@@ -1223,7 +1223,7 @@ impl Command for DiagnoseCommand {
let platform_overrides_unboxed: indexmap::IndexMap<String, PhpMixed> =
platform_overrides.into_iter().collect();
let mut platform_repo =
- PlatformRepository::new(vec![], platform_overrides_unboxed).unwrap();
+ PlatformRepository::new(vec![], platform_overrides_unboxed, None, None).unwrap();
let php_pkg = <PlatformRepository as crate::repository::RepositoryInterface>::find_package(
&mut platform_repo,
"php",
diff --git a/crates/shirabe/src/command/init_command.rs b/crates/shirabe/src/command/init_command.rs
index f2bf19d7..5168b2ba 100644
--- a/crates/shirabe/src/command/init_command.rs
+++ b/crates/shirabe/src/command/init_command.rs
@@ -830,7 +830,7 @@ impl Command for InitCommand {
let mut repos: Vec<crate::repository::RepositoryInterfaceHandle> =
vec![crate::repository::RepositoryInterfaceHandle::new(
- PlatformRepository::new(vec![], IndexMap::new())?,
+ PlatformRepository::new(vec![], IndexMap::new(), None, None)?,
)];
let mut create_default_packagist_repo = true;
for repo in &repositories {
diff --git a/crates/shirabe/src/command/package_discovery_trait.rs b/crates/shirabe/src/command/package_discovery_trait.rs
index 5c921e95..db7db488 100644
--- a/crates/shirabe/src/command/package_discovery_trait.rs
+++ b/crates/shirabe/src/command/package_discovery_trait.rs
@@ -42,7 +42,7 @@ pub trait PackageDiscoveryTrait: BaseCommand {
// PHP: array_merge([new PlatformRepository], RepositoryFactory::defaultReposWithDefaultManager($this->getIO()))
let mut repos: Vec<crate::repository::RepositoryInterfaceHandle> =
vec![crate::repository::RepositoryInterfaceHandle::new(
- PlatformRepository::new(vec![], IndexMap::new())
+ PlatformRepository::new(vec![], IndexMap::new(), None, None)
.expect("PlatformRepository::new should not fail"),
)];
let io_owned: std::rc::Rc<std::cell::RefCell<dyn IOInterface>> = self.get_io();
diff --git a/crates/shirabe/src/command/require_command.rs b/crates/shirabe/src/command/require_command.rs
index f4410c19..85e53a37 100644
--- a/crates/shirabe/src/command/require_command.rs
+++ b/crates/shirabe/src/command/require_command.rs
@@ -916,8 +916,12 @@ impl Command for RequireCommand {
.map(|m| m.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
.unwrap_or_default();
// initialize self.repos as it is used by the PackageDiscoveryTrait
- let platform_repo =
- PlatformRepositoryHandle::new(PlatformRepository::new(vec![], platform_overrides_map)?);
+ let platform_repo = PlatformRepositoryHandle::new(PlatformRepository::new(
+ vec![],
+ platform_overrides_map,
+ None,
+ None,
+ )?);
let mut combined: Vec<crate::repository::RepositoryInterfaceHandle> =
vec![platform_repo.clone().into()];
for repo in repos {
diff --git a/crates/shirabe/src/command/search_command.rs b/crates/shirabe/src/command/search_command.rs
index 0b675109..4bf75c8b 100644
--- a/crates/shirabe/src/command/search_command.rs
+++ b/crates/shirabe/src/command/search_command.rs
@@ -111,7 +111,7 @@ impl Command for SearchCommand {
input: std::rc::Rc<std::cell::RefCell<dyn InputInterface>>,
output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
) -> anyhow::Result<i64> {
- let platform_repo = PlatformRepository::new4(vec![], IndexMap::new(), None, None)?;
+ let platform_repo = PlatformRepository::new(vec![], IndexMap::new(), None, None)?;
let io = self.get_io();
let format = input
diff --git a/crates/shirabe/src/command/show_command.rs b/crates/shirabe/src/command/show_command.rs
index 44733531..ff20f325 100644
--- a/crates/shirabe/src/command/show_command.rs
+++ b/crates/shirabe/src/command/show_command.rs
@@ -1784,8 +1784,12 @@ impl Command for ShowCommand {
platform_overrides = p.into_iter().collect();
}
}
- let platform_repo =
- PlatformRepositoryHandle::new(PlatformRepository::new(vec![], platform_overrides)?);
+ let platform_repo = PlatformRepositoryHandle::new(PlatformRepository::new(
+ vec![],
+ platform_overrides,
+ None,
+ None,
+ )?);
let mut locked_repo: Option<RepositoryInterfaceHandle> = None;
// The single-package $package binding from PHP gets surfaced here.
diff --git a/crates/shirabe/src/command/suggests_command.rs b/crates/shirabe/src/command/suggests_command.rs
index 5c73da9b..96989dc0 100644
--- a/crates/shirabe/src/command/suggests_command.rs
+++ b/crates/shirabe/src/command/suggests_command.rs
@@ -133,6 +133,8 @@ impl Command for SuggestsCommand {
installed_repos.push(RepositoryInterfaceHandle::new(PlatformRepository::new(
vec![],
platform_overrides,
+ None,
+ None,
)?));
let locked_repo = composer.get_locker().borrow_mut().get_locked_repository(
!input
@@ -151,6 +153,8 @@ impl Command for SuggestsCommand {
installed_repos.push(RepositoryInterfaceHandle::new(PlatformRepository::new(
vec![],
platform_overrides,
+ None,
+ None,
)?));
installed_repos.push(
composer
diff --git a/crates/shirabe/src/dependency_resolver/problem.rs b/crates/shirabe/src/dependency_resolver/problem.rs
index 09adafea..8ee4b816 100644
--- a/crates/shirabe/src/dependency_resolver/problem.rs
+++ b/crates/shirabe/src/dependency_resolver/problem.rs
@@ -458,7 +458,7 @@ impl Problem {
);
// Per-extension version info can't be known statically; query the real PHP
- // runtime via the RPC bridge, same as platform::runtime::Runtime::get_extension_version.
+ // runtime via the RPC bridge, as PHP's Composer\Platform\Runtime does.
let runtime_version = shirabe_php_rpc::phpversion(&ext);
let effective_version = match runtime_version {
None => "0".to_string(),
diff --git a/crates/shirabe/src/installer.rs b/crates/shirabe/src/installer.rs
index bda4e2f6..091fb35f 100644
--- a/crates/shirabe/src/installer.rs
+++ b/crates/shirabe/src/installer.rs
@@ -1323,6 +1323,8 @@ impl Installer {
Ok(PlatformRepositoryHandle::new(PlatformRepository::new(
vec![],
platform_overrides,
+ None,
+ None,
)?))
}
diff --git a/crates/shirabe/src/platform.rs b/crates/shirabe/src/platform.rs
index 6abbb769..11b00e3f 100644
--- a/crates/shirabe/src/platform.rs
+++ b/crates/shirabe/src/platform.rs
@@ -1,7 +1,9 @@
+//! `Composer\Platform\Runtime` has no Rust counterpart. Its work belongs to the running PHP
+//! interpreter, so it is ported as PHP into the RPC worker (`ShirabePlatformRuntime` in
+//! `shirabe-php-rpc`), and its callers read the answers off `shirabe_php_rpc::PlatformInfo`.
+
pub mod hhvm_detector;
-pub mod runtime;
pub mod version;
pub use hhvm_detector::*;
-pub use runtime::*;
pub use version::*;
diff --git a/crates/shirabe/src/platform/runtime.rs b/crates/shirabe/src/platform/runtime.rs
deleted file mode 100644
index 265f007c..00000000
--- a/crates/shirabe/src/platform/runtime.rs
+++ /dev/null
@@ -1,237 +0,0 @@
-//! ref: composer/src/Composer/Platform/Runtime.php
-
-use indexmap::IndexMap;
-use shirabe_external_packages::composer::pcre::{CaptureKey, Preg};
-use shirabe_php_rpc::{PhpThrow, PluginValue};
-use shirabe_php_shim::{
- PhpMixed, RuntimeException, function_exists, html_entity_decode, implode, ltrim, php_regex,
- strip_tags, trim,
-};
-
-/// Seam over the PHP runtime so PlatformRepository can be tested against mocked
-/// extension/constant/function probes. PHP has no such interface (the test mocks the
-/// concrete `Composer\Platform\Runtime` directly); it is introduced here to keep the
-/// consumer dependent only on trait methods.
-pub trait RuntimeInterface: std::fmt::Debug {
- fn has_constant(&self, constant_name: &str, class: Option<String>) -> bool;
- fn get_constant(&self, constant_name: &str, class: Option<String>) -> PhpMixed;
- /// `callable` carries the PHP callable spec (a function name string or a
- /// `[class, method]` list), matching PHP `invoke($callable, $arguments)`.
- fn invoke(&self, callable: PhpMixed, arguments: Vec<PhpMixed>) -> PhpMixed;
- fn has_class(&self, class: &str) -> bool;
- fn construct(&self, class: &str, arguments: Vec<PhpMixed>) -> anyhow::Result<PhpMixed>;
- fn get_extensions(&self) -> Vec<String>;
- fn get_extension_version(&self, extension: &str) -> String;
- fn get_extension_info(&self, extension: &str) -> anyhow::Result<String>;
-}
-
-#[derive(Debug)]
-pub struct Runtime;
-
-impl RuntimeInterface for Runtime {
- fn has_constant(&self, constant_name: &str, class: Option<String>) -> bool {
- shirabe_php_rpc::has_constant(&ltrim(
- &format!("{}::{}", class.as_deref().unwrap_or(""), constant_name),
- Some(":"),
- ))
- }
-
- fn get_constant(&self, constant_name: &str, class: Option<String>) -> PhpMixed {
- shirabe_php_rpc::get_constant(&ltrim(
- &format!("{}::{}", class.as_deref().unwrap_or(""), constant_name),
- Some(":"),
- ))
- }
-
- fn invoke(&self, callable: PhpMixed, arguments: Vec<PhpMixed>) -> PhpMixed {
- // PHP: return $callable(...$arguments);
- // Only the specific dynamic callables PlatformRepository actually reaches are
- // wired through php-rpc; arbitrary PHP callables are still unsupported.
- match (&callable, arguments.as_slice()) {
- (PhpMixed::String(name), [PhpMixed::String(arg)]) if name == "inet_pton" => {
- shirabe_php_rpc::inet_pton(arg)
- }
- (PhpMixed::String(name), []) if name == "curl_version" => {
- let mut version = IndexMap::new();
- if let Some(v) = shirabe_php_rpc::curl_version() {
- version.insert("version".to_string(), PhpMixed::String(v));
- }
- PhpMixed::Array(version)
- }
- (PhpMixed::List(spec), _) => match class_callable(spec) {
- ("ResourceBundle", "create") => resource_bundle_create(arguments),
- ("IntlChar", "getUnicodeVersion") => {
- php_value(shirabe_php_rpc::call_static_method(
- "IntlChar",
- "getUnicodeVersion",
- Vec::new(),
- None,
- ))
- }
- (class, method) => panic!(
- "the PHP callable `{class}::{method}` is not wired through the runtime seam"
- ),
- },
- _ => panic!("the PHP callable {callable:?} is not wired through the runtime seam"),
- }
- }
-
- fn has_class(&self, class: &str) -> bool {
- shirabe_php_rpc::class_exists(class)
- }
-
- fn construct(&self, class: &str, arguments: Vec<PhpMixed>) -> anyhow::Result<PhpMixed> {
- match class {
- "Imagick" => imagick_version(arguments),
- other => Err(anyhow::anyhow!(RuntimeException {
- message: format!("the PHP class `{other}` is not wired through the runtime seam"),
- code: 0,
- })),
- }
- }
-
- fn get_extensions(&self) -> Vec<String> {
- shirabe_php_rpc::get_loaded_extensions()
- }
-
- fn get_extension_version(&self, extension: &str) -> String {
- shirabe_php_rpc::phpversion(extension).unwrap_or_else(|| "0".to_string())
- }
-
- fn get_extension_info(&self, extension: &str) -> anyhow::Result<String> {
- Ok(shirabe_php_rpc::get_extension_info(extension))
- }
-}
-
-/// The `[class, method]` pair of a PHP callable given in array form.
-fn class_callable(spec: &[PhpMixed]) -> (&str, &str) {
- match spec {
- [PhpMixed::String(class), PhpMixed::String(method)] => (class, method),
- other => panic!("a PHP callable given as an array must be [class, method], got {other:?}"),
- }
-}
-
-/// Unwraps an RPC outcome whose failure means the runtime probe itself is broken, not that the
-/// probed extension is absent.
-fn php_value(outcome: anyhow::Result<Result<PluginValue, PhpThrow>>) -> PhpMixed {
- match outcome {
- Ok(Ok(value)) => value
- .to_php_mixed()
- .expect("a runtime probe answers with plain values"),
- Ok(Err(throw)) => panic!("the PHP runtime probe failed: {}", throw.message),
- Err(e) => panic!("the PHP runtime probe could not be sent: {e:#}"),
- }
-}
-
-/// PHP `ResourceBundle::create(...)`, whose result the caller reads `->get('Version')` off.
-/// A live PHP object has no `PhpMixed` counterpart, so that entry crosses in its place.
-fn resource_bundle_create(arguments: Vec<PhpMixed>) -> PhpMixed {
- let bundle = match php_handle(shirabe_php_rpc::call_static_method(
- "ResourceBundle",
- "create",
- arguments.iter().map(PluginValue::from_php_mixed).collect(),
- None,
- )) {
- Some(phandle) => phandle,
- // PHP returns null when the bundle cannot be opened.
- None => return PhpMixed::Null,
- };
- let version = php_value(shirabe_php_rpc::call_php_method(
- bundle,
- "get",
- vec![PluginValue::string("Version")],
- None,
- ));
- let _ = shirabe_php_rpc::release_php_handle(bundle);
- PhpMixed::Object(IndexMap::from([("Version".to_string(), version)]))
-}
-
-/// PHP `(new Imagick())->getVersion()`, reported as the entries the caller reads.
-fn imagick_version(arguments: Vec<PhpMixed>) -> anyhow::Result<PhpMixed> {
- let imagick = php_handle(shirabe_php_rpc::new_object(
- "Imagick",
- arguments.iter().map(PluginValue::from_php_mixed).collect(),
- None,
- ))
- .ok_or_else(|| {
- anyhow::anyhow!(RuntimeException {
- message: "`new Imagick` did not answer with an object".to_string(),
- code: 0,
- })
- })?;
- let version = php_value(shirabe_php_rpc::call_php_method(
- imagick,
- "getVersion",
- Vec::new(),
- None,
- ));
- let _ = shirabe_php_rpc::release_php_handle(imagick);
- Ok(version)
-}
-
-/// The handle of a PHP-side object an RPC answered with, or `None` when it answered with null.
-fn php_handle(outcome: anyhow::Result<Result<PluginValue, PhpThrow>>) -> Option<u64> {
- match outcome {
- Ok(Ok(PluginValue::PhpHandle(handle))) => Some(handle.phandle),
- Ok(Ok(PluginValue::Null)) => None,
- Ok(Ok(other)) => panic!("the PHP runtime probe answered with {other:?}, not an object"),
- Ok(Err(throw)) => panic!("the PHP runtime probe failed: {}", throw.message),
- Err(e) => panic!("the PHP runtime probe could not be sent: {e:#}"),
- }
-}
-
-impl Runtime {
- pub fn has_function(&self, f: &str) -> bool {
- function_exists(f)
- }
-
- pub fn parse_html_extension_info(html: &str) -> String {
- let mut result: Vec<String> = vec![];
-
- let mut matches: IndexMap<CaptureKey, String> = IndexMap::new();
- if Preg::match3(
- php_regex!(r"~<h2>\s*<a[^>]*>([^<]+)</a>\s*</h2>~i"),
- html,
- Some(&mut matches),
- ) {
- result.push(trim(
- &html_entity_decode(
- matches
- .get(&CaptureKey::ByIndex(1))
- .map(|s| s.as_str())
- .unwrap_or(""),
- ),
- None,
- ));
- result.push(String::new());
- }
-
- let mut matches: IndexMap<CaptureKey, Vec<String>> = IndexMap::new();
- if Preg::match_all3(
- php_regex!(
- r#"~<tr>\s*<td class="e">\s*(.*?)\s*</td>\s*<td class="v">\s*(.*?)\s*</td>\s*</tr>~is"#
- ),
- html,
- Some(&mut matches),
- ) > 0
- {
- let group1 = matches
- .get(&CaptureKey::ByIndex(1))
- .cloned()
- .unwrap_or_default();
- let group2 = matches
- .get(&CaptureKey::ByIndex(2))
- .cloned()
- .unwrap_or_default();
- let count = std::cmp::min(group1.len(), group2.len());
-
- for i in 0..count {
- let key = trim(&html_entity_decode(&strip_tags(&group1[i])), None);
- let value = trim(&html_entity_decode(&strip_tags(&group2[i])), None);
- result.push(format!("{} => {}", key, value));
- }
- }
-
- implode("\n", &result)
- }
-}
diff --git a/crates/shirabe/src/repository/platform_repository.rs b/crates/shirabe/src/repository/platform_repository.rs
index 5b205ba0..944bb4db 100644
--- a/crates/shirabe/src/repository/platform_repository.rs
+++ b/crates/shirabe/src/repository/platform_repository.rs
@@ -11,16 +11,14 @@ use crate::package::PackageInterfaceHandle;
use crate::package::version::VersionParser;
use crate::platform::HhvmDetector;
use crate::platform::HhvmDetectorInterface;
-use crate::platform::Runtime;
-use crate::platform::RuntimeInterface;
use crate::platform::Version;
use crate::plugin::plugin_interface::{self};
use crate::repository::ArrayRepository;
use crate::repository::RepositoryInterface;
-use crate::util::Silencer;
use indexmap::IndexMap;
use shirabe_external_packages::composer::pcre::{CaptureKey, Preg};
use shirabe_external_packages::composer::xdebug_handler::XdebugHandler;
+use shirabe_php_rpc::PlatformInfo;
use shirabe_php_shim::{
InvalidArgumentException, PhpMixed, UnexpectedValueException, array_map_str_fn,
array_slice_strs, explode, get_class, implode, in_array_strict, is_string, php_regex,
@@ -47,27 +45,21 @@ pub struct PlatformRepository {
pub(crate) version_parser: Option<VersionParser>,
pub(crate) overrides: IndexMap<String, PlatformOverride>,
pub(crate) disabled_packages: IndexMap<String, CompletePackageInterfaceHandle>,
- pub(crate) runtime: Box<dyn RuntimeInterface>,
+ pub(crate) platform_info: Option<PlatformInfo>,
pub(crate) hhvm_detector: Box<dyn HhvmDetectorInterface>,
}
impl PlatformRepository {
const PLATFORM_PACKAGE_REGEX: &'static str = "{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[a-z0-9](?:[_.-]?[a-z0-9]+)*|composer(?:-(?:plugin|runtime)-api)?)$}iD";
+ /// A `None` `platform_info` is resolved from the PHP worker on the first `initialize()`, so
+ /// constructing the repository on its own never starts the worker.
pub fn new(
packages: Vec<PackageInterfaceHandle>,
overrides: IndexMap<String, PhpMixed>,
- ) -> anyhow::Result<Self> {
- Self::new4(packages, overrides, None, None)
- }
-
- pub fn new4(
- packages: Vec<PackageInterfaceHandle>,
- overrides: IndexMap<String, PhpMixed>,
- runtime: Option<Box<dyn RuntimeInterface>>,
+ platform_info: Option<PlatformInfo>,
hhvm_detector: Option<Box<dyn HhvmDetectorInterface>>,
) -> anyhow::Result<Self> {
- let runtime: Box<dyn RuntimeInterface> = runtime.unwrap_or_else(|| Box::new(Runtime));
let hhvm_detector: Box<dyn HhvmDetectorInterface> =
hhvm_detector.unwrap_or_else(|| Box::new(HhvmDetector::new(None, None)));
let mut overrides_map: IndexMap<String, PlatformOverride> = IndexMap::new();
@@ -107,7 +99,7 @@ impl PlatformRepository {
version_parser: None,
overrides: overrides_map,
disabled_packages: IndexMap::new(),
- runtime,
+ platform_info,
hhvm_detector,
};
for package in packages {
@@ -139,6 +131,11 @@ impl PlatformRepository {
pub(crate) fn initialize(&mut self) -> anyhow::Result<()> {
self.inner.initialize();
+ let platform_info = self
+ .platform_info
+ .get_or_insert_with(|| shirabe_php_rpc::get_platform_info().clone())
+ .clone();
+
let mut libraries: IndexMap<String, bool> = IndexMap::new();
self.version_parser = Some(VersionParser::new());
@@ -214,7 +211,7 @@ impl PlatformRepository {
CompletePackageHandle::from_complete_package(composer_runtime_api).into(),
)?;
- let php_version_const = self.runtime.get_constant("PHP_VERSION", None);
+ let php_version_const = platform_info.get_constant("PHP_VERSION", None);
let php_version_str = match &php_version_const {
PhpMixed::String(s) => s.clone(),
_ => "".to_string(),
@@ -245,8 +242,7 @@ impl PlatformRepository {
php.set_description("The PHP interpreter".to_string());
self.add_package(CompletePackageHandle::from_complete_package(php).into())?;
- if self
- .runtime
+ if platform_info
.get_constant("PHP_DEBUG", None)
.as_bool()
.unwrap_or(false)
@@ -260,9 +256,8 @@ impl PlatformRepository {
self.add_package(CompletePackageHandle::from_complete_package(phpdebug).into())?;
}
- if self.runtime.has_constant("PHP_ZTS", None)
- && self
- .runtime
+ if platform_info.has_constant("PHP_ZTS", None)
+ && platform_info
.get_constant("PHP_ZTS", None)
.as_bool()
.unwrap_or(false)
@@ -276,8 +271,7 @@ impl PlatformRepository {
self.add_package(CompletePackageHandle::from_complete_package(phpzts).into())?;
}
- if self
- .runtime
+ if platform_info
.get_constant("PHP_INT_SIZE", None)
.as_int()
.map(|v| v == 8)
@@ -294,15 +288,8 @@ impl PlatformRepository {
// The AF_INET6 constant is only defined if ext-sockets is available but
// IPv6 support might still be available.
- let has_inet6 = self.runtime.has_constant("AF_INET6", None);
- // PHP: Silencer::call([$this->runtime, 'invoke'], 'inet_pton', ['::'])
- let inet_pton_check = Silencer::call(|| {
- Ok::<PhpMixed, anyhow::Error>(self.runtime.invoke(
- PhpMixed::String("inet_pton".to_string()),
- vec![PhpMixed::String("::".to_string())],
- ))
- })
- .unwrap_or(PhpMixed::Bool(false));
+ let has_inet6 = platform_info.has_constant("AF_INET6", None);
+ let inet_pton_check = &platform_info.inet_pton_ipv6;
if has_inet6 || !matches!(inet_pton_check, PhpMixed::Bool(false)) {
let mut php_ipv6 =
CompletePackage::new("php-ipv6".to_string(), version, pretty_version);
@@ -310,7 +297,7 @@ impl PlatformRepository {
self.add_package(CompletePackageHandle::from_complete_package(php_ipv6).into())?;
}
- let loaded_extensions = self.runtime.get_extensions();
+ let loaded_extensions = platform_info.get_extensions().to_vec();
// Extensions scanning
for name in &loaded_extensions {
@@ -318,7 +305,7 @@ impl PlatformRepository {
continue;
}
- self.add_extension(name, &self.runtime.get_extension_version(name))?;
+ self.add_extension(name, platform_info.get_extension_version(name))?;
}
// Check for Xdebug in a restarted process
@@ -340,13 +327,13 @@ impl PlatformRepository {
for name in &loaded_extensions {
match name.as_str() {
"amqp" => {
- let info = self.runtime.get_extension_info(name)?;
+ let info = platform_info.get_extension_info(name);
// librabbitmq version => 0.9.0
let mut librabbitmq_matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("/^librabbitmq version => (?<version>.+)$/im"),
- &info,
+ info,
Some(&mut librabbitmq_matches),
) {
self.add_library(
@@ -365,7 +352,7 @@ impl PlatformRepository {
let mut protocol_matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("/^AMQP protocol version => (?<version>.+)$/im"),
- &info,
+ info,
Some(&mut protocol_matches),
) {
let version_str = protocol_matches
@@ -384,13 +371,13 @@ impl PlatformRepository {
}
"bz2" => {
- let info = self.runtime.get_extension_info(name)?;
+ let info = platform_info.get_extension_info(name);
// BZip2 Version => 1.0.6, 6-Sept-2010
let mut matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("/^BZip2 Version => (?<version>.*),/im"),
- &info,
+ info,
Some(&mut matches),
) {
self.add_library(
@@ -407,9 +394,7 @@ impl PlatformRepository {
}
"curl" => {
- let curl_version = self
- .runtime
- .invoke(PhpMixed::String("curl_version".to_string()), vec![]);
+ let curl_version = &platform_info.curl_version;
let curl_version_str = curl_version
.as_array()
.and_then(|m| m.get("version"))
@@ -419,13 +404,13 @@ impl PlatformRepository {
self.add_library(&mut libraries, name, Some(cv), None, &[], &[])?;
}
- let info = self.runtime.get_extension_info(name)?;
+ let info = platform_info.get_extension_info(name);
// SSL Version => OpenSSL/1.0.1t
let mut ssl_matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("{^SSL Version => (?<library>[^/]+)/(?<version>.+)$}im"),
- &info,
+ info,
Some(&mut ssl_matches),
) {
let ssl_library_raw = ssl_matches
@@ -495,7 +480,7 @@ impl PlatformRepository {
php_regex!(
"{^libSSH Version => (?<library>[^/]+)/(?<version>.+?)(?:/.*)?$}im"
),
- &info,
+ info,
Some(&mut ssh_matches),
) {
let ssh_library = ssh_matches
@@ -520,7 +505,7 @@ impl PlatformRepository {
let mut zlib_matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("{^ZLib Version => (?<version>.+)$}im"),
- &info,
+ info,
Some(&mut zlib_matches),
) {
self.add_library(
@@ -537,13 +522,13 @@ impl PlatformRepository {
}
"date" => {
- let info = self.runtime.get_extension_info(name)?;
+ let info = platform_info.get_extension_info(name);
// timelib version => 2018.03
let mut timelib_matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("/^timelib version => (?<version>.+)$/im"),
- &info,
+ info,
Some(&mut timelib_matches),
) {
self.add_library(
@@ -562,7 +547,7 @@ impl PlatformRepository {
let mut zoneinfo_source_matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("/^Timezone Database => (?<source>internal|external)$/im"),
- &info,
+ info,
Some(&mut zoneinfo_source_matches),
) {
let external = zoneinfo_source_matches
@@ -574,7 +559,7 @@ impl PlatformRepository {
php_regex!(
"/^\"Olson\" Timezone Database Version => (?<version>.+?)(?:\\.system)?$/im"
),
- &info,
+ info,
Some(&mut zoneinfo_matches),
) {
let zoneinfo_version = zoneinfo_matches
@@ -608,13 +593,13 @@ impl PlatformRepository {
}
"fileinfo" => {
- let info = self.runtime.get_extension_info(name)?;
+ let info = platform_info.get_extension_info(name);
// libmagic => 537
let mut magic_matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("/^libmagic => (?<version>.+)$/im"),
- &info,
+ info,
Some(&mut magic_matches),
) {
self.add_library(
@@ -631,7 +616,7 @@ impl PlatformRepository {
}
"gd" => {
- let gd_version = self.runtime.get_constant("GD_VERSION", None);
+ let gd_version = platform_info.get_constant("GD_VERSION", None);
let gd_version_str = match &gd_version {
PhpMixed::String(s) => Some(s.clone()),
_ => None,
@@ -645,12 +630,12 @@ impl PlatformRepository {
&[],
)?;
- let info = self.runtime.get_extension_info(name)?;
+ let info = platform_info.get_extension_info(name);
let mut libjpeg_matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("/^libJPEG Version => (?<version>.+?)(?: compatible)?$/im"),
- &info,
+ info,
Some(&mut libjpeg_matches),
) {
let libjpeg_version = libjpeg_matches
@@ -671,7 +656,7 @@ impl PlatformRepository {
let mut libpng_matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("/^libPNG Version => (?<version>.+)$/im"),
- &info,
+ info,
Some(&mut libpng_matches),
) {
self.add_library(
@@ -689,7 +674,7 @@ impl PlatformRepository {
let mut freetype_matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("/^FreeType Version => (?<version>.+)$/im"),
- &info,
+ info,
Some(&mut freetype_matches),
) {
self.add_library(
@@ -707,7 +692,7 @@ impl PlatformRepository {
let mut libxpm_matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("/^libXpm Version => (?<versionId>\\d+)$/im"),
- &info,
+ info,
Some(&mut libxpm_matches),
) {
let version_id: i64 = libxpm_matches
@@ -727,7 +712,7 @@ impl PlatformRepository {
}
"gmp" => {
- let gmp_version = self.runtime.get_constant("GMP_VERSION", None);
+ let gmp_version = platform_info.get_constant("GMP_VERSION", None);
let gmp_version_str = match &gmp_version {
PhpMixed::String(s) => Some(s.clone()),
_ => None,
@@ -743,7 +728,7 @@ impl PlatformRepository {
}
"iconv" => {
- let iconv_version = self.runtime.get_constant("ICONV_VERSION", None);
+ let iconv_version = platform_info.get_constant("ICONV_VERSION", None);
let iconv_version_str = match &iconv_version {
PhpMixed::String(s) => Some(s.clone()),
_ => None,
@@ -759,12 +744,12 @@ impl PlatformRepository {
}
"intl" => {
- let info = self.runtime.get_extension_info(name)?;
+ let info = platform_info.get_extension_info(name);
let description = "The ICU unicode and globalization support library";
// Truthy check is for testing only so we can make the condition fail
- if self.runtime.has_constant("INTL_ICU_VERSION", None) {
- let intl_icu_version = self.runtime.get_constant("INTL_ICU_VERSION", None);
+ if platform_info.has_constant("INTL_ICU_VERSION", None) {
+ let intl_icu_version = platform_info.get_constant("INTL_ICU_VERSION", None);
let intl_icu_str = match &intl_icu_version {
PhpMixed::String(s) => Some(s.clone()),
_ => None,
@@ -781,7 +766,7 @@ impl PlatformRepository {
let mut matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("/^ICU version => (?<version>.+)$/im"),
- &info,
+ info,
Some(&mut matches),
) {
self.add_library(
@@ -801,7 +786,7 @@ impl PlatformRepository {
let mut zoneinfo_matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("/^ICU TZData version => (?<version>.*)$/im"),
- &info,
+ info,
Some(&mut zoneinfo_matches),
) {
let zi_version = zoneinfo_matches
@@ -821,22 +806,11 @@ impl PlatformRepository {
}
// Add a separate version for the CLDR library version
- if self.runtime.has_class("ResourceBundle") {
- let resource_bundle = self.runtime.invoke(
- PhpMixed::List(vec![
- PhpMixed::String("ResourceBundle".to_string()),
- PhpMixed::String("create".to_string()),
- ]),
- vec![
- PhpMixed::String("root".to_string()),
- PhpMixed::String("ICUDATA".to_string()),
- PhpMixed::Bool(false),
- ],
- );
+ if platform_info.has_class("ResourceBundle") {
+ let resource_bundle = &platform_info.resource_bundle;
if !matches!(resource_bundle, PhpMixed::Null) {
- // TODO(plugin): `$resourceBundle->get('Version')` dynamic method call
let version_value =
- Self::resource_bundle_get(&resource_bundle, "Version");
+ Self::resource_bundle_get(resource_bundle, "Version");
let version_str = match version_value {
PhpMixed::String(s) => Some(s),
_ => None,
@@ -852,16 +826,10 @@ impl PlatformRepository {
}
}
- if self.runtime.has_class("IntlChar") {
- let intl_char_versions = self.runtime.invoke(
- PhpMixed::List(vec![
- PhpMixed::String("IntlChar".to_string()),
- PhpMixed::String("getUnicodeVersion".to_string()),
- ]),
- vec![],
- );
+ if platform_info.has_class("IntlChar") {
+ let intl_char_versions = &platform_info.intl_char_unicode_version;
let sliced =
- shirabe_php_shim::array_slice_mixed(&intl_char_versions, 0, Some(3));
+ shirabe_php_shim::array_slice_mixed(intl_char_versions, 0, Some(3));
let joined = implode(".", &Self::php_array_to_string_vec(&sliced));
self.add_library(
&mut libraries,
@@ -875,10 +843,9 @@ impl PlatformRepository {
}
"imagick" => {
- let image_magick_version = self.runtime.construct("Imagick", Vec::new())?;
- // TODO(plugin): `->getVersion()` is a dynamic method call on Imagick
+ let image_magick_version = &platform_info.imagick;
let image_magick_version_str =
- Self::imagick_get_version_string(&image_magick_version);
+ Self::imagick_get_version_string(image_magick_version);
// 6.x: ImageMagick 6.2.9 08/24/06 Q16 http://www.imagemagick.org
// 7.x: ImageMagick 7.0.8-34 Q16 x86_64 2019-03-23 https://imagemagick.org
let mut matches: IndexMap<CaptureKey, String> = IndexMap::new();
@@ -907,17 +874,17 @@ impl PlatformRepository {
}
"ldap" => {
- let info = self.runtime.get_extension_info(name)?;
+ let info = platform_info.get_extension_info(name);
let mut matches: IndexMap<CaptureKey, String> = IndexMap::new();
let mut vendor_matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("/^Vendor Version => (?<versionId>\\d+)$/im"),
- &info,
+ info,
Some(&mut matches),
) && Preg::is_match3(
php_regex!("/^Vendor Name => (?<vendor>.+)$/im"),
- &info,
+ info,
Some(&mut vendor_matches),
) {
let version_id: i64 = matches
@@ -950,7 +917,7 @@ impl PlatformRepository {
.collect();
let libxml_provides: Vec<String> =
array_map_str_fn(|extension| format!("{}-libxml", extension), &intersected);
- let libxml_dotted = self.runtime.get_constant("LIBXML_DOTTED_VERSION", None);
+ let libxml_dotted = platform_info.get_constant("LIBXML_DOTTED_VERSION", None);
let libxml_dotted_str = match &libxml_dotted {
PhpMixed::String(s) => Some(s.clone()),
_ => None,
@@ -966,13 +933,13 @@ impl PlatformRepository {
}
"mbstring" => {
- let info = self.runtime.get_extension_info(name)?;
+ let info = platform_info.get_extension_info(name);
// libmbfl version => 1.3.2
let mut libmbfl_matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("/^libmbfl version => (?<version>.+)$/im"),
- &info,
+ info,
Some(&mut libmbfl_matches),
) {
self.add_library(
@@ -987,8 +954,8 @@ impl PlatformRepository {
)?;
}
- if self.runtime.has_constant("MB_ONIGURUMA_VERSION", None) {
- let oniguruma = self.runtime.get_constant("MB_ONIGURUMA_VERSION", None);
+ if platform_info.has_constant("MB_ONIGURUMA_VERSION", None) {
+ let oniguruma = platform_info.get_constant("MB_ONIGURUMA_VERSION", None);
let oniguruma_str = match &oniguruma {
PhpMixed::String(s) => Some(s.clone()),
_ => None,
@@ -1010,7 +977,7 @@ impl PlatformRepository {
php_regex!(
"/^(?:oniguruma|Multibyte regex \\(oniguruma\\)) version => (?<version>.+)$/im"
),
- &info,
+ info,
Some(&mut oniguruma_matches),
) {
self.add_library(
@@ -1028,13 +995,13 @@ impl PlatformRepository {
}
"memcached" => {
- let info = self.runtime.get_extension_info(name)?;
+ let info = platform_info.get_extension_info(name);
// libmemcached version => 1.0.18
let mut matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("/^libmemcached version => (?<version>.+)$/im"),
- &info,
+ info,
Some(&mut matches),
) {
self.add_library(
@@ -1051,7 +1018,7 @@ impl PlatformRepository {
}
"openssl" => {
- let openssl_text = self.runtime.get_constant("OPENSSL_VERSION_TEXT", None);
+ let openssl_text = platform_info.get_constant("OPENSSL_VERSION_TEXT", None);
let openssl_text_str = match &openssl_text {
PhpMixed::String(s) => s.clone(),
_ => "".to_string(),
@@ -1086,7 +1053,7 @@ impl PlatformRepository {
}
"pcre" => {
- let pcre_version = self.runtime.get_constant("PCRE_VERSION", None);
+ let pcre_version = platform_info.get_constant("PCRE_VERSION", None);
let pcre_version_str = match &pcre_version {
PhpMixed::String(s) => s.clone(),
_ => "".to_string(),
@@ -1095,13 +1062,13 @@ impl PlatformRepository {
Preg::replace(php_regex!("{^(\\S+).*}"), "$1", &pcre_version_str);
self.add_library(&mut libraries, name, Some(&stripped), None, &[], &[])?;
- let info = self.runtime.get_extension_info(name)?;
+ let info = platform_info.get_extension_info(name);
// PCRE Unicode Version => 12.1.0
let mut pcre_unicode_matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("/^PCRE Unicode Version => (?<version>.+)$/im"),
- &info,
+ info,
Some(&mut pcre_unicode_matches),
) {
self.add_library(
@@ -1118,14 +1085,14 @@ impl PlatformRepository {
}
"mysqlnd" | "pdo_mysql" => {
- let info = self.runtime.get_extension_info(name)?;
+ let info = platform_info.get_extension_info(name);
let mut matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!(
"/^(?:Client API version|Version) => mysqlnd (?<version>.+?) /mi"
),
- &info,
+ info,
Some(&mut matches),
) {
self.add_library(
@@ -1142,12 +1109,12 @@ impl PlatformRepository {
}
"mongodb" => {
- let info = self.runtime.get_extension_info(name)?;
+ let info = platform_info.get_extension_info(name);
let mut libmongoc_matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("/^libmongoc bundled version => (?<version>.+)$/im"),
- &info,
+ info,
Some(&mut libmongoc_matches),
) {
self.add_library(
@@ -1165,7 +1132,7 @@ impl PlatformRepository {
let mut libbson_matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("/^libbson bundled version => (?<version>.+)$/im"),
- &info,
+ info,
Some(&mut libbson_matches),
) {
self.add_library(
@@ -1182,8 +1149,8 @@ impl PlatformRepository {
}
"pgsql" => {
- if self.runtime.has_constant("PGSQL_LIBPQ_VERSION", None) {
- let pq_version = self.runtime.get_constant("PGSQL_LIBPQ_VERSION", None);
+ if platform_info.has_constant("PGSQL_LIBPQ_VERSION", None) {
+ let pq_version = platform_info.get_constant("PGSQL_LIBPQ_VERSION", None);
let pq_version_str = match &pq_version {
PhpMixed::String(s) => Some(s.clone()),
_ => None,
@@ -1198,12 +1165,12 @@ impl PlatformRepository {
)?;
} else {
// intentional fall-through to next case...
- let info = self.runtime.get_extension_info(name)?;
+ let info = platform_info.get_extension_info(name);
let mut matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("/^PostgreSQL\\(libpq\\) Version => (?<version>.*)$/im"),
- &info,
+ info,
Some(&mut matches),
) {
self.add_library(
@@ -1221,12 +1188,12 @@ impl PlatformRepository {
}
"pdo_pgsql" => {
- let info = self.runtime.get_extension_info(name)?;
+ let info = platform_info.get_extension_info(name);
let mut matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("/^PostgreSQL\\(libpq\\) Version => (?<version>.*)$/im"),
- &info,
+ info,
Some(&mut matches),
) {
self.add_library(
@@ -1243,14 +1210,14 @@ impl PlatformRepository {
}
"pq" => {
- let info = self.runtime.get_extension_info(name)?;
+ let info = platform_info.get_extension_info(name);
// Used Library => Compiled => Linked
// libpq => 14.3 (Ubuntu 14.3-1.pgdg22.04+1) => 15.0.2
let mut matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("/^libpq => (?<compiled>.+) => (?<linked>.+)$/im"),
- &info,
+ info,
Some(&mut matches),
) {
self.add_library(
@@ -1267,7 +1234,7 @@ impl PlatformRepository {
}
"rdkafka" => {
- if self.runtime.has_constant("RD_KAFKA_VERSION", None) {
+ if platform_info.has_constant("RD_KAFKA_VERSION", None) {
// Interpreted as hex MM.mm.rr.xx:
// - MM = Major
// - mm = minor
@@ -1275,8 +1242,7 @@ impl PlatformRepository {
// - xx = pre-release id (0xff is the final release)
//
// pre-release ID in practice is always 0xff even for RCs etc, so we ignore it
- let lib_rd_kafka_version_int = self
- .runtime
+ let lib_rd_kafka_version_int = platform_info
.get_constant("RD_KAFKA_VERSION", None)
.as_int()
.unwrap_or(0);
@@ -1298,8 +1264,8 @@ impl PlatformRepository {
}
"libsodium" | "sodium" => {
- if self.runtime.has_constant("SODIUM_LIBRARY_VERSION", None) {
- let sodium = self.runtime.get_constant("SODIUM_LIBRARY_VERSION", None);
+ if platform_info.has_constant("SODIUM_LIBRARY_VERSION", None) {
+ let sodium = platform_info.get_constant("SODIUM_LIBRARY_VERSION", None);
let sodium_str = match &sodium {
PhpMixed::String(s) => Some(s.clone()),
_ => None,
@@ -1324,12 +1290,12 @@ impl PlatformRepository {
}
"sqlite3" | "pdo_sqlite" => {
- let info = self.runtime.get_extension_info(name)?;
+ let info = platform_info.get_extension_info(name);
let mut matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("/^SQLite Library => (?<version>.+)$/im"),
- &info,
+ info,
Some(&mut matches),
) {
self.add_library(
@@ -1346,12 +1312,12 @@ impl PlatformRepository {
}
"ssh2" => {
- let info = self.runtime.get_extension_info(name)?;
+ let info = platform_info.get_extension_info(name);
let mut matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("/^libssh2 version => (?<version>.+)$/im"),
- &info,
+ info,
Some(&mut matches),
) {
self.add_library(
@@ -1368,7 +1334,8 @@ impl PlatformRepository {
}
"xsl" => {
- let libxslt_version = self.runtime.get_constant("LIBXSLT_DOTTED_VERSION", None);
+ let libxslt_version =
+ platform_info.get_constant("LIBXSLT_DOTTED_VERSION", None);
let libxslt_str = match &libxslt_version {
PhpMixed::String(s) => Some(s.clone()),
_ => None,
@@ -1382,13 +1349,13 @@ impl PlatformRepository {
&[],
)?;
- let info = self.runtime.get_extension_info("xsl")?;
+ let info = platform_info.get_extension_info("xsl");
let mut matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!(
"/^libxslt compiled against libxml Version => (?<version>.+)$/im"
),
- &info,
+ info,
Some(&mut matches),
) {
self.add_library(
@@ -1405,12 +1372,12 @@ impl PlatformRepository {
}
"yaml" => {
- let info = self.runtime.get_extension_info("yaml")?;
+ let info = platform_info.get_extension_info("yaml");
let mut matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("/^LibYAML Version => (?<version>.+)$/im"),
- &info,
+ info,
Some(&mut matches),
) {
self.add_library(
@@ -1427,13 +1394,9 @@ impl PlatformRepository {
}
"zip" => {
- if self
- .runtime
- .has_constant("LIBZIP_VERSION", Some("ZipArchive".to_string()))
- {
- let libzip = self
- .runtime
- .get_constant("LIBZIP_VERSION", Some("ZipArchive".to_string()));
+ if platform_info.has_constant("LIBZIP_VERSION", Some("ZipArchive")) {
+ let libzip =
+ platform_info.get_constant("LIBZIP_VERSION", Some("ZipArchive"));
let libzip_str = match &libzip {
PhpMixed::String(s) => Some(s.clone()),
_ => None,
@@ -1450,8 +1413,8 @@ impl PlatformRepository {
}
"zlib" => {
- if self.runtime.has_constant("ZLIB_VERSION", None) {
- let zlib = self.runtime.get_constant("ZLIB_VERSION", None);
+ if platform_info.has_constant("ZLIB_VERSION", None) {
+ let zlib = platform_info.get_constant("ZLIB_VERSION", None);
let zlib_str = match &zlib {
PhpMixed::String(s) => Some(s.clone()),
_ => None,
@@ -1467,11 +1430,11 @@ impl PlatformRepository {
// Linked Version => 1.2.8
} else {
- let info = self.runtime.get_extension_info(name)?;
+ let info = platform_info.get_extension_info(name);
let mut matches: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
php_regex!("/^Linked Version => (?<version>.+)$/im"),
- &info,
+ info,
Some(&mut matches),
) {
self.add_library(
@@ -1840,7 +1803,7 @@ impl PlatformRepository {
}
/// PHP `$resourceBundle->get($key)`. A live PHP object has no `PhpMixed` counterpart, so
- /// [`RuntimeInterface`] answers with the entries the caller reads instead of the object.
+ /// [`PlatformInfo`] carries the entries the caller reads instead of the object.
fn resource_bundle_get(value: &PhpMixed, key: &str) -> PhpMixed {
Self::php_object_field(value, key).unwrap_or(PhpMixed::Null)
}
diff --git a/crates/shirabe/tests/package/version/version_selector_test.rs b/crates/shirabe/tests/package/version/version_selector_test.rs
index 13544399..16ad5fe2 100644
--- a/crates/shirabe/tests/package/version/version_selector_test.rs
+++ b/crates/shirabe/tests/package/version/version_selector_test.rs
@@ -120,7 +120,7 @@ fn test_latest_version_is_returned_that_matches_php_requirements() {
let mut overrides: IndexMap<String, PhpMixed> = IndexMap::new();
overrides.insert("php".to_string(), PhpMixed::String("5.5.0".to_string()));
- let mut platform = PlatformRepository::new(vec![], overrides).unwrap();
+ let mut platform = PlatformRepository::new(vec![], overrides, None, None).unwrap();
let package0 = get_package("foo/bar", "0.9.0");
package0.__set_requires(IndexMap::from([(
@@ -216,7 +216,7 @@ fn test_latest_version_is_returned_that_matches_ext_requirements() {
let mut overrides: IndexMap<String, PhpMixed> = IndexMap::new();
overrides.insert("ext-zip".to_string(), PhpMixed::String("5.3.0".to_string()));
- let mut platform = PlatformRepository::new(vec![], overrides).unwrap();
+ let mut platform = PlatformRepository::new(vec![], overrides, None, None).unwrap();
let package1 = get_package("foo/bar", "1.0.0");
package1.__set_requires(IndexMap::from([(
@@ -263,7 +263,7 @@ fn test_latest_version_is_returned_that_matches_ext_requirements() {
fn test_latest_version_is_returned_that_matches_platform_ext() {
let package_name = "foo/bar";
- let mut platform = PlatformRepository::new(vec![], IndexMap::new()).unwrap();
+ let mut platform = PlatformRepository::new(vec![], IndexMap::new(), None, None).unwrap();
let package1 = get_package("foo/bar", "1.0.0");
let package2 = get_package("foo/bar", "2.0.0");
@@ -311,7 +311,7 @@ fn test_latest_version_is_returned_that_matches_composer_requirements() {
"composer-runtime-api".to_string(),
PhpMixed::String("1.0.0".to_string()),
);
- let mut platform = PlatformRepository::new(vec![], overrides).unwrap();
+ let mut platform = PlatformRepository::new(vec![], overrides, None, None).unwrap();
let package1 = get_package("foo/bar", "1.0.0");
package1.__set_requires(IndexMap::from([(
diff --git a/crates/shirabe/tests/platform/main.rs b/crates/shirabe/tests/platform/main.rs
index 936049ac..3cf2cf48 100644
--- a/crates/shirabe/tests/platform/main.rs
+++ b/crates/shirabe/tests/platform/main.rs
@@ -1,3 +1,2 @@
mod hhvm_detector_test;
-mod runtime_test;
mod version_test;
diff --git a/crates/shirabe/tests/platform/runtime_test.rs b/crates/shirabe/tests/platform/runtime_test.rs
deleted file mode 100644
index ebdeaffa..00000000
--- a/crates/shirabe/tests/platform/runtime_test.rs
+++ /dev/null
@@ -1,28 +0,0 @@
-//! ref: composer/tests/Composer/Test/Platform/RuntimeTest.php
-
-use shirabe::platform::runtime::Runtime;
-
-#[test]
-fn test_parse_extension_info() {
- for (html_input, expected_output) in provide_extension_infos() {
- assert_eq!(
- expected_output,
- Runtime::parse_html_extension_info(html_input)
- );
- }
-}
-
-fn provide_extension_infos() -> Vec<(&'static str, &'static str)> {
- vec![(
- // 'pdo_sqlite'
- "<h2><a name=\"module_pdo_sqlite\" href=\"#module_pdo_sqlite\">pdo_sqlite</a></h2>
-<table>
-<tr><td class=\"e\">PDO Driver for SQLite 3.x </td><td class=\"v\">enabled </td></tr>
-<tr><td class=\"e\">SQLite Library </td><td class=\"v\">3.40.1 </td></tr>
-</table>",
- "pdo_sqlite
-
-PDO Driver for SQLite 3.x => enabled
-SQLite Library => 3.40.1",
- )]
-}
diff --git a/crates/shirabe/tests/repository/platform_repository_test.rs b/crates/shirabe/tests/repository/platform_repository_test.rs
index 3a4bf45d..573f96ed 100644
--- a/crates/shirabe/tests/repository/platform_repository_test.rs
+++ b/crates/shirabe/tests/repository/platform_repository_test.rs
@@ -1,31 +1,15 @@
//! ref: composer/tests/Composer/Test/Repository/PlatformRepositoryTest.php
use indexmap::IndexMap;
-use mockall::predicate::eq;
use shirabe::package::{BasePackageHandle, Link};
-use shirabe::platform::{HhvmDetectorInterface, RuntimeInterface};
+use shirabe::platform::HhvmDetectorInterface;
use shirabe::repository::{
FindPackageConstraint, PlatformRepository, RepositoryInterface, SEARCH_NAME,
};
+use shirabe_php_rpc::PlatformInfo;
use shirabe_php_shim::PhpMixed;
use shirabe_semver::constraint::SimpleConstraint;
-// The Runtime/HhvmDetector seams are concrete structs in PHP; the tests mock them
-// directly.
-mockall::mock! {
- pub Runtime {}
- impl RuntimeInterface for Runtime {
- fn has_constant(&self, constant_name: &str, class: Option<String>) -> bool;
- fn get_constant(&self, constant_name: &str, class: Option<String>) -> PhpMixed;
- fn invoke(&self, callable: PhpMixed, arguments: Vec<PhpMixed>) -> PhpMixed;
- fn has_class(&self, class: &str) -> bool;
- fn construct(&self, class: &str, arguments: Vec<PhpMixed>) -> anyhow::Result<PhpMixed>;
- fn get_extensions(&self) -> Vec<String>;
- fn get_extension_version(&self, extension: &str) -> String;
- fn get_extension_info(&self, extension: &str) -> anyhow::Result<String>;
- }
-}
-
mockall::mock! {
pub HhvmDetector {}
impl HhvmDetectorInterface for HhvmDetector {
@@ -34,25 +18,71 @@ mockall::mock! {
}
}
-// The seam traits require `Debug` (so `PlatformRepository` can derive it); mockall does
+// The seam trait requires `Debug` (so `PlatformRepository` can derive it); mockall does
// not generate it for mocks.
-impl std::fmt::Debug for MockRuntime {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.write_str("MockRuntime")
- }
-}
-
impl std::fmt::Debug for MockHhvmDetector {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("MockHhvmDetector")
}
}
-/// PHP: ltrim($class.'::'.$constant, ':')
-fn constant_key(constant_name: &str, class: Option<&str>) -> String {
- format!("{}::{}", class.unwrap_or(""), constant_name)
- .trim_start_matches(':')
- .to_string()
+/// The payload form of what a dataset's `Runtime` mock answers: the listed constants and classes
+/// exist, and each extension is loaded at `extension_version` with `info` as its info() output.
+fn platform_info(
+ constants: Vec<(String, Option<String>, PhpMixed)>,
+ extensions: Vec<String>,
+ extension_version: &str,
+ info: Option<&str>,
+ functions: &[(PhpMixed, Vec<PhpMixed>, PhpMixed)],
+ class_definitions: &[ClassDef],
+) -> PlatformInfo {
+ let mut platform_info = PlatformInfo::default();
+
+ for (constant_name, class, value) in constants {
+ platform_info.__set_constant(&constant_name, class.as_deref(), value);
+ }
+
+ for extension in &extensions {
+ platform_info.__set_extension_info(extension, info.unwrap_or_default());
+ }
+ platform_info.__set_extensions(extensions, extension_version);
+
+ for (callable, arguments, result) in functions {
+ match (callable, arguments.as_slice()) {
+ (PhpMixed::String(name), []) if name == "curl_version" => {
+ platform_info.curl_version = result.clone();
+ }
+ (PhpMixed::String(name), [PhpMixed::String(address)])
+ if name == "inet_pton" && address == "::" =>
+ {
+ platform_info.inet_pton_ipv6 = result.clone();
+ }
+ (PhpMixed::List(spec), _) => match spec.as_slice() {
+ [PhpMixed::String(class), PhpMixed::String(method)]
+ if class == "ResourceBundle" && method == "create" =>
+ {
+ platform_info.resource_bundle = result.clone();
+ }
+ [PhpMixed::String(class), PhpMixed::String(method)]
+ if class == "IntlChar" && method == "getUnicodeVersion" =>
+ {
+ platform_info.intl_char_unicode_version = result.clone();
+ }
+ other => panic!("the platform payload does not report {other:?}"),
+ },
+ other => panic!("the platform payload does not report {other:?}"),
+ }
+ }
+
+ for definition in class_definitions {
+ match (definition.class, &definition.construct) {
+ ("Imagick", Some((_arguments, result))) => platform_info.imagick = result.clone(),
+ (class, None) => platform_info.__set_class(class),
+ (class, Some(_)) => panic!("the platform payload does not construct {class}"),
+ }
+ }
+
+ platform_info
}
#[test]
@@ -64,7 +94,7 @@ fn test_hhvm_package() {
.returning(|| Some("2.1.0".to_string()));
let mut platform_repository =
- PlatformRepository::new4(vec![], IndexMap::new(), None, Some(Box::new(hhvm_detector)))
+ PlatformRepository::new(vec![], IndexMap::new(), None, Some(Box::new(hhvm_detector)))
.unwrap();
let hhvm = platform_repository
@@ -137,44 +167,20 @@ fn php_flavor_test_cases() -> Vec<(
#[test]
fn test_php_version() {
for (constants, packages, functions) in php_flavor_test_cases() {
- let constants_has = constants.clone();
- let constants_get = constants.clone();
-
- let mut runtime = MockRuntime::new();
- runtime
- .expect_get_extensions()
- .times(..)
- .returning(Vec::new);
- runtime
- .expect_has_constant()
- .times(..)
- .returning(move |constant, class| {
- constants_has.contains_key(&constant_key(constant, class.as_deref()))
- });
- runtime
- .expect_get_constant()
- .times(..)
- .returning(move |constant, class| {
- constants_get
- .get(&constant_key(constant, class.as_deref()))
- .cloned()
- .unwrap_or(PhpMixed::Null)
- });
- runtime
- .expect_invoke()
- .times(..)
- .returning(move |callable, arguments| {
- for (c, a, ret) in &functions {
- if *c == callable && *a == arguments {
- return ret.clone();
- }
- }
- PhpMixed::Null
- });
+ let platform_info = platform_info(
+ constants
+ .into_iter()
+ .map(|(constant_name, value)| (constant_name, None, value))
+ .collect(),
+ Vec::new(),
+ "",
+ None,
+ &functions,
+ &[],
+ );
let mut repository =
- PlatformRepository::new4(vec![], IndexMap::new(), Some(Box::new(runtime)), None)
- .unwrap();
+ PlatformRepository::new(vec![], IndexMap::new(), Some(platform_info), None).unwrap();
for (package_name, version) in packages {
let package = repository
@@ -198,22 +204,16 @@ fn test_php_version() {
#[test]
fn test_inet_pton_regression() {
- let mut runtime = MockRuntime::new();
// PHP: ->expects(self::once())->method('invoke')->with('inet_pton', ['::'])->willReturn(false).
- runtime
- .expect_invoke()
- .with(
- eq(PhpMixed::String("inet_pton".to_string())),
- eq(vec![PhpMixed::String("::".to_string())]),
- )
- .times(1)
- .returning(|_callable, _arguments| PhpMixed::Bool(false));
- // suppressing PHP_ZTS & AF_INET6
- runtime
- .expect_has_constant()
- .times(..)
- .returning(|_, _| false);
+ // TODO(phase-d): the payload reports the result of `@inet_pton('::')` instead of answering a
+ // call, so there is nothing left for the once() call-count check to observe.
+ let functions = [(
+ PhpMixed::String("inet_pton".to_string()),
+ vec![PhpMixed::String("::".to_string())],
+ PhpMixed::Bool(false),
+ )];
+ // suppressing PHP_ZTS & AF_INET6 by leaving them undefined
let constants: IndexMap<String, PhpMixed> = IndexMap::from([
(
"PHP_VERSION".to_string(),
@@ -221,22 +221,21 @@ fn test_inet_pton_regression() {
),
("PHP_DEBUG".to_string(), PhpMixed::Bool(false)),
]);
- runtime
- .expect_get_constant()
- .times(..)
- .returning(move |constant, class| {
- constants
- .get(&constant_key(constant, class.as_deref()))
- .cloned()
- .unwrap_or(PhpMixed::Null)
- });
- runtime
- .expect_get_extensions()
- .times(..)
- .returning(Vec::new);
+
+ let platform_info = platform_info(
+ constants
+ .into_iter()
+ .map(|(constant_name, value)| (constant_name, None, value))
+ .collect(),
+ Vec::new(),
+ "",
+ None,
+ &functions,
+ &[],
+ );
let mut repository =
- PlatformRepository::new4(vec![], IndexMap::new(), Some(Box::new(runtime)), None).unwrap();
+ PlatformRepository::new(vec![], IndexMap::new(), Some(platform_info), None).unwrap();
let package = repository
.find_package("php-ipv6", FindPackageConstraint::String("*".to_string()))
.unwrap();
@@ -1630,79 +1629,17 @@ fn test_library_information() {
PhpMixed::String("7.1.0".to_string()),
));
- let functions = case.functions.clone();
- let info = case.info.map(|s| s.to_string());
-
- let exts_for_get = extensions.clone();
- let constants_has = constants.clone();
- let constants_get = constants.clone();
-
- let mut runtime = MockRuntime::new();
- runtime
- .expect_get_extensions()
- .times(..)
- .returning(move || exts_for_get.clone());
- runtime
- .expect_get_extension_version()
- .times(..)
- .returning(move |_extension| extension_version.to_string());
- runtime
- .expect_get_extension_info()
- .times(..)
- .returning(move |_extension| Ok(info.clone().unwrap_or_default()));
- runtime
- .expect_invoke()
- .times(..)
- .returning(move |callable, arguments| {
- for (c, a, ret) in &functions {
- if *c == callable && *a == arguments {
- return ret.clone();
- }
- }
- PhpMixed::Null
- });
- runtime
- .expect_has_constant()
- .times(..)
- .returning(move |constant, class| {
- constants_has
- .iter()
- .any(|(n, c, _)| n == constant && c.as_deref() == class.as_deref())
- });
- runtime
- .expect_get_constant()
- .times(..)
- .returning(move |constant, class| {
- constants_get
- .iter()
- .find(|(n, c, _)| n == constant && c.as_deref() == class.as_deref())
- .map(|(_, _, v)| v.clone())
- .unwrap_or(PhpMixed::Null)
- });
- let class_definitions_has = case.class_definitions.clone();
- let class_definitions_construct = case.class_definitions.clone();
- runtime
- .expect_has_class()
- .times(..)
- .returning(move |class| class_definitions_has.iter().any(|d| d.class == class));
- runtime
- .expect_construct()
- .times(..)
- .returning(move |class, arguments| {
- for d in &class_definitions_construct {
- if d.class == class
- && let Some((args, ret)) = &d.construct
- && *args == arguments
- {
- return Ok(ret.clone());
- }
- }
- Ok(PhpMixed::Null)
- });
+ let platform_info = platform_info(
+ constants,
+ extensions.clone(),
+ extension_version,
+ case.info,
+ &case.functions,
+ &case.class_definitions,
+ );
let mut platform_repository =
- PlatformRepository::new4(vec![], IndexMap::new(), Some(Box::new(runtime)), None)
- .unwrap();
+ PlatformRepository::new(vec![], IndexMap::new(), Some(platform_info), None).unwrap();
let libraries: Vec<String> = platform_repository
.search("lib".to_string(), SEARCH_NAME, None)
@@ -1794,33 +1731,22 @@ fn test_composer_platform_version() {
("PHP_DEBUG".to_string(), PhpMixed::Bool(false)),
]);
- let mut runtime = MockRuntime::new();
- runtime
- .expect_get_extensions()
- .times(..)
- .returning(Vec::new);
- runtime
- .expect_get_constant()
- .times(..)
- .returning(move |constant, class| {
- constants
- .get(&constant_key(constant, class.as_deref()))
- .cloned()
- .unwrap_or(PhpMixed::Null)
- });
// PHP only stubs getExtensions/getConstant; PHPUnit auto-returns null/false for the
- // other probed methods. Mirror that so initialize() does not hit unset expectations.
- runtime
- .expect_has_constant()
- .times(..)
- .returning(|_, _| false);
- runtime
- .expect_invoke()
- .times(..)
- .returning(|_, _| PhpMixed::Null);
+ // other methods, which is what the default payload reports.
+ let platform_info = platform_info(
+ constants
+ .into_iter()
+ .map(|(constant_name, value)| (constant_name, None, value))
+ .collect(),
+ Vec::new(),
+ "",
+ None,
+ &[],
+ &[],
+ );
let mut platform_repository =
- PlatformRepository::new4(vec![], IndexMap::new(), Some(Box::new(runtime)), None).unwrap();
+ PlatformRepository::new(vec![], IndexMap::new(), Some(platform_info), None).unwrap();
let package = platform_repository
.find_package(