Makefile规则被错误地应用了。

4
我有一个针对一些 C++ 代码的 makefile(如下所示),但是它存在一个令人烦恼的问题,即当我执行 make clean 时,依赖文件被编译(然后再次删除),这会使 make clean 变得太慢。依赖规则为:
$(DEPENDDIR)%.d: %.cpp
    @mkdir -p $(DEPENDDIR)
    $(CXX) -M -MG -MT $(OBJECTDIR)$*.o $(CXXFLAGS) $< > $@

有人能看出问题在哪里吗?

我尝试将依赖项移动到编译对象的部分,即:

$(OBJECTDIR)%.o: %.cpp
    @mkdir -p $(OBJECTDIR) # $(dir $@)
    @echo " "
    $(CXX) -M -MG -MT $@ $(CXXFLAGS) $< \
        -MF $(patsubst $(OBJECTDIR)%.o, $(DEPENDDIR)%.d, $@)

但是 version.hpp 头文件没有生成(编译失败)。

KERNEL := $(shell uname -s)

PROGNAME=nextsim.exec

CXX = g++

# setting the C++ standard according to the gcc compiler version (from gcc-5.2, the default is C++14)
ifeq ($(shell echo `$(CXX) -dumpversion | cut -f1-2 -d.` \>= 5.2 | sed -e 's/\.//g' | bc),1)
    CXXFLAGS += -std=c++14
else
    CXXFLAGS += -std=c++11
endif

# add g++ option flags
CXXFLAGS += -ftemplate-depth-256 -Wno-inline \
        -fPIC -fopenmp \
        -DHAVE_CONFIG_H -D_MULTITHREADING_
ifdef NEXTSIM_COMPILE_VERBOSE
    CXXFLAGS += -v
endif

ifdef USE_OASIS
    CXXFLAGS += -DOASIS
    CXXFLAGS += -I $(NEXTSIMDIR)/modules/oasis/include
    LDFLAGS += -lgfortran
    LDFLAGS += -L $(NEXTSIMDIR)/lib -loasis
    CHAN = MPI1
    #LIBPSMILE = $(OASIS_DIR)/lib/libpsmile.${CHAN}.a $(OASIS_DIR)/lib/libmct.a $(OASIS_DIR)/lib/libmpeu.a $(OASIS_DIR)/lib/libscrip.a
endif

ifneq (,$(strip $(filter DEBUG Debug debug PROFILE Profile profile,$(NEXTSIM_BUILD_TYPE))))
    #ifeq ($(NEXTSIM_BUILD_TYPE),$(filter $(NEXTSIM_BUILD_TYPE),Debug debug))
    CXXFLAGS := $(filter-out -O3 -pthread,$(CXXFLAGS))
    CXXFLAGS += -g -O0 -DNDEBUG
ifneq (,$(strip $(filter PROFILE Profile profile,$(NEXTSIM_BUILD_TYPE))))
    CXXFLAGS += -DWITHGPERFTOOLS
endif
ifneq ($(KERNEL),Linux)
    CXXFLAGS += -Wl,-no_pie
endif
else
    CXXFLAGS += -O3 -pthread
endif

# add include paths
CXXFLAGS += -I $(NEXTSIMDIR)/core/include
CXXFLAGS += -isystem $(NEXTSIMDIR)/contrib/bamg/include # suppress annoying compilation warnings from -I
CXXFLAGS += -isystem $(NEXTSIMDIR)/contrib/mapx/include # suppress annoying compilation warnings from -I
# CXXFLAGS += -I $(NEXTSIMDIR)/contrib/interp/include

ifdef USE_ENSEMBLE
    CXXFLAGS += -DENSEMBLE
    CXXFLAGS += -I $(NEXTSIMDIR)/modules/enkf/perturbation/include
endif

ifdef USE_AEROBULK
        CXXFLAGS += -I $(AEROBULK_DIR)/include
        CXXFLAGS += -DAEROBULK
endif

# openmpi
CXXFLAGS += -I $(OPENMPI_INCLUDE_DIR)/

# petsc
CXXFLAGS += -I $(PETSC_DIR)/include

# boost
CXXFLAGS += -I $(BOOST_INCDIR)/ -I .

# netcdf
CXXFLAGS += -I $(NETCDF_DIR)/include

# gmsh
CXXFLAGS += -I $(GMSH_DIR)/include/gmsh

CXXFLAGS += -I /opt/local/include

