aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/shirabe/src')
-rw-r--r--crates/shirabe/src/event_dispatcher/event_dispatcher.rs112
-rw-r--r--crates/shirabe/src/installer/installation_manager.rs98
-rw-r--r--crates/shirabe/src/installer/package_event.rs24
-rw-r--r--crates/shirabe/src/plugin/php_plugin_proxy.rs144
4 files changed, 281 insertions, 97 deletions
diff --git a/crates/shirabe/src/event_dispatcher/event_dispatcher.rs b/crates/shirabe/src/event_dispatcher/event_dispatcher.rs
index 1480188a..b821f647 100644
--- a/crates/shirabe/src/event_dispatcher/event_dispatcher.rs
+++ b/crates/shirabe/src/event_dispatcher/event_dispatcher.rs
@@ -16,7 +16,7 @@ use crate::installer::PackageEvent;
use crate::io::IOInterface;
use crate::io::IOInterfaceImmutable;
use crate::plugin::php_plugin_proxy::PluginRpcDispatcher;
-use crate::repository::RepositoryInterface;
+use crate::repository::InstalledRepositoryInterfaceHandle;
use crate::script::Event as ScriptEvent;
use crate::util::Platform;
use crate::util::ProcessExecutor;
@@ -250,9 +250,9 @@ impl EventDispatcher {
&mut self,
event_name: &str,
dev_mode: bool,
- local_repo: Box<dyn RepositoryInterface>,
- operations: Vec<AnyOperation>,
- operation: AnyOperation,
+ local_repo: InstalledRepositoryInterfaceHandle,
+ operations: Vec<std::rc::Rc<AnyOperation>>,
+ operation: std::rc::Rc<AnyOperation>,
) -> anyhow::Result<i64> {
let composer = self.composer();
assert!(
@@ -432,8 +432,7 @@ impl EventDispatcher {
crate::io::VERBOSE,
);
let stub_class = Self::event_stub_class(event).ok_or_else(|| {
- // TODO(plugin): only the base Event and Script\Event proxy stubs exist so
- // far; installer/package/plugin events need their own stubs.
+ // TODO(plugin): installer and plugin events have no proxy stub yet.
anyhow::anyhow!(RuntimeException {
message: format!(
"no proxy stub is available yet for the event `{}` dispatched to {}::{}",
@@ -1154,8 +1153,7 @@ try {{
}
let stub_class = Self::event_stub_class(event).ok_or_else(|| {
- // TODO(plugin): only the base Event and Script\Event proxy stubs exist so far;
- // installer/package/plugin events need their own stubs.
+ // TODO(plugin): installer and plugin events have no proxy stub yet.
anyhow::anyhow!(RuntimeException {
message: format!(
"no proxy stub is available yet for the event `{}` dispatched to {}::{}",
@@ -1200,6 +1198,8 @@ try {{
fn event_stub_class(event: &dyn EventInterface) -> Option<&'static str> {
if event.as_any().downcast_ref::<ScriptEvent>().is_some() {
Some("Composer\\Script\\Event")
+ } else if event.as_any().downcast_ref::<PackageEvent>().is_some() {
+ Some("Composer\\Installer\\PackageEvent")
} else if event.as_any().downcast_ref::<Event>().is_some() {
Some("Composer\\EventDispatcher\\Event")
} else {
@@ -1755,39 +1755,75 @@ pub(crate) fn dispatch_event_method(
event.get_flags().clone(),
))),
"isPropagationStopped" => Ok(PluginValue::Bool(event.is_propagation_stopped())),
- "isDevMode" => match event.as_any().downcast_ref::<ScriptEvent>() {
- Some(script_event) => Ok(PluginValue::Bool(script_event.is_dev_mode())),
+ "isDevMode" => match (script_event(event), package_event(event)) {
+ (Some(event), _) => Ok(PluginValue::Bool(event.is_dev_mode())),
+ (_, Some(event)) => Ok(PluginValue::Bool(event.is_dev_mode())),
+ _ => Err(runtime_throw(
+ "isDevMode is only available on script and package events".to_string(),
+ )),
+ },
+ "getComposer" => {
+ let composer = match (script_event(event), package_event(event)) {
+ (Some(event), _) => event.get_composer().upgrade(),
+ (_, Some(event)) => event.get_composer().upgrade(),
+ _ => {
+ return Err(runtime_throw(
+ "getComposer is only available on script and package events".to_string(),
+ ));
+ }
+ };
+ let composer = composer.ok_or_else(|| {
+ runtime_throw("the Composer instance of this event is gone".to_string())
+ })?;
+ let rhandle = crate::plugin::php_plugin_proxy::register_composer_entity(&composer);
+ Ok(crate::plugin::php_plugin_proxy::rust_handle_value(
+ rhandle,
+ "Composer\\Composer",
+ ))
+ }
+ "getIO" => {
+ let io = match (script_event(event), package_event(event)) {
+ (Some(event), _) => event.get_io(),
+ (_, Some(event)) => event.get_io(),
+ _ => {
+ return Err(runtime_throw(
+ "getIO is only available on script and package events".to_string(),
+ ));
+ }
+ };
+ let class = crate::plugin::php_plugin_proxy::io_stub_class(&io)
+ .map_err(|error| runtime_throw(error.to_string()))?;
+ let rhandle = crate::plugin::php_plugin_proxy::register_io_entity(&io);
+ Ok(crate::plugin::php_plugin_proxy::rust_handle_value(
+ rhandle, class,
+ ))
+ }
+ "getLocalRepo" => match package_event(event) {
+ Some(event) => crate::plugin::php_plugin_proxy::repository_handle_value(
+ &event.get_local_repo().as_repository_handle(),
+ ),
None => Err(runtime_throw(
- "isDevMode is only available on script events".to_string(),
+ "getLocalRepo is only available on package events".to_string(),
)),
},
- "getComposer" => match event.as_any().downcast_ref::<ScriptEvent>() {
- Some(script_event) => {
- let composer = script_event.get_composer().upgrade().ok_or_else(|| {
- runtime_throw("the Composer instance of this event is gone".to_string())
- })?;
- let rhandle = crate::plugin::php_plugin_proxy::register_composer_entity(&composer);
- Ok(crate::plugin::php_plugin_proxy::rust_handle_value(
- rhandle,
- "Composer\\Composer",
- ))
- }
+ "getOperations" => match package_event(event) {
+ Some(event) => Ok(PluginValue::List(
+ event
+ .get_operations()
+ .iter()
+ .map(crate::plugin::php_plugin_proxy::operation_handle_value)
+ .collect(),
+ )),
None => Err(runtime_throw(
- "getComposer is only available on script events".to_string(),
+ "getOperations is only available on package events".to_string(),
)),
},
- "getIO" => match event.as_any().downcast_ref::<ScriptEvent>() {
- Some(script_event) => {
- let io = script_event.get_io();
- let class = crate::plugin::php_plugin_proxy::io_stub_class(&io)
- .map_err(|error| runtime_throw(error.to_string()))?;
- let rhandle = crate::plugin::php_plugin_proxy::register_io_entity(&io);
- Ok(crate::plugin::php_plugin_proxy::rust_handle_value(
- rhandle, class,
- ))
- }
+ "getOperation" => match package_event(event) {
+ Some(event) => Ok(crate::plugin::php_plugin_proxy::operation_handle_value(
+ event.get_operation(),
+ )),
None => Err(runtime_throw(
- "getIO is only available on script events".to_string(),
+ "getOperation is only available on package events".to_string(),
)),
},
// TODO(plugin): stopPropagation and the rest need full proxying of the object graph
@@ -1798,6 +1834,14 @@ pub(crate) fn dispatch_event_method(
}
}
+fn script_event(event: &dyn EventInterface) -> Option<&ScriptEvent> {
+ event.as_any().downcast_ref::<ScriptEvent>()
+}
+
+fn package_event(event: &dyn EventInterface) -> Option<&PackageEvent> {
+ event.as_any().downcast_ref::<PackageEvent>()
+}
+
fn runtime_throw(message: String) -> PhpThrow {
PhpThrow {
exception_class: "RuntimeException".to_string(),
diff --git a/crates/shirabe/src/installer/installation_manager.rs b/crates/shirabe/src/installer/installation_manager.rs
index 439026b0..1f1c60a5 100644
--- a/crates/shirabe/src/installer/installation_manager.rs
+++ b/crates/shirabe/src/installer/installation_manager.rs
@@ -327,16 +327,19 @@ impl InstallationManager {
}),
);
- let all_operations: Vec<AnyOperation> = operations.clone();
+ // Shared rather than owned so that one operation reaches a plugin as one object, both
+ // through the whole batch pipeline and through its pre- and post-event.
+ let all_operations: Vec<std::rc::Rc<AnyOperation>> =
+ operations.into_iter().map(std::rc::Rc::new).collect();
let result: anyhow::Result<()> = (|| -> anyhow::Result<()> {
// execute operations in batches to make sure download-modifying-plugins are installed
// before the other packages get downloaded
- let mut batches: Vec<IndexMap<i64, AnyOperation>> = vec![];
- let mut batch: IndexMap<i64, AnyOperation> = IndexMap::new();
- for (index, operation) in operations.into_iter().enumerate() {
+ let mut batches: Vec<IndexMap<i64, std::rc::Rc<AnyOperation>>> = vec![];
+ let mut batch: IndexMap<i64, std::rc::Rc<AnyOperation>> = IndexMap::new();
+ for (index, operation) in all_operations.iter().cloned().enumerate() {
let index = index as i64;
- let package: Option<PackageInterfaceHandle> = match &operation {
+ let package: Option<PackageInterfaceHandle> = match &*operation {
AnyOperation::Update(update) => Some(update.get_target_package()),
AnyOperation::Install(install) => Some(install.get_package()),
_ => None,
@@ -409,7 +412,7 @@ impl InstallationManager {
async fn download_and_execute_batch(
&self,
repo: &InstalledRepositoryInterfaceHandle,
- operations: IndexMap<i64, AnyOperation>,
+ operations: IndexMap<i64, std::rc::Rc<AnyOperation>>,
cleanup_promises: &mut IndexMap<
i64,
Box<
@@ -421,7 +424,7 @@ impl InstallationManager {
dev_mode: bool,
run_scripts: bool,
download_only: bool,
- all_operations: Vec<AnyOperation>,
+ all_operations: Vec<std::rc::Rc<AnyOperation>>,
) -> anyhow::Result<()> {
let mut promises: Vec<
std::pin::Pin<Box<dyn std::future::Future<Output = anyhow::Result<()>>>>,
@@ -436,7 +439,7 @@ impl InstallationManager {
}
let package = operation.get_target_package();
- let initial_package: Option<PackageInterfaceHandle> = match operation {
+ let initial_package: Option<PackageInterfaceHandle> = match &**operation {
AnyOperation::Update(update_op) => Some(update_op.get_initial_package()),
_ => None,
};
@@ -505,10 +508,10 @@ impl InstallationManager {
// execute operations in batches to make sure every plugin is installed in the
// right order and activated before the packages depending on it are installed
- let mut batches: Vec<IndexMap<i64, AnyOperation>> = vec![];
- let mut batch: IndexMap<i64, AnyOperation> = IndexMap::new();
+ let mut batches: Vec<IndexMap<i64, std::rc::Rc<AnyOperation>>> = vec![];
+ let mut batch: IndexMap<i64, std::rc::Rc<AnyOperation>> = IndexMap::new();
for (index, operation) in operations {
- let package: Option<PackageInterfaceHandle> = match &operation {
+ let package: Option<PackageInterfaceHandle> = match &*operation {
AnyOperation::Update(update) => Some(update.get_target_package()),
AnyOperation::Install(install) => Some(install.get_package()),
_ => None,
@@ -551,7 +554,7 @@ impl InstallationManager {
async fn execute_batch(
&self,
repo: &InstalledRepositoryInterfaceHandle,
- operations: IndexMap<i64, AnyOperation>,
+ operations: IndexMap<i64, std::rc::Rc<AnyOperation>>,
cleanup_promises: &IndexMap<
i64,
Box<
@@ -562,11 +565,13 @@ impl InstallationManager {
>,
dev_mode: bool,
run_scripts: bool,
- all_operations: &[AnyOperation],
+ all_operations: &[std::rc::Rc<AnyOperation>],
) -> anyhow::Result<()> {
let mut promises: Vec<
std::pin::Pin<Box<dyn std::future::Future<Output = anyhow::Result<()>> + '_>>,
> = vec![];
+ // @var array<callable(): void> $postExecCallbacks
+ let mut post_exec_callbacks: Vec<Box<dyn Fn() -> anyhow::Result<()>>> = vec![];
for (index, operation) in operations {
let op_type = operation.get_operation_type();
@@ -581,7 +586,7 @@ impl InstallationManager {
io_interface::NORMAL,
);
}
- match &operation {
+ match &*operation {
AnyOperation::MarkAliasInstalled(op) => {
self.mark_alias_installed(&mut *repo.borrow_mut(), op)?;
}
@@ -595,7 +600,7 @@ impl InstallationManager {
}
let package = operation.get_target_package();
- let initial_package: Option<PackageInterfaceHandle> = match &operation {
+ let initial_package: Option<PackageInterfaceHandle> = match &*operation {
AnyOperation::Update(update_op) => Some(update_op.get_initial_package()),
_ => None,
};
@@ -607,11 +612,14 @@ impl InstallationManager {
_ => "",
};
- if run_scripts && self.event_dispatcher.is_some() {
- // TODO(phase-c): dispatch_package_event takes Box<dyn RepositoryInterface>/Vec<Box<...>>
- // but we hold a RefCell'd &mut dyn here. Needs structural rework (likely shared Rc
- // on repo and ops).
- let _ = (event_name, dev_mode, &repo, &all_operations, &operation);
+ if run_scripts && let Some(event_dispatcher) = &self.event_dispatcher {
+ event_dispatcher.borrow_mut().dispatch_package_event(
+ event_name,
+ dev_mode,
+ repo.clone(),
+ all_operations.to_vec(),
+ operation.clone(),
+ )?;
}
let installer = self.get_installer(&package.get_type())?;
@@ -620,16 +628,16 @@ impl InstallationManager {
// ->then(fn() => $this->{$opType}($repo, $operation))
// ->then($cleanupPromises[$index])
// ->then(fn() => $repo->write($devMode, $this), fn($e) => { "<op> of <pkg>
- // failed"; throw $e; })
- // ->then(fn() => dispatch POST_PACKAGE_* event);
+ // failed"; throw $e; });
// each package gets its own chain and the whole batch resolves via waitOnPromises.
+ let executed_operation = std::rc::Rc::clone(&operation);
promises.push(Box::pin(async move {
let chain_result: anyhow::Result<()> = async {
installer
.prepare(op_type, package.clone(), initial_package.clone())
.await?;
- match &operation {
+ match &*executed_operation {
AnyOperation::Install(op) => {
self.install(repo, op).await?;
}
@@ -668,24 +676,32 @@ impl InstallationManager {
// PHP: ->then(fn() => $repo->write($devMode, $this)) persists the repository after each op.
repo.borrow_mut().write(dev_mode, self)?;
- let event_name_post = match op_type {
- "install" => PackageEvents::POST_PACKAGE_INSTALL,
- "update" => PackageEvents::POST_PACKAGE_UPDATE,
- "uninstall" => PackageEvents::POST_PACKAGE_UNINSTALL,
- _ => "",
- };
-
- if run_scripts && self.event_dispatcher.is_some() {
- // PHP dispatches the POST_PACKAGE_* event at the end of the chain via the event
- // dispatcher with repo/all_operations/operation.
- // TODO(phase-c): dispatch_package_event takes Box<dyn RepositoryInterface>/
- // Vec<Box<...>> but we hold a RefCell'd &mut dyn here. Needs structural rework
- // (likely shared Rc on repo and ops).
- let _ = event_name_post;
- }
-
Ok(())
}));
+
+ let event_name = match op_type {
+ "install" => PackageEvents::POST_PACKAGE_INSTALL,
+ "update" => PackageEvents::POST_PACKAGE_UPDATE,
+ "uninstall" => PackageEvents::POST_PACKAGE_UNINSTALL,
+ _ => "",
+ };
+
+ if run_scripts && let Some(event_dispatcher) = &self.event_dispatcher {
+ let event_dispatcher = event_dispatcher.clone();
+ let repo = repo.clone();
+ let all_operations = all_operations.to_vec();
+ post_exec_callbacks.push(Box::new(move || {
+ event_dispatcher.borrow_mut().dispatch_package_event(
+ event_name,
+ dev_mode,
+ repo.clone(),
+ all_operations.clone(),
+ operation.clone(),
+ )?;
+
+ Ok(())
+ }));
+ }
}
if !promises.is_empty() {
@@ -694,6 +710,10 @@ impl InstallationManager {
Platform::workaround_filesystem_issues();
+ for cb in post_exec_callbacks {
+ cb()?;
+ }
+
Ok(())
}
diff --git a/crates/shirabe/src/installer/package_event.rs b/crates/shirabe/src/installer/package_event.rs
index 9435a581..5e6ddb34 100644
--- a/crates/shirabe/src/installer/package_event.rs
+++ b/crates/shirabe/src/installer/package_event.rs
@@ -5,19 +5,21 @@ use crate::dependency_resolver::operation::AnyOperation;
use crate::event_dispatcher::Event;
use crate::event_dispatcher::EventInterface;
use crate::io::IOInterface;
-use crate::repository::RepositoryInterface;
+use crate::repository::InstalledRepositoryInterfaceHandle;
use indexmap::IndexMap;
use shirabe_php_shim::PhpMixed;
+/// The operations are shared rather than owned so that the same operation crosses the plugin
+/// boundary as one object for both the pre- and the post-event, as it does in PHP.
#[derive(Debug)]
pub struct PackageEvent {
inner: Event,
composer: ComposerWeakHandle,
io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>,
dev_mode: bool,
- local_repo: Box<dyn RepositoryInterface>,
- operations: Vec<AnyOperation>,
- operation: AnyOperation,
+ local_repo: InstalledRepositoryInterfaceHandle,
+ operations: Vec<std::rc::Rc<AnyOperation>>,
+ operation: std::rc::Rc<AnyOperation>,
}
impl PackageEvent {
@@ -26,9 +28,9 @@ impl PackageEvent {
composer: ComposerWeakHandle,
io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>,
dev_mode: bool,
- local_repo: Box<dyn RepositoryInterface>,
- operations: Vec<AnyOperation>,
- operation: AnyOperation,
+ local_repo: InstalledRepositoryInterfaceHandle,
+ operations: Vec<std::rc::Rc<AnyOperation>>,
+ operation: std::rc::Rc<AnyOperation>,
) -> Self {
Self {
inner: Event::new(event_name, vec![], IndexMap::new()),
@@ -57,15 +59,15 @@ impl PackageEvent {
self.dev_mode
}
- pub fn get_local_repo(&self) -> &dyn RepositoryInterface {
- self.local_repo.as_ref()
+ pub fn get_local_repo(&self) -> InstalledRepositoryInterfaceHandle {
+ self.local_repo.clone()
}
- pub fn get_operations(&self) -> &Vec<AnyOperation> {
+ pub fn get_operations(&self) -> &Vec<std::rc::Rc<AnyOperation>> {
&self.operations
}
- pub fn get_operation(&self) -> &AnyOperation {
+ pub fn get_operation(&self) -> &std::rc::Rc<AnyOperation> {
&self.operation
}
}
diff --git a/crates/shirabe/src/plugin/php_plugin_proxy.rs b/crates/shirabe/src/plugin/php_plugin_proxy.rs
index 541718af..686c05bd 100644
--- a/crates/shirabe/src/plugin/php_plugin_proxy.rs
+++ b/crates/shirabe/src/plugin/php_plugin_proxy.rs
@@ -8,6 +8,7 @@
use crate::autoload::ClassLoader;
use crate::command::BaseCommand;
use crate::composer::ComposerHandle;
+use crate::dependency_resolver::operation::AnyOperation;
use crate::event_dispatcher::event_dispatcher::dispatch_event_method;
use crate::event_dispatcher::{
EventInterface, EventSubscriberInterface, SubscribedEventEntry, unwrap_php_result,
@@ -52,6 +53,7 @@ enum RustEntity {
EventDispatcher(
std::rc::Rc<std::cell::RefCell<dyn crate::event_dispatcher::EventDispatcherInterface>>,
),
+ Operation(std::rc::Rc<AnyOperation>),
}
/// The pointer identity backing R-table interning: the same shared instance must always cross
@@ -71,6 +73,7 @@ fn entity_ptr_id(entity: &RustEntity) -> usize {
RustEntity::EventDispatcher(dispatcher) => {
std::rc::Rc::as_ptr(dispatcher) as *const () as usize
}
+ RustEntity::Operation(operation) => std::rc::Rc::as_ptr(operation) as *const () as usize,
}
}
@@ -169,6 +172,21 @@ fn repository_stub_class(repository: &RepositoryInterfaceHandle) -> Result<&'sta
}
}
+/// The proxy stub class matching a solver operation's concrete type.
+fn operation_stub_class(operation: &AnyOperation) -> &'static str {
+ match operation {
+ AnyOperation::Install(_) => "Composer\\DependencyResolver\\Operation\\InstallOperation",
+ AnyOperation::Update(_) => "Composer\\DependencyResolver\\Operation\\UpdateOperation",
+ AnyOperation::Uninstall(_) => "Composer\\DependencyResolver\\Operation\\UninstallOperation",
+ AnyOperation::MarkAliasInstalled(_) => {
+ "Composer\\DependencyResolver\\Operation\\MarkAliasInstalledOperation"
+ }
+ AnyOperation::MarkAliasUninstalled(_) => {
+ "Composer\\DependencyResolver\\Operation\\MarkAliasUninstalledOperation"
+ }
+ }
+}
+
/// Registers a package and returns its wire descriptor.
pub(crate) fn package_handle_value(
package: &std::rc::Rc<std::cell::RefCell<AnyPackage>>,
@@ -178,6 +196,13 @@ pub(crate) fn package_handle_value(
rust_handle_value(rhandle, class)
}
+/// Registers a solver operation and returns its wire descriptor.
+pub(crate) fn operation_handle_value(operation: &std::rc::Rc<AnyOperation>) -> PluginValue {
+ let class = operation_stub_class(operation);
+ let rhandle = register_entity(RustEntity::Operation(operation.clone()));
+ rust_handle_value(rhandle, class)
+}
+
/// The PHP class name (= proxy stub class) of a Rust IO instance, for the `__class` field of
/// its handle descriptor.
pub(crate) fn io_stub_class(
@@ -335,6 +360,9 @@ impl RustMethodDispatcher for PluginRpcDispatcher<'_> {
Some(RustEntity::EventDispatcher(dispatcher)) => {
dispatch_event_dispatcher_method(&dispatcher, method_name, &args)
}
+ Some(RustEntity::Operation(operation)) => {
+ dispatch_operation_method(&operation, method_name, &args)
+ }
None => Err(runtime_throw(format!("unknown Rust handle {rhandle}"))),
}
}
@@ -368,7 +396,13 @@ pub(crate) fn construct_entity(args: &[PluginValue]) -> Result<PluginValue, PhpT
))),
}
};
- let entity = match class.as_str() {
+ let alias_package_arg =
+ |position: usize| -> Result<crate::package::AliasPackageHandle, PhpThrow> {
+ package_from_arg(&class, ctor_args.get(position))?
+ .as_alias()
+ .ok_or_else(|| runtime_throw(format!("{class} expects an AliasPackage")))
+ };
+ let package = match class.as_str() {
"Composer\\Package\\Package" => AnyPackage::Package(crate::package::Package::new(
string_arg(0)?,
string_arg(1)?,
@@ -413,6 +447,50 @@ pub(crate) fn construct_entity(args: &[PluginValue]) -> Result<PluginValue, PhpT
string_arg(2)?,
))
}
+ // A solver operation carries no state beyond the packages it names, so a plugin-built
+ // one is a complete instance rather than a second view on a Rust-side service.
+ "Composer\\DependencyResolver\\Operation\\InstallOperation" => {
+ return Ok(operation_construction_result(AnyOperation::Install(
+ crate::dependency_resolver::operation::InstallOperation::new(package_from_arg(
+ &class,
+ ctor_args.first(),
+ )?),
+ )));
+ }
+ "Composer\\DependencyResolver\\Operation\\UpdateOperation" => {
+ return Ok(operation_construction_result(AnyOperation::Update(
+ crate::dependency_resolver::operation::UpdateOperation::new(
+ package_from_arg(&class, ctor_args.first())?,
+ package_from_arg(&class, ctor_args.get(1))?,
+ ),
+ )));
+ }
+ "Composer\\DependencyResolver\\Operation\\UninstallOperation" => {
+ return Ok(operation_construction_result(AnyOperation::Uninstall(
+ crate::dependency_resolver::operation::UninstallOperation::new(package_from_arg(
+ &class,
+ ctor_args.first(),
+ )?),
+ )));
+ }
+ "Composer\\DependencyResolver\\Operation\\MarkAliasInstalledOperation" => {
+ return Ok(operation_construction_result(
+ AnyOperation::MarkAliasInstalled(
+ crate::dependency_resolver::operation::MarkAliasInstalledOperation::new(
+ alias_package_arg(0)?,
+ ),
+ ),
+ ));
+ }
+ "Composer\\DependencyResolver\\Operation\\MarkAliasUninstalledOperation" => {
+ return Ok(operation_construction_result(
+ AnyOperation::MarkAliasUninstalled(
+ crate::dependency_resolver::operation::MarkAliasUninstalledOperation::new(
+ alias_package_arg(0)?,
+ ),
+ ),
+ ));
+ }
// TODO(plugin): the remaining proxied classes get a construction story on demand,
// driven by explicit errors from real plugins. Each one has to decide what a
// plugin-built instance means for the Rust-side graph, which is why none of them is
@@ -424,12 +502,20 @@ pub(crate) fn construct_entity(args: &[PluginValue]) -> Result<PluginValue, PhpT
}
};
let rhandle = register_entity(RustEntity::Package(std::rc::Rc::new(
- std::cell::RefCell::new(entity),
+ std::cell::RefCell::new(package),
)));
- Ok(PluginValue::List(vec![
- PluginValue::Int(rhandle as i64),
- PluginValue::Int(0),
- ]))
+ Ok(construction_result(rhandle))
+}
+
+fn operation_construction_result(operation: AnyOperation) -> PluginValue {
+ construction_result(register_entity(RustEntity::Operation(std::rc::Rc::new(
+ operation,
+ ))))
+}
+
+/// The `[$rhandle, $epoch]` pair a proxy stub's constructor binds itself to.
+fn construction_result(rhandle: u64) -> PluginValue {
+ PluginValue::List(vec![PluginValue::Int(rhandle as i64), PluginValue::Int(0)])
}
/// Serves the `__clone` forwarder every proxy stub carries. Only entities whose Rust type
@@ -452,7 +538,8 @@ fn clone_entity(entity: &RustEntity) -> Result<PluginValue, PhpThrow> {
| RustEntity::InstallationManager(_)
| RustEntity::RepositoryManager(_)
| RustEntity::Repository(_)
- | RustEntity::EventDispatcher(_) => {
+ | RustEntity::EventDispatcher(_)
+ | RustEntity::Operation(_) => {
return Err(runtime_throw(
"cloning this Rust-side entity over RPC is not supported".to_string(),
));
@@ -810,12 +897,7 @@ fn dispatch_repository_manager_method(
method_name: &str,
) -> Result<PluginValue, PhpThrow> {
match method_name {
- "getLocalRepository" => {
- let local = rm.borrow().get_local_repository();
- let class = repository_stub_class(&local)?;
- let rhandle = register_entity(RustEntity::Repository(local));
- Ok(rust_handle_value(rhandle, class))
- }
+ "getLocalRepository" => repository_handle_value(&rm.borrow().get_local_repository()),
// TODO(plugin): the remaining RepositoryManager surface is widened on demand, driven
// by explicit errors from real plugins.
other => Err(runtime_throw(format!(
@@ -1761,6 +1843,42 @@ fn dispatch_package_method(
}
}
+fn dispatch_operation_method(
+ operation: &AnyOperation,
+ method_name: &str,
+ args: &[PluginValue],
+) -> Result<PluginValue, PhpThrow> {
+ match (method_name, operation) {
+ ("getOperationType", _) => Ok(PluginValue::string(operation.get_operation_type())),
+ ("show", _) => Ok(PluginValue::string(
+ operation.show(bool_arg(method_name, args.first())?),
+ )),
+ ("__toString", _) => Ok(PluginValue::string(operation.to_string())),
+ ("getPackage", AnyOperation::Install(op)) => {
+ Ok(package_handle_value(op.get_package().as_rc()))
+ }
+ ("getPackage", AnyOperation::Uninstall(op)) => {
+ Ok(package_handle_value(op.get_package().as_rc()))
+ }
+ ("getPackage", AnyOperation::MarkAliasInstalled(op)) => {
+ Ok(package_handle_value(op.get_package().as_rc()))
+ }
+ ("getPackage", AnyOperation::MarkAliasUninstalled(op)) => {
+ Ok(package_handle_value(op.get_package().as_rc()))
+ }
+ ("getInitialPackage", AnyOperation::Update(op)) => {
+ Ok(package_handle_value(op.get_initial_package().as_rc()))
+ }
+ ("getTargetPackage", AnyOperation::Update(op)) => {
+ Ok(package_handle_value(op.get_target_package().as_rc()))
+ }
+ (other, _) => Err(runtime_throw(format!(
+ "the operation method `{other}` is not available on a {} over RPC yet",
+ operation_stub_class(operation)
+ ))),
+ }
+}
+
fn dispatch_installation_manager_method(
im: &std::rc::Rc<std::cell::RefCell<dyn InstallationManagerInterface>>,
method_name: &str,