Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: now runs commands sequentially #161

Merged
merged 1 commit into from
Jul 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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