Skip to content

Commit

Permalink
feat(tasks): optional automatic outputs
Browse files Browse the repository at this point in the history
Fixes #2621
  • Loading branch information
jdx committed Dec 14, 2024
1 parent 6861587 commit 133f617
Show file tree
Hide file tree
Showing 12 changed files with 409 additions and 99 deletions.
10 changes: 10 additions & 0 deletions e2e/tasks/test_task_run_sources
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,14 @@ mkdir subdir && cd subdir || exit 1
assert_empty "mise -q hi"
assert_empty "mise -q hi"
touch ../older
assert "mise -q --trace hi" "hi"

cat <<EOF >mise.toml
[tasks.hi]
sources = ["{{cwd}}/input"]
outputs = { auto = true }
run = "echo hi"
EOF

assert "mise -q hi" "hi"
assert_empty "mise -q hi"
21 changes: 20 additions & 1 deletion src/cli/doctor.rs → src/cli/doctor/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mod path;

use crate::exit;

use crate::backend::backend_type::BackendType;
Expand Down Expand Up @@ -25,14 +27,31 @@ use strum::IntoEnumIterator;
#[derive(Debug, clap::Args)]
#[clap(visible_alias = "dr", verbatim_doc_comment, after_long_help = AFTER_LONG_HELP)]
pub struct Doctor {
#[clap(subcommand)]
subcommand: Option<Commands>,
#[clap(skip)]
errors: Vec<String>,
#[clap(skip)]
warnings: Vec<String>,
}

#[derive(Debug, clap::Subcommand)]
pub enum Commands {
Path(path::Path),
}

