Generate Dependencies for a Makefile for a Project in C/C++

generate dependencies for a makefile for a project in C/C++

GNU make's documentation provides a good solution.

Absolutely. g++ -MM <your file> will generate a GMake compatible list of dependencies. I use something like this:

# Add .d to Make's recognized suffixes.
SUFFIXES += .d

#We don't need to clean up when we're making these targets
NODEPS:=clean tags svn
#Find all the C++ files in the src/ directory
SOURCES:=$(shell find src/ -name "*.cpp")
#These are the dependency files, which make will clean up after it creates them
DEPFILES:=$(patsubst %.cpp,%.d,$(SOURCES))

#Don't create dependencies when we're cleaning, for instance
ifeq (0, $(words $(findstring $(MAKECMDGOALS), $(NODEPS))))
#Chances are, these files don't exist. GMake will create them and
#clean up automatically afterwards
-include $(DEPFILES)
endif

#This is the rule for creating the dependency files
src/%.d: src/%.cpp
$(CXX) $(CXXFLAGS) -MM -MT '$(patsubst src/%.cpp,obj/%.o,$<)' $< -MF $@

#This rule does the compilation
obj/%.o: src/%.cpp src/%.d src/%.h
@$(MKDIR) $(dir $@)
$(CXX) $(CXXFLAGS) -o $@ -c $<

Note: $(CXX)/gcc command must be preceded with a hard tab

What this will do is automatically generate the dependencies for each file that has changed, and compile them according to whatever rule you have in place. This allows me to just dump new files into the src/ directory, and have them compiled automatically, dependencies and all.

Generate all project dependencies in a single file using gcc -MM flag

Something along these lines is what I use to get all my dependencies in a single file:

program_H_SRCS := $(wildcard *.h)
program_C_SRCS := $(wildcard *.c)
DEPS = make.deps

make.deps: $(program_C_SRCS) $(program_H_SRCS)
$(CC) $(CPPFLAGS) -MM $(program_C_SRCS) > make.deps

include $(DEPS)

This basically causes all the user ( as opposed to system ) dependencies to be rebuilt into a single file whenever any C or H file in the project is modified.

+++++++++ EDIT +++++++++++

I've since found a better way of doing things. I generate a separate dep file for each source file. Here is the basic makefile:

program_NAME := myprogram
program_SRCS := $(wildcard *.c)
program_OBJS := ${program_SRCS:.c=.o}
clean_list += $(program_OBJS) $(program_NAME)

# C Preprocessor Flags
CPPFLAGS +=
# compiler flags
CFLAGS += -ansi -Wall -Wextra -pedantic-errors

.PHONY: all clean distclean

all: $(program_NAME)

clean:
@- $(RM) $(clean_list)

distclean: clean

# Generate dependencies for all files in project
%.d: $(program_SRCS)
@ $(CC) $(CPPFLAGS) -MM $*.c | sed -e 's@^\(.*\)\.o:@\1.d \1.o:@' > $@

clean_list += ${program_SRCS:.c=.d}

$(program_NAME): $(program_OBJS)
indent -linux -brf $(program_SRCS)
splint $(program_SRCS)
$(LINK.c) $(program_OBJS) -o $(program_NAME)

ifneq "$(MAKECMDGOALS)" "clean"
# Include the list of dependancies generated for each object file
-include ${program_SRCS:.c=.d}
endif

This does two things:

  1. If any of the files that foo.c depend on change then foo.o is rebuilt without having to rebuild other files in the project.
  2. The dep file itself has the same dependencies as the object file, so that if any of the deps are modified the dep file itself is also regenerated, before the object file deps are checked.

Makefile (Auto-Dependency Generation)

Newer versions of GCC have an -MP option which can be used with -MD. I simply added -MP and -MD to the CPPFLAGS variable for my project (I did not write a custom recipe for compiling C++) and added an "-include $(SRC:.cpp=.d)" line.

Using -MD and -MP gives a dependency file which includes both the dependencies (without having to use some weird sed) and dummy targets (so that deleting header files will not cause errors).

Proper makefile dependency list (C++)

The example from your textbook:

main.o : main.cpp threeintsfcts.cpp threeintsfcts.h
g++ -Wall -c main.cpp

is wrong. The purpose of separating source code into two files is so that they can be compiled independently; if one depends on the other, they have been separated incorrectly.

"...Why should any .h file be included as a dependency anyways?
Wouldn't a change in any .h file register as a change in the
respective .cpp file since the contents of the .h are just going to be
copy and pasted into the respective .cpp file through #include?"

