0

I'm writing a Terminal Match-Anything Pattern Rule, i.e. %::, that, as expected, will run only if no other target is matched. In its recipe I want to iterate over makefile's explicit targets and check if the found pattern ($*) is the beginning of any other target

By now I'm successfully getting all desired targets in a space-separated string and storing it in a variable TARGETS, however I couldn't turn it in an array to be able to iterate over each word in the string.

For instance

%::
   $(eval TARGETS ::= $(shell grep -Ph "^[^\t].*::.*##" ./Makefile | cut -d : -f 1 | sort))
   echo $(TARGETS)

gives me just what I was expecting:

build clean compile deploy execute init run serve

The Question

How could I iterate over each of $(TARGET) string words inside a GNU Make 4.2.1 loop?


I found a bunch of BASH solutions, but none of them worked in my tests:

artu-hnrq
  • 1,343
  • 1
  • 8
  • 30
  • 1
    You might be interested how bash make completion does it: https://github.com/scop/bash-completion/blob/master/completions/make – KamilCuk Feb 16 '21 at 22:51
  • 1
    Loops are an alien concept in make. The real question would be: "Loop to do what?" to build a variable?, to check some values?. You may be forced to use some other language to process a loop (shell? Perl? python?) –  Feb 17 '21 at 00:29

1 Answers1

1

It's generally a really bad idea to use eval and shell inside a recipe. A recipe is already a shell script so you should just use shell scripting.

It's not really clear exactly what you want to do. If you want to do this in a recipe, you can use a shell loop:

%::
        TARGETS=$$(grep -Ph "^[^\t].*::.*##" ./Makefile | cut -d : -f 1 | sort); \
        for t in $$TARGETS; do \
             echo $$t; \
        done

If you want to do it outside of a recipe you can use the GNU make foreach function.

MadScientist
  • 92,819
  • 9
  • 109
  • 136
  • 2
    Note, neither make (including GNU make) _nor_ POSIX sh have such a thing as "array variable". The GNU Bash shell does have array variables but they are not portable to other shells and have different syntax. – MadScientist Feb 16 '21 at 22:50
  • Great to know, Mad, now I'm aware of it! Thanks a lot for your fast and precise attention – artu-hnrq Feb 18 '21 at 01:33
  • Hey Mad, simple late bonus question: I've been trying `$(if $(filter $*, $$t), echo true, echo false)` inside `for`, but without success. How should I do that comparison? Thanks! – artu-hnrq Mar 09 '21 at 00:10
  • 1
    You cannot use make functions inside a recipe to operate on shell variables like `$t`. How can that work? They are completely separate programs. Make will _first_ expand all make variables and functions in your recipe, _then_ it will invoke a shell and pass the fully-expanded script to the shell to run. By the time the shell receives the script to run, all make content is gone. The shell runs the script and exits, and make waits for it to exit and determines whether it worked by examining the exit code. – MadScientist Mar 09 '21 at 01:19
  • Oow, pretty interesting enlightenment about execution order. Thanks for your valuable attention, again, man! – artu-hnrq Mar 09 '21 at 07:34