diff options
Diffstat (limited to 'crates/shirabe/src/installer')
7 files changed, 129 insertions, 145 deletions
diff --git a/crates/shirabe/src/installer/installation_manager.rs b/crates/shirabe/src/installer/installation_manager.rs index ae0470a..7a05324 100644 --- a/crates/shirabe/src/installer/installation_manager.rs +++ b/crates/shirabe/src/installer/installation_manager.rs @@ -3,7 +3,6 @@ use crate::io::io_interface; use anyhow::Result; use indexmap::IndexMap; -use shirabe_external_packages::react::promise; use shirabe_external_packages::seld::signal::SignalHandler; use shirabe_php_shim::{ InvalidArgumentException, PhpMixed, array_search_mixed, array_splice, array_unshift, count, @@ -181,7 +180,10 @@ impl InstallationManager { // @var array<callable(): ?PromiseInterface<void|null>> $cleanupPromises let mut cleanup_promises: IndexMap< i64, - Box<dyn Fn() -> Option<Box<dyn PromiseInterface>>>, + Box< + dyn Fn() + -> Option<std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>>>>>, + >, > = IndexMap::new(); let signal_handler = SignalHandler::create( @@ -234,15 +236,17 @@ impl InstallationManager { } for batch_to_execute in batches { - self.download_and_execute_batch( - repo, - batch_to_execute, - &mut cleanup_promises, - dev_mode, - run_scripts, - download_only, - // TODO(phase-b): allOperations should be the original full list; would require clone - vec![], + tokio::runtime::Runtime::new().unwrap().block_on( + self.download_and_execute_batch( + repo, + batch_to_execute, + &mut cleanup_promises, + dev_mode, + run_scripts, + download_only, + // TODO(phase-b): allOperations should be the original full list; would require clone + vec![], + ), )?; } @@ -255,7 +259,9 @@ impl InstallationManager { match result { Ok(()) => {} Err(e) => { - self.run_cleanup(&cleanup_promises); + tokio::runtime::Runtime::new() + .unwrap() + .block_on(self.run_cleanup(&cleanup_promises)); return Err(e); } } @@ -275,20 +281,22 @@ impl InstallationManager { /// @param OperationInterface[] $operations List of operations to execute in this batch /// @param OperationInterface[] $allOperations Complete list of operations to be executed in the install job, used for event listeners /// @phpstan-param array<callable(): ?PromiseInterface<void|null>> $cleanupPromises - fn download_and_execute_batch( + async fn download_and_execute_batch( &mut self, repo: &mut dyn InstalledRepositoryInterface, operations: IndexMap<i64, Box<dyn OperationInterface>>, - cleanup_promises: &mut IndexMap<i64, Box<dyn Fn() -> Option<Box<dyn PromiseInterface>>>>, + cleanup_promises: &mut IndexMap< + i64, + Box< + dyn Fn() + -> Option<std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>>>>>, + >, + >, dev_mode: bool, run_scripts: bool, download_only: bool, all_operations: Vec<Box<dyn OperationInterface>>, ) -> Result<()> { - // TODO(phase-c-promise): Loop::wait-driven batch orchestration (download/cleanup promises); - // rewrite to await collected futures once the async Loop boundary lands. - let mut promises: Vec<Box<dyn PromiseInterface>> = vec![]; - for (index, operation) in &operations { let op_type = operation.get_operation_type(); @@ -316,35 +324,35 @@ impl InstallationManager { } let installer = self.get_installer(package.get_type())?; - // TODO(phase-b): closure captures installer + package; needs Arc/Rc for shared state + // TODO(phase-b): closure captures installer + package; needs Rc-shared installer/package let _ = installer; let op_type_clone = op_type.clone(); - let cleanup: Box<dyn Fn() -> Option<Box<dyn PromiseInterface>>> = - Box::new(move || -> Option<Box<dyn PromiseInterface>> { - // avoid calling cleanup if the download was not even initialized for a package - // as without installation source configured nothing will work - // TODO(phase-b): if (null === $package->getInstallationSource()) return \React\Promise\resolve(null); - let _ = &op_type_clone; - Some(promise::resolve(None)) - }); + let cleanup: Box< + dyn Fn() + -> Option<std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>>>>>, + > = Box::new(move || { + // avoid calling cleanup if the download was not even initialized for a package + // as without installation source configured nothing will work + // TODO(phase-b): if (null === $package->getInstallationSource()) return resolve(null); + let _ = &op_type_clone; + // TODO(phase-c-promise): build the real installer.cleanup() future once the installer + // can be shared into a 'static cleanup closure (Stage 2 Rc/Arc). + let fut: std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>>>> = + Box::pin(async { Ok(()) }); + Some(fut) + }); cleanup_promises.insert(*index, cleanup); if op_type != "uninstall" { + // TODO(phase-c-promise): PHP collects every download and runs them concurrently via + // Loop::wait; the single-threaded loop awaits each serially instead. let installer = self.get_installer(package.get_type())?; - let promise = installer.download(package, initial_package); - if let Ok(Some(p)) = promise { - promises.push(p); - } + installer.download(package, initial_package).await?; } } - // execute all downloads first - if (promises.len() as i64) > 0 { - self.wait_on_promises(promises); - } - if download_only { - self.run_cleanup(cleanup_promises); + self.run_cleanup(cleanup_promises).await; return Ok(()); } @@ -385,7 +393,8 @@ impl InstallationManager { dev_mode, run_scripts, &all_operations, - )?; + ) + .await?; } Ok(()) @@ -394,18 +403,21 @@ impl InstallationManager { /// @param OperationInterface[] $operations List of operations to execute in this batch /// @param OperationInterface[] $allOperations Complete list of operations to be executed in the install job, used for event listeners /// @phpstan-param array<callable(): ?PromiseInterface<void|null>> $cleanupPromises - fn execute_batch( + async fn execute_batch( &mut self, repo: &mut dyn InstalledRepositoryInterface, operations: IndexMap<i64, Box<dyn OperationInterface>>, - cleanup_promises: &IndexMap<i64, Box<dyn Fn() -> Option<Box<dyn PromiseInterface>>>>, + cleanup_promises: &IndexMap< + i64, + Box< + dyn Fn() + -> Option<std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>>>>>, + >, + >, dev_mode: bool, run_scripts: bool, all_operations: &[Box<dyn OperationInterface>], ) -> Result<()> { - // TODO(phase-c-promise): Loop::wait-driven batch orchestration (prepare/install promises); - // rewrite to await collected futures once the async Loop boundary lands. - let mut promises: Vec<Box<dyn PromiseInterface>> = vec![]; let mut post_exec_callbacks: Vec<Box<dyn Fn()>> = vec![]; for (index, operation) in operations { @@ -473,14 +485,13 @@ impl InstallationManager { let _io = self.io.as_ref(); let installer = self.get_installer(package.get_type())?; - let promise = installer.prepare(&op_type, package, initial_package); - let promise = match promise { - Ok(Some(p)) => p, - Ok(None) => promise::resolve(None), - Err(e) => return Err(e), - }; + // TODO(phase-c-promise): PHP chains prepare()->then(install/update/uninstall)->then(cleanup + // + repo.write); the single-threaded loop awaits prepare and leaves the rest as phase-b work. + installer + .prepare(&op_type, package, initial_package) + .await?; - // TODO(phase-b): chain `.then(cb1).then(cb2)` with cleanup_promises[index], repo.write, etc. + // TODO(phase-b): chain the install/update/uninstall step with cleanup_promises[index], repo.write, etc. let _ = cleanup_promises.get(&index); let event_name_post = match op_type.as_str() { @@ -497,13 +508,6 @@ impl InstallationManager { // dispatcher.dispatch_package_event(event_name_post, dev_mode, repo, all_operations, operation); })); } - - promises.push(promise); - } - - // execute all prepare => installs/updates/removes => cleanup steps - if (promises.len() as i64) > 0 { - self.wait_on_promises(promises); } Platform::workaround_filesystem_issues(); @@ -515,32 +519,6 @@ impl InstallationManager { Ok(()) } - /// @param array<PromiseInterface<void|null>> $promises - fn wait_on_promises(&mut self, promises: Vec<Box<dyn PromiseInterface>>) { - // TODO(phase-c-promise): thin wrapper over Loop::wait (the async boundary owned separately). - let mut progress: Option<()> = None; - // TODO(phase-b): self.io instanceof ConsoleIO downcast - let io_is_console = false; - if self.output_progress - && io_is_console - && Platform::get_env("CI").is_none() - && !self.io.is_debug() - && (promises.len() as i64) > 1 - { - // TODO(phase-b): progress = self.io.get_progress_bar(); - progress = Some(()); - } - // TODO(phase-b): pass actual ProgressBar when self.io.get_progress_bar() is implemented - let _ = self.loop_.borrow_mut().wait(promises, None); - if progress.is_some() { - // progress.clear(); - // ProgressBar in non-decorated output does not output a final line-break and clear() does nothing - if !self.io.is_decorated() { - self.io.write_error3("", true, io_interface::NORMAL); - } - } - } - /// Executes download operation. /// /// @phpstan-return PromiseInterface<void|null>|null @@ -653,10 +631,8 @@ impl InstallationManager { } pub fn notify_installs(&mut self, _io: &dyn IOInterface) { - // TODO(phase-c-promise): collects http_downloader.add() promises and drives them via Loop::wait; - // rewrite to await the collected futures once the async Loop boundary lands. - let mut promises: Vec<Box<dyn PromiseInterface>> = vec![]; - + // TODO(phase-c-promise): PHP collects every http_downloader.add() promise and runs them via + // Loop::wait; the single-threaded sync bridge block_on's each notification serially instead. let result: Result<()> = (|| -> Result<()> { for (repo_url, packages) in &self.notifiable_packages { // non-batch API, deprecated @@ -699,13 +675,13 @@ impl InstallationManager { ), ); - promises.push( + tokio::runtime::Runtime::new().unwrap().block_on( self.loop_ .borrow() .get_http_downloader() .borrow_mut() - .add(&url, opts)?, - ); + .add(&url, opts), + )?; } continue; @@ -771,17 +747,15 @@ impl InstallationManager { PhpMixed::Array(http.into_iter().map(|(k, v)| (k, Box::new(v))).collect()), ); - promises.push( + tokio::runtime::Runtime::new().unwrap().block_on( self.loop_ .borrow() .get_http_downloader() .borrow_mut() - .add(repo_url, opts)?, - ); + .add(repo_url, opts), + )?; } - let _ = self.loop_.borrow_mut().wait(promises, None); - Ok(()) })(); // PHP swallows the exception silently here @@ -800,28 +774,33 @@ impl InstallationManager { } /// @phpstan-param array<callable(): ?PromiseInterface<void|null>> $cleanupPromises - fn run_cleanup( + async fn run_cleanup( &mut self, - cleanup_promises: &IndexMap<i64, Box<dyn Fn() -> Option<Box<dyn PromiseInterface>>>>, + cleanup_promises: &IndexMap< + i64, + Box< + dyn Fn() + -> Option<std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>>>>>, + >, + >, ) { - // TODO(phase-c-promise): runs cleanup closures and drives them via Loop::wait; - // rewrite once the async Loop boundary and async cleanup closures land. - let mut promises: Vec<Box<dyn PromiseInterface>> = vec![]; + let mut promises: Vec<std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>>>>> = + vec![]; self.loop_.borrow().abort_jobs(); for (_, cleanup) in cleanup_promises { - // TODO(phase-b): React\Promise\Promise constructor with executor; emulate by wrapping cleanup() + // PHP wraps a missing cleanup promise in \React\Promise\resolve(null). let promise = cleanup(); if let Some(p) = promise { promises.push(p); } else { - promises.push(promise::resolve(None)); + promises.push(Box::pin(async { Ok(()) })); } } if (promises.len() as i64) > 0 { - let _ = self.loop_.borrow_mut().wait(promises, None); + let _ = self.loop_.borrow_mut().wait(promises, None).await; } } } diff --git a/crates/shirabe/src/installer/installer_interface.rs b/crates/shirabe/src/installer/installer_interface.rs index 29171cf..bb510b8 100644 --- a/crates/shirabe/src/installer/installer_interface.rs +++ b/crates/shirabe/src/installer/installer_interface.rs @@ -4,6 +4,7 @@ use crate::package::PackageInterface; use crate::repository::InstalledRepositoryInterface; use shirabe_php_shim::PhpMixed; +#[async_trait::async_trait(?Send)] pub trait InstallerInterface: std::fmt::Debug { fn supports(&self, package_type: &str) -> bool; diff --git a/crates/shirabe/src/installer/library_installer.rs b/crates/shirabe/src/installer/library_installer.rs index 0629de5..29cb455 100644 --- a/crates/shirabe/src/installer/library_installer.rs +++ b/crates/shirabe/src/installer/library_installer.rs @@ -5,7 +5,8 @@ use std::any::Any; use anyhow::Result; use shirabe_external_packages::composer::pcre::Preg; use shirabe_php_shim::{ - InvalidArgumentException, LogicException, is_link, preg_quote, realpath, rmdir, rtrim, strpos, + InvalidArgumentException, LogicException, PhpMixed, dirname, is_dir, is_link, preg_quote, + realpath, rmdir, rtrim, strpos, }; use crate::composer::PartialComposerWeakHandle; @@ -215,6 +216,7 @@ impl LibraryInstaller { } } +#[async_trait::async_trait(?Send)] impl InstallerInterface for LibraryInstaller { fn supports(&self, package_type: &str) -> bool { match &self.r#type { @@ -314,20 +316,16 @@ impl InstallerInterface for LibraryInstaller { self.binary_installer.remove_binaries(package); } - // TODO(phase-c-promise): rewrite install_code().then(installBinaries + repo.addPackage) as an await sequence. - let promise = self.install_code(package)?; - let promise = match promise { - Some(p) => p, - None => shirabe_external_packages::react::promise::resolve(None), - }; + let _ = self.install_code(package).await?; - // TODO(phase-b): promise.then expects Option<Box<dyn FnOnce(Option<PhpMixed>) -> Option<PhpMixed>>> - // arguments. The original PHP closure captures &mut self/binary_installer/repo/package; - // restructuring required. - let _ = promise; - Ok(Some(todo!( - "promise.then(...) chain to install_binaries + repo.add_package" - ))) + let install_path = self.get_install_path(package).unwrap(); + self.binary_installer + .install_binaries(package, &install_path, true); + if !repo.has_package(package) { + repo.add_package(package.clone_package_box()); + } + + Ok(None) } async fn update( @@ -348,20 +346,17 @@ impl InstallerInterface for LibraryInstaller { // self.initialize_vendor_dir(); self.binary_installer.remove_binaries(initial); - // TODO(phase-c-promise): rewrite update_code().then(installBinaries + repo updates) as an await sequence. - let promise = self.update_code(initial, target)?; - let promise = match promise { - Some(p) => p, - None => shirabe_external_packages::react::promise::resolve(None), - }; + let _ = self.update_code(initial, target).await?; + + let install_path = self.get_install_path(target).unwrap(); + self.binary_installer + .install_binaries(target, &install_path, true); + repo.remove_package(initial); + if !repo.has_package(target) { + repo.add_package(target.clone_package_box()); + } - // TODO(phase-b): promise.then expects Option<Box<dyn FnOnce(Option<PhpMixed>) -> Option<PhpMixed>>> - // arguments. Closure captures &mut self/binary_installer/repo/initial/target; - // restructuring required. - let _ = promise; - Ok(Some(todo!( - "promise.then(...) chain to install_binaries + repo updates" - ))) + Ok(None) } async fn uninstall( @@ -377,20 +372,25 @@ impl InstallerInterface for LibraryInstaller { .into()); } - // TODO(phase-c-promise): rewrite remove_code().then(remove_binaries/remove_package/rmdir) as an await sequence. - let promise = self.remove_code(package)?; - let promise = match promise { - Some(p) => p, - None => shirabe_external_packages::react::promise::resolve(None), - }; + let _ = self.remove_code(package).await?; + + let download_path = self.get_package_base_path(package); + self.binary_installer.remove_binaries(package); + repo.remove_package(package); + + if strpos(package.get_name(), "/").map_or(false, |pos| pos != 0) { + let package_vendor_dir = dirname(&download_path); + if is_dir(&package_vendor_dir) + && self.filesystem.borrow().is_dir_empty(&package_vendor_dir) + { + let _ = Silencer::call(|| { + rmdir(&package_vendor_dir); + Ok(()) + }); + } + } - // TODO(phase-b): promise.then expects Option<Box<dyn FnOnce(Option<PhpMixed>) -> Option<PhpMixed>>> - // arguments. Closure captures binary_installer/filesystem/download_path/package/repo; - // restructuring required. - let _ = promise; - Ok(Some(todo!( - "promise.then(...) chain to remove_binaries/remove_package/rmdir" - ))) + Ok(None) } fn get_install_path(&self, package: &dyn PackageInterface) -> Option<String> { diff --git a/crates/shirabe/src/installer/metapackage_installer.rs b/crates/shirabe/src/installer/metapackage_installer.rs index 23d92f2..2ea9685 100644 --- a/crates/shirabe/src/installer/metapackage_installer.rs +++ b/crates/shirabe/src/installer/metapackage_installer.rs @@ -22,6 +22,7 @@ impl MetapackageInstaller { } } +#[async_trait::async_trait(?Send)] impl InstallerInterface for MetapackageInstaller { fn supports(&self, package_type: &str) -> bool { package_type == "metapackage" diff --git a/crates/shirabe/src/installer/noop_installer.rs b/crates/shirabe/src/installer/noop_installer.rs index 68e4ef8..8297165 100644 --- a/crates/shirabe/src/installer/noop_installer.rs +++ b/crates/shirabe/src/installer/noop_installer.rs @@ -8,6 +8,7 @@ use shirabe_php_shim::{InvalidArgumentException, PhpMixed}; #[derive(Debug)] pub struct NoopInstaller; +#[async_trait::async_trait(?Send)] impl InstallerInterface for NoopInstaller { fn supports(&self, _package_type: &str) -> bool { true diff --git a/crates/shirabe/src/installer/plugin_installer.rs b/crates/shirabe/src/installer/plugin_installer.rs index 4f9918e..a540677 100644 --- a/crates/shirabe/src/installer/plugin_installer.rs +++ b/crates/shirabe/src/installer/plugin_installer.rs @@ -41,7 +41,7 @@ impl PluginInstaller { self.get_plugin_manager().borrow_mut().disable_plugins(); } - fn rollback_install( + async fn rollback_install( &mut self, e: anyhow::Error, repo: &mut dyn InstalledRepositoryInterface, @@ -51,7 +51,7 @@ impl PluginInstaller { "Plugin initialization failed ({}), uninstalling plugin", e )); - self.inner.uninstall(repo, package)?; + self.inner.uninstall(repo, package).await?; Err(e) } @@ -61,6 +61,7 @@ impl PluginInstaller { } } +#[async_trait::async_trait(?Send)] impl InstallerInterface for PluginInstaller { fn supports(&self, package_type: &str) -> bool { package_type == "composer-plugin" || package_type == "composer-installer" diff --git a/crates/shirabe/src/installer/project_installer.rs b/crates/shirabe/src/installer/project_installer.rs index 034a07a..8960854 100644 --- a/crates/shirabe/src/installer/project_installer.rs +++ b/crates/shirabe/src/installer/project_installer.rs @@ -29,6 +29,7 @@ impl ProjectInstaller { } } +#[async_trait::async_trait(?Send)] impl InstallerInterface for ProjectInstaller { fn supports(&self, _package_type: &str) -> bool { true |
