Skip to content

Commit

Permalink
Merge pull request #161 from tdejager/feat/run-deps-in-sequence
Browse files Browse the repository at this point in the history
feat: now runs commands sequentially
  • Loading branch information
tdejager authored Jul 3, 2023
2 parents 010bd06 + 4cb6059 commit c2718b3
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 50 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,4 @@ url = "2.4.0"
[dev-dependencies]
rattler_digest = { default-features = false, git = "https://github.com/mamba-org/rattler", branch = "main" }
serde_json = "1.0.96"
tokio = { version = "1.27.0", features = ["rt"] }
89 changes: 53 additions & 36 deletions src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ pub struct RunScriptCommand {
_script: tempfile::NamedTempFile,
}

pub async fn create_command(args: Args) -> anyhow::Result<RunScriptCommand> {
let command: Vec<_> = args.command.iter().map(|c| c.to_string()).collect();
let project = Project::load_or_else_discover(args.manifest_path.as_deref())?;
pub async fn order_commands(
commands: Vec<String>,
project: &Project,
) -> anyhow::Result<VecDeque<Command>> {
let command: Vec<_> = commands.iter().map(|c| c.to_string()).collect();

let (command_name, command) = command
.first()
Expand All @@ -53,37 +55,12 @@ pub async fn create_command(args: Args) -> anyhow::Result<RunScriptCommand> {
(
None,
Command::Process(ProcessCmd {
cmd: CmdArgs::Multiple(args.command),
cmd: CmdArgs::Multiple(commands),
depends_on: vec![],
}),
)
});

// Determine the current shell
let shell: ShellEnum = ShellEnum::default();

// Construct an activator so we can run commands from the environment
let prefix = get_up_to_date_prefix(&project).await?;
let activator = Activator::from_path(prefix.root(), shell.clone(), Platform::current())?;

let activator_result = activator.activation(ActivationVariables {
// Get the current PATH variable
path: std::env::var_os("PATH").map(|path_var| std::env::split_paths(&path_var).collect()),

// Start from an empty prefix
conda_prefix: None,

// Prepending environment paths so they get found first.
path_modification_behaviour: PathModificationBehaviour::Prepend,
})?;

// Generate a temporary file with the script to execute. This includes the activation of the
// environment.
let mut script = format!("{}\n", activator_result.script.trim());

// Add meta data env variables to help user interact with there configuration.
add_metadata_as_env_vars(&mut script, &shell, &project)?;

// Perform post order traversal of the commands and their `depends_on` to make sure they are
// executed in the right order.
let mut s1 = VecDeque::new();
Expand Down Expand Up @@ -120,10 +97,39 @@ pub async fn create_command(args: Args) -> anyhow::Result<RunScriptCommand> {
s2.push_back(command)
}

while let Some(command) = s2.pop_back() {
// Write the invocation of the command into the script.
command.write_invoke_script(&mut script, &shell, &project, &activator_result)?;
}
Ok(s2)
}

