aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-07 20:51:59 +0900
committernsfisis <nsfisis@gmail.com>2026-08-07 20:51:59 +0900
commitae365645730b95d5b01b0df7382b91668d5fa24e (patch)
tree962e3abb51132097ff11a79d453e058945ae699c /crates/shirabe
parentf749a47804cd296a3059cd3f8079c62dbaa5fdc0 (diff)
downloadphp-shirabe-ae365645730b95d5b01b0df7382b91668d5fa24e.tar.gz
php-shirabe-ae365645730b95d5b01b0df7382b91668d5fa24e.tar.zst
php-shirabe-ae365645730b95d5b01b0df7382b91668d5fa24e.zip
refactor(platform-repository): fetch the PHP runtime in one RPC call
The RuntimeInterface seam asked the worker one question at a time: a round trip per loaded extension, per ReflectionExtension::info() output and per constant, so a single `show --platform` cost 70 to 100 of them. A `platform` dispatch entry now answers all of it as one PHP array, which shirabe-php-rpc decodes into a OnceLock-cached PlatformInfo, the way the diagnose command already works. Composer\Platform\Runtime therefore has no Rust counterpart any more. Its work belongs to the running interpreter, and invoke()/construct() could only be ported as a whitelist that panicked on anything unlisted; it is ported as PHP into the worker instead, and PlatformRepository reads the answers off PlatformInfo. Accessors panic on a name the payload does not carry, so the worker and its consumers cannot drift apart unnoticed. The tests describe the runtime as payload data where they used to mock the seam, with the datasets unchanged. The one loss is the call-count assertion of test_inet_pton_regression: the payload reports the result of `@inet_pton('::')` rather than answering a call. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe')
-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
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(&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(