Skip to content

Commit 6e5c060

Browse files
committed
feat: pixi-conda-run
1 parent b1eaff0 commit 6e5c060

File tree

6 files changed

+127
-1
lines changed

6 files changed

+127
-1
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/pixi_conda/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ pixi_utils = { workspace = true }
5151
rattler = { workspace = true }
5252
rattler_conda_types = { workspace = true }
5353
rattler_repodata_gateway = { workspace = true }
54+
rattler_shell = { workspace = true }
5455
rattler_solve = { workspace = true, features = ["libsolv_c", "resolvo"] }
5556
rattler_virtual_packages = { workspace = true }
5657
tabwriter = { workspace = true, features = ["ansi_formatting"] }

crates/pixi_conda/src/cli.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1-
use crate::create;
1+
use crate::{create, run};
22
use clap::Subcommand;
33
use pixi_config::Config;
44

55
/// Pixi-conda is a tool for managing conda environments.
66
#[derive(Subcommand, Debug)]
77
pub enum Args {
88
Create(create::Args),
9+
Run(run::Args),
910
}
1011

1112
pub async fn execute(args: Args) -> miette::Result<()> {
1213
let config = Config::load_global();
1314

1415
match args {
1516
Args::Create(args) => create::execute(config, args).await,
17+
Args::Run(args) => run::execute(config, args).await,
1618
}
1719
}

crates/pixi_conda/src/environment_name.rs

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::fmt::Display;
12
use std::str::FromStr;
23
use thiserror::Error;
34

@@ -10,6 +11,12 @@ const INVALID_CHARACTERS: &[char] = &['/', '\\', ':', ',', ' '];
1011
#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
1112
pub struct EnvironmentName(String);
1213

14+
impl Display for EnvironmentName {
15+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16+
write!(f, "{}", self.0)
17+
}
18+
}
19+
1320
impl AsRef<str> for EnvironmentName {
1421
fn as_ref(&self) -> &str {
1522
&self.0

crates/pixi_conda/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ pub mod cli;
22
mod create;
33
mod environment_name;
44
mod registry;
5+
mod run;
56

67
pub use environment_name::EnvironmentName;

crates/pixi_conda/src/run/mod.rs

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
use std::{path::PathBuf, process::Stdio};
2+
3+
use clap::Parser;
4+
use miette::{IntoDiagnostic, Report};
5+
use pixi_config::Config;
6+
use rattler_conda_types::Platform;
7+
use rattler_shell::shell::ShellEnum;
8+
9+
use crate::{registry::Registry, EnvironmentName};
10+
11+
/// Run an executable in a conda environment.
12+
#[derive(Parser, Debug)]
13+
#[clap(trailing_var_arg = true, disable_help_flag = true)]
14+
pub struct Args {
15+
#[clap(num_args = 1.., allow_hyphen_values = true, required = true)]
16+
args: Vec<String>,
17+
18+
/// Print help
19+
#[clap(long, short, action = clap::ArgAction::Help)]
20+
help: Option<bool>,
21+
22+
/// Name of environment.
23+
#[clap(
24+
long,
25+
short,
26+
help_heading = "Target Environment Specification",
27+
conflicts_with = "prefix",
28+
required = true
29+
)]
30+
name: Option<EnvironmentName>,
31+
32+
/// Path to environment location (i.e. prefix).
33+
#[clap(long, short, help_heading = "Target Environment Specification")]
34+
prefix: Option<PathBuf>,
35+
}
36+
37+
pub async fn execute(_config: Config, mut args: Args) -> miette::Result<()> {
38+
// Determine the prefix to use
39+
let prefix = if let Some(name) = &args.name {
40+
&Registry::from_env().root().join(name.as_ref())
41+
} else if let Some(prefix) = &args.prefix {
42+
prefix
43+
} else {
44+
unreachable!("Either a name or a prefix must be provided")
45+
};
46+
47+
// Make sure it exists
48+
if !prefix.is_dir() || !prefix.join("conda-meta").is_dir() {
49+
let prefix_or_name = if let Some(name) = &args.name {
50+
format!("--name {name}")
51+
} else if let Some(prefix) = &args.prefix {
52+
format!("--prefix {}", prefix.display())
53+
} else {
54+
unreachable!("Either a name or a prefix must be provided")
55+
};
56+
miette::bail!(
57+
help = format!(
58+
"You can create an environment with:\n\n\tpixi-conda create {prefix_or_name} ..."
59+
),
60+
"The environment at '{}' does not appear to be a valid conda environment",
61+
prefix.display()
62+
);
63+
};
64+
65+
// Collect environment variables for the prefix.
66+
let activation_variables = rattler_shell::activation::Activator::from_path(
67+
prefix,
68+
ShellEnum::default(),
69+
Platform::current(),
70+
)
71+
.into_diagnostic()?
72+
.run_activation(
73+
rattler_shell::activation::ActivationVariables::from_env().into_diagnostic()?,
74+
None,
75+
)
76+
.into_diagnostic()?;
77+
78+
// Spawn the process
79+
let executable = args.args.remove(0);
80+
let mut command = std::process::Command::new(&executable);
81+
82+
// Set the environment variables
83+
command.envs(activation_variables);
84+
85+
// Add the arguments
86+
command.args(args.args);
87+
88+
// Inherit stdin, stdout, and stderr
89+
command
90+
.stdin(Stdio::inherit())
91+
.stdout(Stdio::inherit())
92+
.stderr(Stdio::inherit());
93+
94+
// Spawn the child process
95+
#[cfg(target_family = "unix")]
96+
command.exec();
97+
98+
#[cfg(target_os = "windows")]
99+
{
100+
let mut child = match command.spawn() {
101+
Ok(child) => child,
102+
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
103+
miette::bail!("The executable '{}' could not be found", executable);
104+
}
105+
Err(e) => return Err(Report::from_err(e)),
106+
};
107+
108+
// Wait for the child process to complete
109+
let status = child.wait().into_diagnostic()?;
110+
111+
// Exit with the same status code as the child process
112+
std::process::exit(status.code().unwrap_or(1));
113+
}
114+
}

0 commit comments

Comments
 (0)