Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: project model using targets, features and environments #616

Merged
merged 7 commits into from
Jan 6, 2024
Merged
Prev Previous commit
Next Next commit
Merge remote-tracking branch 'upstream/main' into refactor/project_model
  • Loading branch information
baszalmstra committed Jan 6, 2024

Verified

This commit was signed with the committer’s verified signature.
targos Michaël Zasso
commit 384f082a605735b87ec69e8ae3dac14e0e15a155
95 changes: 65 additions & 30 deletions src/cli/remove.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
use std::path::PathBuf;
use std::str::FromStr;

use clap::Parser;
use rattler_conda_types::{PackageName, Platform};
use miette::miette;
use rattler_conda_types::Platform;

use crate::environment::LockFileUsage;
use crate::{environment::get_up_to_date_prefix, project::SpecType, Project};
use crate::{consts, environment::get_up_to_date_prefix, project::SpecType, Project};

/// Remove the dependency from the project
#[derive(Debug, Default, Parser)]
pub struct Args {
/// List of dependencies you wish to remove from the project
#[arg(required = true)]
pub deps: Vec<PackageName>,
pub deps: Vec<String>,

/// The path to 'pixi.toml'
#[arg(long)]
@@ -25,11 +27,27 @@ pub struct Args {
#[arg(long, conflicts_with = "host")]
pub build: bool,

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

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

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 mut project = Project::load_or_else_discover(args.manifest_path.as_deref())?;
let deps = args.deps;
@@ -41,39 +59,56 @@ pub async fn execute(args: Args) -> miette::Result<()> {
SpecType::Run
};

let results = deps
.iter()
.map(|dep| {
project
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
};

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::<rip::types::PackageName>(&deps)?;
for dep in all_pkg_name.iter() {
let (name, req) = project
.manifest
.remove_dependency(dep, spec_type, args.platform)
})
.collect::<Vec<_>>();
.remove_pypi_dependency(dep, args.platform)?;
sucessful_output.push(format_ok_message(
name.as_str(),
&req.to_string(),
&table_name,
));
}
} else {
let all_pkg_name = convert_pkg_name::<rattler_conda_types::PackageName>(&deps)?;
for dep in all_pkg_name.iter() {
let (name, req) = project
.manifest
.remove_dependency(dep, spec_type, args.platform)?;
sucessful_output.push(format_ok_message(
name.as_source(),
&req.to_string(),
&table_name,
));
}
};

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

// updating prefix after removing from toml
let _ = get_up_to_date_prefix(&project, LockFileUsage::Update, false, None).await?;

for (removed, spec) in results.iter().flatten() {
let table_name = if let Some(p) = &args.platform {
format!("target.{}.{}", p.as_str(), spec_type.name())
} else {
spec_type.name().to_string()
};

eprintln!(
"Removed {} from [{}]",
console::style(format!("{} {spec}", removed.as_source())).bold(),
console::style(table_name).bold(),
);
}

for result in &results {
if let Err(e) = result {
eprintln!("{e}");
}
}

Ok(())
}
1 change: 1 addition & 0 deletions src/consts.rs
Original file line number Diff line number Diff line change
@@ -3,3 +3,4 @@ pub const PROJECT_LOCK_FILE: &str = "pixi.lock";
pub const PIXI_DIR: &str = ".pixi";
pub const PREFIX_FILE_NAME: &str = "prefix";
pub const ENVIRONMENT_DIR: &str = "env";
pub const PYPI_DEPENDENCIES: &str = "pypi-dependencies";
25 changes: 2 additions & 23 deletions src/lock_file/satisfiability.rs
Original file line number Diff line number Diff line change
@@ -1,41 +1,20 @@
use super::package_identifier;
use crate::project::{DependencyKind, DependencyName};
use crate::{
lock_file::pypi::{determine_marker_environment, is_python_record},
Project,
};
use itertools::Itertools;
use miette::IntoDiagnostic;
use pep508_rs::Requirement;
use rattler_conda_types::{MatchSpec, PackageName, Platform, Version};
use rattler_conda_types::{MatchSpec, Platform, Version};
use rattler_lock::{CondaLock, LockedDependency, LockedDependencyKind};
use rip::types::NormalizedPackageName;
use std::{
collections::{HashMap, HashSet, VecDeque},
fmt::{Display, Formatter},
str::FromStr,
};

#[derive(Clone)]
enum DependencyKind {
Conda(MatchSpec),
PyPi(pep508_rs::Requirement),
}

impl Display for DependencyKind {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
DependencyKind::Conda(spec) => write!(f, "{}", spec),
DependencyKind::PyPi(req) => write!(f, "{}", req),
}
}
}

#[derive(Eq, PartialEq, Hash)]
enum DependencyName {
Conda(PackageName),
PyPi(NormalizedPackageName),
}

