make -j behaves differently with and without sub makes - makefile

Can someone explain why make with sub-makes has different parallelization behavior?
I have two examples below. In one, all targets are in one Makefile where as in the second example each target has its own folder/makefile and they call each other using $(MAKE).
Running them with -j produce outputs which are vastly different. In the first case, all access to the top-level dependency (a.out below) is synchronized. Make waits for the target to build before running any targets which depend on that.
In the second case, the top-level target (a.out below) is subjected to concurrent access. This causes serious problems for us unless we use our own synchronization techniques to overcome that.
Examples 1:
jesaremi#u16-3:~/maketest$ cat Makefile
.ONESHELL:
all: d.out b.out c.out a.out
a.out: CALLER ?= self
a.out:
#
echo entering $# "(called by $(CALLER))"
sleep 10
echo exiting $#
b.out: a.out
#
echo entering $#
sleep 1
echo exiting $#
c.out: a.out
#
echo entering $#
sleep 1
echo exiting $#
d.out: a.out
#
echo entering $#
sleep 1
echo exiting $#
jesaremi#u16-3:~/maketest$ make
entering a.out (called by self)
exiting a.out
entering d.out
exiting d.out
entering b.out
exiting b.out
entering c.out
exiting c.out
jesaremi#u16-3:~/maketest$ make -j
entering a.out (called by self)
exiting a.out
entering d.out
entering c.out
entering b.out
exiting c.out
exiting d.out
exiting b.out
Example 2 (using sub-make):
jesaremi#js-u16-1:~/maketest$ cat */Makefile
---------- a/Makefile -------------
.ONESHELL:
a.out: CALLER ?= self
a.out:
#
echo entering $# "(called by $(CALLER))"
sleep 10
echo exiting $#
---------- b/Makefile -------------
.ONESHELL:
export CALLER:=b
a.out:
$(MAKE) -C ../a
b.out: a.out
#
echo entering $#
sleep 1
echo exiting $#
---------- c/Makefile -------------
.ONESHELL:
export CALLER:=c
a.out:
$(MAKE) -C ../a
c.out: a.out
#
echo entering $#
sleep 1
echo exiting $#
---------- d/Makefile -------------
.ONESHELL:
export CALLER:=d
a.out:
$(MAKE) -C ../a
d.out: a.out
#
echo entering $#
sleep 1
echo exiting $#
jesaremi#js-u16-1:~/maketest$ cat Makefile
all: d.out c.out b.out a.out
%.out:
$(MAKE) -C $*
.PHONY:all
jesaremi#js-u16-1:~/maketest$ make
make -C d
make[1]: Entering directory '/home/jesaremi/maketest/d'
make -C ../a
make[2]: Entering directory '/home/jesaremi/maketest/a'
entering a.out (called by d)
exiting a.out
make[2]: Leaving directory '/home/jesaremi/maketest/a'
make[1]: Leaving directory '/home/jesaremi/maketest/d'
make -C c
make[1]: Entering directory '/home/jesaremi/maketest/c'
make -C ../a
make[2]: Entering directory '/home/jesaremi/maketest/a'
entering a.out (called by c)
exiting a.out
make[2]: Leaving directory '/home/jesaremi/maketest/a'
make[1]: Leaving directory '/home/jesaremi/maketest/c'
make -C b
make[1]: Entering directory '/home/jesaremi/maketest/b'
make -C ../a
make[2]: Entering directory '/home/jesaremi/maketest/a'
entering a.out (called by b)
exiting a.out
make[2]: Leaving directory '/home/jesaremi/maketest/a'
make[1]: Leaving directory '/home/jesaremi/maketest/b'
make -C a
make[1]: Entering directory '/home/jesaremi/maketest/a'
entering a.out (called by self)
exiting a.out
make[1]: Leaving directory '/home/jesaremi/maketest/a'
jesaremi#js-u16-1:~/maketest$ make -j
make -C d
make -C c
make -C b
make -C a
make[1]: Entering directory '/home/jesaremi/maketest/d'
make -C ../a
make[1]: Entering directory '/home/jesaremi/maketest/c'
make -C ../a
make[1]: Entering directory '/home/jesaremi/maketest/b'
make -C ../a
make[2]: Entering directory '/home/jesaremi/maketest/a'
make[1]: Entering directory '/home/jesaremi/maketest/a'
entering a.out (called by self)
make[2]: Entering directory '/home/jesaremi/maketest/a'
entering a.out (called by c)
make[2]: Entering directory '/home/jesaremi/maketest/a'
entering a.out (called by d)
entering a.out (called by b)
exiting a.out
make[1]: Leaving directory '/home/jesaremi/maketest/a'
exiting a.out
make[2]: Leaving directory '/home/jesaremi/maketest/a'
exiting a.out
make[1]: Leaving directory '/home/jesaremi/maketest/c'
make[2]: Leaving directory '/home/jesaremi/maketest/a'
make[1]: Leaving directory '/home/jesaremi/maketest/d'
exiting a.out
make[2]: Leaving directory '/home/jesaremi/maketest/a'
make[1]: Leaving directory '/home/jesaremi/maketest/b'

