Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/fernandodanielmaqueda/gcc-bison-flex-GNUmakefile/llms.txt

Use this file to discover all available pages before exploring further.

When you include a header in a C file, GNU Make has no built-in knowledge of that relationship. If the header changes, Make will not know to recompile the .c file unless you list the header as an explicit prerequisite — something that quickly becomes impossible to maintain by hand. The framework solves this by using the compiler itself to record exactly which headers each source file depends on, then feeding those records back into Make on the next run.

What .d files are

A .d file is a fragment of valid Make syntax emitted by the C compiler. It contains a rule of the form:
obj/foo.o: src/foo.c include/foo.h include/common.h
When Make includes this file, it gains the knowledge that obj/foo.o depends on those headers. If any header changes, Make rebuilds the object. If no .d file exists yet (first build), Make silently skips it and the object is built unconditionally, which produces the .d file as a side effect.

The DEPDIR directory

All dependency files are stored under DEPDIR, which defaults to .deps/. The directory is created automatically before any rule that needs it, as an order-only prerequisite. The two file types in DEPDIR for each source are:
FilePurpose
$(DEPDIR)%.dThe Make-syntax dependency fragment itself
$(DEPDIR)%.d.timestampA sentinel file touched after the .d file is fully written
The naming patterns differ by source type:
Source.d file.d.timestamp file
foo.c.deps/foo.d.deps/foo.d.timestamp
foo.y.deps/foo.tab.d.deps/foo.tab.d.timestamp
foo.l.deps/foo.lex.yy.d.deps/foo.lex.yy.d.timestamp

Why timestamps are separate from .d files

The .d file is regenerated by the compiler, which writes it atomically to a .tmp.1 scratch file before being post-processed and moved into place. The .d.timestamp file is then touch-ed to mark that the whole process completed successfully. Make uses the .d.timestamp file — not the .d file itself — as the prerequisite in build rules. This separation matters for two reasons:
  1. Atomicity: a crashed or interrupted compilation leaves an incomplete .d.tmp.1 rather than a corrupt .d. The .d.timestamp is only updated if the full recipe succeeds.
  2. Decoupling: the .d file can be re-read on the next Make invocation without that re-read itself triggering another compilation. Make tracks freshness through the timestamp file, not the content of the .d file.

How .d files are generated: -MMD -MF

When compiling to an object file (the default GENERATE_ASSEMBLING_OUTPUT=X case), dependency generation is appended to the same compiler invocation that produces the .o:
$(CC) $(CPPFLAGS) $(CFLAGS) $(ASFLAGS) \
 -c -o'$(OBJDIR)$*.o' '$<' \
 -MMD -MF'$(DEPDIR)$*.d.tmp.1' -MT'X' || \
   { $(RM) $(RMFLAGS) '$(DEPDIR)$*.d.tmp.1' ; exit 1 ; }
  • -MMD — write dependency information for Make, excluding system headers
  • -MF file — write the dependency output to file instead of stdout
  • -MT X — use the literal string X as the target name in the generated rule (replaced in the post-processing step)
If compilation fails, the || { rm ... ; exit 1 ; } block removes the partial scratch file and propagates the error. When no compilation output options are active at all (no -E, -S, or -c stop point), a separate dedicated pattern rule regenerates .d and .d.timestamp files using only the preprocessor:
$(DEPDIR)%.d.timestamp: \
  $(OBJDIR)%.c \
  $(DEPDIR)%.d \
  | $(call mkfwk_dirname,$@)
	$(CC) $(CPPFLAGS) $(CFLAGS) \
	 '$<' \
	 -MM -MF'$(DEPDIR)$*.d.tmp.1' -MT'X' || \
	   { $(RM) $(RMFLAGS) '$(DEPDIR)$*.d.tmp.1' ; exit 1 ; }
	$(call mkfwk_common_recipe_for_.d_files,)
-MM generates a rule without system headers, writing it to the scratch file.

The mkfwk_common_recipe_for_.d_files canned recipe

After the compiler writes the raw dependency output to $(DEPDIR)$*.d.tmp.1, the following canned recipe post-processes it:
define mkfwk_common_recipe_for_.d_files
	$(PRINTF) 'empty:\n\n%s: %c\n' '$(DEPDIR)$*$(1).d.timestamp' '\' \
	  > '$(DEPDIR)$*$(1).d.tmp.2'
	$(SED) '1s?^X:??' < '$(DEPDIR)$*$(1).d.tmp.1' \
	  >> '$(DEPDIR)$*$(1).d.tmp.2'
	$(RM) $(RMFLAGS) '$(DEPDIR)$*$(1).d.tmp.1'
	$(MV) '$(DEPDIR)$*$(1).d.tmp.2' '$(DEPDIR)$*$(1).d'
	$(TOUCH) '$(DEPDIR)$*$(1).d.timestamp'