ifeq ($(KERNEL),Linux)
    #CXXFLAGS += -std=c++0x -std=c++11 -pedantic -ftemplate-depth-256 -Wno-inline -fPIC -g -lm -pthread -v #-MMD -MP -lm -pthread -v

else

    ifeq ($(CXX),clang)
    CXXFLAGS += -stdlib=libc++
    endif

    CXXFLAGS += -I /usr/local/include #-I /opt/local/include/openmpi-mp

    #LDFLAGS += -Wl,-rpath,/usr/local/lib #-Wl,-rpath,/opt/local/lib/openmpi-mp
    #LDFLAGS += -L /usr/local/lib #-L /opt/local/lib/openmpi-mp -lmpi_cxx -lmpi -ldl -lstdc++ -lpthread

    ifeq ($(CXX),clang)
    LDFLAGS += -stdlib=libc++
    endif

endif

LDFLAGS += -L /usr/local/lib

LDFLAGS += -Wl,-rpath,$(OPENMPI_LIB_DIR)/
ifndef MACHINE_HEXAGON
    LDFLAGS += -L $(OPENMPI_LIB_DIR)/ -lmpi_cxx -lmpi -ldl -lstdc++ #-lpthread
else
    LDFLAGS += -L $(OPENMPI_LIB_DIR)/ -lmpichcxx -lmpich -ldl -lstdc++ #-lpthread #-lssl -luuid -lpthread -lrt
        LDFLAGS += -Wl,-rpath,$(BLAS_LAPACK_DIR)/lib
        LDFLAGS += -L $(BLAS_LAPACK_DIR)/lib -lsci_gnu_mp
endif

LDFLAGS += -Wl,-rpath,$(NETCDF_DIR)/lib -L $(NETCDF_DIR)/lib -lnetcdf_c++4

LDFLAGS += -Wl,-rpath,$(BOOST_LIBDIR)
LDFLAGS += -L $(BOOST_LIBDIR) -lboost_program_options -lboost_filesystem -lboost_system -lboost_serialization -lboost_mpi -lboost_date_time

LDFLAGS += -Wl,-rpath,$(PETSC_DIR)/lib
LDFLAGS += -L $(PETSC_DIR)/lib -lpetsc

LDFLAGS += -Wl,-rpath,$(NEXTSIMDIR)/lib
LDFLAGS += -L $(NEXTSIMDIR)/lib -lbamg
#LDFLAGS += -L $(NEXTSIMDIR)/lib -linterp
LDFLAGS += -L $(NEXTSIMDIR)/lib -lmapx
#LDFLAGS += -L $(NEXTSIMDIR)/lib -loasis

ifdef USE_ENSEMBLE
    LDFLAGS += -L $(NEXTSIMDIR)/lib -lpseudo2D
    LDFLAGS += -lgfortran
endif

ifdef USE_AEROBULK
        LDFLAGS += -L $(AEROBULK_DIR)/lib -laerobulk_cxx -laerobulk
        LDFLAGS += -lgfortran
endif

ifneq (,$(strip $(filter DEBUG Debug debug PROFILE Profile profile,$(NEXTSIM_BUILD_TYPE))))
#ifeq ($(NEXTSIM_BUILD_TYPE),$(filter $(NEXTSIM_BUILD_TYPE),Debug debug))
    LDFLAGS += -Wl,-rpath,/opt/local/lib
ifneq (,$(strip $(filter PROFILE Profile profile,$(NEXTSIM_BUILD_TYPE))))
    LDFLAGS += -L /opt/local/lib -lprofiler
endif
endif

LDFLAGS += -L $(NEXTSIMDIR)/lib -lnextsim

OBJECTDIR=$(NEXTSIMDIR)/objs/
DEPENDDIR=$(NEXTSIMDIR)/.deps/
BINARYDIR=bin/

