Skip to content

Commit

Permalink
Feat/add command cli (#177)
Browse files Browse the repository at this point in the history
* feat: added the abilty to add commands through the command line and test
these

* fix: improved testing and cli output for adding commands

* feat: now uses single command whenever you expect it to

* feat: now prints warning and doesnt quote if its a single argument

* feat: renamed command to task

* feat: accepts 1..n args for specific arguments

* feat: added to docs

* feat: task add remove docs
  • Loading branch information
tdejager authored Jul 7, 2023
1 parent 72656ef commit 24a101d
Show file tree
Hide file tree
Showing 20 changed files with 716 additions and 175 deletions.
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 <[email protected]>"]
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 <[email protected]>"]
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 <[email protected]>"]
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 <[email protected]>"]
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

0 comments on commit 24a101d

Please sign in to comment.