aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-07-04 22:43:46 +0900
committernsfisis <nsfisis@gmail.com>2026-07-05 00:32:15 +0900
commitb36410024e46a7de5314af6549ba39bf88fe598c (patch)
tree7011b27eaed4da0b38978b17cb700fee958d1e33 /crates
parentb556dbb66123145983df7a62949061029e38f0a3 (diff)
downloadphp-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')
-rw-r--r--crates/shirabe/src/installer/installation_manager.rs77
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 {