Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b4ad922
initial commit for supporting task args
Hofer-Julian Mar 28, 2025
9f3c8b7
add example, fix ux, and replace arg names
prsabahrami Mar 30, 2025
8ffa6a7
Merge branch 'main' into feat_taskargs
prsabahrami Mar 30, 2025
93f13b4
add example, fix ux, and replace arg names
prsabahrami Mar 30, 2025
385bc7d
update schema
prsabahrami Mar 30, 2025
be49655
fix pre-commit and schema
prsabahrami Mar 30, 2025
f3de226
update calculator example
prsabahrami Mar 30, 2025
ce2c8de
fix end of file
prsabahrami Mar 30, 2025
a57169f
docs: update advanced tasks docs with task arguments section
prsabahrami Apr 1, 2025
2dbe37f
Merge branch 'main' into feat_taskargs
prsabahrami Apr 1, 2025
bc9fdb7
Add newline before list
Hofer-Julian Apr 1, 2025
d46d5a2
update: update tasks documentation and introducing new error enum for…
prsabahrami Apr 1, 2025
c544d87
run fmt
prsabahrami Apr 1, 2025
b4e4d16
fix: pre-commit issues
prsabahrami Apr 1, 2025
225d4aa
chore: generate cli docs
prsabahrami Apr 1, 2025
0826b6c
refactor: update the error prints for replacing arguments
prsabahrami Apr 1, 2025
a27433e
Improve highlighting slightly
Hofer-Julian Apr 2, 2025
ca07248
Update src/task/executable_task.rs
prsabahrami Apr 2, 2025
01366f6
Update src/task/executable_task.rs
prsabahrami Apr 2, 2025
f5c75c1
update docs and remove real-world example and move the tomls to pixi_…
prsabahrami Apr 2, 2025
7039a43
add: a test for multiple inputs to a simple task case
prsabahrami Apr 2, 2025
0a74f55
Merge branch 'main' into feat_taskargs
prsabahrami Apr 2, 2025
d94a292
Merge branch 'main' into feat_taskargs
prsabahrami Apr 2, 2025
3e5a6e1
update docs
prsabahrami Apr 2, 2025
3827b4e
refactor: change section header from [project] to [workspace] in TOML…
prsabahrami Apr 2, 2025
bf059d2
Merge branch 'main' into feat_taskargs
prsabahrami Apr 2, 2025
131dc60
chore: remove extra print
prsabahrami Apr 2, 2025
de8491c
Make the tomls prettier
Hofer-Julian Apr 3, 2025
a14e832
Clarify error message
Hofer-Julian Apr 3, 2025
a7eec79
Adapt error messages and tests
Hofer-Julian Apr 3, 2025
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.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ uv-pep440 = { workspace = true }
uv-pep508 = { workspace = true }
uv-pypi-types = { workspace = true }

