From 1ff98c312134294f15e402d63bff19ae080f3c8b Mon Sep 17 00:00:00 2001 From: "Joshua L. Adelman" Date: Wed, 21 Aug 2024 13:22:51 -0400 Subject: [PATCH 01/19] export conda explicit specification file from project --- docs/reference/cli.md | 12 + src/cli/project/export/conda_explicit_spec.rs | 220 ++++++++++++++++++ src/cli/project/export/mod.rs | 31 +++ src/cli/project/mod.rs | 3 + 4 files changed, 266 insertions(+) create mode 100644 src/cli/project/export/conda_explicit_spec.rs create mode 100644 src/cli/project/export/mod.rs diff --git a/docs/reference/cli.md b/docs/reference/cli.md index beebb5690..7f195db4e 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -1178,6 +1178,18 @@ List the environments in the manifest file. pixi project environment list ``` +### `project export conda_explicit_spec` + +Render a platform-specific conda [explicit specification file](https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#building-identical-conda-environments) +for an environment. The file can be then used to create a conda environment using conda/mamba: + +```shell +mamba create --name --file +``` + +As the explicit specification file format does not support pypi-dependencies, use either the `--ignore-pypi-errors` option to ignore those dependencies or +`--write-pypi-requirements` to write a `requirements.txt` file that can then be used to install the packages into a conda env using `pip` or `uv`. + ### `project platform add` Adds a platform(s) to the manifest file and updates the lock file. diff --git a/src/cli/project/export/conda_explicit_spec.rs b/src/cli/project/export/conda_explicit_spec.rs new file mode 100644 index 000000000..238547ae1 --- /dev/null +++ b/src/cli/project/export/conda_explicit_spec.rs @@ -0,0 +1,220 @@ +use std::fs; +use std::path::{Path, PathBuf}; + +use clap::Parser; + +use crate::cli::cli_config::PrefixUpdateConfig; +use crate::cli::LockFileUsageArgs; +use crate::lock_file::UpdateLockFileOptions; +use crate::Project; +use rattler_conda_types::{ExplicitEnvironmentEntry, ExplicitEnvironmentSpec, Platform}; +use rattler_lock::{CondaPackage, Package, PackageHashes, PypiPackage, PypiPackageData, UrlOrPath}; + +#[derive(Debug, Parser)] +#[clap(arg_required_else_help = false)] +pub struct Args { + /// Environment to render + #[arg(short, long)] + environment: Option, + + /// The platform to render. Defaults to the current platform. + #[arg(long)] + pub platform: Option, + + /// PyPI dependencies are not supported in the conda spec file. + /// This flag allows creating the spec file even if PyPI dependencies are present. + /// Alternatively see --write-pypi-requirements + #[arg(long, default_value = "false")] + ignore_pypi_errors: bool, + + /// Write a requirements file containing all pypi dependencies + #[arg(long, default_value = "false", conflicts_with = "ignore_pypi_errors")] + write_pypi_requirements: bool, + + #[clap(flatten)] + pub lock_file_usage: LockFileUsageArgs, + + #[clap(flatten)] + pub prefix_update_config: PrefixUpdateConfig, +} + +fn cwd() -> PathBuf { + std::env::current_dir().expect("failed to obtain current working directory") +} + +fn build_explicit_spec<'a>( + platform: Platform, + conda_packages: impl IntoIterator, +) -> miette::Result { + let mut packages = Vec::new(); + + for cp in conda_packages { + let prec = cp.package_record(); + let mut url = cp.url().to_owned(); + let hash = prec.md5.ok_or(miette::miette!( + "Package {} does not contain an md5 hash", + prec.name.as_normalized() + ))?; + + url.set_fragment(Some(&format!("{:x}", hash))); + + packages.push(ExplicitEnvironmentEntry { + url: url.to_owned(), + }); + } + + Ok(ExplicitEnvironmentSpec { + platform: Some(platform), + packages, + }) +} + +fn write_explicit_spec( + target: impl AsRef, + exp_env_spec: &ExplicitEnvironmentSpec, +) -> miette::Result<()> { + let mut environment = String::new(); + environment.push_str("# Generated by `pixi project export`\n"); + environment.push_str(exp_env_spec.to_spec_string().as_str()); + + fs::write(target, environment) + .map_err(|e| miette::miette!("Could not write environment file: {}", e))?; + + Ok(()) +} + +fn get_pypi_hash_str(package_data: &PypiPackageData) -> Option { + if let Some(hashes) = &package_data.hash { + let h = match hashes { + PackageHashes::Sha256(h) => format!("--hash=sha256:{:x}", h).to_string(), + PackageHashes::Md5Sha256(_, h) => format!("--hash=sha256:{:x}", h).to_string(), + PackageHashes::Md5(h) => format!("--hash=md5:{:x}", h).to_string(), + }; + Some(h) + } else { + None + } +} + +fn write_pypi_requirements( + target: impl AsRef, + packages: &[PypiPackage], +) -> miette::Result<()> { + let mut reqs = String::new(); + + for p in packages { + // pip --verify-hashes does not accept hashes for local files + let (s, include_hash) = match p.url() { + UrlOrPath::Url(url) => (url.as_str(), true), + UrlOrPath::Path(path) => ( + path.as_os_str() + .to_str() + .unwrap_or_else(|| panic!("Could not convert {:?} to str", path)), + false, + ), + }; + + // remove "direct+ since not valid for pip urls" + let s = s.trim_start_matches("direct+"); + + let hash = match (include_hash, get_pypi_hash_str(p.data().package)) { + (true, Some(h)) => format!(" {}", h), + (false, _) => "".to_string(), + (_, None) => "".to_string(), + }; + + if p.is_editable() { + reqs.push_str(&format!("-e {}{}\n", s, hash)); + } else { + reqs.push_str(&format!("{}{}\n", s, hash)); + } + } + + fs::write(target, reqs) + .map_err(|e| miette::miette!("Could not write requirements file: {}", e))?; + + Ok(()) +} + +pub async fn execute(project: Project, args: Args) -> miette::Result<()> { + let environment = project.environment_from_name_or_env_var(args.environment)?; + // Load the platform + let platform = args.platform.unwrap_or_else(|| environment.best_platform()); + + let lock_file = project + .update_lock_file(UpdateLockFileOptions { + lock_file_usage: args.prefix_update_config.lock_file_usage(), + no_install: args.prefix_update_config.no_install, + ..UpdateLockFileOptions::default() + }) + .await? + .lock_file; + + let env = lock_file + .environment(environment.name().as_str()) + .ok_or(miette::miette!( + "unknown environment '{}' in {}", + environment.name(), + project + .manifest_path() + .to_str() + .expect("expected to have a manifest_path") + ))?; + + let packages = env.packages(platform).ok_or(miette::miette!( + "platform '{platform}' not found in {}", + project + .manifest_path() + .to_str() + .expect("expected to have a manifest_path"), + ))?; + + let mut conda_packages_from_lockfile: Vec = Vec::new(); + let mut pypi_packags_from_lockfile: Vec = Vec::new(); + + for package in packages { + match package { + Package::Conda(p) => conda_packages_from_lockfile.push(p), + Package::Pypi(pyp) => { + if args.ignore_pypi_errors { + tracing::warn!("ignoring PyPI package since PyPI packages are not supported"); + } else if args.write_pypi_requirements { + pypi_packags_from_lockfile.push(pyp); + } else { + miette::bail!( + "PyPI packages are not supported. Specify `--ignore-pypi-errors` to ignore this error\ + or `--write-pypi-requirements` to write pypi requirements to a separate requirements.txt file" + ); + } + } + } + } + + let ees = build_explicit_spec(platform, &conda_packages_from_lockfile)?; + + tracing::info!("Creating conda lock file"); + let target = cwd() + .join(format!( + "conda-{}-{}.lock", + platform, + environment.name().as_str() + )) + .into_os_string(); + + write_explicit_spec(target, &ees)?; + + if args.write_pypi_requirements { + tracing::info!("Creating conda lock file"); + let pypi_target = cwd() + .join(format!( + "requirements-{}-{}.txt", + platform, + environment.name().as_str() + )) + .into_os_string(); + + write_pypi_requirements(pypi_target, &pypi_packags_from_lockfile)?; + } + + Ok(()) +} diff --git a/src/cli/project/export/mod.rs b/src/cli/project/export/mod.rs new file mode 100644 index 000000000..8c135f2a1 --- /dev/null +++ b/src/cli/project/export/mod.rs @@ -0,0 +1,31 @@ +use std::path::PathBuf; +pub mod conda_explicit_spec; + +use crate::Project; +use clap::Parser; + +/// Commands to export projects to other formats +#[derive(Parser, Debug)] +pub struct Args { + /// The path to 'pixi.toml' or 'pyproject.toml' + #[clap(long, global = true)] + pub manifest_path: Option, + + #[clap(subcommand)] + pub command: Command, +} + +#[derive(Parser, Debug)] +pub enum Command { + /// Export project environment to a conda explicit specification file + #[clap(visible_alias = "ces")] + CondaExplicitSpec(conda_explicit_spec::Args), +} + +pub async fn execute(args: Args) -> miette::Result<()> { + let project = Project::load_or_else_discover(args.manifest_path.as_deref())?; + match args.command { + Command::CondaExplicitSpec(args) => conda_explicit_spec::execute(project, args).await?, + }; + Ok(()) +} diff --git a/src/cli/project/mod.rs b/src/cli/project/mod.rs index cca40223e..26dcd4e2e 100644 --- a/src/cli/project/mod.rs +++ b/src/cli/project/mod.rs @@ -3,6 +3,7 @@ use clap::Parser; pub mod channel; pub mod description; +pub mod export; pub mod environment; pub mod platform; pub mod version; @@ -14,6 +15,7 @@ pub enum Command { Platform(platform::Args), Version(version::Args), Environment(environment::Args), + Export(export::Args), } /// Modify the project configuration file through the command line. @@ -33,6 +35,7 @@ pub async fn execute(cmd: Args) -> miette::Result<()> { Command::Platform(args) => platform::execute(args).await?, Command::Version(args) => version::execute(args).await?, Command::Environment(args) => environment::execute(args).await?, + Command::Export(cmd) => export::execute(cmd).await?, }; Ok(()) } From 5a26aedb49e44530eff847d3cd63fa993e809d18 Mon Sep 17 00:00:00 2001 From: "Joshua L. Adelman" Date: Wed, 21 Aug 2024 13:55:42 -0400 Subject: [PATCH 02/19] fix formatting errors --- docs/reference/cli.md | 4 ++-- src/cli/project/export/conda_explicit_spec.rs | 6 +++--- src/cli/project/mod.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 7f195db4e..71e0198a6 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -1187,8 +1187,8 @@ for an environment. The file can be then used to create a conda environment usin mamba create --name --file ``` -As the explicit specification file format does not support pypi-dependencies, use either the `--ignore-pypi-errors` option to ignore those dependencies or -`--write-pypi-requirements` to write a `requirements.txt` file that can then be used to install the packages into a conda env using `pip` or `uv`. +As the explicit specification file format does not support pypi-dependencies, use either the `--ignore-pypi-errors` option to ignore those dependencies +or `--write-pypi-requirements` to write a `requirements.txt` file that can then be used to install the packages into a conda env using `pip` or `uv`. ### `project platform add` diff --git a/src/cli/project/export/conda_explicit_spec.rs b/src/cli/project/export/conda_explicit_spec.rs index 238547ae1..48b5fd6ba 100644 --- a/src/cli/project/export/conda_explicit_spec.rs +++ b/src/cli/project/export/conda_explicit_spec.rs @@ -170,7 +170,7 @@ pub async fn execute(project: Project, args: Args) -> miette::Result<()> { ))?; let mut conda_packages_from_lockfile: Vec = Vec::new(); - let mut pypi_packags_from_lockfile: Vec = Vec::new(); + let mut pypi_packages_from_lockfile: Vec = Vec::new(); for package in packages { match package { @@ -179,7 +179,7 @@ pub async fn execute(project: Project, args: Args) -> miette::Result<()> { if args.ignore_pypi_errors { tracing::warn!("ignoring PyPI package since PyPI packages are not supported"); } else if args.write_pypi_requirements { - pypi_packags_from_lockfile.push(pyp); + pypi_packages_from_lockfile.push(pyp); } else { miette::bail!( "PyPI packages are not supported. Specify `--ignore-pypi-errors` to ignore this error\ @@ -213,7 +213,7 @@ pub async fn execute(project: Project, args: Args) -> miette::Result<()> { )) .into_os_string(); - write_pypi_requirements(pypi_target, &pypi_packags_from_lockfile)?; + write_pypi_requirements(pypi_target, &pypi_packages_from_lockfile)?; } Ok(()) diff --git a/src/cli/project/mod.rs b/src/cli/project/mod.rs index 26dcd4e2e..99d098dc4 100644 --- a/src/cli/project/mod.rs +++ b/src/cli/project/mod.rs @@ -3,8 +3,8 @@ use clap::Parser; pub mod channel; pub mod description; -pub mod export; pub mod environment; +pub mod export; pub mod platform; pub mod version; From 63479bb97d3a66ed5e17056368437b684c8c0acc Mon Sep 17 00:00:00 2001 From: "Joshua L. Adelman" Date: Sat, 24 Aug 2024 21:18:05 -0400 Subject: [PATCH 03/19] refactor export conda explicit to dump multiple spec files --- src/cli/project/export/conda_explicit_spec.rs | 185 +++++++++++------- 1 file changed, 117 insertions(+), 68 deletions(-) diff --git a/src/cli/project/export/conda_explicit_spec.rs b/src/cli/project/export/conda_explicit_spec.rs index 48b5fd6ba..ddd677be9 100644 --- a/src/cli/project/export/conda_explicit_spec.rs +++ b/src/cli/project/export/conda_explicit_spec.rs @@ -1,3 +1,4 @@ +use std::collections::HashSet; use std::fs; use std::path::{Path, PathBuf}; @@ -8,28 +9,34 @@ use crate::cli::LockFileUsageArgs; use crate::lock_file::UpdateLockFileOptions; use crate::Project; use rattler_conda_types::{ExplicitEnvironmentEntry, ExplicitEnvironmentSpec, Platform}; -use rattler_lock::{CondaPackage, Package, PackageHashes, PypiPackage, PypiPackageData, UrlOrPath}; +use rattler_lock::{ + CondaPackage, Environment, Package, PackageHashes, PypiPackage, PypiPackageData, UrlOrPath, +}; #[derive(Debug, Parser)] #[clap(arg_required_else_help = false)] pub struct Args { - /// Environment to render + /// Output directory for rendered explicit environment spec files + pub output_dir: PathBuf, + + /// Environment to render. Can be repeated for multiple envs. Defaults to all environments #[arg(short, long)] - environment: Option, + pub environment: Option>, - /// The platform to render. Defaults to the current platform. - #[arg(long)] - pub platform: Option, + /// The platform to render. Can be repeated for multiple platforms. + /// Defaults to all platforms available for selected environments. + #[arg(short, long)] + pub platform: Option>, - /// PyPI dependencies are not supported in the conda spec file. + /// PyPI dependencies are not supported in the conda explicit spec file. /// This flag allows creating the spec file even if PyPI dependencies are present. /// Alternatively see --write-pypi-requirements #[arg(long, default_value = "false")] - ignore_pypi_errors: bool, + pub ignore_pypi_errors: bool, /// Write a requirements file containing all pypi dependencies #[arg(long, default_value = "false", conflicts_with = "ignore_pypi_errors")] - write_pypi_requirements: bool, + pub write_pypi_requirements: bool, #[clap(flatten)] pub lock_file_usage: LockFileUsageArgs, @@ -38,12 +45,8 @@ pub struct Args { pub prefix_update_config: PrefixUpdateConfig, } -fn cwd() -> PathBuf { - std::env::current_dir().expect("failed to obtain current working directory") -} - fn build_explicit_spec<'a>( - platform: Platform, + platform: &Platform, conda_packages: impl IntoIterator, ) -> miette::Result { let mut packages = Vec::new(); @@ -64,15 +67,19 @@ fn build_explicit_spec<'a>( } Ok(ExplicitEnvironmentSpec { - platform: Some(platform), + platform: Some(*platform), packages, }) } -fn write_explicit_spec( +fn render_explicit_spec( target: impl AsRef, exp_env_spec: &ExplicitEnvironmentSpec, ) -> miette::Result<()> { + if exp_env_spec.packages.is_empty() { + return Ok(()); + } + let mut environment = String::new(); environment.push_str("# Generated by `pixi project export`\n"); environment.push_str(exp_env_spec.to_spec_string().as_str()); @@ -96,10 +103,14 @@ fn get_pypi_hash_str(package_data: &PypiPackageData) -> Option { } } -fn write_pypi_requirements( +fn render_pypi_requirements( target: impl AsRef, packages: &[PypiPackage], ) -> miette::Result<()> { + if packages.is_empty() { + return Ok(()); + } + let mut reqs = String::new(); for p in packages { @@ -136,37 +147,17 @@ fn write_pypi_requirements( Ok(()) } -pub async fn execute(project: Project, args: Args) -> miette::Result<()> { - let environment = project.environment_from_name_or_env_var(args.environment)?; - // Load the platform - let platform = args.platform.unwrap_or_else(|| environment.best_platform()); - - let lock_file = project - .update_lock_file(UpdateLockFileOptions { - lock_file_usage: args.prefix_update_config.lock_file_usage(), - no_install: args.prefix_update_config.no_install, - ..UpdateLockFileOptions::default() - }) - .await? - .lock_file; - - let env = lock_file - .environment(environment.name().as_str()) - .ok_or(miette::miette!( - "unknown environment '{}' in {}", - environment.name(), - project - .manifest_path() - .to_str() - .expect("expected to have a manifest_path") - ))?; - - let packages = env.packages(platform).ok_or(miette::miette!( - "platform '{platform}' not found in {}", - project - .manifest_path() - .to_str() - .expect("expected to have a manifest_path"), +fn render_env_platform( + output_dir: &Path, + env_name: &str, + env: &Environment, + platform: &Platform, + ignore_pypi_errors: bool, + write_pypi_requirements: bool, +) -> miette::Result<()> { + let packages = env.packages(*platform).ok_or(miette::miette!( + "platform '{platform}' not found for env {}", + env_name, ))?; let mut conda_packages_from_lockfile: Vec = Vec::new(); @@ -176,13 +167,13 @@ pub async fn execute(project: Project, args: Args) -> miette::Result<()> { match package { Package::Conda(p) => conda_packages_from_lockfile.push(p), Package::Pypi(pyp) => { - if args.ignore_pypi_errors { + if ignore_pypi_errors { tracing::warn!("ignoring PyPI package since PyPI packages are not supported"); - } else if args.write_pypi_requirements { + } else if write_pypi_requirements { pypi_packages_from_lockfile.push(pyp); } else { miette::bail!( - "PyPI packages are not supported. Specify `--ignore-pypi-errors` to ignore this error\ + "PyPI packages are not supported. Specify `--ignore-pypi-errors` to ignore this error \ or `--write-pypi-requirements` to write pypi requirements to a separate requirements.txt file" ); } @@ -192,28 +183,86 @@ pub async fn execute(project: Project, args: Args) -> miette::Result<()> { let ees = build_explicit_spec(platform, &conda_packages_from_lockfile)?; - tracing::info!("Creating conda lock file"); - let target = cwd() - .join(format!( - "conda-{}-{}.lock", - platform, - environment.name().as_str() - )) + tracing::info!("Creating conda explicit spec for env: {env_name} platform: {platform}"); + let target = output_dir + .join(format!("{}-{}-conda_spec.txt", env_name, platform)) .into_os_string(); - write_explicit_spec(target, &ees)?; + render_explicit_spec(target, &ees)?; - if args.write_pypi_requirements { - tracing::info!("Creating conda lock file"); - let pypi_target = cwd() - .join(format!( - "requirements-{}-{}.txt", - platform, - environment.name().as_str() - )) + if write_pypi_requirements { + tracing::info!("Creating pypi requirements file for env: {env_name} platform: {platform}"); + let pypi_target = output_dir + .join(format!("{}-{}-requirements.txt", env_name, platform)) .into_os_string(); - write_pypi_requirements(pypi_target, &pypi_packages_from_lockfile)?; + render_pypi_requirements(pypi_target, &pypi_packages_from_lockfile)?; + } + + Ok(()) +} + +pub async fn execute(project: Project, args: Args) -> miette::Result<()> { + let lockfile = project + .update_lock_file(UpdateLockFileOptions { + lock_file_usage: args.prefix_update_config.lock_file_usage(), + no_install: args.prefix_update_config.no_install, + ..UpdateLockFileOptions::default() + }) + .await? + .lock_file; + + let mut environments = Vec::new(); + if let Some(env_names) = args.environment { + for env_name in &env_names { + environments.push(( + env_name.to_string(), + lockfile + .environment(env_name) + .ok_or(miette::miette!("unknown environment {}", env_name))?, + )); + } + } else { + for (env_name, env) in lockfile.environments() { + environments.push((env_name.to_string(), env)); + } + }; + + let mut env_platform = Vec::new(); + + for (env_name, env) in environments { + let available_platforms: HashSet = HashSet::from_iter(env.platforms()); + + if let Some(ref platforms) = args.platform { + for plat in platforms { + if available_platforms.contains(plat) { + env_platform.push((env_name.clone(), env.clone(), *plat)); + } else { + tracing::warn!( + "Platform {} not available for environment {}. Skipping...", + plat, + env_name, + ); + } + } + } else { + for plat in available_platforms { + env_platform.push((env_name.clone(), env.clone(), plat)); + } + } + } + + fs::create_dir_all(&args.output_dir).ok(); + + for (env_name, env, plat) in env_platform { + render_env_platform( + &args.output_dir, + &env_name, + &env, + &plat, + args.ignore_pypi_errors, + args.write_pypi_requirements, + )?; } Ok(()) From c1a4e30fe19057a1a1d2d052a5fb15c39c4b6993 Mon Sep 17 00:00:00 2001 From: "Joshua L. Adelman" Date: Sat, 24 Aug 2024 21:36:45 -0400 Subject: [PATCH 04/19] update export conda_explicit_spec cli docs --- docs/reference/cli.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 71e0198a6..d66c12fd7 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -1190,6 +1190,23 @@ mamba create --name --file As the explicit specification file format does not support pypi-dependencies, use either the `--ignore-pypi-errors` option to ignore those dependencies or `--write-pypi-requirements` to write a `requirements.txt` file that can then be used to install the packages into a conda env using `pip` or `uv`. +##### Arguments + +1. ``: Output directory for rendered explicit environment spec files. + +##### Options + +- `--environment (-e)`: Environment to render. Can be repeated for multiple envs. Defaults to all environments. +- `--platform (-p)`: The platform to render. Can be repeated for multiple platforms. Defaults to all platforms available for selected environments. +- `--ignore-pypi-errors`: PyPI dependencies are not supported in the conda explicit spec file. This flag allows creating the spec file even if PyPI dependencies are present. Alternatively see `--write-pypi-requirements`. +- `--write-pypi-requirements`: Write a requirements file containing all pypi dependencies. + +```sh +pixi project export conda_explicit_spec output +pixi project export conda_explicit_spec --write-pypi-requirements -e default -e test -p linux-64 output +``` + + ### `project platform add` Adds a platform(s) to the manifest file and updates the lock file. From 00007d4f9b7c6722343d695038f66a0ebfb59638 Mon Sep 17 00:00:00 2001 From: "Joshua L. Adelman" Date: Thu, 5 Sep 2024 15:59:11 -0400 Subject: [PATCH 05/19] Remove pypi functionality from export conda explicit spec Additionally added snapshot testing --- docs/reference/cli.md | 8 +- src/cli/project/export/conda_explicit_spec.rs | 125 +- ..._conda_explicit_spec_default_linux-64.snap | 46 + ...er_conda_explicit_spec_default_osx-64.snap | 23 + ...der_conda_explicit_spec_test_linux-64.snap | 53 + ...ender_conda_explicit_spec_test_osx-64.snap | 30 + .../export/test-data/testenv/pixi.lock | 1268 +++++++++++++++++ .../export/test-data/testenv/pixi.toml | 27 + 8 files changed, 1494 insertions(+), 86 deletions(-) create mode 100644 src/cli/project/export/snapshots/pixi__cli__project__export__conda_explicit_spec__tests__test_render_conda_explicit_spec_default_linux-64.snap create mode 100644 src/cli/project/export/snapshots/pixi__cli__project__export__conda_explicit_spec__tests__test_render_conda_explicit_spec_default_osx-64.snap create mode 100644 src/cli/project/export/snapshots/pixi__cli__project__export__conda_explicit_spec__tests__test_render_conda_explicit_spec_test_linux-64.snap create mode 100644 src/cli/project/export/snapshots/pixi__cli__project__export__conda_explicit_spec__tests__test_render_conda_explicit_spec_test_osx-64.snap create mode 100644 src/cli/project/export/test-data/testenv/pixi.lock create mode 100644 src/cli/project/export/test-data/testenv/pixi.toml diff --git a/docs/reference/cli.md b/docs/reference/cli.md index d66c12fd7..bc5a8ee51 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -1187,8 +1187,7 @@ for an environment. The file can be then used to create a conda environment usin mamba create --name --file ``` -As the explicit specification file format does not support pypi-dependencies, use either the `--ignore-pypi-errors` option to ignore those dependencies -or `--write-pypi-requirements` to write a `requirements.txt` file that can then be used to install the packages into a conda env using `pip` or `uv`. +As the explicit specification file format does not support pypi-dependencies, use the `--ignore-pypi-errors` option to ignore those dependencies. ##### Arguments @@ -1198,12 +1197,11 @@ or `--write-pypi-requirements` to write a `requirements.txt` file that can then - `--environment (-e)`: Environment to render. Can be repeated for multiple envs. Defaults to all environments. - `--platform (-p)`: The platform to render. Can be repeated for multiple platforms. Defaults to all platforms available for selected environments. -- `--ignore-pypi-errors`: PyPI dependencies are not supported in the conda explicit spec file. This flag allows creating the spec file even if PyPI dependencies are present. Alternatively see `--write-pypi-requirements`. -- `--write-pypi-requirements`: Write a requirements file containing all pypi dependencies. +- `--ignore-pypi-errors`: PyPI dependencies are not supported in the conda explicit spec file. This flag allows creating the spec file even if PyPI dependencies are present. ```sh pixi project export conda_explicit_spec output -pixi project export conda_explicit_spec --write-pypi-requirements -e default -e test -p linux-64 output +pixi project export conda_explicit_spec -e default -e test -p linux-64 output ``` diff --git a/src/cli/project/export/conda_explicit_spec.rs b/src/cli/project/export/conda_explicit_spec.rs index ddd677be9..1d31b4ed7 100644 --- a/src/cli/project/export/conda_explicit_spec.rs +++ b/src/cli/project/export/conda_explicit_spec.rs @@ -9,9 +9,7 @@ use crate::cli::LockFileUsageArgs; use crate::lock_file::UpdateLockFileOptions; use crate::Project; use rattler_conda_types::{ExplicitEnvironmentEntry, ExplicitEnvironmentSpec, Platform}; -use rattler_lock::{ - CondaPackage, Environment, Package, PackageHashes, PypiPackage, PypiPackageData, UrlOrPath, -}; +use rattler_lock::{CondaPackage, Environment, Package}; #[derive(Debug, Parser)] #[clap(arg_required_else_help = false)] @@ -30,14 +28,9 @@ pub struct Args { /// PyPI dependencies are not supported in the conda explicit spec file. /// This flag allows creating the spec file even if PyPI dependencies are present. - /// Alternatively see --write-pypi-requirements #[arg(long, default_value = "false")] pub ignore_pypi_errors: bool, - /// Write a requirements file containing all pypi dependencies - #[arg(long, default_value = "false", conflicts_with = "ignore_pypi_errors")] - pub write_pypi_requirements: bool, - #[clap(flatten)] pub lock_file_usage: LockFileUsageArgs, @@ -90,70 +83,12 @@ fn render_explicit_spec( Ok(()) } -fn get_pypi_hash_str(package_data: &PypiPackageData) -> Option { - if let Some(hashes) = &package_data.hash { - let h = match hashes { - PackageHashes::Sha256(h) => format!("--hash=sha256:{:x}", h).to_string(), - PackageHashes::Md5Sha256(_, h) => format!("--hash=sha256:{:x}", h).to_string(), - PackageHashes::Md5(h) => format!("--hash=md5:{:x}", h).to_string(), - }; - Some(h) - } else { - None - } -} - -fn render_pypi_requirements( - target: impl AsRef, - packages: &[PypiPackage], -) -> miette::Result<()> { - if packages.is_empty() { - return Ok(()); - } - - let mut reqs = String::new(); - - for p in packages { - // pip --verify-hashes does not accept hashes for local files - let (s, include_hash) = match p.url() { - UrlOrPath::Url(url) => (url.as_str(), true), - UrlOrPath::Path(path) => ( - path.as_os_str() - .to_str() - .unwrap_or_else(|| panic!("Could not convert {:?} to str", path)), - false, - ), - }; - - // remove "direct+ since not valid for pip urls" - let s = s.trim_start_matches("direct+"); - - let hash = match (include_hash, get_pypi_hash_str(p.data().package)) { - (true, Some(h)) => format!(" {}", h), - (false, _) => "".to_string(), - (_, None) => "".to_string(), - }; - - if p.is_editable() { - reqs.push_str(&format!("-e {}{}\n", s, hash)); - } else { - reqs.push_str(&format!("{}{}\n", s, hash)); - } - } - - fs::write(target, reqs) - .map_err(|e| miette::miette!("Could not write requirements file: {}", e))?; - - Ok(()) -} - fn render_env_platform( output_dir: &Path, env_name: &str, env: &Environment, platform: &Platform, ignore_pypi_errors: bool, - write_pypi_requirements: bool, ) -> miette::Result<()> { let packages = env.packages(*platform).ok_or(miette::miette!( "platform '{platform}' not found for env {}", @@ -161,16 +96,16 @@ fn render_env_platform( ))?; let mut conda_packages_from_lockfile: Vec = Vec::new(); - let mut pypi_packages_from_lockfile: Vec = Vec::new(); for package in packages { match package { Package::Conda(p) => conda_packages_from_lockfile.push(p), Package::Pypi(pyp) => { if ignore_pypi_errors { - tracing::warn!("ignoring PyPI package since PyPI packages are not supported"); - } else if write_pypi_requirements { - pypi_packages_from_lockfile.push(pyp); + tracing::warn!( + "ignoring PyPI package {} since PyPI packages are not supported", + pyp.data().package.name + ); } else { miette::bail!( "PyPI packages are not supported. Specify `--ignore-pypi-errors` to ignore this error \ @@ -185,20 +120,11 @@ fn render_env_platform( tracing::info!("Creating conda explicit spec for env: {env_name} platform: {platform}"); let target = output_dir - .join(format!("{}-{}-conda_spec.txt", env_name, platform)) + .join(format!("{}_{}_conda_spec.txt", env_name, platform)) .into_os_string(); render_explicit_spec(target, &ees)?; - if write_pypi_requirements { - tracing::info!("Creating pypi requirements file for env: {env_name} platform: {platform}"); - let pypi_target = output_dir - .join(format!("{}-{}-requirements.txt", env_name, platform)) - .into_os_string(); - - render_pypi_requirements(pypi_target, &pypi_packages_from_lockfile)?; - } - Ok(()) } @@ -261,9 +187,46 @@ pub async fn execute(project: Project, args: Args) -> miette::Result<()> { &env, &plat, args.ignore_pypi_errors, - args.write_pypi_requirements, )?; } Ok(()) } + +#[cfg(test)] +mod tests { + use std::path::Path; + + use super::*; + use rattler_lock::LockFile; + use tempfile::tempdir; + + #[test] + fn test_render_conda_explicit_spec() { + let path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("src/cli/project/export/test-data/testenv/pixi.lock"); + let lockfile = LockFile::from_path(&path).unwrap(); + + let output_dir = tempdir().unwrap(); + + for (env_name, env) in lockfile.environments() { + for platform in env.platforms() { + // example contains pypi dependencies so should fail if `ignore_pypi_errors` is + // false. + assert!( + render_env_platform(output_dir.path(), env_name, &env, &platform, false) + .is_err() + ); + render_env_platform(output_dir.path(), env_name, &env, &platform, true).unwrap(); + + let file_path = output_dir + .path() + .join(format!("{}_{}_conda_spec.txt", env_name, platform)); + insta::assert_snapshot!( + format!("test_render_conda_explicit_spec_{}_{}", env_name, platform), + fs::read_to_string(file_path).unwrap() + ); + } + } + } +} diff --git a/src/cli/project/export/snapshots/pixi__cli__project__export__conda_explicit_spec__tests__test_render_conda_explicit_spec_default_linux-64.snap b/src/cli/project/export/snapshots/pixi__cli__project__export__conda_explicit_spec__tests__test_render_conda_explicit_spec_default_linux-64.snap new file mode 100644 index 000000000..d9185f076 --- /dev/null +++ b/src/cli/project/export/snapshots/pixi__cli__project__export__conda_explicit_spec__tests__test_render_conda_explicit_spec_default_linux-64.snap @@ -0,0 +1,46 @@ +--- +source: src/cli/project/export/conda_explicit_spec.rs +expression: "fs::read_to_string(file_path).unwrap()" +--- +# Generated by `pixi project export` +# platform: linux-64 +@EXPLICIT +https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 +https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2#73aaf86a425cc6e73fcf236a5a46396d +https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda#b0b867af6fc74b2a0aa206da29c0f3cf +https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda#62ee74e96c5ebb0af99386de58cf9553 +https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.8.30-hbcca054_0.conda#c27d1c142233b5bc9ca570c6e2e0c244 +https://conda.anaconda.org/conda-forge/noarch/certifi-2024.8.30-pyhd8ed1ab_0.conda#12f7d00853807b0531775e9be891cb11 +https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.0-py312h06ac9bb_1.conda#db9bdbaee0f524ead0471689f002781e +https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.3.2-pyhd8ed1ab_0.conda#7f4a9e3fcff3f6356ae99244a014da6a +https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_0.tar.bz2#b748fbf7060927a6e82df7cb5ee8f097 +https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyh9f0ad1d_0.tar.bz2#914d6646c4dbb1fd3ff539830a12fd71 +https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_0.tar.bz2#9f765cbfab6870c8435b9eefecd7a1f4 +https://conda.anaconda.org/conda-forge/noarch/idna-3.8-pyhd8ed1ab_0.conda#99e164522f6bdf23c177c8d9ae63f975 +https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-hf3520f5_7.conda#b80f2f396ca2c28b8c14c437a4ed1e74 +https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.2-h59595ed_0.conda#e7ba12deb7020dd080c6c70e7b6f6a3d +https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 +https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.1.0-h77fa898_1.conda#002ef4463dd1e2b44a94a4ace468f5d2 +https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.1.0-h69a702a_1.conda#1efc0ad219877a73ef977af7dbb51f17 +https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.1.0-h77fa898_1.conda#23c255b008c4f2ae008f81edcabaca89 +https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda#30fd6e37fe21f86f4bd26d6ee73eeec7 +https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.46.1-hadc24fc_0.conda#36f79405ab16bf271edb55b213836dac +https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.1.0-hc0a3c3a_1.conda#9dbb9699ea467983ba8a4ba89b08b066 +https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.1.0-h4852527_1.conda#bd2598399a70bb86d8218e95548d735e +https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda#40b61aab5c7ba9ff276c41cfffe6b80b +https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda#5aa797f8787fe7a17d1b0821485b5adc +https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-h4ab18f5_1.conda#57d7dc60e9325e3de37ff8dffd18e814 +https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda#70caf8bb6cf39a0b6b7efc885f51c0fe +https://conda.anaconda.org/conda-forge/linux-64/openssl-3.3.2-hb9d3cd8_0.conda#4d638782050ab6faa27275bed57e9b4e +https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyhd8ed1ab_0.conda#844d9eb3b43095b031874477f7d70088 +https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 +https://conda.anaconda.org/conda-forge/linux-64/python-3.12.5-h2ad013b_0_cpython.conda#9c56c4df45f6571b13111d8df2448692 +https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda#0424ae29b104430108f5218a66db7260 +https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda#47d31b792659ce70f470b5c82fdfb7a4 +https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_0.conda#5ede4753180c7a550a443c430dc8ab52 +https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda#d453b98d9c83e71da0741bb0ff4d76bc +https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h8827d51_1.conda#8bfdead4e0fff0383ae4c9c50d0531bd +https://conda.anaconda.org/conda-forge/noarch/urllib3-2.2.2-pyhd8ed1ab_1.conda#e804c43f58255e977093a2298e442bb8 +https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0 +https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda#8b7069e9792ee4e5b4919a7a306d2e67 +https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda#4d056880988120e29d75bfff282e0f45 diff --git a/src/cli/project/export/snapshots/pixi__cli__project__export__conda_explicit_spec__tests__test_render_conda_explicit_spec_default_osx-64.snap b/src/cli/project/export/snapshots/pixi__cli__project__export__conda_explicit_spec__tests__test_render_conda_explicit_spec_default_osx-64.snap new file mode 100644 index 000000000..891d98a1c --- /dev/null +++ b/src/cli/project/export/snapshots/pixi__cli__project__export__conda_explicit_spec__tests__test_render_conda_explicit_spec_default_osx-64.snap @@ -0,0 +1,23 @@ +--- +source: src/cli/project/export/conda_explicit_spec.rs +expression: "fs::read_to_string(file_path).unwrap()" +--- +# Generated by `pixi project export` +# platform: osx-64 +@EXPLICIT +https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-hfdf4475_7.conda#7ed4301d437b59045be7e051a0308211 +https://conda.anaconda.org/conda-forge/osx-64/ca-certificates-2024.8.30-h8857fd0_0.conda#b7e5424e7f06547a903d28e4651dbb21 +https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.6.2-h73e2aa4_0.conda#3d1d51c8f716d97c864d12f7af329526 +https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.2-h0d85af4_5.tar.bz2#ccb34fb14960ad8b125962d3d79b31a9 +https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.46.1-h4b8f8c9_0.conda#84de0078b58f899fc164303b0603ff0e +https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-h87427d6_1.conda#b7575b5aa92108dcc9aaab0f05f2dbce +https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-hf036a51_1.conda#e102bbf8a6ceeaf429deab8032fc8977 +https://conda.anaconda.org/conda-forge/osx-64/openssl-3.3.2-hd23fc13_0.conda#2ff47134c8e292868a4609519b1ea3b6 +https://conda.anaconda.org/conda-forge/osx-64/python-3.12.5-h37a9e06_0_cpython.conda#517cb4e16466f8d96ba2a72897d14c48 +https://conda.anaconda.org/conda-forge/osx-64/python_abi-3.12-5_cp312.conda#c34dd4920e0addf7cfcc725809f25d8e +https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0.2-py312hb553811_1.conda#66514594817d51c78db7109a23ad322f +https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h9e318b2_1.conda#f17f77f2acf4d344734bda76829ce14e +https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h1abcd95_1.conda#bf830ba5afc507c6232d4ef0fb1a882d +https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h8827d51_1.conda#8bfdead4e0fff0383ae4c9c50d0531bd +https://conda.anaconda.org/conda-forge/osx-64/xz-5.2.6-h775f41a_0.tar.bz2#a72f9d4ea13d55d745ff1ed594747f10 +https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h0d85af4_2.tar.bz2#d7e08fcf8259d742156188e8762b4d20 diff --git a/src/cli/project/export/snapshots/pixi__cli__project__export__conda_explicit_spec__tests__test_render_conda_explicit_spec_test_linux-64.snap b/src/cli/project/export/snapshots/pixi__cli__project__export__conda_explicit_spec__tests__test_render_conda_explicit_spec_test_linux-64.snap new file mode 100644 index 000000000..31dd3cf3e --- /dev/null +++ b/src/cli/project/export/snapshots/pixi__cli__project__export__conda_explicit_spec__tests__test_render_conda_explicit_spec_test_linux-64.snap @@ -0,0 +1,53 @@ +--- +source: src/cli/project/export/conda_explicit_spec.rs +expression: "fs::read_to_string(file_path).unwrap()" +--- +# Generated by `pixi project export` +# platform: linux-64 +@EXPLICIT +https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 +https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2#73aaf86a425cc6e73fcf236a5a46396d +https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda#b0b867af6fc74b2a0aa206da29c0f3cf +https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda#62ee74e96c5ebb0af99386de58cf9553 +https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.8.30-hbcca054_0.conda#c27d1c142233b5bc9ca570c6e2e0c244 +https://conda.anaconda.org/conda-forge/noarch/certifi-2024.8.30-pyhd8ed1ab_0.conda#12f7d00853807b0531775e9be891cb11 +https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.0-py312h06ac9bb_1.conda#db9bdbaee0f524ead0471689f002781e +https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.3.2-pyhd8ed1ab_0.conda#7f4a9e3fcff3f6356ae99244a014da6a +https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2#3faab06a954c2a04039983f2c4a50d99 +https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda#d02ae936e42063ca46af6cdad2dbd1e0 +https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_0.tar.bz2#b748fbf7060927a6e82df7cb5ee8f097 +https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyh9f0ad1d_0.tar.bz2#914d6646c4dbb1fd3ff539830a12fd71 +https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_0.tar.bz2#9f765cbfab6870c8435b9eefecd7a1f4 +https://conda.anaconda.org/conda-forge/noarch/idna-3.8-pyhd8ed1ab_0.conda#99e164522f6bdf23c177c8d9ae63f975 +https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda#f800d2da156d08e289b14e87e43c1ae5 +https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-hf3520f5_7.conda#b80f2f396ca2c28b8c14c437a4ed1e74 +https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.2-h59595ed_0.conda#e7ba12deb7020dd080c6c70e7b6f6a3d +https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 +https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.1.0-h77fa898_1.conda#002ef4463dd1e2b44a94a4ace468f5d2 +https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.1.0-h69a702a_1.conda#1efc0ad219877a73ef977af7dbb51f17 +https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.1.0-h77fa898_1.conda#23c255b008c4f2ae008f81edcabaca89 +https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda#30fd6e37fe21f86f4bd26d6ee73eeec7 +https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.46.1-hadc24fc_0.conda#36f79405ab16bf271edb55b213836dac +https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.1.0-hc0a3c3a_1.conda#9dbb9699ea467983ba8a4ba89b08b066 +https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.1.0-h4852527_1.conda#bd2598399a70bb86d8218e95548d735e +https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda#40b61aab5c7ba9ff276c41cfffe6b80b +https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda#5aa797f8787fe7a17d1b0821485b5adc +https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-h4ab18f5_1.conda#57d7dc60e9325e3de37ff8dffd18e814 +https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda#70caf8bb6cf39a0b6b7efc885f51c0fe +https://conda.anaconda.org/conda-forge/linux-64/openssl-3.3.2-hb9d3cd8_0.conda#4d638782050ab6faa27275bed57e9b4e +https://conda.anaconda.org/conda-forge/noarch/packaging-24.1-pyhd8ed1ab_0.conda#cbe1bb1f21567018ce595d9c2be0f0db +https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_0.conda#d3483c8fc2dc2cc3f5cf43e26d60cabf +https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyhd8ed1ab_0.conda#844d9eb3b43095b031874477f7d70088 +https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 +https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.2-pyhd8ed1ab_0.conda#e010a224b90f1f623a917c35addbb924 +https://conda.anaconda.org/conda-forge/linux-64/python-3.12.5-h2ad013b_0_cpython.conda#9c56c4df45f6571b13111d8df2448692 +https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda#0424ae29b104430108f5218a66db7260 +https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda#47d31b792659ce70f470b5c82fdfb7a4 +https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_0.conda#5ede4753180c7a550a443c430dc8ab52 +https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda#d453b98d9c83e71da0741bb0ff4d76bc +https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96 +https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h8827d51_1.conda#8bfdead4e0fff0383ae4c9c50d0531bd +https://conda.anaconda.org/conda-forge/noarch/urllib3-2.2.2-pyhd8ed1ab_1.conda#e804c43f58255e977093a2298e442bb8 +https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0 +https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda#8b7069e9792ee4e5b4919a7a306d2e67 +https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda#4d056880988120e29d75bfff282e0f45 diff --git a/src/cli/project/export/snapshots/pixi__cli__project__export__conda_explicit_spec__tests__test_render_conda_explicit_spec_test_osx-64.snap b/src/cli/project/export/snapshots/pixi__cli__project__export__conda_explicit_spec__tests__test_render_conda_explicit_spec_test_osx-64.snap new file mode 100644 index 000000000..c638cef69 --- /dev/null +++ b/src/cli/project/export/snapshots/pixi__cli__project__export__conda_explicit_spec__tests__test_render_conda_explicit_spec_test_osx-64.snap @@ -0,0 +1,30 @@ +--- +source: src/cli/project/export/conda_explicit_spec.rs +expression: "fs::read_to_string(file_path).unwrap()" +--- +# Generated by `pixi project export` +# platform: osx-64 +@EXPLICIT +https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-hfdf4475_7.conda#7ed4301d437b59045be7e051a0308211 +https://conda.anaconda.org/conda-forge/osx-64/ca-certificates-2024.8.30-h8857fd0_0.conda#b7e5424e7f06547a903d28e4651dbb21 +https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2#3faab06a954c2a04039983f2c4a50d99 +https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda#d02ae936e42063ca46af6cdad2dbd1e0 +https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda#f800d2da156d08e289b14e87e43c1ae5 +https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.6.2-h73e2aa4_0.conda#3d1d51c8f716d97c864d12f7af329526 +https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.2-h0d85af4_5.tar.bz2#ccb34fb14960ad8b125962d3d79b31a9 +https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.46.1-h4b8f8c9_0.conda#84de0078b58f899fc164303b0603ff0e +https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-h87427d6_1.conda#b7575b5aa92108dcc9aaab0f05f2dbce +https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-hf036a51_1.conda#e102bbf8a6ceeaf429deab8032fc8977 +https://conda.anaconda.org/conda-forge/osx-64/openssl-3.3.2-hd23fc13_0.conda#2ff47134c8e292868a4609519b1ea3b6 +https://conda.anaconda.org/conda-forge/noarch/packaging-24.1-pyhd8ed1ab_0.conda#cbe1bb1f21567018ce595d9c2be0f0db +https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_0.conda#d3483c8fc2dc2cc3f5cf43e26d60cabf +https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.2-pyhd8ed1ab_0.conda#e010a224b90f1f623a917c35addbb924 +https://conda.anaconda.org/conda-forge/osx-64/python-3.12.5-h37a9e06_0_cpython.conda#517cb4e16466f8d96ba2a72897d14c48 +https://conda.anaconda.org/conda-forge/osx-64/python_abi-3.12-5_cp312.conda#c34dd4920e0addf7cfcc725809f25d8e +https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0.2-py312hb553811_1.conda#66514594817d51c78db7109a23ad322f +https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h9e318b2_1.conda#f17f77f2acf4d344734bda76829ce14e +https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h1abcd95_1.conda#bf830ba5afc507c6232d4ef0fb1a882d +https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96 +https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h8827d51_1.conda#8bfdead4e0fff0383ae4c9c50d0531bd +https://conda.anaconda.org/conda-forge/osx-64/xz-5.2.6-h775f41a_0.tar.bz2#a72f9d4ea13d55d745ff1ed594747f10 +https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h0d85af4_2.tar.bz2#d7e08fcf8259d742156188e8762b4d20 diff --git a/src/cli/project/export/test-data/testenv/pixi.lock b/src/cli/project/export/test-data/testenv/pixi.lock new file mode 100644 index 000000000..ae2f5570f --- /dev/null +++ b/src/cli/project/export/test-data/testenv/pixi.lock @@ -0,0 +1,1268 @@ +version: 5 +environments: + default: + channels: + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.8.30-hbcca054_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2024.8.30-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.0-py312h06ac9bb_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.3.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyh9f0ad1d_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-hf3520f5_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.2-h59595ed_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.1.0-h77fa898_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.1.0-h69a702a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.1.0-h77fa898_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.46.1-hadc24fc_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.1.0-hc0a3c3a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.1.0-h4852527_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-h4ab18f5_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.3.2-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.5-h2ad013b_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h8827d51_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.2.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda + - pypi: https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/d9/c2a126eeae791e90ea099d05cb0515feea3688474b978343f3cdcfe04523/rich-13.8.0-py3-none-any.whl + osx-64: + - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-hfdf4475_7.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ca-certificates-2024.8.30-h8857fd0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.6.2-h73e2aa4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.2-h0d85af4_5.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.46.1-h4b8f8c9_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-h87427d6_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-hf036a51_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.3.2-hd23fc13_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.12.5-h37a9e06_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python_abi-3.12-5_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0.2-py312hb553811_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h9e318b2_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h1abcd95_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h8827d51_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xz-5.2.6-h775f41a_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h0d85af4_2.tar.bz2 + - pypi: https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/d9/c2a126eeae791e90ea099d05cb0515feea3688474b978343f3cdcfe04523/rich-13.8.0-py3-none-any.whl + test: + channels: + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.8.30-hbcca054_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2024.8.30-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.0-py312h06ac9bb_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.3.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyh9f0ad1d_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-hf3520f5_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.2-h59595ed_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.1.0-h77fa898_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.1.0-h69a702a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.1.0-h77fa898_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.46.1-hadc24fc_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.1.0-hc0a3c3a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.1.0-h4852527_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-h4ab18f5_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.3.2-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.5-h2ad013b_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h8827d51_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.2.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda + - pypi: https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/d9/c2a126eeae791e90ea099d05cb0515feea3688474b978343f3cdcfe04523/rich-13.8.0-py3-none-any.whl + osx-64: + - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-hfdf4475_7.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ca-certificates-2024.8.30-h8857fd0_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.6.2-h73e2aa4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.2-h0d85af4_5.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.46.1-h4b8f8c9_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-h87427d6_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-hf036a51_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.3.2-hd23fc13_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.12.5-h37a9e06_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python_abi-3.12-5_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0.2-py312hb553811_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h9e318b2_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h1abcd95_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h8827d51_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xz-5.2.6-h775f41a_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h0d85af4_2.tar.bz2 + - pypi: https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/d9/c2a126eeae791e90ea099d05cb0515feea3688474b978343f3cdcfe04523/rich-13.8.0-py3-none-any.whl +packages: +- kind: conda + name: _libgcc_mutex + version: '0.1' + build: conda_forge + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 + md5: d7c89558ba9fa0495403155b64376d81 + license: None + purls: [] + size: 2562 + timestamp: 1578324546067 +- kind: conda + name: _openmp_mutex + version: '4.5' + build: 2_gnu + build_number: 16 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 + sha256: fbe2c5e56a653bebb982eda4876a9178aedfc2b545f25d0ce9c4c0b508253d22 + md5: 73aaf86a425cc6e73fcf236a5a46396d + depends: + - _libgcc_mutex 0.1 conda_forge + - libgomp >=7.5.0 + constrains: + - openmp_impl 9999 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 23621 + timestamp: 1650670423406 +- kind: conda + name: brotli-python + version: 1.1.0 + build: py312h2ec8cdc_2 + build_number: 2 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda + sha256: f2a59ccd20b4816dea9a2a5cb917eb69728271dbf1aeab4e1b7e609330a50b6f + md5: b0b867af6fc74b2a0aa206da29c0f3cf + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libstdcxx >=13 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + constrains: + - libbrotlicommon 1.1.0 hb9d3cd8_2 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 349867 + timestamp: 1725267732089 +- kind: conda + name: bzip2 + version: 1.0.8 + build: h4bc722e_7 + build_number: 7 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda + sha256: 5ced96500d945fb286c9c838e54fa759aa04a7129c59800f0846b4335cee770d + md5: 62ee74e96c5ebb0af99386de58cf9553 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc-ng >=12 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 252783 + timestamp: 1720974456583 +- kind: conda + name: bzip2 + version: 1.0.8 + build: hfdf4475_7 + build_number: 7 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-hfdf4475_7.conda + sha256: cad153608b81fb24fc8c509357daa9ae4e49dfc535b2cb49b91e23dbd68fc3c5 + md5: 7ed4301d437b59045be7e051a0308211 + depends: + - __osx >=10.13 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 134188 + timestamp: 1720974491916 +- kind: conda + name: ca-certificates + version: 2024.8.30 + build: h8857fd0_0 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/ca-certificates-2024.8.30-h8857fd0_0.conda + sha256: 593f302d0f44c2c771e1614ee6d56fffdc7d616e6f187669c8b0e34ffce3e1ae + md5: b7e5424e7f06547a903d28e4651dbb21 + license: ISC + purls: [] + size: 158665 + timestamp: 1725019059295 +- kind: conda + name: ca-certificates + version: 2024.8.30 + build: hbcca054_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.8.30-hbcca054_0.conda + sha256: afee721baa6d988e27fef1832f68d6f32ac8cc99cdf6015732224c2841a09cea + md5: c27d1c142233b5bc9ca570c6e2e0c244 + license: ISC + purls: [] + size: 159003 + timestamp: 1725018903918 +- kind: conda + name: certifi + version: 2024.8.30 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/certifi-2024.8.30-pyhd8ed1ab_0.conda + sha256: 7020770df338c45ac6b560185956c32f0a5abf4b76179c037f115fc7d687819f + md5: 12f7d00853807b0531775e9be891cb11 + depends: + - python >=3.7 + license: ISC + purls: + - pkg:pypi/certifi?source=hash-mapping + size: 163752 + timestamp: 1725278204397 +- kind: conda + name: cffi + version: 1.17.0 + build: py312h06ac9bb_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.0-py312h06ac9bb_1.conda + sha256: 397f588c30dd1a30236d289d8dc7f3c34cd71a498dc66d20450393014594cf4d + md5: db9bdbaee0f524ead0471689f002781e + depends: + - __glibc >=2.17,<3.0.a0 + - libffi >=3.4,<4.0a0 + - libgcc >=13 + - pycparser + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + size: 294242 + timestamp: 1724956485789 +- kind: conda + name: charset-normalizer + version: 3.3.2 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.3.2-pyhd8ed1ab_0.conda + sha256: 20cae47d31fdd58d99c4d2e65fbdcefa0b0de0c84e455ba9d6356a4bdbc4b5b9 + md5: 7f4a9e3fcff3f6356ae99244a014da6a + depends: + - python >=3.7 + license: MIT + license_family: MIT + purls: + - pkg:pypi/charset-normalizer?source=hash-mapping + size: 46597 + timestamp: 1698833765762 +- kind: conda + name: colorama + version: 0.4.6 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2 + sha256: 2c1b2e9755ce3102bca8d69e8f26e4f087ece73f50418186aee7c74bef8e1698 + md5: 3faab06a954c2a04039983f2c4a50d99 + depends: + - python >=3.7 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/colorama?source=hash-mapping + size: 25170 + timestamp: 1666700778190 +- kind: conda + name: exceptiongroup + version: 1.2.2 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda + sha256: e0edd30c4b7144406bb4da975e6bb97d6bc9c0e999aa4efe66ae108cada5d5b5 + md5: d02ae936e42063ca46af6cdad2dbd1e0 + depends: + - python >=3.7 + license: MIT and PSF-2.0 + purls: + - pkg:pypi/exceptiongroup?source=hash-mapping + size: 20418 + timestamp: 1720869435725 +- kind: conda + name: h2 + version: 4.1.0 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_0.tar.bz2 + sha256: bfc6a23849953647f4e255c782e74a0e18fe16f7e25c7bb0bc57b83bb6762c7a + md5: b748fbf7060927a6e82df7cb5ee8f097 + depends: + - hpack >=4.0,<5 + - hyperframe >=6.0,<7 + - python >=3.6.1 + license: MIT + license_family: MIT + purls: + - pkg:pypi/h2?source=hash-mapping + size: 46754 + timestamp: 1634280590080 +- kind: conda + name: hpack + version: 4.0.0 + build: pyh9f0ad1d_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyh9f0ad1d_0.tar.bz2 + sha256: 5dec948932c4f740674b1afb551223ada0c55103f4c7bf86a110454da3d27cb8 + md5: 914d6646c4dbb1fd3ff539830a12fd71 + depends: + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/hpack?source=hash-mapping + size: 25341 + timestamp: 1598856368685 +- kind: conda + name: hyperframe + version: 6.0.1 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_0.tar.bz2 + sha256: e374a9d0f53149328134a8d86f5d72bca4c6dcebed3c0ecfa968c02996289330 + md5: 9f765cbfab6870c8435b9eefecd7a1f4 + depends: + - python >=3.6 + license: MIT + license_family: MIT + purls: + - pkg:pypi/hyperframe?source=hash-mapping + size: 14646 + timestamp: 1619110249723 +- kind: conda + name: idna + version: '3.8' + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/idna-3.8-pyhd8ed1ab_0.conda + sha256: 8660d38b272d3713ec8ac5ae918bc3bc80e1b81e1a7d61df554bded71ada6110 + md5: 99e164522f6bdf23c177c8d9ae63f975 + depends: + - python >=3.6 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/idna?source=hash-mapping + size: 49275 + timestamp: 1724450633325 +- kind: conda + name: iniconfig + version: 2.0.0 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda + sha256: 38740c939b668b36a50ef455b077e8015b8c9cf89860d421b3fff86048f49666 + md5: f800d2da156d08e289b14e87e43c1ae5 + depends: + - python >=3.7 + license: MIT + license_family: MIT + purls: + - pkg:pypi/iniconfig?source=hash-mapping + size: 11101 + timestamp: 1673103208955 +- kind: conda + name: ld_impl_linux-64 + version: '2.40' + build: hf3520f5_7 + build_number: 7 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-hf3520f5_7.conda + sha256: 764b6950aceaaad0c67ef925417594dd14cd2e22fff864aeef455ac259263d15 + md5: b80f2f396ca2c28b8c14c437a4ed1e74 + constrains: + - binutils_impl_linux-64 2.40 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 707602 + timestamp: 1718625640445 +- kind: conda + name: libexpat + version: 2.6.2 + build: h59595ed_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.2-h59595ed_0.conda + sha256: 331bb7c7c05025343ebd79f86ae612b9e1e74d2687b8f3179faec234f986ce19 + md5: e7ba12deb7020dd080c6c70e7b6f6a3d + depends: + - libgcc-ng >=12 + constrains: + - expat 2.6.2.* + license: MIT + license_family: MIT + purls: [] + size: 73730 + timestamp: 1710362120304 +- kind: conda + name: libexpat + version: 2.6.2 + build: h73e2aa4_0 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.6.2-h73e2aa4_0.conda + sha256: a188a77b275d61159a32ab547f7d17892226e7dac4518d2c6ac3ac8fc8dfde92 + md5: 3d1d51c8f716d97c864d12f7af329526 + constrains: + - expat 2.6.2.* + license: MIT + license_family: MIT + purls: [] + size: 69246 + timestamp: 1710362566073 +- kind: conda + name: libffi + version: 3.4.2 + build: h0d85af4_5 + build_number: 5 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.2-h0d85af4_5.tar.bz2 + sha256: 7a2d27a936ceee6942ea4d397f9c7d136f12549d86f7617e8b6bad51e01a941f + md5: ccb34fb14960ad8b125962d3d79b31a9 + license: MIT + license_family: MIT + purls: [] + size: 51348 + timestamp: 1636488394370 +- kind: conda + name: libffi + version: 3.4.2 + build: h7f98852_5 + build_number: 5 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 + sha256: ab6e9856c21709b7b517e940ae7028ae0737546122f83c2aa5d692860c3b149e + md5: d645c6d2ac96843a2bfaccd2d62b3ac3 + depends: + - libgcc-ng >=9.4.0 + license: MIT + license_family: MIT + purls: [] + size: 58292 + timestamp: 1636488182923 +- kind: conda + name: libgcc + version: 14.1.0 + build: h77fa898_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.1.0-h77fa898_1.conda + sha256: 10fa74b69266a2be7b96db881e18fa62cfa03082b65231e8d652e897c4b335a3 + md5: 002ef4463dd1e2b44a94a4ace468f5d2 + depends: + - _libgcc_mutex 0.1 conda_forge + - _openmp_mutex >=4.5 + constrains: + - libgomp 14.1.0 h77fa898_1 + - libgcc-ng ==14.1.0=*_1 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 846380 + timestamp: 1724801836552 +- kind: conda + name: libgcc-ng + version: 14.1.0 + build: h69a702a_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.1.0-h69a702a_1.conda + sha256: b91f7021e14c3d5c840fbf0dc75370d6e1f7c7ff4482220940eaafb9c64613b7 + md5: 1efc0ad219877a73ef977af7dbb51f17 + depends: + - libgcc 14.1.0 h77fa898_1 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 52170 + timestamp: 1724801842101 +- kind: conda + name: libgomp + version: 14.1.0 + build: h77fa898_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.1.0-h77fa898_1.conda + sha256: c96724c8ae4ee61af7674c5d9e5a3fbcf6cd887a40ad5a52c99aa36f1d4f9680 + md5: 23c255b008c4f2ae008f81edcabaca89 + depends: + - _libgcc_mutex 0.1 conda_forge + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 460218 + timestamp: 1724801743478 +- kind: conda + name: libnsl + version: 2.0.1 + build: hd590300_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda + sha256: 26d77a3bb4dceeedc2a41bd688564fe71bf2d149fdcf117049970bc02ff1add6 + md5: 30fd6e37fe21f86f4bd26d6ee73eeec7 + depends: + - libgcc-ng >=12 + license: LGPL-2.1-only + license_family: GPL + purls: [] + size: 33408 + timestamp: 1697359010159 +- kind: conda + name: libsqlite + version: 3.46.1 + build: h4b8f8c9_0 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.46.1-h4b8f8c9_0.conda + sha256: 1d075cb823f0cad7e196871b7c57961d669cbbb6cd0e798bf50cbf520dda65fb + md5: 84de0078b58f899fc164303b0603ff0e + depends: + - __osx >=10.13 + - libzlib >=1.3.1,<2.0a0 + license: Unlicense + purls: [] + size: 908317 + timestamp: 1725353652135 +- kind: conda + name: libsqlite + version: 3.46.1 + build: hadc24fc_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.46.1-hadc24fc_0.conda + sha256: 9851c049abafed3ee329d6c7c2033407e2fc269d33a75c071110ab52300002b0 + md5: 36f79405ab16bf271edb55b213836dac + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libzlib >=1.3.1,<2.0a0 + license: Unlicense + purls: [] + size: 865214 + timestamp: 1725353659783 +- kind: conda + name: libstdcxx + version: 14.1.0 + build: hc0a3c3a_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.1.0-hc0a3c3a_1.conda + sha256: 44decb3d23abacf1c6dd59f3c152a7101b7ca565b4ef8872804ceaedcc53a9cd + md5: 9dbb9699ea467983ba8a4ba89b08b066 + depends: + - libgcc 14.1.0 h77fa898_1 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 3892781 + timestamp: 1724801863728 +- kind: conda + name: libstdcxx-ng + version: 14.1.0 + build: h4852527_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.1.0-h4852527_1.conda + sha256: a2dc44f97290740cc187bfe94ce543e6eb3c2ea8964d99f189a1d8c97b419b8c + md5: bd2598399a70bb86d8218e95548d735e + depends: + - libstdcxx 14.1.0 hc0a3c3a_1 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 52219 + timestamp: 1724801897766 +- kind: conda + name: libuuid + version: 2.38.1 + build: h0b41bf4_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda + sha256: 787eb542f055a2b3de553614b25f09eefb0a0931b0c87dbcce6efdfd92f04f18 + md5: 40b61aab5c7ba9ff276c41cfffe6b80b + depends: + - libgcc-ng >=12 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 33601 + timestamp: 1680112270483 +- kind: conda + name: libxcrypt + version: 4.4.36 + build: hd590300_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + sha256: 6ae68e0b86423ef188196fff6207ed0c8195dd84273cb5623b85aa08033a410c + md5: 5aa797f8787fe7a17d1b0821485b5adc + depends: + - libgcc-ng >=12 + license: LGPL-2.1-or-later + purls: [] + size: 100393 + timestamp: 1702724383534 +- kind: conda + name: libzlib + version: 1.3.1 + build: h4ab18f5_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-h4ab18f5_1.conda + sha256: adf6096f98b537a11ae3729eaa642b0811478f0ea0402ca67b5108fe2cb0010d + md5: 57d7dc60e9325e3de37ff8dffd18e814 + depends: + - libgcc-ng >=12 + constrains: + - zlib 1.3.1 *_1 + license: Zlib + license_family: Other + purls: [] + size: 61574 + timestamp: 1716874187109 +- kind: conda + name: libzlib + version: 1.3.1 + build: h87427d6_1 + build_number: 1 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-h87427d6_1.conda + sha256: 80a62db652b1da0ccc100812a1d86e94f75028968991bfb17f9536f3aa72d91d + md5: b7575b5aa92108dcc9aaab0f05f2dbce + depends: + - __osx >=10.13 + constrains: + - zlib 1.3.1 *_1 + license: Zlib + license_family: Other + purls: [] + size: 57372 + timestamp: 1716874211519 +- kind: pypi + name: markdown-it-py + version: 3.0.0 + url: https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl + sha256: 355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 + requires_dist: + - mdurl~=0.1 + - psutil ; extra == 'benchmarking' + - pytest ; extra == 'benchmarking' + - pytest-benchmark ; extra == 'benchmarking' + - pre-commit~=3.0 ; extra == 'code-style' + - commonmark~=0.9 ; extra == 'compare' + - markdown~=3.4 ; extra == 'compare' + - mistletoe~=1.0 ; extra == 'compare' + - mistune~=2.0 ; extra == 'compare' + - panflute~=2.3 ; extra == 'compare' + - linkify-it-py>=1,<3 ; extra == 'linkify' + - mdit-py-plugins ; extra == 'plugins' + - gprof2dot ; extra == 'profiling' + - mdit-py-plugins ; extra == 'rtd' + - myst-parser ; extra == 'rtd' + - pyyaml ; extra == 'rtd' + - sphinx ; extra == 'rtd' + - sphinx-copybutton ; extra == 'rtd' + - sphinx-design ; extra == 'rtd' + - sphinx-book-theme ; extra == 'rtd' + - jupyter-sphinx ; extra == 'rtd' + - coverage ; extra == 'testing' + - pytest ; extra == 'testing' + - pytest-cov ; extra == 'testing' + - pytest-regressions ; extra == 'testing' + requires_python: '>=3.8' +- kind: pypi + name: mdurl + version: 0.1.2 + url: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + sha256: 84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 + requires_python: '>=3.7' +- kind: conda + name: ncurses + version: '6.5' + build: he02047a_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda + sha256: 6a1d5d8634c1a07913f1c525db6455918cbc589d745fac46d9d6e30340c8731a + md5: 70caf8bb6cf39a0b6b7efc885f51c0fe + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc-ng >=12 + license: X11 AND BSD-3-Clause + purls: [] + size: 889086 + timestamp: 1724658547447 +- kind: conda + name: ncurses + version: '6.5' + build: hf036a51_1 + build_number: 1 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-hf036a51_1.conda + sha256: b0b3180039ef19502525a2abd5833c00f9624af830fd391f851934d57bffb9af + md5: e102bbf8a6ceeaf429deab8032fc8977 + depends: + - __osx >=10.13 + license: X11 AND BSD-3-Clause + purls: [] + size: 822066 + timestamp: 1724658603042 +- kind: conda + name: openssl + version: 3.3.2 + build: hb9d3cd8_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.3.2-hb9d3cd8_0.conda + sha256: cee91036686419f6dd6086902acf7142b4916e1c4ba042e9ca23e151da012b6d + md5: 4d638782050ab6faa27275bed57e9b4e + depends: + - __glibc >=2.17,<3.0.a0 + - ca-certificates + - libgcc >=13 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 2891789 + timestamp: 1725410790053 +- kind: conda + name: openssl + version: 3.3.2 + build: hd23fc13_0 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.3.2-hd23fc13_0.conda + sha256: 2b75d4b56e45992adf172b158143742daeb316c35274b36f385ccb6644e93268 + md5: 2ff47134c8e292868a4609519b1ea3b6 + depends: + - __osx >=10.13 + - ca-certificates + license: Apache-2.0 + license_family: Apache + purls: [] + size: 2544654 + timestamp: 1725410973572 +- kind: conda + name: packaging + version: '24.1' + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/packaging-24.1-pyhd8ed1ab_0.conda + sha256: 36aca948219e2c9fdd6d80728bcc657519e02f06c2703d8db3446aec67f51d81 + md5: cbe1bb1f21567018ce595d9c2be0f0db + depends: + - python >=3.8 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/packaging?source=hash-mapping + size: 50290 + timestamp: 1718189540074 +- kind: conda + name: pluggy + version: 1.5.0 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_0.conda + sha256: 33eaa3359948a260ebccf9cdc2fd862cea5a6029783289e13602d8e634cd9a26 + md5: d3483c8fc2dc2cc3f5cf43e26d60cabf + depends: + - python >=3.8 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pluggy?source=hash-mapping + size: 23815 + timestamp: 1713667175451 +- kind: conda + name: pycparser + version: '2.22' + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyhd8ed1ab_0.conda + sha256: 406001ebf017688b1a1554b49127ca3a4ac4626ec0fd51dc75ffa4415b720b64 + md5: 844d9eb3b43095b031874477f7d70088 + depends: + - python >=3.8 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pycparser?source=hash-mapping + size: 105098 + timestamp: 1711811634025 +- kind: pypi + name: pygments + version: 2.18.0 + url: https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl + sha256: b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a + requires_dist: + - colorama>=0.4.6 ; extra == 'windows-terminal' + requires_python: '>=3.8' +- kind: conda + name: pysocks + version: 1.7.1 + build: pyha2e5f31_6 + build_number: 6 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2 + sha256: a42f826e958a8d22e65b3394f437af7332610e43ee313393d1cf143f0a2d274b + md5: 2a7de29fb590ca14b5243c4c812c8025 + depends: + - __unix + - python >=3.8 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pysocks?source=hash-mapping + size: 18981 + timestamp: 1661604969727 +- kind: conda + name: pytest + version: 8.3.2 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.2-pyhd8ed1ab_0.conda + sha256: 72c84a3cd9fe82835a88e975fd2a0dbf2071d1c423ea4f79e7930578c1014873 + md5: e010a224b90f1f623a917c35addbb924 + depends: + - colorama + - exceptiongroup >=1.0.0rc8 + - iniconfig + - packaging + - pluggy <2,>=1.5 + - python >=3.8 + - tomli >=1 + constrains: + - pytest-faulthandler >=2 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pytest?source=hash-mapping + size: 257671 + timestamp: 1721923749407 +- kind: conda + name: python + version: 3.12.5 + build: h2ad013b_0_cpython + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.5-h2ad013b_0_cpython.conda + sha256: e2aad83838988725d4ffba4e9717b9328054fd18a668cff3377e0c50f109e8bd + md5: 9c56c4df45f6571b13111d8df2448692 + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.6.2,<3.0a0 + - libffi >=3.4,<4.0a0 + - libgcc-ng >=12 + - libnsl >=2.0.1,<2.1.0a0 + - libsqlite >=3.46.0,<4.0a0 + - libuuid >=2.38.1,<3.0a0 + - libxcrypt >=4.4.36 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.3.1,<4.0a0 + - readline >=8.2,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - xz >=5.2.6,<6.0a0 + constrains: + - python_abi 3.12.* *_cp312 + license: Python-2.0 + purls: [] + size: 31663253 + timestamp: 1723143721353 +- kind: conda + name: python + version: 3.12.5 + build: h37a9e06_0_cpython + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/python-3.12.5-h37a9e06_0_cpython.conda + sha256: c0f39e625b2fd65f70a9cc086fe4b25cc72228453dbbcd92cd5d140d080e38c5 + md5: 517cb4e16466f8d96ba2a72897d14c48 + depends: + - __osx >=10.13 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.6.2,<3.0a0 + - libffi >=3.4,<4.0a0 + - libsqlite >=3.46.0,<4.0a0 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.3.1,<4.0a0 + - readline >=8.2,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - xz >=5.2.6,<6.0a0 + constrains: + - python_abi 3.12.* *_cp312 + license: Python-2.0 + purls: [] + size: 12173272 + timestamp: 1723142761765 +- kind: conda + name: python_abi + version: '3.12' + build: 5_cp312 + build_number: 5 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda + sha256: d10e93d759931ffb6372b45d65ff34d95c6000c61a07e298d162a3bc2accebb0 + md5: 0424ae29b104430108f5218a66db7260 + constrains: + - python 3.12.* *_cpython + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 6238 + timestamp: 1723823388266 +- kind: conda + name: python_abi + version: '3.12' + build: 5_cp312 + build_number: 5 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/python_abi-3.12-5_cp312.conda + sha256: 4da26c7508d5bc5d8621e84dc510284402239df56aab3587a7d217de9d3c806d + md5: c34dd4920e0addf7cfcc725809f25d8e + constrains: + - python 3.12.* *_cpython + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 6312 + timestamp: 1723823137004 +- kind: conda + name: pyyaml + version: 6.0.2 + build: py312hb553811_1 + build_number: 1 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0.2-py312hb553811_1.conda + sha256: 455ce40588b35df654cb089d29cc3f0d3c78365924ffdfc6ee93dba80cea5f33 + md5: 66514594817d51c78db7109a23ad322f + depends: + - __osx >=10.13 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyyaml?source=hash-mapping + size: 189347 + timestamp: 1725456465705 +- kind: conda + name: readline + version: '8.2' + build: h8228510_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + sha256: 5435cf39d039387fbdc977b0a762357ea909a7694d9528ab40f005e9208744d7 + md5: 47d31b792659ce70f470b5c82fdfb7a4 + depends: + - libgcc-ng >=12 + - ncurses >=6.3,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 281456 + timestamp: 1679532220005 +- kind: conda + name: readline + version: '8.2' + build: h9e318b2_1 + build_number: 1 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h9e318b2_1.conda + sha256: 41e7d30a097d9b060037f0c6a2b1d4c4ae7e942c06c943d23f9d481548478568 + md5: f17f77f2acf4d344734bda76829ce14e + depends: + - ncurses >=6.3,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 255870 + timestamp: 1679532707590 +- kind: conda + name: requests + version: 2.32.3 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_0.conda + sha256: 5845ffe82a6fa4d437a2eae1e32a1ad308d7ad349f61e337c0a890fe04c513cc + md5: 5ede4753180c7a550a443c430dc8ab52 + depends: + - certifi >=2017.4.17 + - charset-normalizer >=2,<4 + - idna >=2.5,<4 + - python >=3.8 + - urllib3 >=1.21.1,<3 + constrains: + - chardet >=3.0.2,<6 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/requests?source=hash-mapping + size: 58810 + timestamp: 1717057174842 +- kind: pypi + name: rich + version: 13.8.0 + url: https://files.pythonhosted.org/packages/c7/d9/c2a126eeae791e90ea099d05cb0515feea3688474b978343f3cdcfe04523/rich-13.8.0-py3-none-any.whl + sha256: 2e85306a063b9492dffc86278197a60cbece75bcb766022f3436f567cae11bdc + requires_dist: + - ipywidgets>=7.5.1,<9 ; extra == 'jupyter' + - markdown-it-py>=2.2.0 + - pygments>=2.13.0,<3.0.0 + - typing-extensions>=4.0.0,<5.0 ; python_full_version < '3.9' + requires_python: '>=3.7.0' +- kind: conda + name: tk + version: 8.6.13 + build: h1abcd95_1 + build_number: 1 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h1abcd95_1.conda + sha256: 30412b2e9de4ff82d8c2a7e5d06a15f4f4fef1809a72138b6ccb53a33b26faf5 + md5: bf830ba5afc507c6232d4ef0fb1a882d + depends: + - libzlib >=1.2.13,<2.0.0a0 + license: TCL + license_family: BSD + purls: [] + size: 3270220 + timestamp: 1699202389792 +- kind: conda + name: tk + version: 8.6.13 + build: noxft_h4845f30_101 + build_number: 101 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda + sha256: e0569c9caa68bf476bead1bed3d79650bb080b532c64a4af7d8ca286c08dea4e + md5: d453b98d9c83e71da0741bb0ff4d76bc + depends: + - libgcc-ng >=12 + - libzlib >=1.2.13,<2.0.0a0 + license: TCL + license_family: BSD + purls: [] + size: 3318875 + timestamp: 1699202167581 +- kind: conda + name: tomli + version: 2.0.1 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2 + sha256: 4cd48aba7cd026d17e86886af48d0d2ebc67ed36f87f6534f4b67138f5a5a58f + md5: 5844808ffab9ebdb694585b50ba02a96 + depends: + - python >=3.7 + license: MIT + license_family: MIT + purls: + - pkg:pypi/tomli?source=hash-mapping + size: 15940 + timestamp: 1644342331069 +- kind: conda + name: tzdata + version: 2024a + build: h8827d51_1 + build_number: 1 + subdir: noarch + noarch: generic + url: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h8827d51_1.conda + sha256: 7d21c95f61319dba9209ca17d1935e6128af4235a67ee4e57a00908a1450081e + md5: 8bfdead4e0fff0383ae4c9c50d0531bd + license: LicenseRef-Public-Domain + purls: [] + size: 124164 + timestamp: 1724736371498 +- kind: conda + name: urllib3 + version: 2.2.2 + build: pyhd8ed1ab_1 + build_number: 1 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.2.2-pyhd8ed1ab_1.conda + sha256: 00c47c602c03137e7396f904eccede8cc64cc6bad63ce1fc355125df8882a748 + md5: e804c43f58255e977093a2298e442bb8 + depends: + - brotli-python >=1.0.9 + - h2 >=4,<5 + - pysocks >=1.5.6,<2.0,!=1.5.7 + - python >=3.8 + - zstandard >=0.18.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/urllib3?source=hash-mapping + size: 95048 + timestamp: 1719391384778 +- kind: conda + name: xz + version: 5.2.6 + build: h166bdaf_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 + sha256: 03a6d28ded42af8a347345f82f3eebdd6807a08526d47899a42d62d319609162 + md5: 2161070d867d1b1204ea749c8eec4ef0 + depends: + - libgcc-ng >=12 + license: LGPL-2.1 and GPL-2.0 + purls: [] + size: 418368 + timestamp: 1660346797927 +- kind: conda + name: xz + version: 5.2.6 + build: h775f41a_0 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/xz-5.2.6-h775f41a_0.tar.bz2 + sha256: eb09823f34cc2dd663c0ec4ab13f246f45dcd52e5b8c47b9864361de5204a1c8 + md5: a72f9d4ea13d55d745ff1ed594747f10 + license: LGPL-2.1 and GPL-2.0 + purls: [] + size: 238119 + timestamp: 1660346964847 +- kind: conda + name: yaml + version: 0.2.5 + build: h0d85af4_2 + build_number: 2 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h0d85af4_2.tar.bz2 + sha256: 5301417e2c8dea45b401ffee8df3957d2447d4ce80c83c5ff151fc6bfe1c4148 + md5: d7e08fcf8259d742156188e8762b4d20 + license: MIT + license_family: MIT + purls: [] + size: 84237 + timestamp: 1641347062780 +- kind: conda + name: zstandard + version: 0.23.0 + build: py312hef9b889_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda + sha256: b97015e146437283f2213ff0e95abdc8e2480150634d81fbae6b96ee09f5e50b + md5: 8b7069e9792ee4e5b4919a7a306d2e67 + depends: + - __glibc >=2.17,<3.0.a0 + - cffi >=1.11 + - libgcc >=13 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + - zstd >=1.5.6,<1.5.7.0a0 + - zstd >=1.5.6,<1.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/zstandard?source=hash-mapping + size: 419552 + timestamp: 1725305670210 +- kind: conda + name: zstd + version: 1.5.6 + build: ha6fb4c9_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda + sha256: c558b9cc01d9c1444031bd1ce4b9cff86f9085765f17627a6cd85fc623c8a02b + md5: 4d056880988120e29d75bfff282e0f45 + depends: + - libgcc-ng >=12 + - libstdcxx-ng >=12 + - libzlib >=1.2.13,<2.0.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 554846 + timestamp: 1714722996770 diff --git a/src/cli/project/export/test-data/testenv/pixi.toml b/src/cli/project/export/test-data/testenv/pixi.toml new file mode 100644 index 000000000..5db6c50b8 --- /dev/null +++ b/src/cli/project/export/test-data/testenv/pixi.toml @@ -0,0 +1,27 @@ +[project] +authors = [] +channels = ["conda-forge"] +description = "test environments" +name = "testenv" +platforms = ["osx-64", "linux-64"] +version = "0.1.0" + +[tasks] + +[dependencies] +python = ">=3.12.5,<4" + +[pypi-dependencies] +rich = ">=13.8.0, <14" + +[target.linux-64.dependencies] +requests = ">=2.32.3,<3" + +[target.osx-64.dependencies] +pyyaml = ">=6.0.2,<7" + +[feature.test.dependencies] +pytest = "*" + +[environments] +test = ["test"] From 272e8b3d46f875074195a782d8be9f4468d09c0a Mon Sep 17 00:00:00 2001 From: "Joshua L. Adelman" Date: Thu, 5 Sep 2024 17:27:06 -0400 Subject: [PATCH 06/19] fix whitespace issue --- docs/reference/cli.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/cli.md b/docs/reference/cli.md index bc5a8ee51..00dbad0e2 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -1197,7 +1197,7 @@ As the explicit specification file format does not support pypi-dependencies, us - `--environment (-e)`: Environment to render. Can be repeated for multiple envs. Defaults to all environments. - `--platform (-p)`: The platform to render. Can be repeated for multiple platforms. Defaults to all platforms available for selected environments. -- `--ignore-pypi-errors`: PyPI dependencies are not supported in the conda explicit spec file. This flag allows creating the spec file even if PyPI dependencies are present. +- `--ignore-pypi-errors`: PyPI dependencies are not supported in the conda explicit spec file. This flag allows creating the spec file even if PyPI dependencies are present. ```sh pixi project export conda_explicit_spec output From 288e900a769de665c41aa1054309a3b4472e3a13 Mon Sep 17 00:00:00 2001 From: Alex Kerney Date: Sat, 7 Sep 2024 15:12:20 -0400 Subject: [PATCH 07/19] feat: Export conda environment.yml Builds upon #1873 to add exporting of `environment.yml` files. Since #1873 provides the conda-lock style matrix of exports, I focused on exporting an environment.yml that matches the manifest. Tests against a bunch of the different example manifests for the various pypi-options, and I tested locally that at least some of those were installable with micromamba. Replaces #1427 --- Cargo.lock | 123 ++++--- Cargo.toml | 5 +- docs/reference/cli.md | 23 ++ docs/switching_from/conda.md | 6 + src/cli/project/export/conda_environment.rs | 342 ++++++++++++++++++ src/cli/project/export/mod.rs | 5 + ...nt__tests__test_export_conda_env_yaml.snap | 14 + ...nda_env_yaml_with_pip_custom_registry.snap | 14 + ...export_conda_env_yaml_with_pip_extras.snap | 23 ++ ...rt_conda_env_yaml_with_pip_find_links.snap | 15 + ...t_conda_env_yaml_with_source_editable.snap | 18 + 11 files changed, 538 insertions(+), 50 deletions(-) create mode 100644 src/cli/project/export/conda_environment.rs create mode 100644 src/cli/project/export/snapshots/pixi__cli__project__export__conda_environment__tests__test_export_conda_env_yaml.snap create mode 100644 src/cli/project/export/snapshots/pixi__cli__project__export__conda_environment__tests__test_export_conda_env_yaml_with_pip_custom_registry.snap create mode 100644 src/cli/project/export/snapshots/pixi__cli__project__export__conda_environment__tests__test_export_conda_env_yaml_with_pip_extras.snap create mode 100644 src/cli/project/export/snapshots/pixi__cli__project__export__conda_environment__tests__test_export_conda_env_yaml_with_pip_find_links.snap create mode 100644 src/cli/project/export/snapshots/pixi__cli__project__export__conda_environment__tests__test_export_conda_env_yaml_with_source_editable.snap diff --git a/Cargo.lock b/Cargo.lock index 512483451..776985bf6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1426,6 +1426,18 @@ dependencies = [ "url", ] +[[package]] +name = "file_url" +version = "0.1.5" +source = "git+https://github.com/conda/rattler#d54fd4ff817fefc704bf4e649ff2b22ee39f98cb" +dependencies = [ + "itertools 0.13.0", + "percent-encoding", + "thiserror", + "typed-path", + "url", +] + [[package]] name = "filetime" version = "0.2.23" @@ -2304,7 +2316,7 @@ dependencies = [ "pypi-types", "reflink-copy", "regex", - "rustc-hash 2.0.0", + "rustc-hash", "serde", "serde_json", "sha2", @@ -2389,7 +2401,7 @@ checksum = "db2b7379a75544c94b3da32821b0bf41f9062e9970e23b78cc577d0d89676d16" dependencies = [ "jiff-tzdb-platform", "serde", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3263,7 +3275,7 @@ dependencies = [ "pep440_rs", "pubgrub", "regex", - "rustc-hash 2.0.0", + "rustc-hash", "schemars", "serde", "smallvec", @@ -3441,7 +3453,7 @@ dependencies = [ "pypi_modifiers", "rattler", "rattler_conda_types", - "rattler_digest", + "rattler_digest 1.0.1", "rattler_lock", "rattler_networking", "rattler_repodata_gateway", @@ -3593,10 +3605,10 @@ name = "pixi_spec" version = "0.1.0" dependencies = [ "dirs", - "file_url", + "file_url 0.1.4", "insta", "rattler_conda_types", - "rattler_digest", + "rattler_digest 1.0.1", "serde", "serde-untagged", "serde_json", @@ -3672,7 +3684,7 @@ name = "platform-tags" version = "0.0.1" source = "git+https://github.com/astral-sh/uv?tag=0.3.2#c5440001ce2cff1657aaa51ce39779fca3e6c565" dependencies = [ - "rustc-hash 2.0.0", + "rustc-hash", "serde", "thiserror", ] @@ -3796,7 +3808,7 @@ dependencies = [ "indexmap 2.3.0", "log", "priority-queue", - "rustc-hash 1.1.0", + "rustc-hash", "thiserror", ] @@ -3853,7 +3865,7 @@ dependencies = [ "pixi_config", "pixi_consts", "rattler_conda_types", - "rattler_digest", + "rattler_digest 1.0.1", "reqwest 0.12.5", "reqwest-middleware", "reqwest-retry 0.5.0", @@ -3907,7 +3919,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.0.0", + "rustc-hash", "rustls 0.23.12", "socket2", "thiserror", @@ -3924,7 +3936,7 @@ dependencies = [ "bytes", "rand", "ring", - "rustc-hash 2.0.0", + "rustc-hash", "rustls 0.23.12", "slab", "thiserror", @@ -4019,7 +4031,7 @@ dependencies = [ "parking_lot 0.12.3", "rattler_cache", "rattler_conda_types", - "rattler_digest", + "rattler_digest 1.0.1", "rattler_networking", "rattler_package_streaming", "rattler_shell", @@ -4050,7 +4062,7 @@ dependencies = [ "itertools 0.13.0", "parking_lot 0.12.3", "rattler_conda_types", - "rattler_digest", + "rattler_digest 1.0.1", "rattler_networking", "rattler_package_streaming", "reqwest 0.12.5", @@ -4063,13 +4075,12 @@ dependencies = [ [[package]] name = "rattler_conda_types" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4dc7a58ae237e3f57310095033da58b37d98ce7c167bc928b0f81951279c514" +version = "0.27.5" +source = "git+https://github.com/conda/rattler#d54fd4ff817fefc704bf4e649ff2b22ee39f98cb" dependencies = [ "chrono", "dirs", - "file_url", + "file_url 0.1.5", "fxhash", "glob", "hex", @@ -4078,9 +4089,9 @@ dependencies = [ "lazy-regex", "nom", "purl", - "rattler_digest", + "rattler_digest 1.0.2", "rattler_macros", - "rattler_redaction", + "rattler_redaction 0.1.2", "regex", "serde", "serde-untagged", @@ -4114,6 +4125,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "rattler_digest" +version = "1.0.2" +source = "git+https://github.com/conda/rattler#d54fd4ff817fefc704bf4e649ff2b22ee39f98cb" +dependencies = [ + "blake2", + "digest", + "generic-array", + "hex", + "md-5", + "serde", + "serde_with", + "sha2", +] + [[package]] name = "rattler_lock" version = "0.22.20" @@ -4121,14 +4147,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ce9290c3c0a702806b74b015a5c3dd638211255379f59d261e098ffc12d98d9" dependencies = [ "chrono", - "file_url", + "file_url 0.1.4", "fxhash", "indexmap 2.3.0", "itertools 0.13.0", "pep440_rs", "pep508_rs", "rattler_conda_types", - "rattler_digest", + "rattler_digest 1.0.1", "serde", "serde_repr", "serde_with", @@ -4139,9 +4165,8 @@ dependencies = [ [[package]] name = "rattler_macros" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18011e84fcc8dba03a4af5b70cbb99362968cdace525139df430b4f268ef58e0" +version = "1.0.2" +source = "git+https://github.com/conda/rattler#d54fd4ff817fefc704bf4e649ff2b22ee39f98cb" dependencies = [ "quote", "syn 2.0.72", @@ -4186,9 +4211,9 @@ dependencies = [ "futures-util", "num_cpus", "rattler_conda_types", - "rattler_digest", + "rattler_digest 1.0.1", "rattler_networking", - "rattler_redaction", + "rattler_redaction 0.1.1", "reqwest 0.12.5", "reqwest-middleware", "serde_json", @@ -4214,6 +4239,14 @@ dependencies = [ "url", ] +[[package]] +name = "rattler_redaction" +version = "0.1.2" +source = "git+https://github.com/conda/rattler#d54fd4ff817fefc704bf4e649ff2b22ee39f98cb" +dependencies = [ + "url", +] + [[package]] name = "rattler_repodata_gateway" version = "0.21.8" @@ -4229,7 +4262,7 @@ dependencies = [ "chrono", "dashmap", "dirs", - "file_url", + "file_url 0.1.4", "futures", "hex", "http 1.1.0", @@ -4246,9 +4279,9 @@ dependencies = [ "pin-project-lite", "rattler_cache", "rattler_conda_types", - "rattler_digest", + "rattler_digest 1.0.1", "rattler_networking", - "rattler_redaction", + "rattler_redaction 0.1.1", "reqwest 0.12.5", "reqwest-middleware", "rmp-serde", @@ -4295,7 +4328,7 @@ dependencies = [ "futures", "itertools 0.13.0", "rattler_conda_types", - "rattler_digest", + "rattler_digest 1.0.1", "resolvo", "serde", "tempfile", @@ -4808,12 +4841,6 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc-hash" version = "2.0.0" @@ -6157,7 +6184,7 @@ dependencies = [ "reqwest 0.12.5", "reqwest-middleware", "rust-netrc", - "rustc-hash 2.0.0", + "rustc-hash", "tokio", "tracing", "url", @@ -6178,7 +6205,7 @@ dependencies = [ "pep508_rs", "pypi-types", "regex", - "rustc-hash 2.0.0", + "rustc-hash", "serde", "serde_json", "tempfile", @@ -6206,7 +6233,7 @@ dependencies = [ "nanoid", "pypi-types", "rmp-serde", - "rustc-hash 2.0.0", + "rustc-hash", "serde", "tempfile", "tracing", @@ -6272,7 +6299,7 @@ dependencies = [ "pep508_rs", "platform-tags", "pypi-types", - "rustc-hash 2.0.0", + "rustc-hash", "serde", "serde_json", "tracing", @@ -6292,7 +6319,7 @@ dependencies = [ "install-wheel-rs", "itertools 0.13.0", "pypi-types", - "rustc-hash 2.0.0", + "rustc-hash", "tracing", "uv-build", "uv-cache", @@ -6326,7 +6353,7 @@ dependencies = [ "reqwest 0.12.5", "reqwest-middleware", "rmp-serde", - "rustc-hash 2.0.0", + "rustc-hash", "serde", "tempfile", "thiserror", @@ -6361,7 +6388,7 @@ dependencies = [ "pypi-types", "rayon", "reqwest 0.12.5", - "rustc-hash 2.0.0", + "rustc-hash", "sha2", "thiserror", "tokio", @@ -6433,7 +6460,7 @@ dependencies = [ "platform-tags", "pypi-types", "rayon", - "rustc-hash 2.0.0", + "rustc-hash", "same-file", "tempfile", "thiserror", @@ -6563,7 +6590,7 @@ dependencies = [ "pypi-types", "requirements-txt", "rkyv", - "rustc-hash 2.0.0", + "rustc-hash", "same-file", "serde", "textwrap", @@ -6609,7 +6636,7 @@ dependencies = [ "pep440_rs", "pep508_rs", "pypi-types", - "rustc-hash 2.0.0", + "rustc-hash", "thiserror", "url", "uv-cache", @@ -6648,7 +6675,7 @@ source = "git+https://github.com/astral-sh/uv?tag=0.3.2#c5440001ce2cff1657aaa51c dependencies = [ "anstream", "owo-colors", - "rustc-hash 2.0.0", + "rustc-hash", ] [[package]] @@ -6662,7 +6689,7 @@ dependencies = [ "pep440_rs", "pep508_rs", "pypi-types", - "rustc-hash 2.0.0", + "rustc-hash", "serde", "thiserror", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 0b14d4d31..a6ee8bdfa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,6 +91,7 @@ file_url = "0.1.4" rattler = { version = "0.27.6", default-features = false } rattler_cache = { version = "0.1.8", default-features = false } rattler_conda_types = { version = "0.27.2", default-features = false } +# rattler_conda_types = {path = "../rattler/crates/rattler_conda_types", default-features = false } rattler_digest = { version = "1.0.1", default-features = false } rattler_lock = { version = "0.22.20", default-features = false } rattler_networking = { version = "0.21.2", default-features = false } @@ -321,7 +322,7 @@ pep508_rs = { git = "https://github.com/astral-sh/uv", tag = "0.3.2" } reqwest-middleware = { git = "https://github.com/astral-sh/reqwest-middleware", rev = "5e3eaf254b5bd481c75d2710eed055f95b756913" } # deno_task_shell = { path = "../deno_task_shell" } # rattler = { git = "https://github.com/tdejager/rattler", branch = "feat/default-marker-tree" } -# rattler_conda_types = { git = "https://github.com/tdejager/rattler", branch = "feat/default-marker-tree" } +rattler_conda_types = { git = "https://github.com/conda/rattler" } # rattler_digest = { git = "https://github.com/tdejager/rattler", branch = "feat/default-marker-tree" } # rattler_lock = { git = "https://github.com/tdejager/rattler", branch = "feat/default-marker-tree" } # rattler_networking = { git = "https://github.com/tdejager/rattler", branch = "feat/default-marker-tree" } @@ -329,7 +330,7 @@ reqwest-middleware = { git = "https://github.com/astral-sh/reqwest-middleware", # rattler_shell = { git = "https://github.com/tdejager/rattler", branch = "feat/default-marker-tree" } # rattler_solve = { git = "https://github.com/tdejager/rattler", branch = "feat/default-marker-tree" } # rattler_virtual_packages = { git = "https://github.com/tdejager/rattler", branch = "feat/default-marker-tree" } -#rattler_conda_types = { path = "../rattler/crates/rattler_conda_types" } +# rattler_conda_types = { path = "../rattler/crates/rattler_conda_types" } #rattler_digest = { path = "../rattler/crates/rattler_digest" } #rattler_networking = { path = "../rattler/crates/rattler_networking" } #rattler_repodata_gateway = { path = "../rattler/crates/rattler_repodata_gateway" } diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 00dbad0e2..0c076fb16 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -1178,6 +1178,29 @@ List the environments in the manifest file. pixi project environment list ``` +### `project export conda_environment` + +Exports a conda [`environment.yml` file](https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#creating-an-environment-from-an-environment-yml-file). The file can be used to create a conda environment using conda/mamba: + +```shell +pixi project export conda_environment +mamba create --name --file +``` + +##### Arguments + +1. ``: Optional path to render environment.yml to. Otherwise it will be printed to standard out. + +##### Options + +- `--environment (-e)`: Environment to render. +- `--platform (-p)`: The platform to render. + +```sh +pixi project export conda_environment --environment lint +pixi project export conda --platform linux-64 environment.linux-64.yml +``` + ### `project export conda_explicit_spec` Render a platform-specific conda [explicit specification file](https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#building-identical-conda-environments) diff --git a/docs/switching_from/conda.md b/docs/switching_from/conda.md index 7c7b1d3df..733cbc87e 100644 --- a/docs/switching_from/conda.md +++ b/docs/switching_from/conda.md @@ -70,6 +70,12 @@ pixi init --import environment.yml ``` This will create a new project with the dependencies from the `environment.yml` file. +??? tip "Exporting your environment" + If you are working with Conda users or systems, you can [export your environment to a `environment.yml`](../reference/cli/#project-export-conda_environment) file to share them. + ```shell + pixi project export conda + ``` + Additionally you can export a [conda explicit specification](../reference/cli/#project-export-conda_explicit_spec). ## Troubleshooting Encountering issues? Here are solutions to some common problems when being used to the `conda` workflow: diff --git a/src/cli/project/export/conda_environment.rs b/src/cli/project/export/conda_environment.rs new file mode 100644 index 000000000..47850ec81 --- /dev/null +++ b/src/cli/project/export/conda_environment.rs @@ -0,0 +1,342 @@ +use std::path::PathBuf; + +use clap::Parser; +use itertools::Itertools; +use pep508_rs::ExtraName; +use pixi_manifest::{ + pypi::{PyPiPackageName, VersionOrStar}, + FeaturesExt, HasFeaturesIter, PyPiRequirement, +}; +use rattler_conda_types::{ + EnvironmentYaml, MatchSpec, MatchSpecOrSubSection, ParseStrictness, Platform, +}; +use rattler_lock::FindLinksUrlOrPath; + +use crate::cli::LockFileUsageArgs; +use crate::project::Environment; +use crate::Project; + +#[derive(Debug, Parser)] +#[clap(arg_required_else_help = false)] +pub struct Args { + /// Explicit path to export the environment to + pub output_path: Option, + + /// Environment to render, defaults to the current environment + #[arg(short, long)] + pub platform: Option, + + /// The environment to list packages for. Defaults to the default + /// environment. + #[arg(short, long)] + pub environment: Option, + + #[clap(flatten)] + pub lock_file_args: LockFileUsageArgs, +} + +fn format_pip_extras(extras: &[ExtraName]) -> String { + if extras.is_empty() { + return String::new(); + } + format!( + "[{}]", + extras.iter().map(|extra| format!("{extra}")).join("") + ) +} + +fn format_pip_dependency(name: &PyPiPackageName, requirement: &PyPiRequirement) -> String { + match requirement { + PyPiRequirement::Git { + url: git_url, + extras, + } => { + let mut git_string = format!( + "{name}{extras} @ git+{url}", + name = name.as_normalized(), + extras = format_pip_extras(extras), + url = git_url.git, + ); + + if let Some(ref branch) = git_url.branch { + git_string.push_str(&format!("@{branch}")); + } else if let Some(ref tag) = git_url.tag { + git_string.push_str(&format!("@{tag}")); + } else if let Some(ref rev) = git_url.rev { + git_string.push_str(&format!("@{rev}")); + } + + if let Some(ref subdirectory) = git_url.subdirectory { + git_string.push_str(&format!("#subdirectory=={subdirectory}")); + } + + git_string + } + PyPiRequirement::Path { + path, + editable, + extras, + } => { + if let Some(_editable) = editable { + format!( + "-e {path}{extras}", + path = path.to_string_lossy(), + extras = format_pip_extras(extras), + ) + } else { + format!( + "{path}{extras}", + path = path.to_string_lossy(), + extras = format_pip_extras(extras), + ) + } + } + PyPiRequirement::Url { + url, + subdirectory, + extras, + } => { + let mut url_string = format!( + "{name}{extras} @ {url}", + name = name.as_normalized(), + extras = format_pip_extras(extras), + url = url, + ); + + if let Some(ref subdirectory) = subdirectory { + url_string.push_str(&format!("#subdirectory=={subdirectory}")); + } + + url_string + } + PyPiRequirement::Version { version, extras } => { + format!( + "{name}{extras}{version}", + name = name.as_normalized(), + extras = format_pip_extras(extras), + version = version + ) + } + PyPiRequirement::RawVersion(version) => match version { + VersionOrStar::Version(_) => format!( + "{name}{version}", + name = name.as_normalized(), + version = version + ), + VersionOrStar::Star => format!("{name}", name = name.as_normalized()), + }, + } +} + +fn build_env_yaml( + platform: &Platform, + environment: &Environment, +) -> miette::Result { + let mut env_yaml = rattler_conda_types::EnvironmentYaml { + name: Some(environment.name().as_str().to_string()), + channels: environment.channels().into_iter().cloned().collect_vec(), + ..Default::default() + }; + + let mut pip_dependencies: Vec = Vec::new(); + + for feature in environment.features() { + for (key, value) in feature.dependencies(None, Some(*platform)).unwrap().iter() { + let spec = MatchSpec { + name: Some(key.clone()), + version: value.clone().into_version(), + build: None, + build_number: None, + subdir: None, + md5: None, + sha256: None, + url: None, + file_name: None, + channel: None, + namespace: None, + }; + env_yaml + .dependencies + .push(MatchSpecOrSubSection::MatchSpec(spec)); + } + + if feature.has_pypi_dependencies() { + for (name, requirement) in feature.pypi_dependencies(Some(*platform)).unwrap().iter() { + pip_dependencies.push(format_pip_dependency(name, requirement)); + } + } + } + + if !pip_dependencies.is_empty() { + let pypi_options = environment.pypi_options(); + if let Some(ref find_links) = pypi_options.find_links { + for find_link in find_links { + match find_link { + FindLinksUrlOrPath::Url(url) => { + pip_dependencies.insert(0, format!("--find-links {url}")); + } + FindLinksUrlOrPath::Path(path) => { + pip_dependencies + .insert(0, format!("--find-links {}", path.to_string_lossy())); + } + } + } + } + if let Some(ref extra_index_urls) = pypi_options.extra_index_urls { + for extra_index_url in extra_index_urls { + pip_dependencies.insert(0, format!("--extra-index-url {extra_index_url}")); + } + } + if let Some(ref index_url) = pypi_options.index_url { + pip_dependencies.insert(0, format!("--index-url {index_url}")); + } + + env_yaml.dependencies.push(MatchSpecOrSubSection::MatchSpec( + MatchSpec::from_str("pip", ParseStrictness::Lenient).unwrap(), + )); + + env_yaml + .dependencies + .push(MatchSpecOrSubSection::SubSection( + "pip".to_string(), + pip_dependencies.into_iter().collect_vec(), + )); + } + + Ok(env_yaml) +} + +pub async fn execute(project: Project, args: Args) -> miette::Result<()> { + let environment = project.environment_from_name_or_env_var(args.environment)?; + let platform = args.platform.unwrap_or_else(|| environment.best_platform()); + + let env_yaml = build_env_yaml(&platform, &environment).unwrap(); + + if let Some(output_path) = args.output_path { + env_yaml.to_path(output_path.as_path()).unwrap(); + } else { + println!("{}", env_yaml.to_yaml_string()); + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + use std::path::Path; + + #[test] + fn test_export_conda_env_yaml() { + let path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("src/cli/project/export/test-data/testenv/pixi.toml"); + let project = Project::from_path(&path).unwrap(); + let args = Args { + output_path: None, + platform: None, + environment: Some("default".to_string()), + lock_file_args: LockFileUsageArgs::default(), + }; + let environment = project + .environment_from_name_or_env_var(args.environment) + .unwrap(); + let platform = args.platform.unwrap_or_else(|| environment.best_platform()); + + let env_yaml = build_env_yaml(&platform, &environment); + insta::assert_snapshot!( + "test_export_conda_env_yaml", + env_yaml.unwrap().to_yaml_string() + ); + } + + #[test] + fn test_export_conda_env_yaml_with_pip_extras() { + let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("examples/pypi/pixi.toml"); + let project = Project::from_path(&path).unwrap(); + let args = Args { + output_path: None, + platform: None, + environment: Some("default".to_string()), + lock_file_args: LockFileUsageArgs::default(), + }; + let environment = project + .environment_from_name_or_env_var(args.environment) + .unwrap(); + let platform = args.platform.unwrap_or_else(|| environment.best_platform()); + + let env_yaml = build_env_yaml(&platform, &environment); + insta::assert_snapshot!( + "test_export_conda_env_yaml_with_pip_extras", + env_yaml.unwrap().to_yaml_string() + ); + } + + #[test] + fn test_export_conda_env_yaml_with_pip_source_editable() { + let path = + Path::new(env!("CARGO_MANIFEST_DIR")).join("examples/pypi-source-deps/pixi.toml"); + let project = Project::from_path(&path).unwrap(); + let args = Args { + output_path: None, + platform: None, + environment: Some("default".to_string()), + lock_file_args: LockFileUsageArgs::default(), + }; + let environment = project + .environment_from_name_or_env_var(args.environment) + .unwrap(); + let platform = args.platform.unwrap_or_else(|| environment.best_platform()); + + let env_yaml = build_env_yaml(&platform, &environment); + insta::assert_snapshot!( + "test_export_conda_env_yaml_with_source_editable", + env_yaml.unwrap().to_yaml_string() + ); + } + + #[test] + fn test_export_conda_env_yaml_with_pip_custom_registry() { + let path = + Path::new(env!("CARGO_MANIFEST_DIR")).join("examples/pypi-custom-registry/pixi.toml"); + let project = Project::from_path(&path).unwrap(); + let args = Args { + output_path: None, + platform: None, + environment: Some("alternative".to_string()), + lock_file_args: LockFileUsageArgs::default(), + }; + let environment = project + .environment_from_name_or_env_var(args.environment) + .unwrap(); + let platform = args.platform.unwrap_or_else(|| environment.best_platform()); + + let env_yaml = build_env_yaml(&platform, &environment); + insta::assert_snapshot!( + "test_export_conda_env_yaml_with_pip_custom_registry", + env_yaml.unwrap().to_yaml_string() + ); + } + + #[test] + fn test_export_conda_env_yaml_with_pip_find_links() { + let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("examples/pypi-find-links/pixi.toml"); + let project = Project::from_path(&path).unwrap(); + let args = Args { + output_path: None, + platform: None, + environment: Some("default".to_string()), + lock_file_args: LockFileUsageArgs::default(), + }; + let environment = project + .environment_from_name_or_env_var(args.environment) + .unwrap(); + let platform = args.platform.unwrap_or_else(|| environment.best_platform()); + + let env_yaml = build_env_yaml(&platform, &environment); + insta::assert_snapshot!( + "test_export_conda_env_yaml_with_pip_find_links", + env_yaml.unwrap().to_yaml_string() + ); + } +} diff --git a/src/cli/project/export/mod.rs b/src/cli/project/export/mod.rs index 8c135f2a1..20100201d 100644 --- a/src/cli/project/export/mod.rs +++ b/src/cli/project/export/mod.rs @@ -1,4 +1,5 @@ use std::path::PathBuf; +pub mod conda_environment; pub mod conda_explicit_spec; use crate::Project; @@ -20,12 +21,16 @@ pub enum Command { /// Export project environment to a conda explicit specification file #[clap(visible_alias = "ces")] CondaExplicitSpec(conda_explicit_spec::Args), + /// Export project environment to a conda environment.yaml file + #[clap(visible_alias = "conda")] + CondaEnvironment(conda_environment::Args), } pub async fn execute(args: Args) -> miette::Result<()> { let project = Project::load_or_else_discover(args.manifest_path.as_deref())?; match args.command { Command::CondaExplicitSpec(args) => conda_explicit_spec::execute(project, args).await?, + Command::CondaEnvironment(args) => conda_environment::execute(project, args).await?, }; Ok(()) } diff --git a/src/cli/project/export/snapshots/pixi__cli__project__export__conda_environment__tests__test_export_conda_env_yaml.snap b/src/cli/project/export/snapshots/pixi__cli__project__export__conda_environment__tests__test_export_conda_env_yaml.snap new file mode 100644 index 000000000..3cc9acd2b --- /dev/null +++ b/src/cli/project/export/snapshots/pixi__cli__project__export__conda_environment__tests__test_export_conda_env_yaml.snap @@ -0,0 +1,14 @@ +--- +source: src/cli/project/export/conda_environment.rs +assertion_line: 130 +expression: env_yaml.unwrap().to_yaml_string() +--- +name: default +channels: +- conda-forge +dependencies: +- python >=3.12.5,<4 +- pyyaml >=6.0.2,<7 +- pip +- pip: + - rich>=13.8.0, <14 diff --git a/src/cli/project/export/snapshots/pixi__cli__project__export__conda_environment__tests__test_export_conda_env_yaml_with_pip_custom_registry.snap b/src/cli/project/export/snapshots/pixi__cli__project__export__conda_environment__tests__test_export_conda_env_yaml_with_pip_custom_registry.snap new file mode 100644 index 000000000..ef9886219 --- /dev/null +++ b/src/cli/project/export/snapshots/pixi__cli__project__export__conda_environment__tests__test_export_conda_env_yaml_with_pip_custom_registry.snap @@ -0,0 +1,14 @@ +--- +source: src/cli/project/export/conda_environment.rs +assertion_line: 315 +expression: env_yaml.unwrap().to_yaml_string() +--- +name: alternative +channels: +- conda-forge +dependencies: +- python ==3.12 +- pip +- pip: + - --index-url https://pypi.tuna.tsinghua.edu.cn/simple/ + - flask==3.0.3 diff --git a/src/cli/project/export/snapshots/pixi__cli__project__export__conda_environment__tests__test_export_conda_env_yaml_with_pip_extras.snap b/src/cli/project/export/snapshots/pixi__cli__project__export__conda_environment__tests__test_export_conda_env_yaml_with_pip_extras.snap new file mode 100644 index 000000000..fb4a6e131 --- /dev/null +++ b/src/cli/project/export/snapshots/pixi__cli__project__export__conda_environment__tests__test_export_conda_env_yaml_with_pip_extras.snap @@ -0,0 +1,23 @@ +--- +source: src/cli/project/export/conda_environment.rs +assertion_line: 187 +expression: env_yaml.unwrap().to_yaml_string() +--- +name: default +channels: +- conda-forge +dependencies: +- libclang ~=16.0.6 +- numpy 1.26.* +- python ~=3.11.0 +- scipy ~=1.11.4 +- pip +- pip: + - black[jupyter]~=24.0 + - flask + - pyboy==1.6.6 + - tensorflow==2.14.0 + - env-test-package==0.0.3 + - plot-antenna==1.8 + - pycosat + - pyliblzfse diff --git a/src/cli/project/export/snapshots/pixi__cli__project__export__conda_environment__tests__test_export_conda_env_yaml_with_pip_find_links.snap b/src/cli/project/export/snapshots/pixi__cli__project__export__conda_environment__tests__test_export_conda_env_yaml_with_pip_find_links.snap new file mode 100644 index 000000000..47d09c6b1 --- /dev/null +++ b/src/cli/project/export/snapshots/pixi__cli__project__export__conda_environment__tests__test_export_conda_env_yaml_with_pip_find_links.snap @@ -0,0 +1,15 @@ +--- +source: src/cli/project/export/conda_environment.rs +assertion_line: 339 +expression: env_yaml.unwrap().to_yaml_string() +--- +name: default +channels: +- conda-forge +dependencies: +- python ==3.12 +- pip +- pip: + - --index-url https://pypi.org/simple + - --find-links ./links + - requests==2.31.0 diff --git a/src/cli/project/export/snapshots/pixi__cli__project__export__conda_environment__tests__test_export_conda_env_yaml_with_source_editable.snap b/src/cli/project/export/snapshots/pixi__cli__project__export__conda_environment__tests__test_export_conda_env_yaml_with_source_editable.snap new file mode 100644 index 000000000..de5f4bd89 --- /dev/null +++ b/src/cli/project/export/snapshots/pixi__cli__project__export__conda_environment__tests__test_export_conda_env_yaml_with_source_editable.snap @@ -0,0 +1,18 @@ +--- +source: src/cli/project/export/conda_environment.rs +assertion_line: 250 +expression: env_yaml.unwrap().to_yaml_string() +--- +name: default +channels: +- conda-forge +dependencies: +- python * +- pip +- pip: + - rich~=13.7 + - flask @ git+https://github.com/pallets/flask + - requests @ git+https://github.com/psf/requests.git@0106aced5faa299e6ede89d1230bd6784f2c3660 + - -e ./minimal-project + - click @ https://github.com/pallets/click/releases/download/8.1.7/click-8.1.7-py3-none-any.whl + - pytest @ git+https://github.com/pytest-dev/pytest.git From 0046c8bfbbc1d778b121be646d4f8c81054a7e00 Mon Sep 17 00:00:00 2001 From: Alex Kerney Date: Tue, 10 Sep 2024 20:10:14 -0400 Subject: [PATCH 08/19] Specify the environment for the platform specific test --- src/cli/project/export/conda_environment.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/cli/project/export/conda_environment.rs b/src/cli/project/export/conda_environment.rs index 47850ec81..4171a20bb 100644 --- a/src/cli/project/export/conda_environment.rs +++ b/src/cli/project/export/conda_environment.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use clap::Parser; use itertools::Itertools; +use miette::{Context, IntoDiagnostic}; use pep508_rs::ExtraName; use pixi_manifest::{ pypi::{PyPiPackageName, VersionOrStar}, @@ -213,7 +214,10 @@ pub async fn execute(project: Project, args: Args) -> miette::Result<()> { let env_yaml = build_env_yaml(&platform, &environment).unwrap(); if let Some(output_path) = args.output_path { - env_yaml.to_path(output_path.as_path()).unwrap(); + env_yaml + .to_path(output_path.as_path()) + .into_diagnostic() + .with_context(|| "failed to write environment YAML")?; } else { println!("{}", env_yaml.to_yaml_string()); } @@ -234,7 +238,7 @@ mod tests { let project = Project::from_path(&path).unwrap(); let args = Args { output_path: None, - platform: None, + platform: Some(Platform::Osx64), environment: Some("default".to_string()), lock_file_args: LockFileUsageArgs::default(), }; From 62871d4131eabf7a6fa6e063e93c343dc11f33ee Mon Sep 17 00:00:00 2001 From: Alex Kerney Date: Tue, 10 Sep 2024 22:26:21 -0400 Subject: [PATCH 09/19] Don't fail on features that don't have dependencies in a feature --- src/cli/project/export/conda_environment.rs | 68 ++++++++++++------- ...export_conda_env_yaml_pyproject_panic.snap | 17 +++++ 2 files changed, 62 insertions(+), 23 deletions(-) create mode 100644 src/cli/project/export/snapshots/pixi__cli__project__export__conda_environment__tests__test_export_conda_env_yaml_pyproject_panic.snap diff --git a/src/cli/project/export/conda_environment.rs b/src/cli/project/export/conda_environment.rs index 4171a20bb..bc9040478 100644 --- a/src/cli/project/export/conda_environment.rs +++ b/src/cli/project/export/conda_environment.rs @@ -13,7 +13,6 @@ use rattler_conda_types::{ }; use rattler_lock::FindLinksUrlOrPath; -use crate::cli::LockFileUsageArgs; use crate::project::Environment; use crate::Project; @@ -31,9 +30,6 @@ pub struct Args { /// environment. #[arg(short, long)] pub environment: Option, - - #[clap(flatten)] - pub lock_file_args: LockFileUsageArgs, } fn format_pip_extras(extras: &[ExtraName]) -> String { @@ -142,28 +138,32 @@ fn build_env_yaml( let mut pip_dependencies: Vec = Vec::new(); for feature in environment.features() { - for (key, value) in feature.dependencies(None, Some(*platform)).unwrap().iter() { - let spec = MatchSpec { - name: Some(key.clone()), - version: value.clone().into_version(), - build: None, - build_number: None, - subdir: None, - md5: None, - sha256: None, - url: None, - file_name: None, - channel: None, - namespace: None, - }; - env_yaml - .dependencies - .push(MatchSpecOrSubSection::MatchSpec(spec)); + if let Some(dependencies) = feature.dependencies(None, Some(*platform)) { + for (key, value) in dependencies.iter() { + let spec = MatchSpec { + name: Some(key.clone()), + version: value.clone().into_version(), + build: None, + build_number: None, + subdir: None, + md5: None, + sha256: None, + url: None, + file_name: None, + channel: None, + namespace: None, + }; + env_yaml + .dependencies + .push(MatchSpecOrSubSection::MatchSpec(spec)); + } } if feature.has_pypi_dependencies() { - for (name, requirement) in feature.pypi_dependencies(Some(*platform)).unwrap().iter() { - pip_dependencies.push(format_pip_dependency(name, requirement)); + if let Some(pypi_dependencies) = feature.pypi_dependencies(Some(*platform)) { + for (name, requirement) in pypi_dependencies.iter() { + pip_dependencies.push(format_pip_dependency(name, requirement)); + } } } } @@ -343,4 +343,26 @@ mod tests { env_yaml.unwrap().to_yaml_string() ); } + + #[test] + fn test_export_conda_env_yaml_pyproject_panic() { + let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("examples/docker/pyproject.toml"); + let project = Project::from_path(&path).unwrap(); + let args = Args { + output_path: None, + platform: Some(Platform::OsxArm64), + environment: Some("default".to_string()), + lock_file_args: LockFileUsageArgs::default(), + }; + let environment = project + .environment_from_name_or_env_var(args.environment) + .unwrap(); + let platform = args.platform.unwrap_or_else(|| environment.best_platform()); + + let env_yaml = build_env_yaml(&platform, &environment); + insta::assert_snapshot!( + "test_export_conda_env_yaml_pyproject_panic", + env_yaml.unwrap().to_yaml_string() + ); + } } diff --git a/src/cli/project/export/snapshots/pixi__cli__project__export__conda_environment__tests__test_export_conda_env_yaml_pyproject_panic.snap b/src/cli/project/export/snapshots/pixi__cli__project__export__conda_environment__tests__test_export_conda_env_yaml_pyproject_panic.snap new file mode 100644 index 000000000..03babcb22 --- /dev/null +++ b/src/cli/project/export/snapshots/pixi__cli__project__export__conda_environment__tests__test_export_conda_env_yaml_pyproject_panic.snap @@ -0,0 +1,17 @@ +--- +source: src/cli/project/export/conda_environment.rs +assertion_line: 367 +expression: env_yaml.unwrap().to_yaml_string() +--- +name: default +channels: +- conda-forge +dependencies: +- pytest * +- hatch ==1.12.0 +- flask >=3.0.2,<3.1 +- gunicorn >=21.2.0,<21.3 +- python >=3.12 +- pip +- pip: + - -e . From be4a174d6c2a4ad3d011be985dfcba74111d8c98 Mon Sep 17 00:00:00 2001 From: Alex Kerney Date: Tue, 10 Sep 2024 22:32:57 -0400 Subject: [PATCH 10/19] Removed args should be removed from tests as well --- src/cli/project/export/conda_environment.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/cli/project/export/conda_environment.rs b/src/cli/project/export/conda_environment.rs index bc9040478..73a03f971 100644 --- a/src/cli/project/export/conda_environment.rs +++ b/src/cli/project/export/conda_environment.rs @@ -240,7 +240,6 @@ mod tests { output_path: None, platform: Some(Platform::Osx64), environment: Some("default".to_string()), - lock_file_args: LockFileUsageArgs::default(), }; let environment = project .environment_from_name_or_env_var(args.environment) @@ -262,7 +261,6 @@ mod tests { output_path: None, platform: None, environment: Some("default".to_string()), - lock_file_args: LockFileUsageArgs::default(), }; let environment = project .environment_from_name_or_env_var(args.environment) @@ -285,7 +283,6 @@ mod tests { output_path: None, platform: None, environment: Some("default".to_string()), - lock_file_args: LockFileUsageArgs::default(), }; let environment = project .environment_from_name_or_env_var(args.environment) @@ -308,7 +305,6 @@ mod tests { output_path: None, platform: None, environment: Some("alternative".to_string()), - lock_file_args: LockFileUsageArgs::default(), }; let environment = project .environment_from_name_or_env_var(args.environment) @@ -330,7 +326,6 @@ mod tests { output_path: None, platform: None, environment: Some("default".to_string()), - lock_file_args: LockFileUsageArgs::default(), }; let environment = project .environment_from_name_or_env_var(args.environment) @@ -352,7 +347,6 @@ mod tests { output_path: None, platform: Some(Platform::OsxArm64), environment: Some("default".to_string()), - lock_file_args: LockFileUsageArgs::default(), }; let environment = project .environment_from_name_or_env_var(args.environment) From 8dc6b539001af658c3fd6d3d60f7c5f4082327f5 Mon Sep 17 00:00:00 2001 From: Alex Kerney Date: Mon, 16 Sep 2024 11:03:04 -0400 Subject: [PATCH 11/19] Apply suggestions from code review Co-authored-by: Ruben Arts --- src/cli/project/export/conda_environment.rs | 7 ++++--- src/cli/project/export/mod.rs | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cli/project/export/conda_environment.rs b/src/cli/project/export/conda_environment.rs index 73a03f971..56a6d8f0c 100644 --- a/src/cli/project/export/conda_environment.rs +++ b/src/cli/project/export/conda_environment.rs @@ -22,12 +22,13 @@ pub struct Args { /// Explicit path to export the environment to pub output_path: Option, - /// Environment to render, defaults to the current environment + /// The platform to render the environment file for. + /// Defaults to the current platform. #[arg(short, long)] pub platform: Option, - /// The environment to list packages for. Defaults to the default - /// environment. + /// The environment to render the environment file for. + /// Defaults to the default environment. #[arg(short, long)] pub environment: Option, } diff --git a/src/cli/project/export/mod.rs b/src/cli/project/export/mod.rs index 20100201d..6fb4f8e99 100644 --- a/src/cli/project/export/mod.rs +++ b/src/cli/project/export/mod.rs @@ -22,7 +22,6 @@ pub enum Command { #[clap(visible_alias = "ces")] CondaExplicitSpec(conda_explicit_spec::Args), /// Export project environment to a conda environment.yaml file - #[clap(visible_alias = "conda")] CondaEnvironment(conda_environment::Args), } From 42ccb4f43c124a17981e2effe055a46a6369332d Mon Sep 17 00:00:00 2001 From: Alex Kerney Date: Mon, 16 Sep 2024 16:31:14 -0400 Subject: [PATCH 12/19] Add some initial downstream tests and apply suggestions --- .github/workflows/test_downstream.yml | 68 +++++++++++++++++++++ src/cli/project/export/conda_environment.rs | 7 +-- tests/test_export.sh | 45 ++++++++++++++ 3 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 tests/test_export.sh diff --git a/.github/workflows/test_downstream.yml b/.github/workflows/test_downstream.yml index e74e4e2be..a0bf564f9 100644 --- a/.github/workflows/test_downstream.yml +++ b/.github/workflows/test_downstream.yml @@ -203,3 +203,71 @@ jobs: run: ${{ env.TARGET_RELEASE }}/pixi${{ matrix.arch.extension }} run typecheck-integration - name: Run integration tests run: ${{ env.TARGET_RELEASE }}/pixi${{ matrix.arch.extension }} run test-integration-ci + + test-export: + name: ${{ matrix.arch.name }} - Export Tests + runs-on: ${{ matrix.arch.os }} + strategy: + fail-fast: false + matrix: + arch: + # Linux + - { + target: x86_64-unknown-linux-musl, + os: ubuntu-20.04, + name: "Linux", + } + # MacOS + - { target: x86_64-apple-darwin, os: macos-13, name: "MacOS-x86" } + - { target: aarch64-apple-darwin, os: macos-14, name: "MacOS-arm" } # macOS-14 is the ARM chipset + # Windows + - { + target: x86_64-pc-windows-msvc, + os: windows-latest, + extension: .exe, + name: "Windows", + } + steps: + - name: checkout repo + uses: actions/checkout@v4 + - name: setup micromamba + uses: mamba-org/setup-micromamba@v1.8.1 + - name: Download binary from build + uses: actions/download-artifact@v4 + with: + name: pixi-${{ matrix.arch.target }}${{ matrix.arch.extension }} + path: pixi_bin + - name: Debug + run: | + pwd + - name: Setup unix binary, add to github path + if: matrix.arch.name != 'Windows' + run: | + mv pixi_bin/pixi-${{ matrix.arch.target }} pixi_bin/pixi + chmod a+x pixi_bin/pixi + echo "$(pwd)/pixi_bin" >> $GITHUB_PATH + - name: Create Directory and Move Executable + if: matrix.arch.name == 'Windows' && matrix.arch.target == 'x86_64-pc-windows-msvc' + run: | + New-Item -ItemType Directory -Force -Path "D:\.pixi" + Move-Item -Path "pixi_bin/pixi-${{ matrix.arch.target }}${{ matrix.arch.extension }}" -Destination "D:\.pixi\pixi.exe" + shell: pwsh + - name: Add to PATH + if: matrix.arch.name == 'Windows' && matrix.arch.target == 'x86_64-pc-windows-msvc' + run: echo "D:\.pixi" | Out-File -Append -Encoding utf8 -FilePath $env:GITHUB_PATH + shell: pwsh + - name: Verify and Use Executable + if: matrix.arch.name == 'Windows' && matrix.arch.target == 'x86_64-pc-windows-msvc' + run: | + echo "Current PATH: $env:PATH" + pixi --version + shell: pwsh + - name: Help + run: pixi --help + - name: Info + run: pixi info + - name: Install pixi + run: pixi install -v + - name: Test export + shell: bash + run: bash tests/test_export.sh diff --git a/src/cli/project/export/conda_environment.rs b/src/cli/project/export/conda_environment.rs index 56a6d8f0c..4223f49e9 100644 --- a/src/cli/project/export/conda_environment.rs +++ b/src/cli/project/export/conda_environment.rs @@ -17,17 +17,16 @@ use crate::project::Environment; use crate::Project; #[derive(Debug, Parser)] -#[clap(arg_required_else_help = false)] pub struct Args { /// Explicit path to export the environment to pub output_path: Option, - /// The platform to render the environment file for. - /// Defaults to the current platform. + /// The platform to render the environment file for. + /// Defaults to the current platform. #[arg(short, long)] pub platform: Option, - /// The environment to render the environment file for. + /// The environment to render the environment file for. /// Defaults to the default environment. #[arg(short, long)] pub environment: Option, diff --git a/tests/test_export.sh b/tests/test_export.sh new file mode 100644 index 000000000..e466716ef --- /dev/null +++ b/tests/test_export.sh @@ -0,0 +1,45 @@ +# Run from the root of the project using `bash tests/test_export.sh` +set -e +echo "Running test_export.sh" + +echo "Exporting the export test environment" +pixi project export conda-environment --manifest-path src/cli/project/export/test-data/testenv/pixi.toml | tee test-env.yml +echo "Creating the export test environment with micromamba" +micromamba create -y -f test-env.yml -n export-test +micromamba env list +micromamba env remove -y -n export-test + +echo "Exporting an environment.yml with pip extras" +pixi project export conda-environment --manifest-path examples/pypi/pixi.toml | tee test-env-pip-extras.yml +echo "Creating the pip extra test environment with micromamba" +micromamba create -y -f test-env-pip-extras.yml -n export-test-pip-extras +micromamba env list +micromamba env remove -y -n export-test-pip-extras + +echo "Export an environment.yml with editable source dependencies" +pixi project export conda-environment --manifest-path examples/pypi-source-deps/pixi.toml | tee test-env-source-deps.yml +echo "Creating the editable source dependencies test environment with micromamba" +micromamba create -y -f test-env-source-deps.yml -n export-test-source-deps +micromamba env list +micromamba env remove -y -n export-test-source-deps + +echo "Export an environment.yml with custom pip registry" +pixi project export conda-environment --manifest-path examples/pypi-custom-registry/pixi.toml | tee test-env-custom-registry.yml +echo "Creating the custom pip registry test environment with micromamba" +micromamba create -y -f test-env-custom-registry.yml -n export-test-custom-registry +micromamba env list +micromamba env remove -y -n export-test-custom-registry + +echo "Export an environment.yml with pip find links" +pixi project export conda-environment --manifest-path examples/pypi-find-links/pixi.toml | tee test-env-find-links.yml +echo "Creating the pip find links test environment with micromamba" +micromamba create -y -f test-env-find-links.yml -n export-test-find-links +micromamba env list +micromamba env remove -y -n export-test-find-links + +echo "Export an environment.yml from a pyproject.toml that has caused panics" +pixi project export conda-environment --manifest-path examples/docker/pyproject.toml | tee test-env-pyproject-panic.yml +echo "Creating the pyproject.toml panic test environment with micromamba" +micromamba create -y -f test-env-pyproject-panic.yml -n export-test-pyproject-panic +micromamba env list +micromamba env remove -y -n export-test-pyproject-panic From a54dbade3ac4d0c6f4bf4aa9de0495dc8d7dfcb3 Mon Sep 17 00:00:00 2001 From: Alex Kerney Date: Mon, 16 Sep 2024 21:04:42 -0400 Subject: [PATCH 13/19] Removed failing test (needs environment variable?), and cd into manifest dirs --- tests/test_export.sh | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/tests/test_export.sh b/tests/test_export.sh index e466716ef..678f7f697 100644 --- a/tests/test_export.sh +++ b/tests/test_export.sh @@ -3,43 +3,62 @@ set -e echo "Running test_export.sh" echo "Exporting the export test environment" -pixi project export conda-environment --manifest-path src/cli/project/export/test-data/testenv/pixi.toml | tee test-env.yml +cd src/cli/project/export/test-data/testenv +pixi project export conda-environment | tee test-env.yml echo "Creating the export test environment with micromamba" micromamba create -y -f test-env.yml -n export-test micromamba env list micromamba env remove -y -n export-test +rm test-env.yml +cd ../../../../../.. -echo "Exporting an environment.yml with pip extras" -pixi project export conda-environment --manifest-path examples/pypi/pixi.toml | tee test-env-pip-extras.yml -echo "Creating the pip extra test environment with micromamba" -micromamba create -y -f test-env-pip-extras.yml -n export-test-pip-extras -micromamba env list -micromamba env remove -y -n export-test-pip-extras +# Setuptools error with env_test_package +# echo "Exporting an environment.yml with pip extras" +# cd examples/pypi +# pixi project export conda-environment | tee test-env-pip-extras.yml +# echo "Creating the pip extra test environment with micromamba" +# micromamba create -y -f test-env-pip-extras.yml -n export-test-pip-extras +# micromamba env list +# micromamba env remove -y -n export-test-pip-extras +# rm test-env-pip-extras.yml +# cd ../.. echo "Export an environment.yml with editable source dependencies" -pixi project export conda-environment --manifest-path examples/pypi-source-deps/pixi.toml | tee test-env-source-deps.yml +cd examples/pypi-source-deps +pixi project export conda-environment | tee test-env-source-deps.yml echo "Creating the editable source dependencies test environment with micromamba" micromamba create -y -f test-env-source-deps.yml -n export-test-source-deps micromamba env list micromamba env remove -y -n export-test-source-deps +rm test-env-source-deps.yml +cd ../.. echo "Export an environment.yml with custom pip registry" -pixi project export conda-environment --manifest-path examples/pypi-custom-registry/pixi.toml | tee test-env-custom-registry.yml +cd examples/pypi-custom-registry/ +pixi project export conda-environment | tee test-env-custom-registry.yml echo "Creating the custom pip registry test environment with micromamba" micromamba create -y -f test-env-custom-registry.yml -n export-test-custom-registry micromamba env list micromamba env remove -y -n export-test-custom-registry +rm test-env-custom-registry.yml +cd ../.. echo "Export an environment.yml with pip find links" -pixi project export conda-environment --manifest-path examples/pypi-find-links/pixi.toml | tee test-env-find-links.yml +cd examples/pypi-find-links/ +pixi project export conda-environment | tee test-env-find-links.yml echo "Creating the pip find links test environment with micromamba" micromamba create -y -f test-env-find-links.yml -n export-test-find-links micromamba env list micromamba env remove -y -n export-test-find-links +rm test-env-find-links.yml +cd ../.. echo "Export an environment.yml from a pyproject.toml that has caused panics" -pixi project export conda-environment --manifest-path examples/docker/pyproject.toml | tee test-env-pyproject-panic.yml +cd examples/docker/ +pixi project export conda-environment | tee test-env-pyproject-panic.yml echo "Creating the pyproject.toml panic test environment with micromamba" micromamba create -y -f test-env-pyproject-panic.yml -n export-test-pyproject-panic micromamba env list micromamba env remove -y -n export-test-pyproject-panic +rm test-env-pyproject-panic.yml +cd ../.. From b39f100066e19b6ed6a97c9e5a362dbfb877d47f Mon Sep 17 00:00:00 2001 From: Alex Kerney Date: Mon, 16 Sep 2024 22:28:43 -0400 Subject: [PATCH 14/19] Disable windows env export tests --- .github/workflows/test_downstream.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test_downstream.yml b/.github/workflows/test_downstream.yml index a0bf564f9..4ad8fb43b 100644 --- a/.github/workflows/test_downstream.yml +++ b/.github/workflows/test_downstream.yml @@ -220,13 +220,13 @@ jobs: # MacOS - { target: x86_64-apple-darwin, os: macos-13, name: "MacOS-x86" } - { target: aarch64-apple-darwin, os: macos-14, name: "MacOS-arm" } # macOS-14 is the ARM chipset - # Windows - - { - target: x86_64-pc-windows-msvc, - os: windows-latest, - extension: .exe, - name: "Windows", - } + # # Windows + # - { + # target: x86_64-pc-windows-msvc, + # os: windows-latest, + # extension: .exe, + # name: "Windows", + # } steps: - name: checkout repo uses: actions/checkout@v4 From c4066b333f5d2486e333181c24c1cf905c2f7119 Mon Sep 17 00:00:00 2001 From: Alex Kerney Date: Mon, 16 Sep 2024 22:54:44 -0400 Subject: [PATCH 15/19] Include CLI suggested changes --- docs/reference/cli.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 0c076fb16..612c83709 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -1183,8 +1183,8 @@ pixi project environment list Exports a conda [`environment.yml` file](https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#creating-an-environment-from-an-environment-yml-file). The file can be used to create a conda environment using conda/mamba: ```shell -pixi project export conda_environment -mamba create --name --file +pixi project export conda-environment environment.yml +mamba create --name --file environment.yml ``` ##### Arguments @@ -1197,8 +1197,8 @@ mamba create --name --file - `--platform (-p)`: The platform to render. ```sh -pixi project export conda_environment --environment lint -pixi project export conda --platform linux-64 environment.linux-64.yml +pixi project export conda-environment --environment lint +pixi project export conda-environment --platform linux-64 environment.linux-64.yml ``` ### `project export conda_explicit_spec` From 72c95b5dad93ae48a724d008dc480e67b6219bbe Mon Sep 17 00:00:00 2001 From: Alex Kerney Date: Tue, 17 Sep 2024 06:36:00 -0400 Subject: [PATCH 16/19] Show commands to make export test script easier to understand --- tests/test_export.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_export.sh b/tests/test_export.sh index 678f7f697..fa10db1f9 100644 --- a/tests/test_export.sh +++ b/tests/test_export.sh @@ -1,5 +1,6 @@ # Run from the root of the project using `bash tests/test_export.sh` set -e +set -x echo "Running test_export.sh" echo "Exporting the export test environment" From a2bff2b090393b9108b932764e06c246b0af113c Mon Sep 17 00:00:00 2001 From: Alex Kerney Date: Tue, 17 Sep 2024 10:11:20 -0400 Subject: [PATCH 17/19] Filter export tests --- .github/workflows/rust.yml | 27 ++++++++++ .github/workflows/test_downstream.yml | 68 ------------------------ .github/workflows/test_exports.yml | 74 +++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 68 deletions(-) create mode 100644 .github/workflows/test_exports.yml diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 797d85e0a..73c505c68 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -417,3 +417,30 @@ jobs: needs: - build uses: ./.github/workflows/test_downstream.yml + + export-filter: + name: Only run example tests if relevant code or manifests have changed + runs-on: ubuntu-20.04 + outputs: + exports: ${{ steps.filter.outputs.exports }} + + steps: + - name: checkout repo + uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + exports: + - src/cli/project/export/conda_environment.rs + - src/cli/project/export/test-data/testenv/* + - examples/pypi-source-deps/* + - examples/pypi-custom-registry/* + - examples/pypi-find-links/* + - examples/docker/* + + export_tests: + name: "Export tests" + needs: export-filter + if: needs.export-filter.outputs.exports == 'true' + uses: ./.github/workflows/test_exports.yml diff --git a/.github/workflows/test_downstream.yml b/.github/workflows/test_downstream.yml index 4ad8fb43b..e74e4e2be 100644 --- a/.github/workflows/test_downstream.yml +++ b/.github/workflows/test_downstream.yml @@ -203,71 +203,3 @@ jobs: run: ${{ env.TARGET_RELEASE }}/pixi${{ matrix.arch.extension }} run typecheck-integration - name: Run integration tests run: ${{ env.TARGET_RELEASE }}/pixi${{ matrix.arch.extension }} run test-integration-ci - - test-export: - name: ${{ matrix.arch.name }} - Export Tests - runs-on: ${{ matrix.arch.os }} - strategy: - fail-fast: false - matrix: - arch: - # Linux - - { - target: x86_64-unknown-linux-musl, - os: ubuntu-20.04, - name: "Linux", - } - # MacOS - - { target: x86_64-apple-darwin, os: macos-13, name: "MacOS-x86" } - - { target: aarch64-apple-darwin, os: macos-14, name: "MacOS-arm" } # macOS-14 is the ARM chipset - # # Windows - # - { - # target: x86_64-pc-windows-msvc, - # os: windows-latest, - # extension: .exe, - # name: "Windows", - # } - steps: - - name: checkout repo - uses: actions/checkout@v4 - - name: setup micromamba - uses: mamba-org/setup-micromamba@v1.8.1 - - name: Download binary from build - uses: actions/download-artifact@v4 - with: - name: pixi-${{ matrix.arch.target }}${{ matrix.arch.extension }} - path: pixi_bin - - name: Debug - run: | - pwd - - name: Setup unix binary, add to github path - if: matrix.arch.name != 'Windows' - run: | - mv pixi_bin/pixi-${{ matrix.arch.target }} pixi_bin/pixi - chmod a+x pixi_bin/pixi - echo "$(pwd)/pixi_bin" >> $GITHUB_PATH - - name: Create Directory and Move Executable - if: matrix.arch.name == 'Windows' && matrix.arch.target == 'x86_64-pc-windows-msvc' - run: | - New-Item -ItemType Directory -Force -Path "D:\.pixi" - Move-Item -Path "pixi_bin/pixi-${{ matrix.arch.target }}${{ matrix.arch.extension }}" -Destination "D:\.pixi\pixi.exe" - shell: pwsh - - name: Add to PATH - if: matrix.arch.name == 'Windows' && matrix.arch.target == 'x86_64-pc-windows-msvc' - run: echo "D:\.pixi" | Out-File -Append -Encoding utf8 -FilePath $env:GITHUB_PATH - shell: pwsh - - name: Verify and Use Executable - if: matrix.arch.name == 'Windows' && matrix.arch.target == 'x86_64-pc-windows-msvc' - run: | - echo "Current PATH: $env:PATH" - pixi --version - shell: pwsh - - name: Help - run: pixi --help - - name: Info - run: pixi info - - name: Install pixi - run: pixi install -v - - name: Test export - shell: bash - run: bash tests/test_export.sh diff --git a/.github/workflows/test_exports.yml b/.github/workflows/test_exports.yml new file mode 100644 index 000000000..3c94ad8c5 --- /dev/null +++ b/.github/workflows/test_exports.yml @@ -0,0 +1,74 @@ +name: "Test exports" + +on: + workflow_call: + + +jobs: + test-export: + name: ${{ matrix.arch.name }} - Export Tests + runs-on: ${{ matrix.arch.os }} + strategy: + fail-fast: false + matrix: + arch: + # Linux + - { + target: x86_64-unknown-linux-musl, + os: ubuntu-20.04, + name: "Linux", + } + # MacOS + - { target: x86_64-apple-darwin, os: macos-13, name: "MacOS-x86" } + - { target: aarch64-apple-darwin, os: macos-14, name: "MacOS-arm" } # macOS-14 is the ARM chipset + # # Windows + # - { + # target: x86_64-pc-windows-msvc, + # os: windows-latest, + # extension: .exe, + # name: "Windows", + # } + steps: + - name: checkout repo + uses: actions/checkout@v4 + - name: setup micromamba + uses: mamba-org/setup-micromamba@v1.8.1 + - name: Download binary from build + uses: actions/download-artifact@v4 + with: + name: pixi-${{ matrix.arch.target }}${{ matrix.arch.extension }} + path: pixi_bin + - name: Debug + run: | + pwd + - name: Setup unix binary, add to github path + if: matrix.arch.name != 'Windows' + run: | + mv pixi_bin/pixi-${{ matrix.arch.target }} pixi_bin/pixi + chmod a+x pixi_bin/pixi + echo "$(pwd)/pixi_bin" >> $GITHUB_PATH + - name: Create Directory and Move Executable + if: matrix.arch.name == 'Windows' && matrix.arch.target == 'x86_64-pc-windows-msvc' + run: | + New-Item -ItemType Directory -Force -Path "D:\.pixi" + Move-Item -Path "pixi_bin/pixi-${{ matrix.arch.target }}${{ matrix.arch.extension }}" -Destination "D:\.pixi\pixi.exe" + shell: pwsh + - name: Add to PATH + if: matrix.arch.name == 'Windows' && matrix.arch.target == 'x86_64-pc-windows-msvc' + run: echo "D:\.pixi" | Out-File -Append -Encoding utf8 -FilePath $env:GITHUB_PATH + shell: pwsh + - name: Verify and Use Executable + if: matrix.arch.name == 'Windows' && matrix.arch.target == 'x86_64-pc-windows-msvc' + run: | + echo "Current PATH: $env:PATH" + pixi --version + shell: pwsh + - name: Help + run: pixi --help + - name: Info + run: pixi info + - name: Install pixi + run: pixi install -v + - name: Test export + shell: bash + run: bash tests/test_export.sh From 50533482d94273f4be22c6cbfe23d11f77ea9136 Mon Sep 17 00:00:00 2001 From: Alex Kerney Date: Tue, 17 Sep 2024 10:12:23 -0400 Subject: [PATCH 18/19] Make sure export tests depend on the build --- .github/workflows/rust.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 73c505c68..ab7c601be 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -421,6 +421,8 @@ jobs: export-filter: name: Only run example tests if relevant code or manifests have changed runs-on: ubuntu-20.04 + needs: + - build outputs: exports: ${{ steps.filter.outputs.exports }} From 0af9012b1c1f3e9a021307aaf5c6e270a766eefb Mon Sep 17 00:00:00 2001 From: Alex Kerney Date: Tue, 17 Sep 2024 10:16:21 -0400 Subject: [PATCH 19/19] Fix workflow formatting --- .github/workflows/rust.yml | 2 +- .github/workflows/test_exports.yml | 38 +++++++++++++++--------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 18bdd3a57..81c5d3b06 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -444,7 +444,7 @@ jobs: export_tests: name: "Export tests" needs: export-filter - if: needs.export-filter.outputs.exports == 'true' + if: ${{ needs.export-filter.outputs.exports == 'true' }} uses: ./.github/workflows/test_exports.yml test_common_wheels: diff --git a/.github/workflows/test_exports.yml b/.github/workflows/test_exports.yml index 3c94ad8c5..27f4d21ae 100644 --- a/.github/workflows/test_exports.yml +++ b/.github/workflows/test_exports.yml @@ -9,25 +9,25 @@ jobs: name: ${{ matrix.arch.name }} - Export Tests runs-on: ${{ matrix.arch.os }} strategy: - fail-fast: false - matrix: - arch: - # Linux - - { - target: x86_64-unknown-linux-musl, - os: ubuntu-20.04, - name: "Linux", - } - # MacOS - - { target: x86_64-apple-darwin, os: macos-13, name: "MacOS-x86" } - - { target: aarch64-apple-darwin, os: macos-14, name: "MacOS-arm" } # macOS-14 is the ARM chipset - # # Windows - # - { - # target: x86_64-pc-windows-msvc, - # os: windows-latest, - # extension: .exe, - # name: "Windows", - # } + fail-fast: false + matrix: + arch: + # Linux + - { + target: x86_64-unknown-linux-musl, + os: ubuntu-20.04, + name: "Linux", + } + # MacOS + - { target: x86_64-apple-darwin, os: macos-13, name: "MacOS-x86" } + - { target: aarch64-apple-darwin, os: macos-14, name: "MacOS-arm" } # macOS-14 is the ARM chipset + # # Windows + # - { + # target: x86_64-pc-windows-msvc, + # os: windows-latest, + # extension: .exe, + # name: "Windows", + # } steps: - name: checkout repo uses: actions/checkout@v4