Skip to content

Commit

Permalink
Export conda environment.yml
Browse files Browse the repository at this point in the history
Adds an export command group, and a subcommand for exporting in Conda environment.yml files.

Currently it just exports the direct dependency names, but I could see adding flags to include all locked dependencies. It also currently does not export the versions, but that could also be a flag to select if it should export the specs as in the manifest, or as locked.

works on prefix-dev#800
  • Loading branch information
abkfenris committed May 22, 2024
1 parent f7dd008 commit abcc0de
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 6 deletions.
80 changes: 80 additions & 0 deletions src/cli/export/conda.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use std::path::PathBuf;

use clap::Parser;

use itertools::Itertools;
use miette::IntoDiagnostic;
use rattler_conda_types::Platform;

use crate::utils::conda_environment_file::{CondaEnvDep, CondaEnvFile};
use crate::{HasFeatures, Project};

/// Exports a projects dependencies as an environment.yml
///
/// The environment is printed to standard out
#[derive(Debug, Parser)]
#[clap(arg_required_else_help = false)]
pub struct Args {
/// The platform to list packages for. Defaults to the current platform.
#[arg(long)]
pub platform: Option<Platform>,

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

/// The environment to list packages for. Defaults to the default environment.
#[arg(short, long)]
pub environment: Option<String>,

/// Name for environment
#[arg(short, long)]
pub name: Option<String>,
}

pub async fn execute(args: Args) -> miette::Result<()> {
let project = Project::load_or_else_discover(args.manifest_path.as_deref())?;
let environment = project.environment_from_name_or_env_var(args.environment)?;

let platform = args.platform.unwrap_or_else(|| environment.best_platform());

let name = match args.name {
Some(arg_name) => arg_name,
None => format!("{}-{}-{}", project.name(), environment.name(), platform),
};

let channels = environment
.channels()
.into_iter()
.map(|channel| channel.name().to_string())
.collect_vec();

let mut dependencies = environment
.dependencies(None, Some(platform))
.into_specs()
.map(|(name, _spec)| CondaEnvDep::Conda(name.as_source().to_string()))
.collect_vec();

let pypi_dependencies = environment
.pypi_dependencies(Some(platform))
.into_specs()
.map(|(name, _spec)| name.as_source().to_string())
.collect_vec();

if !pypi_dependencies.is_empty() {
dependencies.push(CondaEnvDep::Pip {
pip: pypi_dependencies,
});
}

let env_file = CondaEnvFile {
name: Some(name),
channels,
dependencies,
};

let env_string = serde_yaml::to_string(&env_file).into_diagnostic()?;
println!("{}", env_string);

Ok(())
}
23 changes: 23 additions & 0 deletions src/cli/export/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use clap::Parser;

mod conda;

#[derive(Debug, Parser)]
pub enum Command {
#[clap(alias = "c")]
Conda(conda::Args),
}

/// Subcommand for exporting dependencies to additional formats
#[derive(Debug, Parser)]
pub struct Args {
#[command(subcommand)]
command: Command,
}

pub async fn execute(cmd: Args) -> miette::Result<()> {
match cmd.command {
Command::Conda(args) => conda::execute(args).await?,
};
Ok(())
}
4 changes: 4 additions & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use tracing_subscriber::{filter::LevelFilter, util::SubscriberInitExt, EnvFilter
pub mod add;
pub mod completion;
pub mod config;
pub mod export;
pub mod global;
pub mod has_specs;
pub mod info;
Expand Down Expand Up @@ -103,6 +104,8 @@ pub enum Command {
List(list::Args),
#[clap(visible_alias = "t")]
Tree(tree::Args),
#[clap(visible_alias = "e")]
Export(export::Args),

// Global level commands
#[clap(visible_alias = "g")]
Expand Down Expand Up @@ -276,6 +279,7 @@ pub async fn execute_command(command: Command) -> miette::Result<()> {
Command::SelfUpdate(cmd) => self_update::execute(cmd).await,
Command::List(cmd) => list::execute(cmd).await,
Command::Tree(cmd) => tree::execute(cmd).await,
Command::Export(cmd) => export::execute(cmd).await,
}
}

Expand Down
12 changes: 6 additions & 6 deletions src/utils/conda_environment_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@ use itertools::Itertools;
use miette::IntoDiagnostic;
use rattler_conda_types::ParseStrictness::Lenient;
use rattler_conda_types::{Channel, MatchSpec};
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use std::{io::BufRead, path::Path, sync::Arc};

use crate::config::Config;

#[derive(Deserialize, Debug, Clone)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CondaEnvFile {
#[serde(default)]
name: Option<String>,
pub name: Option<String>,
#[serde(default)]
channels: Vec<String>,
dependencies: Vec<CondaEnvDep>,
pub channels: Vec<String>,
pub dependencies: Vec<CondaEnvDep>,
}

#[derive(Deserialize, Debug, Clone)]
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum CondaEnvDep {
Conda(String),
Expand Down

0 comments on commit abcc0de

Please sign in to comment.