6

I have a Makefile which I use to call different sub-Makefiles.

I have several rules:

  • all
  • clean
  • fclean
  • re

I can already use those rules, which will call every sub makefile with the same rule.

I have several project, and I would like to generate rules with that format:

$(PROJECT_NAME)-$(RULES)

With that, I would like to have each rule for each project:

project1-all

project1-clean

...

project2-all

project2-clean

...

This way, I would be able to call a specific rule, for a specific project, like project1-fclean.

I tried that:

RULES=    all clean fclean re

PROJECTS= project1 project2

define NEWLINE

endef

$(foreach _rule, $(RULES),                                              \
    $(foreach _proj, $(PROJECTS),                                           \
$(_proj)-$(_rule):                                           $(NEWLINE) \
            $(MAKE) $(ARGS) $(PROJECT_DIR)$(_proj) $(_rule) $(NEWLINE)  \
    )                                                                     \
)

But it doesn't seems to work. I have searched, but I haven't found advanced makefile techniques to achieve that. Plz help.

Ludonope
  • 963
  • 9
  • 14
  • Instead of calling `make` recursively, I'd recommend to have a look at non-recursive `makefile` approach. There is an article about disadvantages of recursive make: http://www.linux-mag.com/id/2101/. You can try my template library for non-recursive makefiles: https://github.com/igagis/prorab/blob/master/wiki/TutorialBasicConcepts.md – igagis Apr 13 '17 at 07:12

2 Answers2

6

The problem is that when you combine lines together with line continuations like that, it compresses out all the newlines and other extraneous whitespace (including those newlines you're trying to insert with $(NEWLINE)) resulting in a huge mess on a single line, rather than multiple lines with multiple patterns. To do this correctly, you need to write your rule as a macro with arguments and then call it:

define PROJ_RULE
$(1)-$(2):
        $(MAKE) $(ARGS) $(PROJECT_DIR)$(1) $(2)
endef

$(foreach _rule, $(RULES),
    $(foreach _proj, $(PROJECTS),
        $(eval $(call PROJ_RULE, $(_proj), $(_rule)))))

note that all this define and foreach stuff in GNU make specific -- other make flavors do not support it.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • 3
    It doesn't seems to work for me, it tells me `multiple target pattern`. I know you can't combine more than one pattern as target (like `%.o`), and here it all depends on wether or not `$(1)-$(2)` is a pattern or not. I guess yes. I have found an "ugly" solution right now, but yours seems a lot more esthetic. – Ludonope Apr 12 '17 at 22:08
  • Yes, this won't work if there are any `%` in either `$(PROJECTS)` or `$(RULES)`, but your examples don't seem to need `%` anywhere, so I'm not sure where they are coming from. – Chris Dodd Apr 12 '17 at 23:56
  • This answer is almost right, it's missing the call to eval though so it doesn't work. Wrap the top level foreach with $(eval) and it works. – Samuel Mar 04 '20 at 22:58
  • I came back to this because I'm still having issues. Looks like Chris made the fix. Why doesn't adding the $(eval) around the top level foreach work? Putting it on the inside, like Chris has here, does indeed work though. – Samuel Mar 05 '20 at 22:17
  • 1
    Since the `$(foreach` begins the first column (no preceeding tab or space) it *should* be evaluated as makefile syntax after expanding always, so the explicit `$(eval` should not be needed, but it looks like that only works in some versions of GNUmake. In other versions, the eval is required; I'm not sure which ones. Apparently you have a version where it is not only required, but must be inside the foreach. The indentation of the `$(call` line might be the cause of the problem; the explicit `$(eval` immediately around the call will avoid that. – Chris Dodd Mar 05 '20 at 22:30
  • Thanks for clarifying. For the record, I'm using Cygiwn make, and the output of make -v is: "GNU Make 4.2.1". – Samuel Mar 06 '20 at 00:48
3

Okay, so I finally managed to do it this way:

$(foreach _rule, $(RULES), $(addsuffix -$(_rule),$(PROJECTS))):
            $(eval _rule := $(lastword $(subst -, ,$@)))
            $(eval _proj := $(@:%-$(_rule)=%))
            @$(MAKE) $(ARGS) $(PROJECT_DIR)$(_proj) $(_rule)

I will decompose it for a better explanation:

$(foreach _rule, $(RULES), ...)):

We loop on every RULES and store it in _rule.

$(addsuffix -$(_rule),$(PROJECTS))

We add that rule as a prefix to each of our project. This part generate a rule with every "composed rules". With projet1 and project2 it should result in:

project1-all project2-all project1-clean project2-clean project1-fclean project2-fclean project1-re project2-re:

This way, for any of those rules name, it will be the same rule executed.

$(eval _rule := $(lastword $(subst -, ,$@)))

Here we take the target (if I call project2-clean, $@ will be project2-clean), we replace - by a space to obtain project2 clean and take the last work, wich will be clean here. We then evaluate it to store that into _rule.

$(eval _proj := $(@:%-$(_rule)=%))

We use the same technique to store the project name into _proj. We just use a pattern replacement, to remove the rule name and the dash.

@$(MAKE) $(ARGS) $(PROJECT_DIR)$(_proj) $(_rule)

Finally, we call our submakefile we the right path and right rule!

Ludonope
  • 963
  • 9
  • 14