anyhow = "1.0.97"
ctrlc = { workspace = true }
fs-err = { workspace = true, features = ["tokio"] }
pixi_allocator = { workspace = true, optional = true }
Expand Down
153 changes: 129 additions & 24 deletions crates/pixi_manifest/src/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,35 @@ impl From<String> for TaskName {
TaskName(name)
}
}
impl From<TaskName> for String {
fn from(task_name: TaskName) -> Self {
task_name.0 // Assuming TaskName is a tuple struct with the first
// element as String

/// A task dependency with optional args
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
pub struct Dependency {
pub task_name: TaskName,
pub args: Option<Vec<String>>,
}

impl Dependency {
pub fn new(s: &str, args: Option<Vec<String>>) -> Self {
Dependency {
task_name: TaskName(s.to_string()),
args,
}
}
}

impl From<&str> for Dependency {
fn from(s: &str) -> Self {
Dependency::new(s, None)
}
}

impl std::fmt::Display for Dependency {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.args {
Some(args) if !args.is_empty() => write!(f, "{} with args", self.task_name),
_ => write!(f, "{}", self.task_name),
}
}
}

Expand All @@ -56,14 +81,14 @@ impl FromStr for TaskName {
#[derive(Debug, Clone)]
pub enum Task {
Plain(String),
Execute(Execute),
Execute(Box<Execute>),
Alias(Alias),
Custom(Custom),
}

impl Task {
/// Returns the names of the task that this task depends on
pub fn depends_on(&self) -> &[TaskName] {
pub fn depends_on(&self) -> &[Dependency] {
match self {
Task::Plain(_) | Task::Custom(_) => &[],
Task::Execute(cmd) => &cmd.depends_on,
Expand Down Expand Up @@ -185,6 +210,45 @@ impl Task {
_ => None,
}
}

/// Returns the arguments of the task.
pub fn get_args(&self) -> Option<&IndexMap<TaskArg, Option<String>>> {
match self {
Task::Execute(exe) => exe.args.as_ref(),
_ => None,
}
}

/// Creates a new task with updated arguments from the provided values.
/// Returns None if the task doesn't support arguments.
pub fn with_updated_args(&self, arg_values: &[String]) -> Option<Self> {
match self {
Task::Execute(exe) => {
if arg_values.len() > exe.args.as_ref().map_or(0, |args| args.len()) {
tracing::warn!("Task has more arguments than provided values");
return None;
}

if let Some(args_map) = &exe.args {
let mut new_args = args_map.clone();
for ((arg_name, _), value) in args_map.iter().zip(arg_values.iter()) {
if let Some(arg_value) = new_args.get_mut(arg_name) {
*arg_value = Some(value.clone());
}
}

// Create a new Execute with the updated args
let mut new_exe = (**exe).clone();
new_exe.args = Some(new_args);

Some(Task::Execute(Box::new(new_exe)))
} else {
None
}
}
_ => None,
}
}
}

/// A command script executes a single command from the environment
Expand All @@ -204,7 +268,7 @@ pub struct Execute {

/// A list of commands that should be run before this one
// BREAK: Make the remove the alias and force kebab-case
pub depends_on: Vec<TaskName>,
pub depends_on: Vec<Dependency>,

/// The working directory for the command relative to the root of the
/// project.
Expand All @@ -218,11 +282,34 @@ pub struct Execute {

/// Isolate the task from the running machine
pub clean_env: bool,

/// The arguments to pass to the task
pub args: Option<IndexMap<TaskArg, Option<String>>>,
}

impl From<Execute> for Task {
fn from(value: Execute) -> Self {
Task::Execute(value)
Task::Execute(Box::new(value))
}
}

#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub struct TaskArg {
/// The name of the argument
pub name: String,

/// The default value of the argument
pub default: Option<String>,
}

impl std::str::FromStr for TaskArg {
type Err = miette::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(TaskArg {
name: s.to_string(),
default: None,
})
}
}

Expand Down Expand Up @@ -284,7 +371,7 @@ impl CmdArgs {
#[derive(Debug, Clone)]
pub struct Alias {
/// A list of commands that should be run before this one
pub depends_on: Vec<TaskName>,
pub depends_on: Vec<Dependency>,

/// A description of the task.
pub description: Option<String>,
Expand All @@ -306,7 +393,7 @@ impl Display for Task {
let depends_on = self.depends_on();
if !depends_on.is_empty() {
if depends_on.len() == 1 {
write!(f, ", depends-on = '{}'", depends_on.iter().format(","))?;
write!(f, ", depends-on = '{}'", depends_on[0])?;
} else {
write!(f, ", depends-on = [{}]", depends_on.iter().format(","))?;
}
Expand Down Expand Up @@ -366,13 +453,22 @@ impl From<Task> for Item {
if !process.depends_on.is_empty() {
table.insert(
"depends-on",
Value::Array(Array::from_iter(
process
.depends_on
.into_iter()
.map(String::from)
.map(Value::from),
)),
Value::Array(Array::from_iter(process.depends_on.into_iter().map(
|dep| match &dep.args {
Some(args) if !args.is_empty() => {
let mut table = Table::new().into_inline_table();
table.insert("task", dep.task_name.to_string().into());
table.insert(
"args",
Value::Array(Array::from_iter(
args.iter().map(|arg| Value::from(arg.clone())),
)),
);
Value::InlineTable(table)
}
_ => Value::from(dep.task_name.to_string()),
},
))),
);
}
if let Some(cwd) = process.cwd {
Expand All @@ -390,13 +486,22 @@ impl From<Task> for Item {
let mut table = Table::new().into_inline_table();
table.insert(
"depends-on",
Value::Array(Array::from_iter(
alias
.depends_on
.into_iter()
.map(String::from)
.map(Value::from),
)),
Value::Array(Array::from_iter(alias.depends_on.into_iter().map(|dep| {
match &dep.args {
Some(args) if !args.is_empty() => {
let mut table = Table::new().into_inline_table();
table.insert("task", dep.task_name.to_string().into());
table.insert(
"args",
Value::Array(Array::from_iter(
args.iter().map(|arg| Value::from(arg.clone())),
)),
);
Value::InlineTable(table)
}
_ => Value::from(dep.task_name.to_string()),
}
}))),
);
Item::Value(Value::InlineTable(table))
}
Expand Down
Loading
Loading