Skip to content
Open
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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2112,6 +2112,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>1.44</sup> | recipe | Set env vars that apply only to this 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 in [group](#groups) `NAME`. |
| `[linux]`<sup>1.8.0</sup> | recipe | Enable recipe on Linux. |
Expand Down Expand Up @@ -2494,6 +2495,17 @@ test $RUST_BACKTRACE="1":
cargo test
```

Or you can use the `[env(env_var, value)]` attribute to set environment variables that
apply only to this 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
22 changes: 21 additions & 1 deletion src/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,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 @@ -49,6 +50,7 @@ impl AttributeDiscriminant {
| Self::Unix
| Self::Windows => 0..=0,
Self::Extension | Self::Group | Self::WorkingDirectory => 1..=1,
Self::Env => 2..=2,
Self::Metadata => 1..=usize::MAX,
Self::Script => 0..=usize::MAX,
}
Expand Down Expand Up @@ -87,6 +89,20 @@ 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()
.map_err(|arguments: Vec<StringLiteral>| {
name.error(CompileErrorKind::AttributeArgumentCountMismatch {
attribute: name.lexeme(),
found: arguments.len(),
min: 2,
max: 2,
})
})?;
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 @@ -124,7 +140,10 @@ impl<'src> Attribute<'src> {
}

pub(crate) fn repeatable(&self) -> bool {
matches!(self, Attribute::Group(_) | Attribute::Metadata(_))
matches!(
self,
Attribute::Env(_, _) | Attribute::Group(_) | Attribute::Metadata(_)
)
}
}

Expand Down Expand Up @@ -154,6 +173,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
12 changes: 12 additions & 0 deletions src/recipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,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 @@ -445,6 +451,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
77 changes: 77 additions & 0 deletions tests/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,3 +311,80 @@ fn duplicate_non_repeatable_attributes_are_forbidden() {
.status(EXIT_FAILURE)
.run();
}

#[test]
fn env_attribute_single() {
Test::new()
.justfile(
"
[env('MY_VAR', 'my_value')]
foo:
echo $MY_VAR
",
)
.stdout("my_value\n")
.stderr("echo $MY_VAR\n")
.run();
}

#[test]
fn env_attribute_multiple() {
Test::new()
.justfile(
"
[env('VAR1', 'value1')]
[env('VAR2', 'value 2')]
foo:
echo $VAR1 $VAR2
",
)
.stdout("value1 value 2\n")
.stderr("echo $VAR1 $VAR2\n")
.run();
}

#[test]
fn env_attribute_1_arg() {
Test::new()
.justfile(
"
[env('MY_VAR')]
foo:
echo bar
",
)
.stderr(
"
error: Attribute `env` got 1 argument but takes 2 arguments
——▶ justfile:1:2
1 │ [env('MY_VAR')]
│ ^^^
",
)
.status(EXIT_FAILURE)
.run();
}

#[test]
fn env_attribute_3_args() {
Test::new()
.justfile(
"
[env('A', 'B', 'C')]
foo:
echo bar
",
)
.stderr(
"
error: Attribute `env` got 3 arguments but takes 2 arguments
——▶ justfile:1:2
1 │ [env('A', 'B', 'C')]
│ ^^^
",
)
.status(EXIT_FAILURE)
.run();
}