One of my favorite makefile debugging tricks is this rule for printing out the value of a variable:
print-%: @echo '$*=$($*)'
Throw this into a GNU make makefile and then print any make variable you like by invoking targets like print-MAKE_VERSION:
ericm@chester:/tmp$ gmake print-MAKE_VERSION MAKE_VERSION=3.81
You can imagine how handy this is when diagnosing issues with your makefiles. Here’s how it works:
- print-% defines a pattern rule that matches any target that starts with the characters print-.
- In the context of a pattern rule, the $* variable expands to the stem of the target, that part which matched the % in the pattern. In my example above, that corresponds to MAKE_VERSION.
- GNU make variable expansion rules allow for variable references inside variable names, so $($*) expands first to $(MAKE_VERSION), and finally to the value of the MAKE_VERSION variable.
Makefile injection with -f
The print-% rule is a slick hack, but it’s a nuisance to have to modify a makefile just to use it. Worse, you might not even be able to modify the makefile. Fortunately, there’s a solution: the -f command-line option. You’re probably familiar with it — that’s how you tell gmake to use a different makefile than the default Makefile when it starts. For example, if you have a makefile named build.mak:
gmake -f build.mak
What you may not know is that you can use multiple -f options on the command line. GNU make will read each file in turn, incorporating the contents of each just as if they were included with the include directive. We can create a simple makefile called printvar.mak containing nothing but our print-% rule, then inject it into any makefile we want like this:
gmake -f printvar.mak -f Makefile print-MAKE_VERSION
A shell script to save typing
The combination of the print-% rule and the -f command-line option is powerful, but it’s unwieldy — too many characters to type. The solution is a shell script wrapper:
#!/bin/bash filename="" if [ -f GNUmakefile ] ; then filename="GNUmakefile" elif [ -f makefile ] ; then filename="makefile" elif [ -f Makefile ] ; then filename="Makefile" fi if [ -n "$filename" ] ; then vars="" for n in $@ ; do vars="$vars print-$n" done gmake -f $filename -f printvar.mak $vars else echo "No makefile found" 1>&2 exit 1 fi
Save that in a file called printvars somewhere on your PATH and you can do things like this:
ericm@chester:/tmp$ printvars MAKE_VERSION COMPILE.cc MAKE_VERSION=3.81 COMPILE.cc=g++ -c
Advanced make variable diagnostics
Beyond simply printing the value of a variable, GNU make 3.81 has three built-in functions that allow introspection on variables, which you can add to the print-% rule for additional diagnostics.
First is the $(origin) function, which tells you how a variable was defined. For example, if a variable FOO was inherited from the environment, $(origin FOO) will give the result environment. Variables defined in a makefile will give the result file, and so forth.
Next is the $(flavor) function, which tells you the flavor of the variable, either simple or recursive.
Finally is the $(value) function, which gives you the unexpanded value of the variable. For example, if you have variables like this:
FOO=123 BAR=$(FOO)
$(value BAR) will give the result $(FOO), rather than the fully-expanded 123 that you might expect.
With these additions, the print-% rule now looks like this:
print-%: @echo '$*=$($*)' @echo ' origin = $(origin $*)' @echo ' flavor = $(flavor $*)' @echo ' value = $(value $*)'
And here’s how it looks in action:
ericm@chester:/tmp$ printvars MAKE_VERSION COMPILE.cc MAKE_VERSION=3.81 origin = default flavor = simple value = 3.81 COMPILE.cc=g++ -c origin = default flavor = recursive value = $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
Very nice post! I never thought of using multiple -f arguments.
Excellent!
linked from http://www.pixelbeat.org/programming/make_variables.html
That’s inspiring!
Is it somehow possible to have all variables printed without naming them all? In a complex make systems it might be difficult to know all the variables while it can be helpful to see them all.
In GNU make you can use the $(foreach) function in conjunction with the .VARIABLES special variable to enumerate the variables and print them all:
print-all: ; $(foreach n,$(sort $(.VARIABLES)),echo ‘$n=$($n)’;)
Now you can use “gmake print-all” to get a dump of all the variables. You can also use “gmake –print-data-base”, which includes the variables and their values as well as a bunch of other information gmake extracted from the makefiles.
Great write-up! I encountered this situation in my side project where I had to extract all the “SRCS” variables from different sub-makefiles into a top-level makefile. In my case, I also wanted it to work on Windows (cmd.exe) + GnuWin in the PATH: https://github.com/tanzislam/cryptopals/blob/master/GNUmakefile#L5-L19