Skip to content
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
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2139,6 +2139,7 @@ change their behavior.
| `[confirm(PROMPT)]`<sup>1.23.0</sup> | recipe | Require confirmation prior to executing recipe with a custom prompt. |
| `[default]`<sup>1.43.0</sup> | recipe | Use recipe as module's default recipe. |
| `[doc(DOC)]`<sup>1.27.0</sup> | module, recipe | Set recipe or module's [documentation comment](#documentation-comments) to `DOC`. |
| `[env(ENV_VAR, VALUE)]` <sup>master</sup> | recipe | Set environment variables for recipe. |
| `[extension(EXT)]`<sup>1.32.0</sup> | recipe | Set shebang recipe script's file extension to `EXT`. `EXT` should include a period if one is desired. |
| `[group(NAME)]`<sup>1.27.0</sup> | module, recipe | Put recipe or module in [group](#groups) `NAME`. |
| `[linux]`<sup>1.8.0</sup> | recipe | Enable recipe on Linux. |
Expand Down Expand Up @@ -2518,6 +2519,16 @@ test $RUST_BACKTRACE="1":
cargo test
```

You can also use the `[env(NAME, VALUE)]` attribute to export environment
variables to a specific recipe:

```just
[env("RUST_BACKTRACE", "1")]
test:
# will print a stack trace if it crashes
cargo test
```

Exported variables and parameters are not exported to backticks in the same scope.

```just
Expand Down
9 changes: 8 additions & 1 deletion src/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub(crate) enum Attribute<'src> {
Confirm(Option<StringLiteral<'src>>),
Default,
Doc(Option<StringLiteral<'src>>),
Env(StringLiteral<'src>, StringLiteral<'src>),
ExitMessage,
Extension(StringLiteral<'src>),
Group(StringLiteral<'src>),
Expand Down Expand Up @@ -61,6 +62,7 @@ impl AttributeDiscriminant {
Self::Confirm | Self::Doc => 0..=1,
Self::Script => 0..=usize::MAX,
Self::Arg | Self::Extension | Self::Group | Self::WorkingDirectory => 1..=1,
Self::Env => 2..=2,
Self::Metadata => 1..=usize::MAX,
}
}
Expand Down Expand Up @@ -179,6 +181,10 @@ impl<'src> Attribute<'src> {
AttributeDiscriminant::Confirm => Self::Confirm(arguments.into_iter().next()),
AttributeDiscriminant::Default => Self::Default,
AttributeDiscriminant::Doc => Self::Doc(arguments.into_iter().next()),
AttributeDiscriminant::Env => {
let [key, value]: [StringLiteral; 2] = arguments.try_into().unwrap();
Comment thread
neunenak marked this conversation as resolved.
Self::Env(key, value)
}
AttributeDiscriminant::ExitMessage => Self::ExitMessage,
AttributeDiscriminant::Extension => Self::Extension(arguments.into_iter().next().unwrap()),
AttributeDiscriminant::Group => Self::Group(arguments.into_iter().next().unwrap()),
Expand Down Expand Up @@ -243,7 +249,7 @@ impl<'src> Attribute<'src> {
pub(crate) fn repeatable(&self) -> bool {
matches!(
self,
Attribute::Arg { .. } | Attribute::Group(_) | Attribute::Metadata(_),
Attribute::Arg { .. } | Attribute::Env(_, _) | Attribute::Group(_) | Attribute::Metadata(_),
Comment thread
casey marked this conversation as resolved.
)
}
}
Expand Down Expand Up @@ -307,6 +313,7 @@ impl Display for Attribute<'_> {
| Self::Extension(argument)
| Self::Group(argument)
| Self::WorkingDirectory(argument) => write!(f, "({argument})")?,
Self::Env(key, value) => write!(f, "({key}, {value})")?,
Self::Metadata(arguments) => {
write!(f, "(")?;
for (i, argument) in arguments.iter().enumerate() {
Expand Down
6 changes: 6 additions & 0 deletions src/compile_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ impl Display for CompileError<'_> {
first.ordinal(),
self.token.line.ordinal(),
),
DuplicateEnvAttribute { variable, first } => write!(
f,
"Environment variable `{variable}` first set on line {} is set again on line {}",
first.ordinal(),
self.token.line.ordinal(),
),
DuplicateDefault { recipe } => write!(
f,
"Recipe `{recipe}` has duplicate `[default]` attribute, which may only appear once per module",
Expand Down
4 changes: 4 additions & 0 deletions src/compile_error_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ pub(crate) enum CompileErrorKind<'src> {
DuplicateDefault {
recipe: &'src str,
},
DuplicateEnvAttribute {
variable: String,
first: usize,
},
DuplicateOption {
recipe: &'src str,
option: Switch,
Expand Down
26 changes: 24 additions & 2 deletions src/evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use super::*;
pub(crate) struct Evaluator<'src: 'run, 'run> {
assignments: Option<&'run Table<'src, Assignment<'src>>>,
context: Option<ExecutionContext<'src, 'run>>,
env: BTreeMap<String, String>,
is_dependency: bool,
non_const_assignments: Table<'src, Name<'src>>,
scope: Scope<'src, 'run>,
Expand Down Expand Up @@ -53,6 +54,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
let mut evaluator = Self {
assignments: Some(assignments),
context: None,
env: BTreeMap::new(),
is_dependency: false,
non_const_assignments: Table::new(),
scope,
Expand Down Expand Up @@ -201,6 +203,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
let mut evaluator = Self {
assignments: Some(&module.assignments),
context: Some(context),
env: BTreeMap::new(),
is_dependency: false,
non_const_assignments: Table::new(),
scope,
Expand Down Expand Up @@ -273,7 +276,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
return Ok(format!("`{contents}`"));
}

Self::run_command(context, &self.scope, contents, &[]).map_err(|output_error| {
Self::run_command(context, &self.env, &self.scope, contents, &[]).map_err(|output_error| {
Error::Backtick {
token: *token,
output_error,
Expand Down Expand Up @@ -436,6 +439,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {

pub(crate) fn run_command(
context: &ExecutionContext,
env: &BTreeMap<String, String>,
scope: &Scope,
command: &str,
args: &[&str],
Expand All @@ -460,6 +464,10 @@ impl<'src, 'run> Evaluator<'src, 'run> {
})
.stdout(Stdio::piped());

for (key, value) in env {
cmd.env(key, value);
}

cmd.output_guard_stdout()
}

Expand Down Expand Up @@ -498,7 +506,19 @@ impl<'src, 'run> Evaluator<'src, 'run> {
recipe: &Recipe<'src>,
scope: &'run Scope<'src, 'run>,
) -> RunResult<'src, (Scope<'src, 'run>, Vec<String>)> {
let mut evaluator = Self::new(context, is_dependency, scope);
let env = recipe
.attributes
.iter()
.filter_map(|attribute| {
if let Attribute::Env(key, value) = attribute {
Some((key.cooked.clone(), value.cooked.clone()))
} else {
None
}
})
.collect();

let mut evaluator = Self::new(context, env, is_dependency, scope);

let mut positional = Vec::new();

Expand Down Expand Up @@ -550,12 +570,14 @@ impl<'src, 'run> Evaluator<'src, 'run> {

pub(crate) fn new(
context: &ExecutionContext<'src, 'run>,
env: BTreeMap<String, String>,
is_dependency: bool,
scope: &'run Scope<'src, 'run>,
) -> Self {
Self {
assignments: None,
context: Some(*context),
env,
is_dependency,
non_const_assignments: Table::new(),
scope: scope.child(),
Expand Down
10 changes: 8 additions & 2 deletions src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -544,8 +544,14 @@ fn shell(context: Context, command: &str, args: &[String]) -> FunctionResult {
.chain(args.iter().map(String::as_str))
.collect::<Vec<&str>>();

Evaluator::run_command(context.execution_context, context.scope, command, &args)
.map_err(|output_error| output_error.to_string())
Evaluator::run_command(
context.execution_context,
&BTreeMap::new(),
context.scope,
command,
&args,
)
.map_err(|output_error| output_error.to_string())
}

fn shoutykebabcase(_context: Context, s: &str) -> FunctionResult {
Expand Down
2 changes: 1 addition & 1 deletion src/justfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ impl<'src> Justfile<'src> {

let scope = outer.child();

let mut evaluator = Evaluator::new(&context, true, &scope);
let mut evaluator = Evaluator::new(&context, BTreeMap::new(), true, &scope);

Self::run_dependencies(
config,
Expand Down
12 changes: 12 additions & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1380,6 +1380,7 @@ impl<'run, 'src> Parser<'run, 'src> {
let mut arg_attributes = BTreeMap::new();
let mut attributes = Vec::new();
let mut discriminants = BTreeMap::new();
let mut env_attributes = BTreeMap::new();

let mut token = None;

Expand Down Expand Up @@ -1453,6 +1454,17 @@ impl<'run, 'src> Parser<'run, 'src> {
arg_attributes.insert(arg.cooked.clone(), name.line);
}

if let Attribute::Env(variable, _) = &attribute {
if let Some(&first) = env_attributes.get(&variable.cooked) {
return Err(name.error(CompileErrorKind::DuplicateEnvAttribute {
variable: variable.cooked.clone(),
first,
}));
}

env_attributes.insert(variable.cooked.clone(), name.line);
}

discriminants.insert(attribute.discriminant(), name.line);

attributes.push(attribute);
Expand Down
14 changes: 13 additions & 1 deletion src/recipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ impl<'src, D> Recipe<'src, D> {
}
}

let evaluator = Evaluator::new(context, is_dependency, scope);
let evaluator = Evaluator::new(context, BTreeMap::new(), is_dependency, scope);

if self.is_script() {
self.run_script(context, scope, positional, evaluator)
Expand Down Expand Up @@ -327,6 +327,12 @@ impl<'src, D> Recipe<'src, D> {
cmd.stdout(Stdio::null());
}

for attribute in &self.attributes {
if let Attribute::Env(key, value) = attribute {
cmd.env(&key.cooked, &value.cooked);
}
}

cmd.export(
&context.module.settings,
context.dotenv,
Expand Down Expand Up @@ -477,6 +483,12 @@ impl<'src, D> Recipe<'src, D> {
command.args(positional);
}

for attribute in &self.attributes {
if let Attribute::Env(key, value) = attribute {
command.env(&key.cooked, &value.cooked);
}
}

command.export(
&context.module.settings,
context.dotenv,
Expand Down
Loading