Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: use project_root for task execution #2942

Merged
merged 1 commit into from
Nov 5, 2024
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
50 changes: 32 additions & 18 deletions src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ impl Run {
trace!("running task: {task}");
if let Err(err) = self.run_task(&env, &task) {
let prefix = task.estyled_prefix();
eprintln!("{prefix} {} {err}", style::ered("ERROR"),);
eprintln!("{prefix} {} {err:?}", style::ered("ERROR"),);
let _ = tx_err.send((task.clone(), Error::get_exit_status(&err)));
}
}
Expand Down Expand Up @@ -269,7 +269,7 @@ impl Run {

fn run_task(&self, env: &BTreeMap<String, String>, task: &Task) -> Result<()> {
let prefix = task.estyled_prefix();
if !self.force && self.sources_are_fresh(task) {
if !self.force && self.sources_are_fresh(task)? {
eprintln!("{prefix} sources up-to-date, skipping");
return Ok(());
}
Expand Down Expand Up @@ -322,7 +322,11 @@ impl Run {
prefix: &str,
) -> Result<()> {
let script = script.trim_start();
let cmd = trunc(&style::ebold(format!("$ {script}")).bright().to_string());
let cmd = trunc(
&style::ebold(format!("$ {script} {args}", args = args.join(" ")))
.bright()
.to_string(),
);
eprintln!("{prefix} {cmd}");

if script.starts_with("#!") {
Expand Down Expand Up @@ -423,9 +427,15 @@ impl Run {
if self.raw(task) {
cmd.with_raw();
}
if let Some(cd) = &self.cd.as_ref().or(task.dir.as_ref()) {
cmd = cmd.current_dir(cd);
let dir = self.cwd(task)?;
if !dir.exists() {
eprintln!(
"{prefix} {} task directory does not exist: {}",
style::eyellow("WARN"),
display_path(&dir)
);
}
cmd = cmd.current_dir(dir);
if self.dry_run {
return Ok(());
}
Expand Down Expand Up @@ -496,23 +506,23 @@ impl Run {
Ok(())
}

fn sources_are_fresh(&self, task: &Task) -> bool {
fn sources_are_fresh(&self, task: &Task) -> Result<bool> {
if task.sources.is_empty() && task.outputs.is_empty() {
return false;
return Ok(false);
}
let run = || -> Result<bool> {
let sources = self.get_last_modified(&self.cwd(task), &task.sources)?;
let outputs = self.get_last_modified(&self.cwd(task), &task.outputs)?;
let sources = self.get_last_modified(&self.cwd(task)?, &task.sources)?;
let outputs = self.get_last_modified(&self.cwd(task)?, &task.outputs)?;
trace!("sources: {sources:?}, outputs: {outputs:?}");
match (sources, outputs) {
(Some(sources), Some(outputs)) => Ok(sources < outputs),
_ => Ok(false),
}
};
run().unwrap_or_else(|err| {
Ok(run().unwrap_or_else(|err| {
warn!("sources_are_fresh: {err}");
false
})
}))
}

fn add_failed_task(&self, task: Task, status: Option<i32>) {
Expand Down Expand Up @@ -549,13 +559,17 @@ impl Run {
Ok(last_mod)
}

fn cwd(&self, task: &Task) -> PathBuf {
self.cd
.as_ref()
.or(task.dir.as_ref())
.cloned()
.or_else(|| CONFIG.project_root.clone())
.unwrap_or_else(|| env::current_dir().unwrap().clone())
fn cwd(&self, task: &Task) -> Result<PathBuf> {
if let Some(d) = &self.cd {
Ok(d.clone())
} else if let Some(d) = task.dir()? {
Ok(d)
} else {
Ok(CONFIG
.project_root
.clone()
.unwrap_or_else(|| env::current_dir().unwrap()))
}
}

fn save_checksum(&self, task: &Task) -> Result<()> {
Expand Down
11 changes: 3 additions & 8 deletions src/cli/tasks/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,9 @@ impl TasksEdit {
.cloned()
.map_or_else(
|| {
let path = config
.project_root
.as_ref()
.unwrap_or(&env::current_dir()?)
.join(".mise")
.join("tasks")
.join(&self.task);
Task::from_path(&path)
let project_root = config.project_root.clone().unwrap_or(env::current_dir()?);
let path = project_root.join(".mise").join("tasks").join(&self.task);
Task::from_path(&path, path.parent().unwrap(), &project_root)
},
Ok,
)?;
Expand Down
2 changes: 2 additions & 0 deletions src/config/config_file/mise_toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,10 @@ impl MiseToml {
rf.context
.insert("config_root", path.parent().unwrap().to_str().unwrap());
rf.path = path.to_path_buf();
let project_root = rf.project_root().map(|p| p.to_path_buf());
for task in rf.tasks.0.values_mut() {
task.config_source.clone_from(&rf.path);
task.config_root = project_root.clone();
}
// trace!("{}", rf.dump()?);
Ok(rf)
Expand Down
26 changes: 16 additions & 10 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ impl Config {
let file_tasks = includes
.into_par_iter()
.flat_map(|p| {
self.load_tasks_includes(&p).unwrap_or_else(|err| {
self.load_tasks_includes(&p, dir).unwrap_or_else(|err| {
warn!("loading tasks in {}: {err}", display_path(&p));
vec![]
})
Expand Down Expand Up @@ -348,19 +348,24 @@ impl Config {

fn load_global_tasks(&self) -> Result<Vec<Task>> {
let cf = self.config_files.get(&*env::MISE_GLOBAL_CONFIG_FILE);
let config_root = cf.and_then(|cf| cf.project_root()).unwrap_or(&*env::HOME);
Ok(self
.load_config_tasks(&cf)
.into_iter()
.chain(self.load_file_tasks(&cf))
.chain(self.load_file_tasks(&cf, config_root))
.collect())
}

fn load_system_tasks(&self) -> Result<Vec<Task>> {
let cf = self.config_files.get(&dirs::SYSTEM.join("config.toml"));
let config_root = cf
.and_then(|cf| cf.project_root())
.map(|p| p.to_path_buf())
.unwrap_or_default();
Ok(self
.load_config_tasks(&cf)
.into_iter()
.chain(self.load_file_tasks(&cf))
.chain(self.load_file_tasks(&cf, &config_root))
.collect())
}

Expand All @@ -374,7 +379,7 @@ impl Config {
}

#[allow(clippy::borrowed_box)]
fn load_file_tasks(&self, cf: &Option<&Box<dyn ConfigFile>>) -> Vec<Task> {
fn load_file_tasks(&self, cf: &Option<&Box<dyn ConfigFile>>, config_root: &Path) -> Vec<Task> {
let includes = match cf {
Some(cf) => cf
.task_config()
Expand All @@ -389,15 +394,16 @@ impl Config {
includes
.into_iter()
.flat_map(|p| {
self.load_tasks_includes(&p).unwrap_or_else(|err| {
warn!("loading tasks in {}: {err}", display_path(&p));
vec![]
})
self.load_tasks_includes(&p, config_root)
.unwrap_or_else(|err| {
warn!("loading tasks in {}: {err}", display_path(&p));
vec![]
})
})
.collect()
}

fn load_tasks_includes(&self, root: &Path) -> Result<Vec<Task>> {
fn load_tasks_includes(&self, root: &Path, config_root: &Path) -> Result<Vec<Task>> {
if !root.is_dir() {
return Ok(vec![]);
}
Expand All @@ -411,7 +417,7 @@ impl Config {
.try_collect::<_, Vec<PathBuf>, _>()?
.into_par_iter()
.filter(|p| file::is_executable(p))
.map(|path| Task::from_path(&path))
.map(|path| Task::from_path(&path, root, config_root))
.collect()
}

Expand Down
95 changes: 43 additions & 52 deletions src/task/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ pub struct Task {
pub aliases: Vec<String>,
#[serde(skip)]
pub config_source: PathBuf,
#[serde(skip)]
pub config_root: Option<PathBuf>,
#[serde(default)]
pub depends: Vec<String>,
#[serde(default)]
Expand Down Expand Up @@ -93,7 +95,7 @@ impl Debug for EitherStringOrBool {
}

impl Task {
pub fn from_path(path: &Path) -> Result<Task> {
pub fn from_path(path: &Path, prefix: &Path, config_root: &Path) -> Result<Task> {
let info = file::read_to_string(path)?
.lines()
.filter_map(|line| {
Expand All @@ -113,16 +115,15 @@ impl Task {
map
});
let info = toml::Value::Table(info);
let config_root =
config_root(&path).ok_or_else(|| eyre!("config root not found: {}", path.display()))?;
let mut tera_ctx = BASE_CONTEXT.clone();
tera_ctx.insert("config_root", &config_root);
let p = TomlParser::new(&info, get_tera(Some(config_root)), tera_ctx);
// trace!("task info: {:#?}", info);

let task = Task {
name: name_from_path(config_root, path)?,
name: name_from_path(prefix, path)?,
config_source: path.to_path_buf(),
config_root: Some(config_root.to_path_buf()),
hide: !file::is_executable(path) || p.parse_bool("hide").unwrap_or_default(),
aliases: p
.parse_array("alias")?
Expand Down Expand Up @@ -183,7 +184,8 @@ impl Task {
spec.cmd.name = self.name.clone();
(spec, vec![])
} else {
let (scripts, spec) = TaskScriptParser::new(cwd).parse_run_scripts(&self.run)?;
let (scripts, spec) =
TaskScriptParser::new(cwd).parse_run_scripts(&self.config_root, &self.run)?;
(spec, scripts)
};
spec.name = self.name.clone();
Expand Down Expand Up @@ -251,12 +253,36 @@ impl Task {
let idx = self.name.chars().map(|c| c as usize).sum::<usize>() % COLORS.len();
style::ereset() + &style::estyle(self.prefix()).fg(COLORS[idx]).to_string()
}

pub fn dir(&self) -> Result<Option<PathBuf>> {
if let Some(dir) = &self.dir {
// TODO: memoize
// let dir = self.dir_rendered.get_or_try_init(|| -> Result<PathBuf> {
let dir = dir.to_string_lossy().to_string();
let mut tera = get_tera(self.config_root.as_deref());
let mut ctx = BASE_CONTEXT.clone();
if let Some(config_root) = &self.config_root {
ctx.insert("config_root", config_root);
}
let dir = tera.render_str(&dir, &ctx)?;
let dir = file::replace_path(&dir);
if dir.is_absolute() {
Ok(Some(dir.to_path_buf()))
} else if let Some(root) = &self.config_root {
Ok(Some(root.join(dir)))
} else {
Ok(Some(dir.clone()))
}
} else {
Ok(self.config_root.clone())
}
}
}

fn name_from_path(root: impl AsRef<Path>, path: impl AsRef<Path>) -> Result<String> {
fn name_from_path(prefix: impl AsRef<Path>, path: impl AsRef<Path>) -> Result<String> {
Ok(path
.as_ref()
.strip_prefix(root)
.strip_prefix(prefix)
.map(|p| match p {
p if p.starts_with("mise-tasks") => p.strip_prefix("mise-tasks"),
p if p.starts_with(".mise-tasks") => p.strip_prefix(".mise-tasks"),
Expand Down Expand Up @@ -288,6 +314,7 @@ impl Default for Task {
description: "".to_string(),
aliases: vec![],
config_source: PathBuf::new(),
config_root: None,
depends: vec![],
env: BTreeMap::new(),
dir: None,
Expand Down Expand Up @@ -354,32 +381,6 @@ impl TreeItem for (&Graph<Task, ()>, NodeIndex) {
}
}

fn config_root(config_source: &impl AsRef<Path>) -> Option<&Path> {
for ancestor in config_source.as_ref().ancestors() {
if ancestor.ends_with("mise-tasks") {
return ancestor.parent();
}

if ancestor.ends_with(".mise-tasks") {
return ancestor.parent();
}

if ancestor.ends_with(".mise/tasks") {
return ancestor.parent()?.parent();
}

if ancestor.ends_with(".config/mise/tasks") {
return ancestor.parent()?.parent()?.parent();
}

if ancestor.ends_with("mise/tasks") {
return ancestor.parent()?.parent();
}
}

config_source.as_ref().parent()
}

pub trait GetMatchingExt<T> {
fn get_matching(&self, pat: &str) -> Result<Vec<&T>>;
}
Expand Down Expand Up @@ -409,20 +410,25 @@ where
mod tests {
use std::path::Path;

use pretty_assertions::assert_eq;

use crate::dirs;
use crate::task::Task;
use crate::test::reset;
use pretty_assertions::assert_eq;

use super::{config_root, name_from_path};
use super::name_from_path;

#[test]
fn test_from_path() {
reset();
let test_cases = [(".mise/tasks/filetask", "filetask", vec!["ft"])];

for (path, name, aliases) in test_cases {
let t = Task::from_path(Path::new(path)).unwrap();
let t = Task::from_path(
Path::new(path),
Path::new(".mise/tasks"),
Path::new(dirs::CWD.as_ref().unwrap()),
)
.unwrap();
assert_eq!(t.name, name);
assert_eq!(t.aliases, aliases);
}
Expand Down Expand Up @@ -453,19 +459,4 @@ mod tests {
assert!(name_from_path(root, path).is_err())
}
}

#[test]
fn test_config_root() {
reset();
let test_cases = [
("/base", Some(Path::new("/"))),
("/base/.mise/tasks", Some(Path::new("/base"))),
("/base/.config/mise/tasks", Some(Path::new("/base"))),
("/base/mise/tasks", Some(Path::new("/base"))),
];

for (src, expected) in test_cases {
assert_eq!(config_root(&src), expected)
}
}
}
Loading
Loading