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: allow using previously defined vars #3741

Merged
merged 1 commit into from
Dec 20, 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
7 changes: 7 additions & 0 deletions e2e/tasks/test_task_vars
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,10 @@ tasks.a.run = "echo foo: {{vars.SECRET}}"
vars._.file = ".env.json"
EOF
assert "mise run a" "foo: 123"

cat <<EOF >mise.toml
tasks.a.run = "echo foo: {{vars.bar}}"
vars.foo = "bar"
vars.bar = "bar: {{vars.foo}}"
EOF
assert "mise run a" "foo: bar: bar"
16 changes: 8 additions & 8 deletions src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,14 +335,14 @@ impl Run {
}
let ts = ToolsetBuilder::new().with_args(&tools).build(&config)?;
let mut env = ts.env_with_path(&config)?;
env.insert(
"MISE_TASK_OUTPUT".into(),
self.output(Some(task)).to_string(),
);
env.insert(
"MISE_TASK_LEVEL".into(),
(*env::MISE_TASK_LEVEL + 1).to_string(),
);
let output = self.output(Some(task));
env.insert("MISE_TASK_OUTPUT".into(), output.to_string());
if output == TaskOutput::Prefix {
env.insert(
"MISE_TASK_LEVEL".into(),
(*env::MISE_TASK_LEVEL + 1).to_string(),
);
}
if !self.timings {
env.insert("MISE_TASK_TIMINGS".to_string(), "0".to_string());
}
Expand Down
14 changes: 5 additions & 9 deletions src/config/env_directive/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,33 +22,29 @@ impl EnvResults {
pub fn file(
ctx: &mut tera::Context,
tera: &mut tera::Tera,
env: &mut IndexMap<String, (String, Option<PathBuf>)>,
r: &mut EnvResults,
normalize_path: fn(&Path, PathBuf) -> PathBuf,
source: &Path,
config_root: &Path,
input: String,
) -> Result<()> {
) -> Result<IndexMap<PathBuf, EnvMap>> {
let mut out = IndexMap::new();
let s = r.parse_template(ctx, tera, source, &input)?;
for p in xx::file::glob(normalize_path(config_root, s.into())).unwrap_or_default() {
r.env_files.push(p.clone());
let env = out.entry(p.clone()).or_insert_with(IndexMap::new);
let parse_template = |s: String| r.parse_template(ctx, tera, source, &s);
let ext = p
.extension()
.map(|e| e.to_string_lossy().to_string())
.unwrap_or_default();
let new_vars = match ext.as_str() {
*env = match ext.as_str() {
"json" => Self::json(&p, parse_template)?,
"yaml" => Self::yaml(&p, parse_template)?,
"toml" => unimplemented!("toml"),
_ => Self::dotenv(&p)?,
};
for (k, v) in new_vars {
r.env_remove.remove(&k);
env.insert(k, (v, Some(p.clone())));
}
}
Ok(())
Ok(out)
}

fn json<PT>(p: &Path, parse_template: PT) -> Result<EnvMap>
Expand Down
62 changes: 52 additions & 10 deletions src/config/env_directive/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,19 +111,26 @@ impl Display for EnvDirective {
#[derive(Default, Clone)]
pub struct EnvResults {
pub env: IndexMap<String, (String, PathBuf)>,
pub vars: IndexMap<String, (String, PathBuf)>,
pub env_remove: BTreeSet<String>,
pub env_files: Vec<PathBuf>,
pub env_paths: Vec<PathBuf>,
pub env_scripts: Vec<PathBuf>,
pub redactions: Vec<String>,
}

#[derive(Default)]
pub struct EnvResolveOptions {
pub vars: bool,
pub tools: bool,
}

impl EnvResults {
pub fn resolve(
mut ctx: tera::Context,
initial: &EnvMap,
input: Vec<(EnvDirective, PathBuf)>,
tools: bool,
resolve_opts: EnvResolveOptions,
) -> eyre::Result<Self> {
// trace!("resolve: input: {:#?}", &input);
let mut env = initial
Expand All @@ -132,6 +139,7 @@ impl EnvResults {
.collect::<IndexMap<_, _>>();
let mut r = Self {
env: Default::default(),
vars: Default::default(),
env_remove: BTreeSet::new(),
env_files: Vec::new(),
env_paths: Vec::new(),
Expand All @@ -155,7 +163,7 @@ impl EnvResults {
.iter()
.fold(Vec::new(), |mut acc, (directive, source)| {
// remove directives that need tools if we're not processing tool directives, or vice versa
if directive.options().tools != tools {
if directive.options().tools != resolve_opts.tools {
return acc;
}
if let Some(d) = &last_python_venv {
Expand Down Expand Up @@ -191,14 +199,25 @@ impl EnvResults {
.map(|(k, (v, _))| (k.clone(), v.clone()))
.collect::<EnvMap>();
ctx.insert("env", &env_vars);
ctx.insert(
"vars",
&r.vars
.iter()
.map(|(k, (v, _))| (k.clone(), v.clone()))
.collect::<EnvMap>(),
);
let redact = directive.options().redact;
// trace!("resolve: ctx.get('env'): {:#?}", &ctx.get("env"));
match directive {
EnvDirective::Val(k, v, _opts) => {
let v = r.parse_template(&ctx, &mut tera, &source, &v)?;
r.env_remove.remove(&k);
// trace!("resolve: inserting {:?}={:?} from {:?}", &k, &v, &source);
env.insert(k, (v, Some(source.clone())));
if resolve_opts.vars {
r.vars.insert(k, (v, source.clone()));
} else {
r.env_remove.remove(&k);
// trace!("resolve: inserting {:?}={:?} from {:?}", &k, &v, &source);
env.insert(k, (v, Some(source.clone())));
}
}
EnvDirective::Rm(k, _opts) => {
env.shift_remove(&k);
Expand All @@ -208,30 +227,50 @@ impl EnvResults {
Self::path(&mut ctx, &mut tera, &mut r, &mut paths, source, input_str)?;
}
EnvDirective::File(input, _opts) => {
Self::file(
let files = Self::file(
&mut ctx,
&mut tera,
&mut env,
&mut r,
normalize_path,
&source,
&config_root,
input,
)?;
for (f, new_env) in files {
r.env_files.push(f.clone());
for (k, v) in new_env {
if resolve_opts.vars {
r.vars.insert(k, (v, f.clone()));
} else {
r.env_remove.insert(k.clone());
env.insert(k, (v, Some(f.clone())));
}
}
}
}
EnvDirective::Source(input, _opts) => {
Self::source(
let files = Self::source(
&mut ctx,
&mut tera,
&mut env,
&mut paths,
&mut r,
normalize_path,
&source,
&config_root,
&env_vars,
input,
);
)?;
for (f, new_env) in files {
r.env_scripts.push(f.clone());
for (k, v) in new_env {
if resolve_opts.vars {
r.vars.insert(k, (v, f.clone()));
} else {
r.env_remove.insert(k.clone());
env.insert(k, (v, Some(f.clone())));
}
}
}
}
EnvDirective::PythonVenv {
path,
Expand Down Expand Up @@ -318,6 +357,9 @@ impl Debug for EnvResults {
if !self.env.is_empty() {
ds.field("env", &self.env);
}
if !self.vars.is_empty() {
ds.field("vars", &self.vars);
}
if !self.env_remove.is_empty() {
ds.field("env_remove", &self.env_remove);
}
Expand Down
4 changes: 2 additions & 2 deletions src/config/env_directive/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ impl EnvResults {
#[cfg(unix)]
mod tests {
use super::*;
use crate::config::env_directive::EnvDirective;
use crate::config::env_directive::{EnvDirective, EnvResolveOptions};
use crate::env_diff::EnvMap;
use crate::tera::BASE_CONTEXT;
use crate::test::replace_path;
Expand Down Expand Up @@ -64,7 +64,7 @@ mod tests {
Default::default(),
),
],
false,
EnvResolveOptions::default(),
)
.unwrap();
assert_debug_snapshot!(
Expand Down
55 changes: 28 additions & 27 deletions src/config/env_directive/source.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::config::env_directive::EnvResults;
use crate::env;
use crate::env_diff::{EnvDiff, EnvDiffOperation, EnvDiffOptions, EnvMap};
use crate::Result;
use indexmap::IndexMap;
use std::path::{Path, PathBuf};

Expand All @@ -9,46 +10,46 @@ impl EnvResults {
pub fn source(
ctx: &mut tera::Context,
tera: &mut tera::Tera,
env: &mut IndexMap<String, (String, Option<PathBuf>)>,
paths: &mut Vec<(PathBuf, PathBuf)>,
r: &mut EnvResults,
normalize_path: fn(&Path, PathBuf) -> PathBuf,
source: &Path,
config_root: &Path,
env_vars: &EnvMap,
input: String,
) {
if let Ok(s) = r.parse_template(ctx, tera, source, &input) {
for p in xx::file::glob(normalize_path(config_root, s.into())).unwrap_or_default() {
r.env_scripts.push(p.clone());
let orig_path = env_vars.get(&*env::PATH_KEY).cloned().unwrap_or_default();
let mut env_diff_opts = EnvDiffOptions::default();
env_diff_opts.ignore_keys.shift_remove(&*env::PATH_KEY); // allow modifying PATH
let env_diff =
EnvDiff::from_bash_script(&p, config_root, env_vars.clone(), env_diff_opts)
.unwrap_or_default();
for p in env_diff.to_patches() {
match p {
EnvDiffOperation::Add(k, v) | EnvDiffOperation::Change(k, v) => {
if k == *env::PATH_KEY {
// TODO: perhaps deal with path removals as well
if let Some(new_path) = v.strip_suffix(&orig_path) {
for p in env::split_paths(new_path) {
paths.push((p, source.to_path_buf()));
}
) -> Result<IndexMap<PathBuf, IndexMap<String, String>>> {
let mut out = IndexMap::new();
let s = r.parse_template(ctx, tera, source, &input)?;
for p in xx::file::glob(normalize_path(config_root, s.into())).unwrap_or_default() {
let env = out.entry(p.clone()).or_insert_with(IndexMap::new);
let orig_path = env_vars.get(&*env::PATH_KEY).cloned().unwrap_or_default();
let mut env_diff_opts = EnvDiffOptions::default();
env_diff_opts.ignore_keys.shift_remove(&*env::PATH_KEY); // allow modifying PATH
let env_diff =
EnvDiff::from_bash_script(&p, config_root, env_vars.clone(), env_diff_opts)
.unwrap_or_default();
for p in env_diff.to_patches() {
match p {
EnvDiffOperation::Add(k, v) | EnvDiffOperation::Change(k, v) => {
if k == *env::PATH_KEY {
// TODO: perhaps deal with path removals as well
if let Some(new_path) = v.strip_suffix(&orig_path) {
for p in env::split_paths(new_path) {
paths.push((p, source.to_path_buf()));
}
} else {
r.env_remove.remove(&k);
env.insert(k.clone(), (v.clone(), Some(source.to_path_buf())));
}
} else {
r.env_remove.remove(&k);
env.insert(k.clone(), v.clone());
}
EnvDiffOperation::Remove(k) => {
env.shift_remove(&k);
r.env_remove.insert(k);
}
}
EnvDiffOperation::Remove(k) => {
env.shift_remove(&k);
r.env_remove.insert(k);
}
}
}
}
Ok(out)
}
}
7 changes: 5 additions & 2 deletions src/config/env_directive/venv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ python -m venv {p}",
#[cfg(unix)]
mod tests {
use super::*;
use crate::config::env_directive::{EnvDirective, EnvDirectiveOptions};
use crate::config::env_directive::{EnvDirective, EnvDirectiveOptions, EnvResolveOptions};
use crate::tera::BASE_CONTEXT;
use crate::test::replace_path;
use insta::assert_debug_snapshot;
Expand Down Expand Up @@ -201,7 +201,10 @@ mod tests {
Default::default(),
),
],
true,
EnvResolveOptions {
tools: true,
..Default::default()
},
)
.unwrap();
// expect order to be reversed as it processes directives from global to dir specific
Expand Down
22 changes: 17 additions & 5 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::cli::version;
use crate::config::config_file::idiomatic_version::IdiomaticVersionFile;
use crate::config::config_file::mise_toml::{MiseToml, Tasks};
use crate::config::config_file::{config_trust_root, ConfigFile};
use crate::config::env_directive::EnvResults;
use crate::config::env_directive::{EnvResolveOptions, EnvResults};
use crate::config::tracking::Tracker;
use crate::file::display_path;
use crate::shorthands::{get_shorthands, Shorthands};
Expand Down Expand Up @@ -111,7 +111,7 @@ impl Config {
let mut tera_ctx = BASE_CONTEXT.clone();
let vars_results = load_vars(tera_ctx.clone(), &config_files)?;
let vars: IndexMap<String, String> = vars_results
.env
.vars
.iter()
.map(|(k, (v, _))| (k.clone(), v.clone()))
.collect();
Expand Down Expand Up @@ -595,8 +595,12 @@ impl Config {
.flatten()
.collect();
// trace!("load_env: entries: {:#?}", entries);
let env_results =
EnvResults::resolve(self.tera_ctx.clone(), &env::PRISTINE_ENV, entries, false)?;
let env_results = EnvResults::resolve(
self.tera_ctx.clone(),
&env::PRISTINE_ENV,
entries,
EnvResolveOptions::default(),
)?;
let redact_keys = self
.redaction_keys()
.into_iter()
Expand Down Expand Up @@ -1124,7 +1128,15 @@ fn load_vars(ctx: tera::Context, config_files: &ConfigMap) -> Result<EnvResults>
.into_iter()
.flatten()
.collect();
let vars_results = EnvResults::resolve(ctx, &env::PRISTINE_ENV, entries, false)?;
let vars_results = EnvResults::resolve(
ctx,
&env::PRISTINE_ENV,
entries,
EnvResolveOptions {
vars: true,
..Default::default()
},
)?;
time!("load_vars done");
if log::log_enabled!(log::Level::Trace) {
trace!("{vars_results:#?}");
Expand Down
Loading
Loading