Skip to content
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
55 changes: 55 additions & 0 deletions e2e/tasks/test_task_dep_args
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,61 @@ EOF
assert_contains "mise run lint" "fix=false"
assert_contains "mise run lint --fix" "fix=true"

# Skip dependency entries whose task name template renders empty, both during
# initial rendering and deferred usage rendering.
cat <<'EOF' >mise.toml
[tasks.initial-dependency]
run = 'echo "initial dependency ran"'

[tasks.usage-dependency]
run = 'echo "usage dependency ran"'

[tasks.usage-post-dependency]
run = 'echo "usage post-dependency ran"'

[tasks.usage-wait-task]
run = 'sleep 0.3; echo wait >> wait-order.txt; echo "usage wait task ran"'

[tasks.optional]
usage = 'flag "--enabled" default=#false'
depends = [
"{% if false %}initial-dependency{% endif %}",
"{% if true %}initial-dependency{% endif %}",
"{% if usage.enabled %}usage-dependency{% endif %}",
]
depends_post = [
"{% if false %}initial-post-dependency{% endif %}",
"{% if usage.enabled %}usage-post-dependency{% endif %}",
]
wait_for = [
"{% if false %}initial-wait-task{% endif %}",
"{% if usage.enabled %}usage-wait-task{% endif %}",
]
run = 'echo optional >> wait-order.txt; echo "optional ran"'
EOF

assert_contains "mise run optional" "optional ran"
assert_contains "mise run optional" "initial dependency ran"
output=$(mise run optional --enabled 2>&1)
assert_contains_text "$output" "usage dependency ran"
assert_contains_text "$output" "usage post-dependency ran"
assert_contains_text "$output" "optional ran"

rm -f wait-order.txt
mise run optional --enabled ::: usage-wait-task
assert "cat wait-order.txt" "wait
optional"

# Literal empty task names remain invalid rather than being treated as an
# optional templated dependency.
cat <<'EOF' >mise.toml
[tasks.invalid]
depends = [""]
run = 'echo "should not run"'
EOF

assert_fail "mise run invalid" "task not found:"

# Test with multiple args
cat <<'EOF' >mise.toml
[tasks.start-backend]
Expand Down
46 changes: 22 additions & 24 deletions src/task/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1905,21 +1905,9 @@ impl Task {
// Render deps that don't contain {{usage.*}} references. Deps with usage
// references are deferred until render_depends_with_usage() is called with
// the actual arg values from CLI or parent dependency.
for d in &mut self.depends {
if !dep_has_usage_ref(d) {
d.render(&mut tera, &tera_ctx)?;
}
}
for d in &mut self.depends_post {
if !dep_has_usage_ref(d) {
d.render(&mut tera, &tera_ctx)?;
}
}
for d in &mut self.wait_for {
if !dep_has_usage_ref(d) {
d.render(&mut tera, &tera_ctx)?;
}
}
render_task_deps(&mut self.depends, &mut tera, &tera_ctx, true)?;
render_task_deps(&mut self.depends_post, &mut tera, &tera_ctx, true)?;
render_task_deps(&mut self.wait_for, &mut tera, &tera_ctx, true)?;
if let Some(dir) = &mut self.dir
&& contains_template_syntax(dir)
{
Expand Down Expand Up @@ -1996,25 +1984,19 @@ impl Task {
&& let Some(raw) = &self.depends_raw
{
self.depends = raw.clone();
for d in &mut self.depends {
d.render(&mut tera, &tera_ctx)?;
}
render_task_deps(&mut self.depends, &mut tera, &tera_ctx, false)?;
}
if !self.depends_post.is_empty()
&& let Some(raw) = &self.depends_post_raw
{
self.depends_post = raw.clone();
for d in &mut self.depends_post {
d.render(&mut tera, &tera_ctx)?;
}
render_task_deps(&mut self.depends_post, &mut tera, &tera_ctx, false)?;
}
if !self.wait_for.is_empty()
&& let Some(raw) = &self.wait_for_raw
{
self.wait_for = raw.clone();
for d in &mut self.wait_for {
d.render(&mut tera, &tera_ctx)?;
}
render_task_deps(&mut self.wait_for, &mut tera, &tera_ctx, false)?;
}
Ok(())
}
Expand Down Expand Up @@ -2683,6 +2665,22 @@ pub(crate) fn dep_has_usage_ref(dep: &TaskDep) -> bool {
|| dep.env.values().any(|v| tera_template_has_usage_ref(v))
}

fn render_task_deps(
deps: &mut Vec<TaskDep>,
tera: &mut TeraEngine,
tera_ctx: &tera::Context,
defer_usage: bool,
) -> Result<()> {
let mut rendered = Vec::with_capacity(deps.len());
for mut dep in std::mem::take(deps) {
if (defer_usage && dep_has_usage_ref(&dep)) || dep.render(tera, tera_ctx)? {
rendered.push(dep);
}
}
*deps = rendered;
Ok(())
}

fn tera_template_has_usage_ref(s: &str) -> bool {
const TAGS: [(&str, &str); 2] = [("{{", "}}"), ("{%", "%}")];
for (open, close) in TAGS {
Expand Down
47 changes: 45 additions & 2 deletions src/task/task_dep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@ impl TaskDep {
&mut self,
tera: &mut TeraEngine,
tera_ctx: &tera::Context,
) -> crate::Result<&mut Self> {
) -> crate::Result<bool> {
if contains_template_syntax(&self.task) {
self.task = render_str(tera, &self.task, tera_ctx)?;
if self.task.trim().is_empty() {
return Ok(false);
}
}
let mut rendered_args = Vec::with_capacity(self.args.len());
for a in &self.args {
Expand All @@ -43,7 +46,7 @@ impl TaskDep {
}
}
self.parse_shell_style_env()?;
Ok(self)
Ok(true)
}

pub fn parse_shell_style_env(&mut self) -> crate::Result<&mut Self> {
Expand Down Expand Up @@ -372,6 +375,46 @@ mod tests {
assert_eq!(td.args, vec!["--fix"]);
}

#[test]
fn test_task_dep_render_skips_empty_templated_task() {
let mut td: TaskDep = "{% if false %}lint{% endif %}".parse().unwrap();
let mut tera = TeraEngine::V2(Box::default());
let ctx = tera::Context::new();

assert!(!td.render(&mut tera, &ctx).unwrap());
assert!(td.task.is_empty());
}

#[test]
fn test_task_dep_render_skips_whitespace_only_templated_task() {
let mut td: TaskDep = "\n{% if false %}lint{% endif %}\n".parse().unwrap();
let mut tera = TeraEngine::V2(Box::default());
let ctx = tera::Context::new();

assert!(!td.render(&mut tera, &ctx).unwrap());
assert_eq!(td.task, "\n\n");
}

#[test]
fn test_task_dep_render_keeps_non_empty_templated_task() {
let mut td: TaskDep = "{% if true %}lint{% endif %}".parse().unwrap();
let mut tera = TeraEngine::V2(Box::default());
let ctx = tera::Context::new();

assert!(td.render(&mut tera, &ctx).unwrap());
assert_eq!(td.task, "lint");
}

#[test]
fn test_task_dep_render_keeps_literal_empty_task() {
let mut td: TaskDep = "".parse().unwrap();
let mut tera = TeraEngine::V2(Box::default());
let ctx = tera::Context::new();

assert!(td.render(&mut tera, &ctx).unwrap());
assert!(td.task.is_empty());
}

#[test]
fn test_task_dep_single_token_with_equals() {
// Single token like "build=release" should be treated as task name, not env var
Expand Down
Loading