Many years ago, when building large software projects, I used GNU makeakagmake (or my own patched version of it), and had developed aset of macros to simplify developing complex build trees.
Since the early 90's (when my Sun workstation got zapped by a lighting strike)my main development machines, run BSD(NetBSD andFreeBSD).The BSD source tree is a good example of a large software project.In following NetBSD's -current development, I learned to use theBSD Makefiles andmake(1).It was a vast improvement over anything I'd seen with gmake.I did an autoconf version of NetBSD's make that I'll refer to asbmake(1) from here on.
Since then all my new projects and many of my old ones use bmake.Becausebmake.tar.gzuses GNU's autoconf and is very portable, I've skipped the effort ofmaking the rest of my distributions support GNU make andthanks tomk-files I don't have to consider usingautomake whichproduces huge unreadable makefiles.
As noted above; bmake is derived from NetBSD's make(1), its goal is tobe a portable version of same, so new features are added via importsof NetBSD's make (I'm one of the contributors to NetBSD).Thus bmake is kept in sync with NetBSD's make.
For those interested in change history, the NetBSD mirror on github is handy.Almost all meaningful changes to bmake can be seen here:https://github.com/NetBSD/src/tree/trunk/usr.bin/make
Since 2000 I've worked on Junos (aFreeBSD derived OS)which builds using bmake (inmeta mode).FreeBSD 10.0 and later also use bmake (I'm a FreeBSD committer as well).
Since bmake-20121212 the distribution includes themk/* filesfrom mymk.tar.gz.This a collection of*.mk files (many originally derrived fromearly NetBSD versions) that can augment and even substitute forbsd.*.mk (use the real ones if you have them).
Includingmk.tar.gz with bmake helps those who just want:
./configuremakemake install
to work as they expect.
Note: on Darwin (OS/X) or anything else with a case insensitivefilesystem, the above method will fail since itcannot differentiatemakefile fromMakefile, resulting ininfinite recursion.Actually for Darwinconfigure knows to disable the generation ofmakefile by default.Regardless, the simple expedient of using a separate directory forbuilding in, will work:
mkdir objcd obj../configuremakemake install
Since about 2003, the bmake version typically represents the date ofimport from NetBSD, and I avoid any code changes in bmake which are notstrictly related to portability. All new features come via NetBSD make.
It may be interesting to note that I have projects where much of thetree uses the same simple Makefile:
PROG= ${.CURDIR:T}.include <prog.mk>In fact you could just have a default Makefile for the whole project(in found viaMAKESYSPATH) that did something like:
.if ${.CURDIR:M*lib/*}LIB = ${.CURDIR:T:S,^lib,,}.include <lib.mk>.elsePROG = ${.CURDIR:T}.include <prog.mk>.endifbut that might be going too far ;-)
The example Makefile above, perhaps suggests why I likebmake(1)The important magic is in the line:
.include <prog.mk>
Makefiles for libraries includelib.mk btw. Anyway, apartfrom reading a bunch of rules from the system macros (/usr/share/mk/*by default) it reads"../Makefile.inc" thus providing a hook for muchmagic.
In the early 90's NetBSD'sbsd.*.mk were strictly targeted at building theNetBSD src tree, and were not as flexible or portable as I wanted.So I started on my ownmk-files.
A very powerful feature ofbmake results from its handling ofmakefiles, the following are read in this order:
sys.mk
Found via${MAKESYSPATH} this makefile (and anything itincludes) provide for initial rules and settings.
Makefile
Actually the first member of the list${.MAKE.MAKEFILE_PREFERENCE} (default ismakefileMakefile) that is found, and anything it includes, such asbsd.lib.mk.
.depend
Actually whatever${.MAKE.DEPENDFILE} is set to, if itexists; is read, and anything it includes.This makefile is special in that bmake is told to be tollerantofstale dependencies, this same property applies to anymakefile included using.dinclude.
The above provide for very elaborate arrangements liketheDIRDEPS_BUILD.
As of 20220418 if the special target.POSIX: is encountered,as the first non-comment line of the main makefile (as specified byPOSIX),bmake will try to includeposix.mk to provide forPOSIX compatible default rules.
Another very cool feature ofbmake(1) is the built in distinctionbetween${.CURDIR} (wherebmake was launched) and${.OBJDIR} (where it is working).
This behavior can be confusing to those unfamiliar with it.The algorithm for finding an object dir to work in can be expressedusing shell syntax:
for __objdir in ${MAKEOBJDIRPREFIX}${.CURDIR} \ ${MAKEOBJDIR} \ ${.CURDIR}/obj.${MACHINE} \ ${.CURDIR}/obj \ /usr/obj${.CURDIR} \ ${.CURDIR}do if [ -d ${__objdir} -a ${__objdir} != ${.CURDIR} ]; then break fidoneIn the simplest case, if the directoryobj exists in thedirectory bmake was invoked in, then bmake will chdir into it before doinganything else. This helps keep src and object files separate.IfOBJMACHINE is defined, then the default object dir isobj.${MACHINE}. Note thatobj can be a symlink off to aseparate file system, eg.:
/usr/src/bin/cat/obj -> /usr/obj/bin/cat
in either case, building for multiple architectures is easy.
Even better; ifMAKEOBJDIRPREFIX is set in the environment(see below) then it is trivial to export a source tree read-only,since there is no longer a need forobj dirs (or symlinks) in thetree itself.
The only hassle withMAKEOBJDIR andMAKEOBJDIRPREFIX is thatthey need to be set in the environment.Actually recent versions of bmake allow the objdir to be set later,by use of.OBJDIR:, but settingMAKEOBJDIR* in the environment issimpler. This feature is used if you usemk-files and setMKOBJDIRS=auto.
IfMAKEOBJDIRPREFIX is set in the environment and the directory${MAKEOBJDIRPREFIX}${.CURDIR} (.CURDIR is set by getcwd()) exists,make(1) will chdir into it rather than look for./obj as describedabove.
This simple feature allows true read-only source trees.Note thatMAKEOBJDIRPREFIX is actioned before any Makefiles areread which is why it must be an environment variable.
Bmake also allows variable substitutions onMAKEOBJDIRwhich makes it even better thanMAKEOBJDIRPREFIX.For a neater result you can use something like:
export MAKEOBJDIR="\${.CURDIR:S,$SRCTOP,$OBJTOP,}"which would result in:
$ bmake -C /usr/src/bin/cat -V SRCTOP -V OBJTOP -V .CURDIR -V .OBJDIR/usr/src/var/obj/usr/src/bin/cat/var/obj/bin/cat$
The variablesMACHINE andMACHINE_ARCH are built into bmake.They can also be controlled via the makefiles for cross-building.
In some cases a range of cpu's have a common architecture suchasmips orm68k.MACHINE_ARCH is used to selectcommon include dirs, toolchains etc.
Actually in FreeBSD, there can be multipleMACHINE_ARCHfor a singleMACHINE.
When cross building it can be useful to differentiate thingsbuilt for the host rather than the target.HOST_MACHINE andHOST_MACHINE_ARCH as well asTARGET_* can be useful.
I tend to use the pseudoMACHINEhost, but use${HOST_TARGET} for its objdir so that the same tree can besafely built by mutiple hosts without confusion.
The latest bmake should have an OpenPGP detached signature:
lrwxrwxr-x 1 sjg wheel 21 Apr 26 16:41 bmake.tar.gz -> bmake-20210420.tar.gzlrwxrwxr-x 1 sjg wheel 25 Apr 26 16:41 bmake.tar.gz.asc -> bmake-20210420.tar.gz.asc
The public key for this can be found inhttps://www.crufty.net/ftp/pub/sjg/Crufty.pub.asc
In theory it should also be available from keyservers like pgp.mit.edu.Though I get an error...
Its full key-id is7E228507C26D8DC164F531BFBA54C8AF755A2A99the OpenPGP key-id isBA54C8AF755A2A99
You can try to find/import the above key using:
gpg --search-keys sigs@crufty.net
or:
gpg --receive-keys 7E228507C26D8DC164F531BFBA54C8AF755A2A99
or more reliably from the file above:
gpg --import Crufty.pub.asc
after which you should be able to:
gpg --openpgp --verify bmake.tar.gz.ascgpg: assuming signed data in 'bmake.tar.gz'gpg: Signature made Mon Apr 26 16:29:27 2021 PDTgpg: using RSA key BA54C8AF755A2A99gpg: Good signature from "sigs (OpenPGP) <sigs@crufty.net>" [unknown]Primary key fingerprint: 7E22 8507 C26D 8DC1 64F5 31BF BA54 C8AF 755A 2A99
Sincebmake-20121212 a simple makefile is provided for the benefitof folk who simply want to:
$ tar zxf ~/bmake-$MAKE_VERSION.tar.gz$ cd bmake$ configure$ make# make install
wheremake is not bmake.
As noted earlier; the above will fail on systems like Darwin where it isnecessary to separate the build from the srcs.This is approximately whatbmake/boot-strap does:
$ tar zxf ~/bmake-$MAKE_VERSION.tar.gz$ mkdir obj$ cd obj$ ../bmake/configure$ make# make install
Most of the time though, to build and install bmake I typically do:
$ cd ~/tmp$ tar zxf ~/bmake-$MAKE_VERSION.tar.gz$ ./bmake/boot-strap --prefix=$HOME --install-host-target -DWITH_PROG_VERSION
This gets me:
$HOME/$HOST_TARGET/bin/bmake-$MAKE_VERSION$HOME/$HOST_TARGET/bin/bmake -> bmake-$MAKE_VERSION$HOME/share/mk/*$HOME/share/man/man1/bmake.1.gz
Note: the trivialmakefile just leveragesboot-strap.
If you already havebmake (or equivalent) available, the trivialmakefile is not necessary and in fact quite anoying (see theJuniper example below).
Thus as ofbmake-20181222 if you just unpack the tarballand runbmake, theMakefile will add--without-makefileto theconfigure command to suppress the above.
So you can just:
$ cd ~/tmp$ tar zxf ~/bmake-$MAKE_VERSION.tar.gz
Note: if you want to keep generated files out of the src directorythen you have to useMAKEOBJDIRPREFIX (or the sameMAKEOBJDIRsetup asboot-strap does),so that theunit-tests/Makefile.config will be generated in thecorrect place.Eg.:
$ mkdir /tmp/obj$ export MAKEOBJDIRPREFIX=/tmp/obj$ export WITH_AUTO_OBJ=1
(note: the above syntax is for a POSIX compatible shell)If you just use the defaultobj dir in each src directory, we'd getbmake/obj/unit-tests/Makefile.config which would not be visibleinbmake/unit-tests/obj/.
Then:
$ cd bmake$ bmake$ bmake test# bmake install
willjust work.
At Juniper I have a set of Mercurial repos to facilitatetesting new versions before importing into the official repository(we've used bmake to build Junos for over 20 years).
They are all setup in a single tree:
bmake/cruftybmake/junosbmake/freebsd
Each contains abmake subdir containing the actual code.There are of course variousMakefile.inc files setting all theoptions I use like:
WITH_PROG_VERSION=1WITH_PROG_LINK=1WITH_AUTO_OBJ=1.if defined(.MAKE.PATH_FILEMON).MAKE.MODE += meta verbose silent=yes.endif
Thecrufty repo tracks pristine bmake from upstream.
Thejunos repo has a couple of local tweaks.
Thefreebsd repo also mimics the local tweaks in FreeBSD - mostlyto facilitate the transition from their older make tobmake.
Thejunos andfreebsd repos justhg pull from thecrufty one, and in each case:
mk &&mk test &&mk install
does all that is needed, to get the latest$HOME/$HOST_TARGET/bin/bmake-$MAKE_VERSION ready for testing.
In this setup, that trivialmakefile gets in the way.Withbmake-20181222 and laterbmake/Makefile usesconfigure--without-makefile, so I don't need to specify-f Makefile.
The scriptmk above is part of mysb-tools collection and makesit easy to condition the environment for building a specific project.Especially useful for Emacs users.
Each new version of bmake goes through the above on severalplatforms (NetBSD, FreeBSD, SunOS, Linux, OS/X),and I typically spend a week building Junos with it before posting.Due to its size and complexity; the Junos build is a good torture testfor make.
The example below is building bmake-20121212 on SunOS.Note that the build isn't considered successful unless the unit-tests pass.
Also note that the src directories are left untouched and ahost-target specific objdir is used. Thus I can use the exact samecommand sequence to build bmake for all the platforms available:
$ ./bmake/boot-strap --prefix=$HOME --install-host-target -DDWITH_PROG_VERSIONNOTE: default prefix=/homes/sjg INSTALL_BIN=sunos5-sparc/binNOTE: reading /homes/sjg/.bmake-boot-strap.rcBuilding for sunos5-sparcchecking for gcc... /usr/local/bin/gccchecking for C compiler default output file name... a.outchecking whether the C compiler works... yeschecking whether we are cross compiling... nochecking for suffix of executables......checking if diff -u works... yeschecking for MACHINE & MACHINE_ARCH...defaults: MACHINE=sunos5, MACHINE_ARCH=sparcUsing: MACHINE=sunos5, MACHINE_ARCH=sparcUsing: MKSRC=${srcdir}/mkUsing: SHELL=/usr/xpg4/bin/shconfigure: creating ./config.statusconfig.status: creating makefileconfig.status: creating Makefile.configconfig.status: creating make-bootstrap.shconfig.status: creating unit-tests/Makefileconfig.status: creating config.hYou can now run sh ./make-bootstrap.shto produce a fully functional bmake./usr/local/bin/gcc -c -g -O2 -I. -I/homes/sjg/tmp/bmake -DHAVE_CONFIG_H -DNEED_MAKE_LEVEL_SAFE -I/homes/sjg/tmp/bmake/missing -DMAKE_NATIVE -DUSE_META -DMAKE_VERSION="20121212" -DMACHINE="sunos5" -DMACHINE_ARCH="sparc" -D_PATH_DEFSYSPATH="/homes/sjg/share/mk" -o main.o /homes/sjg/tmp/bmake/main.c.../usr/local/bin/gcc -c -g -O2 -I. -I/homes/sjg/tmp/bmake -DHAVE_CONFIG_H -DNEED_MAKE_LEVEL_SAFE -I/homes/sjg/tmp/bmake/missing -DMAKE_NATIVE -DUSE_META -o meta.o /homes/sjg/tmp/bmake/meta.c/usr/local/bin/gcc -o bmake main.o meta.o arch.o buf.o compat.o cond.o dir.o for.o getopt hash.o job.o make.o make_malloc.o parse.o sigcompat.o str.o strlist.o suff.o targ.o trace.o var.o util.o lstAppend.o lstDupl.o lstInit.o lstOpen.o lstAtEnd.o lstEnQueue.o lstInsert.o lstAtFront.o lstIsAtEnd.o lstClose.o lstFind.o lstIsEmpty.o lstRemove.o lstConcat.o lstFindFrom.o lstLast.o lstReplace.o lstFirst.o lstDatum.o lstForEach.o lstMember.o lstSucc.o lstDeQueue.o lstForEachFrom.o lstDestroy.o lstNext.o lstPrev.o stresep.ocd /homes/sjg/tmp/bmake/unit-tests && MAKEFLAGS= /homes/sjg/tmp/sunos5-sparc/bmake -r -m / TEST_MAKE=/homes/sjg/tmp/sunos5-sparc/bmake test/homes/sjg/tmp/sunos5-sparc/bmake -f /homes/sjg/tmp/sunos5-sparc/unit-tests/Makefile > test.out 2>&1/usr/local/bin/diff -u /homes/sjg/tmp/bmake/unit-tests/test.exp test.out[ -d /homes/sjg/sunos5-sparc/bin ] || /homes/sjg/tmp/bmake/install-sh -d -o sjg -g 705 -m 775 /homes/sjg/sunos5-sparc/bin/homes/sjg/tmp/bmake/install-sh -c -s -o sjg -g 705 -m 555 bmake /homes/sjg/sunos5-sparc/bin/bmake-20121212test -d /homes/sjg/sunos5-sparc/bin || /homes/sjg/tmp/bmake/install-sh -m 775 -d /homes/sjg/sunos5-sparc/bintest -d /homes/sjg/share/man/cat1 || /homes/sjg/tmp/bmake/install-sh -m 775 -d /homes/sjg/share/man/cat1/homes/sjg/tmp/bmake/install-sh -c -o sjg -g 705 -m 444 /homes/sjg/tmp/bmake/bmake.cat1 /homes/sjg/share/man/cat1/bmake.1/homes/sjg/sunos5-sparc/bin/bmake -> bmake-20121212test -d /homes/sjg/share/mk || /homes/sjg/tmp/bmake/install-sh -m 775 -d /homes/sjg/share/mksh /homes/sjg/tmp/bmake/mk/install-mk -v -m 644 /homes/sjg/share/mkcp -f sys.mk auto.obj.mk autoconf.mk autodep.mk auto.dep.mk dep.mk doc.mk dpadd.mk final.mk host-target.mk host.libnames.mk inc.mk init.mk java.mk lib.mk libnames.mk libs.mk links.mk man.mk nls.mk obj.mk options.mk own.mk prlist.mk prog.mk progs.mk rst2htm.mk scripts.mk srctop.mk subdir.mk sys.clean-env.mk sys.dependfile.mk target-flags.mk warnings.mk yacc.mk dirdeps.mk gendirdeps.mk install-new.mk meta.sys.mk meta.autodep.mk meta.stage.mk meta.subdir.mk /homes/sjg/share/mkcp -f sys/AIX.mk sys/Darwin.mk sys/Generic.mk sys/HP-UX.mk sys/IRIX.mk sys/Linux.mk sys/NetBSD.mk sys/OSF1.mk sys/OpenBSD.mk sys/SunOS.mk sys/UnixWare.mk /homes/sjg/share/mk/syscp -f meta2deps.py meta2deps.sh /homes/sjg/share/mkchmod 644 sys.mk auto.obj.mk autoconf.mk autodep.mk auto.dep.mk dep.mk doc.mk dpadd.mk final.mk host-target.mk host.libnames.mk inc.mk init.mk java.mk lib.mk libnames.mk libs.mk links.mk man.mk nls.mk obj.mk options.mk own.mk prlist.mk prog.mk progs.mk rst2htm.mk scripts.mk srctop.mk subdir.mk sys.clean-env.mk sys.dependfile.mk target-flags.mk warnings.mk yacc.mk dirdeps.mk gendirdeps.mk install-new.mk meta.sys.mk meta.autodep.mk meta.stage.mk meta.subdir.mk sys/AIX.mk sys/Darwin.mk sys/Generic.mk sys/HP-UX.mk sys/IRIX.mk sys/Linux.mk sys/NetBSD.mk sys/OSF1.mk sys/OpenBSD.mk sys/SunOS.mk sys/UnixWare.mkchmod 555 meta2deps.py meta2deps.shFolk who just runboot-strap as above probably won't notice, butas ofbmake-20100222, there is no longer a need for a native makeprogram during the bootstraping of bmake. A simple shell script(contibuted by joerg at netbsd.org) takes the place ofmakefile.boot.
The build ofbmake includes checking that its unit tests all pass.In recent years the test suite has been considerably extended (mostly byrillig at netbsd.org).
Much as we try to make the test suite portable, some tests simply willnot work on some platforms.
Such tests are added toBROKEN_TESTS so that they will be skipped.For example:
shell-ksh is broken on Darwin - the shell segfaults.
And about half a dozen tests are broken on SCO_SV.
If you find a test that fails on your platform, and conclude it is dueto some quirk of the OS, you can add it toBROKEN_TESTS in yourenvironment or in the makefile, so you can check that everything elseis working ok. Sending feedback would be a good idea.
Since 2010-09 bmake supportsmeta mode - contributed by Juniper Networks.The filemon kernel module is currently available inNetBSD and FreeBSD.FreeBSD 10 and later usebmake.
Since bmake-20200121 on NetBSD, bmake usesfilemon_ktrace whichuses the fktrace(2) api. This avoids the need for a separate filemondriver. Thefilemon_dev interfaces is used on other platforms tointerract with the filemon driver.
A version of filemon for Linux can be obtained fromhttps://github.com/trixirt/filemon-linux.gitthough it could probably use some serious TLC by now.
Note: patches to adapt bmake to other tracing mechanisms (whilemaintaining the goals offilemon) would bemost welcome.
If filemon is available the data can also be leveraged to auto capturetree dependencies which can be used bydirdeps.mk to drive thebuild.
Whiledirdeps.mk will happily use manually maintainedMakefile.depend files, and is still a vast improvement overalternate means of build orchestration, being able to automate thetree dependencies is preferable.
Even without filemon, meta mode is useful for capturing errors andcomparing command lines to better decide if a target is out-of-date.
| Author: | sjg@crufty.net |
|---|---|
| Revision: | $Id: bmake.txt,v 1.18 2024/02/25 18:55:49 sjg Exp $ |
| Copyright: | Crufty.NET |