Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
377825a
initial commit for short circuit composition
prsabahrami Apr 8, 2025
2bf8204
add short-circuit composition tests
prsabahrami Apr 8, 2025
af9319c
fix pre-commit
prsabahrami Apr 8, 2025
e3a2202
refactor: replace macro vec![] with Vec::from()
prsabahrami Apr 8, 2025
34268fb
test: add task5 to short-circuit composition tests and verify output …
prsabahrami Apr 8, 2025
079d09a
chore: update schema
prsabahrami Apr 8, 2025
5099cec
docs: add shorthand syntax for task composition in advanced_tasks.md
prsabahrami Apr 8, 2025
ebb1684
remove string from short circuit definition
prsabahrami Apr 8, 2025
cb45ad2
minor update of the docs
prsabahrami Apr 8, 2025
9910fc5
Update docs/workspace/advanced_tasks.md
prsabahrami Apr 8, 2025
c50448c
docs: remove extra file and paragraph
prsabahrami Apr 8, 2025
49ca2f5
Rearrange and add todos
Hofer-Julian Apr 8, 2025
af7c3e1
feat: update pixi task alias
prsabahrami Apr 8, 2025
7028332
refactor: update pixi.toml snippets and remove todos in advanced_task…
prsabahrami Apr 8, 2025
6c5814e
Merge branch 'main' into short_circuit
prsabahrami Apr 9, 2025
875c491
Doc improvements
Hofer-Julian Apr 9, 2025
09347f6
Update schema
Hofer-Julian Apr 9, 2025
44f3761
Improve test
Hofer-Julian Apr 9, 2025
5910e7a
test: add integration test for pixi task alias command
prsabahrami Apr 9, 2025
2eb36be
minor fix
prsabahrami Apr 9, 2025
582c9db
replace alias with tasks in the test
prsabahrami Apr 9, 2025
3161fc1
fix the tests
prsabahrami Apr 9, 2025
4c1423a
improve test
prsabahrami Apr 9, 2025
114cfb9
do not load the manifest twice in the test
prsabahrami Apr 9, 2025
5a10fe9
fix pre-commit
prsabahrami Apr 9, 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
47 changes: 39 additions & 8 deletions crates/pixi_manifest/src/toml/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,40 @@ impl<'de> toml_span::Deserialize<'de> for TomlTask {
let mut th = match value.take() {
ValueInner::String(str) => return Ok(Task::Plain(str.into_owned()).into()),
ValueInner::Table(table) => TableHelper::from((table, value.span)),
ValueInner::Array(array) => {
let mut deps = Vec::new();
for mut item in array {
match item.take() {
ValueInner::String(str) => {
deps.push(Dependency::from(str.as_ref()));
Comment thread
prsabahrami marked this conversation as resolved.
Outdated
}
ValueInner::Table(table) => {
let mut th = TableHelper::from((table, item.span));
let name = th.required::<String>("task")?;
let args = th.optional::<Vec<String>>("args");
let environment = th
.optional::<String>("environment")
.map(|env| EnvironmentName::from_str(&env))
.transpose()
.map_err(|e| {
DeserError::from(expected(
"valid environment name",
ValueInner::String(e.attempted_parse.into()),
item.span,
))
})?;

deps.push(Dependency::new(&name, args, environment));
}
_ => return Err(expected("string or table", item.take(), item.span).into()),
}
}
return Ok(Task::Alias(Alias {
depends_on: deps,
description: None,
})
.into());
}
inner => return Err(expected("string or table", inner, value.span).into()),
};

Expand All @@ -58,7 +92,9 @@ impl<'de> toml_span::Deserialize<'de> for TomlTask {
.map(|mut item| {
let span = item.span;
match item.take() {
ValueInner::String(str) => Ok(Dependency::from(str.as_ref())),
ValueInner::String(str) => Ok::<Dependency, DeserError>(
Dependency::new(str.as_ref(), None, None),
),
ValueInner::Table(table) => {
let mut th = TableHelper::from((table, span));
let name = th.required::<String>("task")?;
Expand All @@ -75,16 +111,13 @@ impl<'de> toml_span::Deserialize<'de> for TomlTask {
))
})?;

// If the creating a new dependency fails, it means the environment name is invalid and exists hence we can safely unwrap the environment
Ok(Dependency::new(&name, args, environment))
}
inner => Err(expected("string or table", inner, span).into()),
}
})
.collect::<Result<Vec<Dependency>, DeserError>>()?,
ValueInner::String(str) => {
vec![Dependency::from(str.as_ref())]
}
ValueInner::String(str) => Vec::from([Dependency::from(str.as_ref())]),
inner => {
return Err::<Vec<Dependency>, DeserError>(
expected("string or array", inner, value.span).into(),
Expand Down Expand Up @@ -129,9 +162,7 @@ impl<'de> toml_span::Deserialize<'de> for TomlTask {
}
})
.collect::<Result<Vec<_>, _>>()?,
ValueInner::String(str) => {
vec![Dependency::from(str.as_ref())]
}
ValueInner::String(str) => Vec::from([Dependency::from(str.as_ref())]),
inner => return Err(expected("string or array", inner, value.span).into()),
};

