Skip to content

Commit

Permalink
new: Allow task configs to be nested in folders. (#1067)
Browse files Browse the repository at this point in the history
* Update docs.

* Update impl.

* Add tests.

* Fix tests.

* Fix windows.

* Fix windows.
  • Loading branch information
milesj authored Sep 22, 2023
1 parent 837e56c commit fb885be
Show file tree
Hide file tree
Showing 18 changed files with 171 additions and 120 deletions.
2 changes: 1 addition & 1 deletion crates/cli/src/commands/docker/scaffold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ fn scaffold_workspace(
// Copy moon configuration
let moon_configs = glob::walk(
workspace.root.join(CONFIG_DIRNAME),
["*.yml", "tasks/*.yml"],
["*.yml", "tasks/**/*.yml"],
)?;
let moon_configs = moon_configs
.iter()
Expand Down
2 changes: 1 addition & 1 deletion nextgen/app/src/systems/startup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ pub fn load_tasks_config(workspace_root: StateRef<WorkspaceRoot>, resources: Res
file = ?config_path,
"Attempting to load {} and {} (optional)",
color::file(&config_name),
color::file(format!("{}/tasks/*.yml", consts::CONFIG_DIRNAME)),
color::file(format!("{}/tasks/**/*.yml", consts::CONFIG_DIRNAME)),
);

let manager = InheritedTasksManager::load_from(workspace_root)?;
Expand Down
115 changes: 66 additions & 49 deletions nextgen/config/src/inherited_tasks_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ use crate::project::{validate_deps, TaskConfig};
use crate::project_config::{validate_tasks, ProjectType};
use crate::shapes::InputPath;
use crate::validate::check_yml_extension;
use moon_common::cacheable;
use moon_common::{consts, Id};
use moon_common::path::standardize_separators;
use moon_common::{cacheable, consts, Id};
use moon_target::Target;
use once_map::OnceMap;
use rustc_hash::FxHashMap;
use schematic::{merge, validate, Config, ConfigError, ConfigLoader, PartialConfig};
use std::fs;
use std::hash::Hash;
use std::path::PathBuf;
use std::{collections::BTreeMap, path::Path};

pub fn merge_fxhashmap<K, V, C>(
Expand Down Expand Up @@ -88,10 +89,16 @@ cacheable!(
}
);

#[derive(Debug, Default)]
pub struct InheritedTasksEntry {
pub input: PathBuf,
pub config: PartialInheritedTasksConfig,
}

#[derive(Debug, Default)]
pub struct InheritedTasksManager {
cache: OnceMap<String, InheritedTasksResult>,
pub configs: FxHashMap<String, PartialInheritedTasksConfig>,
pub configs: FxHashMap<String, InheritedTasksEntry>,
}

impl InheritedTasksManager {
Expand All @@ -108,40 +115,17 @@ impl InheritedTasksManager {

if tasks_file.exists() {
manager.add_config(
workspace_root,
&tasks_file,
InheritedTasksConfig::load_partial(workspace_root, &tasks_file)?,
);
}

// tasks/*.yml
// tasks/**/*.yml
let tasks_dir = moon_dir.join("tasks");

if !tasks_dir.exists() {
return Ok(manager);
}

for file in fs::read_dir(&tasks_dir)
.map_err(|error| ConfigError::ReadFileFailed {
path: tasks_dir,
error,
})?
.flatten()
{
let path = file.path();

if file
.file_type()
.map_err(|error| ConfigError::ReadFileFailed {
path: path.to_path_buf(),
error,
})?
.is_file()
{
manager.add_config(
&path,
InheritedTasksConfig::load_partial(workspace_root, &path)?,
);
}
if tasks_dir.exists() {
load_dir(&mut manager, workspace_root, &tasks_dir)?;
}

Ok(manager)
Expand All @@ -153,26 +137,33 @@ impl InheritedTasksManager {
Self::load(workspace_root, workspace_root.join(consts::CONFIG_DIRNAME))
}

pub fn add_config(&mut self, path: &Path, config: PartialInheritedTasksConfig) {
pub fn add_config(
&mut self,
workspace_root: &Path,
path: &Path,
config: PartialInheritedTasksConfig,
) {
let name = path
.file_name()
.unwrap_or_default()
.to_str()
.unwrap_or_default();

if !name.ends_with(".yml") {
return;
}

let name = if name == consts::CONFIG_TASKS_FILENAME {
"*"
} else if let Some(stripped_name) = name.strip_suffix(".yml") {
stripped_name
} else {
name
return;
};

self.configs.insert(name.to_owned(), config);
self.configs.insert(
name.to_owned(),
InheritedTasksEntry {
input: path.strip_prefix(workspace_root).unwrap().to_path_buf(),
config,
},
);
}

pub fn get_lookup_order(
Expand Down Expand Up @@ -223,18 +214,10 @@ impl InheritedTasksManager {
let context = ();

for lookup in &lookup_order {
if let Some(managed_config) = self.configs.get(lookup) {
let mut managed_config = managed_config.clone();

let source_path = if lookup == "*" {
format!(
"{}/{}",
consts::CONFIG_DIRNAME,
consts::CONFIG_TASKS_FILENAME
)
} else {
format!("{}/tasks/{lookup}.yml", consts::CONFIG_DIRNAME)
};
if let Some(config_entry) = self.configs.get(lookup) {
let source_path =
standardize_separators(format!("{}", config_entry.input.display()));
let mut managed_config = config_entry.config.clone();

// Only modify tasks for `tasks/*.yml` files instead of `tasks.yml`,
// as the latter will be globbed alongside toolchain/workspace configs.
Expand Down Expand Up @@ -284,3 +267,37 @@ impl InheritedTasksManager {
})
}
}

fn load_dir(
manager: &mut InheritedTasksManager,
workspace_root: &Path,
dir: &Path,
) -> miette::Result<()> {
for entry in fs::read_dir(dir)
.map_err(|error| ConfigError::ReadFileFailed {
path: dir.to_path_buf(),
error,
})?
.flatten()
{
let path = entry.path();
let file_type = entry
.file_type()
.map_err(|error| ConfigError::ReadFileFailed {
path: path.to_path_buf(),
error,
})?;

if file_type.is_file() {
manager.add_config(
workspace_root,
&path,
InheritedTasksConfig::load_partial(workspace_root, &path)?,
);
} else if file_type.is_dir() {
load_dir(manager, workspace_root, &path)?;
}
}

Ok(())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
tasks:
command:
command: 'global'
inputs: ['a']
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
tasks:
command:
command: 'dotnet-application'
inputs: ['c']
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
tasks:
command:
command: 'node'
inputs: ['b']
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
tasks:
command:
command: 'node-library'
inputs: ['c']
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
tasks:
command:
command: 'dotnet'
inputs: ['b']
67 changes: 47 additions & 20 deletions nextgen/config/tests/inherited_tasks_config_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use starbase_sandbox::{create_empty_sandbox, create_sandbox};
use std::collections::BTreeMap;
use utils::*;

const FILENAME: &str = ".moon/tasks.yml";
const FILENAME: &str = "tasks.yml";

mod tasks_config {
use super::*;
Expand Down Expand Up @@ -393,9 +393,8 @@ mod task_manager {
let mut global_inputs = vec![];

if command != "global" {
global_inputs.push(InputPath::WorkspaceFile(format!(
".moon/tasks/{command}.yml"
)));
// No .moon prefix since the fixture is contrived
global_inputs.push(InputPath::WorkspaceFile(format!("tasks/{command}.yml")));
}

TaskConfig {
Expand Down Expand Up @@ -436,6 +435,38 @@ mod task_manager {
);
}

#[test]
fn can_nest_configs_in_folders() {
let sandbox = create_sandbox("inheritance/nested");
let manager = InheritedTasksManager::load(sandbox.path(), sandbox.path()).unwrap();

let mut keys = manager.configs.keys().collect::<Vec<_>>();
keys.sort();

assert_eq!(
keys,
vec!["*", "dotnet", "dotnet-application", "node", "node-library",]
);

let mut inputs = manager
.configs
.values()
.map(|c| c.input.to_string_lossy().replace('\\', "/"))
.collect::<Vec<_>>();
inputs.sort();

assert_eq!(
inputs,
vec![
"tasks.yml",
"tasks/dotnet/dotnet-application.yml",
"tasks/dotnet/dotnet.yml",
"tasks/node/node-library.yml",
"tasks/node/node.yml"
]
);
}

mod lookup_order {
use super::*;

Expand Down Expand Up @@ -583,10 +614,10 @@ mod task_manager {
assert_eq!(
config.layers.keys().collect::<Vec<_>>(),
vec![
".moon/tasks.yml",
".moon/tasks/javascript.yml",
".moon/tasks/node-application.yml",
".moon/tasks/node.yml",
"tasks.yml",
"tasks/javascript.yml",
"tasks/node-application.yml",
"tasks/node.yml",
]
);
}
Expand Down Expand Up @@ -619,11 +650,7 @@ mod task_manager {

assert_eq!(
config.layers.keys().collect::<Vec<_>>(),
vec![
".moon/tasks.yml",
".moon/tasks/node.yml",
".moon/tasks/typescript.yml",
]
vec!["tasks.yml", "tasks/node.yml", "tasks/typescript.yml",]
);
}

Expand Down Expand Up @@ -651,7 +678,7 @@ mod task_manager {

assert_eq!(
config.layers.keys().collect::<Vec<_>>(),
vec![".moon/tasks.yml", ".moon/tasks/rust.yml",]
vec!["tasks.yml", "tasks/rust.yml",]
);
}

Expand Down Expand Up @@ -688,11 +715,11 @@ mod task_manager {
assert_eq!(
config.layers.keys().collect::<Vec<_>>(),
vec![
".moon/tasks.yml",
".moon/tasks/node.yml",
".moon/tasks/tag-kebab-case.yml",
".moon/tasks/tag-normal.yml",
".moon/tasks/typescript.yml",
"tasks.yml",
"tasks/node.yml",
"tasks/tag-kebab-case.yml",
"tasks/tag-normal.yml",
"tasks/typescript.yml",
]
);
}
Expand Down Expand Up @@ -721,7 +748,7 @@ mod task_manager {

assert_eq!(
config.layers.keys().collect::<Vec<_>>(),
vec![".moon/tasks.yml", ".moon/tasks/kotlin.yml",]
vec!["tasks.yml", "tasks/kotlin.yml",]
);
}
}
Expand Down
2 changes: 1 addition & 1 deletion nextgen/project-graph/src/project_graph_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ impl<'app> ProjectGraphBuilder<'app> {
// Hash all workspace-level config files
for file in glob::walk(
context.workspace_root.join(consts::CONFIG_DIRNAME),
["*.yml", "tasks/*.yml"],
["*.yml", "tasks/**/*.yml"],
)? {
configs.push(to_virtual_string(
file.strip_prefix(context.workspace_root).unwrap(),
Expand Down
21 changes: 12 additions & 9 deletions nextgen/project-graph/tests/project_graph_test.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use moon_common::{path::WorkspaceRelativePathBuf, Id};
use moon_config::PartialTaskConfig;
use moon_config::{
DependencyConfig, DependencyScope, DependencySource, InheritedTasksManager, NodeConfig,
PartialInheritedTasksConfig, ToolchainConfig, WorkspaceConfig, WorkspaceProjects,
WorkspaceProjectsConfig,
DependencyConfig, DependencyScope, DependencySource, InheritedTasksEntry,
InheritedTasksManager, NodeConfig, PartialInheritedTasksConfig, ToolchainConfig,
WorkspaceConfig, WorkspaceProjects, WorkspaceProjectsConfig,
};
use moon_project::{FileGroup, Project};
use moon_project_builder::DetectLanguageEvent;
Expand Down Expand Up @@ -46,12 +46,15 @@ impl GraphContainer {
// Add a global task to all projects
graph.inherited_tasks.configs.insert(
"*".into(),
PartialInheritedTasksConfig {
tasks: Some(BTreeMap::from_iter([(
"global".into(),
PartialTaskConfig::default(),
)])),
..PartialInheritedTasksConfig::default()
InheritedTasksEntry {
input: ".moon/tasks.yml".into(),
config: PartialInheritedTasksConfig {
tasks: Some(BTreeMap::from_iter([(
"global".into(),
PartialTaskConfig::default(),
)])),
..PartialInheritedTasksConfig::default()
},
},
);

Expand Down
Loading

0 comments on commit fb885be

Please sign in to comment.