How to use make's --jobs with subdirs? - makefile

Build time of my C++ project is very big. It consists of a bunch of subprojects (libraries), structured in file system folders. I want to speed up it with parallel build with --jobs (-j) parameter of make. What is the correct way of doing it? Documentation says that there are some tricks of doing parallel build with subdirs.
Makefile in root directory looks like:
...
all:
$(MAKE) -C DirA
$(MAKE) -C DirB
...
Makefile in DirA:
all:
$(MAKE) -C SubDirA
$(MAKE) -C SubDirB
$(MAKE) -C SubDirC
In DirB:
all:
$(MAKE) -C SubDirD
$(MAKE) -C SubDirE
$(MAKE) -C SubDirF
And so on. Makefiles in leaf folders are quite simple, contain only build instruction without any tricks.

You can just add -j to make using these makefiles as-is and you'll get some parallelism, but you won't get maximum parallelism. The problem is that make only parallelizes targets: clearly it won't work (in general) to run multiple commands in the same target in parallel!
So, in the top level make will run $(MAKE) -C DirA then $(MAKE) -C DirB, serially. When make builds DirA, it will first run $(MAKE) -C SubDirA, then $(MAKE) -C SubDirB, etc. serially. Then finally when make gets into SubDirA, it will build the targets there in parallel. This is fine, and maybe what you need to do if the order of building directories is important, except that there will be times when make could start working on SubDirB targets but won't, until all targets in SubDirA are complete.
A better way to handle subdirectories is to use make rules:
SUBDIRS := DirA DirB
all: $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $#
.PHONY: $(SUBDIRS)
and ditto for the subdirectories. Note you must include the .PHONY.
Since each directory is a separate target, now make can invoke them in parallel if you use -j.
Oh, and if some of the contents of some subdirectories depends on others, you can declare that dependency explicitly in the makefile so make know about it:
SUBDIRS := SubDirA SubDirB SubDirC SubDirD
all: $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $#
.PHONY: $(SUBDIRS)
SubDirA: SubDirD
etc.

Related

How to call a bunch of subdirectories Makefile from a top level Makefile with different targets

I have a top level Makefile that define a list of subdirectories containing sub-Makefiles.
Each sub-Makefile define the same targets: all, install, clean, test, verify, style...
How do I make the top level Makefile call all my subdirectories Makefile with the same target ?
It works for the default target, like this:
SUBDIRS = dir1 dir2 dir3
all: subdirs
subdirs: $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $#
.PHONY: subdirs $(SUBDIRS)
But how do I extend that to the other non default targets ?
If you use GNU make you could use the MAKECMDGOALS variable and the filter function:
SUBDIRS := dir1 dir2 dir3
TARGETS := all install clean test verify style
$(TARGETS): subdirs
#echo making top $#
subdirs: $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $# $(filter $(TARGETS),$(MAKECMDGOALS))
.PHONY: subdirs $(TARGETS) $(SUBDIRS)
%::
#echo making top $#
Demo:
$ make --no-print-directory all install foobar
make -C dir1 all install
make -C dir2 all install
make -C dir3 all install
making top all
making top install
making top foobar
Note that filter is needed to avoid passing top-only goals to the sub-makes.
If, for any reason, this MAKECMDGOALS based solution does not work for you, here are some other, but a bit more complex, solutions.
Target-specific variables
Target-specific variables propagate to prerequisites. So you can define a pattern-specific variable (SUBGOAL) for each of your top-level goals and use it in the subdir's recipe:
SUBDIRS := dir1 dir2 dir3
all: SUBGOAL := all
all: subdirs
#echo making top $#
subdirs: $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $# $(SUBGOAL)
.PHONY: subdirs $(SUBDIRS)
If you have many such top-level goals and use GNU make you can factorize a bit with the foreach and eval GNU make functions:
SUBDIRS := dir1 dir2 dir3
TARGETS := all install clean test verify style
$(foreach t,$(TARGETS),$(eval $(t): GOAL := $(t)))
$(TARGETS): subdirs
#echo making top $#
subdirs: $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $# $(GOAL)
.PHONY: subdirs $(SUBDIRS) $(TARGETS)
One important drawback is that as you use the same phony subdirectory target (dir1) for several top phony targets (all, install...) you cannot invoke make with several top goals:
$ make --no-print-directory all install
make -C dir1 all
make -C dir2 all
make -C dir3 all
making top all
making top install
will only make all in the subdirectories. If this is a problem you can use the next solution.
One phony target (all-dir1) per top-subdirectory combination
Let's do this programmatically with a 2-variables macro and a foreach-eval-call combination (GNU make only):
SUBDIRS := dir1 dir2 dir3
TARGETS := all install clean test verify style
$(TARGETS): subdirs
#echo making top $#
TARGETS_X_SUBDIRS :=
# $(1): top target
# $(2): subdirectory target
define TARGETS_X_SUBDIRS_RULES
$(1): $(1)-$(2)
$(1)-$(2):
$(MAKE) -C $(2) $(1)
TARGETS_X_SUBDIRS += $(1)-$(2)
endef
$(foreach t,$(TARGETS),$(foreach s,$(SUBDIRS),$(eval $(call TARGETS_X_SUBDIRS_RULES,$(t),$(s)))))
.PHONY: $(TARGETS) $(TARGETS_X_SUBDIRS)
Demo:
$ make --no-print-directory all install
make -C dir1 all
make -C dir2 all
make -C dir3 all
making top all
make -C dir1 install
make -C dir2 install
make -C dir3 install
making top install
Renaud's answer is more complete, but I also found this technique that worked fine for my use case:
all:
%:
make -C dir1 $#
make -C dir2 $#
make -C dir3 $#
In this case, I don't have the subdirs list in a variable, but they are spelled out in the commands for the generic target %:. It requires to add an extra make -C in front of every subdirectory, and a # in the back, but otherwise pretty maintainable and easy to read.
But it probably would not work if there was any other targets specific to the top-level Makefile.

