Makefile (Auto-Dependency Generation)

2019-01-04 10:31发布

just for quick terminology:

#basic makefile rule
target: dependencies
    recipe

The Problem: I want to generate the dependencies automatically.

For example, I am hoping to turn this:

#one of my targets
file.o: file.cpp 1.h 2.h 3.h 4.h 5.h 6.h 7.h 8.h another.h lots.h evenMore.h
    $(COMPILE)

Into this:

#one of my targets
file.o: $(GENERATE)
    $(COMPILE)

and I'm not too sure if it's possible..

What I do know:

I can use this compiler flag:

g++ -MM file.cpp

and it will return the proper target and dependency.
so from the example, it would return:

file.o: file.cpp 1.h 2.h 3.h 4.h 5.h 6.h 7.h 8.h another.h lots.h evenMore.h  

however, 'make' does NOT allow me to explicitly write shell code in the target or dependency section of a rule :(
I know there is a 'make' function called shell

but I can't quite plug this in as dependency and do parsing magic because it relies on the macro $@ which represents the target.. or at least I think that’s what the problem is

I've even tried just replacing the "file.cpp" dependency with this makefile function and that won't work either..

#it's suppose to turn the $@ (file.o) into file.cpp
THE_CPP := $(addsuffix $(.cpp),$(basename $@))

#one of my targets
file.o: $(THE_CPP) 1.h 2.h 3.h 4.h 5.h 6.h 7.h 8.h another.h lots.h evenMore.h
    $(COMPILE)
#this does not work

So all over google, there appear to be two solutions. both of which I don't fully grasp.
From GNU Make Manual

Some Site that says the GNU Make Manual one is out-of-date

So my ultimate question is: Is it possible to do it the way I want to do it,
and if not, can somebody break down the code from one of these sites and explain to me in detail how they work. I'll implement it one of these ways if I have to, but I'm weary to just paste a chunk of code into my makefile before understanding it

7条回答
你好瞎i
2楼-- · 2019-01-04 10:53

To manipulate the filenames when you already know what the dependencies should be, you can use a pattern rule:

file.o: %.o : %.cpp 1.h 2.h 3.h 4.h 5.h 6.h 7.h 8.h another.h lots.h evenMore.h
    $(COMPILE)

And you can reuse the rule for other targets:

# Note these two rules without recipes:
file.o: 1.h 2.h 3.h 4.h 5.h 6.h 7.h 8.h another.h lots.h evenMore.h
anotherFile.o: 4.h 9.h yetAnother.h

file.o anotherFile.o: %.o : %.cpp
    $(COMPILE)

But if you want Make to figure out the list of dependencies automatically, the best way (that I know of) is Advanced Auto-Dependency Generation. It looks like this:

%.o : %.cc
        @g++ -MD -c -o $@ $<
        @cp $*.d $*.P; \
             sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
                 -e '/^$$/ d' -e 's/$$/ :/' < $*.d >> $*.P; \
             rm -f $*.d

-include *.P

Basically, when it builds file.o, it also builds file.d. Then it runs file.d through a bewildering sed command that turns the list of dependencies into a rule with no recipes. The last line is an instruction to include any such rules that exist. The logic here is subtle and ingenious: you don't actually need the dependencies the first time you build foo.o, because Make already knows that foo.o must be built, because it doesn't exist. The next time you run Make, it will use the dependency list it created last time. If you change one of the files so that there is actually a new dependency which is not in the list, Make will still rebuild foo.o because you changed a file which was a dependency. Try it, it really works!

查看更多
三岁会撩人
3楼-- · 2019-01-04 10:57

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).

查看更多
老娘就宠你
4楼-- · 2019-01-04 10:58

Excellent answers but in my build I put the .obj files in a subdirectory based on build type (ie: debug vs. release). So for example, if I'm building debug, I put all the object files in a build/debug folder. It was a mind-numbing task to try to get the multiline sed command above to use the correct destination folder, but after some experimentation, I stumbled on a solution that works great for my build. Hopefully it'll help someone else as well.

Here's a snippet:

# List my sources
CPP_SOURCES := foo.cpp bar.cpp

# If I'm debugging, change my output location
ifeq (1,$(DEBUG))
  OBJ_DIR:=./obj/debug
  CXXFLAGS+= -g -DDEBUG -O0 -std=c++0x
else
  CXXFLAGS+= -s -O2 
  OBJ_DIR:=./obj/release
endif

# destination path macro we'll use below
df = $(OBJ_DIR)/$(*F)

# create a list of auto dependencies
AUTODEPS:= $(patsubst %.cpp,$(OBJ_DIR)/%.d,$(CPP_SOURCES))

# include by auto dependencies
-include $(AUTODEPS)

.... other rules

# and last but not least my generic compiler rule
$(OBJ_DIR)/%.o: %.cpp 
    @# Build the dependency file
    @$(CXX) -MM -MP -MT $(df).o -MT $(df).d $(CXXFLAGS) $< > $(df).d
    @# Compile the object file
    @echo " C++ : " $< " => " $@
    @$(CXX) -c $< $(CXXFLAGS) -o $@

Now for the details: The first execution of CXX in my generic build rule is the interesting one. Note that I'm not using any "sed" commands. Newer versions of gcc do everything I needed (I'm using gcc 4.7.2).

