Building GNU Make

These notes are about details left off from this post about a bug that has bitten me on an (old) version of GNU Make. So this is a complementary material where I lay out the steps to perform the tests I mentioned there.

In some rare cases the bug is on the tool.


Trivia 1: The first release of GNU Make is probably from 1988 whereas git was first released in 2005.

Trivia 2: GNU Make has been used as the build system for the git project itself since its beginning, of course.

Trivia 3: GNU Make itself started to use git, apparently, in 2013. Before that, it used a source-code management tool from GNU called RCS. The RCS “commits” — including the ones before the migration — seem to have been converted into git commits as well.


GNU Make seems to have two different kinds of source code — one is a git repository as you would expect, and the other is a set of tarballs distributed in a “FTP” page. I don’t know the exact difference between them nor the rationale behind it, but the tarballs seem to be the release versions in source-code form and this source-code comes in an already “pre-processed” way that’s a little bit easier to build.

We’re going to see these three things:

  1. How to build GNU Make from a tarball release.
  2. How to build GNU Make from git.
  3. And how to build some specific versions of GNU Make to test an idea brought up in the post.

So, let’s get started.

#1. Building GNU Make from a tarball

This was the first way I built it because… I don’t remember… Probably because it was one of the first results that appeared on a Google search for “gnu make 3.81 download”…

"FTP" page with the source-code releases of GNU make

https://ftp.gnu.org/gnu/make/
“FTP” page with the tarball releases of GNU make.

So, from within a command line, we could do this:

$ wget https://ftp.gnu.org/gnu/make/make-3.81.tar.gz
$ tar xf make-3.81.tar.gz
$ cd make-3.81/

Building using the ./configure-and-then-make strategy didn’t work here.

Yes, the default GNU Make build system uses Make. So Make is used to build itself.

The ./configure did work but the actual build with make errored out with a __alloca missing symbol:

$ ./configure
...
...
...
$ make -j $(nproc)
gcc  -g -O2   -o make  ar.o arscan.o commands.o default.o dir.o expand.o file.o function.o getopt.o getopt1.o implicit.o job.o main.o misc.o read.o remake.o remote-stub.o rule.o signame.o strcache.o variable.o version.o vpath.o hash.o glob/libglob.a  
/usr/bin/ld: glob/libglob.a(glob.o): in function `glob_in_dir':
[...]/make-3.81/glob/glob.c:1361: undefined reference to `__alloca'
/usr/bin/ld: [...]/make-3.81/glob/glob.c:1336: undefined reference to `__alloca'
/usr/bin/ld: [...]/make-3.81/glob/glob.c:1250: undefined reference to `__alloca'
/usr/bin/ld: [...]/make-3.81/glob/glob.c:1277: undefined reference to `__alloca'
/usr/bin/ld: glob/libglob.a(glob.o): in function `glob':
[...]/make-3.81/glob/glob.c:575: undefined reference to `__alloca'
/usr/bin/ld: glob/libglob.a(glob.o):[...]/make-3.81/glob/glob.c:726: more undefined references to `__alloca' follow
collect2: error: ld returned 1 exit status
make[2]: *** [Makefile:411: make] Error 1
make[2]: Leaving directory '[...]/make-3.81'
make[1]: *** [Makefile:603: all-recursive] Error 1
make[1]: Leaving directory '[...]/make-3.81'
make: *** [Makefile:326: all] Error 2

Stack Overflow had a trick to workaround this problem on Ubuntu 20.04. The fix consists of a patch that, despite not appearing very reliable, works nonetheless.

Stack Overflow answer to the build problem

https://stackoverflow.com/a/71541898
The trick to fix GNU Make 3.81 build error.

The exact lines to change in the make-3.81/glob/glob.c are these:

@@ -181,7 +181,7 @@
 # define mempcpy(Dest, Src, Len) __mempcpy (Dest, Src, Len)
 #endif
 
-#ifdef __GNU_LIBRARY__
+#ifndef        __GNU_LIBRARY__
 # ifdef        __GNUC__
 __inline
 # endif
@@ -270,7 +270,7 @@
 
 /* Some system header files erroneously define these.
    We want our own definitions from <glob.h> to take precedence.  */
-#ifndef __GNU_LIBRARY__
+#ifdef __GNU_LIBRARY__
 # undef        GLOB_ERR
 # undef        GLOB_MARK
 # undef        GLOB_NOSORT

So, that was it, the patch fixed the build and I got a working 3.81 version.

#2. How to build GNU Make from git

I needed this to test the version of Make where the fix for the bug discussed in the post was made. As shown there, the fix commit was e97159745d3359285cef535af780cd8e2b6b0791, so I’m going to build it on the exact version of this commit.

$ git clone https://git.savannah.gnu.org/git/make.git
$ cd make
$ git checkout e97159745d3359285cef535af780cd8e2b6b0791

