可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
What approach do C++ programmers on Unix platform use to create and manage Makefiles?
I was using hand made Makefiles for my projects but they don't handle header file changes and other dependencies. I googled around and found a good solution here.
But I ran into a problem here in the sed command -
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
-e '/^$$/ d' -e 's/$$/ :/' < $*.d >> $*.P; \
The problem is with the 3rd expression "-e 's/ *\$$//'.
It doesn't work. Its supposed to remove trailing backslashes. I understand that there has to be double dollar there since this is part of a Makefile. Can someone tell me what wrong here?
Here's the complete Makefile -
CC=g++
CFLAGS=-g -Wall
LIBS=-lpthread
OBJS=file1.o file2.o
TARGET=testProg
$(TARGET) : $(OBJS)
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
%.o : %.cpp
$(CC) -MMD -c -o $@ $< $(CFLAGS)
@cp $*.d $*.P; \
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
-e '/^$$/ d' -e 's/$$/ :/' < $*.d >> $*.P; \
rm -f $*.d
-include $(OBJS:%.o=%.P)
clean :
rm -f $(TARGET) $(OBJS)
all : $(TARGET)
Apart from the solution to this problem, I would also like some tips/pointers to my 1st question.
回答1:
- I use that approach too and can't praise it highly enough. And I write my makefiles by hand and reuse them a lot on new projects.
- .The expression "s/ *\\$//" will work outside the context of Make. Within a makefile it doesn't work because Make tries to interpret "$/" before handing the result to the shell. So you must use "s/ *\\$$//" (note the extra $) within the makefile, but this won't work outside the context of Make (so testing it is a slight pain).
EDIT:
I've tried your makefile, and that sed statement seems to remove trailing backslashes just fine. Try something simpler, like this:
backslash:
@echo " \\" > $@
test: backslash
@echo without sed:
@cat backslash
@echo with sed:
@sed -e 's/ *\\$$//' < backslash
EDIT:
All right, now I'm hooked. Could you try these experiments and tell us the results?
Change the last character to 'z' : s/.$/z/
Change a trailing backslash to 'z' : s/\\$/z/
Change a trailing backslash to 'z' : sm\\$mzm
Delete a trailing backslash : s/\\$//
Delete spaces and a trailing backslash: s/ *\\$//
Try all of these inside and outside of Make, with '$' and '$$'.
回答2:
gcc/g++ can generate dependencies for you with the -M
family of options.
The following works by specifying how to generate .depends
files given a source file. By doing -include $(DEPS)
$(DEPS) is recognized as a target and will be built/rebuilt when the source files change.
CXX = g++
CXXFLAGS = -Wall -O3
LDFLAGS =
TARGET = testcpp
SRCS = main.cc x.cc foo.cc
OBJS = $(SRCS:.cc=.o)
DEPS = $(SRCS:.cc=.depends)
.PHONY: clean all
all: $(TARGET)
$(TARGET): $(OBJS)
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) -o $(TARGET)
.cc.o:
$(CXX) $(CXXFLAGS) -c $< -o $@
%.depends: %.cc
$(CXX) -M $(CXXFLAGS) $< > $@
clean:
rm -f $(OBJS) $(DEPS) $(TARGET)
-include $(DEPS)
回答3:
In a make file anything you list on the dependency line is a dependency header files or other files included.
BSD tutorial on make Note: you can auto generate header dependency info with the -MM switch of GCC.
回答4:
I must be missing something. Why doesn't generating dependency files work for you?
回答5:
I prefer to use CMake, even though it's not strictly the solution to your problem.
It's a project description language that'll generate your Makefiles, Visual Studio Project, Eclipse Project, KDevelop, etc for you. All the dependencies are done for you:
CMakeLists.txt
add_executable(my_exe file1.c file2.c)
target_link_libraries(my_exe my_library)
add_subdirectory(lib)
In lib/CMakeLists.txt
add_library(my_library file3.c file4.c)
This creates a my_exe from file1.c file2.c linked against my_library. I find this much simpler. It also has things like package discovery:
find_package(Qt4)
回答6:
The makedepend utility is installed on many systems and can be quite useful for generating dependency information.
Here is an example Makefile that uses the include
directive (plus a little Perl magic) to incorporate the output from makedepend:
# the name of the executable that we'll build
TARGET = foo_prog
# our .cc source files
SRCS = foo.cc main.cc
# the .o versions of our source files
OBJS := $(patsubst %.cc, %.o, $(filter %.cc, $(SRCS)))
# some flags for compiling
CXXFLAGS = -Wall -Werror
# In order to build $(TARGET), we first build each of the $(OBJS).
# Then we use the given command to link those $(OBJS) into our
# $(TARGET) executable. $^ is a shortcut for $(OBJS). $@ is a
# shortcut for $(TARGET).
#
# The default compile rule will compile each of the $(OBJS) for us.
$(TARGET): $(OBJS)
$(CXX) $(CXXFLAGS) $^ -o $@
# Use "make clean" to remove all of the support files.
clean:
rm -f $(OBJS) $(TARGET) Makefile.depend *~
# This automatically uses the 'makedepend' utility to add any
# dependencies that our source files have, namely .h files. This way,
# if the .h files change, the code will be re-compiled.
include Makefile.depend
Makefile.depend: $(SRCS)
makedepend -f- -Y $(SRCS) 2> /dev/null | \
perl -p -e "s/(^.*?:)/Makefile.depend \1/" > Makefile.depend
If both foo.cc
and main.cc
depend on foo.h
, then the contents of Makefile.depend
would be:
Makefile.depend foo.o: foo.h
Makefile.depend main.o: foo.h
The end result is that the dependency information from makedepend
is injected into the Makefile as a series of rules. It's similar to the approach of using a .d
file for each .cc
file, but keeps the dependency information in one file instead of scattered all over the place.
回答7:
In Mozilla's build system, we use GCC's -MD switch to generate the dependency files:
http://mxr.mozilla.org/mozilla-central/source/configure.in#7134
and then we use a script called mddepend.pl to check for removed header files, such that
removing a header simply causes a rebuild, not an error:
http://mxr.mozilla.org/mozilla-central/source/config/rules.mk#2066
http://mxr.mozilla.org/mozilla-central/source/build/unix/mddepend.pl
That script generates an .all.pp file containing all the dependencies, with extra foo.o: FORCE
dependencies stuck in for missing header files. We then simply -include the .all.pp file in rules.mk right below there.
回答8:
You can use qmake to generate Makefiles for a project even if that project is not using Qt.
回答9:
I use BSD make (pmake?) which does lot of work for me (my lang is C, but I think no difference here). This is my common 'local.prog.mk', I never change it:
.PHONY: tags .depend
# .depend depends only on $(SRCS) in bsd.dep.mk, so we can't track changes of
# header's own dependencies properly. so .depend is .PHONY target here.
CSTD ?=c99
WARNS ?=9
.if !empty(PC_LIST)
PC_CF !=pkg-config --cflags $(PC_LIST)
PC_LD !=pkg-config --libs $(PC_LIST)
.endif
CFLAGS +=$(PC_CF) -fgnu89-inline
.if !defined(NO_DEBUG)
CFLAGS +=-O0 -ggdb3
.endif
LDFLAGS +=$(PC_LD)
CTAGS =exctags
NO_MAN=
NO_OBJ=
CLEANFILES+=$(PROG).core
.include <bsd.prog.mk>
$(PROG): $(SUBDIR)
build: clean cleandepend depend all
run: $(PROG)
./$(PROG)
Note 'bsd.prog.mk' inclusion -- this handles all, build, depend, clean targets. Project-specific BSDmakefile
s are simple:
.SILENT:
PROG =hello
SRCS =hello.c world.c
PC_LIST =gtk+-2.0 gnet-2.0
.include "../local.prog.mk"
proto:
cproto -siv `pkg-config --cflags $(PC_LIST)` $(SRCS) > prototypes
CLEANFILES+=prototypes
I just make depend every time I insert/remove any #include directives.
回答10:
Instead of the sed scripts, use gcc's -MT option to modify the target of the generated dependency rules. This blog post has more info.
回答11:
With a more modern version of GCC, you can add the -MP flag to have GCC generate empty rules for the headers itself.
回答12:
I top tip that I have found useful when building dependency files is to include the dependency file as a target in the generated rule:
file.d file.o : file.c header.h header2.h ...
Thus make
will regenerate the dependencies if the source or any of the headers change. Including phony targets for the headers (GCC -MP
) should then allow stable builds when headers are removed - the absense of required header remains a compilation error, not a make dependency error.
Assuming that dependency files are generated into the same directory as the object files, the following should work for GCC on Unix:
-include $(OBJ:.o=.d)
$(OBJDIR)/%d : $(SRCDIR)/%.cpp
mkdir -p $(@D)
echo -n "$@ " > $@.tmp
$(CXX) $(CPPFLAGS) -MM -MP -MT $(@:.d=.o) $< >> $@.tmp
mv $@.tmp $@
(from memory)