mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 10:58:28 +00:00
Concurrent progress bars (#3252)
## Summary
Implements concurrent progress bars. Resolves
https://github.com/astral-sh/uv/issues/1209.
## Test Plan
b21bdfbb
-8817-4873-a65c-16c9e8c7c460
This commit is contained in:
parent
70cbc32565
commit
7dc322665c
5 changed files with 477 additions and 230 deletions
|
@ -1,12 +1,14 @@
|
|||
use std::future::Future;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use futures::{FutureExt, TryStreamExt};
|
||||
use tempfile::TempDir;
|
||||
use tokio::io::AsyncSeekExt;
|
||||
use tokio::io::{AsyncRead, AsyncSeekExt, ReadBuf};
|
||||
use tokio::sync::Semaphore;
|
||||
use tokio_util::compat::FuturesAsyncReadCompatExt;
|
||||
use tracing::{info_span, instrument, warn, Instrument};
|
||||
|
@ -49,6 +51,7 @@ pub struct DistributionDatabase<'a, Context: BuildContext> {
|
|||
builder: SourceDistributionBuilder<'a, Context>,
|
||||
locks: Rc<Locks>,
|
||||
client: ManagedClient<'a>,
|
||||
reporter: Option<Arc<dyn Reporter>>,
|
||||
}
|
||||
|
||||
impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
||||
|
@ -62,6 +65,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
|||
builder: SourceDistributionBuilder::new(build_context),
|
||||
locks: Rc::new(Locks::default()),
|
||||
client: ManagedClient::new(client, concurrent_downloads),
|
||||
reporter: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,6 +74,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
|||
pub fn with_reporter(self, reporter: impl Reporter + 'static) -> Self {
|
||||
let reporter = Arc::new(reporter);
|
||||
Self {
|
||||
reporter: Some(reporter.clone()),
|
||||
builder: self.builder.with_reporter(reporter),
|
||||
..self
|
||||
}
|
||||
|
@ -168,6 +173,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
|||
NoBinary::All => true,
|
||||
NoBinary::Packages(packages) => packages.contains(dist.name()),
|
||||
};
|
||||
|
||||
if no_binary {
|
||||
return Err(Error::NoBinary);
|
||||
}
|
||||
|
@ -188,6 +194,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
|||
WheelCache::Index(&wheel.index).wheel_dir(wheel.name().as_ref()),
|
||||
wheel.filename.stem(),
|
||||
);
|
||||
|
||||
return self
|
||||
.load_wheel(path, &wheel.filename, cache_entry, dist, hashes)
|
||||
.await;
|
||||
|
@ -203,7 +210,14 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
|||
|
||||
// Download and unzip.
|
||||
match self
|
||||
.stream_wheel(url.clone(), &wheel.filename, &wheel_entry, dist, hashes)
|
||||
.stream_wheel(
|
||||
url.clone(),
|
||||
&wheel.filename,
|
||||
wheel.file.size,
|
||||
&wheel_entry,
|
||||
dist,
|
||||
hashes,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(archive) => Ok(LocalWheel {
|
||||
|
@ -220,8 +234,16 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
|||
// If the request failed because streaming is unsupported, download the
|
||||
// wheel directly.
|
||||
let archive = self
|
||||
.download_wheel(url, &wheel.filename, &wheel_entry, dist, hashes)
|
||||
.download_wheel(
|
||||
url,
|
||||
&wheel.filename,
|
||||
wheel.file.size,
|
||||
&wheel_entry,
|
||||
dist,
|
||||
hashes,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(LocalWheel {
|
||||
dist: Dist::Built(dist.clone()),
|
||||
archive: self.build_context.cache().archive(&archive.id),
|
||||
|
@ -246,6 +268,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
|||
.stream_wheel(
|
||||
wheel.url.raw().clone(),
|
||||
&wheel.filename,
|
||||
None,
|
||||
&wheel_entry,
|
||||
dist,
|
||||
hashes,
|
||||
|
@ -269,6 +292,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
|||
.download_wheel(
|
||||
wheel.url.raw().clone(),
|
||||
&wheel.filename,
|
||||
None,
|
||||
&wheel_entry,
|
||||
dist,
|
||||
hashes,
|
||||
|
@ -427,6 +451,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
|||
&self,
|
||||
url: Url,
|
||||
filename: &WheelFilename,
|
||||
size: Option<u64>,
|
||||
wheel_entry: &CacheEntry,
|
||||
dist: &BuiltDist,
|
||||
hashes: HashPolicy<'_>,
|
||||
|
@ -434,8 +459,19 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
|||
// Create an entry for the HTTP cache.
|
||||
let http_entry = wheel_entry.with_file(format!("{}.http", filename.stem()));
|
||||
|
||||
// Fetch the archive from the cache, or download it if necessary.
|
||||
let req = self.request(url.clone())?;
|
||||
|
||||
// Extract the size from the `Content-Length` header, if not provided by the registry.
|
||||
let size = size.or_else(|| content_length(&req));
|
||||
|
||||
let download = |response: reqwest::Response| {
|
||||
async {
|
||||
let progress = self
|
||||
.reporter
|
||||
.as_ref()
|
||||
.map(|reporter| (reporter, reporter.on_download_start(dist.name(), size)));
|
||||
|
||||
let reader = response
|
||||
.bytes_stream()
|
||||
.map_err(|err| self.handle_response_errors(err))
|
||||
|
@ -449,7 +485,16 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
|||
// Download and unzip the wheel to a temporary directory.
|
||||
let temp_dir = tempfile::tempdir_in(self.build_context.cache().root())
|
||||
.map_err(Error::CacheWrite)?;
|
||||
|
||||
match progress {
|
||||
Some((reporter, progress)) => {
|
||||
let mut reader = ProgressReader::new(&mut hasher, progress, &**reporter);
|
||||
uv_extract::stream::unzip(&mut reader, temp_dir.path()).await?;
|
||||
}
|
||||
None => {
|
||||
uv_extract::stream::unzip(&mut hasher, temp_dir.path()).await?;
|
||||
}
|
||||
}
|
||||
|
||||
// If necessary, exhaust the reader to compute the hash.
|
||||
if !hashes.is_none() {
|
||||
|
@ -464,6 +509,10 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
|||
.await
|
||||
.map_err(Error::CacheRead)?;
|
||||
|
||||
if let Some((reporter, progress)) = progress {
|
||||
reporter.on_download_complete(dist.name(), progress);
|
||||
}
|
||||
|
||||
Ok(Archive::new(
|
||||
id,
|
||||
hashers.into_iter().map(HashDigest::from).collect(),
|
||||
|
@ -523,6 +572,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
|||
&self,
|
||||
url: Url,
|
||||
filename: &WheelFilename,
|
||||
size: Option<u64>,
|
||||
wheel_entry: &CacheEntry,
|
||||
dist: &BuiltDist,
|
||||
hashes: HashPolicy<'_>,
|
||||
|
@ -530,8 +580,18 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
|||
// Create an entry for the HTTP cache.
|
||||
let http_entry = wheel_entry.with_file(format!("{}.http", filename.stem()));
|
||||
|
||||
let req = self.request(url.clone())?;
|
||||
|
||||
// Extract the size from the `Content-Length` header, if not provided by the registry.
|
||||
let size = size.or_else(|| content_length(&req));
|
||||
|
||||
let download = |response: reqwest::Response| {
|
||||
async {
|
||||
let progress = self
|
||||
.reporter
|
||||
.as_ref()
|
||||
.map(|reporter| (reporter, reporter.on_download_start(dist.name(), size)));
|
||||
|
||||
let reader = response
|
||||
.bytes_stream()
|
||||
.map_err(|err| self.handle_response_errors(err))
|
||||
|
@ -541,9 +601,25 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
|||
let temp_file = tempfile::tempfile_in(self.build_context.cache().root())
|
||||
.map_err(Error::CacheWrite)?;
|
||||
let mut writer = tokio::io::BufWriter::new(tokio::fs::File::from_std(temp_file));
|
||||
|
||||
match progress {
|
||||
Some((reporter, progress)) => {
|
||||
// Wrap the reader in a progress reporter. This will report 100% progress
|
||||
// after the download is complete, even if we still have to unzip and hash
|
||||
// part of the file.
|
||||
let mut reader =
|
||||
ProgressReader::new(reader.compat(), progress, &**reporter);
|
||||
|
||||
tokio::io::copy(&mut reader, &mut writer)
|
||||
.await
|
||||
.map_err(Error::CacheWrite)?;
|
||||
}
|
||||
None => {
|
||||
tokio::io::copy(&mut reader.compat(), &mut writer)
|
||||
.await
|
||||
.map_err(Error::CacheWrite)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Unzip the wheel to a temporary directory.
|
||||
let temp_dir = tempfile::tempdir_in(self.build_context.cache().root())
|
||||
|
@ -588,6 +664,10 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
|||
.await
|
||||
.map_err(Error::CacheRead)?;
|
||||
|
||||
if let Some((reporter, progress)) = progress {
|
||||
reporter.on_download_complete(dist.name(), progress);
|
||||
}
|
||||
|
||||
Ok(Archive::new(id, hashes))
|
||||
}
|
||||
.instrument(info_span!("wheel", wheel = %dist))
|
||||
|
@ -813,6 +893,50 @@ impl<'a> ManagedClient<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns the value of the `Content-Length` header from the [`reqwest::Request`], if present.
|
||||
fn content_length(req: &reqwest::Request) -> Option<u64> {
|
||||
req.headers()
|
||||
.get(reqwest::header::CONTENT_LENGTH)
|
||||
.and_then(|val| val.to_str().ok())
|
||||
.and_then(|val| val.parse::<u64>().ok())
|
||||
}
|
||||
|
||||
/// An asynchronous reader that reports progress as bytes are read.
|
||||
struct ProgressReader<'a, R> {
|
||||
reader: R,
|
||||
index: usize,
|
||||
reporter: &'a dyn Reporter,
|
||||
}
|
||||
|
||||
impl<'a, R> ProgressReader<'a, R> {
|
||||
/// Create a new [`ProgressReader`] that wraps another reader.
|
||||
fn new(reader: R, index: usize, reporter: &'a dyn Reporter) -> Self {
|
||||
Self {
|
||||
reader,
|
||||
index,
|
||||
reporter,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> AsyncRead for ProgressReader<'_, R>
|
||||
where
|
||||
R: AsyncRead + Unpin,
|
||||
{
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut self.as_mut().reader)
|
||||
.poll_read(cx, buf)
|
||||
.map_ok(|()| {
|
||||
self.reporter
|
||||
.on_download_progress(self.index, buf.filled().len() as u64);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A pointer to an archive in the cache, fetched from an HTTP archive.
|
||||
///
|
||||
/// Encoded with `MsgPack`, and represented on disk by a `.http` file.
|
||||
|
|
|
@ -3,6 +3,7 @@ use std::sync::Arc;
|
|||
use url::Url;
|
||||
|
||||
use distribution_types::BuildableSource;
|
||||
use pep508_rs::PackageName;
|
||||
|
||||
pub trait Reporter: Send + Sync {
|
||||
/// Callback to invoke when a source distribution build is kicked off.
|
||||
|
@ -15,7 +16,17 @@ pub trait Reporter: Send + Sync {
|
|||
fn on_checkout_start(&self, url: &Url, rev: &str) -> usize;
|
||||
|
||||
/// Callback to invoke when a repository checkout completes.
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, index: usize);
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, id: usize);
|
||||
|
||||
/// Callback to invoke when a download is kicked off.
|
||||
fn on_download_start(&self, name: &PackageName, size: Option<u64>) -> usize;
|
||||
|
||||
/// Callback to invoke when a download makes progress (i.e. some number of bytes are
|
||||
/// downloaded).
|
||||
fn on_download_progress(&self, id: usize, inc: u64);
|
||||
|
||||
/// Callback to invoke when a download is complete.
|
||||
fn on_download_complete(&self, name: &PackageName, id: usize);
|
||||
}
|
||||
|
||||
/// A facade for converting from [`Reporter`] to [`uv_git::Reporter`].
|
||||
|
@ -34,7 +45,7 @@ impl uv_git::Reporter for Facade {
|
|||
self.reporter.on_checkout_start(url, rev)
|
||||
}
|
||||
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, index: usize) {
|
||||
self.reporter.on_checkout_complete(url, rev, index);
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, id: usize) {
|
||||
self.reporter.on_checkout_complete(url, rev, id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ use std::path::Path;
|
|||
use std::sync::Arc;
|
||||
|
||||
use futures::{stream::FuturesUnordered, FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt};
|
||||
use pep508_rs::PackageName;
|
||||
use tokio::task::JoinError;
|
||||
use tracing::instrument;
|
||||
use url::Url;
|
||||
|
@ -169,6 +170,7 @@ impl<'a, Context: BuildContext> Downloader<'a, Context> {
|
|||
let id = dist.distribution_id();
|
||||
if in_flight.downloads.register(id.clone()) {
|
||||
let policy = self.hashes.get(&dist);
|
||||
|
||||
let result = self
|
||||
.database
|
||||
.get_or_build_wheel(&dist, self.tags, policy)
|
||||
|
@ -223,6 +225,16 @@ pub trait Reporter: Send + Sync {
|
|||
/// Callback to invoke when the operation is complete.
|
||||
fn on_complete(&self);
|
||||
|
||||
/// Callback to invoke when a download is kicked off.
|
||||
fn on_download_start(&self, name: &PackageName, size: Option<u64>) -> usize;
|
||||
|
||||
/// Callback to invoke when a download makes progress (i.e. some number of bytes are
|
||||
/// downloaded).
|
||||
fn on_download_progress(&self, index: usize, bytes: u64);
|
||||
|
||||
/// Callback to invoke when a download is complete.
|
||||
fn on_download_complete(&self, name: &PackageName, index: usize);
|
||||
|
||||
/// Callback to invoke when a source distribution build is kicked off.
|
||||
fn on_build_start(&self, source: &BuildableSource) -> usize;
|
||||
|
||||
|
@ -269,4 +281,16 @@ impl uv_distribution::Reporter for Facade {
|
|||
fn on_checkout_complete(&self, url: &Url, rev: &str, index: usize) {
|
||||
self.reporter.on_checkout_complete(url, rev, index);
|
||||
}
|
||||
|
||||
fn on_download_start(&self, name: &PackageName, size: Option<u64>) -> usize {
|
||||
self.reporter.on_download_start(name, size)
|
||||
}
|
||||
|
||||
fn on_download_progress(&self, index: usize, inc: u64) {
|
||||
self.reporter.on_download_progress(index, inc);
|
||||
}
|
||||
|
||||
fn on_download_complete(&self, name: &PackageName, index: usize) {
|
||||
self.reporter.on_download_complete(name, index);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,11 +20,21 @@ pub trait Reporter: Send + Sync {
|
|||
/// Callback to invoke when a source distribution build is complete.
|
||||
fn on_build_complete(&self, source: &BuildableSource, id: usize);
|
||||
|
||||
/// Callback to invoke when a download is kicked off.
|
||||
fn on_download_start(&self, name: &PackageName, size: Option<u64>) -> usize;
|
||||
|
||||
/// Callback to invoke when a download makes progress (i.e. some number of bytes are
|
||||
/// downloaded).
|
||||
fn on_download_progress(&self, id: usize, bytes: u64);
|
||||
|
||||
/// Callback to invoke when a download is complete.
|
||||
fn on_download_complete(&self, name: &PackageName, id: usize);
|
||||
|
||||
/// Callback to invoke when a repository checkout begins.
|
||||
fn on_checkout_start(&self, url: &Url, rev: &str) -> usize;
|
||||
|
||||
/// Callback to invoke when a repository checkout completes.
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, index: usize);
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, id: usize);
|
||||
}
|
||||
|
||||
/// A facade for converting from [`Reporter`] to [`uv_distribution::Reporter`].
|
||||
|
@ -45,7 +55,19 @@ impl uv_distribution::Reporter for Facade {
|
|||
self.reporter.on_checkout_start(url, rev)
|
||||
}
|
||||
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, index: usize) {
|
||||
self.reporter.on_checkout_complete(url, rev, index);
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, id: usize) {
|
||||
self.reporter.on_checkout_complete(url, rev, id);
|
||||
}
|
||||
|
||||
fn on_download_start(&self, name: &PackageName, size: Option<u64>) -> usize {
|
||||
self.reporter.on_download_start(name, size)
|
||||
}
|
||||
|
||||
fn on_download_progress(&self, id: usize, bytes: u64) {
|
||||
self.reporter.on_download_progress(id, bytes);
|
||||
}
|
||||
|
||||
fn on_download_complete(&self, name: &PackageName, id: usize) {
|
||||
self.reporter.on_download_complete(name, id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ use std::time::Duration;
|
|||
|
||||
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
|
||||
use owo_colors::OwoColorize;
|
||||
use rustc_hash::FxHashMap;
|
||||
use url::Url;
|
||||
|
||||
use distribution_types::{
|
||||
|
@ -14,91 +15,109 @@ use uv_normalize::PackageName;
|
|||
use crate::printer::Printer;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DownloadReporter {
|
||||
struct ProgressReporter {
|
||||
printer: Printer,
|
||||
root: ProgressBar,
|
||||
multi_progress: MultiProgress,
|
||||
progress: ProgressBar,
|
||||
bars: Arc<Mutex<Vec<ProgressBar>>>,
|
||||
state: Arc<Mutex<BarState>>,
|
||||
}
|
||||
|
||||
impl From<Printer> for DownloadReporter {
|
||||
fn from(printer: Printer) -> Self {
|
||||
let multi_progress = MultiProgress::with_draw_target(printer.target());
|
||||
#[derive(Default, Debug)]
|
||||
struct BarState {
|
||||
// The number of bars that precede any download bars (i.e. build/checkout status).
|
||||
headers: usize,
|
||||
// A list of donwnload bar sizes, in descending order.
|
||||
sizes: Vec<u64>,
|
||||
// A map of progress bars, by ID.
|
||||
bars: FxHashMap<usize, ProgressBar>,
|
||||
// A monotonic counter for bar IDs.
|
||||
id: usize,
|
||||
}
|
||||
|
||||
let progress = multi_progress.add(ProgressBar::with_draw_target(None, printer.target()));
|
||||
progress.set_style(
|
||||
ProgressStyle::with_template("{bar:20} [{pos}/{len}] {wide_msg:.dim}").unwrap(),
|
||||
);
|
||||
progress.set_message("Fetching packages...");
|
||||
|
||||
Self {
|
||||
printer,
|
||||
multi_progress,
|
||||
progress,
|
||||
bars: Arc::new(Mutex::new(Vec::new())),
|
||||
}
|
||||
impl BarState {
|
||||
// Returns a unique ID for a new bar.
|
||||
fn id(&mut self) -> usize {
|
||||
self.id += 1;
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl DownloadReporter {
|
||||
#[must_use]
|
||||
pub(crate) fn with_length(self, length: u64) -> Self {
|
||||
self.progress.set_length(length);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl DownloadReporter {
|
||||
impl ProgressReporter {
|
||||
fn on_any_build_start(&self, color_string: &str) -> usize {
|
||||
let mut state = self.state.lock().unwrap();
|
||||
let id = state.id();
|
||||
|
||||
let progress = self.multi_progress.insert_before(
|
||||
&self.progress,
|
||||
&self.root,
|
||||
ProgressBar::with_draw_target(None, self.printer.target()),
|
||||
);
|
||||
|
||||
progress.set_style(ProgressStyle::with_template("{wide_msg}").unwrap());
|
||||
progress.set_message(format!("{} {}", "Building".bold().cyan(), color_string));
|
||||
|
||||
let mut bars = self.bars.lock().unwrap();
|
||||
bars.push(progress);
|
||||
bars.len() - 1
|
||||
state.headers += 1;
|
||||
state.bars.insert(id, progress);
|
||||
id
|
||||
}
|
||||
|
||||
fn on_any_build_complete(&self, color_string: &str, id: usize) {
|
||||
let bars = self.bars.lock().unwrap();
|
||||
let progress = &bars[id];
|
||||
let progress = {
|
||||
let mut state = self.state.lock().unwrap();
|
||||
state.headers -= 1;
|
||||
state.bars.remove(&id).unwrap()
|
||||
};
|
||||
|
||||
progress.finish_with_message(format!(" {} {}", "Built".bold().green(), color_string));
|
||||
}
|
||||
}
|
||||
|
||||
impl uv_installer::DownloadReporter for DownloadReporter {
|
||||
fn on_progress(&self, dist: &CachedDist) {
|
||||
self.progress.set_message(format!("{dist}"));
|
||||
self.progress.inc(1);
|
||||
fn on_download_start(&self, name: &PackageName, size: Option<u64>) -> usize {
|
||||
let mut state = self.state.lock().unwrap();
|
||||
|
||||
// Preserve ascending order.
|
||||
let position = size.map_or(0, |size| state.sizes.partition_point(|&len| len < size));
|
||||
state.sizes.insert(position, size.unwrap_or(0));
|
||||
|
||||
let progress = self.multi_progress.insert(
|
||||
// Make sure not to reorder the initial "Downloading..." bar, or any previous bars.
|
||||
position + 1 + state.headers,
|
||||
ProgressBar::with_draw_target(size, self.printer.target()),
|
||||
);
|
||||
|
||||
if size.is_some() {
|
||||
progress.set_style(
|
||||
ProgressStyle::with_template(
|
||||
"{msg:10.dim} {bar:30.green/dim} {decimal_bytes:>7}/{decimal_total_bytes:7}",
|
||||
)
|
||||
.unwrap()
|
||||
.progress_chars("--"),
|
||||
);
|
||||
progress.set_message(name.to_string());
|
||||
} else {
|
||||
progress.set_style(ProgressStyle::with_template("{wide_msg:.dim} ....").unwrap());
|
||||
progress.set_message(name.to_string());
|
||||
progress.finish();
|
||||
}
|
||||
|
||||
fn on_complete(&self) {
|
||||
self.progress.finish_and_clear();
|
||||
let id = state.id();
|
||||
state.bars.insert(id, progress);
|
||||
id
|
||||
}
|
||||
|
||||
fn on_build_start(&self, source: &BuildableSource) -> usize {
|
||||
self.on_any_build_start(&source.to_color_string())
|
||||
fn on_download_progress(&self, id: usize, bytes: u64) {
|
||||
self.state.lock().unwrap().bars[&id].inc(bytes);
|
||||
}
|
||||
|
||||
fn on_build_complete(&self, source: &BuildableSource, index: usize) {
|
||||
self.on_any_build_complete(&source.to_color_string(), index);
|
||||
}
|
||||
|
||||
fn on_editable_build_start(&self, dist: &LocalEditable) -> usize {
|
||||
self.on_any_build_start(&dist.to_color_string())
|
||||
}
|
||||
|
||||
fn on_editable_build_complete(&self, dist: &LocalEditable, id: usize) {
|
||||
self.on_any_build_complete(&dist.to_color_string(), id);
|
||||
fn on_download_complete(&self, id: usize) {
|
||||
let progress = self.state.lock().unwrap().bars.remove(&id).unwrap();
|
||||
progress.finish_and_clear();
|
||||
}
|
||||
|
||||
fn on_checkout_start(&self, url: &Url, rev: &str) -> usize {
|
||||
let mut state = self.state.lock().unwrap();
|
||||
let id = state.id();
|
||||
|
||||
let progress = self.multi_progress.insert_before(
|
||||
&self.progress,
|
||||
&self.root,
|
||||
ProgressBar::with_draw_target(None, self.printer.target()),
|
||||
);
|
||||
|
||||
|
@ -111,14 +130,18 @@ impl uv_installer::DownloadReporter for DownloadReporter {
|
|||
));
|
||||
progress.finish();
|
||||
|
||||
let mut bars = self.bars.lock().unwrap();
|
||||
bars.push(progress);
|
||||
bars.len() - 1
|
||||
state.headers += 1;
|
||||
state.bars.insert(id, progress);
|
||||
id
|
||||
}
|
||||
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, index: usize) {
|
||||
let bars = self.bars.lock().unwrap();
|
||||
let progress = &bars[index];
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, id: usize) {
|
||||
let progress = {
|
||||
let mut state = self.state.lock().unwrap();
|
||||
state.headers -= 1;
|
||||
state.bars.remove(&id).unwrap()
|
||||
};
|
||||
|
||||
progress.finish_with_message(format!(
|
||||
" {} {} ({})",
|
||||
"Updated".bold().green(),
|
||||
|
@ -128,6 +151,205 @@ impl uv_installer::DownloadReporter for DownloadReporter {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DownloadReporter {
|
||||
reporter: ProgressReporter,
|
||||
}
|
||||
|
||||
impl From<Printer> for DownloadReporter {
|
||||
fn from(printer: Printer) -> Self {
|
||||
let multi_progress = MultiProgress::with_draw_target(printer.target());
|
||||
|
||||
let progress = multi_progress.add(ProgressBar::with_draw_target(None, printer.target()));
|
||||
progress.enable_steady_tick(Duration::from_millis(200));
|
||||
progress.set_style(
|
||||
ProgressStyle::with_template("{spinner:.white} {msg:.dim} ({pos}/{len})")
|
||||
.unwrap()
|
||||
.tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]),
|
||||
);
|
||||
progress.set_message("Downloading packages...");
|
||||
|
||||
let reporter = ProgressReporter {
|
||||
printer,
|
||||
multi_progress,
|
||||
root: progress,
|
||||
state: Arc::default(),
|
||||
};
|
||||
|
||||
Self { reporter }
|
||||
}
|
||||
}
|
||||
|
||||
impl DownloadReporter {
|
||||
#[must_use]
|
||||
pub(crate) fn with_length(self, length: u64) -> Self {
|
||||
self.reporter.root.set_length(length);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl uv_installer::DownloadReporter for DownloadReporter {
|
||||
fn on_progress(&self, _dist: &CachedDist) {
|
||||
self.reporter.root.inc(1);
|
||||
}
|
||||
|
||||
fn on_complete(&self) {
|
||||
self.reporter.root.finish_and_clear();
|
||||
}
|
||||
|
||||
fn on_build_start(&self, source: &BuildableSource) -> usize {
|
||||
self.reporter.on_any_build_start(&source.to_color_string())
|
||||
}
|
||||
|
||||
fn on_build_complete(&self, source: &BuildableSource, id: usize) {
|
||||
self.reporter
|
||||
.on_any_build_complete(&source.to_color_string(), id);
|
||||
}
|
||||
|
||||
fn on_editable_build_start(&self, dist: &LocalEditable) -> usize {
|
||||
self.reporter.on_any_build_start(&dist.to_color_string())
|
||||
}
|
||||
|
||||
fn on_editable_build_complete(&self, dist: &LocalEditable, id: usize) {
|
||||
self.reporter
|
||||
.on_any_build_complete(&dist.to_color_string(), id);
|
||||
}
|
||||
|
||||
fn on_download_start(&self, name: &PackageName, size: Option<u64>) -> usize {
|
||||
self.reporter.on_download_start(name, size)
|
||||
}
|
||||
|
||||
fn on_download_progress(&self, id: usize, bytes: u64) {
|
||||
self.reporter.on_download_progress(id, bytes);
|
||||
}
|
||||
|
||||
fn on_download_complete(&self, _name: &PackageName, id: usize) {
|
||||
self.reporter.on_download_complete(id);
|
||||
}
|
||||
|
||||
fn on_checkout_start(&self, url: &Url, rev: &str) -> usize {
|
||||
self.reporter.on_checkout_start(url, rev)
|
||||
}
|
||||
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, id: usize) {
|
||||
self.reporter.on_checkout_complete(url, rev, id);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ResolverReporter {
|
||||
reporter: ProgressReporter,
|
||||
}
|
||||
|
||||
impl ResolverReporter {
|
||||
#[must_use]
|
||||
pub(crate) fn with_length(self, length: u64) -> Self {
|
||||
self.reporter.root.set_length(length);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Printer> for ResolverReporter {
|
||||
fn from(printer: Printer) -> Self {
|
||||
let multi_progress = MultiProgress::with_draw_target(printer.target());
|
||||
|
||||
let root = multi_progress.add(ProgressBar::with_draw_target(None, printer.target()));
|
||||
root.enable_steady_tick(Duration::from_millis(200));
|
||||
root.set_style(
|
||||
ProgressStyle::with_template("{spinner:.white} {wide_msg:.dim}")
|
||||
.unwrap()
|
||||
.tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]),
|
||||
);
|
||||
root.set_message("Resolving dependencies...");
|
||||
|
||||
let reporter = ProgressReporter {
|
||||
root,
|
||||
printer,
|
||||
multi_progress,
|
||||
state: Arc::default(),
|
||||
};
|
||||
|
||||
ResolverReporter { reporter }
|
||||
}
|
||||
}
|
||||
|
||||
impl uv_resolver::ResolverReporter for ResolverReporter {
|
||||
fn on_progress(&self, name: &PackageName, version_or_url: &VersionOrUrlRef) {
|
||||
match version_or_url {
|
||||
VersionOrUrlRef::Version(version) => {
|
||||
self.reporter.root.set_message(format!("{name}=={version}"));
|
||||
}
|
||||
VersionOrUrlRef::Url(url) => {
|
||||
self.reporter.root.set_message(format!("{name} @ {url}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_complete(&self) {
|
||||
self.reporter.root.finish_and_clear();
|
||||
}
|
||||
|
||||
fn on_build_start(&self, source: &BuildableSource) -> usize {
|
||||
self.reporter.on_any_build_start(&source.to_color_string())
|
||||
}
|
||||
|
||||
fn on_build_complete(&self, source: &BuildableSource, id: usize) {
|
||||
self.reporter
|
||||
.on_any_build_complete(&source.to_color_string(), id);
|
||||
}
|
||||
|
||||
fn on_checkout_start(&self, url: &Url, rev: &str) -> usize {
|
||||
self.reporter.on_checkout_start(url, rev)
|
||||
}
|
||||
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, id: usize) {
|
||||
self.reporter.on_checkout_complete(url, rev, id);
|
||||
}
|
||||
|
||||
fn on_download_start(&self, name: &PackageName, size: Option<u64>) -> usize {
|
||||
self.reporter.on_download_start(name, size)
|
||||
}
|
||||
|
||||
fn on_download_progress(&self, id: usize, bytes: u64) {
|
||||
self.reporter.on_download_progress(id, bytes);
|
||||
}
|
||||
|
||||
fn on_download_complete(&self, _name: &PackageName, id: usize) {
|
||||
self.reporter.on_download_complete(id);
|
||||
}
|
||||
}
|
||||
|
||||
impl uv_distribution::Reporter for ResolverReporter {
|
||||
fn on_build_start(&self, source: &BuildableSource) -> usize {
|
||||
self.reporter.on_any_build_start(&source.to_color_string())
|
||||
}
|
||||
|
||||
fn on_build_complete(&self, source: &BuildableSource, id: usize) {
|
||||
self.reporter
|
||||
.on_any_build_complete(&source.to_color_string(), id);
|
||||
}
|
||||
|
||||
fn on_download_start(&self, name: &PackageName, size: Option<u64>) -> usize {
|
||||
self.reporter.on_download_start(name, size)
|
||||
}
|
||||
|
||||
fn on_download_progress(&self, id: usize, bytes: u64) {
|
||||
self.reporter.on_download_progress(id, bytes);
|
||||
}
|
||||
|
||||
fn on_download_complete(&self, _name: &PackageName, id: usize) {
|
||||
self.reporter.on_download_complete(id);
|
||||
}
|
||||
|
||||
fn on_checkout_start(&self, url: &Url, rev: &str) -> usize {
|
||||
self.reporter.on_checkout_start(url, rev)
|
||||
}
|
||||
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, id: usize) {
|
||||
self.reporter.on_checkout_complete(url, rev, id);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct InstallReporter {
|
||||
progress: ProgressBar,
|
||||
|
@ -163,162 +385,6 @@ impl uv_installer::InstallReporter for InstallReporter {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ResolverReporter {
|
||||
printer: Printer,
|
||||
multi_progress: MultiProgress,
|
||||
progress: ProgressBar,
|
||||
bars: Arc<Mutex<Vec<ProgressBar>>>,
|
||||
}
|
||||
|
||||
impl From<Printer> for ResolverReporter {
|
||||
fn from(printer: Printer) -> Self {
|
||||
let multi_progress = MultiProgress::with_draw_target(printer.target());
|
||||
|
||||
let progress = multi_progress.add(ProgressBar::with_draw_target(None, printer.target()));
|
||||
progress.enable_steady_tick(Duration::from_millis(200));
|
||||
progress.set_style(
|
||||
ProgressStyle::with_template("{spinner:.white} {wide_msg:.dim}")
|
||||
.unwrap()
|
||||
.tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]),
|
||||
);
|
||||
progress.set_message("Resolving dependencies...");
|
||||
|
||||
Self {
|
||||
printer,
|
||||
multi_progress,
|
||||
progress,
|
||||
bars: Arc::new(Mutex::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResolverReporter {
|
||||
#[must_use]
|
||||
pub(crate) fn with_length(self, length: u64) -> Self {
|
||||
self.progress.set_length(length);
|
||||
self
|
||||
}
|
||||
|
||||
fn on_progress(&self, name: &PackageName, version_or_url: &VersionOrUrlRef) {
|
||||
match version_or_url {
|
||||
VersionOrUrlRef::Version(version) => {
|
||||
self.progress.set_message(format!("{name}=={version}"));
|
||||
}
|
||||
VersionOrUrlRef::Url(url) => {
|
||||
self.progress.set_message(format!("{name} @ {url}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_complete(&self) {
|
||||
self.progress.finish_and_clear();
|
||||
}
|
||||
|
||||
fn on_build_start(&self, source: &BuildableSource) -> usize {
|
||||
let progress = self.multi_progress.insert_before(
|
||||
&self.progress,
|
||||
ProgressBar::with_draw_target(None, self.printer.target()),
|
||||
);
|
||||
|
||||
progress.set_style(ProgressStyle::with_template("{wide_msg}").unwrap());
|
||||
progress.set_message(format!(
|
||||
"{} {}",
|
||||
"Building".bold().cyan(),
|
||||
source.to_color_string(),
|
||||
));
|
||||
|
||||
let mut bars = self.bars.lock().unwrap();
|
||||
bars.push(progress);
|
||||
bars.len() - 1
|
||||
}
|
||||
|
||||
fn on_build_complete(&self, source: &BuildableSource, index: usize) {
|
||||
let bars = self.bars.lock().unwrap();
|
||||
let progress = &bars[index];
|
||||
progress.finish_with_message(format!(
|
||||
" {} {}",
|
||||
"Built".bold().green(),
|
||||
source.to_color_string(),
|
||||
));
|
||||
}
|
||||
|
||||
fn on_checkout_start(&self, url: &Url, rev: &str) -> usize {
|
||||
let progress = self.multi_progress.insert_before(
|
||||
&self.progress,
|
||||
ProgressBar::with_draw_target(None, self.printer.target()),
|
||||
);
|
||||
|
||||
progress.set_style(ProgressStyle::with_template("{wide_msg}").unwrap());
|
||||
progress.set_message(format!(
|
||||
"{} {} ({})",
|
||||
"Updating".bold().cyan(),
|
||||
url,
|
||||
rev.dimmed()
|
||||
));
|
||||
progress.finish();
|
||||
|
||||
let mut bars = self.bars.lock().unwrap();
|
||||
bars.push(progress);
|
||||
bars.len() - 1
|
||||
}
|
||||
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, index: usize) {
|
||||
let bars = self.bars.lock().unwrap();
|
||||
let progress = &bars[index];
|
||||
progress.finish_with_message(format!(
|
||||
" {} {} ({})",
|
||||
"Updated".bold().green(),
|
||||
url,
|
||||
rev.dimmed()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
impl uv_resolver::ResolverReporter for ResolverReporter {
|
||||
fn on_progress(&self, name: &PackageName, version_or_url: &VersionOrUrlRef) {
|
||||
self.on_progress(name, version_or_url);
|
||||
}
|
||||
|
||||
fn on_complete(&self) {
|
||||
self.on_complete();
|
||||
}
|
||||
|
||||
fn on_build_start(&self, source: &BuildableSource) -> usize {
|
||||
self.on_build_start(source)
|
||||
}
|
||||
|
||||
fn on_build_complete(&self, source: &BuildableSource, index: usize) {
|
||||
self.on_build_complete(source, index);
|
||||
}
|
||||
|
||||
fn on_checkout_start(&self, url: &Url, rev: &str) -> usize {
|
||||
self.on_checkout_start(url, rev)
|
||||
}
|
||||
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, index: usize) {
|
||||
self.on_checkout_complete(url, rev, index);
|
||||
}
|
||||
}
|
||||
|
||||
impl uv_distribution::Reporter for ResolverReporter {
|
||||
fn on_build_start(&self, source: &BuildableSource) -> usize {
|
||||
self.on_build_start(source)
|
||||
}
|
||||
|
||||
fn on_build_complete(&self, source: &BuildableSource, index: usize) {
|
||||
self.on_build_complete(source, index);
|
||||
}
|
||||
|
||||
fn on_checkout_start(&self, url: &Url, rev: &str) -> usize {
|
||||
self.on_checkout_start(url, rev)
|
||||
}
|
||||
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, index: usize) {
|
||||
self.on_checkout_complete(url, rev, index);
|
||||
}
|
||||
}
|
||||
|
||||
/// Like [`std::fmt::Display`], but with colors.
|
||||
trait ColorDisplay {
|
||||
fn to_color_string(&self) -> String;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue