Skip to content

Commit 72980a3

Browse files
committed
Add config.axl support
1 parent cabb509 commit 72980a3

File tree

7 files changed

+295
-119
lines changed

7 files changed

+295
-119
lines changed

.aspect/config.axl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def config(ctx):
2+
pass
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def config(ctx):
2+
pass

crates/aspect-cli/src/cmd_tree.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{collections::HashMap, path::PathBuf};
1+
use std::collections::HashMap;
22

33
use axl_runtime::engine::task::{TaskLike, MAX_TASK_GROUPS};
44
use clap::{Arg, ArgMatches, Command};
@@ -25,18 +25,18 @@ pub enum TreeError {
2525
#[error(
2626
"task {0:?} in group {1:?} (defined in {2:?}) conflicts with a previously defined group"
2727
)]
28-
TaskGroupConflict(String, Vec<String>, PathBuf),
28+
TaskGroupConflict(String, Vec<String>, String),
2929

3030
#[error("group {0:?} from task {1:?} in group {2:?} (defined in {3:?}) conflicts with a previously defined task")]
31-
GroupConflictTask(String, String, Vec<String>, PathBuf),
31+
GroupConflictTask(String, String, Vec<String>, String),
3232

3333
#[error(
3434
"task {0:?} in group {1:?} (defined in {2:?}) conflicts with a previously defined task"
3535
)]
36-
TaskConflict(String, Vec<String>, PathBuf),
36+
TaskConflict(String, Vec<String>, String),
3737

3838
#[error("task {0:?} (defined in {1:?}) cannot have more than {2:?} group levels")]
39-
TooManyGroups(String, PathBuf, usize),
39+
TooManyGroups(String, String, usize),
4040

