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:
make
reads the text and reaches-include
directive.- For each argument of the
-include
directivemake
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. make
attempts to read files specified in-include
directive and just skips those which don't exist (all of them in this case).make
goes to build the project, which in turn creates those*.d
files.
If you run make
again:
- [same as before]
- [same as before]
- Since files do exist now,
make
reads them. - [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
andDOS
systems - Written for
GNU Make
How to use this Makefile
To adapt this Makefile to your project you have to :
- Change the
TARGET
variable to match your target name - Change the name of the
Sources
andBuild
folders inSOURCEDIR
andBUILDDIR
- Change the verbosity level of the Makefile in the Makefile itself or in make call (
make all VERBOSE=FALSE
) - Change the name of the folders in
DIRS
to match your sources and build folders - 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 beDEPFILES := $(SOURCES:%.c=$(DEPDIR)/%.d)
- otherwiseDEPFILES
is empty sinceSRCS
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 berm -r .deps
orrm -r $(DEPDIR)
.
Related Topics
Black Color Showing on Cmy Channels When Converted to Cmyk Using Ghostscript
Every Command Is Returning 'Bash: <Command>: Command Not Found...'
How to Connect Github Desktop with Cpanel
How to Make Find and Printf Works in Bash Script
How to Append to a File Using X86-64 Linux System Calls
About Process Control Block in Os
Escape Single Quotes in Shell Script
Trailing Arguments with Find -Exec {} +
Adding a Header into Multiple .Txt Files
I Want to Remove Multiple Line of Text on Linux
Grabbing Specific Sections of Text from a String
Why Is Cpu-Cycles Much Less Than CPU Current Frequency
How to Install Cargo on a Rhel Linux Server
Why Do I Get 'Permission Denied' After Using ./File2 in Linux