diff --git a/e2e/tasks/test_task_source_freshness_with_cwd b/e2e/tasks/test_task_source_freshness_with_cwd new file mode 100644 index 0000000000..61d3c359c3 --- /dev/null +++ b/e2e/tasks/test_task_source_freshness_with_cwd @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +cat <mise.toml +[tasks.cwd_task] +run = "cat input.txt" +dir = "{{cwd}}" +sources = ["input.txt"] +outputs = { auto = true } +EOF + +mkdir a +mkdir b + +echo "running a" >a/input.txt +echo "running b 1" >b/input.txt + +# Should first run and then skip +pushd a +assert "mise run -q cwd_task" "running a" +assert_empty "mise run -q cwd_task" +popd + +# Should first run again as directory was changed and then skip +pushd b +assert "mise run -q cwd_task" "running b 1" +assert_empty "mise run -q cwd_task" +popd + +echo "running b 2" >b/input.txt + +# Should still skip as input in this directory was not changed +pushd a +assert_empty "mise run -q cwd_task" +popd + +# Should run again as input in this directory was changed +pushd b +assert "mise run -q cwd_task" "running b 2" +assert_empty "mise run -q cwd_task" +popd diff --git a/src/cli/tasks/info.rs b/src/cli/tasks/info.rs index 5051e31fad..1dd3b302ed 100644 --- a/src/cli/tasks/info.rs +++ b/src/cli/tasks/info.rs @@ -8,6 +8,7 @@ use crate::config::Config; use crate::file::display_path; use crate::task::Task; use crate::task::task_fetcher::TaskFetcher; +use crate::task::task_source_checker::task_cwd; use crate::ui::info; /// Get information about a task @@ -93,7 +94,8 @@ impl TasksInfo { if !task.sources.is_empty() { info::inline_section("Sources", task.sources.join(", "))?; } - let outputs = task.outputs.paths(task); + let root = task_cwd(task, config).await?; + let outputs = task.outputs.paths(task, &root); if !outputs.is_empty() { info::inline_section("Outputs", outputs.join(", "))?; } diff --git a/src/cli/tasks/validate.rs b/src/cli/tasks/validate.rs index 22f7529c57..faa73602a0 100644 --- a/src/cli/tasks/validate.rs +++ b/src/cli/tasks/validate.rs @@ -508,7 +508,7 @@ impl TasksValidate { let mut issues = Vec::new(); // Validate output patterns if they exist - let paths = task.outputs.paths(task); + let paths = task.outputs.patterns(); for path in paths { // Try to compile as glob pattern if let Err(e) = globset::GlobBuilder::new(&path).build() { diff --git a/src/task/task_source_checker.rs b/src/task/task_source_checker.rs index e6cd104106..5ca3edce39 100644 --- a/src/task/task_source_checker.rs +++ b/src/task/task_source_checker.rs @@ -142,11 +142,11 @@ pub async fn sources_are_fresh(task: &Task, config: &Arc) -> Result) -> Result) -> Result<()> { return Ok(()); } if task.outputs.is_auto() { - for p in task.outputs.paths(task) { + let root = task_cwd(task, config).await?; + for p in task.outputs.paths(task, &root) { debug!("touching auto output file: {p}"); file::touch_file(&PathBuf::from(&p))?; } @@ -194,7 +195,7 @@ pub async fn save_checksum(task: &Task, config: &Arc) -> Result<()> { // Check if explicitly defined outputs were generated // Use task_cwd to respect the task's dir setting, matching sources_are_fresh behavior let root = task_cwd(task, config).await?; - for output in task.outputs.paths(task) { + for output in task.outputs.paths(task, &root) { let output_exists = if is_glob_pattern(&output) { // For glob patterns, check if any files match let pattern = root.join(&output); @@ -223,10 +224,11 @@ pub async fn save_checksum(task: &Task, config: &Arc) -> Result<()> { } /// Get the path to store source hashes for a task -fn sources_hash_path(task: &Task, content_hash: bool) -> PathBuf { +fn sources_hash_path(task: &Task, root: &Path, content_hash: bool) -> PathBuf { let mut hasher = DefaultHasher::new(); task.hash(&mut hasher); task.config_source.hash(&mut hasher); + root.hash(&mut hasher); let hash = format!("{:x}", hasher.finish()); let suffix = if content_hash { "-content" } else { "" }; dirs::STATE @@ -235,8 +237,8 @@ fn sources_hash_path(task: &Task, content_hash: bool) -> PathBuf { } /// Get the existing source hash for a task, if it exists -fn source_existing_hash(task: &Task, content_hash: bool) -> Option { - let path = sources_hash_path(task, content_hash); +fn source_existing_hash(task: &Task, root: &Path, content_hash: bool) -> Option { + let path = sources_hash_path(task, root, content_hash); if path.exists() { Some(file::read_to_string(&path).unwrap_or_default()) } else { diff --git a/src/task/task_sources.rs b/src/task/task_sources.rs index 823d57cef6..17435472a7 100644 --- a/src/task/task_sources.rs +++ b/src/task/task_sources.rs @@ -3,6 +3,7 @@ use crate::task::Task; use serde::ser::{SerializeMap, SerializeSeq}; use serde::{Deserialize, Deserializer, Serialize}; use std::hash::{DefaultHasher, Hash, Hasher}; +use std::path::Path; #[derive(Debug, Clone, Eq, PartialEq, strum::EnumIs)] pub enum TaskOutputs { @@ -32,17 +33,25 @@ impl TaskOutputs { } } - pub fn paths(&self, task: &Task) -> Vec { + pub fn patterns(&self) -> Vec { match self { TaskOutputs::Files(files) => files.clone(), - TaskOutputs::Auto => vec![self.auto_path(task)], + TaskOutputs::Auto => vec![], } } - fn auto_path(&self, task: &Task) -> String { + pub fn paths(&self, task: &Task, root: &Path) -> Vec { + match self { + TaskOutputs::Files(files) => files.clone(), + TaskOutputs::Auto => vec![self.auto_path(task, root)], + } + } + + fn auto_path(&self, task: &Task, root: &Path) -> String { let mut hasher = DefaultHasher::new(); task.hash(&mut hasher); task.config_source.hash(&mut hasher); + root.hash(&mut hasher); let hash = format!("{:x}", hasher.finish()); dirs::STATE .join("task-auto-outputs")