Expand Down
23 changes: 23 additions & 0 deletions docs/source_files/pixi_tomls/tasks_short_circuit_advanced.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[workspace]
channels = ["conda-forge"]
name = "tasks-short-circuit-advanced"

# --8<-- [start:tasks]
[tasks]
format = "ruff format"
lint = "ruff check"
test = "pytest tests/"

# Simple references to task names
check = ["format", "lint", "test"]

# Mix of simple names and complex configurations
deploy = [
"test",
{ task = "build", args = [
"production",
] },
{ task = "publish", environment = "release" },
]

# --8<-- [end:tasks]
16 changes: 16 additions & 0 deletions docs/source_files/pixi_tomls/tasks_short_circuit_basic.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[workspace]
channels = ["conda-forge"]
name = "tasks-short-circuit-basic"

# --8<-- [start:tasks]
[tasks]
build = "make build"
test = "make test"

# Short circuit composition - define a task as an array of dependencies
all = [{ task = "build" }, { task = "test" }]

# This is equivalent to:
# all = { depends-on = [{ task = "build" }, { task = "test" }] }
Comment thread
prsabahrami marked this conversation as resolved.
Outdated

# --8<-- [end:tasks]
16 changes: 16 additions & 0 deletions docs/workspace/advanced_tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,22 @@ The environment specified for a task dependency takes precedence over the enviro

In the example above, the `test-all` task runs the `test` task in both Python 3.11 and 3.12 environments, allowing you to verify compatibility across different Python versions with a single command.

### Short circuit composition
Comment thread
prsabahrami marked this conversation as resolved.
Outdated

Pixi supports a shorthand syntax for defining tasks that only depend on other tasks. Instead of using the more verbose `depends-on` field, you can define a task directly as an array of dependencies:

```toml title="pixi.toml"
--8<-- "docs/source_files/pixi_tomls/tasks_short_circuit_basic.toml:tasks"
```

This shorthand is particularly useful for creating task compositions or pipelines without having to use the more verbose syntax. You can also include simple task names or specify arguments and environments:

```toml title="pixi.toml"
--8<-- "docs/source_files/pixi_tomls/tasks_short_circuit_advanced.toml:tasks"
```

This syntax makes it easier to compose tasks together without the extra verbosity when all you need is to define a sequence of dependencies.

## Working directory

Pixi tasks support the definition of a working directory.
Expand Down
4 changes: 2 additions & 2 deletions schema/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,8 +460,8 @@ class Target(StrictBaseModel):
pypi_dependencies: dict[PyPIPackageName, PyPIRequirement] | None = Field(
None, description="The PyPI dependencies for this target"
)
tasks: dict[TaskName, TaskInlineTable | NonEmptyStr] | None = Field(
None, description="The tasks of the target"
tasks: dict[TaskName, TaskInlineTable | list[DependsOn | TaskName] | NonEmptyStr] | None = (
Field(None, description="The tasks of the target")
)
activation: Activation | None = Field(
None, description="The scripts used on the activation of the project for this target"
Expand Down
15 changes: 15 additions & 0 deletions schema/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1556,6 +1556,21 @@
{
"$ref": "#/$defs/TaskInlineTable"
},
{
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/$defs/DependsOn"
},
{
"description": "A valid task name.",
"type": "string",
"pattern": "^[^\\s\\$]+$"
}
]
}
},
{
"type": "string",
"minLength": 1
Expand Down
37 changes: 37 additions & 0 deletions tests/integration_python/test_run_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1085,3 +1085,40 @@ def test_multiple_dependencies_with_environments(
"0.2.0",
],
)


def test_short_circuit_composition(pixi: Path, tmp_pixi_workspace: Path) -> None:
"""Test that short-circuiting composition works."""
manifest_path = tmp_pixi_workspace.joinpath("pixi.toml")

manifest_content = tomli.loads(EMPTY_BOILERPLATE_PROJECT)

manifest_content["tasks"] = {
"task1": "echo task1",
"task2": "echo task2",
"task3": [{"task": "task1"}],
"task4": [{"task": "task3"}, {"task": "task2"}],
"task5": {"depends-on": [{"task": "task3"}, {"task": "task2"}]},
}

manifest_path.write_text(tomli_w.dumps(manifest_content))

verify_cli_command(
[pixi, "run", "--manifest-path", manifest_path, "task4"],
stdout_contains=["task1", "task2"],
)

verify_cli_command(
[pixi, "run", "--manifest-path", manifest_path, "task3"],
stdout_contains="task1",
)

output1 = verify_cli_command(
[pixi, "run", "--manifest-path", manifest_path, "task5"],
)

output2 = verify_cli_command(
[pixi, "run", "--manifest-path", manifest_path, "task4"],
)

assert output1.stdout == output2.stdout and output1.stderr == output2.stderr