Skip to content
HN On Hacker News ↗

Recursive make and -j

▲ 33 points • 14 comments • by ibobev • 1w ago • HN discussion ↗

Pangram verdict · v3.3

We believe that this entire text is human-written.

0 %

AI likelihood · overall

Human
100% human-written 0% AI-generated
SEGMENTS · HUMAN 1 of 1
SEGMENTS · AI 0 of 1
WORD COUNT 875
PEAK AI % 0% · §1
Analyzed
Sep 30
backend: pangram/v3.3
Segments scanned
1 windows
avg 875 words each
Distribution
100 / 0%
human / AI fraction
Verdict
Human
Pangram v3.3

Article text · 875 words · 1 segments analyzed

Human AI-generated
§1 Human · 0%

Today I looked into “recursive” invocations of make; that is, cases in which a Makefile recipe itself invokes make. The official documentation is quite good, but since I’m working with a range of different GNU Make versions, I looked at how the behavior has changed over time, from GNU Make 3.81 to 4.3. Basically, the issue is that if you have a recipe in your Makefile like this: ser: make -C subdir and then you run make -j4 ser, recent versions of GNU Make will give a warning (jobserver unavailable: using -j1). And you’ll lose the parallelism you wanted, because the “jobserver” (basically, the -j option) isn’t forwarded to the child make. And, incidentally, it won’t necessarily use the same make binary that you invoked; it’ll run whatever make happens to be in your path. You can fix all three of those issues by prefixing + to the recipe line, or (since that feels uncomfortably magical) replacing make with $(MAKE). More on this at the end of the post. Now that we’re forwarding the “jobserver” properly, I wondered, how exactly do the various -j options interact, when you specify one degree of parallelism on the command line and another in the recursive recipe? Here’s the pair of Makefiles I used for my test: detab -E -4 - >Makefile <<EOF ser: make -C subdir par2: make -j2 -C subdir par4: make -j4 -C subdir par: make -j -C subdir serplus: +make -C subdir par2plus: +make -j2 -C subdir par4plus: +make -j4 -C subdir parplus: +make -j -C subdir sermake: $(MAKE) -C subdir par2make: $(MAKE) -j2 -C subdir par4make: $(MAKE) -j4 -C subdir parmake: $(MAKE) -j -C subdir EOF mkdir subdir detab -E -4 - >subdir/Makefile <<EOF all: one two three four five one: echo 'one' && sleep 1 && echo 'ONE' two: echo 'two' && sleep 1 && echo 'TWO' three: echo 'thr' && sleep 1 && echo 'THR' four: echo 'fou' && sleep 1 && echo 'FOU' five: echo 'fiv' && sleep 1 && echo 'FIV' EOF Then I tested running make ser, make -j2 ser, etc., on various versions of Make available to me. Here’s GNU Make 3.81 (on OS X). The number in each cell indicates the number of recipes running in parallel. The four rows marked with a dagger † differ among the versions of Make I tested; those idioms are “non-portable” and IMHO should be avoided in practice. make… -j2 -j4 -j ser† 1 2 4 ∞ serplus 1 2 4 ∞ sermake 1 2 4 ∞ par2 2 2b 2b 2 par2plus 2 2b 2b 2 par2make 2 2b 2b 2 par4 4 4b 4b 4 par4plus 4 4b 4b 4 par4make 4 4b 4b 4 par† ∞ 2 4 ∞ parplus† ∞ 2 4 ∞ parmake† ∞ 2 4 ∞ In the table above, “b” indicates this warning: $ make -j2 par2 make[1]: warning: -jN forced in submake: disabling jobserver mode. GNU Make 4.0 (on Debian 8.11): make… -j2 -j4 -j ser† 1 1a 1a ∞ serplus 1 2 4 ∞ sermake 1 2 4 ∞ par2 2 2b 2b 2 par2plus 2 2b 2b 2 par2make 2 2b 2b 2 par4 4 4b 4b 4 par4plus 4 4b 4b 4 par4make 4 4b 4b 4 par† ∞ 1a 1a ∞ parplus† ∞ 2 4 ∞ parmake† ∞ 2 4 ∞ In the table above, “a” and “b” indicate these two warnings, respectively: $ make -j2 ser make[1]: warning: jobserver unavailable: using -j1. Add '+' to parent make rule. $ make -j2 par2 make[1]: warning: -jN forced in submake: disabling jobserver mode. GNU Make 4.2.1 (on Rocky 8.10) and GNU Make 4.3 (on Red Hat 9 or Ubuntu 22.04) tweak the latter error message to include the value of N: $ make -j2 par make[1]: warning: -j0 forced in submake: resetting jobserver mode. $ make -j2 par2 make[1]: warning: -j2 forced in submake: resetting jobserver mode. and also change the behavior of rows par, parplus, and parmake: make… -j2 -j4 -j ser† 1 1a 1a ∞ serplus 1 2 4 ∞ sermake 1 2 4 ∞ par2 2 2b 2b 2 par2plus 2 2b 2b 2 par2make 2 2b 2b 2 par4 4 4b 4b 4 par4plus 4 4b 4b 4 par4make 4 4b 4b 4 par† ∞ ∞b ∞b ∞ parplus† ∞ ∞b ∞b ∞ parmake† ∞ ∞b ∞b ∞ Warning “a,” which explicitly tells you to “Add '+' to parent make rule,” will indeed always vanish if you prefix the make-containing line with +, like this: serplus: +make -C subdir I find that solution as uncomfortably magical as prefix @ or prefix -. (Personally, I’d rather see “|| true” at the end of a recipe line than a magic “-” at the beginning.) A solution that’s less magic-looking (although equally magic under the hood, apparently) is to use $(MAKE), like this: sermake: $(MAKE) -C subdir Neither method silences warning “b,” about “resetting jobserver mode,” though. What we really want is a row that would look like fantasy below. But at the moment, it seems we have to choose our preferred tradeoff from one of the quoted rows, instead. make… -j2 -j4 -j fantasy nprocs 2 4 ∞ sermake 1 2 4 ∞ par4make 4 4b 4b 4