diff --git a/README.adoc b/README.adoc index e32b864575..2aeb392a1d 100644 --- a/README.adoc +++ b/README.adoc @@ -757,6 +757,14 @@ test: cargo test ``` +Parameters prefixed with a `$` will be exported as environment variables: + +```make +test $RUST_BACKTRACE="1": + # will print a stack trace if it crashes + cargo test +``` + === Recipe Parameters Recipes may have parameters. Here recipe `build` has a parameter called `target`: @@ -872,6 +880,13 @@ search QUERY: lynx 'https://www.google.com/?q={{QUERY}}' ``` +Parameters prefixed with a `$` will be exported as environment variables: + +```make +foo $bar: + echo $bar +``` + === Running recipes at the end of a recipe Dependencies of a recipes always run before a recipe starts. That is to say, the dependee always runs before the depender. diff --git a/src/evaluator.rs b/src/evaluator.rs index 35bfd08ec4..39f4d6ac60 100644 --- a/src/evaluator.rs +++ b/src/evaluator.rs @@ -227,7 +227,7 @@ impl<'src, 'run> Evaluator<'src, 'run> { rest = &rest[1..]; value }; - scope.bind(false, parameter.name, value); + scope.bind(parameter.export, parameter.name, value); } Ok(scope) diff --git a/src/lexer.rs b/src/lexer.rs index 5857e1fabf..bbec4bb54d 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -464,6 +464,7 @@ impl<'src> Lexer<'src> { match start { '!' => self.lex_bang(), '*' => self.lex_single(Asterisk), + '$' => self.lex_single(Dollar), '@' => self.lex_single(At), '[' => self.lex_delimiter(BracketL), ']' => self.lex_delimiter(BracketR), @@ -912,6 +913,7 @@ mod tests { Colon => ":", ColonEquals => ":=", Comma => ",", + Dollar => "$", Eol => "\n", Equals => "=", EqualsEquals => "==", @@ -1048,6 +1050,12 @@ mod tests { tokens: (BraceL, BraceL, BraceL, BraceR, BraceR, BraceR), } + test! { + name: dollar, + text: "$", + tokens: (Dollar), + } + test! { name: export_concatination, text: "export foo = 'foo' + 'bar'", diff --git a/src/parameter.rs b/src/parameter.rs index b140f8b59f..1806eed439 100644 --- a/src/parameter.rs +++ b/src/parameter.rs @@ -9,6 +9,8 @@ pub(crate) struct Parameter<'src> { pub(crate) kind: ParameterKind, /// An optional default expression pub(crate) default: Option>, + /// Export parameter as environment variable + pub(crate) export: bool, } impl<'src> Display for Parameter<'src> { diff --git a/src/parser.rs b/src/parser.rs index 75c0aceae8..f6e503d309 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -568,7 +568,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> { let mut positional = Vec::new(); - while self.next_is(Identifier) { + while self.next_is(Identifier) || self.next_is(Dollar) { positional.push(self.parse_parameter(ParameterKind::Singular)?); } @@ -620,6 +620,8 @@ impl<'tokens, 'src> Parser<'tokens, 'src> { /// Parse a recipe parameter fn parse_parameter(&mut self, kind: ParameterKind) -> CompilationResult<'src, Parameter<'src>> { + let export = self.accepted(Dollar)?; + let name = self.parse_name()?; let default = if self.accepted(Equals)? { @@ -632,6 +634,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> { name, kind, default, + export, }) } @@ -1643,7 +1646,10 @@ mod tests { line: 0, column: 5, width: 1, - kind: UnexpectedToken{expected: vec![Asterisk, Colon, Equals, Identifier, Plus], found: Eol}, + kind: UnexpectedToken{ + expected: vec![Asterisk, Colon, Dollar, Equals, Identifier, Plus], + found: Eol + }, } error! { @@ -1728,7 +1734,7 @@ mod tests { line: 0, column: 6, width: 1, - kind: UnexpectedToken{expected: vec![Identifier], found: Colon}, + kind: UnexpectedToken{expected: vec![Dollar, Identifier], found: Colon}, } error! { @@ -1748,7 +1754,10 @@ mod tests { line: 0, column: 8, width: 0, - kind: UnexpectedToken{expected: vec![Asterisk, Colon, Equals, Identifier, Plus], found: Eof}, + kind: UnexpectedToken { + expected: vec![Asterisk, Colon, Dollar, Equals, Identifier, Plus], + found: Eof + }, } error! { diff --git a/src/token_kind.rs b/src/token_kind.rs index ce225aee08..42564d9175 100644 --- a/src/token_kind.rs +++ b/src/token_kind.rs @@ -15,6 +15,7 @@ pub(crate) enum TokenKind { Comma, Comment, Dedent, + Dollar, Eof, Eol, Equals, @@ -50,6 +51,7 @@ impl Display for TokenKind { Comma => "','", Comment => "comment", Dedent => "dedent", + Dollar => "'$'", Eof => "end of file", Eol => "end of line", Equals => "'='", diff --git a/tests/export.rs b/tests/export.rs index ab5c301142..f9ad42e543 100644 --- a/tests/export.rs +++ b/tests/export.rs @@ -13,6 +13,30 @@ wut: stderr: "echo $FOO $BAR $ABC\n", } +test! { + name: parameter, + justfile: r#" + wut $FOO='a' BAR='b': + echo $FOO + echo {{BAR}} + if [ -n "${BAR+1}" ]; then echo defined; else echo undefined; fi + "#, + stdout: "a\nb\nundefined\n", + stderr: "echo $FOO\necho b\nif [ -n \"${BAR+1}\" ]; then echo defined; else echo undefined; fi\n", +} + +test! { + name: parameter_not_visible_to_backtick, + justfile: r#" + wut $FOO BAR=`if [ -n "${FOO+1}" ]; then echo defined; else echo undefined; fi`: + echo $FOO + echo {{BAR}} + "#, + args: ("wut", "bar"), + stdout: "bar\nundefined\n", + stderr: "echo $FOO\necho undefined\n", +} + test! { name: override_variable, justfile: r#" diff --git a/tests/misc.rs b/tests/misc.rs index 4280a1a81f..012fdc48df 100644 --- a/tests/misc.rs +++ b/tests/misc.rs @@ -1422,7 +1422,7 @@ test! { justfile: "foo 'bar'", args: ("foo"), stdout: "", - stderr: "error: Expected '*', ':', identifier, or '+', but found raw string + stderr: "error: Expected '*', ':', '$', identifier, or '+', but found raw string | 1 | foo 'bar' | ^^^^^ @@ -2050,12 +2050,12 @@ foo a=\t`echo blaaaaaah: test! { name: unknown_start_of_token, justfile: " -assembly_source_files = $(wildcard src/arch/$(arch)/*.s) +assembly_source_files = %(wildcard src/arch/$(arch)/*.s) ", stderr: r#" error: Unknown start of token: | - 2 | assembly_source_files = $(wildcard src/arch/$(arch)/*.s) + 2 | assembly_source_files = %(wildcard src/arch/$(arch)/*.s) | ^ "#, status: EXIT_FAILURE,