As said earlier, the git version of GNU Make is different in a way that requires you to initialize/setup the GNU autotools stuff in the code base. The way to do that, for the version from the above commit, is this:

$ autoreconf -i
Copying file config/config.rpath
Copying file config/codeset.m4
Copying file config/extern-inline.m4
Copying file config/fcntl-o.m4
...
Copying file config/xsize.m4
configure.ac:45: installing 'config/ar-lib'
configure.ac:35: installing 'config/compile'
configure.ac:48: installing 'config/config.guess'
configure.ac:48: installing 'config/config.sub'
configure.ac:32: installing 'config/install-sh'
configure.ac:32: installing 'config/missing'
Makefile.am: installing 'config/depcomp'
doc/Makefile.am:21: installing 'config/mdate-sh'
doc/Makefile.am:21: installing 'config/texinfo.tex'

After that, you can use the configure script:

$ ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /usr/bin/mkdir -p
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking whether make supports nested variables... yes
checking whether make supports the include directive... yes (GNU style)
checking for gcc... gcc
checking whether the C compiler works... yes
...
configure: creating ./config.status
config.status: creating Makefile
config.status: creating glob/Makefile
config.status: creating po/Makefile.in
config.status: creating config/Makefile
config.status: creating doc/Makefile
config.status: creating w32/Makefile
config.status: creating tests/config-flags.pm
config.status: creating config.h
config.status: executing depfiles commands
config.status: executing po-directories commands
config.status: creating po/POTFILES
config.status: creating po/Makefile

And then, again, you’ll need to patch the glob/glob.c file to workaround the missing __alloca issue. The patch is the same for this version with only the line numbers being different:

@@ -208,7 +208,7 @@
 #endif /* __GNU_LIBRARY__ || __DJGPP__ */
 
 
-#if !defined __alloca && !defined __GNU_LIBRARY__
+#if !defined __alloca && defined __GNU_LIBRARY__
 
 # ifdef        __GNUC__
 #  undef alloca
@@ -231,7 +231,7 @@
 
 #endif
 
-#ifndef __GNU_LIBRARY__
+#ifdef __GNU_LIBRARY__
 # define __stat stat
 # ifdef STAT_MACROS_BROKEN
 #  undef S_ISDIR

After patching and running make, you’ll can see that the Make version is 4.1.90 instead of 3.81:

$ ./make
GNU Make 4.1.90
Built for x86_64-pc-linux-gnu
Copyright (C) 1988-2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

This makes sense given the bug page seen in the main post where it displays ‘Component Version’ as 4.1.

#3. Building a GNU Make version without the fix commit

The ideia in the main post was a test to see if the found bug — and associated commit — were actually the bug in question. So, the test is simple: build a GNU Make version without the fix commit. If the bug reproduces with this version, then the hypothesis was right — that was indeed the bug.

As I said back there, this is indeed the case.

It’s appropriate to note here that we won’t just take the latest version of GNU Make — the one from the HEAD of the master branch in this case, which at the time of writing is version 4.3.91 — and just revert the commit. That wouldn’t easily work due to the commit being too old and hence a simple git revert wouldn’t work because the files the commit touches changed too much or simply don’t exist anymore. I tried it, of course, just in case, and got conflicts in lots of files, and fixing them would require much more time than I was willing to spend on this.

The approach I chose was to do a git checkout of the commit just before the fix commit. That can be done using the ~ (tilde) suffix in git which means ‘parent commit’.

Before that, let’s just clean up the working tree from the compilation artifacts generated on the previous section and, importantly, save the patch applied to glob/glob.c. We we can just put it in the git stash:

$ make clean
...

$ git stash push glob/glob.c
Saved working directory and index state WIP on (no branch): e9715974 [SV 46995] Strip leading/trailing space from variable names

Now we can check out the commit before the fix:

$ git checkout e97159745d3359285cef535af780cd8e2b6b0791~1
Previous HEAD position was e9715974 [SV 46995] Strip leading/trailing space from variable names
HEAD is now at 2b9dd215 * function.c (func_file): Support reading from files.

$ git status
HEAD detached at 2b9dd215
...

So we checked out the commit 2b9dd215d588a1b9aa8eaa398e567414afaafce8 which is the parent of the fix commit.

We can now restore the patch on the glob/glob.c file:

$ git stash pop

After that, the steps to compile GNU Make are the same as the ones on the previous section:

$ ./configure
$ make -j $(nproc)
$ ./make --version
GNU Make 4.1.90
Built for x86_64-pc-linux-gnu
Copyright (C) 1988-2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Now, if we test this make binary with the one from the previous section, we’ll see the one here reproduces the error — ie., it has the bug — whereas the one from the previous section doesn’t.

#Bonus: building latest GNU Make version

The steps to do this are in the README.git file in the root of the repository. In our case, we could do this:

## Discard the patch 
$ git restore glob/glob.c

$ make clean

$ git checkout master

## These are steps from the README.git:
$ ./bootstrap
$ ./configure
$ make -j $(nproc)