Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dynamic recipe dependencies #1009

Closed
foenixx opened this issue Oct 28, 2021 · 8 comments
Closed

dynamic recipe dependencies #1009

foenixx opened this issue Oct 28, 2021 · 8 comments

Comments

@foenixx
Copy link

foenixx commented Oct 28, 2021

Hello! I'm switching to just from make. For now using just is just great :-).

A feature I cannot find any information about, is dynamic dependencies. In makefile, I can write something like this:

ifeq ...
  dep=recipe2
else
  dep=recipe3
endif

recipe1: $(dep)
    echo "recipe1"

recipe2:
    echo "recipe2"

recipe3:
    echo "recipe3"

Is there a way to achieve similar funcationality in just?

@casey
Copy link
Owner

casey commented Oct 28, 2021

Currently this isn't possible. Can you tell me more about your specific use-case? There might be a workaround.

@foenixx
Copy link
Author

foenixx commented Oct 29, 2021

Actually, I have no case right now. It's just that I've been using this approach several times during my strugglings with make. So this question just poped up when I was reading thru docs. For example, if you have build: clean dependency, but you may want to skip cleaning depending on command line parameters, env vars, etc.

I think that this behaviour can be simulated in justfile like this:

dep := "dont skip me"
recipe1: recipe2
	@echo "recipe1"

recipe2 arg=dep:
	#!/usr/bin/env bash
	set -euo pipefail
	[ -z "{{arg}}" ] && exit 0
	echo "recipe2 arg: {{arg}}"

And then you can pass empty string to dependent recipe to skip it:

❯ just recipe1
recipe2 arg: dont skip me
recipe1


> just dep= recipe1
recipe1

@liquidaty
Copy link

Also new to just (also coming from make) and ran into this issue, wanting to solve without having to rely on the shell (for same reasons as are behind the latest experimental no-shell version). Is the below the best solution? Are there any edge cases where this could behave unexpectedly such as loading a different set of environment variables when the just subcommand is run?

# Conditional targets. This example arbitrarily uses the env var RUN_RECIPE2 as a trigger

# if env var RUN_RECIPE2 is non-empty, run recipe2 else run recipe3

recipe1:
    echo "recipe1"
    {{ if env('RUN_RECIPE2','') != '' { 'just -f ' + justfile() + ' recipe2' } else { 'just -f ' + justfile() + ' recipe3' } }}

recipe2:
    echo "recipe2: {{ env('RUN_RECIPE2') }}"

recipe3:
    echo "recipe3"

@casey
Copy link
Owner

casey commented Dec 22, 2024

@liquidaty Sorry the workaround is so painful! T_T

You should have the same environment variables, but keep in mind that anything that can be set on the command line might be different.

What's your use-case that you need to call recipes dynamically? I'm curious if there's a better workaround, or perhaps something that could be better supported.

The big difference between just and make is that, essentially, just is statically typed with respect to recipes and variables. The justfile is statically analyzed ahead of time, and all variable and recipe references must be resolved statically. So it isn't possibly to dynamically call a recipe or refer to a variable by name.

@liquidaty
Copy link

@casey understood re static typing-- no worries, this solution is perfectly acceptable to me.

Current use case is experimental to see if just could replace make for a Makefile that has multiple targets that either run or don't run depending on whether an environment variable is set.

For example, the Makefile might be called via:

TARGET1=yes TARGET3=yes OTHER_PARAMETER=hello-there make
# or alternatively: make TARGET1=yes TARGET3=yes OTHER_PARAMETER=hello ANOTHER_PARAMETER=there

which would run target1 and target3, but skip target2. The reason these are passed parameters, and not targets, is simply that the calling program does not have any concept of a target-- everything is a parameter, and even those aren't known to (or defined by) the caller-- they are simply passed through at run time. I suppose it could be reengineered to make the calling program differentiate between targets vs parameters, but better if that could be treated independently of this experiment to replace make with just

@casey
Copy link
Owner

casey commented Dec 22, 2024

One thing you could do is make the target return early, which can be done with a shebang recipe:

target1:
  #!/usr/bin/env bash
  if $TARGET1 != yes; then
    exit
  fi
  # the rest of the recipe

And then unconditionally call them.

You can't do this with a linewise recipe (one without a shebang) because there's no way for a linewise recipe to indicate that just should not run the rest of the recipe after a command, aside from returning an error, which causes the whole run to terminate with that error.

We have sigils which change how recipe lines execute, and you could imagine a ? sigil which treated an error code as a signal to stop executing a linewise recipe, but not to return an error:

foo:
  ?[[ $FOO == yes]]
  # the rest of the recipe

Which would make this pattern possible with linewise recipes.

This is backwards incompatible, so would have to be enabled with a setting. I think this is a potentially interesting feature, I'll create an issue for it and see if anyone wants it.

Thanks for the issue! I'm always interested in how just can be used to replace make.

@casey casey closed this as completed Dec 22, 2024
@liquidaty
Copy link

The ? sigil approach would definitely be nice, to obviate the need for a nested just call. It would seem to also allow what I would imagine easily could be an idiomatic pattern which would be for target-specific checks to be placed at the top of each recipe. I think it would be even nicer if it could be followed by a condition that did not rely on the shell, such as ?{{ env('MY_VAR') != '' }}. Comparing the three, in a hypothetical that runs at least one of recipeA or recipeB, and maybe runs recipeC :

Current (for simplicity, assuming each env var has been set to an equivalent lower-case local var):

run_conditional_recipes:
    {{ if run_recipeA != '' { 'just -f ' + justfile() + ' recipeA' } else { 'just -f ' + justfile() + ' recipeB' } }}
    {{ if run_recipeC != '' { 'just -f ' + justfile() + ' recipeC' } else { '' } }}

recipeA:
    echo "recipeA"

recipeB:
    echo "recipeB"

recipeC:
    echo "recipeC"

With ? sigil:

recipeA:
   ? [ "${RUN_RECIPEA}" != "" ]
    echo "recipeA"

recipeB:
   ? [ "${RUN_RECIPEA}" = "" ]
    echo "recipeB"

recipeC:
   ? [ "${RUN_RECIPEC}" != "" ]
    echo "recipeC"

With ? sigil and no shell reliance, for simplicity using local vars instead of env vars:

recipeA:
   ? {{run_recipeA != ''}}
    echo "recipeA"

recipeB:
   ? {{run_recipeA == ''}}
    echo "recipeB"

recipeC:
   ? {{run_recipeA != ''}}
    echo "recipeC"

2nd form, which is your suggestions, seems like a significant improvement over current. The last form to me is a slight further improvement, but more of a nice-to-have (and I guess it might require supporting a whole 'nother expression datatype so maybe not worth it)

@casey
Copy link
Owner

casey commented Dec 23, 2024

Check it out: implemented in #2547. Not merged yet to collect some feedback first. Issue in #2544.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants