Add set export
to export all variables as environment variables (#767)
Add a setting that exports all variables by default, regardless of whether they use the `export` keyword. This includes assignments as well as parameters. Just does dependency analysis of variable uses, allowing variables to be used out of order in assignments, as long as there are no circular dependencies. However, use of environment variable is not known to Just, so exported variables are only exported to child scopes, to avoid ordering dependencies, since dependency analysis cannot be done.
This commit is contained in:
parent
86c2e52dc6
commit
b66a979c08
@ -55,7 +55,8 @@ assignment : NAME ':=' expression eol
|
|||||||
|
|
||||||
export : 'export' assignment
|
export : 'export' assignment
|
||||||
|
|
||||||
setting : 'set' 'shell' ':=' '[' string (',' string)* ','? ']'
|
setting : 'set' 'export'
|
||||||
|
| 'set' 'shell' ':=' '[' string (',' string)* ','? ']'
|
||||||
|
|
||||||
expression : 'if' condition '{' expression '}' else '{' expression '}'
|
expression : 'if' condition '{' expression '}' else '{' expression '}'
|
||||||
| value '+' expression
|
| value '+' expression
|
||||||
|
21
README.adoc
21
README.adoc
@ -388,6 +388,7 @@ foo:
|
|||||||
[options="header"]
|
[options="header"]
|
||||||
|=================
|
|=================
|
||||||
| Name | Value | Description
|
| Name | Value | Description
|
||||||
|
| `export` | | Export all variables as environment variables
|
||||||
|`shell` | `[COMMAND, ARGS...]` | Set the command used to invoke recipes and evaluate backticks.
|
|`shell` | `[COMMAND, ARGS...]` | Set the command used to invoke recipes and evaluate backticks.
|
||||||
|=================
|
|=================
|
||||||
|
|
||||||
@ -407,6 +408,26 @@ foo:
|
|||||||
print("{{foos}}")
|
print("{{foos}}")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
==== Export
|
||||||
|
|
||||||
|
The `export` setting causes all Just variables to be exported as environment variables.
|
||||||
|
|
||||||
|
```make
|
||||||
|
set export
|
||||||
|
|
||||||
|
a := "hello"
|
||||||
|
|
||||||
|
@foo b:
|
||||||
|
echo $a
|
||||||
|
echo $b
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ just foo goodbye
|
||||||
|
hello
|
||||||
|
goodbye
|
||||||
|
```
|
||||||
|
|
||||||
=== Documentation Comments
|
=== Documentation Comments
|
||||||
|
|
||||||
Comments immediately preceding a recipe will appear in `just --list`:
|
Comments immediately preceding a recipe will appear in `just --list`:
|
||||||
|
@ -69,6 +69,9 @@ impl<'src> Analyzer<'src> {
|
|||||||
assert!(settings.shell.is_none());
|
assert!(settings.shell.is_none());
|
||||||
settings.shell = Some(shell);
|
settings.shell = Some(shell);
|
||||||
},
|
},
|
||||||
|
Setting::Export => {
|
||||||
|
settings.export = true;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,29 +1,29 @@
|
|||||||
use crate::common::*;
|
use crate::common::*;
|
||||||
|
|
||||||
pub(crate) trait CommandExt {
|
pub(crate) trait CommandExt {
|
||||||
fn export(&mut self, dotenv: &BTreeMap<String, String>, scope: &Scope);
|
fn export(&mut self, settings: &Settings, dotenv: &BTreeMap<String, String>, scope: &Scope);
|
||||||
|
|
||||||
fn export_scope(&mut self, scope: &Scope);
|
fn export_scope(&mut self, settings: &Settings, scope: &Scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommandExt for Command {
|
impl CommandExt for Command {
|
||||||
fn export(&mut self, dotenv: &BTreeMap<String, String>, scope: &Scope) {
|
fn export(&mut self, settings: &Settings, dotenv: &BTreeMap<String, String>, scope: &Scope) {
|
||||||
for (name, value) in dotenv {
|
for (name, value) in dotenv {
|
||||||
self.env(name, value);
|
self.env(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(parent) = scope.parent() {
|
if let Some(parent) = scope.parent() {
|
||||||
self.export_scope(parent);
|
self.export_scope(settings, parent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn export_scope(&mut self, scope: &Scope) {
|
fn export_scope(&mut self, settings: &Settings, scope: &Scope) {
|
||||||
if let Some(parent) = scope.parent() {
|
if let Some(parent) = scope.parent() {
|
||||||
self.export_scope(parent);
|
self.export_scope(settings, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
for binding in scope.bindings() {
|
for binding in scope.bindings() {
|
||||||
if binding.export {
|
if settings.export || binding.export {
|
||||||
self.env(binding.name.lexeme(), &binding.value);
|
self.env(binding.name.lexeme(), &binding.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,7 +143,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
|
|||||||
|
|
||||||
cmd.current_dir(&self.search.working_directory);
|
cmd.current_dir(&self.search.working_directory);
|
||||||
|
|
||||||
cmd.export(self.dotenv, &self.scope);
|
cmd.export(self.settings, self.dotenv, &self.scope);
|
||||||
|
|
||||||
cmd.stdin(process::Stdio::inherit());
|
cmd.stdin(process::Stdio::inherit());
|
||||||
|
|
||||||
@ -197,14 +197,14 @@ impl<'src, 'run> Evaluator<'src, 'run> {
|
|||||||
) -> RunResult<'src, Scope<'src, 'run>> {
|
) -> RunResult<'src, Scope<'src, 'run>> {
|
||||||
let mut evaluator = Evaluator {
|
let mut evaluator = Evaluator {
|
||||||
assignments: None,
|
assignments: None,
|
||||||
scope: Scope::child(scope),
|
scope: scope.child(),
|
||||||
search,
|
search,
|
||||||
settings,
|
settings,
|
||||||
dotenv,
|
dotenv,
|
||||||
config,
|
config,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut scope = Scope::child(scope);
|
let mut scope = scope.child();
|
||||||
|
|
||||||
let mut rest = arguments;
|
let mut rest = arguments;
|
||||||
for parameter in parameters {
|
for parameter in parameters {
|
||||||
|
@ -221,7 +221,7 @@ impl<'src> Justfile<'src> {
|
|||||||
search: &'run Search,
|
search: &'run Search,
|
||||||
ran: &mut BTreeSet<Vec<String>>,
|
ran: &mut BTreeSet<Vec<String>>,
|
||||||
) -> RunResult<'src, ()> {
|
) -> RunResult<'src, ()> {
|
||||||
let scope = Evaluator::evaluate_parameters(
|
let outer = Evaluator::evaluate_parameters(
|
||||||
context.config,
|
context.config,
|
||||||
dotenv,
|
dotenv,
|
||||||
&recipe.parameters,
|
&recipe.parameters,
|
||||||
@ -231,6 +231,8 @@ impl<'src> Justfile<'src> {
|
|||||||
search,
|
search,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
let scope = outer.child();
|
||||||
|
|
||||||
let mut evaluator =
|
let mut evaluator =
|
||||||
Evaluator::recipe_evaluator(context.config, dotenv, &scope, context.settings, search);
|
Evaluator::recipe_evaluator(context.config, dotenv, &scope, context.settings, search);
|
||||||
|
|
||||||
@ -251,7 +253,7 @@ impl<'src> Justfile<'src> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
recipe.run(context, dotenv, scope, search)?;
|
recipe.run(context, dotenv, scope.child(), search)?;
|
||||||
|
|
||||||
let mut invocation = vec![recipe.name().to_owned()];
|
let mut invocation = vec![recipe.name().to_owned()];
|
||||||
for argument in arguments.iter().cloned() {
|
for argument in arguments.iter().cloned() {
|
||||||
|
@ -197,6 +197,7 @@ impl<'src> Node<'src> for Set<'src> {
|
|||||||
set.push_mut(Tree::string(&argument.cooked));
|
set.push_mut(Tree::string(&argument.cooked));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Export => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
set
|
set
|
||||||
|
@ -345,7 +345,9 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
|
|||||||
items.push(Item::Recipe(self.parse_recipe(doc, false)?));
|
items.push(Item::Recipe(self.parse_recipe(doc, false)?));
|
||||||
},
|
},
|
||||||
Some(Keyword::Set) =>
|
Some(Keyword::Set) =>
|
||||||
if self.next_are(&[Identifier, Identifier, ColonEquals]) {
|
if self.next_are(&[Identifier, Identifier, ColonEquals])
|
||||||
|
|| self.next_are(&[Identifier, Identifier, Eol])
|
||||||
|
{
|
||||||
items.push(Item::Set(self.parse_set()?));
|
items.push(Item::Set(self.parse_set()?));
|
||||||
} else {
|
} else {
|
||||||
items.push(Item::Recipe(self.parse_recipe(doc, false)?));
|
items.push(Item::Recipe(self.parse_recipe(doc, false)?));
|
||||||
@ -677,6 +679,14 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
|
|||||||
fn parse_set(&mut self) -> CompilationResult<'src, Set<'src>> {
|
fn parse_set(&mut self) -> CompilationResult<'src, Set<'src>> {
|
||||||
self.presume_keyword(Keyword::Set)?;
|
self.presume_keyword(Keyword::Set)?;
|
||||||
let name = Name::from_identifier(self.presume(Identifier)?);
|
let name = Name::from_identifier(self.presume(Identifier)?);
|
||||||
|
|
||||||
|
if name.lexeme() == Keyword::Export.lexeme() {
|
||||||
|
return Ok(Set {
|
||||||
|
value: Setting::Export,
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
self.presume(ColonEquals)?;
|
self.presume(ColonEquals)?;
|
||||||
if name.lexeme() == Keyword::Shell.lexeme() {
|
if name.lexeme() == Keyword::Shell.lexeme() {
|
||||||
self.expect(BracketL)?;
|
self.expect(BracketL)?;
|
||||||
|
@ -176,7 +176,7 @@ impl<'src, D> Recipe<'src, D> {
|
|||||||
output_error,
|
output_error,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
command.export(dotenv, &scope);
|
command.export(context.settings, dotenv, &scope);
|
||||||
|
|
||||||
// run it!
|
// run it!
|
||||||
match InterruptHandler::guard(|| command.status()) {
|
match InterruptHandler::guard(|| command.status()) {
|
||||||
@ -265,7 +265,7 @@ impl<'src, D> Recipe<'src, D> {
|
|||||||
cmd.stdout(Stdio::null());
|
cmd.stdout(Stdio::null());
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.export(dotenv, &scope);
|
cmd.export(context.settings, dotenv, &scope);
|
||||||
|
|
||||||
match InterruptHandler::guard(|| cmd.status()) {
|
match InterruptHandler::guard(|| cmd.status()) {
|
||||||
Ok(exit_status) =>
|
Ok(exit_status) =>
|
||||||
|
@ -7,9 +7,9 @@ pub(crate) struct Scope<'src: 'run, 'run> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'src, 'run> Scope<'src, 'run> {
|
impl<'src, 'run> Scope<'src, 'run> {
|
||||||
pub(crate) fn child(parent: &'run Scope<'src, 'run>) -> Scope<'src, 'run> {
|
pub(crate) fn child(&'run self) -> Scope<'src, 'run> {
|
||||||
Scope {
|
Scope {
|
||||||
parent: Some(parent),
|
parent: Some(self),
|
||||||
bindings: Table::new(),
|
bindings: Table::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ use crate::common::*;
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum Setting<'src> {
|
pub(crate) enum Setting<'src> {
|
||||||
Shell(Shell<'src>),
|
Shell(Shell<'src>),
|
||||||
|
Export,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
|
@ -2,12 +2,16 @@ use crate::common::*;
|
|||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub(crate) struct Settings<'src> {
|
pub(crate) struct Settings<'src> {
|
||||||
pub(crate) shell: Option<setting::Shell<'src>>,
|
pub(crate) shell: Option<setting::Shell<'src>>,
|
||||||
|
pub(crate) export: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'src> Settings<'src> {
|
impl<'src> Settings<'src> {
|
||||||
pub(crate) fn new() -> Settings<'src> {
|
pub(crate) fn new() -> Settings<'src> {
|
||||||
Settings { shell: None }
|
Settings {
|
||||||
|
shell: None,
|
||||||
|
export: false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn shell_command(&self, config: &Config) -> Command {
|
pub(crate) fn shell_command(&self, config: &Config) -> Command {
|
||||||
|
122
tests/export.rs
Normal file
122
tests/export.rs
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
test! {
|
||||||
|
name: success,
|
||||||
|
justfile: r#"
|
||||||
|
export FOO := "a"
|
||||||
|
baz := "c"
|
||||||
|
export BAR := "b"
|
||||||
|
export ABC := FOO + BAR + baz
|
||||||
|
|
||||||
|
wut:
|
||||||
|
echo $FOO $BAR $ABC
|
||||||
|
"#,
|
||||||
|
stdout: "a b abc\n",
|
||||||
|
stderr: "echo $FOO $BAR $ABC\n",
|
||||||
|
}
|
||||||
|
|
||||||
|
test! {
|
||||||
|
name: override_variable,
|
||||||
|
justfile: r#"
|
||||||
|
export FOO := "a"
|
||||||
|
baz := "c"
|
||||||
|
export BAR := "b"
|
||||||
|
export ABC := FOO + "-" + BAR + "-" + baz
|
||||||
|
|
||||||
|
wut:
|
||||||
|
echo $FOO $BAR $ABC
|
||||||
|
"#,
|
||||||
|
args: ("--set", "BAR", "bye", "FOO=hello"),
|
||||||
|
stdout: "hello bye hello-bye-c\n",
|
||||||
|
stderr: "echo $FOO $BAR $ABC\n",
|
||||||
|
}
|
||||||
|
|
||||||
|
test! {
|
||||||
|
name: shebang,
|
||||||
|
justfile: r#"
|
||||||
|
export FOO := "a"
|
||||||
|
baz := "c"
|
||||||
|
export BAR := "b"
|
||||||
|
export ABC := FOO + BAR + baz
|
||||||
|
|
||||||
|
wut:
|
||||||
|
#!/bin/sh
|
||||||
|
echo $FOO $BAR $ABC
|
||||||
|
"#,
|
||||||
|
stdout: "a b abc\n",
|
||||||
|
}
|
||||||
|
|
||||||
|
test! {
|
||||||
|
name: recipe_backtick,
|
||||||
|
justfile: r#"
|
||||||
|
export EXPORTED_VARIABLE := "A-IS-A"
|
||||||
|
|
||||||
|
recipe:
|
||||||
|
echo {{`echo recipe $EXPORTED_VARIABLE`}}
|
||||||
|
"#,
|
||||||
|
stdout: "recipe A-IS-A\n",
|
||||||
|
stderr: "echo recipe A-IS-A\n",
|
||||||
|
}
|
||||||
|
|
||||||
|
test! {
|
||||||
|
name: setting,
|
||||||
|
justfile: "
|
||||||
|
set export
|
||||||
|
|
||||||
|
A := 'hello'
|
||||||
|
|
||||||
|
foo B C=`echo $A`:
|
||||||
|
echo $A
|
||||||
|
echo $B
|
||||||
|
echo $C
|
||||||
|
",
|
||||||
|
args: ("foo", "goodbye"),
|
||||||
|
stdout: "hello\ngoodbye\nhello\n",
|
||||||
|
stderr: "echo $A\necho $B\necho $C\n",
|
||||||
|
}
|
||||||
|
|
||||||
|
test! {
|
||||||
|
name: setting_shebang,
|
||||||
|
justfile: "
|
||||||
|
set export
|
||||||
|
|
||||||
|
A := 'hello'
|
||||||
|
|
||||||
|
foo B:
|
||||||
|
#!/bin/sh
|
||||||
|
echo $A
|
||||||
|
echo $B
|
||||||
|
",
|
||||||
|
args: ("foo", "goodbye"),
|
||||||
|
stdout: "hello\ngoodbye\n",
|
||||||
|
stderr: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
test! {
|
||||||
|
name: setting_override_undefined,
|
||||||
|
justfile: r#"
|
||||||
|
set export
|
||||||
|
|
||||||
|
A := 'hello'
|
||||||
|
B := `if [ -n "${A+1}" ]; then echo defined; else echo undefined; fi`
|
||||||
|
|
||||||
|
foo C='goodbye' D=`if [ -n "${C+1}" ]; then echo defined; else echo undefined; fi`:
|
||||||
|
echo $B
|
||||||
|
echo $D
|
||||||
|
"#,
|
||||||
|
args: ("A=zzz", "foo"),
|
||||||
|
stdout: "undefined\nundefined\n",
|
||||||
|
stderr: "echo $B\necho $D\n",
|
||||||
|
}
|
||||||
|
|
||||||
|
test! {
|
||||||
|
name: setting_variable_not_visible,
|
||||||
|
justfile: r#"
|
||||||
|
export A := 'hello'
|
||||||
|
export B := `if [ -n "${A+1}" ]; then echo defined; else echo undefined; fi`
|
||||||
|
|
||||||
|
foo:
|
||||||
|
echo $B
|
||||||
|
"#,
|
||||||
|
args: ("A=zzz"),
|
||||||
|
stdout: "undefined\n",
|
||||||
|
stderr: "echo $B\n",
|
||||||
|
}
|
@ -11,6 +11,7 @@ mod dotenv;
|
|||||||
mod edit;
|
mod edit;
|
||||||
mod error_messages;
|
mod error_messages;
|
||||||
mod examples;
|
mod examples;
|
||||||
|
mod export;
|
||||||
mod init;
|
mod init;
|
||||||
mod interrupts;
|
mod interrupts;
|
||||||
mod invocation_directory;
|
mod invocation_directory;
|
||||||
|
@ -574,64 +574,6 @@ hello := "c"
|
|||||||
"#,
|
"#,
|
||||||
}
|
}
|
||||||
|
|
||||||
test! {
|
|
||||||
name: export_success,
|
|
||||||
justfile: r#"
|
|
||||||
export FOO := "a"
|
|
||||||
baz := "c"
|
|
||||||
export BAR := "b"
|
|
||||||
export ABC := FOO + BAR + baz
|
|
||||||
|
|
||||||
wut:
|
|
||||||
echo $FOO $BAR $ABC
|
|
||||||
"#,
|
|
||||||
stdout: "a b abc\n",
|
|
||||||
stderr: "echo $FOO $BAR $ABC\n",
|
|
||||||
}
|
|
||||||
|
|
||||||
test! {
|
|
||||||
name: export_override,
|
|
||||||
justfile: r#"
|
|
||||||
export FOO := "a"
|
|
||||||
baz := "c"
|
|
||||||
export BAR := "b"
|
|
||||||
export ABC := FOO + "-" + BAR + "-" + baz
|
|
||||||
|
|
||||||
wut:
|
|
||||||
echo $FOO $BAR $ABC
|
|
||||||
"#,
|
|
||||||
args: ("--set", "BAR", "bye", "FOO=hello"),
|
|
||||||
stdout: "hello bye hello-bye-c\n",
|
|
||||||
stderr: "echo $FOO $BAR $ABC\n",
|
|
||||||
}
|
|
||||||
|
|
||||||
test! {
|
|
||||||
name: export_shebang,
|
|
||||||
justfile: r#"
|
|
||||||
export FOO := "a"
|
|
||||||
baz := "c"
|
|
||||||
export BAR := "b"
|
|
||||||
export ABC := FOO + BAR + baz
|
|
||||||
|
|
||||||
wut:
|
|
||||||
#!/bin/sh
|
|
||||||
echo $FOO $BAR $ABC
|
|
||||||
"#,
|
|
||||||
stdout: "a b abc\n",
|
|
||||||
}
|
|
||||||
|
|
||||||
test! {
|
|
||||||
name: export_recipe_backtick,
|
|
||||||
justfile: r#"
|
|
||||||
export EXPORTED_VARIABLE := "A-IS-A"
|
|
||||||
|
|
||||||
recipe:
|
|
||||||
echo {{`echo recipe $EXPORTED_VARIABLE`}}
|
|
||||||
"#,
|
|
||||||
stdout: "recipe A-IS-A\n",
|
|
||||||
stderr: "echo recipe A-IS-A\n",
|
|
||||||
}
|
|
||||||
|
|
||||||
test! {
|
test! {
|
||||||
name: raw_string,
|
name: raw_string,
|
||||||
justfile: r#"
|
justfile: r#"
|
||||||
|
Loading…
Reference in New Issue
Block a user