Skip to content

Commit 00772f5

Browse files
committed
Add env attribute to set environment variables for recipes
The env attribute accepts two arguments (env_var_name, value) and can be used multiple times per recipe to set environment variables: [env('API_KEY', 'secret')] [env('LOG_LEVEL', 'debug')] deploy: ./deploy.sh
1 parent 088953c commit 00772f5

4 files changed

Lines changed: 122 additions & 1 deletion

File tree

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2112,6 +2112,7 @@ change their behavior.
21122112
| `[confirm(PROMPT)]`<sup>1.23.0</sup> | recipe | Require confirmation prior to executing recipe with a custom prompt. |
21132113
| `[default]`<sup>1.43.0</sup> | recipe | Use recipe as module's default recipe. |
21142114
| `[doc(DOC)]`<sup>1.27.0</sup> | module, recipe | Set recipe or module's [documentation comment](#documentation-comments) to `DOC`. |
2115+
| `[env(ENV_VAR, VALUE)]` <sup>1.44</sup> | recipe | Set env vars that apply only to this recipe. |
21152116
| `[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. |
21162117
| `[group(NAME)]`<sup>1.27.0</sup> | module, recipe | Put recipe or module in in [group](#groups) `NAME`. |
21172118
| `[linux]`<sup>1.8.0</sup> | recipe | Enable recipe on Linux. |
@@ -2494,6 +2495,17 @@ test $RUST_BACKTRACE="1":
24942495
cargo test
24952496
```
24962497

2498+
Or you can use the `[env(env_var, value)]` attribute to set environment variables that
2499+
apply only to this recipe:
2500+
2501+
```just
2502+
2503+
[env("RUST_BACKTRACE", "1")]
2504+
test:
2505+
# will print a stack trace if it crashes
2506+
cargo test
2507+
```
2508+
24972509
Exported variables and parameters are not exported to backticks in the same scope.
24982510

24992511
```just

src/attribute.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub(crate) enum Attribute<'src> {
1212
Confirm(Option<StringLiteral<'src>>),
1313
Default,
1414
Doc(Option<StringLiteral<'src>>),
15+
Env(StringLiteral<'src>, StringLiteral<'src>),
1516
ExitMessage,
1617
Extension(StringLiteral<'src>),
1718
Group(StringLiteral<'src>),
@@ -49,6 +50,7 @@ impl AttributeDiscriminant {
4950
| Self::Unix
5051
| Self::Windows => 0..=0,
5152
Self::Extension | Self::Group | Self::WorkingDirectory => 1..=1,
53+
Self::Env => 2..=2,
5254
Self::Metadata => 1..=usize::MAX,
5355
Self::Script => 0..=usize::MAX,
5456
}
@@ -87,6 +89,20 @@ impl<'src> Attribute<'src> {
8789
AttributeDiscriminant::Confirm => Self::Confirm(arguments.into_iter().next()),
8890
AttributeDiscriminant::Default => Self::Default,
8991
AttributeDiscriminant::Doc => Self::Doc(arguments.into_iter().next()),
92+
AttributeDiscriminant::Env => {
93+
let [key, value]: [StringLiteral; 2] =
94+
arguments
95+
.try_into()
96+
.map_err(|arguments: Vec<StringLiteral>| {
97+
name.error(CompileErrorKind::AttributeArgumentCountMismatch {
98+
attribute: name.lexeme(),
99+
found: arguments.len(),
100+
min: 2,
101+
max: 2,
102+
})
103+
})?;
104+
Self::Env(key, value)
105+
}
90106
AttributeDiscriminant::ExitMessage => Self::ExitMessage,
91107
AttributeDiscriminant::Extension => Self::Extension(arguments.into_iter().next().unwrap()),
92108
AttributeDiscriminant::Group => Self::Group(arguments.into_iter().next().unwrap()),
@@ -124,7 +140,10 @@ impl<'src> Attribute<'src> {
124140
}
125141

126142
pub(crate) fn repeatable(&self) -> bool {
127-
matches!(self, Attribute::Group(_) | Attribute::Metadata(_))
143+
matches!(
144+
self,
145+
Attribute::Env(_, _) | Attribute::Group(_) | Attribute::Metadata(_)
146+
)
128147
}
129148
}
130149

@@ -154,6 +173,7 @@ impl Display for Attribute<'_> {
154173
| Self::Extension(argument)
155174
| Self::Group(argument)
156175
| Self::WorkingDirectory(argument) => write!(f, "({argument})")?,
176+
Self::Env(key, value) => write!(f, "({key}, {value})")?,
157177
Self::Metadata(arguments) => {
158178
write!(f, "(")?;
159179
for (i, argument) in arguments.iter().enumerate() {

src/recipe.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,12 @@ impl<'src, D> Recipe<'src, D> {
314314
cmd.stdout(Stdio::null());
315315
}
316316

317+
for attribute in &self.attributes {
318+
if let Attribute::Env(key, value) = attribute {
319+
cmd.env(&key.cooked, &value.cooked);
320+
}
321+
}
322+
317323
cmd.export(
318324
&context.module.settings,
319325
context.dotenv,
@@ -445,6 +451,12 @@ impl<'src, D> Recipe<'src, D> {
445451
command.args(positional);
446452
}
447453

454+
for attribute in &self.attributes {
455+
if let Attribute::Env(key, value) = attribute {
456+
command.env(&key.cooked, &value.cooked);
457+
}
458+
}
459+
448460
command.export(
449461
&context.module.settings,
450462
context.dotenv,

tests/attributes.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,3 +311,80 @@ fn duplicate_non_repeatable_attributes_are_forbidden() {
311311
.status(EXIT_FAILURE)
312312
.run();
313313
}
314+
315+
#[test]
316+
fn env_attribute_single() {
317+
Test::new()
318+
.justfile(
319+
"
320+
[env('MY_VAR', 'my_value')]
321+
foo:
322+
echo $MY_VAR
323+
",
324+
)
325+
.stdout("my_value\n")
326+
.stderr("echo $MY_VAR\n")
327+
.run();
328+
}
329+
330+
#[test]
331+
fn env_attribute_multiple() {
332+
Test::new()
333+
.justfile(
334+
"
335+
[env('VAR1', 'value1')]
336+
[env('VAR2', 'value 2')]
337+
foo:
338+
echo $VAR1 $VAR2
339+
",
340+
)
341+
.stdout("value1 value 2\n")
342+
.stderr("echo $VAR1 $VAR2\n")
343+
.run();
344+
}
345+
346+
#[test]
347+
fn env_attribute_1_arg() {
348+
Test::new()
349+
.justfile(
350+
"
351+
[env('MY_VAR')]
352+
foo:
353+
echo bar
354+
",
355+
)
356+
.stderr(
357+
"
358+
error: Attribute `env` got 1 argument but takes 2 arguments
359+
——▶ justfile:1:2
360+
361+
1 │ [env('MY_VAR')]
362+
│ ^^^
363+
",
364+
)
365+
.status(EXIT_FAILURE)
366+
.run();
367+
}
368+
369+
#[test]
370+
fn env_attribute_3_args() {
371+
Test::new()
372+
.justfile(
373+
"
374+
[env('A', 'B', 'C')]
375+
foo:
376+
echo bar
377+
",
378+
)
379+
.stderr(
380+
"
381+
error: Attribute `env` got 3 arguments but takes 2 arguments
382+
——▶ justfile:1:2
383+
384+
1 │ [env('A', 'B', 'C')]
385+
│ ^^^
386+
",
387+
)
388+
.status(EXIT_FAILURE)
389+
.run();
390+
}

0 commit comments

Comments
 (0)