diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-07-04 22:43:46 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-07-05 00:32:15 +0900 |
| commit | b36410024e46a7de5314af6549ba39bf88fe598c (patch) | |
| tree | 7011b27eaed4da0b38978b17cb700fee958d1e33 /crates/shirabe/src/installer/installation_manager.rs | |
| parent | b556dbb66123145983df7a62949061029e38f0a3 (diff) | |
| download | php-shirabe-b36410024e46a7de5314af6549ba39bf88fe598c.tar.gz php-shirabe-b36410024e46a7de5314af6549ba39bf88fe598c.tar.zst php-shirabe-b36410024e46a7de5314af6549ba39bf88fe598c.zip | |
feat(installation-manager): render install/download progress bar
Real Composer shows a ProgressBar during InstallationManager's
waitOnPromises (`Package operations: N installs` ... `0/109 [>---] 0%`
... `100%`), but this port never wired one up: output_progress was set
by callers and never consulted. This adds the same gating PHP uses
(output_progress, ConsoleIO, not CI, not debug, more than one
operation) for both the download phase and the install/extract phase.
The port runs downloads/installs serially rather than as concurrently
polled promises (see the existing TODO(phase-c-promise) notes), so
there is no active-job count to poll for intermediate snapshots.
Stepping the bar per completed operation was tried first, but it
interleaves with the "- Installing ..." lines mid-terminal-line since
both share the same overwrite/newline state; rendering a single 0% ->
100% jump after each phase avoids that garbling at the cost of the
timing-driven intermediate snapshots real Composer shows.
Diffstat (limited to 'crates/shirabe/src/installer/installation_manager.rs')
| -rw-r--r-- | crates/shirabe/src/installer/installation_manager.rs | 77 |
1 files changed, 77 insertions, 0 deletions
diff --git a/crates/shirabe/src/installer/installation_manager.rs b/crates/shirabe/src/installer/installation_manager.rs index 97e1985..d89487c 100644 --- a/crates/shirabe/src/installer/installation_manager.rs +++ b/crates/shirabe/src/installer/installation_manager.rs @@ -10,6 +10,7 @@ use crate::downloader::FileDownloader; use crate::event_dispatcher::EventDispatcher; use crate::installer::InstallerInterface; use crate::installer::PackageEvents; +use crate::io::ConsoleIO; use crate::io::IOInterface; use crate::io::IOInterfaceImmutable; use crate::io::io_interface; @@ -415,6 +416,18 @@ impl InstallationManager { download_only: bool, all_operations: Vec<std::rc::Rc<dyn OperationInterface>>, ) -> anyhow::Result<()> { + // PHP: waitOnPromises() shows a ProgressBar while the concurrent downloads resolve. + // TODO(phase-c-promise): see the identical note in execute_batch — the single-threaded + // port downloads serially in this same loop, so only a 0% -> 100% jump is rendered after + // the loop instead of PHP's timing-driven intermediate snapshots. + let download_promise_count = operations + .values() + .filter(|op| { + let t = op.get_operation_type(); + t == "update" || t == "install" + }) + .count() as i64; + for (index, operation) in &operations { let op_type = operation.get_operation_type(); @@ -474,6 +487,29 @@ impl InstallationManager { } } + if self.output_progress + && Platform::get_env("CI").is_none() + && !self.io.is_debug() + && download_promise_count > 1 + { + let bar = { + let io_ref = self.io.borrow(); + io_ref + .as_any() + .downcast_ref::<ConsoleIO>() + .map(|console_io| console_io.get_progress_bar(download_promise_count)) + }; + if let Some(mut bar) = bar { + bar.start(Some(download_promise_count))?; + bar.set_progress(download_promise_count)?; + bar.finish()?; + bar.clear()?; + if !self.io.is_decorated() { + self.io.write_error(""); + } + } + } + if download_only { self.run_cleanup(cleanup_promises).await; @@ -546,6 +582,23 @@ impl InstallationManager { ) -> anyhow::Result<()> { let mut post_exec_callbacks: Vec<Box<dyn Fn()>> = vec![]; + // PHP: waitOnPromises() shows a ProgressBar while React\Promise\all($promises) resolves, + // driven by Loop::wait's active-job polling as concurrent downloads/installs finish over + // real wall-clock time, interleaved with nothing else since the "- Installing ..." lines + // are all written up front while the promises are being constructed. + // TODO(phase-c-promise): the single-threaded port runs prepare/install/cleanup serially + // in this same loop that also writes the "- Installing ..." lines, so there is no way to + // draw a step-by-step bar without garbling it into the middle of that output (the bar's + // line-overwrite state and the plain `write_error` lines fight over the same terminal + // line). Rendering a single 0% -> 100% jump after the loop keeps output well-formed at + // the cost of the intermediate snapshots real Composer shows. + let promise_count = operations + .values() + .filter(|op| { + ["update", "install", "uninstall"].contains(&op.get_operation_type().as_str()) + }) + .count() as i64; + for (index, operation) in operations { let op_type = operation.get_operation_type(); @@ -693,6 +746,30 @@ impl InstallationManager { } } + if self.output_progress + && Platform::get_env("CI").is_none() + && !self.io.is_debug() + && promise_count > 1 + { + let bar = { + let io_ref = self.io.borrow(); + io_ref + .as_any() + .downcast_ref::<ConsoleIO>() + .map(|console_io| console_io.get_progress_bar(promise_count)) + }; + if let Some(mut bar) = bar { + bar.start(Some(promise_count))?; + bar.set_progress(promise_count)?; + bar.finish()?; + bar.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_error(""); + } + } + } + Platform::workaround_filesystem_issues(); for cb in &post_exec_callbacks { |