endef
Step by step:
  1. Write the headerprintf opens a second scratch file (.tmp.2) and writes two things into it: a declaration of the empty target (so that when this .d file is included, Make’s default goal does not change), and the start of a rule for $(DEPDIR)*.d.timestamp with a continuation backslash.
  2. Strip the placeholder target — the compiler wrote X: ... (because of -MT X). sed replaces that X: on the first line with nothing, so the continuation of the timestamp rule’s prerequisite list flows straight from the header written in step 1.
  3. Remove the first scratch file.tmp.1 is deleted.
  4. Atomically rename.tmp.2 is moved to the final .d path with mv, which is atomic on the same filesystem.
  5. Touch the timestamp — the .d.timestamp file is updated to signal that dependency tracking is complete and up to date.
The empty target written at the top of every .d file is a phony target with no recipe. Its presence ensures that when Make includes the .d file as part of reading makefiles, it does not adopt the timestamp rule’s target as the default goal.

Including .d files: sinclude and INCLUDE_DEPS

The framework uses sinclude (silent include) rather than include:
ifneq ($(INCLUDE_DEPS),)
sinclude $(patsubst %.c,$(DEPDIR)%.d, \
          $(patsubst %.y,$(DEPDIR)%.tab.d, \
           $(patsubst %.l,$(DEPDIR)%.lex.yy.d, \
            $(filter %.c %.y %.l,$(ALL_SOURCES)))))
endif
sinclude does not error if the listed files do not exist. On a clean build the .d files have not been generated yet, so Make silently skips them and proceeds to compile everything. After the first successful build, the .d files exist and are included on subsequent runs, providing accurate header dependency information. Setting INCLUDE_DEPS= (empty) disables this entirely. In that case, the .d files are still listed as explicit prerequisites of the build targets (so they still get generated), but their contents are never fed back to Make, making every build a full rebuild.
Disabling INCLUDE_DEPS means header changes will not trigger recompilation. Only use INCLUDE_DEPS= when debugging the framework itself or when working with a makefile tool that cannot handle sinclude.

The empty .d rule: preventing errors for missing targets

The .d file pattern rule has an intentionally empty recipe:
$(DEPDIR)%.d: ;
When Make includes a .d file and then finds that one of the listed headers no longer exists on disk, it looks for a rule to rebuild that header. Without a matching rule it would print an error. The framework adds a catch-all for header files:
%.h: ;
This rule matches any .h file. Its empty recipe tells Make: “you can consider this header up to date; there is nothing to do.” Make marks the dependent objects as out of date (because the header file’s timestamp is older than the object) and recompiles them — which will either succeed (the header was moved) or fail with a proper compiler error (the header was genuinely deleted).
The $(DEPDIR)%.d: ; rule serves a similar purpose for .d files themselves: Make may try to rebuild an included .d file if it appears out of date. The empty recipe lets Make believe it has been remade, after which the real .d.timestamp rule takes over on the next invocation.

Pattern rule summary

Three pattern rules cover the three source types. All three are generated by expanding the mkfwk_rule_for_.d_files template in making.mk:
# For .c files → .deps/%.d and .deps/%.d.timestamp
$(DEPDIR)%.d.timestamp: %.c $(DEPDIR)%.d | $(call mkfwk_dirname,$@)
	$(CC) $(CPPFLAGS) $(CC_CPPFLAGS) $(CFLAGS) $(CC_CFLAGS) \
	 '$<' -MM -MF'$(DEPDIR)$*.d.tmp.1' -MT'X' || ...
	$(call mkfwk_common_recipe_for_.d_files,)

# For .y files → .deps/%.tab.d and .deps/%.tab.d.timestamp
$(DEPDIR)%.tab.d.timestamp: $(OBJDIR)%.tab.c $(DEPDIR)%.tab.d \
  | $(call mkfwk_dirname,$@)
	$(CC) $(CPPFLAGS) $(YACC_CPPFLAGS) $(CFLAGS) $(YACC_CFLAGS) \
	 '$<' -MM -MF'$(DEPDIR)$*.tab.d.tmp.1' -MT'X' || ...
	$(call mkfwk_common_recipe_for_.d_files,.tab)

# For .l files → .deps/%.lex.yy.d and .deps/%.lex.yy.d.timestamp
$(DEPDIR)%.lex.yy.d.timestamp: $(OBJDIR)%.lex.yy.c $(DEPDIR)%.lex.yy.d \
  $(MKFWK_YDEFS) | $(call mkfwk_dirname,$@)
	$(CC) $(CPPFLAGS) $(LEX_CPPFLAGS) $(CFLAGS) $(LEX_CFLAGS) \
	 '$<' -MM -MF'$(DEPDIR)$*.lex.yy.d.tmp.1' -MT'X' || ...
	$(call mkfwk_common_recipe_for_.d_files,.lex.yy)
The .l rule additionally lists $(MKFWK_YDEFS) as a prerequisite, which expands to the set of .tab.h files produced by YACC. This ensures that token headers are present before the scanner’s generated C code is analysed for dependencies.

Build docs developers (and LLMs) love