Foreach template in makefile recipe

11,801

Solution 1

I've ended up going with a hybrid approach from my original question and jml's answer (thanks for the inspiration!).

TOOLS:=foo bar

.PHONY: all
all : $(TOOLS)

.PHONY: install $(addprefix install_,$(TOOLS))
install : $(addprefix install_,$(TOOLS))

.PHONY: uninstall $(addprefix uninstall_,$(TOOLS))
uninstall : $(addprefix uninstall_,$(TOOLS))

define TOOL_template
$(1): $$($(1)_OBJS)
        #commands to build it

install_$(1): $(1)
        install -c $(1) $$(prefix)/bin/$(1)

uninstall_$(1):
        rm $$(prefix)/bin/$(1)
endef

$(foreach tool,$(TOOLS),$(eval $(call TOOL_template,$(tool))))

Seems to do the trick.

Solution 2

You should not use eval here, because you do not want to create dynamic constructs but constant rules. So just remove eval and add a ; at the end of your TOOL_install define:

TOOLS := foo bar

define TOOL_install
echo $(1);
endef

.PHONY: install
install:
    $(foreach tool,$(TOOLS),$(call TOOL_install,$(tool)))

After expansion your Makefile will look like:

.PHONY: install
install:
    echo foo; echo bar;

ANOTHER ANSWER:

We are using some temporary target install_foo and install_bar. These two targets are added to the install dependencies and declare just after. Moreover, in each temporary target we add dependency about the file to install (foo or bar). This way you can add as rules as you want, plus it's "parallel compliant".

prefix := foobar
TOOLS := foo bar

install: $(addprefix install_,$(TOOLS))

$(addprefix install_,$(TOOLS)): install_%: %
    install -c $< $(prefix)/bin/$<

.PHONY: install $(addprefix install_,$(TOOLS))

EDIT:

For the uninstal targets (without dependencies) you can use the patsubst function like this:

prefix := foobar
TOOLS := foo bar

uninstall: $(addprefix uninstall_,$(TOOLS))

$(addprefix uninstall_,$(TOOLS)):
    install -c $(patsubst uninstall_%,%,$@) $(prefix)/bin/$(patsubst uninstall_%,%,$@)

.PHONY: uninstall $(addprefix uninstall_,$(TOOLS))
Share:
11,801
Miral
Author by

Miral

Updated on June 04, 2022

Comments

  • Miral
    Miral almost 2 years

    Given the following Makefile fragment:

    TOOLS=foo bar
    
    define TOOL_install
    install -c $(1) $$(prefix)/bin/$(1)
    endef
    
    .PHONY: install
    install: all
            $(foreach tool,$(TOOLS),$(eval $(call TOOL_install,$(tool))))
    

    Why does make install print "Nothing to be done for `install'." instead of executing the commands specified by the foreach? (There is a tab before the foreach, and I have tried putting a tab inside the TOOL_install definition; it doesn't help.)

    According to make -p the install target has no commands, which is obviously not as intended.

    Elsewhere in the same makefile I've successfully used the same technique to create entire rules including command recipes; how can I make this work within an existing rule?

    (If I replace eval with info then it prints the commands that I am expecting it to run.)

    In this simple case I can get it to work by inlining the command:

    install: all
            @$(foreach tool,$(TOOLS),install -c $(tool) $(prefix)/bin/$(tool) ; )
    

    But I would like to know how to get the first form to work as expected, in case I need something more complicated in future.

  • Miral
    Miral almost 10 years
    That's pretty much the same as the example at the end of my question though. Which is flawed in that everything executes in the same subshell, so eg. will abort entirely if something fails even if I try flagging it with "-" (because I can't flag each individual line that way). Which admittedly I don't actually need for this specific example, but I'd prefer to find a general case that works.
  • bobbogo
    bobbogo almost 10 years
    The Another Answer is much the best of the two. The ; in the first answer hides failing installs (apart from the last one). The separate targets in the second do not have this fault, and they allow the two installs to proceed in parallel. The whole point of make really.
  • Miral
    Miral almost 10 years
    Two colons like that is new to me, but that looks quite elegant. I'll give it a try shortly. (One concern is that I'm using this sort of thing in two places, and in one of those each of the TOOLS contains a path including slashes. Is it legal to have a phony install_foo/bar rule?)
  • jmlemetayer
    jmlemetayer almost 10 years
    @Miral The 2 colons stuff is called Static Pattern Rules. As the / character is valid for standard target, I think that this is also valid for phony targets.
  • Miral
    Miral almost 10 years
    In standard targets it expects there to actually be a directory with that name though, which won't be the case. I'll give it a try though and see what happens -- if it does object I can probably make it a suffix instead of a prefix, which might work around it.
  • Miral
    Miral almost 10 years
    One more question: you're using $< in the above to get the original TOOL name for each individual install_TOOL rule, which works because the rule has that as its single dependency. Is there a way to do that without the dependency? (I'm trying to make an uninstall rule now along similar lines, and uninstalling should not depend on building the tool.)
  • jmlemetayer
    jmlemetayer almost 10 years
    The multiple definitions of targets in a single template is a very good idea. Much easier to get the variable name you need than finding it by parsing $@. +1