Perhaps you're thinking that recusive invocations of make communicate to each other which targets they are building and somehow interlock so that no two sub-makes are trying to build the same target at the same time.
That's nowhere close to something make can do. All make can do is notify other sub-makes how many targets they are building so that you are ensured that no more than N total targets (for -jN) are invoked.
If you want to ensure that two different makefiles don't try to build the same target, that's up to you to do by organizing your makefiles; make can't do it for you.
In the second example, you list all the prerequisites as a single target:
all: d.out c.out b.out a.out
This recipe says, "before all can be built the targets d.out, c.out, b.out, and a.out must be finished". This says nothing about the relative relationship between the prerequisites (d.out, c.out, b.out, or a.out) themselves so when you run with -j the sub-makes for all of them will be invoked at the same time. If each of those tries to build the same shared target, then they will interfere with each other.
If you want to ensure that doesn't happen you must declare the prerequisite relationship in your makefile so make knows about it. For example you could do this:
all: d.out c.out b.out a.out
d.out c.out b.out: a.out
%.out:
$(MAKE) -C $*
.PHONY:all
Now, the recipes to build d.out, c.out, and b.out won't be started until after the recipe to build a.out is complete.

Related

dependency on file created by recursive make

Here's a minimal example of what I'm trying to do.
Directory structure:
.
├── Makefile
└── subdir
├── a
└── Makefile
./subdir/Makefile:
a.copy: a
cp a a.copy
./Makefile:
.PHONY: build_subdir
a.copy: subdir/a.copy
cp subdir/a.copy a.copy
build_subdir:
$(MAKE) -C subdir
subdir/a.copy: build_subdir
The first time I run make, everything's fine:
$ make
make -C subdir
make[1]: Entering directory `/home/kkourt/src/tests/make/subdir'
cp a a.copy
make[1]: Leaving directory `/home/kkourt/src/tests/make/subdir'
cp subdir/a.copy a.copy
Re-runing make is also fine (nothing happens):
$ make
make -C subdir
make[1]: Entering directory `/home/kkourt/src/tests/make/subdir'
make[1]: `a.copy' is up to date.
make[1]: Leaving directory `/home/kkourt/src/tests/make/subdir'
If I update subdir/a, however, ./a is not updated:
$ touch subdir/a
$ make
make -C subdir
make[1]: Entering directory `/home/kkourt/src/tests/make/subdir'
cp a a.copy
make[1]: Leaving directory `/home/kkourt/src/tests/make/subdir'
If I run make a second time, ./a gets updated
$ make
make -C subdir
make[1]: Entering directory `/home/kkourt/src/tests/make/subdir'
make[1]: `a.copy' is up to date.
make[1]: Leaving director
Why does this happen? shouldn't the check of whether subdir/a.copy is older than a.copy happen after the build_subdir target is finished since it's its dependency? Is there a way to fix this?
Edit:
As MadScientist suggested, I added a dummy recipe and it works:
subdir/a.copy: build_subdir
#:
Because your rule subdir/a.copy: build_subdir has no recipe, make "knows" that it can't actually change the timestamp on subdir/a.copy.
You need add a dummy recipe to it, maybe like this:
subdir/a.copy: build_subdir ;
(note I can't test this right now but I think it'll work).

Parallel Make - Sub-Projects called multiple times

The question is already answered on file-level. But I have a bigger project which has quite a lot of inter-project-dependencies (caused by DBus headers, which were generated dynamically).
I ve created the following example (example files as ZIP - the real project is much more complex).
The top-level Makefile is the following:
sub-%:
$(MAKE) -C $(patsubst sub-%,%,$#)
default:
$(MAKE) -j12 sub-p1 sub-p2 sub-p3
The Makefile of the sub-project look like this (p1, p2 and p3):
all: p1
../lib/lib.a:
$(MAKE) -C ../lib lib.a
p1: ../lib/lib.a
cp -f ../lib/lib.a p1
And the Makefile of the lib looks like this:
lib.a:
sleep 2
date > $#
echo Done with building $#
THE PROBLEM: The library is built for each p*-project separately in parallel - in this example it's not a problem, but in our case it causes unsolvable problems.
When I call make on the top-level, I get the following output:
$ make
make -j12 sub-p1 sub-p2 sub-p3
make[1]: Entering directory '/home/kkr/tmp/parallelmake'
make -C p1
make -C p2
make -C p3
make[2]: Entering directory '/home/kkr/tmp/parallelmake/p1'
make -C ../lib lib.a
make[2]: Entering directory '/home/kkr/tmp/parallelmake/p2'
make -C ../lib lib.a
make[2]: Entering directory '/home/kkr/tmp/parallelmake/p3'
make -C ../lib lib.a
make[3]: Entering directory '/home/kkr/tmp/parallelmake/lib'
make[3]: Entering directory '/home/kkr/tmp/parallelmake/lib'
make[3]: Entering directory '/home/kkr/tmp/parallelmake/lib'
sleep 2
sleep 2
sleep 2
date > lib.a
date > lib.a
date > lib.a
make[3]: Leaving directory '/home/kkr/tmp/parallelmake/lib'
make[3]: Leaving directory '/home/kkr/tmp/parallelmake/lib'
make[3]: Leaving directory '/home/kkr/tmp/parallelmake/lib'
cp -f ../lib/lib.a p3
cp -f ../lib/lib.a p1
cp -f ../lib/lib.a p2
make[2]: Leaving directory '/home/kkr/tmp/parallelmake/p3'
make[2]: Leaving directory '/home/kkr/tmp/parallelmake/p1'
make[2]: Leaving directory '/home/kkr/tmp/parallelmake/p2'
make[1]: Leaving directory '/home/kkr/tmp/parallelmake'
QUESTION: Is it possible to synchronize the sub-projects somehow automatically?
Since the real project has 13 sub-projects - with most of them having inter-project-dependencies - a manual synchronization would be quite difficult.
There is clearly no way for a recursive make system to "automatically" synchronize this; they are completely separate processes and have no way to tell each other that they are running a given target.
You have two choices. The first, which is best but may be a lot of work, is to rework your build system so it's non-recursive. If you have a single make invocation which builds the entire system then there is only one instance of make, and it will globally coordinate the creation of all targets. You won't need the $(MAKE) -C ../lib lib.a rules at all.
If that's not feasible, then you need to rewrite your top-level makefile so that you enforce an ordering such that you never start making two directories that depend on the same external target, at the same time. For example instead of the top-level makefile you show above, write it like this:
default: p1 p2 p3
p%: ; $(MAKE) -C $#
.PHONY: default p1 p2 p3
Now suppose you want to ensure that p1 runs before p2 and p3 because you want p1 to build all the shared targets; then you'd add a rule like this to the top-level makefile:
p2 p3: p1
And you can add other orderings as needed.

Makefile: Explanation of circular dependencies on .o files

Consider the following makefile:
all : a.vo a.glob
a.gl%b a.v% : a.v
touch a.glob a.vo
When I run make -j2, I get:
$ rm -f a.vo a.glob; make -j2
make: Circular a.v.o <- a.v dependency dropped.
touch a.glob a.vo
I see why a.v.o depends on a.v, but I don't see how a.v depends on a.v.o. Where does this dependency come from (and is there a way to disable it)?
GNU make uses Implicit Rules when no rule is defined. In particular,
n is made automatically from n.o by running the linker (usually called ld) via the C compiler. The precise recipe used is ‘$(CC) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)’.
As you can see:
~ $ cat Makefile
all: a.v
echo "foo"
a.v.o:
echo "bar"
~ $ make
echo "bar"
bar
cc a.v.o -o a.v
gcc: error: a.v.o: No such file or directory
gcc: fatal error: no input files
compilation terminated.
<builtin>: recipe for target 'a.v' failed
make: *** [a.v] Error 1
cc is executed.

gnu make: how to stop and exit when errors occur in for loop make

I use 'for' loop to make subdirectories,but when some errors occur when make in subdirectory,it continues to make the next directory,can i stop when errors occur 'make' in subdirectory?
all :
for i in $(SUBDIRS); do make -C $$i dll; done;
||
make[1]: *** [bd_snmp.o] error 1
make[1]: Leaving directory `/home/ping/work/svnsocserv/src/bd_snmp'
make[1]: Entering directory `/home/ping/work/svnsocserv/src/bd_snmp_proxy'
Sure, just add something to your shell script that checks:
all:
for i in $(SUBDIRS); do $(MAKE) -C $$i dll || exit 1; done
(note ALWAYS use $(MAKE) to recursively invoke sub-makes, never make).
This is not a great method though, because while it does exit immediately when a sub-make fails it doesn't obey the make -k option to keep going.

makefile calling makefile error

I have a working make, I have platform code and like several makes for each os in the folder. Right now I have one makefile which works. I renamed it to Makefile.ws and wrote this in Makefile
all:
make -f Makefile.w32
clean:
make -f Makefile.w32 clean
I ran it and got this error
> "make"
make -f Makefile.w32
make[1]: Entering directory `/c/nightly/test'
make -f Makefile.w32
make[3]: Makefile.w32: No such file or directory
make[3]: *** No rule to make target `Makefile.w32'. Stop.
make[2]: *** [all] Error 2
make[1]: *** [build] Error 2
make[1]: Leaving directory `/c/nightly/test'
"make": *** [all] Error 2
Oddly enough the clean works perfectly. Then I decided to write "make -f Makefile.w32 mingw32" and that did not work correctly. In fact it made a folder called mingw32 which I thought was very strange.
As for the mingw32 rule I just copy build which I suspect is the main/normal rule that is used to build
$(BUILD):
#[ -d $# ] || mkdir -p $#
#make --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
mingw32:
#[ -d $# ] || mkdir -p $#
#make --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
full .w32 source is here http://pastie.org/320035
First, what make are you running? Cygwin or MinGW, or something else?
make -f Makefile.w32
make[1]: Entering directory `/c/nightly/test'
make -f Makefile.w32
make[3]: Makefile.w32: No such file or directory
"Entering directory" is a hint. Why is it entering /c/nightly/test? Is there a Makefile.w32 there?
As to creating the directory "mingw32", the rule
mingw32:
#[ -d $# ] || mkdir -p $#
...
does exactly that. If "mingw32" does not exist, it creates it.
It would be easier to help you if you had a shorter example and clearly explain what you want to accomplish and what you expect to happen.
I think I see the problem in your second example. The mingw32 line should be changed so that it does not include the $(BUILD) variable:
mingw32:
#[ -d $# ] || mkdir -p $#
#make --no-print-directory -C mingw32 -f $(CURDIR)/Makefile
It is clear that he created the directory,
your first command in your given 2 rules, include a mkdir with the object name, i.e. either build or mingw32
Afterwards he changes into the current directory (i.e. no cd at all) and execute Makefile. But as you wrote you renamed Makefile into Makefile.ws, so offcourse you are getting File-Not-Found-error.
From the questions I can only recommend you to have a look at an short introduction or even better the manual for make.
Have you tried calling the secondary makefile using
$(MAKE) -f ...
instead of
make -f ...?

Resources