Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions docs/tasks/task-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,9 @@ includes = [
]
```

Entries support Tera templates from the config-file context, such as <span v-pre>`{{ config_root }}`</span> or
<span v-pre>`{{ env.HOME }}`</span>. Local paths also support `~/` home expansion.

When `task_config.includes` is set, it replaces the default file-task directories for that config scope instead of adding to them.

The default file-task directories are:
Expand Down
37 changes: 37 additions & 0 deletions e2e/tasks/test_task_config_includes_templates
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env bash

# Test Tera rendering and home expansion for task_config.includes.

mkdir -p root-tasks "$HOME/home-tasks" "$HOME/env-home-tasks"

cat >root-tasks/task.toml <<'EOF'
[root_include]
run = 'echo "root include"'
EOF

cat >"$HOME/home-tasks/task.toml" <<'EOF'
[tilde_include]
run = 'echo "tilde include"'
EOF

cat >"$HOME/env-home-tasks/task.toml" <<'EOF'
[env_home_include]
run = 'echo "env home include"'
EOF

cat >mise.toml <<'EOF'
[task_config]
includes = [
"{{ config_root }}/root-tasks/*.toml",
"~/home-tasks/*.toml",
"{{ env.HOME }}/env-home-tasks/*.toml",
]
EOF

assert_contains "mise tasks" "root_include"
assert_contains "mise tasks" "tilde_include"
assert_contains "mise tasks" "env_home_include"

assert "mise run root_include" "root include"
assert "mise run tilde_include" "tilde include"
assert "mise run env_home_include" "env home include"
4 changes: 2 additions & 2 deletions schema/mise.json
Original file line number Diff line number Diff line change
Expand Up @@ -1935,9 +1935,9 @@
"type": "string"
},
"includes": {
"description": "files/directories to include searching for tasks. Can be local paths or git repository URLs using git:: prefix (e.g., git::https://github.com/org/repo.git//path?ref=branch)",
"description": "files/directories to include searching for tasks. Supports Tera templates, local paths with ~/ home expansion, and git repository URLs using git:: prefix (e.g., git::https://github.com/org/repo.git//path?ref=branch)",
"items": {
"description": "file/directory root to include in task execution. Supports local paths and git URLs (git::https://github.com/org/repo.git//path?ref=branch)",
"description": "file/directory root to include in task execution. Supports Tera templates, local paths with ~/ home expansion, and git URLs (git::https://github.com/org/repo.git//path?ref=branch)",
"type": "string"
},
"type": "array"
Expand Down
2 changes: 1 addition & 1 deletion src/cli/tasks/ls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ impl TasksLs {
&& !cfg!(windows)
&& let Some(cwd) = &*dirs::CWD
{
let includes = config::task_includes_for_dir(cwd, &config.config_files);
let includes = config::task_includes_for_dir(cwd, &config.config_files)?;
if !find_non_executable_task_files(&includes).is_empty() {
warn!(
"no tasks found, but non-executable files exist in task directories.\nFiles must be executable to be detected as tasks. Run `chmod +x` on the task files to fix this."
Expand Down
13 changes: 13 additions & 0 deletions src/config/config_file/mise_toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,19 @@ impl ConfigFile for MiseToml {
&self.task_config
}

fn task_config_includes(&self) -> eyre::Result<Option<Vec<String>>> {
self.task_config
.includes
.as_ref()
.map(|includes| {
includes
.iter()
.map(|include| self.parse_template(include))
.collect()
})
.transpose()
}

fn experimental_monorepo_root(&self) -> Option<bool> {
self.experimental_monorepo_root
}
Expand Down
4 changes: 4 additions & 0 deletions src/config/config_file/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ pub trait ConfigFile: Debug + Send + Sync {
&DEFAULT_TASK_CONFIG
}

fn task_config_includes(&self) -> eyre::Result<Option<Vec<String>>> {
Ok(self.task_config().includes.clone())
}

fn task_templates(&self) -> IndexMap<String, TaskTemplate> {
IndexMap::new()
}
Expand Down
39 changes: 21 additions & 18 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1928,7 +1928,7 @@ async fn load_local_tasks_with_context(

// If no config file exists, still load default task include dirs
if !found_config {
let includes = task_includes_for_dir(&subdir, &config.config_files);
let includes = task_includes_for_dir(&subdir, &config.config_files)?;
for include in includes {
let mut subdir_tasks =
load_tasks_includes(&config, &include, &subdir, &None).await?;
Expand Down Expand Up @@ -2376,8 +2376,10 @@ fn is_glob_pattern(pattern: &str) -> bool {

/// Expand a task include pattern (which may be a glob) to a list of paths
fn expand_task_include(dir: &Path, pattern: &str) -> Vec<PathBuf> {
if is_glob_pattern(pattern) {
match glob(dir, pattern) {
let pattern = file::replace_path(pattern);
let pattern = pattern.to_string_lossy();
if is_glob_pattern(&pattern) {
match glob(dir, &pattern) {
Ok(paths) => paths,
Err(err) => {
warn!(
Expand All @@ -2391,7 +2393,7 @@ fn expand_task_include(dir: &Path, pattern: &str) -> Vec<PathBuf> {
}
} else {
// Literal path
let path = PathBuf::from(pattern);
let path = PathBuf::from(pattern.as_ref());
let resolved = if path.is_absolute() {
path
} else {
Expand All @@ -2411,9 +2413,7 @@ async fn load_file_tasks(
config_root: &Path,
) -> Result<Vec<Task>> {
let includes = cf
.task_config()
.includes
.clone()
.task_config_includes()?
.unwrap_or_else(default_task_includes);

let mut tasks = vec![];
Expand All @@ -2439,25 +2439,28 @@ async fn load_file_tasks(
Ok(tasks)
}

pub fn task_includes_for_dir(dir: &Path, config_files: &ConfigMap) -> Vec<PathBuf> {
pub fn task_includes_for_dir(dir: &Path, config_files: &ConfigMap) -> Result<Vec<PathBuf>> {
let configs = configs_at_root(dir, config_files);

// Find the highest-precedence config that has explicit task_config.includes
// and resolve paths relative to that config file's directory
let (includes, resolve_dir) = configs
.iter()
.find_map(|cf| {
cf.task_config().includes.clone().map(|includes| {
.find_map(|cf| match cf.task_config_includes() {
Ok(Some(includes)) => Some(Ok({
// Resolve relative paths from the config root, not the config file's directory
(includes, cf.config_root())
})
})),
Ok(None) => None,
Err(err) => Some(Err(err)),
})
.transpose()?
.unwrap_or_else(|| {
// Default includes should be resolved relative to the search directory
(default_task_includes(), dir.to_path_buf())
});

includes
Ok(includes
.into_iter()
.flat_map(|p| {
// Git URLs are handled by load_file_tasks, not here
Expand All @@ -2467,7 +2470,7 @@ pub fn task_includes_for_dir(dir: &Path, config_files: &ConfigMap) -> Vec<PathBu
expand_task_include(&resolve_dir, &p)
})
.unique()
.collect::<Vec<_>>()
.collect::<Vec<_>>())
}

pub async fn load_tasks_in_dir(
Expand All @@ -2480,12 +2483,12 @@ pub async fn load_tasks_in_dir(

let (includes, resolve_dir) = configs
.iter()
.find_map(|cf| {
cf.task_config()
.includes
.clone()
.map(|includes| (includes, cf.config_root()))
.find_map(|cf| match cf.task_config_includes() {
Ok(Some(includes)) => Some(Ok((includes, cf.config_root()))),
Ok(None) => None,
Err(err) => Some(Err(err)),
})
.transpose()?
.unwrap_or_else(|| (default_task_includes(), dir.to_path_buf()));

let mut config_tasks = vec![];
Expand Down
4 changes: 3 additions & 1 deletion src/task/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,9 @@ impl Task {
let config = Config::get().await.unwrap();
let cwd = dirs::CWD.clone().unwrap_or_default();
let project_root = config.project_root.clone().unwrap_or(cwd);
for dir in config::task_includes_for_dir(&project_root, &config.config_files) {
for dir in
config::task_includes_for_dir(&project_root, &config.config_files).unwrap_or_default()
{
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Outdated
if dir.is_dir() && project_root.join(&dir).exists() {
return project_root.join(dir);
}
Expand Down
4 changes: 2 additions & 2 deletions src/task/task_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ async fn err_no_task(config: &Config, name: &str) -> Result<()> {
if !cfg!(windows)
&& let Some(cwd) = &*dirs::CWD
{
let includes = config::task_includes_for_dir(cwd, &config.config_files);
let includes = config::task_includes_for_dir(cwd, &config.config_files)?;
let non_exec_files = find_non_executable_task_files(&includes);
if !non_exec_files.is_empty() {
let dirs_with_files: Vec<String> = includes
Expand Down Expand Up @@ -271,7 +271,7 @@ async fn err_no_task(config: &Config, name: &str) -> Result<()> {
);
}
if let Some(cwd) = &*dirs::CWD {
let includes = config::task_includes_for_dir(cwd, &config.config_files);
let includes = config::task_includes_for_dir(cwd, &config.config_files)?;
let path = includes
.iter()
.map(|d| d.join(name))
Expand Down
Loading