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/add command cli #177

Merged
merged 10 commits into from
Jul 7, 2023
25 changes: 25 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ With `pixi` you can install packages in global space or local to the environment
| `install` | Installs all dependencies of the project in its environment |
| `run` | Runs the given command in a project's environment |
| `shell` | Starts a shell in the project's environment |
| `tasks` | Manage tasks in your `pixi.toml` file |

### Initialize a new project
This command is used to create a new project.
Expand Down Expand Up @@ -59,6 +60,30 @@ pixi run --manifest-path ~/myproject python
pixi run build
```

### Create a task from a command
If you want to make a shorthand for a specific command you can add a task for it
```bash
pixi task add cow cowpy "Hello User"
```

This adds the following to the `pixi.toml`:

```toml
[tasks]
cow = "cowpy \"Hello User\""
```
Which you can then run with the `run` command:

```bash
pixi run cow
```

To remove a task you can use the `task remove` command:

```bash
pixi task remove cow
```

### Start a shell in the environment
This command starts a new shell in the project's environment.
To exit the pixi shell, simply run exit
Expand Down
2 changes: 1 addition & 1 deletion examples/cpp-sdl/pixi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ authors = ["Bas Zalmstra <bas@prefix.dev>"]
channels = ["conda-forge"]
platforms = ["win-64", "linux-64", "osx-64", "osx-arm64"]

[commands]
[tasks]
# Configures CMake
configure = { cmd = [
"cmake",
Expand Down
2 changes: 1 addition & 1 deletion examples/flask-hello-world/pixi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ authors = ["Wolf Vollprecht <wolf@prefix.dev>"]
channels = ["conda-forge"]
platforms = ["linux-64", "win-64", "osx-64", "osx-arm64"]

[commands]
[tasks]
start = "python -m flask run --port=5050"

[dependencies]
Expand Down
2 changes: 1 addition & 1 deletion examples/opencv/pixi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ authors = ["Ruben Arts <ruben@prefix.dev>"]
channels = ["conda-forge"]
platforms = ["linux-64", "win-64", "osx-64", "osx-arm64"]

[commands]
[tasks]
start = "python webcam_capture.py"
calibrate = "python calibrate.py"

Expand Down
2 changes: 1 addition & 1 deletion examples/turtlesim/pixi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ authors = ["Ruben Arts <ruben@prefix.dev>"]
channels = ["conda-forge", "robostack-staging"]
platforms = ["linux-64", "win-64", "osx-64", "osx-arm64"]

[commands]
[tasks]
start = "ros2 run turtlesim turtlesim_node"
teleop = "ros2 run turtlesim turtle_teleop_key"

Expand Down
4 changes: 2 additions & 2 deletions src/cli/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ authors = ["{{ author[0] }} <{{ author[1] }}>"]
channels = ["{{ channels|join("\", \"") }}"]
platforms = ["{{ platform }}"]

[commands]
[tasks]

[dependencies]
"#;
Expand Down Expand Up @@ -141,7 +141,7 @@ mod tests {
);
assert_eq!(
get_dir(std::env::current_dir().unwrap()).unwrap(),
PathBuf::from(std::env::current_dir().unwrap().canonicalize().unwrap())
std::env::current_dir().unwrap().canonicalize().unwrap()
);
}

Expand Down
4 changes: 4 additions & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub mod init;
pub mod install;
pub mod run;
pub mod shell;
pub mod task;

#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
Expand Down Expand Up @@ -49,7 +50,9 @@ pub enum Command {
#[clap(alias = "g")]
Global(global::Args),
Auth(auth::Args),
#[clap(alias = "i")]
Install(install::Args),
Task(task::Args),
}

fn completion(args: CompletionCommand) -> Result<(), Error> {
Expand Down Expand Up @@ -104,5 +107,6 @@ pub async fn execute_command(command: Command) -> Result<(), Error> {
Command::Auth(cmd) => auth::execute(cmd).await,
Command::Install(cmd) => install::execute(cmd).await,
Command::Shell(cmd) => shell::execute(cmd).await,
Command::Task(cmd) => task::execute(cmd),
}
}
74 changes: 36 additions & 38 deletions src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,90 +12,87 @@ use rattler_conda_types::Platform;
use crate::prefix::Prefix;
use crate::progress::await_in_progress;
use crate::project::environment::get_metadata_env;
use crate::{
command::{CmdArgs, Command, ProcessCmd},
environment::get_up_to_date_prefix,
Project,
};
use crate::task::{CmdArgs, Execute, Task};
use crate::{environment::get_up_to_date_prefix, Project};
use rattler_shell::{
activation::{ActivationVariables, Activator, PathModificationBehaviour},
shell::ShellEnum,
};

// Run output which includes the information gotten from the deno task shell run.
/// Runs task in project.
#[derive(Default)]
pub struct RunOutput {
pub exit_code: i32,
pub stdout: String,
pub stderr: String,
}

/// Runs command in project.
/// Runs task in project.
#[derive(Parser, Debug, Default)]
#[clap(trailing_var_arg = true, arg_required_else_help = true)]
pub struct Args {
/// The command you want to run in the projects environment.
pub command: Vec<String>,
/// The task you want to run in the projects environment.
pub task: Vec<String>,

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

pub fn order_commands(
commands: Vec<String>,
pub fn order_tasks(
tasks: Vec<String>,
project: &Project,
) -> anyhow::Result<VecDeque<(Command, Vec<String>)>> {
let command: Vec<_> = commands.iter().map(|c| c.to_string()).collect();
) -> anyhow::Result<VecDeque<(Task, Vec<String>)>> {
let tasks: Vec<_> = tasks.iter().map(|c| c.to_string()).collect();

// Find the command in the project.
let (command_name, command, additional_args) = command
let (task_name, task, additional_args) = tasks
.first()
.and_then(|cmd_name| {
project.command_opt(cmd_name).map(|cmd| {
project.task_opt(cmd_name).map(|cmd| {
(
Some(cmd_name.clone()),
cmd.clone(),
command[1..].iter().cloned().collect_vec(),
tasks[1..].iter().cloned().collect_vec(),
)
})
})
.unwrap_or_else(|| {
(
None,
Command::Process(ProcessCmd {
cmd: CmdArgs::Multiple(commands),
Task::Execute(Execute {
cmd: CmdArgs::Multiple(tasks),
depends_on: vec![],
}),
Vec::new(),
)
});

// Perform post order traversal of the commands and their `depends_on` to make sure they are
// Perform post order traversal of the tasks and their `depends_on` to make sure they are
// executed in the right order.
let mut s1 = VecDeque::new();
let mut s2 = VecDeque::new();
let mut added = HashSet::new();

// Add the command specified on the command line first
s1.push_back((command, additional_args));
if let Some(command_name) = command_name {
added.insert(command_name);
s1.push_back((task, additional_args));
if let Some(task_name) = task_name {
added.insert(task_name);
}

while let Some((command, additional_args)) = s1.pop_back() {
while let Some((task, additional_args)) = s1.pop_back() {
// Get the dependencies of the command
let depends_on = match &command {
Command::Process(process) => process.depends_on.as_slice(),
Command::Alias(alias) => &alias.depends_on,
let depends_on = match &task {
Task::Execute(process) => process.depends_on.as_slice(),
Task::Alias(alias) => &alias.depends_on,
_ => &[],
};

// Locate the dependencies in the project and add them to the stack
for dependency in depends_on.iter() {
if !added.contains(dependency) {
let cmd = project
.command_opt(dependency)
.task_opt(dependency)
.ok_or_else(|| anyhow::anyhow!("failed to find dependency {}", dependency))?
.clone();

Expand All @@ -104,21 +101,21 @@ pub fn order_commands(
}
}

s2.push_back((command, additional_args))
s2.push_back((task, additional_args))
}

Ok(s2)
}

pub async fn create_script(command: Command, args: Vec<String>) -> anyhow::Result<SequentialList> {
// Construct the script from the command
let command = match command {
Command::Process(ProcessCmd {
pub async fn create_script(task: Task, args: Vec<String>) -> anyhow::Result<SequentialList> {
// Construct the script from the task
let task = match task {
Task::Execute(Execute {
cmd: CmdArgs::Single(cmd),
..
})
| Command::Plain(cmd) => cmd,
Command::Process(ProcessCmd {
| Task::Plain(cmd) => cmd,
Task::Execute(Execute {
cmd: CmdArgs::Multiple(args),
..
}) => quote_arguments(args),
Expand All @@ -129,11 +126,12 @@ pub async fn create_script(command: Command, args: Vec<String>) -> anyhow::Resul

// Append the command line arguments
let cli_args = quote_arguments(args);
let full_script = format!("{command} {cli_args}");
let full_script = format!("{task} {cli_args}");

// Parse the shell command
deno_task_shell::parser::parse(full_script.trim())
}

/// Executes the given command withing the specified project and with the given environment.
pub async fn execute_script(
script: SequentialList,
Expand Down Expand Up @@ -185,10 +183,10 @@ pub async fn execute(args: Args) -> anyhow::Result<()> {
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)?;
let mut ordered_commands = order_tasks(args.task, &project)?;

// Get the environment to run the commands in.
let command_env = get_command_env(&project).await?;
let command_env = get_task_env(&project).await?;

// Execute the commands in the correct order
while let Some((command, args)) = ordered_commands.pop_back() {
Expand All @@ -206,7 +204,7 @@ pub async fn execute(args: Args) -> anyhow::Result<()> {
/// activation scripts from the environment and stores the environment variables it added, it adds
/// environment variables set by the project and merges all of that with the system environment
/// variables.
pub async fn get_command_env(project: &Project) -> anyhow::Result<HashMap<String, String>> {
pub async fn get_task_env(project: &Project) -> anyhow::Result<HashMap<String, String>> {
// Get the prefix which we can then activate.
let prefix = get_up_to_date_prefix(project).await?;

Expand Down
Loading