diff options
Diffstat (limited to 'crates/shirabe')
20 files changed, 262 insertions, 621 deletions
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(<rim( - &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(<rim( - &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( |
