Skip to content

Commit

Permalink
fix: use project_root for task execution
Browse files Browse the repository at this point in the history
Fixe #1660
  • Loading branch information
jdx committed Nov 5, 2024
1 parent c4b1628 commit 14e50c3
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 118 deletions.
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

0 comments on commit 14e50c3

Please sign in to comment.