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
40 changes: 40 additions & 0 deletions e2e/tasks/test_task_source_freshness_with_cwd
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env bash

cat <<EOF >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
4 changes: 3 additions & 1 deletion src/cli/tasks/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(", "))?;
}
Expand Down
2 changes: 1 addition & 1 deletion src/cli/tasks/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
18 changes: 10 additions & 8 deletions src/task/task_source_checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,11 @@ pub async fn sources_are_fresh(task: &Task, config: &Arc<Config>) -> Result<bool
} else {
file_metadatas_to_hash(&source_metadatas)
};
let source_hash_path = sources_hash_path(task, use_content_hash);
let source_hash_path = sources_hash_path(task, &root, use_content_hash);
if let Some(dir) = source_hash_path.parent() {
file::create_dir_all(dir)?;
}
if source_existing_hash(task, use_content_hash).is_some_and(|h| h != source_hash) {
if source_existing_hash(task, &root, use_content_hash).is_some_and(|h| h != source_hash) {
debug!(
"source {} hash mismatch in {}",
if use_content_hash {
Expand All @@ -160,7 +160,7 @@ pub async fn sources_are_fresh(task: &Task, config: &Arc<Config>) -> Result<bool
return Ok(false);
}
let sources = get_last_modified_from_metadatas(&source_metadatas);
let outputs = get_last_modified(&root, &task.outputs.paths(task))?;
let outputs = get_last_modified(&root, &task.outputs.paths(task, &root))?;
file::write(&source_hash_path, &source_hash)?;
trace!("sources: {sources:?}, outputs: {outputs:?}");
match (sources, outputs) {
Expand All @@ -186,15 +186,16 @@ pub async fn save_checksum(task: &Task, config: &Arc<Config>) -> 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))?;
}
} else {
// 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);
Expand Down Expand Up @@ -223,10 +224,11 @@ pub async fn save_checksum(task: &Task, config: &Arc<Config>) -> 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
Expand All @@ -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<String> {
let path = sources_hash_path(task, content_hash);
fn source_existing_hash(task: &Task, root: &Path, content_hash: bool) -> Option<String> {
let path = sources_hash_path(task, root, content_hash);
if path.exists() {
Some(file::read_to_string(&path).unwrap_or_default())
} else {
Expand Down
15 changes: 12 additions & 3 deletions src/task/task_sources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -32,17 +33,25 @@ impl TaskOutputs {
}
}

pub fn paths(&self, task: &Task) -> Vec<String> {
pub fn patterns(&self) -> Vec<String> {
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<String> {
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")
Expand Down
Loading