Calling other makefile from make

I have seen this question and am somehow still unable to get make to properly call another Makefile. Here is my Makefile:
base:
cd Base && $(MAKE)
Base is a sub-directory of my current directory where there is a different Makefile used to compile the files in said folder. However, when I call make, I simply get nothing to be done for base, whereas when I do cd Base && make from the command-line, I get expected behavior (i.e., compilation occurs). In case it is something wrong with the Makefile in Base, here is a copy of that as well:
CC=g++
CFLAGS=-O3 -Wall -pedantic -Werror -c
LINK=g++
LFLAGS=-O3
SRC=main.cpp
OBJ=..\lib\main.o
EXE=..\bin\test.exe
all: $(EXE)
$(EXE): $(OBJ) $(SRC)
$(LINK) $(LFLAGS) $(OBJ) -o $(EXE)
..\lib\main.o: main.cpp
$(CC) $(CFLAGS) $< -o $#
I'm not sure what $(MAKE) amounts to (I'm assuming just 'make'), but I have my makefiles setup in a cascading manner where one calls another in order to cover all subdirectories.
Each makefile has some common targets like 'all' and 'clean'.
Then my makefiles which are just delegating to subdirs look something like this:
.PHONY : all
all:
cd SubDir1 && make $#
cd SubDir2 && make $#
...
.PHONY : clean
clean:
cd SubDir1 && make $#
cd SubDir2 && make $#
...
$# is an automatic variable which will be substituted for the target ('all' or 'clean' in the example above).
This allows you to run 'make', 'make clean', 'make all', or even 'make clean all' from the top-level dir and it will recurse through the subdirs specified in the makefiles for the appropriate target. So if you run 'make clean' at the top, all directories in the makefile's list will be called with 'make clean'.
You can still use this technique even if you don't have common targets. Simply change the automatic variable $# with the appropriate target for each directory's makefile.

How to avoid code repetition calling recursive Makefiles?