# C++ files
CXXSRCDIR=.
CXXHDRDIR=.
CXXSRC=$(wildcard $(CXXSRCDIR)/*.cpp)
# We must exclude the version.hpp file from the list of header files because otherwise we get a circular dependency
CXXHDR=$(filter-out $(CXXHDRDIR)/version.hpp, $(wildcard $(CXXHDRDIR)/*.hpp))

OBJS=$(CXXSRC:%.cpp=$(OBJECTDIR)%.o)
DEPS=$(CXXSRC:%.cpp=$(DEPENDDIR)%.d)

# Rules to always execute.
.PHONY: exec clean mrproper all cleanall mrproperall

# Default action.
exec: $(PROGNAME)

# Create a header file with the git version
version.hpp: version.sh $(CXXSRC) $(CXXHDR)
    $(SHELL) -x $<

# Delete the object files.
clean:
    @echo " "
    $(RM) $(OBJS) $(DEPS)
    @echo " "

mrproper: clean
    $(RM) $(BINARYDIR)$(PROGNAME)
    @echo " "

# Rule for making the actual target
lines="=========="
Lines=$(lines)$(lines)$(lines)$(lines)$(lines)$(lines)$(lines)$(lines)
$(PROGNAME): $(OBJS) #$(CCOBJS)
    @mkdir -p $(BINARYDIR)
    @echo " "
    @echo $(Lines)$(Lines)
    @echo "Creating executable: $(BINARYDIR)$(PROGNAME)"
    @echo $(Lines)$(Lines)
    @echo " "
    $(CXX) $(CXXFLAGS) -o $(BINARYDIR)$@ $^ $(LDFLAGS)
    @echo " "
    @echo $(Lines)$(Lines)
    @echo "Created executable: $(BINARYDIR)$(PROGNAME)"
    @echo $(Lines)$(Lines)
    @echo " "

# Rules for object files from cpp files
$(OBJECTDIR)%.o: %.cpp
    @mkdir -p $(OBJECTDIR) # $(dir $@)
    @echo " "
    $(CXX) -o $@ -c $< $(CXXFLAGS)

# Make dependancy rules
$(DEPENDDIR)%.d: %.cpp
    @mkdir -p $(DEPENDDIR)
    $(CXX) -M -MG -MT $(OBJECTDIR)$*.o $(CXXFLAGS) $< > $@

# The compilation depends on this Makefile.
$(OBJS): Makefile

# Make everything
all:
    cd ..; $(MAKE) all


# Clean everything
cleanall:
    cd ..; $(MAKE) clean

# Properly clean everything
mrproperall:
    cd ..; $(MAKE) mrproper

# Properly clean & recompile
fresh:
    cd ..; $(MAKE) fresh

-include $(DEPS)

我暂时看不出问题,但是既然你似乎正在使用GNU make,你应该在执行make clean时打开-d选项以获取(大量)调试信息。 这将告诉你它使用了哪些规则以及找到了哪些过时的前提条件。 通过一些努力,从中应该可以弄清楚出现了什么问题。 - John Bollinger
2个回答

2
它的构建是因为你使用了-include $(DEPS)。请参阅包含其他makefile。如果在这些目录中找不到已包含的makefile,则会生成警告消息,但它不是立即致命的错误;继续处理包含的makefile。一旦完成读取makefile,make将尝试重新制作任何过时或不存在的makefile。只有在尝试找到重新制作makefile的方法并失败后,make才会将缺少的makefile诊断为致命错误。然后它继续说道:如果您希望使make简单地忽略不存在或无法重建的makefile,而没有错误消息,请使用-include指令而不是include,如下所示:-include filenames…。这在所有方面都像include一样,除了如果任何文件名(或任何文件名的任何先决条件)不存在或无法重建,则不会出现错误(甚至没有警告)。可以承认,这可能会被不同方式解释。我刚刚验证了它确实尝试重新制作具有显式规则需要重新制作的-include行上的文件:
all:
        @echo building $@

foo.d :
        @echo building $@
        @touch $@

-include foo.d bar.d

"最初的回答":这将给出:
 tmp> make all
 building foo.d
 building all

所以,这留下了一个有趣的情况来解决您的问题。单独为依赖文件设置规则会增加编译时间--现在您需要两次解析每个源文件--一次生成 .d 文件,一次编译。这不好。我认为将依赖和 .o 的规则组合起来对您最有利。然后,将依赖于 version.hpp 的 $(OBJS) 子集明确地依赖于它,您就可以顺利进行了。最初的回答。

感谢提供这个好的Makefile示例。然而(不幸的是),明确地将特定文件依赖于version.hpp,然后将依赖和.o的规则组合在一起并没有起作用。 - DingoTim
奇怪...但是既然@TobySpeight的方法有效,你可能只需要这样做就可以了。请注意,为编译和依赖项生成设置单独的规则将显著增加初始构建时间,并且对于非常大的构建系统并不是很好。 - HardcoreHenry

1

你应该将-include $(DEPS)行设为条件行 - 当MAKECMDGOALS仅包含“clean”目标时,从makefile中排除它:

ifneq(,$(filter-out clean distclean clobber,$(MAKECMDGOALS)))
-include $(DEPS)
endif

作为旁注,不要认为cd命令一定会成功 - 在其后使用&&命令 - 或者,如果在另一个目录中调用Make,则只需使用其-C选项。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接