.Dd September 28, 2021 .Dt MAKE-OBJ 7 .Os .Sh NAME .Nm make-obj .Nd out-of-tree builds with BSD .Xr make 1 .Sh DESCRIPTION If you've ever built parts of OpenBSD from source, you may know that the sequence of commands recommended by .Xr release 8 is: .Pp .Bd -literal -offset indent $ make obj $ make # make install .Ed .Pp If, like me, you've forgotten the .Ql make obj step, you'll find yourself with many derived files in the current directory of whatever program you're building. By running .Ql make obj first, a directory called .Pa obj appears and the derived files (usually .Pa *.o files) are placed there instead. Cleverly, the .Pa obj directory is actually a symlink to another filesystem under .Pa /usr/obj , making it truly an out-of-tree build. .Pp Up until recently, I understood what the .Ql obj target did and why it was useful. However, it wasn't until I tried to replicate it with the build for text.alexkarle.com that I discovered how it worked. I figured I'd document it here in case it helps anyone else. .Sh HOW IT WORKS My discovery of the inner workings of this target was a classic lesson in RTFM. After 10-15 minutes of trying to parse the makefiles in .Pa /usr/share/mk , I finally searched for .Pa obj in the .Xr make 1 man page, and sure enough the answer was the first hit! I've copied it for convenience below (licensed under the BSD-3 clause): .Pp .Bl -tag -offset indent .It Va .OBJDIR Path to the directory where targets are built. At startup, .Ic make searches for an alternate directory to place target files. .Ic make tries to .Xr chdir 2 into .Ev MAKEOBJDIR (or .Pa obj if .Ev MAKEOBJDIR is not defined), and sets .Va .OBJDIR accordingly. Should that fail, .Va .OBJDIR is set to .Va .CURDIR . .El .Pp With this new knowledge, getting an out-of-tree build was almost as simple as running .Ql mkdir obj before .Ql make ! .Pp The one catch was that, having chdir'd in, I had to canonicalize the paths to any scripts used in the build recipes. For instance, I have a genpost.sh script in the .Pa bin/ directory of this repo. To call it from the .Pa obj directory, I needed to use its absolute path via the .Va .CURDIR variable: .Pp .Dl $(.CURDIR)/bin/genpost.sh < $< > $@ .Sh PORTABILITY While I mostly build my site on OpenBSD, it's important to me that it builds with GNU make too. .Pp Unfortunately, the .Va .OBJDIR chdir'ing appears to be an extension in OpenBSD's make (and possibly NetBSD too). The good news is that, with one more trick, GNU make support is easy to add (albeit without out-of-tree builds). .Pp The one final hack to support GNU make was to define a portable version of .Va .CURDIR . Since .Va .CURDIR isn't defined in GNU make (which uses .Va CURDIR instead), I had to define the .Va DIR variable that's the concatenation of the two: .Pp .Dl DIR = $(.CURDIR)$(CURDIR) .Pp .Sh CONCLUSION I hope this sheds some light on why .Ql make obj is common practice on OpenBSD as well as how to add similar support to your own projects! .Pp While not as flexible as GNU make's pattern matching inference rules (that allow builds in subdirectories), I find the chdir-ing into .Pa obj a cleverly simple way to obtain a similar end result. .Sh SEE ALSO .Bl -bullet -compact .It .Xr blog 7 .It .Xr text-only 7 .It .Xr my-old-man 7 .El