4141
#[error("task {0:?} in group {1:?} conflicts with a previously defined command")]
4242
TaskCommandConflict(String, Vec<String>),
@@ -51,7 +51,7 @@ impl CommandTree {
5151
name: &str,
5252
group: &[String],
5353
subgroup: &[String],
54-
path: &PathBuf,
54+
path: &String,
5555
cmd: Command,
5656
) -> Result<(), TreeError> {
5757
if group.len() > MAX_TASK_GROUPS {
@@ -151,7 +151,7 @@ impl CommandTree {
151151
pub fn make_command_from_task(
152152
name: &String,
153153
defined_in: &str,
154-
path: &PathBuf,
154+
path: &String,
155155
symbol: &String,
156156
task: &dyn TaskLike<'_>,
157157
) -> Command {
@@ -176,7 +176,7 @@ pub fn make_command_from_task(
176176
.hide_short_help(true)
177177
.hide_possible_values(true)
178178
.hide_long_help(true)
179-
.default_value(path.as_os_str().to_string_lossy().to_string()),
179+
.default_value(path),
180180
)
181181
.arg(
182182
Arg::new(TASK_COMMAND_SYMBOL_ID)

crates/aspect-cli/src/main.rs

Lines changed: 129 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ use std::path::PathBuf;
99
use std::process::ExitCode;
1010

1111
use aspect_config::cli_version;
12-
use axl_runtime::engine::task::{AsTaskLike, FrozenTask, Task};
12+
use axl_runtime::engine::task::{FrozenTask, Task};
1313
use axl_runtime::engine::task_arg::TaskArg;
1414
use axl_runtime::engine::task_args::TaskArgs;
1515
use axl_runtime::eval::{AxlScriptEvaluator, EvaluatedAxlScript};
16-
use axl_runtime::module::AXL_ROOT_MODULE_NAME;
16+
use axl_runtime::helpers::normalize_abs_path_lexically;
1717
use axl_runtime::module::{AxlModuleEvaluator, DiskStore};
18+
use axl_runtime::module::{AXL_MODULE_FILE, AXL_ROOT_MODULE_NAME};
1819
use clap::{Arg, ArgAction, Command};
1920
use miette::{miette, IntoDiagnostic};
2021
use starlark::values::ValueLike;
@@ -64,7 +65,7 @@ async fn main() -> miette::Result<ExitCode> {
6465
.evaluate(AXL_ROOT_MODULE_NAME.to_string(), repo_root.clone())
6566
.into_diagnostic()?;
6667

67-
// Expand all modules (including the builtin @aspect module) to the disk store and return the module roots on disk.
68+
// Expand all module deps (including the builtin @aspect module) to the disk store and return the module roots on disk.
6869
// This results in a Vec of (String, PathBuf) such as
6970
// [
7071
// ( "aspect", "/Users/username/Library/Caches/axl/deps/27e6d838c365a7c5d79674a7b6c7ec7b8d22f686dbcc8088a8d1454a6489a9ae/aspect" ),
@@ -76,24 +77,23 @@ async fn main() -> miette::Result<ExitCode> {
7677
.await
7778
.into_diagnostic()?;
7879

79-
// Gather all tasks from use_task calls in the repository root axl module
80-
let mut use_tasks = vec![(
80+
// Gather evaluated root and deps modules into modules vec
81+
let mut modules = vec![(
8182
module_store.module_name,
8283
module_store.module_root,
8384
module_store.tasks.take(),
8485
)];
8586

86-
// Gather all tasks from use_task calls in the axl module deps
8787
for (name, root) in module_roots {
8888
let module_store = module_eval.evaluate(name, root).into_diagnostic()?;
89-
use_tasks.push((
89+
modules.push((
9090
module_store.module_name,
9191
module_store.module_root,
9292
module_store.tasks.take(),
9393
))
9494
}
9595

96-
let deps_path = disk_store.deps_path();
96+
let axl_deps_root = disk_store.deps_path();
9797

9898
// Get the default search paths given the current working directory and the repository root
9999
let search_paths = get_default_axl_search_paths(&current_work_dir, &repo_root);
@@ -109,6 +109,110 @@ async fn main() -> miette::Result<ExitCode> {
109109
let out = spawn_blocking(move || {
110110
let _enter = espan.enter();
111111

112+
// Evaluate all scripts to find tasks and configs. The order of task discovery will be load bearing in the future
113+
// when task overloading is supported
114+
// 1. repository axl_sources
115+
// 2. use_task in the root module
116+
// 3. auto_use_tasks from the @aspect built-in module (if not overloaded by an dep in the root MODULE.aspect)
117+
// 4. auto_use_tasks from axl module deps in the root MODULE.aspect
118+
let mut scripts: HashMap<String, EvaluatedAxlScript> = HashMap::new();
119+
let mut tasks: Vec<(String, String)> = Vec::new();
120+
let mut configs: Vec<String> = Vec::new();
121+
let script_eval = AxlScriptEvaluator::new(
122+
AXL_ROOT_MODULE_NAME.to_string(),
123+
repo_root.clone(),
124+
axl_deps_root.clone(),
125+
);
126+
for path in axl_sources.iter() {
127+
let rel_path = path
128+
.strip_prefix(&repo_root)
129+
.map(|p| p.to_path_buf())
130+
.into_diagnostic()?;
131+
let mut has_config = false;
132+
let mut has_tasks = false;
133+
if path.ends_with(".aspect/config.axl") {
134+
configs.push(path.as_os_str().to_string_lossy().to_string());
135+
has_config = true;
136+
}
137+
let script = script_eval.eval(&rel_path).into_diagnostic()?;
138+
for symbol in script.module.names() {
139+
if let Some(task_val) = script.module.get(symbol.as_str()) {
140+
if task_val.downcast_ref::<Task>().is_none()
141+
&& task_val.downcast_ref::<FrozenTask>().is_none()
142+
{
143+
continue;
144+
}
145+
tasks.push((
146+
symbol.as_str().to_string(),
147+
path.as_os_str().to_string_lossy().to_string(),
148+
));
149+
has_tasks = true;
150+
}
151+
}
152+
if has_config || has_tasks {
153+
scripts
154+
.entry(path.as_os_str().to_string_lossy().to_string())
155+
.or_insert(script);
156+
}
157+
}
158+
for (module_name, module_root, use_tasks) in modules.iter() {
159+
let script_eval = AxlScriptEvaluator::new(
160+
module_name.clone(),
161+
module_root.clone(),
162+
axl_deps_root.clone(),
163+
);
164+
for (rel_path, symbol) in use_tasks {
165+
let script = script_eval
166+
.eval(&PathBuf::from(&rel_path))
167+
.into_diagnostic()?;
168+
let path =
169+
normalize_abs_path_lexically(&module_root.join(&rel_path)).expect("TODO");
170+
if let Some(task_val) = script.module.get(symbol.as_str()) {
171+
if task_val.downcast_ref::<Task>().is_none()
172+
&& task_val.downcast_ref::<FrozenTask>().is_none()
173+
{
174+
return Err(miette!(
175+
"invalid use_task({:?}, {:?}) call in @{} module at {}/{}",
176+
rel_path,
177+
symbol,
178+
module_name,
179+
module_root.display(),
180+
AXL_MODULE_FILE
181+
));
182+
};
183+
tasks.push((
184+
symbol.clone(),
185+
path.as_os_str().to_string_lossy().to_string(),
186+
));
187+
} else {
188+
return Err(miette!(
189+
"task symbol {:?} not found in @{} module use_task({:?}, {:?}) at {}/{}",
190+
symbol,
191+
module_name,
192+
rel_path,
193+
symbol,
194+
module_root.display(),
195+
AXL_MODULE_FILE
196+
));
197+
}
198+
scripts
199+
.entry(path.as_os_str().to_string_lossy().to_string())
200+
.or_insert(script);
201+
}
202+
}
203+
204+
// Call config.axl config() functions
205+
// TODO: pass tasks to the config function and allow them to be mutated
206+
for path in configs.iter() {
207+
let script = scripts
208+
.get(path)
209+
.expect(&format!("expected to find {:?} script", path));
210+
script.execute_config("config").into_diagnostic()?;
211+
}
212+
213+
// Iterate through tasks after any config mutations and create the command with make_command_from_task
214+
let mut tree = CommandTree::default();
215+
112216
// TODO: add .about()
113217
let cmd = Command::new("aspect")
114218
// set binary name to "aspect" in help
@@ -134,97 +238,23 @@ async fn main() -> miette::Result<ExitCode> {
134238
.display_order(BUILTIN_COMMAND_DISPLAY_ORDER),
135239
);
136240

137-
// Collect tasks into tree
138-
let mut tree = CommandTree::default();
139-
let mut tasks: HashMap<String, EvaluatedAxlScript> = HashMap::new();
140-
141-
// First gather tasks from use_task calls in axl modules
142-
for (module_name, module_root, use_tasks) in use_tasks {
143-
let te = AxlScriptEvaluator::new(module_root.clone(), deps_path.clone());
144-
145-
for (relative_path, symbol) in use_tasks {
146-
let path = module_root.join(&relative_path);
147-
let script = te.eval(&PathBuf::from(&relative_path)).into_diagnostic()?;
148-
if let Some(task_val) = script.module.get(symbol.as_str()) {
149-
let def = if let Some(task) = task_val.downcast_ref::<Task>() {
150-
task.as_task()
151-
} else if let Some(task) = task_val.downcast_ref::<FrozenTask>() {
152-
task.as_task()
153-
} else {
154-
return Err(miette!(
155-
"invalid use_task({}, {}) call in {} at {:?}",
156-
relative_path,
157-
symbol,
158-
module_name,
159-
module_root
160-
));
161-
};
162-
163-
let name = if def.name().is_empty() {
164-
symbol.clone()
165-
} else {
166-
def.name().clone()
167-
};
168-
let rel_path = &path
169-
.strip_prefix(&module_root)
170-
.expect("failed make path relative")
171-
.as_os_str()
172-
.to_str()
173-
.expect("failed to encode path");
174-
let group = def.group();
175-
let defined_in = format!("@{}/{}", module_name, rel_path);
176-
let cmd = make_command_from_task(&name, &defined_in, &path, &symbol, def);
177-
tree.insert(&name, &group, &group, &path, cmd)
178-
.into_diagnostic()?;
179-
tasks.insert(path.to_str().unwrap().to_string(), script);
180-
}
181-
}
182-
}
183-
184-
// Next gather tasks from axl sources in the repository
185-
let te = AxlScriptEvaluator::new(repo_root.clone(), deps_path.clone());
186-
for path in axl_sources.iter() {
187-
let rel_path = path
188-
.strip_prefix(&repo_root)
189-
.map(|p| p.to_path_buf())
241+
for task in tasks.iter() {
242+
let symbol = &task.0;
243+
let path = &task.1;
244+
let script = scripts
245+
.get(path)
246+
.expect(&format!("expected to find {:?} script", path));
247+
let def = script.task_definition(symbol).into_diagnostic()?;
248+
let name = if def.name().is_empty() {
249+
symbol
250+
} else {
251+
def.name()
252+
};
253+
let group = def.group();
254+
let defined_in = format!("@{}/{}", script.module_name, script.module_subpath);
255+
let cmd = make_command_from_task(name, &defined_in, path, symbol, def);
256+
tree.insert(name, group, group, path, cmd)
190257
.into_diagnostic()?;
191-
192-
let script = te.eval(&rel_path).into_diagnostic()?;
193-
194-
'inner: for symbol in script.module.names() {
195-
if let Some(task_val) = script.module.get(symbol.as_str()) {
196-
let def = if let Some(task) = task_val.downcast_ref::<Task>() {
197-
task.as_task()
198-
} else if let Some(task) = task_val.downcast_ref::<FrozenTask>() {
199-
task.as_task()
200-
} else {
201-
continue 'inner;
202-
};
203-
204-
let name = if def.name().is_empty() {
205-
symbol.as_str().to_string()
206-
} else {
207-
def.name().clone()
208-
};
209-
let group = def.group();
210-
let defined_in = path
211-
.strip_prefix(&repo_root)
212-
.expect("failed make path relative")
213-
.as_os_str()
214-
.to_str()
215-
.expect("failed to encode path");
216-
let cmd = make_command_from_task(
217-
&name,
218-
defined_in,
219-
path,
220-
&symbol.as_str().to_string(),
221-
def,
222-
);
223-
tree.insert(&name, &group, &group, &path, cmd)
224-
.into_diagnostic()?;
225-
}
226-
}
227-
tasks.insert(path.to_str().unwrap().to_string(), script);
228258
}
229259

230260
// Turn the command tree into a command with subcommands.
@@ -256,7 +286,7 @@ async fn main() -> miette::Result<ExitCode> {
256286
let (name, cmdargs) = cmd;
257287
let task_path = tree.get_task_path(&cmdargs);
258288
let task_symbol = tree.get_task_symbol(&cmdargs);
259-
let task_script = tasks.get(&task_path).unwrap();
289+
let task_script = scripts.get(&task_path).unwrap();
260290
let def = task_script
261291
.task_definition(&task_symbol)
262292
.into_diagnostic()?;

0 commit comments

Comments
 (0)