pub async fn create_command(
command: Command,
project: &Project,
) -> anyhow::Result<RunScriptCommand> {
// Determine the current shell
let shell: ShellEnum = ShellEnum::default();

// Construct an activator so we can run commands from the environment
let prefix = get_up_to_date_prefix(project).await?;
let activator = Activator::from_path(prefix.root(), shell.clone(), Platform::current())?;

let activator_result = activator.activation(ActivationVariables {
// Get the current PATH variable
path: std::env::var_os("PATH").map(|path_var| std::env::split_paths(&path_var).collect()),

// Start from an empty prefix
conda_prefix: None,

// Prepending environment paths so they get found first.
path_modification_behaviour: PathModificationBehaviour::Prepend,
})?;

// Generate a temporary file with the script to execute. This includes the activation of the
// environment.
let mut script = format!("{}\n", activator_result.script.trim());

// Add meta data env variables to help user interact with there configuration.
add_metadata_as_env_vars(&mut script, &shell, project)?;

command.write_invoke_script(&mut script, &shell, project, &activator_result)?;

tracing::debug!("Activation script:\n{}", script);

Expand All @@ -144,9 +150,20 @@ pub async fn create_command(args: Args) -> anyhow::Result<RunScriptCommand> {

/// CLI entry point for `pixi run`
pub async fn execute(args: Args) -> anyhow::Result<()> {
let mut script_command = create_command(args).await?;
let status = script_command.command.spawn()?.wait()?.code().unwrap_or(1);
std::process::exit(status);
let project = Project::load_or_else_discover(args.manifest_path.as_deref())?;
// Get the correctly ordered commands
let mut ordered_commands = order_commands(args.command, &project).await?;

// Execute the commands in the correct order
while let Some(command) = ordered_commands.pop_back() {
let mut script_command = create_command(command, &project).await?;
let status = script_command.command.spawn()?.wait()?.code().unwrap_or(1);
if status != 0 {
std::process::exit(status);
}
}

Ok(())
}

/// Given a command and arguments to invoke it, format it so that it is as generalized as possible.
Expand Down
45 changes: 34 additions & 11 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ pub mod package_database;

use pixi::cli::add::SpecType;
use pixi::cli::install::Args;
use pixi::cli::run::create_command;
use pixi::cli::run::{create_command, order_commands};
use pixi::cli::{add, init, run};
use pixi::{consts, Project};
use rattler_conda_types::conda_lock::CondaLock;
use rattler_conda_types::{MatchSpec, Version};
use std::future::{Future, IntoFuture};
use std::path::{Path, PathBuf};
use std::pin::Pin;
use std::process::Stdio;
use std::process::{Output, Stdio};
use std::str::FromStr;
use tempfile::TempDir;
use tokio::sync::mpsc::UnboundedReceiver;
use tokio::task::spawn_blocking;
use url::Url;

/// To control the pixi process
Expand All @@ -24,7 +26,7 @@ pub struct PixiControl {
}

pub struct RunResult {
output: std::process::Output,
output: Output,
}

impl RunResult {
Expand Down Expand Up @@ -121,15 +123,36 @@ impl PixiControl {
}

/// Run a command
pub async fn run(&self, mut args: run::Args) -> anyhow::Result<RunResult> {
pub async fn run(&self, mut args: run::Args) -> anyhow::Result<UnboundedReceiver<RunResult>> {
args.manifest_path = args.manifest_path.or_else(|| Some(self.manifest_path()));
let mut script_command = create_command(args).await?;
let output = script_command
.command
.stdout(Stdio::piped())
.spawn()?
.wait_with_output()?;
Ok(RunResult { output })
let mut commands = order_commands(args.command, &self.project().unwrap()).await?;

let project = self.project().unwrap();
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();

tokio::spawn(async move {
while let Some(command) = commands.pop_back() {
let mut command = create_command(command, &project)
.await
.expect("could not create command");
let tx = tx.clone();
spawn_blocking(move || {
let output = command
.command
.stdout(Stdio::piped())
.spawn()
.expect("could not spawn task")
.wait_with_output()
.expect("could not run command");
tx.send(RunResult { output })
.expect("could not send output");
})
.await
.unwrap();
}
});

Ok(rx)
}

/// Create an installed environment. I.e a resolved and installed prefix
Expand Down
7 changes: 4 additions & 3 deletions tests/install_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,16 @@ async fn install_run_python() {
assert!(lock.contains_matchspec("python==3.11.0"));

// Check if python is installed and can be run
let result = pixi
let mut result = pixi
.run(run::Args {
command: string_from_iter(["python", "--version"]),
..Default::default()
})
.await
.unwrap();
assert!(result.success());
assert_eq!(result.stdout().trim(), "Python 3.11.0");
let run_result = result.recv().await.unwrap();
assert!(run_result.success());
assert_eq!(run_result.stdout().trim(), "Python 3.11.0");
}

/// This is a test to check that creating incremental lock files works.
Expand Down

0 comments on commit c2718b3

Please sign in to comment.