bmake

Introduction

Many years ago, when building large software projects, I've had to ensure that GNU make (if not my own patched version of it), was installed and had developed a set of macros to greatly simplify delveloping complex build trees.

Since the early 90's my main development machines, run BSD (NetBSD to be precise), and the BSD source tree is an excellent example of managing a large software project. In following NetBSD's -current delevlopment, I learned to use the new BSD Makefiles and make(1), I did an autoconf version of it that I'll refer to as bmake(1) from here on.

Since then all my new projects and many of my old ones use bmake. Since bmake.tar.gz uses GNU's autoconf and is quite portable, I've skipped the effort of making the rest of my distributions support GNU make.

Bmake is derrived from NetBSD's make(1), its goal is to be a portable version of same, so new features are added via imports of NetBSD's make (I'm one of the contributors to NetBSD). Thus bmake is kept in sync with NetBSD's make.

Unless you are using NetBSD you will need mk.tar.gz as well.

Since about 2003, the bmake version typically represents the date of import from NetBSD, and I avoid any code changes which are not strictly related to portability.

It may be insteresting to note that I have projects where much of the tree uses the same simple Makefile:

PROG=   ${.CURDIR:T}

SRCS=   ${PROG}.c

.include "prog.mk"

macros

The example Makefile above, perhaps suggests why I like bmake(1) The important magic is in the line:

.include "prog.mk"

Makefiles for libraries inlcude lib.mk btw. Anyway, apart from reading a bunch of rules from the system macros (/usr/share/mk/* by default) it reads "../Makefile.inc" thus provinding a hook for much magic.

Note that I used to use the bsd.*.mk macros (intended for building /usr/src), but attempts to get simple portability improvments committed into these, usually failed, so the only viable solution was to maintain a parallel set.

obj

Another cool feature of bmake(1) is that if the directory obj exists in the same directory as the Makefile, then bmake will chdir into it before doing anything else. This helps keep the build tree clean, and if the obj directories are symlinks off to a separate file system, eg.:

/usr/src/bin/cat/obj -> /usr/obj/bin/cat

then suddenly building for multiple architectures is very easy.

Even better if MAKEOBJDIRPREFIX is set in the environment (see below) then it is trival to export a source tree read-only. The only hassle with MAKEOBJDIR and MAKEOBJDIRPREFIX is that they need to be set in the environment.

MAKEOBJDIRPREFIX

If MAKEOBJDIRPREFIX 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 described above. This simple feature allows true read-only source trees. This entire build tree has been built using MAKEOBJDIRPREFIX. Note that MAKEOBJDIRPREFIX is actioned before any Makefiles are read which is why it must be an environment variable.

MAKEOBJDIR

Recent versions of bmake, also allow variable substitutions on MAKEOBJDIR so for a neater result you can use something like:

export MAKEOBJDIR="\${.CURDIR:S,$SRCTOP,$OBJTOP,}"

MACHINE and MACHINE_ARCH

The variables MACHINE and MACHINE_ARCH are built into bmake. They can also be controlled via the makefiles for cross-building.

MACHINE
This is normally used to represent a specific cpu such as i386.
MACHINE_ARCH
In some cases a range of cpu's have a common architecture such as mips or m68k. MACHINE_ARCH is used to select common include dirs, toolchains etc.
HOST_TARGET
When cross building it can be useful to differentiate things build for the build host rather than the target. HOST_MACHINE and HOST_MACHINE_ARCH as well as TARGET_* can be useful.

Building bmake

Each new version of bmake goes through the following on several platforms (NetBSD, FreeBSD, SunOS, Linux, OS/X):

$ tar zxf ~/bmake.tar.gz
$ tar zxf ~/mk.tar.gz
$ ./bmake/boot-strap

The boot-strap script will build bmake, and run its unit-test suite, and only if that succeeds output the commands to install it.

Note that I have the following content in ~/.bmake-boot-strap.rc to keep things simple:

:
echo "Building for $HOST_TARGET"
case "$HOST_TARGET" in
netbsd*) sys_share_mk=:/usr/share/mk;;
*) sys_share_mk=;;
esac
objdir=./$HOST_TARGET
case "$prefix" in
*/$HOST_TARGET)
   CONFIGURE_ARGS="$CONFIGURE_ARGS --with-default-sys-path=$prefix/share/mk:`dirname $prefix`/share/mk$sys_share_mk"
   ;;
esac

For building bmake on NetBSD I use the system bsd.*.mk so the following is sufficient:

$ tar zxf ~/bmake.tar.gz
$ ./bmake/boot-strap -m none

The example below is building bmake-20091118 on SunOS. Note that the build isn't considered successful unless the unit-tests pass. With the above content in ~/.bmake-boot-strap.rc, boot-strap will print the commands to install into /homes/sjg/sunos5-sparc/bin/ etc.

Also note that the src directories are left untouched and a host-target specific objdir is used. Thus I can use the exact same command sequence to build bmake for all the platforms available:

$ mkdir ~/tmp/x
$ cd ~/tmp/x
$ tar zxf ../bmake-20091118.tar.gz
$ tar zxf ../mk-20091201.tar.gz
./bmake/boot-strap
NOTE: reading /homes/sjg/.bmake-boot-strap.rc
Building for sunos5-sparc
checking for gcc... gcc
checking for C compiler default output file name... a.out
...
checking if diff -u works... no
checking for MACHINE & MACHINE_ARCH...
defaults: MACHINE=sunos5, MACHINE_ARCH=sparc
Using: MACHINE=sunos5, MACHINE_ARCH=sparc
Using: SHELL=/usr/xpg4/bin/sh
Using: MKSRC=/homes/sjg/tmp/x/mk
configure: creating ./config.status
config.status: creating Makefile
config.status: creating makefile.boot
config.status: creating lst.lib/makefile.boot
config.status: creating unit-tests/Makefile
config.status: creating config.h
...
rm -f bmake *.[ado] */*.[ado] .*.done .depend
gcc -g -O2 -I. -I/homes/sjg/tmp/x/bmake -DHAVE_CONFIG_H -D_PATH_DEFSYSPATH=\"/homes/sjg/sunos5-sparc/share/mk:/homes/sjg/share/mk\"   -c -o arch.o /homes/sjg/tmp/x/bmake/arch.c
gcc -g -O2 -I. -I/homes/sjg/tmp/x/bmake -DHAVE_CONFIG_H -D_PATH_DEFSYSPATH=\"/homes/sjg/sunos5-sparc/share/mk:/homes/sjg/share/mk\"   -c -o buf.o /homes/sjg/tmp/x/bmake/buf.c
...
gcc *.o lst.lib/*.o -o bmake.boot
rm -f *.[ado] */*.[ado]
mkdir -p /homes/sjg/tmp/x/sunos5-sparc/mk
cp -f sys.mk autoconf.mk autodep.mk auto.obj.mk dep.mk doc.mk dpadd.mk host-target.mk init.mk inc.mk final.mk java.mk lib.mk libs.mk links.mk man.mk nls.mk obj.mk own.mk prlist.mk prog.mk progs.mk scripts.mk subdir.mk target-flags.mk warnings.mk yacc.mk /homes/sjg/tmp/x/sunos5-sparc/mk
cp -f sys/AIX.mk sys/HP-UX.mk sys/Linux.mk sys/NetBSD.mk sys/OSF1.mk sys/SunOS.mk sys/Darwin.mk sys/IRIX.mk sys/OpenBSD.mk sys/Generic.mk sys/UnixWare.mk /homes/sjg/tmp/x/sunos5-sparc/mk/sys
..
ln -s dep.mk bsd.dep.mk
ln -s doc.mk bsd.doc.mk
ln -s init.mk bsd.init.mk
ln -s lib.mk bsd.lib.mk
..
CC="gcc" LIBC= MAKEFLAGS= MAKESYSPATH=`pwd`/mk:/homes/sjg/sunos5-sparc/share/mk:/homes/sjg/tmp/x/mk:/homes/sjg/sunos5-sparc/share/mk:/homes/sjg/share/mk:/usr/share/mk:/usr/local/share/mk:/opt/share/mk ./bmake.boot -f Makefile
..
/homes/sjg/tmp/x/sunos5-sparc/bmake -f Makefile > test.out 2>&1
diff  /homes/sjg/tmp/x/bmake-20091118/unit-tests/test.exp test.out

Commands to install into /homes/sjg/sunos5-sparc/

mkdir -p /homes/sjg/sunos5-sparc/bin
cp ./sunos5-sparc/bmake /homes/sjg/sunos5-sparc/bin/bmake-20091118
rm -f /homes/sjg/sunos5-sparc/bin/bmake
ln -s bmake-20091118 /homes/sjg/sunos5-sparc/bin/bmake
mkdir -p /homes/sjg/share/man/cat1
cp /homes/sjg/tmp/x/bmake-20091118/bmake.cat1 /homes/sjg/share/man/cat1/bmake.1
sh /homes/sjg/tmp/x/mk/install-mk /homes/sjg/share/mk

I just cut/paste those last few lines to install.


Author:sjg@crufty.net
Revision:$Id: bmake.txt,v 1.2 2009/12/18 17:06:13 sjg Exp $
Copyright:Crufty.NET