If threeintsfcts.h is the only file that has been changed, then main.cpp has not been changed. Make is not smart enough to parse main.cpp and deduce that threeintsfcts.h ought to be a prerequisite of main.o. (There are ways to get Make to do that, but you must master the basics first.)

...In case threeintsfcts.cpp changes the name of one of its functions
used in main and you forget to change it in main.

In that case you will not be able to build the executable; Make can (and will) inform you of the problem, but not fix it, no matter how you arrange the prerequisite lists.

How to collect all dependencies of a large Makefile project?

Instead of -MMD, you can use -MM, which sends the dependencies to standard output.

You can then collect all the output to some dependency file in the top level directory with

gcc -MM ... file.c >>$(top)/all.d

If post processing is the only reason for collecting the output in one file, you can filter the output with a pipe

gcc -MM ... file.c | sh filter.sh >file.d

and keep the dependency files separate.

If the path to some local include file (defs.h) or the main source is important, you can force gcc to include a path by giving the appropriate -I option, e.g.

gcc -MM -I$(top)/path/to ... $(top)/path/to/file.c >>$(top)/all.d

or

gcc -MM -I$(top)/path/to ... $(top)/path/to/file.c | sh filter.sh >file.d

Instead of

file.o: file.c defs.h

gcc will emit

file.o: /absolute/path/to/file.c /absolute/path/to/defs.h

This works with relative paths as well, of course.

how to manage c files dependencies in make

-include means to include the dep file if it is there but not fail if it isn't.

The trick, and this is common in make dependency tracking, is that your dependencies are actually one build out of date. You're including, if they are there, the dependency files that were built the last time around.

This is not a problem because for dependencies to change, changes have to be made to something that the target depended on during the last build -- so even though make doesn't know the full new dependencies, it knows that it has to rebuild the target (and generate a new dependency file in the process).

Addendum: By the way, gcc and clang have a -MD option that can generate a dependency file while building the .o (by default with a .d suffix). This means that you can do automatic dependency tracking with implicit rules and cut down your Makefile to the bare minimum like so (for a simple project with .c files in a flat directory):

#!/usr/bin/make -f

# name of the binary to build
TARGET = target

CC = gcc

# These flags are used by the implicit rules for C preprocessor flags,
# C compiler flags, linker flags, and libraries to link (-lfoo options)
# -MD in CPPFLAGS means that the implicit rules for .o files will also
# generate a corresponding .d file that contains the dependencies.
# The values here are just examples (thank you, Rear Admiral Obvious!)
CPPFLAGS = -MD -I somewhere/include
CFLAGS = -O2 -g
LDFLAGS = -L somewhere/lib
LDLIBS = -lsomelibrary

# SRCS is a list of all .c files in the directory, the other two are
# pattern substitution expressions that take SRCS and replace the .c with .o
# and .d, respectively
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
DEPS = $(OBJS:.o=.d)

all: $(TARGET)

$(TARGET): $(OBJS)

# Look, Ma, no explicit rules for .o files!

clean:
rm -f $(TARGET) $(OBJS)

.PHONY: all clean

# include dep files (if available).
-include $(DEPS)

How to generate one large dependency map for the whole project that builds with makefiles?

One thing you could look for is "makefile graph" -- there are a few projects out there to make dependency trees from large makefile projects. Here's one: http://sailhome.cs.queensu.ca/~bram/makao/index.html . I think it will both create a pretty graph, and actually build things.

If you want to roll your own Makefile parser, and essentially replace make with your own custom script, my advice would be "don't". But there is a collection of Perl modules: Makefile::Parser. It claims to "pass 51% of the GNU Make test suite".

If you want to just look at what's taking so long, you can turn on Make's debugging output. Bits of it are usually sort of useless, like the pages and pages of Make deciding whether or not to apply implicit rules, but maybe there will be some obvious thing that happens that doesn't need to happen. If you want to do this, you could look at remake, "GNU Make with comprehensible tracing and a debugger".

Generating dependency files in separate folder

Drop all of the sed nonsense. gcc can generate everything in one go:

$(builddir)/%.o : $(srcdir)/%.cc
$(CPP) -c $< -o $@ -MP -MMD -MF $(@:.o=.d)

That will in one go build build/foo.o and build/foo.d, where build/foo.d will have the autogenerated dependencies for build/foo.o.



Related Topics



Leave a reply



Submit