Skip to content

Commit

Permalink
feat: Align remove arguments with add (prefix-dev#1406)
Browse files Browse the repository at this point in the history
  • Loading branch information
olivier-lacroix authored May 21, 2024
1 parent 95176bc commit ebbeb29
Show file tree
Hide file tree
Showing 16 changed files with 430 additions and 528 deletions.
310 changes: 142 additions & 168 deletions src/cli/add.rs

Large diffs are not rendered by default.

19 changes: 1 addition & 18 deletions src/cli/global/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ use std::path::PathBuf;
use indexmap::IndexMap;
use miette::IntoDiagnostic;
use rattler_conda_types::{
Channel, ChannelConfig, MatchSpec, PackageName, ParseStrictness, Platform, PrefixRecord,
RepoDataRecord,
Channel, ChannelConfig, MatchSpec, PackageName, Platform, PrefixRecord, RepoDataRecord,
};
use rattler_repodata_gateway::sparse::SparseRepoData;
use rattler_solve::{resolvo, ChannelPriority, SolverImpl, SolverTask};
Expand All @@ -17,22 +16,6 @@ use crate::{
utils::reqwest::build_reqwest_clients,
};

/// A trait to facilitate extraction of packages data from arguments
pub(super) trait HasSpecs {
/// returns packages passed as arguments to the command
fn packages(&self) -> Vec<&str>;

fn specs(&self) -> miette::Result<IndexMap<PackageName, MatchSpec>> {
let mut map = IndexMap::with_capacity(self.packages().len());
for package in self.packages() {
let spec = MatchSpec::from_str(package, ParseStrictness::Strict).into_diagnostic()?;
let name = package_name(&spec)?;
map.insert(name, spec);
}

Ok(map)
}
}
/// Global binaries directory, default to `$HOME/.pixi/bin`
pub struct BinDir(pub PathBuf);

Expand Down
3 changes: 2 additions & 1 deletion src/cli/global/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::sync::Arc;

use crate::cli::has_specs::HasSpecs;
use crate::config::{Config, ConfigCli};
use crate::install::execute_transaction;
use crate::{config, prefix::Prefix, progress::await_in_progress};
Expand All @@ -20,7 +21,7 @@ use reqwest_middleware::ClientWithMiddleware;

use super::common::{
channel_name_from_prefix, find_designated_package, get_client_and_sparse_repodata,
load_package_records, BinDir, BinEnvDir, HasSpecs,
load_package_records, BinDir, BinEnvDir,
};

/// Installs the defined package in a global accessible location.
Expand Down
3 changes: 2 additions & 1 deletion src/cli/global/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ use itertools::Itertools;
use miette::IntoDiagnostic;
use rattler_conda_types::PackageName;

use crate::cli::has_specs::HasSpecs;
use crate::prefix::Prefix;

use super::common::{find_designated_package, BinDir, BinEnvDir, HasSpecs};
use super::common::{find_designated_package, BinDir, BinEnvDir};
use super::install::{find_and_map_executable_scripts, BinScriptMapping};

/// Removes a package previously installed into a globally accessible location via `pixi global install`.
Expand Down
5 changes: 2 additions & 3 deletions src/cli/global/upgrade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@ use miette::{IntoDiagnostic, Report};
use rattler_conda_types::{Channel, MatchSpec, PackageName, Platform};
use tokio::task::JoinSet;

use crate::cli::has_specs::HasSpecs;
use crate::config::Config;
use crate::progress::{global_multi_progress, long_running_progress_style};

use super::common::{
find_installed_package, get_client_and_sparse_repodata, load_package_records, HasSpecs,
};
use super::common::{find_installed_package, get_client_and_sparse_repodata, load_package_records};
use super::install::globally_install_package;

/// Upgrade specific package which is installed globally.
Expand Down
37 changes: 37 additions & 0 deletions src/cli/has_specs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use indexmap::IndexMap;
use miette::IntoDiagnostic;
use pep508_rs::Requirement;
use rattler_conda_types::{MatchSpec, PackageName, ParseStrictness};

use crate::{project::manifest::python::PyPiPackageName, Project};

/// A trait to facilitate extraction of packages data from arguments
pub(crate) trait HasSpecs {
/// returns packages passed as arguments to the command
fn packages(&self) -> Vec<&str>;

fn specs(&self) -> miette::Result<IndexMap<PackageName, MatchSpec>> {
let mut map = IndexMap::with_capacity(self.packages().len());
for package in self.packages() {
let spec = MatchSpec::from_str(package, ParseStrictness::Strict).into_diagnostic()?;
let name = spec.name.clone().ok_or_else(|| {
miette::miette!("could not find package name in MatchSpec {}", spec)
})?;
map.insert(name, spec);
}
Ok(map)
}

fn pypi_deps(
&self,
project: &Project,
) -> miette::Result<IndexMap<PyPiPackageName, Requirement>> {
let mut map = IndexMap::with_capacity(self.packages().len());
for package in self.packages() {
let dep = Requirement::parse(package, project.root()).into_diagnostic()?;
let name = PyPiPackageName::from_normalized(dep.clone().name);
map.insert(name, dep);
}
Ok(map)
}
}
34 changes: 17 additions & 17 deletions src/cli/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,26 +179,26 @@ pub async fn execute(args: Args) -> miette::Result<()> {
&vec![],
);
let mut project = Project::from_str(&pixi_manifest_path, &rv)?;
let platforms = platforms
.into_iter()
.map(|p| p.parse().into_diagnostic())
.collect::<Result<Vec<Platform>, _>>()?;
for spec in conda_deps {
for platform in platforms.iter() {
// TODO: fix serialization of channels in rattler_conda_types::MatchSpec
project.manifest.add_dependency(
&spec,
crate::SpecType::Run,
Some(platform.parse().into_diagnostic()?),
&FeatureName::default(),
)?;
}
// TODO: fix serialization of channels in rattler_conda_types::MatchSpec
project.manifest.add_dependency(
&spec,
crate::SpecType::Run,
&platforms,
&FeatureName::default(),
)?;
}
for requirement in pypi_deps {
for platform in platforms.iter() {
project.manifest.add_pypi_dependency(
&requirement,
Some(platform.parse().into_diagnostic()?),
&FeatureName::default(),
None,
)?;
}
project.manifest.add_pypi_dependency(
&requirement,
&platforms,
&FeatureName::default(),
None,
)?;
}
project.save()?;

Expand Down
1 change: 1 addition & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub mod add;
pub mod completion;
pub mod config;
pub mod global;
pub mod has_specs;
pub mod info;
pub mod init;
pub mod install;
Expand Down
162 changes: 38 additions & 124 deletions src/cli/remove.rs
Original file line number Diff line number Diff line change
@@ -1,156 +1,70 @@
use std::path::PathBuf;
use std::str::FromStr;

use clap::Parser;
use miette::miette;
use pep508_rs::Requirement;
use rattler_conda_types::Platform;

use crate::config::ConfigCli;
use crate::environment::{get_up_to_date_prefix, LockFileUsage};
use crate::project::manifest::python::PyPiPackageName;
use crate::project::manifest::FeatureName;
use crate::{consts, project::SpecType, Project};
use crate::environment::get_up_to_date_prefix;
use crate::DependencyType;
use crate::Project;

use super::add::DependencyConfig;
use super::has_specs::HasSpecs;

/// Removes dependencies from the project
///
/// If the project manifest is a `pyproject.toml`, removing a pypi dependency with the `--pypi` flag will remove it from either
/// - the native pyproject `project.dependencies` array or, if a feature is specified, the native `project.optional-dependencies` table
/// - pixi `pypi-dependencies` tables of the default feature or, if a feature is specified, a named feature
///
#[derive(Debug, Default, Parser)]
#[clap(arg_required_else_help = true)]
pub struct Args {
/// Specify the dependencies you wish to remove from the project.
///
/// If the project manifest is a `pyproject.toml`, removing a pypi dependency with the `--pypi` flag will remove it from either
/// - the native pyproject `project.dependencies` array or, if a feature is specified, the native `project.optional-dependencies` table
/// - pixi `pypi-dependencies` tables of the default feature or, if a feature is specified, a named feature
///
#[arg(required = true, verbatim_doc_comment)]
pub deps: Vec<String>,

/// The path to 'pixi.toml' or 'pyproject.toml'
#[arg(long)]
pub manifest_path: Option<PathBuf>,

/// Whether dependency is a host dependency
#[arg(long, conflicts_with = "build")]
pub host: bool,

/// Whether dependency is a build dependency
#[arg(long, conflicts_with = "host")]
pub build: bool,

/// Whether the dependency is a pypi package
#[arg(long)]
pub pypi: bool,

/// Don't install the environment, only remove the package from the lock-file and manifest.
#[arg(long)]
pub no_install: bool,

/// The platform for which the dependency should be removed
#[arg(long, short)]
pub platform: Option<Platform>,

/// The feature for which the dependency should be removed
#[arg(long, short)]
pub feature: Option<String>,
#[clap(flatten)]
pub dependency_config: DependencyConfig,

#[clap(flatten)]
pub config: ConfigCli,
}

fn convert_pkg_name<T>(deps: &[String]) -> miette::Result<Vec<T>>
where
T: FromStr,
{
deps.iter()
.map(|dep| {
T::from_str(dep)
.map_err(|_| miette!("Can't convert dependency name `{dep}` to package name"))
})
.collect()
}

pub async fn execute(args: Args) -> miette::Result<()> {
let (args, config) = (args.dependency_config, args.config);
let mut project =
Project::load_or_else_discover(args.manifest_path.as_deref())?.with_cli_config(args.config);
let deps = args.deps;
let spec_type = if args.host {
SpecType::Host
} else if args.build {
SpecType::Build
} else {
SpecType::Run
};

let section_name: String = if args.pypi {
consts::PYPI_DEPENDENCIES.to_string()
} else {
spec_type.name().to_string()
};
let table_name = if let Some(p) = &args.platform {
format!("target.{}.{}", p.as_str(), section_name)
} else {
section_name
};
let feature_name = args
.feature
.map_or(FeatureName::Default, FeatureName::Named);

fn format_ok_message(pkg_name: &str, pkg_extras: &str, table_name: &str) -> String {
format!(
"Removed {} from [{}]",
console::style(format!("{pkg_name} {pkg_extras}")).bold(),
console::style(table_name).bold()
)
}
let mut sucessful_output: Vec<String> = Vec::with_capacity(deps.len());
if args.pypi {
let all_pkg_name = convert_pkg_name::<Requirement>(&deps)?;
for dep in all_pkg_name.iter() {
let name = PyPiPackageName::from_normalized(dep.clone().name);
let (name, req) =
project
.manifest
.remove_pypi_dependency(&name, args.platform, &feature_name)?;
sucessful_output.push(format_ok_message(
name.as_source(),
&req.to_string(),
&table_name,
));
Project::load_or_else_discover(args.manifest_path.as_deref())?.with_cli_config(config);
let dependency_type = args.dependency_type();

match dependency_type {
DependencyType::PypiDependency => {
for name in args.pypi_deps(&project)?.keys() {
project.manifest.remove_pypi_dependency(
name,
&args.platform,
&args.feature_name(),
)?;
}
}
} else {
let all_pkg_name = convert_pkg_name::<rattler_conda_types::MatchSpec>(&deps)?;
for dep in all_pkg_name.iter() {
// Get name or error on missing name
let name = dep
.clone()
.name
.ok_or_else(|| miette!("Can't remove dependency without a name: {}", dep))?;
let (name, req) = project.manifest.remove_dependency(
&name,
spec_type,
args.platform,
&feature_name,
)?;
sucessful_output.push(format_ok_message(
name.as_source(),
&req.to_string(),
&table_name,
));
DependencyType::CondaDependency(spec_type) => {
for name in args.specs()?.keys() {
project.manifest.remove_dependency(
name,
spec_type,
&args.platform,
&args.feature_name(),
)?;
}
}
};

project.save()?;
eprintln!("{}", sucessful_output.join("\n"));

// TODO: update all environments touched by this feature defined.
// updating prefix after removing from toml
get_up_to_date_prefix(
&project.default_environment(),
LockFileUsage::Update,
args.lock_file_usage(),
args.no_install,
)
.await?;

args.display_success("Removed");

Project::warn_on_discovered_from_env(args.manifest_path.as_deref());
Ok(())
}
Loading

0 comments on commit ebbeb29

Please sign in to comment.