From 5d523ce9cfdd858366686957e1210a29cc207c59 Mon Sep 17 00:00:00 2001 From: Tim de Jager Date: Tue, 13 Feb 2024 11:04:10 +0100 Subject: [PATCH] refactor: changed the CLI to have different subcommands e.g `resolve` and `install` (#217) --------- Co-authored-by: nichmor --- .github/workflows/rust-compile.yml | 4 +- Cargo.lock | 11 + README.md | 13 +- crates/rip_bin/Cargo.toml | 1 + crates/rip_bin/src/cli/mod.rs | 3 + crates/rip_bin/src/cli/resolve.rs | 347 +++++++++++++++++++++++++++++ crates/rip_bin/src/cli/wheels.rs | 33 +++ crates/rip_bin/src/lib.rs | 2 + crates/rip_bin/src/main.rs | 339 ++++------------------------ end_to_end_tests/test_endtoend.py | 4 +- 10 files changed, 456 insertions(+), 301 deletions(-) create mode 100644 crates/rip_bin/src/cli/mod.rs create mode 100644 crates/rip_bin/src/cli/resolve.rs create mode 100644 crates/rip_bin/src/cli/wheels.rs diff --git a/.github/workflows/rust-compile.yml b/.github/workflows/rust-compile.yml index 1b2b87ab..60d63480 100644 --- a/.github/workflows/rust-compile.yml +++ b/.github/workflows/rust-compile.yml @@ -149,7 +149,7 @@ jobs: pixi init pixi add "python==3.7.5" cd ../ - cargo run -- boltons --only-sdists -p /d/a/rip/rip/pixi_old/.pixi/env/python.exe + cargo run -- resolve boltons --only-sdists -p /d/a/rip/rip/pixi_old/.pixi/env/python.exe - name: Run pixi latest python test if: contains(matrix.name, 'Windows') @@ -160,4 +160,4 @@ jobs: pixi init pixi add python cd ../ - cargo run -- boltons --only-sdists -p /d/a/rip/rip/pixi_new/.pixi/env/python.exe + cargo run -- resolve boltons --only-sdists -p /d/a/rip/rip/pixi_new/.pixi/env/python.exe diff --git a/Cargo.lock b/Cargo.lock index dfe35e3d..428ad810 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -509,6 +509,16 @@ dependencies = [ "clap_derive", ] +[[package]] +name = "clap-verbosity-flag" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b57f73ca21b17a0352944b9bb61803b6007bd911b6cccfef7153f7f0600ac495" +dependencies = [ + "clap", + "log", +] + [[package]] name = "clap_builder" version = "4.5.0" @@ -2511,6 +2521,7 @@ name = "rip_bin" version = "0.7.0" dependencies = [ "clap", + "clap-verbosity-flag", "console", "dirs", "fs-err", diff --git a/README.md b/README.md index fcc8716c..6bdfb942 100644 --- a/README.md +++ b/README.md @@ -47,18 +47,19 @@ We cache everything in a local directory so that we can re-use the metadata and # Installation We have added very simple installation support for the resolved packages. -For testing purposes exclusively, we have added a `--install-into` flag to the `rip_bin` binary. -Use the `--install-into ` to create a venv and install the packages into it. +For testing purposes exclusively, we have added an `install` command to the `rip_bin` binary. +E.g. `cargo r -- install flask /tmp/flask` to create a venv and install the flask and it's into it. There is no detection of existing packages yet. ## Features -This is a list of current and planned features of `RIP`, the biggest are listed below: +This is a list of current features of `RIP`, the biggest are listed below: -* [x] Downloading and aggressive caching of PyPI metadata. +* [x] Async downloading and aggressive caching of PyPI metadata. * [x] Resolving of PyPI packages using [Resolvo](https://github.com/mamba-org/resolvo). -* [x] Installation of wheel files (see: https://github.com/prefix-dev/rip/issues/6 for last open issues) -* [x] Support sdist files +* [x] Installation of wheel files. +* [x] Support sdist files (must currently adhere to the `PEP 517` and `PEP 518` standards). +* [x] Caching of locally built wheels. More intricacies of the PyPI ecosystem need to be implemented, see our GitHub issues for more details. diff --git a/crates/rip_bin/Cargo.toml b/crates/rip_bin/Cargo.toml index fe2c02b7..e5a73d2b 100644 --- a/crates/rip_bin/Cargo.toml +++ b/crates/rip_bin/Cargo.toml @@ -40,6 +40,7 @@ rand = "0.8.5" serde = "1.0.196" serde_json = "1.0.113" fs-err = "2.11.0" +clap-verbosity-flag = "2.1.2" [package.metadata.release] release = false diff --git a/crates/rip_bin/src/cli/mod.rs b/crates/rip_bin/src/cli/mod.rs new file mode 100644 index 00000000..31feaaed --- /dev/null +++ b/crates/rip_bin/src/cli/mod.rs @@ -0,0 +1,3 @@ +pub mod resolve; + +pub mod wheels; diff --git a/crates/rip_bin/src/cli/resolve.rs b/crates/rip_bin/src/cli/resolve.rs new file mode 100644 index 00000000..6394ef9c --- /dev/null +++ b/crates/rip_bin/src/cli/resolve.rs @@ -0,0 +1,347 @@ +use clap::{Parser, Subcommand}; +use fs_err as fs; +use itertools::Itertools; +use miette::{Context, IntoDiagnostic}; +use rattler_installs_packages::artifacts::wheel::UnpackWheelOptions; +use rattler_installs_packages::index::PackageDb; +use rattler_installs_packages::python_env::{Pep508EnvMakers, PythonLocation, WheelTags}; +use rattler_installs_packages::resolve::solve_options::{ + OnWheelBuildFailure, PreReleaseResolution, ResolveOptions, SDistResolution, +}; +use rattler_installs_packages::resolve::PinnedPackage; +use rattler_installs_packages::types::Requirement; +use rattler_installs_packages::wheel_builder::WheelBuilder; +use serde::Serialize; +use std::collections::HashMap; +use std::io::Write; +use std::path::PathBuf; +use std::sync::Arc; + +#[derive(Serialize, Debug)] +struct Solution { + resolved: bool, + packages: HashMap, + error: Option, +} + +#[derive(Subcommand)] +pub enum Commands { + /// Resolve a set of requirements and output the resolved versions + #[clap(alias = "r")] + Resolve(ResolveArgs), + + /// Resolve and install a set of requirements + #[clap(alias = "i")] + Install(InstallArgs), +} + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +pub struct ResolveArgs { + #[clap(num_args = 1.., required = true)] + /// The specs to resolve + specs: Vec, + + /// How to handle SDists + #[clap(flatten)] + sdist_resolution: SDistResolutionArgs, + + /// Path to the python interpreter to use for resolving environment markers and creating venvs + #[clap(long, short)] + python_interpreter: Option, + + /// Disable inheritance of env variables. + #[arg(short = 'c', long)] + clean_env: bool, + + /// Save failed wheel build environments + #[arg(long)] + save_on_failure: bool, + + /// Prefer pre-releases to normal releases + #[clap(long)] + pre: bool, + + /// Output the result as json + #[clap(long)] + json: bool, +} + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +pub struct InstallArgs { + #[clap(flatten)] + resolve_args: ResolveArgs, + + /// The target directory to install into + target: PathBuf, +} + +#[derive(Parser)] +#[group(multiple = false)] +pub struct SDistResolutionArgs { + /// Prefer any version with wheels over any version with sdists + #[clap(long)] + prefer_wheels: bool, + + /// Prefer any version with sdists over any version with wheels + #[clap(long)] + prefer_sdists: bool, + + /// Only select versions with wheels, ignore versions with sdists + #[clap(long)] + only_wheels: bool, + + /// Only select versions with sdists, ignore versions with wheels + #[clap(long)] + only_sdists: bool, +} + +impl From for SDistResolution { + fn from(value: SDistResolutionArgs) -> Self { + if value.only_sdists { + SDistResolution::OnlySDists + } else if value.only_wheels { + SDistResolution::OnlyWheels + } else if value.prefer_sdists { + SDistResolution::PreferSDists + } else if value.prefer_wheels { + SDistResolution::PreferWheels + } else { + SDistResolution::Normal + } + } +} + +pub async fn execute(package_db: Arc, commands: Commands) -> miette::Result<()> { + let (args, target) = match commands { + Commands::Resolve(args) => (args, None), + Commands::Install(args) => (args.resolve_args, Some(args.target)), + }; + + // Determine the environment markers for the current machine + let env_markers = Arc::new(match args.python_interpreter { + Some(ref python) => { + let python = fs::canonicalize(python).into_diagnostic()?; + Pep508EnvMakers::from_python(&python).await.into_diagnostic() + .wrap_err_with(|| { + format!( + "failed to determine environment markers for the current machine (could not run Python in path: {:?})" + , python + ) + })? + } + None => Pep508EnvMakers::from_env().await.into_diagnostic() + .wrap_err_with(|| { + "failed to determine environment markers for the current machine (could not run Python)" + })?, + }.0); + tracing::debug!( + "extracted the following environment markers from the system python interpreter:\n{:#?}", + env_markers + ); + + let python_location = match args.python_interpreter { + Some(python_interpreter) => PythonLocation::Custom(python_interpreter), + None => PythonLocation::System, + }; + + let compatible_tags = + WheelTags::from_python(python_location.executable().into_diagnostic()?.as_path()) + .await + .into_diagnostic() + .map(Arc::new)?; + tracing::debug!( + "extracted the following compatible wheel tags from the system python interpreter: {}", + compatible_tags.tags().format(", ") + ); + + let on_wheel_build_failure = if args.save_on_failure { + OnWheelBuildFailure::SaveBuildEnv + } else { + OnWheelBuildFailure::DeleteBuildEnv + }; + + let pre_release_resolution = if args.pre { + PreReleaseResolution::Allow + } else { + PreReleaseResolution::from_specs(&args.specs) + }; + + let resolve_opts = ResolveOptions { + sdist_resolution: args.sdist_resolution.into(), + python_location: python_location.clone(), + clean_env: args.clean_env, + on_wheel_build_failure, + pre_release_resolution, + ..Default::default() + }; + + // Solve the environment + let blueprint = match rattler_installs_packages::resolve::resolve( + package_db.clone(), + &args.specs, + env_markers.clone(), + Some(compatible_tags.clone()), + HashMap::default(), + HashMap::default(), + resolve_opts.clone(), + HashMap::default(), + ) + .await + { + Ok(blueprint) => blueprint, + Err(err) => { + return if args.json { + let solution = Solution { + resolved: false, + packages: HashMap::default(), + error: Some(format!("{}", err)), + }; + println!("{}", serde_json::to_string_pretty(&solution).unwrap()); + return Ok(()); + } else { + Err(err.wrap_err("Could not solve for requested requirements")) + } + } + }; + + // Output the selected versions + println!( + "{}:", + console::style("Successfully resolved environment").bold() + ); + for spec in args.specs.iter() { + println!("- {}", spec); + } + + println!(); + let mut tabbed_stdout = tabwriter::TabWriter::new(std::io::stdout()); + writeln!( + tabbed_stdout, + "{}\t{}", + console::style("Name").bold(), + console::style("Version").bold() + ) + .into_diagnostic()?; + + for pinned_package in blueprint.iter().sorted_by(|a, b| a.name.cmp(&b.name)) { + write!(tabbed_stdout, "{name}", name = pinned_package.name.as_str()).into_diagnostic()?; + if !pinned_package.extras.is_empty() { + write!( + tabbed_stdout, + "[{}]", + pinned_package.extras.iter().map(|e| e.as_str()).join(",") + ) + .into_diagnostic()?; + } + writeln!( + tabbed_stdout, + "\t{version}", + version = pinned_package.version + ) + .into_diagnostic()?; + } + tabbed_stdout.flush().into_diagnostic()?; + + if args.json { + let solution = Solution { + resolved: true, + packages: blueprint + .iter() + .map(|p| (p.name.to_string(), p.version.to_string())) + .collect(), + error: None, + }; + println!("{}", serde_json::to_string_pretty(&solution).unwrap()); + } + + // Install if requested + if let Some(target) = target { + let wheel_builder = WheelBuilder::new( + package_db.clone(), + env_markers, + Some(compatible_tags), + resolve_opts, + Default::default(), + ) + .into_diagnostic()?; + + install_packages( + package_db, + wheel_builder, + blueprint, + python_location, + target, + ) + .await? + } + + Ok(()) +} + +/// Install resolved packages into a virtual environment +pub async fn install_packages( + package_db: Arc, + wheel_builder: WheelBuilder, + pinned_packages: Vec, + python_location: PythonLocation, + target: PathBuf, +) -> miette::Result<()> { + println!( + "\n\nInstalling into: {}", + console::style(target.display()).bold() + ); + if !target.exists() { + std::fs::create_dir_all(&target).into_diagnostic()?; + } + + let venv = rattler_installs_packages::python_env::VEnv::create(&target, python_location) + .into_diagnostic()?; + + let longest = pinned_packages + .iter() + .map(|p| p.name.as_str().len()) + .max() + .unwrap_or_default(); + let mut tabbed_stdout = tabwriter::TabWriter::new(std::io::stdout()).minwidth(longest); + + for pinned_package in pinned_packages + .clone() + .into_iter() + .sorted_by(|a, b| a.name.cmp(&b.name)) + { + writeln!( + tabbed_stdout, + "{name}\t{version}", + name = console::style(pinned_package.name).bold().green(), + version = console::style(pinned_package.version).italic() + ) + .into_diagnostic()?; + tabbed_stdout.flush().into_diagnostic()?; + // println!( + // "\ninstalling: {} - {}", + // console::style(pinned_package.name).bold().green(), + // console::style(pinned_package.version).italic() + // ); + let artifact_info = pinned_package.artifacts.first().unwrap(); + let (artifact, direct_url_json) = package_db + .get_wheel(artifact_info, Some(&wheel_builder)) + .await?; + venv.install_wheel( + &artifact, + &UnpackWheelOptions { + direct_url_json, + ..Default::default() + }, + ) + .into_diagnostic()?; + } + + println!( + "\n{}", + console::style("Successfully installed environment!").bold() + ); + + Ok(()) +} diff --git a/crates/rip_bin/src/cli/wheels.rs b/crates/rip_bin/src/cli/wheels.rs new file mode 100644 index 00000000..a3491b74 --- /dev/null +++ b/crates/rip_bin/src/cli/wheels.rs @@ -0,0 +1,33 @@ +use clap::{Parser, Subcommand}; +use miette::IntoDiagnostic; +use rattler_installs_packages::index::PackageDb; +use std::sync::Arc; + +#[derive(Parser)] +#[command(version, about, long_about = None)] +#[command(propagate_version = true)] +pub struct Args { + #[clap(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +pub enum Commands { + /// List locally built wheels + List, +} + +pub fn wheels(package_db: Arc, args: Args) -> miette::Result<()> { + match args.command { + Commands::List => list_wheels(package_db), + } +} + +fn list_wheels(package_db: Arc) -> miette::Result<()> { + let wheels = package_db.local_wheel_cache().wheels(); + for wheel in wheels { + println!("{}", wheel.into_diagnostic()?); + } + + Ok(()) +} diff --git a/crates/rip_bin/src/lib.rs b/crates/rip_bin/src/lib.rs index 7425a0cd..848ecdfd 100644 --- a/crates/rip_bin/src/lib.rs +++ b/crates/rip_bin/src/lib.rs @@ -3,6 +3,8 @@ use std::io; use std::sync::OnceLock; use tracing_subscriber::fmt::MakeWriter; +pub mod cli; + /// Returns a global instance of [`indicatif::MultiProgress`]. /// /// Although you can always create an instance yourself any logging will interrupt pending diff --git a/crates/rip_bin/src/main.rs b/crates/rip_bin/src/main.rs index 1f29a5f2..c2d11b0c 100644 --- a/crates/rip_bin/src/main.rs +++ b/crates/rip_bin/src/main.rs @@ -1,123 +1,51 @@ -use fs_err as fs; -use rattler_installs_packages::resolve::solve_options::{PreReleaseResolution, ResolveOptions}; -use rip_bin::{global_multi_progress, IndicatifWriter}; -use serde::Serialize; -use std::collections::HashMap; -use std::default::Default; -use std::io::Write; -use std::path::PathBuf; +use rip_bin::{cli, global_multi_progress, IndicatifWriter}; + use std::str::FromStr; use std::sync::Arc; -use clap::Parser; -use itertools::Itertools; -use miette::{Context, IntoDiagnostic}; +use clap::{Parser, Subcommand}; +use miette::Context; use tracing_subscriber::filter::Directive; use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; -use url::Url; -use rattler_installs_packages::artifacts::wheel::UnpackWheelOptions; use rattler_installs_packages::index::PackageSourcesBuilder; -use rattler_installs_packages::python_env::{PythonLocation, WheelTags}; -use rattler_installs_packages::resolve::solve_options::OnWheelBuildFailure; -use rattler_installs_packages::wheel_builder::WheelBuilder; -use rattler_installs_packages::{ - normalize_index_url, python_env::Pep508EnvMakers, resolve, resolve::resolve, types::Requirement, -}; -#[derive(Serialize, Debug)] -struct Solution { - resolved: bool, - packages: HashMap, - error: Option, -} +use rattler_installs_packages::normalize_index_url; +use reqwest::Client; +use reqwest_middleware::ClientWithMiddleware; +use rip_bin::cli::wheels::wheels; +use tracing::metadata::LevelFilter; +use url::Url; #[derive(Parser)] -#[command(author, version, about, long_about = None)] -struct Args { - #[clap(num_args = 1.., required = true)] - specs: Vec, +#[command(version, about, long_about = None)] +#[command(propagate_version = true)] +struct Cli { + /// Subcommand to run + #[command(subcommand)] + command: Commands, - /// Create a venv and install into this environment - /// Does not check for any installed packages for now - #[clap(long)] - install_into: Option, + /// Sets the logging level + #[command(flatten)] + verbose: clap_verbosity_flag::Verbosity, /// Base URL of the Python Package Index (default ). This should point /// to a repository compliant with PEP 503 (the simple repository API). - #[clap(default_value = "https://pypi.org/simple/", long)] + #[clap(default_value = "https://pypi.org/simple/", long, global = true)] index_url: Url, - - /// Verbose logging from resolvo - #[clap(short)] - verbose: bool, - - /// How to handle sidsts - #[clap(flatten)] - sdist_resolution: SDistResolutionArgs, - - /// Path to the python interpreter to use for resolving environment markers and creating venvs - #[clap(long, short)] - python_interpreter: Option, - - #[arg(short = 'c', long)] - /// Disable inheritance of env variables. - clean_env: bool, - - #[arg(long)] - /// Save failed wheel build environments - save_on_failure: bool, - - /// Prefer pre-releases over normal releases - #[clap(long)] - pre: bool, - - #[clap(long)] - json: bool, } -#[derive(Parser)] -#[group(multiple = false)] -struct SDistResolutionArgs { - /// Prefer any version with wheels over any version with sdists - #[clap(long)] - prefer_wheels: bool, - - /// Prefer any version with sdists over any version with wheels - #[clap(long)] - prefer_sdists: bool, +#[derive(Subcommand)] +enum Commands { + /// Options w.r.t locally built wheels + Wheels(cli::wheels::Args), - /// Only select versions with wheels, ignore versions with sdists - #[clap(long)] - only_wheels: bool, - - /// Only select versions with sdists, ignore versions with wheels - #[clap(long)] - only_sdists: bool, -} - -use resolve::solve_options::SDistResolution; -impl From for SDistResolution { - fn from(value: SDistResolutionArgs) -> Self { - if value.only_sdists { - SDistResolution::OnlySDists - } else if value.only_wheels { - SDistResolution::OnlyWheels - } else if value.prefer_sdists { - SDistResolution::PreferSDists - } else if value.prefer_wheels { - SDistResolution::PreferWheels - } else { - SDistResolution::Normal - } - } + #[command(flatten)] + InstallOrResolve(cli::resolve::Commands), } async fn actual_main() -> miette::Result<()> { - use reqwest::Client; - use reqwest_middleware::ClientWithMiddleware; - - let args = Args::parse(); + let args = Cli::parse(); // Setup tracing subscriber tracing_subscriber::registry() @@ -149,191 +77,10 @@ async fn actual_main() -> miette::Result<()> { })?, ); - // Determine the environment markers for the current machine - let env_markers = Arc::new(match args.python_interpreter { - Some(ref python) => { - let python = fs::canonicalize(python).into_diagnostic()?; - Pep508EnvMakers::from_python(&python).await.into_diagnostic() - .wrap_err_with(|| { - format!( - "failed to determine environment markers for the current machine (could not run Python in path: {:?})" - , python - ) - })? - } - None => Pep508EnvMakers::from_env().await.into_diagnostic() - .wrap_err_with(|| { - "failed to determine environment markers for the current machine (could not run Python)" - })?, - }.0); - tracing::debug!( - "extracted the following environment markers from the system python interpreter:\n{:#?}", - env_markers - ); - - let python_location = match args.python_interpreter { - Some(python_interpreter) => PythonLocation::Custom(python_interpreter), - None => PythonLocation::System, - }; - - let compatible_tags = - WheelTags::from_python(python_location.executable().into_diagnostic()?.as_path()) - .await - .into_diagnostic() - .map(Arc::new)?; - tracing::debug!( - "extracted the following compatible wheel tags from the system python interpreter: {}", - compatible_tags.tags().format(", ") - ); - - let on_wheel_build_failure = if args.save_on_failure { - OnWheelBuildFailure::SaveBuildEnv - } else { - OnWheelBuildFailure::DeleteBuildEnv - }; - - let pre_release_resolution = if args.pre { - PreReleaseResolution::Allow - } else { - PreReleaseResolution::from_specs(&args.specs) - }; - - let resolve_opts = ResolveOptions { - sdist_resolution: args.sdist_resolution.into(), - python_location: python_location.clone(), - clean_env: args.clean_env, - on_wheel_build_failure, - pre_release_resolution, - ..Default::default() - }; - - // Solve the environment - let blueprint = match resolve( - package_db.clone(), - &args.specs, - env_markers.clone(), - Some(compatible_tags.clone()), - HashMap::default(), - HashMap::default(), - resolve_opts.clone(), - HashMap::default(), - ) - .await - { - Ok(blueprint) => blueprint, - Err(err) => { - return if args.json { - let solution = Solution { - resolved: false, - packages: HashMap::default(), - error: Some(format!("{}", err)), - }; - println!("{}", serde_json::to_string_pretty(&solution).unwrap()); - Ok(()) - } else { - Err(err.wrap_err("Could not solve for requested requirements")) - } - } - }; - - // Output the selected versions - println!("{}:", console::style("Resolved environment").bold()); - for spec in args.specs.iter() { - println!("- {}", spec); - } - - println!(); - let mut tabbed_stdout = tabwriter::TabWriter::new(std::io::stdout()); - writeln!( - tabbed_stdout, - "{}\t{}", - console::style("Name").bold(), - console::style("Version").bold() - ) - .into_diagnostic()?; - for pinned_package in blueprint.iter().sorted_by(|a, b| a.name.cmp(&b.name)) { - write!(tabbed_stdout, "{name}", name = pinned_package.name.as_str()).into_diagnostic()?; - if !pinned_package.extras.is_empty() { - write!( - tabbed_stdout, - "[{}]", - pinned_package.extras.iter().map(|e| e.as_str()).join(",") - ) - .into_diagnostic()?; - } - writeln!( - tabbed_stdout, - "\t{version}", - version = pinned_package.version - ) - .into_diagnostic()?; + match args.command { + Commands::InstallOrResolve(cmds) => cli::resolve::execute(package_db.clone(), cmds).await, + Commands::Wheels(args) => wheels(package_db.clone(), args), } - tabbed_stdout.flush().into_diagnostic()?; - - // Try to install into this environment - if let Some(install) = args.install_into { - println!( - "\n\nInstalling into: {}", - console::style(install.display()).bold() - ); - if !install.exists() { - std::fs::create_dir_all(&install).into_diagnostic()?; - } - - let venv = rattler_installs_packages::python_env::VEnv::create(&install, python_location) - .into_diagnostic()?; - let wheel_builder = WheelBuilder::new( - package_db.clone(), - env_markers, - Some(compatible_tags), - resolve_opts, - Default::default(), - ) - .into_diagnostic()?; - - for pinned_package in blueprint - .clone() - .into_iter() - .sorted_by(|a, b| a.name.cmp(&b.name)) - { - println!( - "\ninstalling: {} - {}", - console::style(pinned_package.name).bold().green(), - console::style(pinned_package.version).italic() - ); - let artifact_info = pinned_package.artifacts.first().unwrap(); - let (artifact, direct_url_json) = package_db - .get_wheel(artifact_info, Some(&wheel_builder)) - .await?; - venv.install_wheel( - &artifact, - &UnpackWheelOptions { - direct_url_json, - ..Default::default() - }, - ) - .into_diagnostic()?; - } - } - - println!( - "\n{}", - console::style("Successfully installed environment!").bold() - ); - - if args.json { - let solution = Solution { - resolved: true, - packages: blueprint - .into_iter() - .map(|p| (p.name.to_string(), p.version.to_string())) - .collect(), - error: None, - }; - println!("{}", serde_json::to_string_pretty(&solution).unwrap()); - } - - Ok(()) } #[tokio::main] @@ -344,13 +91,23 @@ async fn main() { } /// Constructs a default [`EnvFilter`] that is used when the user did not specify a custom RUST_LOG. -pub fn get_default_env_filter(verbose: bool) -> EnvFilter { - let mut result = EnvFilter::new("rip=info") - .add_directive(Directive::from_str("rattler_installs_packages=info").unwrap()); - - if verbose { - result = result.add_directive(Directive::from_str("resolvo=info").unwrap()); - } +pub fn get_default_env_filter(verbose: clap_verbosity_flag::Verbosity) -> EnvFilter { + // Always log info for rattler_installs_packages + let (rip, rest) = match verbose.log_level_filter() { + clap_verbosity_flag::LevelFilter::Off => (LevelFilter::OFF, LevelFilter::OFF), + clap_verbosity_flag::LevelFilter::Error => (LevelFilter::INFO, LevelFilter::ERROR), + clap_verbosity_flag::LevelFilter::Warn => (LevelFilter::INFO, LevelFilter::WARN), + clap_verbosity_flag::LevelFilter::Info => (LevelFilter::INFO, LevelFilter::INFO), + clap_verbosity_flag::LevelFilter::Debug => (LevelFilter::DEBUG, LevelFilter::DEBUG), + clap_verbosity_flag::LevelFilter::Trace => (LevelFilter::TRACE, LevelFilter::TRACE), + }; - result + EnvFilter::builder() + .with_default_directive(rest.into()) + .from_env() + .expect("failed to get env filter") + .add_directive( + Directive::from_str(&format!("rattler_installs_packages={}", rip)) + .expect("cannot parse directive"), + ) } diff --git a/end_to_end_tests/test_endtoend.py b/end_to_end_tests/test_endtoend.py index bc9417a1..0d6f6b89 100644 --- a/end_to_end_tests/test_endtoend.py +++ b/end_to_end_tests/test_endtoend.py @@ -89,7 +89,7 @@ def __init__(self, path): def __call__(self, *args: Any, **kwds: Any) -> Any: try: - return check_output([str(self.path), *args], **kwds).decode("utf-8") + return check_output([str(self.path), "resolve", *args], **kwds).decode("utf-8") except CalledProcessError as e: print(e.output) print(e.stderr) @@ -139,7 +139,7 @@ def rip(): def test_functionality(rip: rip): text = rip("--help").splitlines() - assert text[0] == "Binary to verify and play around with rattler_installs_packages" + assert text[0] == "Resolve a set of requirements and output the resolved versions" def test_solve(rip: rip):