/// Returns true if the locked packages match the dependencies in the project.
pub fn lock_file_satisfies_project(
project: &Project,
116 changes: 108 additions & 8 deletions src/project/manifest/mod.rs
Original file line number Diff line number Diff line change
@@ -6,9 +6,10 @@ mod metadata;
mod system_requirements;
mod target;

use crate::consts::PYPI_DEPENDENCIES;
use crate::{
consts,
project::{manifest::target::Targets, python::PyPiRequirement, DependencyType, SpecType},
project::{manifest::target::Targets, python::PyPiRequirement, SpecType},
task::Task,
utils::spanned::PixiSpanned,
};
@@ -285,11 +286,8 @@ impl Manifest {
platform: Option<Platform>,
) -> miette::Result<()> {
// Find the table toml table to add the dependency to.
let dependency_table = ensure_toml_target_table(
&mut self.document,
platform,
DependencyType::PypiDependency.name(),
)?;
let dependency_table =
ensure_toml_target_table(&mut self.document, platform, PYPI_DEPENDENCIES)?;

// Add the pypi dependency to the table
dependency_table.insert(name.as_str(), (*requirement).clone().into());
@@ -315,10 +313,15 @@ impl Manifest {
get_toml_target_table(&mut self.document, platform, spec_type.name())?
.remove(dep.as_normalized())
.ok_or_else(|| {
let table_name = match platform {
Some(platform) => format!("target.{}.{}", platform.as_str(), spec_type.name()),
None => spec_type.name().to_string(),
};

miette::miette!(
"Couldn't find {} in [{}]",
console::style(dep.as_normalized()).bold(),
console::style(spec_type.name()).bold(),
console::style(dep.as_source()).bold(),
console::style(table_name).bold(),
)
})?;

@@ -331,6 +334,39 @@ impl Manifest {
.expect("dependency should exist"))
}

/// Removes a pypi dependency from `pixi.toml`.
pub fn remove_pypi_dependency(
&mut self,
dep: &rip::types::PackageName,
platform: Option<Platform>,
) -> miette::Result<(rip::types::PackageName, PyPiRequirement)> {
get_toml_target_table(&mut self.document, platform, PYPI_DEPENDENCIES)?
.remove(dep.as_str())
.ok_or_else(|| {
let table_name = match platform {
Some(platform) => format!("target.{}.{}", platform.as_str(), PYPI_DEPENDENCIES),
None => PYPI_DEPENDENCIES.to_string(),
};

miette::miette!(
"Couldn't find {} in [{}]",
console::style(dep.as_source_str()).bold(),
console::style(table_name).bold(),
)
})?;

Ok(self
.default_feature_mut()
.targets
.for_opt_target_mut(platform.map(TargetSelector::Platform).as_ref())
.expect("target should exist")
.pypi_dependencies
.as_mut()
.expect("pypi-dependencies should exist")
.shift_remove_entry(dep)
.expect("dependency should exist"))
}

/// Returns true if any of the features has pypi dependencies defined.
///
/// This also returns true if the `pypi-dependencies` key is defined but empty.
@@ -1128,6 +1164,70 @@ mod test {
assert_display_snapshot!(manifest.document.to_string());
}

fn test_remove_pypi(file_contents: &str, name: &str, platform: Option<Platform>) {
let mut manifest = Manifest::from_str(Path::new(""), file_contents).unwrap();

let name = rip::types::PackageName::from_str(name).unwrap();

// Initially the dependency should exist
assert!(manifest
.default_feature()
.targets
.for_opt_target(platform.map(TargetSelector::Platform).as_ref())
.unwrap()
.pypi_dependencies
.as_ref()
.unwrap()
.get(&name)
.is_some());

// Remove the dependency from the manifest
manifest.remove_pypi_dependency(&name, platform).unwrap();

// The dependency should no longer exist
assert!(manifest
.default_feature()
.targets
.for_opt_target(platform.map(TargetSelector::Platform).as_ref())
.unwrap()
.pypi_dependencies
.as_ref()
.unwrap()
.get(&name)
.is_none());

// Write the toml to string and verify the content
assert_display_snapshot!(manifest.document.to_string());
}

#[test]
fn test_remove_pypi_dependencies() {
let pixi_cfg = r#"[project]
name = "pixi_fun"
version = "0.1.0"
channels = []
platforms = ["linux-64", "win-64"]

[dependencies]
python = ">=3.12.1,<3.13"

[pypi-dependencies]
requests = "*"

[target.win-64.pypi-dependencies]
jax = { version = "*", extras = ["cpu"] }
requests = "*"

[target.linux-64.pypi-dependencies]
xpackage = "==1.2.3"
ypackage = {version = ">=1.2.3"}
"#;

test_remove_pypi(pixi_cfg, "xpackage", Some(Platform::Linux64));
test_remove_pypi(pixi_cfg, "jax", Some(Platform::Win64));
test_remove_pypi(pixi_cfg, "requests", None);
}

#[test]
fn test_remove_target_dependencies() {
// Using known files in the project so the test succeed including the file check.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
source: src/project/manifest/mod.rs
expression: manifest.document.to_string()
---
[project]
name = "pixi_fun"
version = "0.1.0"
channels = []
platforms = ["linux-64", "win-64"]

[dependencies]
python = ">=3.12.1,<3.13"

[pypi-dependencies]
requests = "*"

[target.win-64.pypi-dependencies]
requests = "*"

[target.linux-64.pypi-dependencies]
xpackage = "==1.2.3"
ypackage = {version = ">=1.2.3"}

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
source: src/project/manifest/mod.rs
expression: manifest.document.to_string()
---
[project]
name = "pixi_fun"
version = "0.1.0"
channels = []
platforms = ["linux-64", "win-64"]

[dependencies]
python = ">=3.12.1,<3.13"

[pypi-dependencies]

[target.win-64.pypi-dependencies]
jax = { version = "*", extras = ["cpu"] }
requests = "*"

[target.linux-64.pypi-dependencies]
xpackage = "==1.2.3"
ypackage = {version = ">=1.2.3"}

Loading
Loading
You are viewing a condensed version of this merge commit. You can view the full changes here.