diff options
Diffstat (limited to 'crates/shirabe/src')
| -rw-r--r-- | crates/shirabe/src/event_dispatcher/event_dispatcher.rs | 112 | ||||
| -rw-r--r-- | crates/shirabe/src/installer/installation_manager.rs | 98 | ||||
| -rw-r--r-- | crates/shirabe/src/installer/package_event.rs | 24 | ||||
| -rw-r--r-- | crates/shirabe/src/plugin/php_plugin_proxy.rs | 144 |
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, |
