just
just copied to clipboard
Feature request: Enable setting Variables in a Recipe
There's already a great docs section that talks about Setting Variables in a Recipe.
This is a feature that I frequently miss, and sometimes I want to avoid the suggested solution of using a single shell for the recipe using the she-bang line. (Typically, this is when I want the exit-code behavior of just, as using a she-bang line changes that functionality.)
It seems like it could be achieved by mimicking a shell's export behavior; that is, if the recipe line starts with export then add the resulting variable to the just process environment.
Since each line is within a shell, any current shell line with export NAME=variable is effectively a no-op, so this feature only extends, but does not modify, existing functionality.
Though not likely, this could be a breaking change for some recipes. Maybe a special function you could call at the end of the recipe could give back the normal just exit code behavior? Might that be a possibility @casey ?
I wouldn't do this as a breaking change, since that would be too disruptive, so this would have to have syntax that didn't conflict with normal shell syntax. Also, just is very static-analysis heavy, which I think is one of its strengths. I.e. it checks that all variable accesses are valid ahead of time, and resolves dependencies between them. So this feature would also have to have the same properties, i.e. not possible to misspell a variable.
Though not likely, this could be a breaking change for some recipes. Maybe a special function you could call at the end of the recipe could give back the normal just exit code behavior? Might that be a possibility @casey ?
I realized that the exit on non-zero codes can be recovered with #!/bin/sh -e. With that realization, it's easy for work around not having this feature in my current use.
This feature might still be nice, as I think setting variables within scripts, without needing to add the she-bang line, would be more intuitive for users seeing just for the first time. Specifically, I think it's surprising and somewhat counter-intuitive to have to change the execution context with a shebang line just to set variables.
@runeimp What scenario would be a breaking change? As I mentioned above, if the syntax was export NAME=value, such a line is a no-op in the shell-per-line context that just uses to execute recipes, so I don't see how that feature could break existing functionality. I'm curious to hear a possible counter-examples where it could be a breaking change, but I can't think of any.
Using the export syntax has the advantage, in my opinion, of being "less surprising" to new users, since it's giving just the exact same variable behavior that shells follow. Namely, a line NAME=value is a no-op unless there's a command on the same line, whereas export NAME=value causes subsequent executions to have NAME in its environment.
@kurtbuilds hm, I'd thought of one immediately as I was reading the suggestion originally. But now I'm drawing a blank. Like I said it would be rare. But on the off chance it comes up it would likely be a complete nightmare to debug. And could effect recipes already in use that may be part of a CI/CD process. If I think of an, or remember my, example I'll update this issue. Sorry, my brain is done for the day I think. 😇
This feature might still be nice, as I think setting variables within scripts ...
Similar feature request was discussed in a previous issue.
https://github.com/casey/just/issues/1011
It would be more useful to set the value computed within the shell process.
k := ""
demo:
v=$(program); just --rpc-client k := "$v";
$a="{{k}}"; if [ $a = "ok" ]; then just --rpc-client run-code-in-parent-just-process; \
else just --rpc-client issue-cmd-from-child-process; fi
If this feature just sets a fixed value (defined at the time of writing the justfile) when running to the specific line, the usage scenario is limited.
Also, just is very static-analysis heavy ... I.e. it checks that all variable accesses are valid ahead of time ...
The above can be written as v=$(program); {| k := "$v" |};, after doing static analysis, dynamically converted to the corresponding shell command when executed. For example, v=$(program); just --rpc-client k := "$v";
The parent just process receives the instruction and changes the corresponding variable.
A clean way to do this could be a recipe attribute with parameters that allows to declare environment variables should be passed on to subsequent recipes.
Something like:
[ env VAR1 VAR2 ]
recipe1:
#!/bin/sh
export VAR1="hello"
export VAR2="world"
recipe2: recipe1
#!/bin/sh
echo $VAR1 $VAR2 # will output "hello world"
could work if just inspects the environment after running recipe1 and makes (only) the variables declared in env available before running recipe2 (there's probably some better name than "env" for this).
I'm using a shebang in recipe1 above, but the same thing may work for non-shebang recipes, as long as the environment is inspected after each line is executed.
This could also make things like this work
[env GREETING]
recipe:
export GREETING="hello"
echo "$GREETING world"
I came to browse the issues in this repo just to raise the exact same issue. I'm currently using Taskfile and although I'm not a huge fan of using YAML over something more bespoke, they did nail the general syntax, limited by YAML as it may be.
For reference, in a taskfile you can do
tasks:
taskname:
env:
STATUS: done # this will be injected as an environmental variable
vars:
one: hello
two: "{{.one}} world"
cmds:
- echo "It is {{.STATUS}} and the message is '{{.two}}'"
and I've been thinking if maybe this sort of "block" for variables would be a solution. For example
recipe $STATUS="done" [
one: "hello"
two: "{{one}} world"
]:
echo "It is {{STATUS}} and the message is '{{two}}'"
or some such. Perhaps for env variables for ad-hoc use as well, those could be denoted with $ prefix, or could belong to another block entirely:
recipe [
$STATUS: "done"
one: "hello"
two: "{{one}} world"
]:
echo "It is {{STATUS}} and the message is '{{two}}'"