What Is .D File After Building with Make

what is .d file after building with make

Many build systems add automatically detected make dependencies into the .d file. In particular, for C/C++ source files they determine what #include files are required and automatically generate that information into the .d file.

The .d files are then included by the makefile so make is aware of that information. If you look at the contents of those files they'll be make prerequisite statements, like:

foo.o : foo.h bar.h biz.h

etc.

In Makefiles GCC C programs, What are .d files and also what is a wildcard.?

These *.d files usually (and just conventionally) are make dependencies (but perhaps, and unlikely, D-language source code).

The GCC compiler knows about -M (and related) preprocessor option, which asks make to ....

Instead of outputting the result of preprocessing, output a rule suitable for make describing the dependencies of the main source file.

With the help of a few good Makefile tricks, you could write a Makefile automatically dealing with dependencies, e.g. with things like

 ## dependencies of foo.c
foo.d: foo.c
$(COMPILE.c) -M $^ -o $@
## include them
-include foo.d

About $(wildcard *.c), read the GNU make documentation, section on file name functions. So $(wildcard *.c) is globbing the *.c by make expanding it into the list of files ending with .c; you could use it e.g. to define a make variable: SOURCE_FILES= $(wildcard *.c), etc.

See also this, that and that examples.

Don't forget to try make -p to understand all the good builtin rules known by GNU make.... Use make --trace or remake-x for debugging your Makefile-s.

Autodependencies for make generate the .d files but are not reading them

You cannot rely on implicit rules if your targets have a path separator due to the way implicit rule lookup works, the most immediate way to fix your issue is to provide an explicit recipe after $(BIN)/app.exe

$(objects):
$(GPP) $(CPPFLAGS) $(CXXFLAGS) -c $^ -o $@

That said, GCC has had better dependency generation for some time now but it looks like the documentation hasn't caught up yet. You can generate dependencies as a side-effect of compilation using -MMD which means you can get rid of all the sed crud (-MP adds dummy targets for headers to avoid problems if you delete them).

Your Makefile would look something like this

BIN := bin
SRC := src
INC := include
OBJ := objects

