Skip to content

Commit

Permalink
feat: Improve UX by adding console output (#24)
Browse files Browse the repository at this point in the history
* feat: Improve UX by adding console output

* Print to stderr

* Add spinner for unpacking

* Revert to no spinner

* Fix

* Fix progress bar for unpacking

* Remove total length from bar

* Fix bug

* Fix bug

* Remove install progress reporter again
  • Loading branch information
delsner authored Jun 8, 2024
1 parent d4bef2c commit 47e5096
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 21 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "pixi-pack"
description = "A command line tool to pack and unpack conda environments for easy sharing"
version = "0.1.2"
version = "0.1.3"
edition = "2021"

[features]
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
mod pack;
mod unpack;
mod util;

pub use pack::{pack, PackOptions};
use rattler_conda_types::Platform;
use serde::{Deserialize, Serialize};
pub use unpack::{unarchive, unpack, UnpackOptions};
pub use util::{get_size, ProgressReporter};

pub const CHANNEL_DIRECTORY_NAME: &str = "channel";
pub const PIXI_PACK_METADATA_PATH: &str = "pixi-pack.json";
Expand Down
45 changes: 29 additions & 16 deletions src/pack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::{
};

use fxhash::FxHashMap;
use indicatif::HumanBytes;
use rattler_index::{package_record_from_conda, package_record_from_tar_bz2};
use tokio::{
fs::{self, create_dir_all, File},
Expand All @@ -13,14 +14,15 @@ use tokio::{

use anyhow::Result;
use futures::{stream, StreamExt, TryFutureExt, TryStreamExt};
use indicatif::ProgressStyle;
use rattler_conda_types::{package::ArchiveType, ChannelInfo, PackageRecord, Platform, RepoData};
use rattler_lock::{CondaPackage, LockFile, Package};
use rattler_networking::{AuthenticationMiddleware, AuthenticationStorage};
use reqwest_middleware::ClientWithMiddleware;
use tokio_tar::Builder;

use crate::{PixiPackMetadata, CHANNEL_DIRECTORY_NAME, PIXI_PACK_METADATA_PATH};
use crate::{
get_size, PixiPackMetadata, ProgressReporter, CHANNEL_DIRECTORY_NAME, PIXI_PACK_METADATA_PATH,
};
use anyhow::anyhow;

/// Options for packing a pixi environment.
Expand Down Expand Up @@ -89,31 +91,24 @@ pub async fn pack(options: PackOptions) -> Result<()> {

// Download packages to temporary directory.
tracing::info!(
"Downloading {} packages",
"Downloading {} packages...",
conda_packages_from_lockfile.len()
);
let bar = indicatif::ProgressBar::new(conda_packages_from_lockfile.len() as u64);
bar.set_style(
ProgressStyle::with_template(
"[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}",
)
.expect("could not set progress style")
.progress_chars("##-"),
eprintln!(
"⏳ Downloading {} packages...",
conda_packages_from_lockfile.len()
);

let bar = ProgressReporter::new(conda_packages_from_lockfile.len() as u64);
stream::iter(conda_packages_from_lockfile.iter())
.map(Ok)
.try_for_each_concurrent(50, |package| async {
download_package(&client, package, &channel_dir).await?;

bar.inc(1);

bar.pb.inc(1);
Ok(())
})
.await
.map_err(|e: anyhow::Error| anyhow!("could not download package: {}", e))?;

bar.finish();
bar.pb.finish_and_clear();

let mut conda_packages: Vec<(String, PackageRecord)> = Vec::new();

Expand All @@ -133,6 +128,8 @@ pub async fn pack(options: PackOptions) -> Result<()> {
.map(|(p, t)| (PathBuf::from(format!("{}{}", p, t.extension())), t))
})
.collect();

tracing::info!("Injecting {} packages", injected_packages.len());
for (path, archive_type) in injected_packages {
// step 1: Derive PackageRecord from index.json inside the package
let package_record = match archive_type {
Expand All @@ -157,23 +154,39 @@ pub async fn pack(options: PackOptions) -> Result<()> {
}

// Create `repodata.json` files.
tracing::info!("Creating repodata.json files");
create_repodata_files(conda_packages.iter(), &channel_dir).await?;

// Add pixi-pack.json containing metadata.
tracing::info!("Creating pixi-pack.json file");
let metadata_path = output_folder.path().join(PIXI_PACK_METADATA_PATH);
let mut metadata_file = File::create(&metadata_path).await?;

let metadata = serde_json::to_string_pretty(&options.metadata)?;
metadata_file.write_all(metadata.as_bytes()).await?;

// Create environment file.
tracing::info!("Creating environment.yml file");
create_environment_file(output_folder.path(), conda_packages.iter().map(|(_, p)| p)).await?;

// Pack = archive the contents.
tracing::info!("Creating archive at {}", options.output_file.display());
archive_directory(output_folder.path(), &options.output_file)
.await
.map_err(|e| anyhow!("could not archive directory: {}", e))?;

let output_size = HumanBytes(get_size(&options.output_file)?).to_string();
tracing::info!(
"Created pack at {} with size {}.",
options.output_file.display(),
output_size
);
eprintln!(
"📦 Created pack at {} with size {}.",
options.output_file.display(),
output_size
);

Ok(())
}

Expand Down
27 changes: 24 additions & 3 deletions src/unpack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ use tokio_tar::Archive;
use url::Url;

use crate::{
PixiPackMetadata, CHANNEL_DIRECTORY_NAME, DEFAULT_PIXI_PACK_VERSION, PIXI_PACK_METADATA_PATH,
PixiPackMetadata, ProgressReporter, CHANNEL_DIRECTORY_NAME, DEFAULT_PIXI_PACK_VERSION,
PIXI_PACK_METADATA_PATH,
};

/// Options for unpacking a pixi environment.
Expand All @@ -41,6 +42,7 @@ pub async fn unpack(options: UnpackOptions) -> Result<()> {

let channel_directory = unpack_dir.join(CHANNEL_DIRECTORY_NAME);

tracing::info!("Unarchiving pack to {}", unpack_dir.display());
unarchive(&options.pack_file, &unpack_dir)
.await
.map_err(|e| anyhow!("Could not unarchive: {}", e))?;
Expand All @@ -49,10 +51,12 @@ pub async fn unpack(options: UnpackOptions) -> Result<()> {

let target_prefix = options.output_directory.join("env");

tracing::info!("Creating prefix at {}", target_prefix.display());
create_prefix(&channel_directory, &target_prefix)
.await
.map_err(|e| anyhow!("Could not create prefix: {}", e))?;

tracing::info!("Generating activation script");
create_activation_script(
&options.output_directory,
&target_prefix,
Expand All @@ -61,6 +65,15 @@ pub async fn unpack(options: UnpackOptions) -> Result<()> {
.await
.map_err(|e| anyhow!("Could not create activation script: {}", e))?;

tracing::info!(
"Finished unpacking to {}.",
options.output_directory.display(),
);
eprintln!(
"💫 Finished unpacking to {}.",
options.output_directory.display()
);

Ok(())
}

Expand Down Expand Up @@ -155,11 +168,16 @@ async fn create_prefix(channel_dir: &Path, target_prefix: &Path) -> Result<()> {
.map_err(|e| anyhow!("could not create temporary directory: {}", e))?
.into_path();

eprintln!(
"⏳ Extracting and installing {} packages...",
packages.len()
);
let reporter = ProgressReporter::new(packages.len() as u64);

// extract packages to cache
tracing::info!("Creating cache with {} packages", packages.len());
let package_cache = PackageCache::new(cache_dir);

let installer = Installer::default();

let repodata_records: Vec<RepoDataRecord> = stream::iter(packages)
.map(|(file_name, package_record)| {
let cache_key = CacheKey::from(&package_record);
Expand Down Expand Up @@ -189,6 +207,7 @@ async fn create_prefix(channel_dir: &Path, target_prefix: &Path) -> Result<()> {
)
.await
.map_err(|e| anyhow!("could not extract package: {}", e))?;
reporter.pb.inc(1);

Ok::<RepoDataRecord, anyhow::Error>(repodata_record)
}
Expand All @@ -198,6 +217,8 @@ async fn create_prefix(channel_dir: &Path, target_prefix: &Path) -> Result<()> {
.await?;

// Invariant: all packages are in the cache
tracing::info!("Installing {} packages", repodata_records.len());
let installer = Installer::default();
installer
.with_package_cache(package_cache)
.install(&target_prefix, repodata_records)
Expand Down
32 changes: 32 additions & 0 deletions src/util.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use std::{path::Path, time::Duration};

use indicatif::{ProgressBar, ProgressStyle};

/// Progress reporter that wraps a progress bar with default styles.
pub struct ProgressReporter {
pub pb: ProgressBar,
}

impl ProgressReporter {
pub fn new(length: u64) -> Self {
let pb = ProgressBar::new(length).with_style(
ProgressStyle::with_template("[{elapsed_precise}] {bar:40.cyan/blue} {msg}")
.expect("could not set progress style")
.progress_chars("##-"),
);
pb.enable_steady_tick(Duration::from_millis(500));
Self { pb }
}
}

/// Get the size of a file or directory in bytes.
pub fn get_size<P: AsRef<Path>>(path: P) -> std::io::Result<u64> {
let metadata = std::fs::metadata(&path)?;
let mut size = metadata.len();
if metadata.is_dir() {
for entry in std::fs::read_dir(&path)? {
size += get_size(entry?.path())?;
}
}
Ok(size)
}

0 comments on commit 47e5096

Please sign in to comment.