The Makefile of No Makefile

The simplest Makefile I use is:

That is, I use no Makefile and rely on GNU Make's built-in rules. When I first start a project, or when I have a simple piece of C/C++ code that I want to test in a stand-alone form, I write a simple main() routine in, for example, a file called prog.cpp and run:

$ make prog
g++     prog.cpp   -o prog
$ 

GNU Make's -d and -p options help to explain what it does when you give it no explicit instructions. The debugging output (-d) shows how GNU Make first tries to find a Makefile, checking a variety of guises, and when it fails that it falls back to using its built-in rules.

$ make -d prog
GNU Make 3.80
Copyright (C) 2002  Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Reading makefiles...
Updating makefiles....
 Considering target file `GNUmakefile'.
  File `GNUmakefile' does not exist.
   Looking for an implicit rule for `GNUmakefile'.
   ...
  No implicit rule found for `Makefile'.
  Finished prerequisites of target file `Makefile'.
 Must remake target `Makefile'.
 Failed to remake target file `Makefile'.
Updating goal targets....
Considering target file `prog'.
 File `prog' does not exist.
 Looking for an implicit rule for `prog'.
 Trying pattern rule with stem `prog'.
 ...
 Trying implicit prerequisite `prog.cpp'.
  Found an implicit rule for `prog'.
  ...
g++     prog.cpp   -o prog
$ 

GNU Make's built-in (implicit) rules are its secret Makefile. We provide a target name, prog, and GNU Make uses that to infer a rule for building it. It checks its implicit rules for one whose conditions can be satisfied from the contents of the current directory. Eventually it discovers that the rule for making prog from prog.cpp can be used since there is a file called prog.cpp.

GNU Make's -p exposes the details of its implicit rules:

$ make -p prog
# Variables
...
CXX = g++
LINK.cc = $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)
LINK.cpp = $(LINK.cc)

# Implicit Rules
%: %.cpp
#  commands to execute (built-in):
        $(LINK.cpp) $^ $(LOADLIBES) $(LDLIBS) -o $@
...

Reading the excerpted -p output above from bottom up, we see a built-in rule for generating file from file.cpp. A variable, LINK.cpp, is used to specify the command to use, additional variables, LOADLIBES and LDLIBS, are used to supply arguments for the command. GNU Make sets LINK.cpp from LINK.cc, and LINK.cc is set to CXX and some additional arguments. Finally, CXX is set to the GNU C++ compiler, g++.

All of the variables used to specify command arguments — CXXFLAGS, LDLIBS, et al — are empty/unset by default. The rule simply calls g++ on the rule's prerequisite, $^, using rule's target, $@, as the output file name.

By breaking down actions and using variables to represent the elements GNU Make makes it easier to customize its behavior. For example, to build prog with debugging instrumentation we do not have to write a new rule we only need to add the necessary options to the current rule for compiling prog.cpp, we need only set the CPPFLAGS variable. For such a small change we still do not need a Makefile, we can add it to our command line:

$ make CPPFLAGS=-g prog
g++  -g   prog.cpp   -o prog
$ 

Or if this is a change in behavior we want throughout our work session we can set CXXFLAGS in our environment.

$ export CXXFLAGS=-g
$ make prog
g++  -g   prog.cpp   -o prog
$ 

Or of course we could set CXXFLAGS in a Makefile.