Dumping Every Makefile Variable

[article]
        print-%:
                @echo $* = $($*)

But what if you want to print out every variable defined in a Makefile? In this tip I'm going to show you how and introduce GNU Make's powerful functions.

Consider the following example Makefile (cunningly named Makefile):

        X=$(YS) hate $(ZS)
        Y=dog
        YS=$(Y)$(S)
        Z=cat
        ZS=$(Z)$(S)
        S=s

        all:

It sets five variables: X, Y, Z, S, YS, and ZS. Using the special rule shown above it's possible to type commands like

        gmake print-X

to get the value of X once for each variable you are interested in. In GNU Make 3.80 the authors introduced a couple of new features that make it feasible to print out the value of all the variables defined in a Makefile with a single rule. But be prepared, this tip requires unleashing the power of GNU Make's functions. A full reference to GNU Make's functions can be found in the GNU Make manual here.

Create a Makefile named helper.mak containing the following:

        .PHONY: printvars
        printvars:
                @$(foreach V,$(sort $(.VARIABLES)),
                   $(if $(filter-out environment% default automatic,
                   $(origin $V)),$(warning $V=$($V) ($(value $V)))))

Before understanding how this works we can try it out on the sample Makefile above:

        gmake -f Makefile -f helper.mak printvars

That command directs GNU Make to load Makefile and then load helper.mak and then “build” printvars. Here's the output:

        helper.mak:3: MAKEFILE_LIST= Makefile helper.mak ( Makefile helper.mak)
        helper.mak:3: MAKEFLAGS= ()
        helper.mak:3: S=s (s)
        helper.mak:3: SHELL=/bin/sh (/bin/sh)
        helper.mak:3: X=dogs hate cats ($(YS) hate $(ZS))
        helper.mak:3: Y=dog (dog)
        helper.mak:3: YS=dogs ($(Y)$(S))
        helper.mak:3: Z=cat (cat)
        helper.mak:3: ZS=cats ($(Z)$(S))

GNU Make has thrown in three extra variables that weren't explicitely defined (MAKEFILE_LIST, MAKEFLAGS and SHELL), but the rest are all the variables defined in Makefile. Each line shows the name of the variable, its fully substituted value and the way in which is was defined.

Now to understand how this works. It's a lot easier to understand the long complex line used to print out the variables if we reformat it a bit, like this:

        $(foreach V,
             $(sort $(.VARIABLES)),
   	     $(if 
                 $(filter-out environment% default automatic,
                     $(origin $V)),
                 $(warning $V=$($V) ($(value $V)))
              )
         )

Start by finding .VARIABLES. That's a new feature of GNU Make 3.80: it's a variable whose value is a list of the names of all the variables defined in the Makefile. The first thing we do is sort it into order: $(sort $(.VARIABLES)). Then we go through the list element by element (i.e. variable name by variable name) setting a variable called V to the name of the variable we are considering: $(foreach V, $(sort (.VARIABLES)),...).

For each variable name we decide whether to print or ignore it based on where the variable was defined. If it was one of GNU Make's built-in variables (like $@ or $(CC)) or came from the environment we don't want to print it out. To make that decision we use $(if).

If $(if)'s predicate is true then we do $(warning $V=$($V) ($(value $V))) to output a warning containing the name of the variable, it's fully expanded value, and its defined value.

The other cool feature in GNU Make 3.80 is the $(value) function which outputs the value of a variable without expanding it. In our Makefile example above YS will have the value dogs when used in the form $(YS), but $(value YS) would return $(Y)$(S): it shows us how YS is defined, not its final value. That's a very useful debugging feature.

So now for the hard part: how to decide which variables to print and which to ignore. $(if) will print the variable information if its predicate is not an empty string. GNU Make is very string-centric and thinks of false as 'empty string' and true as 'not an empty string'. The predicate $(filter-out environment% default automatic,$(origin $V)) figures out where the variable referenced by $V was defined by calling $(origin $V). That returns a string describing where the variable was defined (e.g. it returns environment for environment variables, file for variables defined in a Makefile and default for things the GNU Make defines by itself).

The $(filter-out environment% default automatic,$(origin $V)) says if the result of $(origin) matches any of the patterns environment% (), default or automatic then return an empty string, otherwise leave it alone. Combined with the $(if) this results on only variables defined in the Makefile being printed.

About the author

AgileConnection is a TechWell community.

Through conferences, training, consulting, and online resources, TechWell helps you develop and deliver great software every day.