From c30640eef5988f504247c900fd1cec6400445413 Mon Sep 17 00:00:00 2001 From: Tarun Pratap Singh <101409098+Wackyator@users.noreply.github.com> Date: Thu, 22 Feb 2024 13:56:44 +0530 Subject: [PATCH] feat: add import yml flag (#792) Co-authored-by: Bas Zalmstra Co-authored-by: Ruben Arts --- Cargo.lock | 1 + Cargo.toml | 1 + docs/cli.md | 6 + src/cli/init.rs | 377 ++++- ...test_import_from_env_yaml.cockpit.yml.snap | 1312 +++++++++++++++++ ...__test_import_from_env_yaml.conda.yml.snap | 68 + ..._test_import_from_env_yaml.crnnft.yml.snap | 221 +++ ...est_import_from_env_yaml.let-plot.yml.snap | 257 ++++ ...est_import_from_env_yaml.test_env.yml.snap | 511 +++++++ src/utils/conda_environment_file.rs | 40 + src/utils/mod.rs | 1 + tests/common/builders.rs | 4 +- tests/common/mod.rs | 2 + tests/environment_yamls/cockpit.yml | 29 + tests/environment_yamls/conda.yml | 3 + tests/environment_yamls/crnnft.yml | 14 + tests/environment_yamls/let-plot.yml | 18 + tests/environment_yamls/test_env.yml | 23 + 18 files changed, 2850 insertions(+), 38 deletions(-) create mode 100644 src/cli/snapshots/pixi__cli__init__tests__test_import_from_env_yaml.cockpit.yml.snap create mode 100644 src/cli/snapshots/pixi__cli__init__tests__test_import_from_env_yaml.conda.yml.snap create mode 100644 src/cli/snapshots/pixi__cli__init__tests__test_import_from_env_yaml.crnnft.yml.snap create mode 100644 src/cli/snapshots/pixi__cli__init__tests__test_import_from_env_yaml.let-plot.yml.snap create mode 100644 src/cli/snapshots/pixi__cli__init__tests__test_import_from_env_yaml.test_env.yml.snap create mode 100644 src/utils/conda_environment_file.rs create mode 100644 tests/environment_yamls/cockpit.yml create mode 100644 tests/environment_yamls/conda.yml create mode 100644 tests/environment_yamls/crnnft.yml create mode 100644 tests/environment_yamls/let-plot.yml create mode 100644 tests/environment_yamls/test_env.yml diff --git a/Cargo.lock b/Cargo.lock index ec164a32e..8a3d916c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2726,6 +2726,7 @@ dependencies = [ "serde_json", "serde_spanned", "serde_with", + "serde_yaml", "serial_test", "shlex", "signal-hook", diff --git a/Cargo.toml b/Cargo.toml index 94b38f081..20b652b5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,7 @@ serde-untagged = "0.1.5" serde_json = "1.0.113" serde_spanned = "0.6.5" serde_with = { version = "3.6.1", features = ["indexmap"] } +serde_yaml = "0.9.31" shlex = "1.3.0" spdx = "0.10.3" strsim = "0.10.0" diff --git a/docs/cli.md b/docs/cli.md index 8dc43e9f62..71dc05f10 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -28,6 +28,11 @@ It initializes a `pixi.toml` file and also prepares a `.gitignore` to prevent th - `--channel (-c)`: specify a channel that the project uses. Defaults to `conda-forge`. (Allowed to be used more than once) - `--platform (-p)`: specify a platform that the project supports. (Allowed to be used more than once) +- `--import (-i)`: Import an existing conda environment file, e.g. `environment.yml`. +!!! info "Importing an environment.yml" + When importing an environment, the `pixi.toml` will be created with the dependencies from the environment file. + The `pixi.lock` will be created when you install the environment. + We don't support `git+` urls as dependencies for pip packages and for the `defaults` channel we use `main`, `r` and `msys2` as the default channels. ```shell pixi init myproject @@ -35,6 +40,7 @@ pixi init ~/myproject pixi init # Initializes directly in the current directory. pixi init --channel conda-forge --channel bioconda myproject pixi init --platform osx-64 --platform linux-64 myproject +pixi init --import environment.yml ``` ## `add` diff --git a/src/cli/init.rs b/src/cli/init.rs index 3e71d8cc1..f022f42c2 100644 --- a/src/cli/init.rs +++ b/src/cli/init.rs @@ -1,10 +1,20 @@ +use crate::environment::{get_up_to_date_prefix, LockFileUsage}; +use crate::project::manifest::PyPiRequirement; +use crate::utils::conda_environment_file::{CondaEnvDep, CondaEnvFile}; +use crate::Project; use crate::{config::get_default_author, consts}; use clap::Parser; +use indexmap::IndexMap; +use itertools::Itertools; use miette::IntoDiagnostic; use minijinja::{context, Environment}; -use rattler_conda_types::Platform; +use rattler_conda_types::{Channel, ChannelConfig, MatchSpec, Platform}; +use regex::Regex; +use rip::types::PackageName; use std::io::{Error, ErrorKind, Write}; use std::path::Path; +use std::str::FromStr; +use std::sync::Arc; use std::{fs, path::PathBuf}; /// Creates a new project @@ -15,12 +25,16 @@ pub struct Args { pub path: PathBuf, /// Channels to use in the project. - #[arg(short, long = "channel", id = "channel")] + #[arg(short, long = "channel", id = "channel", conflicts_with = "env_file")] pub channels: Option>, /// Platforms that the project supports. #[arg(short, long = "platform", id = "platform")] pub platforms: Vec, + + /// Environment.yml file to bootstrap the project. + #[arg(short = 'i', long = "import")] + pub env_file: Option, } /// The default channels to use for a new project. @@ -70,48 +84,115 @@ pub async fn execute(args: Args) -> miette::Result<()> { // Fail silently if it already exists or cannot be created. fs::create_dir_all(&dir).ok(); - // Write pixi.toml - let name = dir - .file_name() - .ok_or_else(|| { - miette::miette!( - "Cannot get file or directory name from the path: {}", - dir.to_string_lossy() - ) - })? - .to_string_lossy(); let version = "0.1.0"; let author = get_default_author(); - let channels = if let Some(channels) = args.channels { - channels - } else { - DEFAULT_CHANNELS - .iter() - .copied() - .map(ToOwned::to_owned) - .collect() - }; - let platforms = if args.platforms.is_empty() { vec![Platform::current().to_string()] } else { - args.platforms + args.platforms.clone() }; - let rv = env - .render_named_str( - consts::PROJECT_MANIFEST, - PROJECT_TEMPLATE, - context! { - name, - version, - author, - channels, - platforms - }, + // If env file load that else use default template only + if let Some(env_file) = args.env_file { + let conda_env_file = CondaEnvFile::from_path(&env_file)?; + + let name = match conda_env_file.name() { + // Default to something to avoid errors + None => get_name_from_dir(&dir).unwrap_or_else(|_| String::from("new_project")), + Some(name) => name.to_string(), + }; + + // TODO: Improve this: + // - Use .condarc as channel config + // - Implement it for `[crate::project::manifest::ProjectManifest]` to do this for other filetypes, e.g. (pyproject.toml, requirements.txt) + let (conda_deps, pypi_deps, channels) = conda_env_to_manifest(conda_env_file)?; + + let rv = env + .render_named_str( + consts::PROJECT_MANIFEST, + PROJECT_TEMPLATE, + context! { + name, + version, + author, + channels, + platforms + }, + ) + .unwrap(); + + let mut project = Project::from_str(&dir, &rv)?; + for spec in conda_deps { + match &args.platforms.is_empty() { + true => project + .manifest + .add_dependency(&spec, crate::SpecType::Run, None)?, + false => { + for platform in args.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()?), + )?; + } + } + } + } + for spec in pypi_deps { + match &args.platforms.is_empty() { + true => project + .manifest + .add_pypi_dependency(&spec.0, &spec.1, None)?, + false => { + for platform in args.platforms.iter() { + project.manifest.add_pypi_dependency( + &spec.0, + &spec.1, + Some(platform.parse().into_diagnostic()?), + )?; + } + } + } + } + project.save()?; + + get_up_to_date_prefix( + &project.default_environment(), + LockFileUsage::Update, + false, + IndexMap::default(), ) - .unwrap(); - fs::write(&manifest_path, rv).into_diagnostic()?; + .await?; + } else { + // Default to something to avoid errors + let name = get_name_from_dir(&dir).unwrap_or_else(|_| String::from("new_project")); + + let channels = if let Some(channels) = args.channels { + channels + } else { + DEFAULT_CHANNELS + .iter() + .copied() + .map(ToOwned::to_owned) + .collect() + }; + + let rv = env + .render_named_str( + consts::PROJECT_MANIFEST, + PROJECT_TEMPLATE, + context! { + name, + version, + author, + channels, + platforms + }, + ) + .unwrap(); + fs::write(&manifest_path, rv).into_diagnostic()?; + }; // create a .gitignore if one is missing if let Err(e) = create_or_append_file(&gitignore_path, GITIGNORE_TEMPLATE) { @@ -141,6 +222,17 @@ pub async fn execute(args: Args) -> miette::Result<()> { Ok(()) } +fn get_name_from_dir(path: &Path) -> miette::Result { + Ok(path + .file_name() + .ok_or(miette::miette!( + "Cannot get file or directory name from the path: {}", + path.to_string_lossy() + ))? + .to_string_lossy() + .to_string()) +} + // When the specific template is not in the file or the file does not exist. // Make the file and append the template to the file. fn create_or_append_file(path: &Path, template: &str) -> std::io::Result<()> { @@ -176,14 +268,189 @@ fn get_dir(path: PathBuf) -> Result { } } +type PipReq = (PackageName, PyPiRequirement); +type ParsedDependencies = (Vec, Vec, Vec>); + +fn conda_env_to_manifest( + env_info: CondaEnvFile, +) -> miette::Result<(Vec, Vec, Vec)> { + let channels = parse_channels(env_info.channels().clone()); + let (conda_deps, pip_deps, mut extra_channels) = + parse_dependencies(env_info.dependencies().clone())?; + extra_channels.extend( + channels + .into_iter() + .map(|c| Arc::new(Channel::from_str(c, &ChannelConfig::default()).unwrap())), + ); + let mut channels: Vec<_> = extra_channels + .into_iter() + .unique() + .map(|c| c.name().to_string()) + .collect(); + if channels.is_empty() { + channels = DEFAULT_CHANNELS + .iter() + .copied() + .map(ToOwned::to_owned) + .collect() + } + + Ok((conda_deps, pip_deps, channels)) +} +fn parse_dependencies(deps: Vec) -> miette::Result { + let mut conda_deps = vec![]; + let mut pip_deps = vec![]; + let mut picked_up_channels = vec![]; + for dep in deps { + match dep { + CondaEnvDep::Conda(d) => { + let match_spec = MatchSpec::from_str(&d).into_diagnostic()?; + if let Some(channel) = match_spec.clone().channel { + picked_up_channels.push(channel); + } + conda_deps.push(match_spec); + } + CondaEnvDep::Pip { pip } => pip_deps.extend( + pip.into_iter() + .map(|mut dep| { + let re = Regex::new(r"/([^/]+)\.git").unwrap(); + if let Some(caps) = re.captures(dep.as_str()) { + let name= caps.get(1).unwrap().as_str().to_string(); + tracing::warn!("The dependency '{}' is a git repository, as that is not available in pixi we'll try to install it as a package with the name: {}", dep, name); + dep = name; + } + let req = pep508_rs::Requirement::from_str(&dep).into_diagnostic()?; + let name = rip::types::PackageName::from_str(req.name.as_str())?; + let requirement = PyPiRequirement::from(req); + Ok((name, requirement)) + }) + .collect::>>()?, + ), + } + } + + if !pip_deps.is_empty() { + conda_deps.push(MatchSpec::from_str("pip").into_diagnostic()?); + } + + Ok((conda_deps, pip_deps, picked_up_channels)) +} + +fn parse_channels(channels: Vec) -> Vec { + let mut new_channels = vec![]; + for channel in channels { + if channel == "defaults" { + // https://docs.anaconda.com/free/working-with-conda/reference/default-repositories/#active-default-channels + new_channels.push("main".to_string()); + new_channels.push("r".to_string()); + new_channels.push("msys2".to_string()); + } else { + let channel = channel.trim(); + if !channel.is_empty() { + new_channels.push(channel.to_string()); + } + } + } + new_channels +} + #[cfg(test)] mod tests { use super::*; use crate::cli::init::get_dir; + use itertools::Itertools; + use rattler_conda_types::ChannelConfig; use std::io::Read; use std::path::{Path, PathBuf}; use tempfile::tempdir; + #[test] + fn test_parse_conda_env_file() { + let example_conda_env_file = r#" + name: pixi_example_project + channels: + - conda-forge + dependencies: + - python + - pytorch::torchvision + - conda-forge::pytest + - wheel=0.31.1 + - pip: + - requests + - git+https://git@github.com/fsschneider/DeepOBS.git@develop#egg=deepobs + - torch==1.8.1 + "#; + let conda_env_file_data: CondaEnvFile = + serde_yaml::from_str(example_conda_env_file).unwrap(); + + assert_eq!(conda_env_file_data.name(), Some("pixi_example_project")); + assert_eq!( + conda_env_file_data.channels(), + &vec!["conda-forge".to_string()] + ); + + let (conda_deps, pip_deps, mut channels) = + parse_dependencies(conda_env_file_data.dependencies().clone()).unwrap(); + + channels.extend( + conda_env_file_data + .channels() + .into_iter() + .map(|c| Arc::new(Channel::from_str(c, &ChannelConfig::default()).unwrap())), + ); + let channels = channels.into_iter().unique().collect::>(); + + assert_eq!( + channels, + vec![ + Arc::new(Channel::from_str("pytorch", &ChannelConfig::default()).unwrap()), + Arc::new(Channel::from_str("conda-forge", &ChannelConfig::default()).unwrap()) + ], + ); + + println!("{conda_deps:?}"); + assert_eq!( + conda_deps, + vec![ + MatchSpec::from_str("python").unwrap(), + MatchSpec::from_str("pytorch::torchvision").unwrap(), + MatchSpec::from_str("conda-forge::pytest").unwrap(), + MatchSpec::from_str("wheel=0.31.1").unwrap(), + MatchSpec::from_str("pip").unwrap(), + ] + ); + + assert_eq!( + pip_deps, + vec![ + ( + PackageName::from_str("requests").unwrap(), + PyPiRequirement { + version: None, + extras: None, + index: None, + } + ), + ( + PackageName::from_str("DeepOBS").unwrap(), + PyPiRequirement { + version: None, + extras: None, + index: None, + }, + ), + ( + PackageName::from_str("torch").unwrap(), + PyPiRequirement { + version: pep440_rs::VersionSpecifiers::from_str("==1.8.1").ok(), + extras: None, + index: None, + } + ), + ] + ); + } + #[test] fn test_get_name() { assert_eq!( @@ -240,4 +507,42 @@ mod tests { dir.close().unwrap(); } + + #[test] + fn test_import_from_env_yamls() { + let test_files_path = Path::new(&env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("environment_yamls"); + + let entries = match fs::read_dir(test_files_path) { + Ok(entries) => entries, + Err(e) => panic!("Failed to read directory: {}", e), + }; + + let mut paths = Vec::new(); + for entry in entries { + let entry = entry.expect("Failed to read directory entry"); + if entry.path().is_file() { + paths.push(entry.path()); + } + } + + for path in paths { + let env_info = CondaEnvFile::from_path(&path).unwrap(); + // Try `cargo insta test` to run all at once + let snapshot_name = format!( + "test_import_from_env_yaml.{}", + path.file_name().unwrap().to_string_lossy() + ); + + insta::assert_debug_snapshot!( + snapshot_name, + ( + parse_dependencies(env_info.dependencies().clone()).unwrap(), + parse_channels(env_info.channels().clone()), + env_info.name() + ) + ); + } + } } diff --git a/src/cli/snapshots/pixi__cli__init__tests__test_import_from_env_yaml.cockpit.yml.snap b/src/cli/snapshots/pixi__cli__init__tests__test_import_from_env_yaml.cockpit.yml.snap new file mode 100644 index 000000000..62a22729f --- /dev/null +++ b/src/cli/snapshots/pixi__cli__init__tests__test_import_from_env_yaml.cockpit.yml.snap @@ -0,0 +1,1312 @@ +--- +source: src/cli/init.rs +expression: "(parse_dependencies(env_info.dependencies).unwrap(),\n parse_channels(env_info.channels), env_info.name)" +--- +( + ( + [ + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "pip", + }, + ), + version: None, + build: None, + build_number: None, + file_name: None, + channel: None, + subdir: None, + namespace: None, + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "python", + }, + ), + version: None, + build: None, + build_number: None, + file_name: None, + channel: None, + subdir: None, + namespace: None, + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "pytorch", + }, + ), + version: None, + build: None, + build_number: None, + file_name: None, + channel: Some( + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/pytorch/", + query: None, + fragment: None, + }, + name: Some( + "pytorch", + ), + }, + ), + subdir: None, + namespace: Some( + "", + ), + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "torchvision", + }, + ), + version: None, + build: None, + build_number: None, + file_name: None, + channel: Some( + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/pytorch/", + query: None, + fragment: None, + }, + name: Some( + "pytorch", + ), + }, + ), + subdir: None, + namespace: Some( + "", + ), + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "black", + }, + ), + version: None, + build: None, + build_number: None, + file_name: None, + channel: Some( + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + ), + subdir: None, + namespace: Some( + "", + ), + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "darglint", + }, + ), + version: None, + build: None, + build_number: None, + file_name: None, + channel: Some( + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + ), + subdir: None, + namespace: Some( + "", + ), + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "flake8", + }, + ), + version: None, + build: None, + build_number: None, + file_name: None, + channel: Some( + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + ), + subdir: None, + namespace: Some( + "", + ), + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "flake8-bugbear", + }, + ), + version: None, + build: None, + build_number: None, + file_name: None, + channel: Some( + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + ), + subdir: None, + namespace: Some( + "", + ), + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "flake8-comprehensions", + }, + ), + version: None, + build: None, + build_number: None, + file_name: None, + channel: Some( + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + ), + subdir: None, + namespace: Some( + "", + ), + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "isort", + }, + ), + version: None, + build: None, + build_number: None, + file_name: None, + channel: Some( + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + ), + subdir: None, + namespace: Some( + "", + ), + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "palettable", + }, + ), + version: None, + build: None, + build_number: None, + file_name: None, + channel: Some( + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + ), + subdir: None, + namespace: Some( + "", + ), + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "pydocstyle", + }, + ), + version: None, + build: None, + build_number: None, + file_name: None, + channel: Some( + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + ), + subdir: None, + namespace: Some( + "", + ), + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "pytest", + }, + ), + version: None, + build: None, + build_number: None, + file_name: None, + channel: Some( + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + ), + subdir: None, + namespace: Some( + "", + ), + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "pytest-cov", + }, + ), + version: None, + build: None, + build_number: None, + file_name: None, + channel: Some( + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + ), + subdir: None, + namespace: Some( + "", + ), + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "pytest-benchmark", + }, + ), + version: None, + build: None, + build_number: None, + file_name: None, + channel: Some( + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + ), + subdir: None, + namespace: Some( + "", + ), + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "m2r2", + }, + ), + version: None, + build: None, + build_number: None, + file_name: None, + channel: Some( + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + ), + subdir: None, + namespace: Some( + "", + ), + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "sphinx", + }, + ), + version: None, + build: None, + build_number: None, + file_name: None, + channel: Some( + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + ), + subdir: None, + namespace: Some( + "", + ), + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "sphinx-automodapi", + }, + ), + version: None, + build: None, + build_number: None, + file_name: None, + channel: Some( + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + ), + subdir: None, + namespace: Some( + "", + ), + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "sphinx_rtd_theme", + }, + ), + version: None, + build: None, + build_number: None, + file_name: None, + channel: Some( + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + ), + subdir: None, + namespace: Some( + "", + ), + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "matplotlib", + }, + ), + version: Some( + StrictRange( + StartsWith, + StrictVersion( + Version { + version: [[0], [3], [4], [3]], + local: [], + }, + ), + ), + ), + build: None, + build_number: None, + file_name: None, + channel: Some( + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + ), + subdir: None, + namespace: Some( + "", + ), + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "tikzplotlib", + }, + ), + version: None, + build: None, + build_number: None, + file_name: None, + channel: Some( + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + ), + subdir: None, + namespace: Some( + "", + ), + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "pip", + }, + ), + version: None, + build: None, + build_number: None, + file_name: None, + channel: None, + subdir: None, + namespace: None, + md5: None, + sha256: None, + }, + ], + [ + ( + PackageName { + source: "memory-profiler", + normalized: "memory-profiler", + }, + PyPiRequirement { + version: None, + extras: None, + index: None, + }, + ), + ( + PackageName { + source: "pre-commit", + normalized: "pre-commit", + }, + PyPiRequirement { + version: None, + extras: None, + index: None, + }, + ), + ( + PackageName { + source: "graphviz", + normalized: "graphviz", + }, + PyPiRequirement { + version: None, + extras: None, + index: None, + }, + ), + ( + PackageName { + source: "sphinx-notfound-page", + normalized: "sphinx-notfound-page", + }, + PyPiRequirement { + version: None, + extras: None, + index: None, + }, + ), + ( + PackageName { + source: "DeepOBS", + normalized: "deepobs", + }, + PyPiRequirement { + version: None, + extras: None, + index: None, + }, + ), + ], + [ + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/pytorch/", + query: None, + fragment: None, + }, + name: Some( + "pytorch", + ), + }, + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/pytorch/", + query: None, + fragment: None, + }, + name: Some( + "pytorch", + ), + }, + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + Channel { + platforms: None, + base_url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "conda.anaconda.org", + ), + ), + port: None, + path: "/conda-forge/", + query: None, + fragment: None, + }, + name: Some( + "conda-forge", + ), + }, + ], + ), + [], + Some( + "cockpit", + ), +) diff --git a/src/cli/snapshots/pixi__cli__init__tests__test_import_from_env_yaml.conda.yml.snap b/src/cli/snapshots/pixi__cli__init__tests__test_import_from_env_yaml.conda.yml.snap new file mode 100644 index 000000000..66a009aeb --- /dev/null +++ b/src/cli/snapshots/pixi__cli__init__tests__test_import_from_env_yaml.conda.yml.snap @@ -0,0 +1,68 @@ +--- +source: src/cli/init.rs +expression: "(parse_dependencies(env_info.dependencies).unwrap(),\n parse_channels(env_info.channels), env_info.name)" +--- +( + ( + [ + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "pip", + }, + ), + version: Some( + StrictRange( + StartsWith, + StrictVersion( + Version { + version: [[0], [20]], + local: [], + }, + ), + ), + ), + build: None, + build_number: None, + file_name: None, + channel: None, + subdir: None, + namespace: None, + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "python", + }, + ), + version: Some( + StrictRange( + StartsWith, + StrictVersion( + Version { + version: [[0], [3]], + local: [], + }, + ), + ), + ), + build: None, + build_number: None, + file_name: None, + channel: None, + subdir: None, + namespace: None, + md5: None, + sha256: None, + }, + ], + [], + [], + ), + [], + None, +) diff --git a/src/cli/snapshots/pixi__cli__init__tests__test_import_from_env_yaml.crnnft.yml.snap b/src/cli/snapshots/pixi__cli__init__tests__test_import_from_env_yaml.crnnft.yml.snap new file mode 100644 index 000000000..6df4fa076 --- /dev/null +++ b/src/cli/snapshots/pixi__cli__init__tests__test_import_from_env_yaml.crnnft.yml.snap @@ -0,0 +1,221 @@ +--- +source: src/cli/init.rs +expression: "(parse_dependencies(env_info.dependencies).unwrap(),\n parse_channels(env_info.channels), env_info.name)" +--- +( + ( + [ + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "pip", + }, + ), + version: Some( + StrictRange( + StartsWith, + StrictVersion( + Version { + version: [[0], [10], [0], [1]], + local: [], + }, + ), + ), + ), + build: None, + build_number: None, + file_name: None, + channel: None, + subdir: None, + namespace: None, + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "python", + }, + ), + version: Some( + StrictRange( + StartsWith, + StrictVersion( + Version { + version: [[0], [3], [5], [5]], + local: [], + }, + ), + ), + ), + build: None, + build_number: None, + file_name: None, + channel: None, + subdir: None, + namespace: None, + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "setuptools", + }, + ), + version: Some( + StrictRange( + StartsWith, + StrictVersion( + Version { + version: [[0], [39], [1], [0]], + local: [], + }, + ), + ), + ), + build: None, + build_number: None, + file_name: None, + channel: None, + subdir: None, + namespace: None, + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "wheel", + }, + ), + version: Some( + StrictRange( + StartsWith, + StrictVersion( + Version { + version: [[0], [0], [31], [1]], + local: [], + }, + ), + ), + ), + build: None, + build_number: None, + file_name: None, + channel: None, + subdir: None, + namespace: None, + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "pip", + }, + ), + version: None, + build: None, + build_number: None, + file_name: None, + channel: None, + subdir: None, + namespace: None, + md5: None, + sha256: None, + }, + ], + [ + ( + PackageName { + source: "tensorflow_gpu", + normalized: "tensorflow-gpu", + }, + PyPiRequirement { + version: Some( + VersionSpecifiers( + [ + VersionSpecifier { + operator: Equal, + version: Version { + epoch: 0, + release: [ + 1, + 15, + 0, + ], + pre: None, + post: None, + dev: None, + local: None, + }, + }, + ], + ), + ), + extras: None, + index: None, + }, + ), + ( + PackageName { + source: "numpy", + normalized: "numpy", + }, + PyPiRequirement { + version: None, + extras: None, + index: None, + }, + ), + ( + PackageName { + source: "opencv_python", + normalized: "opencv-python", + }, + PyPiRequirement { + version: None, + extras: None, + index: None, + }, + ), + ( + PackageName { + source: "matplotlib", + normalized: "matplotlib", + }, + PyPiRequirement { + version: None, + extras: None, + index: None, + }, + ), + ( + PackageName { + source: "easydict", + normalized: "easydict", + }, + PyPiRequirement { + version: None, + extras: None, + index: None, + }, + ), + ], + [], + ), + [ + "main", + "r", + "msys2", + ], + Some( + "crnntf", + ), +) diff --git a/src/cli/snapshots/pixi__cli__init__tests__test_import_from_env_yaml.let-plot.yml.snap b/src/cli/snapshots/pixi__cli__init__tests__test_import_from_env_yaml.let-plot.yml.snap new file mode 100644 index 000000000..548a1c656 --- /dev/null +++ b/src/cli/snapshots/pixi__cli__init__tests__test_import_from_env_yaml.let-plot.yml.snap @@ -0,0 +1,257 @@ +--- +source: src/cli/init.rs +expression: "(parse_dependencies(env_info.dependencies).unwrap(),\n parse_channels(env_info.channels), env_info.name)" +--- +( + ( + [ + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "python", + }, + ), + version: Some( + StrictRange( + StartsWith, + StrictVersion( + Version { + version: [[0], [3], [8]], + local: [], + }, + ), + ), + ), + build: None, + build_number: None, + file_name: None, + channel: None, + subdir: None, + namespace: None, + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "numpy", + }, + ), + version: Some( + Range( + GreaterEquals, + Version { + version: [[0], [1], [24], [2]], + local: [], + }, + ), + ), + build: None, + build_number: None, + file_name: None, + channel: None, + subdir: None, + namespace: None, + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "jupyter", + }, + ), + version: Some( + StrictRange( + StartsWith, + StrictVersion( + Version { + version: [[0], [1], [0], [0]], + local: [], + }, + ), + ), + ), + build: None, + build_number: None, + file_name: None, + channel: None, + subdir: None, + namespace: None, + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "pandas", + }, + ), + version: Some( + Range( + GreaterEquals, + Version { + version: [[0], [1], [5], [3]], + local: [], + }, + ), + ), + build: None, + build_number: None, + file_name: None, + channel: None, + subdir: None, + namespace: None, + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "geopandas", + }, + ), + version: Some( + Range( + GreaterEquals, + Version { + version: [[0], [0], [12], [2]], + local: [], + }, + ), + ), + build: None, + build_number: None, + file_name: None, + channel: None, + subdir: None, + namespace: None, + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "shapely", + }, + ), + version: Some( + Range( + GreaterEquals, + Version { + version: [[0], [2], [0], [0]], + local: [], + }, + ), + ), + build: None, + build_number: None, + file_name: None, + channel: None, + subdir: None, + namespace: None, + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "pytest", + }, + ), + version: Some( + Range( + GreaterEquals, + Version { + version: [[0], [7], [2], [1]], + local: [], + }, + ), + ), + build: None, + build_number: None, + file_name: None, + channel: None, + subdir: None, + namespace: None, + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "pip", + }, + ), + version: None, + build: None, + build_number: None, + file_name: None, + channel: None, + subdir: None, + namespace: None, + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "pip", + }, + ), + version: None, + build: None, + build_number: None, + file_name: None, + channel: None, + subdir: None, + namespace: None, + md5: None, + sha256: None, + }, + ], + [ + ( + PackageName { + source: "pypng", + normalized: "pypng", + }, + PyPiRequirement { + version: None, + extras: None, + index: None, + }, + ), + ( + PackageName { + source: "palettable", + normalized: "palettable", + }, + PyPiRequirement { + version: None, + extras: None, + index: None, + }, + ), + ], + [], + ), + [ + "conda-forge", + "main", + "r", + "msys2", + ], + Some( + "lets-plot-python-module", + ), +) diff --git a/src/cli/snapshots/pixi__cli__init__tests__test_import_from_env_yaml.test_env.yml.snap b/src/cli/snapshots/pixi__cli__init__tests__test_import_from_env_yaml.test_env.yml.snap new file mode 100644 index 000000000..3acbfb536 --- /dev/null +++ b/src/cli/snapshots/pixi__cli__init__tests__test_import_from_env_yaml.test_env.yml.snap @@ -0,0 +1,511 @@ +--- +source: src/cli/init.rs +expression: "(parse_dependencies(env_info.dependencies).unwrap(),\n parse_channels(env_info.channels), env_info.name)" +--- +( + ( + [ + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "python", + }, + ), + version: Some( + StrictRange( + StartsWith, + StrictVersion( + Version { + version: [[0], [3], [5], [5]], + local: [], + }, + ), + ), + ), + build: None, + build_number: None, + file_name: None, + channel: None, + subdir: None, + namespace: None, + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "setuptools", + }, + ), + version: Some( + Range( + GreaterEquals, + Version { + version: [[0], [39], [1], [0]], + local: [], + }, + ), + ), + build: None, + build_number: None, + file_name: None, + channel: None, + subdir: None, + namespace: None, + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "wheel", + }, + ), + version: Some( + Range( + LessEquals, + Version { + version: [[0], [0], [31], [1]], + local: [], + }, + ), + ), + build: None, + build_number: None, + file_name: None, + channel: None, + subdir: None, + namespace: None, + md5: None, + sha256: None, + }, + MatchSpec { + name: Some( + PackageName { + normalized: None, + source: "pip", + }, + ), + version: None, + build: None, + build_number: None, + file_name: None, + channel: None, + subdir: None, + namespace: None, + md5: None, + sha256: None, + }, + ], + [ + ( + PackageName { + source: "package1", + normalized: "package1", + }, + PyPiRequirement { + version: Some( + VersionSpecifiers( + [ + VersionSpecifier { + operator: Equal, + version: Version { + epoch: 0, + release: [ + 1, + 0, + 0, + ], + pre: None, + post: None, + dev: None, + local: None, + }, + }, + ], + ), + ), + extras: None, + index: None, + }, + ), + ( + PackageName { + source: "package2", + normalized: "package2", + }, + PyPiRequirement { + version: Some( + VersionSpecifiers( + [ + VersionSpecifier { + operator: Equal, + version: Version { + epoch: 0, + release: [ + 2, + 0, + ], + pre: None, + post: None, + dev: None, + local: None, + }, + }, + ], + ), + ), + extras: None, + index: None, + }, + ), + ( + PackageName { + source: "package3", + normalized: "package3", + }, + PyPiRequirement { + version: Some( + VersionSpecifiers( + [ + VersionSpecifier { + operator: Equal, + version: Version { + epoch: 0, + release: [ + 3, + ], + pre: None, + post: None, + dev: None, + local: None, + }, + }, + ], + ), + ), + extras: None, + index: None, + }, + ), + ( + PackageName { + source: "package4", + normalized: "package4", + }, + PyPiRequirement { + version: Some( + VersionSpecifiers( + [ + VersionSpecifier { + operator: LessThanEqual, + version: Version { + epoch: 0, + release: [ + 4, + 0, + 0, + ], + pre: None, + post: None, + dev: None, + local: None, + }, + }, + ], + ), + ), + extras: None, + index: None, + }, + ), + ( + PackageName { + source: "package5", + normalized: "package5", + }, + PyPiRequirement { + version: Some( + VersionSpecifiers( + [ + VersionSpecifier { + operator: GreaterThanEqual, + version: Version { + epoch: 0, + release: [ + 5, + 0, + 0, + ], + pre: None, + post: None, + dev: None, + local: None, + }, + }, + ], + ), + ), + extras: None, + index: None, + }, + ), + ( + PackageName { + source: "package6", + normalized: "package6", + }, + PyPiRequirement { + version: Some( + VersionSpecifiers( + [ + VersionSpecifier { + operator: GreaterThan, + version: Version { + epoch: 0, + release: [ + 6, + 0, + 0, + ], + pre: None, + post: None, + dev: None, + local: None, + }, + }, + ], + ), + ), + extras: None, + index: None, + }, + ), + ( + PackageName { + source: "package7", + normalized: "package7", + }, + PyPiRequirement { + version: Some( + VersionSpecifiers( + [ + VersionSpecifier { + operator: LessThan, + version: Version { + epoch: 0, + release: [ + 7, + 0, + 0, + ], + pre: None, + post: None, + dev: None, + local: None, + }, + }, + ], + ), + ), + extras: None, + index: None, + }, + ), + ( + PackageName { + source: "package8", + normalized: "package8", + }, + PyPiRequirement { + version: Some( + VersionSpecifiers( + [ + VersionSpecifier { + operator: NotEqual, + version: Version { + epoch: 0, + release: [ + 8, + 0, + 0, + ], + pre: None, + post: None, + dev: None, + local: None, + }, + }, + ], + ), + ), + extras: None, + index: None, + }, + ), + ( + PackageName { + source: "package9", + normalized: "package9", + }, + PyPiRequirement { + version: None, + extras: None, + index: None, + }, + ), + ( + PackageName { + source: "package10", + normalized: "package10", + }, + PyPiRequirement { + version: None, + extras: Some( + [ + "extra", + ], + ), + index: None, + }, + ), + ( + PackageName { + source: "package11", + normalized: "package11", + }, + PyPiRequirement { + version: None, + extras: Some( + [ + "extra1", + "extra2", + ], + ), + index: None, + }, + ), + ( + PackageName { + source: "package12", + normalized: "package12", + }, + PyPiRequirement { + version: Some( + VersionSpecifiers( + [ + VersionSpecifier { + operator: Equal, + version: Version { + epoch: 0, + release: [ + 12, + 0, + 0, + ], + pre: None, + post: None, + dev: None, + local: None, + }, + }, + ], + ), + ), + extras: Some( + [ + "extra1", + "extra2", + ], + ), + index: None, + }, + ), + ( + PackageName { + source: "package13", + normalized: "package13", + }, + PyPiRequirement { + version: Some( + VersionSpecifiers( + [ + VersionSpecifier { + operator: GreaterThanEqual, + version: Version { + epoch: 0, + release: [ + 13, + 0, + 0, + ], + pre: None, + post: None, + dev: None, + local: None, + }, + }, + ], + ), + ), + extras: Some( + [ + "extra1", + "extra2", + ], + ), + index: None, + }, + ), + ( + PackageName { + source: "package14", + normalized: "package14", + }, + PyPiRequirement { + version: Some( + VersionSpecifiers( + [ + VersionSpecifier { + operator: LessThanEqual, + version: Version { + epoch: 0, + release: [ + 14, + 0, + 0, + ], + pre: None, + post: None, + dev: None, + local: None, + }, + }, + ], + ), + ), + extras: Some( + [ + "extra1", + "extra2", + ], + ), + index: None, + }, + ), + ], + [], + ), + [ + "main", + "r", + "msys2", + "conda-forge", + ], + Some( + "test_env", + ), +) diff --git a/src/utils/conda_environment_file.rs b/src/utils/conda_environment_file.rs new file mode 100644 index 000000000..cd1557259 --- /dev/null +++ b/src/utils/conda_environment_file.rs @@ -0,0 +1,40 @@ +use miette::IntoDiagnostic; +use serde::Deserialize; +use std::path::Path; + +#[derive(Deserialize, Debug, Clone)] +pub struct CondaEnvFile { + #[serde(default)] + name: Option, + #[serde(default)] + channels: Vec, + dependencies: Vec, +} + +#[derive(Deserialize, Debug, Clone)] +#[serde(untagged)] +pub enum CondaEnvDep { + Conda(String), + Pip { pip: Vec }, +} + +impl CondaEnvFile { + pub fn name(&self) -> Option<&str> { + self.name.as_deref() + } + + pub fn channels(&self) -> &Vec { + &self.channels + } + + pub fn dependencies(&self) -> &Vec { + &self.dependencies + } + + pub fn from_path(path: &Path) -> miette::Result { + let file = std::fs::File::open(path).into_diagnostic()?; + let reader = std::io::BufReader::new(file); + let env_file = serde_yaml::from_reader(reader).into_diagnostic()?; + Ok(env_file) + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index cc07a2958..ac400c733 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,4 +1,5 @@ mod barrier_cell; +pub mod conda_environment_file; pub mod spanned; pub use barrier_cell::BarrierCell; diff --git a/tests/common/builders.rs b/tests/common/builders.rs index 4b98fc8d2..6ae84c951 100644 --- a/tests/common/builders.rs +++ b/tests/common/builders.rs @@ -69,10 +69,10 @@ impl InitBuilder { impl IntoFuture for InitBuilder { type Output = miette::Result<()>; - type IntoFuture = Pin + Send + 'static>>; + type IntoFuture = Pin + 'static>>; fn into_future(self) -> Self::IntoFuture { - Box::pin(init::execute(self.args)) + init::execute(self.args).boxed_local() } } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 28199edd1..21bc80483 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -185,6 +185,7 @@ impl PixiControl { path: self.project_path().to_path_buf(), channels: None, platforms: Vec::new(), + env_file: None, }, } } @@ -197,6 +198,7 @@ impl PixiControl { path: self.project_path().to_path_buf(), channels: None, platforms, + env_file: None, }, } } diff --git a/tests/environment_yamls/cockpit.yml b/tests/environment_yamls/cockpit.yml new file mode 100644 index 000000000..09d5888d7 --- /dev/null +++ b/tests/environment_yamls/cockpit.yml @@ -0,0 +1,29 @@ +name: cockpit +dependencies: + - pip + - python + - pytorch::pytorch + - pytorch::torchvision + - conda-forge::black + - conda-forge::darglint + - conda-forge::flake8 + - conda-forge::flake8-bugbear + - conda-forge::flake8-comprehensions + - conda-forge::isort + - conda-forge::palettable + - conda-forge::pydocstyle + - conda-forge::pytest + - conda-forge::pytest-cov + - conda-forge::pytest-benchmark + - conda-forge::m2r2 + - conda-forge::sphinx + - conda-forge::sphinx-automodapi + - conda-forge::sphinx_rtd_theme + - conda-forge::matplotlib=3.4.3 + - conda-forge::tikzplotlib + - pip: + - memory-profiler + - pre-commit + - graphviz + - sphinx-notfound-page + - git+https://git@github.com/fsschneider/DeepOBS.git@develop#egg=deepobs diff --git a/tests/environment_yamls/conda.yml b/tests/environment_yamls/conda.yml new file mode 100644 index 000000000..5fe9e6cbe --- /dev/null +++ b/tests/environment_yamls/conda.yml @@ -0,0 +1,3 @@ +dependencies: + - pip=20 + - python=3 diff --git a/tests/environment_yamls/crnnft.yml b/tests/environment_yamls/crnnft.yml new file mode 100644 index 000000000..2e01866ba --- /dev/null +++ b/tests/environment_yamls/crnnft.yml @@ -0,0 +1,14 @@ +name: crnntf +channels: + - defaults +dependencies: + - pip=10.0.1 + - python=3.5.5 + - setuptools=39.1.0 + - wheel=0.31.1 + - pip: + - tensorflow_gpu==1.15.0 + - numpy + - opencv_python + - matplotlib + - easydict diff --git a/tests/environment_yamls/let-plot.yml b/tests/environment_yamls/let-plot.yml new file mode 100644 index 000000000..2de59c584 --- /dev/null +++ b/tests/environment_yamls/let-plot.yml @@ -0,0 +1,18 @@ +--- + +name: lets-plot-python-module +channels: + - conda-forge + - defaults +dependencies: + - python=3.8 + - numpy>=1.24.2 + - jupyter=1.0.0 + - pandas>=1.5.3 + - geopandas>=0.12.2 + - shapely>=2.0.0 + - pytest>=7.2.1 + - pip + - pip: + - pypng + - palettable diff --git a/tests/environment_yamls/test_env.yml b/tests/environment_yamls/test_env.yml new file mode 100644 index 000000000..0adf6a49a --- /dev/null +++ b/tests/environment_yamls/test_env.yml @@ -0,0 +1,23 @@ +name: test_env +channels: + - defaults + - conda-forge +dependencies: + - python=3.5.5 + - setuptools>=39.1.0 + - wheel<=0.31.1 + - pip: + - package1 ==1.0.0 + - package2 ==2.0 + - package3 ==3 + - package4 <=4.0.0 + - package5 >=5.0.0 + - package6 >6.0.0 + - package7 <7.0.0 + - package8 !=8.0.0 + - package9 + - package10[extra] + - package11[extra1,extra2] + - package12[extra1,extra2]==12.0.0 + - package13[extra1,extra2]>=13.0.0 + - package14[extra1,extra2]<=14.0.0