app := $(BIN)/app.exe
sources := $(wildcard $(SRC)/*.cpp)
objects := $(subst $(SRC),$(OBJ),$(sources:.cpp=.o))
deps := $(objects:.o=.d)

CXX := g++
CPPFLAGS := -I $(INC) -MMD -MP
CXXFLAGS := -std=c++11
LDFLAGS := -L /usr/lib/x86_64-linux-gnu
LDLIBS := -lcurl

$(app) : $(objects)
$(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@

$(OBJ)/%.o: $(SRC)/%.cpp
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $^ -o $@

.PHONY: clean
clean: ; $(RM) $(objects) $(deps) $(app)

-include $(deps)

A few other notes you can see from the file

  • Use := instead of = unless you have a need for the latter
  • If your headers are in a separate folder you'll need to add the path
  • Outputting dependencies in the same directory as your objects is simpler when using GCC's auto dependency generator. You can change the output file with -MF if necessary.
  • You don't need to provide CPPFLAGS when linking, it isn't performing any preprocessing.
  • You should probably stick to the default linker recipe & variables (and CXX for that matter).
  • Use automatic variables ($@ etc) where possible in recipes.

If you really want to use the implicit rules you would need to do something like strip the paths from your object files and change to the object dir beforehand.

Issue with dependency files using Makefiles; file.d exist

You should remove the @ from your line that creates the .d file, so you can see what the command line actually is. It's always a bad idea to add @ before your makefile is working 100% correctly. Then you could cut and paste a full failure example into your question, including the command that generated the error message.

Your build lines are not right. During the compilation, you need to use $@ not $(OBJ_PATH)/$(notdir $@). It's always wrong to build a file that is not exactly $@.

During the creation of the dependency file $(@:.d=.o) is useless because $@ is already set to xxx.o so changing the .d suffix to .o doesn't do anything. You should just use -MT $@ here.

You can replace $(OBJ_PATH)/$(notdir $*.d) with the simpler %*.d.

This error is being shown by the shell and there's really no way we can understand what the problem is with the info given. Why would the shell give a "File exists" error when you use ">" to overwrite it?

I have a suspicion that it's not actually this command that is generating that error.

What is the exact chain of events when GNU make updates the .d files?

Here's what happens when you run make after make clean and dependency files don't exist yet:

  1. make reads the text and reaches -include directive.
  2. For each argument of the -include directive make tries to find a rule that has it as a target and run that rule. make does this as part of processing of -include directive. No such rule will be found, because you didn't provide one.
  3. make attempts to read files specified in -include directive and just skips those which don't exist (all of them in this case).
  4. make goes to build the project, which in turn creates those *.d files.

If you run make again:

  1. [same as before]
  2. [same as before]
  3. Since files do exist now, make reads them.
  4. [same as before]

You might notice that this implies that make uses dependencies from the previous run, which is exactly how it works.
Dependencies lag behind changes in the source files.
This doesn't usually cause inconveniences though, and when it does removing dependency files can be used to fix the build.

Building C-program out of source tree with GNU make

Here's the Makefile I've added to the documentation (currently in review so I'll post it here) :

# Set project directory one level above the Makefile directory. $(CURDIR) is a GNU make variable containing the path to the current working directory
PROJDIR := $(realpath $(CURDIR)/..)
SOURCEDIR := $(PROJDIR)/Sources
BUILDDIR := $(PROJDIR)/Build

# Name of the final executable
TARGET = myApp.exe

# Decide whether the commands will be shown or not
VERBOSE = TRUE

# Create the list of directories
DIRS = Folder0 Folder1 Folder2
SOURCEDIRS = $(foreach dir, $(DIRS), $(addprefix $(SOURCEDIR)/, $(dir)))
TARGETDIRS = $(foreach dir, $(DIRS), $(addprefix $(BUILDDIR)/, $(dir)))

# Generate the GCC includes parameters by adding -I before each source folder
INCLUDES = $(foreach dir, $(SOURCEDIRS), $(addprefix -I, $(dir)))

# Add this list to VPATH, the place make will look for the source files
VPATH = $(SOURCEDIRS)

# Create a list of *.c sources in DIRS
SOURCES = $(foreach dir,$(SOURCEDIRS),$(wildcard $(dir)/*.c))

# Define objects for all sources
OBJS := $(subst $(SOURCEDIR),$(BUILDDIR),$(SOURCES:.c=.o))

# Define dependencies files for all objects
DEPS = $(OBJS:.o=.d)

# Name the compiler
CC = gcc

# OS specific part
ifeq ($(OS),Windows_NT)
RM = del /F /Q
RMDIR = -RMDIR /S /Q
MKDIR = -mkdir
ERRIGNORE = 2>NUL || true
SEP=\\
else
RM = rm -rf
RMDIR = rm -rf
MKDIR = mkdir -p
ERRIGNORE = 2>/dev/null
SEP=/
endif

# Remove space after separator
PSEP = $(strip $(SEP))

# Hide or not the calls depending of VERBOSE
ifeq ($(VERBOSE),TRUE)
HIDE =
else
HIDE = @
endif

# Define the function that will generate each rule
define generateRules
$(1)/%.o: %.c
@echo Building $$@
$(HIDE)$(CC) -c $$(INCLUDES) -o $$(subst /,$$(PSEP),$$@) $$(subst /,$$(PSEP),$$<) -MMD
endef

# Indicate to make which targets are not files
.PHONY: all clean directories

all: directories $(TARGET)

$(TARGET): $(OBJS)
$(HIDE)echo Linking $@
$(HIDE)$(CC) $(OBJS) -o $(TARGET)

# Include dependencies
-include $(DEPS)

# Generate rules
$(foreach targetdir, $(TARGETDIRS), $(eval $(call generateRules, $(targetdir))))

directories:
$(HIDE)$(MKDIR) $(subst /,$(PSEP),$(TARGETDIRS)) $(ERRIGNORE)

# Remove all objects, dependencies and executable files generated during the build
clean:
$(HIDE)$(RMDIR) $(subst /,$(PSEP),$(TARGETDIRS)) $(ERRIGNORE)
$(HIDE)$(RM) $(TARGET) $(ERRIGNORE)
@echo Cleaning done !

Main features

  • Automatic detection of C sources in specified folders
  • Multiple source folders
  • Multiple corresponding target folders for object and dependency files
  • Automatic rule generation for each target folder
  • Creation of target folders when they don't exist
  • Dependency management with gcc : Build only what is necessary
  • Works on Unix and DOS systems
  • Written for GNU Make

How to use this Makefile

To adapt this Makefile to your project you have to :

  1. Change the TARGET variable to match your target name
  2. Change the name of the Sources and Build folders in SOURCEDIR and BUILDDIR
  3. Change the verbosity level of the Makefile in the Makefile itself or in make call (make all VERBOSE=FALSE)
  4. Change the name of the folders in DIRS to match your sources and build folders
  5. If required, change the compiler and the flags

In this Makefile Folder0, Folder1 and Folder2 are the equivalent to your FolderA, FolderB and FolderC.

Note that I have not had the opportunity to test it on a Unix system at the moment but it works correctly on Windows.


Explanation of a few tricky parts :

Ignoring Windows mkdir errors

ERRIGNORE = 2>NUL || true

This has two effects :
The first one, 2>NUL is to redirect the error output to NUL, so as it does not comes in the console.

The second one, || true prevents the command from rising the error level. This is Windows stuff unrelated with the Makefile, it's here because Windows' mkdir command rises the error level if we try to create an already-existing folder, whereas we don't really care, if it does exist that's fine. The common solution is to use the if not exist structure, but that's not UNIX-compatible so even if it's tricky, I consider my solution more clear.


Creation of OBJS containing all object files with their correct path

OBJS := $(subst $(SOURCEDIR),$(BUILDDIR),$(SOURCES:.c=.o))

Here we want OBJS to contain all the object files with their paths, and we already have SOURCES which contains all the source files with their paths.
$(SOURCES:.c=.o) changes *.c in *.o for all sources, but the path is still the one of the sources.
$(subst $(SOURCEDIR),$(BUILDDIR), ...) will simply subtract the whole source path with the build path, so we finally have a variable that contains the .o files with their paths.


Dealing with Windows and Unix-style path separators

SEP=\\
SEP = /
PSEP = $(strip $(SEP))

This only exist to allow the Makefile to work on Unix and Windows, since Windows uses backslashes in path whereas everyone else uses slashes.

SEP=\\ Here the double backslash is used to escape the backslash character, which make usually treats as an "ignore newline character" to allow writing on multiple lines.

PSEP = $(strip $(SEP)) This will remove the space char of the SEP variable, which has been added automatically.


Automatic generation of rules for each target folder

define generateRules
$(1)/%.o: %.c
@echo Building $$@
$(HIDE)$(CC) -c $$(INCLUDES) -o $$(subst /,$$(PSEP),$$@) $$(subst /,$$(PSEP),$$<) -MMD
endef

That's maybe the trick that is the most related with your usecase. It's a rule template that can be generated with $(eval $(call generateRules, param)) where param is what you can find in the template as $(1).
This will basically fill the Makefile with rules like this for each target folder :

path/to/target/%.o: %.c
@echo Building $@
$(HIDE)$(CC) -c $(INCLUDES) -o $(subst /,$(PSEP),$@) $(subst /,$(PSEP),$<) -MMD

How to generate dependency files when generating object files of c sources with make

There are two small mistakes which took me quite a while to see:

  • DEPFILES := $(SRCS:%.c=$(DEPDIR)/%.d) has to be DEPFILES := $(SOURCES:%.c=$(DEPDIR)/%.d) - otherwise DEPFILES is empty since SRCS is undefined.
  • In $(CC) $(DEPFLAGS) -c $(CFLAGS) $(INCLUDES) -o $@ $^ the $^ (all the prerequisites) expands to e. g. main.c .deps/main.d, so a not yet existing .deps/main.d is passed as an input file; we want $*.c instead of $^.

Another minor error is:

  • rm -r .dep should be rm -r .deps or rm -r $(DEPDIR).


Related Topics



Leave a reply



Submit