Kaz
Kaz

Reputation: 58578

GNU Make: obtain list of primary prerequisites of a rule

Consider the following Makefile:

.SUFFIXES:
.SUFFIXES: .c.o

.PHONY: all

all: foo.o

foo.o: foo.h bar.h xyzzy.h

%.o: %.c
    @printf "prerequisites of %s are %s\n" $@ "$^"

All the files exist except for foo.o, and the output is:

prerequisites of foo.o are foo.c foo.h bar.h xyzzy.h

Correctly, the automatic variable $^ gives us all the prerequisites, including the ones obtained from dependencies stated in other rules.

Let us call the prerequisites given in the rule itself primary prerequisites, and the prerequisites that come from other dependencies secondary prerequisites.

Above, the primary prerequisites are:

foo.c

and the secondary ones are:

foo.h bar.h xyzzy.h

The category is important because the primary prerequisites are the objects that the rule actually works with, which are needed to build the program. The secondary prerequisites are only involved in the correct triggering of incremental builds, not in a full build. A full build from scratch will work even if we remove the dependency line:

foo.o: foo.h bar.h xyzzy.h

This is reflected in our Makefile structure. We usually don't write Makefiles with rules like these:

foo.o: foo.c foo.h bar.h xyzzy.h
    # commands

the additional prerequisites after foo.c are factored out elsewhere, often into a completely separate dependency makefile that is generated by tools, and that can be deleted completely, without affecting the ability to do a complete build from scratch.

The question is: how can we obtain just the list of the primary prerequisites, not including the secondary prerequisites?

This should be doable in a generic way, without any hard coding. For instance, if I have some recipe lines defined as a macro, they can be re-used in multiple rules.

define RULE_BODY
@printf "the primary prerequisites of target %s are %s\n" $@ [what goes here?]
endef

%.o: %.c
    $(call RULE_BODY)

I don't want to pass arguments to RULE_BODY for this it should "just know", the same way it knows the target and the total prerequisites.

Note that the use of a pattern rule is a red herring: we can replace %.o: %.c by foo.o: foo.c.

Upvotes: 3

Views: 1735

Answers (1)

Kaz
Kaz

Reputation: 58578

A possible solution is to add an an intermediate dependency node, which captures the secondary prerequisites, and represents them as a single prerequisite. The phony prerequisite has a certain recognizable lexical form based on which it can be filtered out:

Proof of concept, closely based on Makefile in the question:

.SUFFIXES:
.SUFFIXES: .c.o

all: foo.o

secondary_foo.o: foo.h bar.h xyzzy.h
    echo $^ > $@

foo.o: secondary_foo.o

define RULE_BODY
@printf "prerequisites of %s are %s\n" $@ "$^"
@printf "primary prerequisites of %s are %s\n" $@ "$(filter-out secondary_$@,$^)"
@printf "secondary prerequisites of %s are %s\n" $@ "$(shell cat secondary_$@)"
endef

%.o: %.c
    $(call RULE_BODY)
    touch $@

Output:

prerequisites of foo.o are foo.c secondary_foo.o
primary prerequisites of foo.o are foo.c
secondary prerequisites of foo.o are foo.h bar.h xyzzy.h
touch foo.o

Unfortunately, the build directory is littered with these intermediate files. Even if the propagation of the secondary prerequisites is handled in some other way, the secondary_foo.o file still cannot be a phony target; at the very least it must be an empty time stamp file.


The following alternative solution is more complicated, requiring computed variables, eval, and the use of a trick to store dependencies in variables, which are used to generate rules. However, it has the virtue that it doesn't generate a proliferation of timestamp files.

.SUFFIXES:
.SUFFIXES: .c.o

OBJS := foo.o bar.o

all: $(OBJS)

# These variables give secondary dependencies for the objectg files,
# in place of rules. These would typeically be "farmed out" to
# a machine-generated dependency makefile which is included:
DEP_foo.o := foo.h bar.h xyzzy.h
DEP_bar.o := bar.h xyzzy.h

define RULE_BODY
@printf "\n"
@printf "prerequisites of %s are %s\n" $@ "$^"
@printf "primary prerequisites of %s are %s\n" $@ "$(filter-out $(DEP_$@),$^)"
@printf "secondary prerequisites of %s are %s\n" $@ "$(DEP_$@)"
endef

%.o: %.c
        $(call RULE_BODY)

# Now the trickery: generate the dependency rules from OBJS and DEP_ vars:

# $(NL) provides newline, so we can insert newline into eval expansions
define NL


endef

# For each object <obj>, generate the rule <obj>: $(DEP_<obj>)
$(eval $(foreach obj,$(OBJS),$(obj): $(DEP_$(obj))$(NL)))

Output:

prerequisites of foo.o are foo.c foo.h bar.h xyzzy.h
primary prerequisites of foo.o are foo.c
secondary prerequisites of foo.o are foo.h bar.h xyzzy.h

prerequisites of bar.o are bar.c bar.h xyzzy.h
primary prerequisites of bar.o are bar.c
secondary prerequisites of bar.o are bar.h xyzzy.h

The disadvantage is that any additional dependencies must be inserted into the variables rather than asserted via an ordinary rule. For instance, suppose we want to recompile all the $(OBJS) if the config.make makefile is touched. We cannot just do this:

$(OBJS): config.make   # Oops, config.make is now considered primary

Instead, we stick to the DEP_ variable scheme and do it like this:

$(eval $(foreach obj,$(OBJS),DEP_$(obj) += config.make$(NL)))

In other words, loop over $(OBJS), and generate a += variable assignment for each DEP_ variable which adds config.make, followed by a newline, and eval the whole thing as if it were Makefile text.

When the above eval is inserted into to our Makefile (in front of the existing eval, not after) the output shows that config.make has been added to foo.o and bar.o as a secondary prerequisite:

prerequisites of foo.o are foo.c foo.h bar.h xyzzy.h config.make
primary prerequisites of foo.o are foo.c
secondary prerequisites of foo.o are foo.h bar.h xyzzy.h config.make

prerequisites of bar.o are bar.c bar.h xyzzy.h config.make
primary prerequisites of bar.o are bar.c
secondary prerequisites of bar.o are bar.h xyzzy.h config.make

This is a workable solution which avoids temporary files, but is more challenging to understand for Makefile maintainers.

Also note that since GNU Make allows periods and slashes in variable names, something like the following is not a problem:

DEP_libs/parser/scan.o := config.h libs/parser/parser.h ...

In a rule, where libs/parser/scan.o is the $@ target, $(DEP_$@) nicely gives us config.h libs/parser/parser.h ....

Lastly, note that instead of the eval lines, the dependency generator could just generate the code and stick it into the dependency makefile. That is to say, generate the file along these lines:

DEP_foo.o := foo.h bar.h xyzzy.h config.make  # config.make tacked on
foo.o: $(DEP_foo.o)                           # also generated

DEP_bar.o := ... # and so forth
bar.o: $(DEP_bar.o)

Upvotes: 2

Related Questions