-MM builds the main dependency rule including project headers but not system headers. If I left it like this, my .obj file would NOT have the correct path. So I use the -MT option to specify the "real" path to my .obj destination. (using the "df" macro I created).
I also use a second -MT option to make sure the resulting dependency file (ie: .d file) has the correct path, and that it is included in the target list and therefor has the same dependencies as the source file.

Last but not least is the inclusion of the -MP option. This tell gcc to also make stubbed rules for each header solving the problem that occurs if I delete a header causing make to generate an error.

I suspect that since I'm using gcc for all the dependency generation instead of piping out to sed, my build is faster (although I've yet to prove that since my build is relatively small at this point). If you see ways I can improve upon this, I'm always open to suggestions. Enjoy

查看更多
劫难
5楼-- · 2019-01-04 11:06

First, you can have THE_CPP=$(patsubst %.o,%.cpp,$@)

Then you can run make -p to understand the builtin rules of make

A usual way of doing could be to generate the makefile dependencies into *.md files:

%.o: %.c
       $(COMPILE.c) $(OUTPUT_OPTION) $< -MMD -MF $(patsubst %.c,%.md,$@)

and later in your Makefile including them with something like

-include $(wildcard *.md)

But you can also consider using other builders like omake and many many others

查看更多
放荡不羁爱自由
6楼-- · 2019-01-04 11:06

WOOO! I did manage to get the code in Beta's post to work on a small test project.
I should note, for anyone else who may come across this, If you're using the bash shell(which I was), you will need to add an escape character in front of the pound sign to escape from making the rest of the expression a comment. (see 4th line of code)

%.o : %.cpp  
    g++ -c -MD -o $@ $<  
    cp $*.d $*.P; \  
    sed -e 's/\#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \  
        -e '/^$$/ d' -e 's/$$/ :/' < $*.d >> $*.P; \  
    rm -f $*.d  
-include *.P  

Now I want to share information that I found in Managing Projects with GNU Make, 3rd Edition. because it's points out some important issues on this matter, and supplies code that I still don't fully grasp yet.
A method appears in the book that is similar to the method found on the Make manual page.
It looks like this:

include $(subst .c,.d,$(SOURCES))

%.d: %.c
    $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
    sed 's,\($*\).o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
    rm -f $@.$$$$

This is what I believe is happening.
Right away, 'make' wants to include a ".d" file for every source file.
Because no .d files initially exist, the chunk of code is ran again and again in order to create all the missing .d files.
This means make will start over again and again until every .d file is created and included in the makefile.
Each ".d" file is what Beta said: a target with a set of dependencies and NO recipe.

If a header file is ever changed, those rules that are included in, will need the dependencies updated first. This is what throws me off a bit, how is it that the chunk of code is able to be called again? It is used to update .d files, so if a .h file changes how does it get called? Aside from this, I realize that the default rule is used to compile the object. Any clarifications/misconceptions to this explanation are appreciated.


Later in the book it points out problems with this method, and problems that I believe also exist in the Advanced Auto-Dependency Generation implementation.
Problem 1: It's inefficient. 'make' must restart every time it makes a .d file
Problem 2: make generates warning messages for all the missing .d files- Which is mostly just a nuisance and can be hidden by adding a "-" in front of the include statement.
Problem 3: If you delete a src file because it's no longer needed, 'make' will crash the next time you try to compile because some .d file has the missing src as a dependency, and because there is no rule to recreate that src, make will refuse to go any further.

They say a fix to these issues is Tromey's method, but the code looks very different from the code on the website. Perhaps it's just because they used some macros, made it a function call, and wrote it slightly different. I'm still looking into it, but wanted to share some discoveries I've made so far. Hopefully this opens up a little bit more discussion, and gets me closer to the bottom of all this.

查看更多
唯我独甜
7楼-- · 2019-01-04 11:07

I prefer to use $(shell ...) function with find. Here is a sample of one of my Makefiles:

SRCDIR = src
OBJDIR = obj
LIBDIR = lib
DOCDIR = doc

# Get Only the Internal Structure of Directories from SRCDIR
STRUCTURE := $(shell find $(SRCDIR) -type d)

#Filter-out hidden directories
STRUCTURE := $(filter-out $(shell find $(SRCDIR)/.* -type d),$(STRUCTURE))

# Get All Files From STRUCTURE
CODEFILES := $(addsuffix /*,$(STRUCTURE))
CODEFILES := $(wildcard $(CODEFILES))


## Filter Only Specific Files
SRCFILES := $(filter %.c,$(CODEFILES))
HDRFILES := $(filter %.h,$(CODEFILES))
OBJFILES := $(subst $(SRCDIR),$(OBJDIR),$(SRCFILES:%.c=%.o))
DOCFILES := $(addprefix $(DOCDIR)/,             \
            $(addsuffix .md,                    \
            $(basename $(SRCFILES))))


# Filter Out Function main for Libraries
LIBDEPS := $(filter-out $(OBJDIR)/main.o,$(OBJFILES))

In this approach, I first get all the internal directory structure, with any depth. Then I get all files inside the Structure. At this time, I can use filter, filter-out, addsuffix, etc, to get exactly what I need at each time.

This example covers *.c files, but you can change it to *.cpp as well.

查看更多
登录 后发表回答