Is it possible to simplify a Makefile of the following form in order to avoid code repetition:
dir1/foo:
make -C dir1 foo
dir1/bar:
make -C dir1 bar
dir2/baz:
make -C dir2 baz
...
clean:
make -C dir1 clean
make -C dir2 clean
...
I imagine I could specify only:
MY_TARGETS=dir1/foo dir1/bar dir2/baz ...
And than have some general rules to derive targets, as presented in the Makefile above.
You haven't given us much information. Recursive Make can be a good solution, if your main makefile is as simple as your example (which I doubt).
You could do this:
%:
$(MAKE) -C $(dir $#) $(notdir $#)
clean:
$(MAKE) -C dir1 clean
$(MAKE) -C dir2 clean
...
If that clean recipe is too long, you can simplify it:
DIRS := dir1 dir2 ...
clean:
#for x in $(DIRS); do $(MAKE) -C $$x clean; done
Or if you don't like having that DIRS list there, you could have Make generate it, depending on whether you want to make clean in all subdirectories, or all that match the dir* pattern, or... you haven't given us enough information.

How to combine makefiles?

I encountered a software that had three makefiles, one in the root directory and two in immediate subdirectories. Experimenting a bit it turned out that there is a good order of them:
$ make -C wordn all
$ make -C buses all
$ make all
from the root directory. What is an elegant way to rewrite the main makefile so that this sequence happens?
In the target all of the main Makefile you can add:
all:
$(MAKE) -C wordn all
$(MAKE) -C buses all
...
This will execute the submake before doing the all commands.
You can also create a dependency of the main all target:
all: suball
...
suball:
$(MAKE) -C wordn all
$(MAKE) -C buses all
.PHONY: suball
The suball target must be a PHONY target as it is a virtual target

Run make in each subdirectory

I have a directory (root_dir), that contains a number of sub-directories (subdir1, subdir2, ...).
I want to run the make in each directory in root_dir, using a Makefile placed in it.
(Obviously supposed that each of subdir... has inside its own Makefile).
So there are essentially two questions:
How to get a list of directories in Makefile (automatically)?
How to run make for each of the directories inside a make file?
As I know in order to run make in a specific directory I need to do the following:
$(MAKE) -C subdir
There are various problems with doing the sub-make inside a for loop in a single recipe. The best way to do multiple subdirectories is like this:
SUBDIRS := $(wildcard */.)
all: $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $#
.PHONY: all $(SUBDIRS)
(Just to point out this is GNU make specific; you didn't mention any restrictions on the version of make you're using).
ETA Here's a version which supports multiple top-level targets.
TOPTARGETS := all clean
SUBDIRS := $(wildcard */.)
$(TOPTARGETS): $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $# $(MAKECMDGOALS)
.PHONY: $(TOPTARGETS) $(SUBDIRS)
Try this :
SUBDIRS = foo bar baz
subdirs:
for dir in $(SUBDIRS); do \
$(MAKE) -C $$dir; \
done
This may help you link
Edit : you can also do :
The simplest way is to do:
CODE_DIR = code
.PHONY: project_code
project_code:
$(MAKE) -C $(CODE_DIR)
The .PHONY rule means that project_code is not a file that needs to be
built, and the -C flag indicates a change in directory (equivalent to
running cd code before calling make). You can use the same approach
for calling other targets in the code Makefile.
For example:
clean:
$(MAKE) -C $(CODE_DIR) clean
Source
This is another approach to MadScientist's answer. .PHONY is a GNU-specific feature that can be used to force make into recursing into each subdirectory. However, some non-GNU versions of make do not support .PHONY, so an alternative is a force target.
4.7 Rules without Recipes or Prerequisites
If a rule has no prerequisites or recipe, and the target of the rule
is a nonexistent file, then make imagines this target to have been
updated whenever its rule is run. This implies that all targets
depending on this one will always have their recipe run.
An example will illustrate this:
clean: FORCE
rm $(objects)
FORCE:
Here the target ‘FORCE’ satisfies the special conditions, so the
target clean that depends on it is forced to run its recipe. There is
nothing special about the name ‘FORCE’, but that is one name commonly
used this way.
As you can see, using ‘FORCE’ this way has the same results as using
‘.PHONY: clean’.
Using ‘.PHONY’ is more explicit and more efficient. However, other
versions of make do not support ‘.PHONY’; thus ‘FORCE’ appears in many
makefiles. See Phony Targets.
The following is a minimal example that recurses make into each subdirectory, each of which presumably contains a Makefile. If you simply run make, only the first subdirectory, which is non-deterministic, is processed. You may also run make subdir1 subdir2 ....
# Register all subdirectories in the project's root directory.
SUBDIRS := $(wildcard */.)
# Recurse `make` into each subdirectory.
$(SUBDIRS): FORCE
$(MAKE) -C $#
# A target without prerequisites and a recipe, and there is no file named `FORCE`.
# `make` will always run this and any other target that depends on it.
FORCE:
Here is another example with top-level phony targets: all and clean. Note that the all and clean targets, passed from command-line via $(MAKECMDGOALS), are handled by each subdirectory's all and clean targets respectively.
# Register all subdirectories in the project's root directory.
SUBDIRS := $(wildcard */.)
# Top-level phony targets.
all clean: $(SUBDIRS) FORCE
# Similar to:
# .PHONY: all clean
# all clean: $(SUBDIRS)
# GNU's .PHONY target is more efficient in that it explicitly declares non-files.
# Recurse `make` into each subdirectory
# Pass along targets specified at command-line (if any).
$(SUBDIRS): FORCE
$(MAKE) -C $# $(MAKECMDGOALS)
# Force targets.
FORCE:
You can also define a function in the Makefile (also you of course need an additional makefile in each subdirectory). This is shell-dependent, but can be useful:
define FOREACH
for DIR in packages/*; do \
$(MAKE) -C $$DIR $(1); \
done
endef
.PHONY: build
build:
$(call FOREACH,build)
.PHONY: clean
clean:
$(call FOREACH,clean)
.PHONY: test
test:
$(call FOREACH,test)
Only a small icing on the cake after MadScientist's answer in order to make all the individual targets in the sub-directories available from the top level (you will need to have the SUBDIRS variable defined in order to use the following snippet – you can use MadScientist's answer for that):
# Make all the individual targets in the sub-directories available from the top
# level; as in, for instance, `make foo/my_program` or `make bar/clean`
$(foreach __dir__,$(SUBDIRS),$(__dir__)/%):
#$(MAKE) -C '$(#D)' '$(#F)'
With the code above you can run, for instance,
make foo/my_program
or
make bar/clean
Furthermore, by pasting the code above you can even use an individual target from a sub-directory as a prerequisite for a target in the top level. For example:
my_target: my_subdirectory/my_prerequisite
'my_subdirectory/my_prerequisite' > 'my_target'
…With the example above, launching make my_target from the top level will first build the my_subdirectory/my_prerequisite program, then the latter will be run for building the my_target file.
Since I was not aware of the MAKECMDGOALS variable and overlooked that MadScientist has its own implementation of multiple top-level targets, I wrote an alternative implementation. Maybe someone find it useful.
SUBDIRS := $(wildcard */.)
define submake
for d in $(SUBDIRS); \
do \
$(MAKE) $(1) --directory=$$d; \
done
endef
all:
$(call submake,$#)
install:
$(call submake,$#)
.PHONY: all install $(SUBDIRS)
There is a library called prorab for GNU make which supports inclusion of standalone makefiles in subdirectories.
Some info on github: https://github.com/cppfw/prorab/blob/master/wiki/HomePage.adoc
Basically, with prorab invoking all makefiles in subdirectories looks like this:
include prorab.mk
$(eval $(prorab-build-subdirs))
In reference to https://stackoverflow.com/posts/17845120/revisions
This is what I learned from that post.
Top Level Makefile
# set the default goal.
# I want the default to really just dump contents of dirs
# as a stub. For instance, I don't want it to
# push code or
.DEFAULT_GOAL := deploy
TOPTARGETS := all clean
SUBDIRS := docs src
$(TOPTARGETS): $(SUBDIRS)
$(SUBDIRS):
echo "make arg is" $(MAKECMDGOALS)
$(MAKE) -C $# $(MAKECMDGOALS)
SUBCLEAN = $(addsuffix .clean,$(SUBDIRS))
clean: $(SUBCLEAN)
$(SUBCLEAN): %.clean:
$(MAKE) -C $* clean
deploy:
echo do deploy stub
The src/ and docs/ common to this Makefile directory, all have a corresponding Makefile.
Here is an example of the docs setup:
# set the default goal.
.DEFAULT_GOAL := list_docs
list_docs:
ls -l
clean:
echo "docs: make clean"
-rm "*.backup"
I did this a little different than any of the answers because I didn't want to have to define each possible make target
SUBDIRS := $(patsubst %/,%,$(wildcard */))
.PHONY: all $(MAKECMDGOALS) $(SUBDIRS)
$(MAKECMDGOALS) all: $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $# $(MAKECMDGOALS)

Resources