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:
- How to build GNU Make from a tarball release.
- How to build GNU Make from git.
- 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”…
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.
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)