impl Doctor {
pub fn run(mut self) -> eyre::Result<()> {
pub fn run(self) -> eyre::Result<()> {
if let Some(cmd) = self.subcommand {
match cmd {
Commands::Path(cmd) => cmd.run(),
}
} else {
self.doctor()
}
}

fn doctor(mut self) -> eyre::Result<()> {
info::inline_section("version", &*VERSION)?;
#[cfg(unix)]
info::inline_section("activated", yn(env::is_activated()))?;
Expand Down
40 changes: 40 additions & 0 deletions src/cli/doctor/path.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use std::env;
use crate::config::Config;
use crate::Result;

/// Print the current PATH entries mise is providing
#[derive(Debug, clap::Args)]
#[clap(verbatim_doc_comment, after_long_help = AFTER_LONG_HELP)]
pub struct Path {
/// Print all entries including those not provided by mise
#[clap(long, short, verbatim_doc_comment)]
full: bool,
}

impl Path {
pub fn run(self) -> Result<()> {
let config = Config::get();
let ts = config.get_toolset()?;
let paths = if self.full {
let env = ts.env_with_path(&config)?;
let path = env.get("PATH").cloned().unwrap_or_default();
env::split_paths(&path).collect()
} else {
ts.list_final_paths()?
};
for path in paths {
println!("{}", path.display());
}
Ok(())
}
}

static AFTER_LONG_HELP: &str = color_print::cstr!(
r#"<bold><underline>Examples:</underline></bold>
Get the current PATH entries mise is providing
$ mise path
/home/user/.local/share/mise/installs/node/24.0.0/bin
/home/user/.local/share/mise/installs/rust/1.90.0/bin
/home/user/.local/share/mise/installs/python/3.10.0/bin
"#);
46 changes: 27 additions & 19 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,6 @@ pub struct Cli {
/// Dry run, don't actually do anything
#[clap(short = 'n', long, hide = true)]
pub dry_run: bool,
/// Sets log level to debug
#[clap(long, global = true, hide = true)]
pub debug: bool,
/// Set the environment for loading `mise.<ENV>.toml`
#[clap(short = 'E', long, global = true)]
pub env: Option<Vec<String>>,
Expand All @@ -107,8 +104,6 @@ pub struct Cli {
/// How many jobs to run in parallel [default: 8]
#[clap(long, short, global = true, env = "MISE_JOBS")]
pub jobs: Option<usize>,
#[clap(long, global = true, hide = true, value_name = "LEVEL", value_enum)]
pub log_level: Option<LevelFilter>,
#[clap(long, short, hide = true, overrides_with = "interleave")]
pub prefix: bool,
/// Set the profile (environment)
Expand All @@ -126,15 +121,9 @@ pub struct Cli {
env = "MISE_QUIET"
)]
pub tool: Vec<ToolArg>,
/// Suppress non-error messages
#[clap(short = 'q', long, global = true, overrides_with = "verbose")]
pub quiet: bool,
/// Read/write directly to stdin/stdout/stderr instead of by line
#[clap(long, global = true)]
pub raw: bool,
/// Suppress all task output and mise non-error messages
#[clap(long)]
pub silent: bool,
/// Shows elapsed time after each task completes
///
/// Default to always show with `MISE_TASK_TIMINGS=1`
Expand All @@ -146,17 +135,36 @@ pub struct Cli {
#[clap(long, alias = "no-timing", hide = true, verbatim_doc_comment)]
pub no_timings: bool,

/// Sets log level to trace
#[clap(long, global = true, hide = true)]
pub trace: bool,
/// Show extra output (use -vv for even more)
#[clap(short='v', long, global=true, overrides_with="quiet", action=ArgAction::Count)]
pub verbose: u8,
#[clap(long, short = 'V', hide = true)]
pub version: bool,
/// Answer yes to all confirmation prompts
#[clap(short = 'y', long, global = true)]
pub yes: bool,

#[clap(flatten)]
pub global_output_flags: CliGlobalOutputFlags,
}

#[derive(Debug, clap::Args)]
#[group(multiple=false)]
pub struct CliGlobalOutputFlags {
/// Sets log level to debug
#[clap(long, global = true, hide = true, overrides_with_all = &["quiet", "trace", "verbose", "silent", "log_level"])]
pub debug: bool,
#[clap(long, global = true, hide = true, value_name = "LEVEL", value_enum, overrides_with_all = &["quiet", "trace", "verbose", "silent", "debug"])]
pub log_level: Option<LevelFilter>,
/// Suppress non-error messages
#[clap(short = 'q', long, global = true, overrides_with_all = &["silent", "trace", "verbose", "debug", "log_level"])]
pub quiet: bool,
/// Suppress all task output and mise non-error messages
#[clap(long, global = true, overrides_with_all = &["quiet", "trace", "verbose", "debug", "log_level"])]
pub silent: bool,
/// Sets log level to trace
#[clap(long, global = true, hide = true, overrides_with_all = &["quiet", "silent", "verbose", "debug", "log_level"])]
pub trace: bool,
/// Show extra output (use -vv for even more)
#[clap(short='v', long, global=true, action=ArgAction::Count, overrides_with_all = &["quiet", "silent", "trace", "debug"])]
pub verbose: u8,
}

#[derive(Debug, Subcommand, strum::Display)]
Expand Down Expand Up @@ -349,8 +357,8 @@ impl Cli {
output: run::TaskOutput::Prefix,
prefix: self.prefix,
shell: self.shell,
quiet: self.quiet,
silent: self.silent,
quiet: self.global_output_flags.quiet,
silent: self.global_output_flags.silent,
raw: self.raw,
timings: self.timings,
tmpdir: Default::default(),
Expand Down
14 changes: 10 additions & 4 deletions src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -586,14 +586,15 @@ impl Run {
}

fn sources_are_fresh(&self, task: &Task) -> Result<bool> {
if task.sources.is_empty() && task.outputs.is_empty() {
let outputs = task.outputs.paths(task);
if task.sources.is_empty() && outputs.is_empty() {
return Ok(false);
}
let run = || -> Result<bool> {
let mut sources = task.sources.clone();
sources.push(task.config_source.to_string_lossy().to_string());
let sources = self.get_last_modified(&self.cwd(task)?, &sources)?;
let outputs = self.get_last_modified(&self.cwd(task)?, &task.outputs)?;
let outputs = self.get_last_modified(&self.cwd(task)?, &outputs)?;
trace!("sources: {sources:?}, outputs: {outputs:?}");
match (sources, outputs) {
(Some(sources), Some(outputs)) => Ok(sources < outputs),
Expand Down Expand Up @@ -658,7 +659,12 @@ impl Run {
if task.sources.is_empty() {
return Ok(());
}
// TODO
if task.outputs.is_auto() {
for p in task.outputs.paths(task) {
debug!("touching auto output file: {p}");
file::touch_file(&PathBuf::from(&p))?;
}
}
Ok(())
}

Expand Down Expand Up @@ -913,4 +919,4 @@ pub fn get_task_lists(args: &[String], prompt: bool) -> Result<Vec<Task>> {
})
.flatten_ok()
.collect()
}
}
5 changes: 3 additions & 2 deletions src/cli/tasks/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ impl TasksInfo {
if !task.sources.is_empty() {
info::inline_section("Sources", task.sources.join(", "))?;
}
if !task.outputs.is_empty() {
info::inline_section("Outputs", task.outputs.join(", "))?;
let outputs = task.outputs.paths(task);
if !outputs.is_empty() {
info::inline_section("Outputs", outputs.join(", "))?;
}
if let Some(file) = &task.file {
info::inline_section("File", display_path(file))?;
Expand Down
33 changes: 8 additions & 25 deletions src/config/config_file/toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,31 @@ use std::str::FromStr;

use either::Either;
use serde::de;
use tera::{Context, Tera};

use crate::task::{EitherIntOrBool, EitherStringOrIntOrBool};

pub struct TomlParser<'a> {
table: &'a toml::Value,
tera: Tera,
tera_ctx: Context,
}

impl<'a> TomlParser<'a> {
pub fn new(table: &'a toml::Value, tera: Tera, tera_ctx: Context) -> Self {
Self {
table,
tera,
tera_ctx,
}
pub fn new(table: &'a toml::Value) -> Self {
Self { table }
}

pub fn parse_str<T>(&self, key: &str) -> eyre::Result<Option<T>>
pub fn parse_str<T>(&self, key: &str) -> Option<T>
where
T: From<String>,
{
self.table
.get(key)
.and_then(|value| value.as_str())
.map(|s| self.render_tmpl(s))
.transpose()
.map(|value| value.to_string().into())
}
pub fn parse_bool(&self, key: &str) -> Option<bool> {
self.table.get(key).and_then(|value| value.as_bool())
}
pub fn parse_array<T>(&self, key: &str) -> eyre::Result<Option<Vec<T>>>
pub fn parse_array<T>(&self, key: &str) -> Option<Vec<T>>
where
T: From<String>,
{
Expand All @@ -46,10 +38,9 @@ impl<'a> TomlParser<'a> {
.map(|array| {
array
.iter()
.filter_map(|value| value.as_str().map(|v| self.render_tmpl(v)))
.collect::<eyre::Result<Vec<T>>>()
.filter_map(|value| value.as_str().map(|v| v.to_string().into()))
.collect::<Vec<T>>()
})
.transpose()
}
pub fn parse_env(
&self,
Expand All @@ -65,7 +56,7 @@ impl<'a> TomlParser<'a> {
let v = value
.as_str()
.map(|v| {
Ok(EitherStringOrIntOrBool(Either::Left(self.render_tmpl(v)?)))
Ok(EitherStringOrIntOrBool(Either::Left(v.to_string())))
})
.or_else(|| {
value.as_integer().map(|v| {
Expand All @@ -88,14 +79,6 @@ impl<'a> TomlParser<'a> {
})
.transpose()
}

fn render_tmpl<T>(&self, tmpl: &str) -> eyre::Result<T>
where
T: From<String>,
{
let tmpl = self.tera.clone().render_str(tmpl, &self.tera_ctx)?;
Ok(tmpl.into())
}
}

pub fn deserialize_arr<'de, D, T>(deserializer: D) -> eyre::Result<Vec<T>, D::Error>
Expand Down
19 changes: 12 additions & 7 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ impl Config {
let mut system_tasks = None;
rayon::scope(|s| {
s.spawn(|_| {
file_tasks = Some(self.load_file_tasks_recursively());
file_tasks = Some(self.load_local_tasks());
});
global_tasks = Some(self.load_global_tasks());
system_tasks = Some(self.load_system_tasks());
Expand Down Expand Up @@ -339,8 +339,7 @@ impl Config {
let configs = self.configs_at_root(dir);
let config_tasks = configs
.par_iter()
.flat_map(|cf| cf.tasks())
.cloned()
.flat_map(|cf| self.load_config_tasks(&Some(*cf), dir))
.collect::<Vec<_>>();
let includes = self.task_includes_for_dir(dir);
let extra_tasks = includes
Expand Down Expand Up @@ -386,7 +385,7 @@ impl Config {
Ok(tasks.into_values().collect())
}

fn load_file_tasks_recursively(&self) -> Result<Vec<Task>> {
fn load_local_tasks(&self) -> Result<Vec<Task>> {
let file_tasks = file::all_dirs()?
.into_iter()
.filter(|d| {
Expand All @@ -410,7 +409,7 @@ impl Config {
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)
.load_config_tasks(&cf.map(|cf| cf.as_ref()), config_root)
.into_iter()
.chain(self.load_file_tasks(&cf, config_root))
.collect())
Expand All @@ -423,17 +422,23 @@ impl Config {
.map(|p| p.to_path_buf())
.unwrap_or_default();
Ok(self
.load_config_tasks(&cf)
.load_config_tasks(&cf.map(|cf| cf.as_ref()), &config_root)
.into_iter()
.chain(self.load_file_tasks(&cf, &config_root))
.collect())
}

fn load_config_tasks(&self, cf: &Option<&Box<dyn ConfigFile>>) -> Vec<Task> {
fn load_config_tasks(&self, cf: &Option<&dyn ConfigFile>, config_root: &Path) -> Vec<Task> {
cf.map(|cf| cf.tasks())
.unwrap_or_default()
.into_iter()
.cloned()
.map(|mut t| {
if let Err(err) = t.render(config_root) {
warn!("rendering task: {err:?}");
}
t
})
.collect()
}

Expand Down
Loading

0 comments on commit 133f617

Please sign in to comment.