Hidden targets

3 minute read

eMake differs from other Make variants in the way it searches for files needed by pattern rules (also called suffix or implicit rules) in a build.

  • At the beginning of each Make instance, eMake searches for matching files for all pattern rules before it runs any commands. After eMake has rules for every target that needs updating, it schedules the rules (creating jobs) and then runs those jobs in parallel for maximum concurrency.

  • Microsoft NMAKE and GNU Make match pattern rules as they run commands , interleaving execution and pattern search.

Because of the difference in the way eMake and NMAKE match pattern rules, NMAKE and eMake can produce different makefile output with hidden targets . A hidden target (also known as a “hidden dependency”) is a file that is:

  • created as an undeclared side-effect of updating another target

  • required by a pattern to build a rule

Consider the following makefile example:

all: bar.lib foo.obj
bar.lib: touch bar.lib foo.c
.c.obj: touch $@

Notice that foo.c is created as a side effect of updating the bar.lib target. Until bar.lib is updated, no rule is available to update foo.obj because nothing matches the .c.obj suffix rule.

NMAKE accepts this construct because it checks for foo.c existence before it attempts to update foo.obj. NMAKE produces the following result for this makefile:

touch bar.lib foo.c touch foo.obj

eMake, however, performs the search for files that match the suffix rule once so it can schedule all jobs immediately and maximize concurrency. eMake will not notice the existence of foo.c by the time it attempts to update foo.obj, even if foo.c was created. eMake fails with:

NMAKE : fatal error U1073: don't know how to make 'foo.obj' Stop.

The fix is simply to identify foo.c as a product for updating the bar.lib target, so it is no longer a hidden target. For the example above, adding a line such as foo.c: bar.lib is sufficient for eMake to understand that .c.obj suffix rule matches the foo.obj target if bar.lib is built first. Adding this line is more accurate and has no effect on NMAKE.

GNU Make is similarly incompatible with eMake, but the incompatibility is sometimes masked by the GNU Make directory cache. GNU Make attempts to cache the directory contents on first access to improve performance. Unfortunately, because the time of first directory access can vary widely depending on which targets reference the directory and when they execute, GNU Make can appear to fail or succeed randomly in the presence of hidden targets.

For example, in this makefile, the file $(DIR)/foo.yy is a hidden target created as a side-effect of updating aa and needed by the pattern rule for foo.xx :

all: aa bb aa: touch $(DIR)/foo.yybb: foo.xx
%.xx: $(DIR)/%.yy
@echo $@

Depending on the value of DIR, this build might or might not work with GNU Make:

% mkdir sub; gmake DIR=sub touch sub/foo.yy foo.xx
% gmake DIR=. touch ./foo.yy gmake: *** No rule to make target ’foo.xx’, needed by ’bb’. Stop.

eMake does not attempt to emulate this behavior. Instead, it consistently refuses to schedule foo.xx because it depends on a hidden target (just as it did in the NMAKE emulation mode in the earlier example). In this case, adding a single line declaring the target: $(DIR)/foo.yy: aa is sufficient to ensure it always matches the %.xx pattern rule.

If a build completes successfully with Microsoft NMAKE or GNU Make, but fails with “don’t know how to make <x> ” with eMake, look for rules that create <x> as a side-effect of updating another target. If <x> is required by a suffix rule also, it is a hidden target and needs to be declared as explicit output to be compatible with eMake.

There are many other reasons why hidden targets are problematic for all Make-based systems and why eliminating them is good practice in general. For more information, see:

In a limited number of cases, eMake might conclude that a matching pattern rule for an output target does not exist. This occurs because eMake’s strict string equality matching for prerequisites determines that the prerequisites are different (even though the paths refer to the same file) and that there is no rule to build it.