subdirmk - assistance for non-recursive use of make =================================================== Introduction ------------ Peter Miller's 1997 essay _Recursive Make Considered Harmful_ persuasively argues that it is better to arrannge to have a single make invocation with the project's complete dependency tree, rather than the currently conventional `$(MAKE) -C subdirectory' approach. However, actually writing a project's build system in a non-recursive style is not very ergonomic. The main difficulties are: - constantly having to write out long file and directory names - the lack of a per-directory make variable namespace means long make variables (or namespace clashes) - it is difficult to arrange that one can cd to a subdirectory and say `make all' and have something reasonable happen (to wit, build an appropriate subset) `subdirmk' is an attempt to solve these problems (and it also slightly alleviates some of the boilerplate needed to support out-of-tree builds well). Basic approach -------------- The developer is expected to write a makefile fragment in each relevant subdirectory called `Subdir.mk.in'. These fragments may contain ordinary make language. However, the sigil & is treated specially. By and large, it refers to `the current directory'. There are a variety of convenient constructions. The result is that to a large extent, the Subdir.mk.in has an easy way to namespace its "local" make variables, and an easy way to refer to its "local" filenames. The Subdir.mk.in's are filtered, fed through autoconf in the usual way (for @..@-substitutions) and included by one autogenerated toplevel makefile. So all of the input is combined and passed to one make invocation. (A corollary is that there is no enforcement of the namespacing: discipline is required to prefix relevant variable names with &, etc. Each subdirectory is also provided with an autogenerated `Makefile' which exists purely to capture ordinary make invocations and arrange for something suitable to happen. Where there are dependencies between subdirectories, each Subdir.mk.in can simply refer to files in other subdirectories directly. Invocation, "recursive" per-directory targets --------------------------------------------- Arrangements are made so that when you run `make foo' in a subdirectory, it is like running the whole toplevel makefile, from the toplevel, as `make subdir/foo'. If `subdir/foo' is a file that might be built, that builds it. But `foo' can also be a conventional target like `all'. Each subdirectory has its own `all' target. For example a subdirectory `src' has a target `src/all'. The rules for these are automatically generated from the settings of the per-directory &TARGETS variables. (In src/Subdir.mk.in, this of course refers to a make variable called src_TARGETS.) The `all' target in a parent directory is taken to imply the `all' targets in all of its subdirectories, recursively. And in the autogenerated stub Makefiles, `all' is the default target. So if you just type `make' in the toplevel, you are asking for `&all' (/all) for every directory in the project. In a parallel build, the rules for all these various subdirectory targets may be in run in parallel: there is only one `make' invocation at a time. You can define other per-directory recursive targets too: simply mention (usually, by setting) the variable &TARGETS_zonk, or whatever. This will create a src/zonk target. (&TARGETS is magic.) Unlike `all', these other targets only exist in areas of the project where at least something mentions them. So for example, if &TARGETS_zonk is mentioned in src but not lib, `make zonk' in lib will fail. If you want to make a target exist everywhere, mention its name in Perdir.mk.in (see below). Perdir.mk.in, inclusion ----------------------- The file Perdir.mk.in in the toplevel of fthe source is automatically processed after each individual directory's Subdir.mk.in, and the &-substituted contents therefore appear once for each subdirectory. This lets you do per-directory boilerplate. Some useful boilerplate is already provided in subdirmk, for you to reference like this: &:include subdirmk/cdeps.mk.in &:include subdirmk/clean.mk.in Note that you must use &:include, which is an include processed during the generation of the per-directory Subdir.mk files. That ensures that the contents of these files is replicated, with appropriate per-directory substitutions, for each directory. Global definitions ------------------ If want to set global variables, such as CC subdirmk/cdeps.mk.in subdirmk/cdeps.mk.in (None of this prevents https://web.archive.org/web/20150330111905/http://miller.emu.id.au/pmiller/books/rmch/ &CAPS => subdir_CAPS or TOP_CAPS &lc => subdir/lc or lc &_ => subdir_ or TOP_ &/ => subdir/ or nothing &=_ => subdir or TOP &=/ => subdir or . &^ => $(top_srcdir)/subdir or $(top_srcdir) &~ => $(abs_top_srcdir)/subdir or $(abs_top_srcdir) && => && \& => & & thing thing... & => each thing prefixed by &/ &^/ &~/ resp & ^ thing thing... & each thing is any non-ws & ~ thing thing... & & may be omitted before EOL or before \EOL other &'s not recognised start of line (maybe after ws): &: .... args are processed for & first &:include filename filename should usually be foo.mk.in &:-include filename CAPS is [A-Z][0-9_A-Z]*(?!\w) lc is [a-z][-+,0-9_a-z]*(?!\w) &! disables & *until* EOL (and disappears) &!STUFF STUFF is recognised instead of & the terminating lwsp is discarded too may also occur at eol eg notably STUFF!& now & is recognised instead (ie back to normal) STUFFSTUFF STUFF eg &!@@@ @@@ is recognised instead of & @@@!& go back to & &TARGETS[_things] is handled specially must be spelled precisely this way if no _things, means _all Also, `all' is weird in that it is present even if not specified