Non-recursive make (gmake) part 2:

I love the power of GMAKE’s implicit rules and actions. The fewer magical relationships, the better. Ask me how I feel about dev/runtime frameworks that tie your elbows together as you try to write/extend code.

I’m going to assume you are familiar with most GMAKE constructs. For those who aren’t, here’s is my favourite gmake quick-ref.

So is all about extending the existing rules, and sticking to pattern rules and variables that GMAKE default rules/actions use. Here’s in sections:

ifndef RULES_MK
RULES_MK:=# Prevent repeated "-include".
...all the rest of the file...

The guard variable RULES_MK, as in #include headers, allows to be repeatedly included in project and sub-project directories. It’s pretty much the only use I’ve had for GMAKE’s source-level conditionals.

# Import from PREFIX, export to DESTDIR.
PREFIX  ?= /usr/local
OSNAME  := $(shell uname -s)
# Before gcc 4.5, -Wno-unused-result was unknown and causes an error.
Wno-unused-result := $(shell gcc -dumpversion | awk '$$0 >= 4.5 {print "-Wno-unused-result"}')

I use “:=” with $(shell …), so these are only evaluated once. Again if anyone can figure out a more elegant way to solve the above, I’m all ears.

#---------------- Explicitly CANCEL EVIL BUILTIN RULES:
%   : %.c
%.c : %.l
%.c : %.y
%.r : %.l

The comment says it all. They will bite you when you least expect it.

CFLAGS  += -g -MMD -Wall -Werror -Wextra $(Wno-unused-result) $(CFLAGS.$(BLD))
CPPFLAGS += -mtune=native -I$(PREFIX)/include -D_GNU_SOURCE $(CPPFLAGS.$(BLD))

Here’s where all the $(BLD)- and $(OSNAME)- dependent flags are added in. I try to be rigorous about separating C preprocessor flags from C flags; and much to my surprise, some “-m” flags affect the preprocessor.

The CFLAGS element “-MMD” makes GCC generate the make-compatible dependency files, one per source file, that are included with the “-include $(madns)/*.d” lines in sub-project makefiles.

.DEFAULT_GOAL   := all
all   :;@echo "$@ done for BLD='$(BLD)'"

Each sub-project “foo” defines a dependency “all : foo.all”, then makes foo.all depend on all its local targets.
$(BLD) is the build-type variable: debug cover profile or default (release).

clean :;@rm -rf $(shell $(MAKE) -nps all test cover profile \
                       | sed -n '/^# I/,$${/^[^\#\[%.][^ %]*: /s/:.*//p;}') \
                 $(filter %.d,$(MAKEFILE_LIST))

You may recognize this as one of my GMAKE tricks posts. A bit cryptic, but using make’s own knowledge of intermediate targets means you never miss anything.

Within a sub-makefile it is possible to have specific clean-up targets; e.g.

clean : madns.clean
madns.clean :;@rm -rf $(madns.tmpdirs)

Remember the “madns.bin” and “madns.include” variables in part 1? I referred to them as standard inputs for the “install” target. Here’s one way “make install” can be implemented:

Install   = [ -z "$($1.$2)" ] || cp -p $($1.$2) $(DESTDIR)/$2/
%.install : $(patsubst %, $(DESTDIR)/%/.., bin include lib) $(%.bin) $(%.include) $(%.lib) \
          ; $(call Install,$*,bin); $(call Install,$*,include); $(call Install,$*,lib)

Install is a GMAKE macro
Now come the basic pattern rules:

%.test : $(%.test)
%.pass : % ; $(@:.pass=) >$( 2>&1 && mv -f $( $@

This is useful for running many tests in parallel without confusing their output.
$(%.test) is a list of “.pass” filenames, such as “$(madns)/madns_t.pass”.
The above will run:

$ madns/madns_t >& madns/ && mv -f madns/ madns/madns_t.pass

In other words, if the test fails, there will be a *.fail file left behind, containing (only) this test’s failure output. If not, the *.pass file will mark that the test passed, and (until the next “make clean”) will not re-run the test. : CFLAGS := -fPIC $(filter-out $(CFLAGS.cover) $(CFLAGS.profile), $(CFLAGS))

This is GMAKE’s target-specific variable setting. It applies to any target, and any dependency of that target. It ensures that, if code is compiled to be in a .so, it is compiled with -fPIC and without any coverage or profiling flags … which usually make the .so unloadable due to static dependencies. : %.a ; $(CC) $(CFLAGS)  -o $@ -shared -Wl,-whole-archive $< $(LDLIBS)
%.a  :          ; [ "$^" ] && ar crs $@ $(filter %.o,$^)

Here’s an example of writing explicit commands that stick to using ONLY the variables that GMAKE default rules use.

The %.a rule has that odd conditional ‘[ “$^” ]’. Why? Note back in part 1, that “madns_t” depends on “$(util)/libtap.a”. That sub-project makefile has no way to create that file if it does not exist; but it is convenient to list that file in madns_t’s dependencies. The ‘[ “$^” ]’ tests whether any dependencies have been defined, from which to build $(util)/libtap.a. If they haven’t then MAKE stops on an error.

%.yy.c  : %.l       ; flex -o $@ $< : %.y       ; bison $<
%/..    :           ;@mkdir -p $(@D)

Here are examples of the correct rules for building from [f]lex and yacc/bison source files.
And the %/.. rule is a cheap way to ensure that a required directory (dependency) is created.

That’s about it. There are minor bells and whistles that make “clean” more thorough; but I will be posting the complete project on shortly.


About mischasan

I've had the privilege to work in a field where abstract thinking has concrete value. That applies at the macro level --- optimizing actions on terabyte database --- or the micro level --- fast parallel string searches in memory. You can find my documents on production-system radix sort (NOT just for academics!) and some neat little tricks for developers, on my blog My e-mail sig (since 1976): Engineers think equations approximate reality. Physicists think reality approximates the equations. Mathematicians never make the connection.
This entry was posted in make, non-recursive make and tagged . Bookmark the permalink.

One Response to Non-recursive make (gmake) part 2:

  1. Pingback: Non-recursive Make part 3 – a tool for the fearless | Coding on the edges

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s