关于在一个有很多子目录的项目上使用make的反馈

Feedback about using make on a project with many subdirectories

本文关键字:项目 子目录 make 一个      更新时间:2023-10-16

对于我的面向对象编程课程,我必须做一个最终项目(学术目的)。我想做一个项目"正确的方式"(即:makefile,模块化,DRY,易于扩展等),以便更好地理解类,makefile和c++。

我的想法是有一个"tree-source-file-structure-directory",所以在每个子文件夹中,我得到了它的头文件,测试文件和单个makefile的源文件。所以如果我想在接口上工作,我就去子文件夹接口,编辑文件,运行测试,如果一切正常,我就把根目录上的对象链接在一起。如果我想处理我的数据结构也是一样的,以此类推。一个很好的特性是,在每个子文件夹中都存在源代码和目标文件,因此根目录中的链接器将搜索已经在子文件夹中编译的目标文件并将它们链接在一起

我一直在网上搜索,我可以看到许多不同的解决方案:-递归地做make,例如:

SUBDIRS=eda
.PHONY: subdirs $(SUBDIRS)
$(SUBDIRS):
    $(MAKE) -C $@

我发现的问题是,我在"eda"文件夹上的先决条件是"古怪的"

-使用自动变量 $(@D),但我不太明白它是如何工作的-也许使用通配符功能,但我对这个选项有点困惑。

无论如何,对我来说最诱人的解决方案是第一个(使用递归make),但我发现很多评论说不建议使用递归make有趣的文章

所以我想问你们一些建议:我怎样才能完成我的目标,并把每个重要的模块放在一个单独的文件夹里?递归是最佳解吗?也许我应该潜入"汽车制造"?或者最好将所有的目标文件放到根目录下的一个新的"object"子文件夹中,然后将它们链接在一起?

顺便说一下,我的灵感使我的项目与这个树状结构嗅嗅Amarok源代码:它有一个子文件夹称为"src",当你进入那里,你可以看到很多子文件夹:均衡器,播放列表,动态,状态栏,核心,playlistgenerator, playlistmanager等。许多子文件夹都有自己的子目录……结果就是一个不可思议的音乐播放器。如果这种方法对Amarok团队有效……我也可以做类似的事情!

欢迎任何评论,反馈,建议和其他,提前感谢!


编辑# 1

Beta,我有一些隐式规则(后缀)和链接器的目标,需要一个对象在我的eda文件夹。此目标的所有其他先决条件都建立在当前文件夹上。我遇到的问题是,当我运行make来构建该目标时,它将"eda"文件夹上的先决条件的名称作为使用隐式规则构建的目标。这是我的项目中makefile递归的棘手/不干净的部分:我想我必须为make必须在子文件夹中搜索的每个目标文件创建一个特殊的隐式规则。

这就是为什么我想要一些反馈:有没有更好的选择?或者在我的项目中使用make递归的优势压倒了其他选择?

无论如何,如果能让你更好地理解,这是我的草稿Makefile(它是西班牙语-英语:p)

#Makefile hecho para las pruebas de los archivos dentro de esta carpeta
FLAGS=-g -DDEBUG
OUT_TI=TIndividuo
OUT_TP=TProfesor
OUT_TA=TAula
.SUFFIXES: .cpp .c .h .o
.c.o: ; cc $(FLAGS) -c $*.c
.cc.o: ; gcc $(FLAGS) -c $*.cc
.cpp.o: ; g++ $(FLAGS) -c $*.cpp
SUBDIRS=eda
.PHONY: subdirs $(SUBDIRS) 
$(OUT_TI): eda/TAula.o CandidatoHorario.o TIndividuo.o TIndividuoTest.o TGen.o
    g++ CandidatoHorario.o TIndividuo.o TIndividuoTest.o TGen.o eda/TAula.o -o $@
CandidatoHorario.o: CandidatoHorario.cpp CandidatoHorario.h
TIndividuoTest.o: TIndividuoTest.cpp TIndividuo.h
TIndividuo.o: TIndividuo.cpp TIndividuo.h
TGen.o: TGen.cpp
#eda/TAula.o: eda/TAula.cpp eda/TAula.h
#   g++ -c eda/TAula.cpp -o $@
$(SUBDIRS):
    $(MAKE) -C $@
clean:
    rm -f *.o $(OUT_TI) $(OUT_TA) eda/TAula.o

"递归使被认为有害"当然是一篇值得阅读和理解的论文。然后,你选择的工具应该真正适合你的具体项目。

对于您发起的小项目(或者您有影响力指导高层决策的地方),我建议您花一点时间确定您的偏好(项目布局、目录结构、单元测试框架等),并编写一组通用的makefile,您将在所有项目中使用这些makefile。你可以很容易地以一个通用的主makefile结束,可能还有一些更通用的包含makefile的模块化(例如,构建库,单元测试或自动依赖检测)。你还可以提供一些额外的灵活性,包括可选的配置makefile(例如指定库的顺序)。大多数DAG构建将严重依赖于项目目录的内容。例如:

include config.mk
sources := $(wildcard *.cpp)
programs := $(sources:%.cpp=%)
lib_sources := $(wildcard lib/*/*.cpp)
lib_dirs := $(sort $(patsubst %/, %, $(dir $(lib_sources:lib/%=%))))
lib_objects := $(lib_sources:lib/%.cpp=$(BUILD)/%.o)
all: $(programs:%=$(BUILD)/%)
.PRECIOUS: %/.sentinel %.d
# for dependencies on directories in build tree
%/.sentinel:
        @mkdir -p $* && touch $@
clean:
        $(RM) -r $(BUILD)
programs_aux:=$(programs)
include $(foreach program, $(programs), program.mk)
lib_dirs_aux:=$(lib_dirs)
include $(foreach lib_dir, $(lib_dirs), lib.mk)
# this should probably be in lib.mk
-include $(lib_objects:%.o=%.d)

包含的program.mk(和lib.mk)将包含一些样板代码来遍历程序列表(和库列表),并将提取出makefile的特定部分来构建程序(和库)。

为了帮助实现这样的makefile,您可以使用一些标准库,如http://gmsl.sourceforge.net.

这种方法有几个问题:*它导致制作文件需要很强的技能*它并不总是适用于大型项目*它严重依赖于"惯例而不是配置",并且需要对你将要使用的惯例有一个清晰的预先定义(在我看来这是好的,其他人可能会认为它缺乏灵活性)。人生苦短,不要在makefiles上瞎折腾

否则,我建议使用更高级的配置工具,如SCons或CMake,因为它们往往在概念上更简单,而且它们还允许其他风格的生成器。