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
|
||||
|
||||
setting : 'set' 'shell' ':=' '[' string (',' string)* ','? ']'
|
||||
setting : 'set' 'export'
|
||||
| 'set' 'shell' ':=' '[' string (',' string)* ','? ']'
|
||||
|
||||
expression : 'if' condition '{' expression '}' else '{' expression '}'
|
||||
| value '+' expression
|
||||
|
21
README.adoc
21
README.adoc
@ -388,6 +388,7 @@ foo:
|
||||
[options="header"]
|
||||
|=================
|
||||
| Name | Value | Description
|
||||
| `export` | | Export all variables as environment variables
|
||||
|`shell` | `[COMMAND, ARGS...]` | Set the command used to invoke recipes and evaluate backticks.
|
||||
|=================
|
||||
|
||||
@ -407,6 +408,26 @@ foo:
|
||||
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
|
||||
|
||||
Comments immediately preceding a recipe will appear in `just --list`:
|
||||
|
@ -69,6 +69,9 @@ impl<'src> Analyzer<'src> {
|
||||
assert!(settings.shell.is_none());
|
||||
settings.shell = Some(shell);
|
||||
},
|
||||
Setting::Export => {
|
||||
settings.export = true;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,29 +1,29 @@
|
||||
use crate::common::*;
|
||||
|
||||
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 {
|
||||
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 {
|
||||
self.env(name, value);
|
||||
}
|
||||
|
||||
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() {
|
||||
self.export_scope(parent);
|
||||
self.export_scope(settings, parent);
|
||||
}
|
||||
|
||||
for binding in scope.bindings() {
|
||||
if binding.export {
|
||||
if settings.export || binding.export {
|
||||
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.export(self.dotenv, &self.scope);
|
||||
cmd.export(self.settings, self.dotenv, &self.scope);
|
||||
|
||||
cmd.stdin(process::Stdio::inherit());
|
||||
|
||||
@ -197,14 +197,14 @@ impl<'src, 'run> Evaluator<'src, 'run> {
|
||||
) -> RunResult<'src, Scope<'src, 'run>> {
|
||||
let mut evaluator = Evaluator {
|
||||
assignments: None,
|
||||
scope: Scope::child(scope),
|
||||
scope: scope.child(),
|
||||
search,
|
||||
settings,
|
||||
dotenv,
|
||||
config,
|
||||
};
|
||||
|
||||
let mut scope = Scope::child(scope);
|
||||
let mut scope = scope.child();
|
||||
|
||||
let mut rest = arguments;
|
||||
for parameter in parameters {
|
||||
|
@ -221,7 +221,7 @@ impl<'src> Justfile<'src> {
|
||||
search: &'run Search,
|
||||
ran: &mut BTreeSet<Vec<String>>,
|
||||
) -> RunResult<'src, ()> {
|
||||
let scope = Evaluator::evaluate_parameters(
|
||||
let outer = Evaluator::evaluate_parameters(
|
||||
context.config,
|
||||
dotenv,
|
||||
&recipe.parameters,
|
||||
@ -231,6 +231,8 @@ impl<'src> Justfile<'src> {
|
||||
search,
|
||||
)?;
|
||||
|
||||
let scope = outer.child();
|
||||
|
||||
let mut evaluator =
|
||||
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()];
|
||||
for argument in arguments.iter().cloned() {
|
||||
|
@ -197,6 +197,7 @@ impl<'src> Node<'src> for Set<'src> {
|
||||
set.push_mut(Tree::string(&argument.cooked));
|
||||
}
|
||||
},
|
||||
Export => {},
|
||||
}
|
||||
|
||||
set
|
||||
|
@ -345,7 +345,9 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
|
||||
items.push(Item::Recipe(self.parse_recipe(doc, false)?));
|
||||
},
|
||||
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()?));
|
||||
} else {
|
||||
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>> {
|
||||
self.presume_keyword(Keyword::Set)?;
|
||||
let name = Name::from_identifier(self.presume(Identifier)?);
|
||||
|
||||
if name.lexeme() == Keyword::Export.lexeme() {
|
||||
return Ok(Set {
|
||||
value: Setting::Export,
|
||||
name,
|
||||
});
|
||||
}
|
||||
|
||||
self.presume(ColonEquals)?;
|
||||
if name.lexeme() == Keyword::Shell.lexeme() {
|
||||
self.expect(BracketL)?;
|
||||
|
@ -176,7 +176,7 @@ impl<'src, D> Recipe<'src, D> {
|
||||
output_error,
|
||||
})?;
|
||||
|
||||
command.export(dotenv, &scope);
|
||||
command.export(context.settings, dotenv, &scope);
|
||||
|
||||
// run it!
|
||||
match InterruptHandler::guard(|| command.status()) {
|
||||
@ -265,7 +265,7 @@ impl<'src, D> Recipe<'src, D> {
|
||||
cmd.stdout(Stdio::null());
|
||||
}
|
||||
|
||||
cmd.export(dotenv, &scope);
|
||||
cmd.export(context.settings, dotenv, &scope);
|
||||
|
||||
match InterruptHandler::guard(|| cmd.status()) {
|
||||
Ok(exit_status) =>
|
||||
|
@ -7,9 +7,9 @@ pub(crate) struct Scope<'src: 'run, '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 {
|
||||
parent: Some(parent),
|
||||
parent: Some(self),
|
||||
bindings: Table::new(),
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ use crate::common::*;
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Setting<'src> {
|
||||
Shell(Shell<'src>),
|
||||
Export,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
|
@ -2,12 +2,16 @@ use crate::common::*;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
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> {
|
||||
pub(crate) fn new() -> Settings<'src> {
|
||||
Settings { shell: None }
|
||||
Settings {
|
||||
shell: None,
|
||||
export: false,
|
||||
}
|
||||
}
|
||||
|
||||
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 error_messages;
|
||||
mod examples;
|
||||
mod export;
|
||||
mod init;
|
||||
mod interrupts;
|
||||
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! {
|
||||
name: raw_string,
|
||||
justfile: r#"
|
||||
|
Loading…
Reference in New Issue
Block a user