如何使用子目录中的源代码为 C 和 C++ 创建 makefile

Posted

技术标签:

【中文标题】如何使用子目录中的源代码为 C 和 C++ 创建 makefile【英文标题】:How to make a makefile for C and C++, with sources in subdirectories 【发布时间】:2016-10-20 21:25:01 【问题描述】:

我有一个项目目前在 OS X 上的 Xcode 中构建。我正在尝试构建一个 makefile 以允许它在其他 Un*x 系统上构建。我是编写 makefile 的新手,所以我一直在拼凑来自网络上各种示例的 makefile,也许不出所料,我无法使结果正常工作。我已经查看了关于 SO 的其他类似问题,并从中收集了我能从中得到的信息,但我的情况似乎有点不同,因为

    我有大量的源文件,所以明确指定所有源和对象是不切实际的,我需要使用通配符 我的一些源代码位于子目录中,并且可以在不同的子目录中使用相同的文件名,因此对象需要按其源文件放置在文件层次结构中以避免冲突 我正在构建 C 和 C++ 并在最后将它们链接在一起 我希望能够使用目录中源文件的不同子集构建两个不同的目标,名为“slim”和“eidos”。

所以请不要将此标记为重复,除非其他问题确实解决了所有这些要求;谢谢。

我还想避免使用 makefile 生成工具,部分原因是它增加了我目前不理解的另一个级别的复杂性,部分原因是因为我对处理头文件依赖项不感兴趣,所以它看起来有点矫枉过正等等(见下面的讨论),部分原因是我需要最大的可移植性(我不想依赖除了 vanilla make 之外的特定工具)。

我从make -n 得到的错误很简单,所以我的错误可能非常愚蠢:

make: *** No rule to make target `gsl/complex/*.c.o', needed by `slim'.  Stop.

但在我看来*.c.o 目标应该由我的%.c.o 规则处理,所以我很困惑。我的makefile:

SHELL = /bin/sh
CC = gcc
CXX = g++
INCLUDES = -iquote./eidos -iquote./gsl -iquote./gsl/rng -iquote./gsl/randist -iquote./gsl/sys -iquote./gsl/specfunc -iquote./gsl/complex
CCFLAGS = -O3 -v $(INCLUDES)
CXXFLAGS = -O3 -v $(INCLUDES) -std=c++11

OUTPUTDIR = ./bin/
MKDIR = mkdir -p $(OUTPUTDIR)

CSOURCES = ./gsl/*/*.c
SLIM_CXXSOURCES = ./core/*.cpp ./eidos/*.cpp
EIDOS_CXXSOURCES = ./eidostool/*.cpp ./eidos/*.cpp

COBJECTS = $(patsubst %.c, %.c.o, $(CSOURCES))
SLIM_CXXOBJECTS = $(patsubst %.cpp, %.cpp.o, $(SLIM_CXXSOURCES))
EIDOS_CXXOBJECTS = $(patsubst %.cpp, %.cpp.o, $(EIDOS_CXXSOURCES))

all: slim eidos FORCE

slim: $(COBJECTS) $(SLIM_CXXOBJECTS) FORCE
    $(MKDIR)
    $(CXX) $(COBJECTS) $(SLIM_CXXOBJECTS) -o ./bin/slim

eidos: $(COBJECTS) $(EIDOS_CXXOBJECTS) FORCE
    $(MKDIR)
    $(CXX) $(COBJECTS) $(EIDOS_CXXOBJECTS) -o ./bin/eidos

%.cpp.o : %.cpp
    $(CXX) -c $(CXXFLAGS) -o $@ $<

%.c.o : %.c
    $(CC) -c $(CCFLAGS) -o $@ $<

clean: FORCE
    $(RM) -rf $(OUTPUTDIR)
    $(RM) ./*.o

# Would use .PHONY, but we don't want to depend on GNU make.
# See http://www.gnu.org/software/make/manual/make.html#Phony-Targets
# and http://www.gnu.org/software/make/manual/make.html#Force-Targets
FORCE:

我并不是要建立每个文件头的依赖关系或任何类似的智能。相反,我只是尝试在每次构建***目标之一(slim、eidos、all)时使用 FORCE 强制进行完全重建。这是因为我不希望人们在开发过程中使用这个 makefile。所有开发都是在 Xcode 中的 OS X 上完成的。所以它不需要最小/高效,它只需要可靠地工作来生产最终产品。不过,我不确定我是否正确使用了 FORCE。

我还有几个附带问题。

    我从 web 上的示例 makefile 中为两个编译命令使用了 $&lt; 结尾,但我不知道它们的作用。那里发生了什么?它看起来像一个重定向,但它不是我熟悉的重定向命令,当然谷歌搜索这种符号的东西是没有用的。 patsubst 业务有点棘手,我不知道如何判断它是否有效。我尝试make -np 打印出make 的内部信息,但它似乎显示了具有原始定义的变量,而不是它们的解析定义,所以我无法确定make 的最终值是多少用于我的变量。我该如何调试呢? patsubstmake 的标准部分吗?有没有更好的方法来做我想做的事情?

谢谢。很抱歉这个繁忙的多部分问题。顺便说一句,我真正想做的就是将以前存在的 makefile 转换为单独构建源文件,生成 .o 目标文件,然后在最后链接。先前存在的 makefile 在对 g++ 的一次调用中构建了所有内容,由于过多的内存使用和构建时间,这给某些用户带来了问题。这是旧的 Makefile:

SHELL = /bin/sh
CC = g++
CFLAGS = -O3 -Wno-deprecated-register
INCLUDES = -iquote./eidos -iquote./gsl -iquote./gsl/rng -iquote./gsl/randist -iquote./gsl/sys -iquote./gsl/specfunc -iquote./gsl/complex
ALL_CFLAGS = $(CFLAGS) $(INCLUDES) -std=c++11

all: slim eidos FORCE

slim: FORCE
    mkdir -p ./bin
    $(CC) $(ALL_CFLAGS) ./core/*.cpp ./eidos/*.cpp ./gsl/*/*.c -o ./bin/slim

eidos: FORCE
    mkdir -p ./bin
    $(CC) $(ALL_CFLAGS) ./eidostool/*.cpp ./eidos/*.cpp ./gsl/*/*.c -o ./bin/eidos

clean: FORCE
    rm -f ./bin/slim
    rm -f ./bin/eidos

# Would use .PHONY, but we don't want to depend on GNU make.
# See http://www.gnu.org/software/make/manual/make.html#Phony-Targets
# and http://www.gnu.org/software/make/manual/make.html#Force-Targets
FORCE:

除了前面提到的速度慢和使用大量内存的问题之外,这很好用。

【问题讨论】:

看看this 您想要做到多便携?特别是,您准备好依赖 GNU make 了吗? GNU 的实现有很多不错的特性,包括通配符支持(但不支持您尝试使用的语法),POSIX 不需要make 提供,而且许多其他实现实际上不提供。跨度> 真的需要通配符吗?也就是说,我可以理解不想为您需要构建的每个目标文件编写单独的规则 - 而且您不需要这样做 - 但是枚举您的 makefile 中的所有源文件会是一个大问题吗?我实际上并不认为这是一个问题,但也许有一些我不欣赏的考虑因素。 关于可移植性,顺便提一下,请注意您提供的 makefile 似乎已经在尝试使用 GNU 扩展。 嗯。我想我可以依靠 GNU make 生活;它的存在非常广泛,对吧?我不知道这段代码将建立在什么平台上,因为它是世界各地的研究小组将下载并构建以在本地使用的开源软件。是的,我想我可以显式枚举所有源文件,但是其中有 100 多个,并且文件会随着时间的推移而被添加和删除,所以这将是(a)一个麻烦,和(b)一个失败点我忘了维护 makefile(因为通常 Xcode 会为我处理构建过程。)但是是的,我愿意妥协。 【参考方案1】:

make 的基本概念实际上非常简单:对于您希望能够构建的每个文件,您指定其先决条件是什么,以及如何根据这些先决条件构建目标文件。 Make 可以弄清楚如何将多个构建步骤链接在一起,以创建您从实际存在的资源中请求的最终目标,如果实际上有任何方法可以这样做的话。

尽管 GNU make 确实很强大,并且已被广泛移植,但我通常建议避免特定于该版本的 make 的扩展。仅仅因为您可以获得 GNU make 几乎适用于任何机器,这并不能减轻在给定机器上不是默认 make 时的混乱,如果确实必须这样做,它也不会避免惊愕在能够构建您的软件之前获取并安装GNU make。而且您的特定项目具有足够简单的结构(尽管有很多源文件),您不需要 GNU 扩展。

关于你的资格:

我有大量的源文件,所以明确指定所有源和对象是不切实际的,我需要使用通配符

您在 cmets 中澄清了“大量源文件”的意思是“超过 100 个”,我猜这意味着“大”在旁观者的眼中。你所描述的当然不小,但它仍然相当谦虚。我已经为包含数百个源文件的软件套件构建了基于 make 的构建系统。

我的一些源文件位于子目录中,并且可以在不同的子目录中使用相同的文件名,因此对象需要按其源文件放置在文件层次结构中以避免冲突

这根本不是问题。 Make 本身并不真正了解目录。它主要与字符串一起工作,并让它执行的 shell 命令适当地解释这些命令。包含相对甚至绝对路径的目标名称和先决条件名称没有特殊问题。

我正在构建 C 和 C++,并在最后将它们链接在一起

同样,这不是什么大问题,或者至少不是make 特有的问题。这里的问题是,如何将 C 链接到 C++ 可能取决于底层工具链(编译器和链接器)。

我希望能够使用目录中源文件的不同子集构建两个不同的目标,名为“slim”和“eidos”

完全没有问题。这很常见。

你也这么说

每次构建***目标之一(slim、eidos、all)时,我只是尝试使用 FORCE 强制进行完全重建。这是因为我不希望人们在开发过程中使用这个 makefile。所有开发都是在 Xcode 中的 OS X 上完成的。所以它不需要最小/高效,它只需要可靠地工作来生产最终产品。不过,我不确定我是否正确使用了 FORCE。

我很抱歉,但这没有任何意义。如果人们在开发期间没有使用 makefile,那么预期的用例是尚未构建任何内容,因此不需要强制。另一方面,强迫能得到什么?不多。强制的唯一原因是您的项目或制作文件的某些内容使make 无法正确评估需要重建哪些目标。无论如何,如果您提供了正确的clean 目标,那么用户可以在他们想要的任何时候轻松获得干净的构建。为您的用户投资权力。它得到了回报。

我从 web 上的示例 makefile 中为两个编译命令取了 $

Make 提供了一些自动变量;其中之一被命名为&lt;。在构建规则中,其值(通过语法 $&lt; 访问)扩展为当前规则的第一个先决条件的名称。

patsubst 业务有点棘手,我不知道如何判断它是否有效。

您不需要它,但您可以通过在 echos 其结果的构建规则之一中包含一个命令来测试它是否正常工作。

这是makefile 的模板,它可能对您有用:

CC = gcc
CXX = g++

INCLUDES = -Ieidos -Igsl -Igsl/rng -Igsl/randist -Igsl/sys -Igsl/specfunc -Igsl/complex
CFLAGS = -O3 -Wno-deprecated-register
CXXFLAGS = $(CFLAGS) -std=c++11

CSOURCES = \
    gsl/subdir/something.c \
    gsl/subdir/something_else.c \
    gsl/dir2/important.c

COMMON_CXXSOURCES = \
    eidos/file1.cpp \
    eidos/file2.cpp

SLIM_CXXSOURCES = \
    core/slim1.cpp \
    core/slim2.cpp \
    core/slim3.cpp

EIDOS_CXXSOURCES = \
    eidostool/et.cpp \
    eidostool/et42.cpp \
    eidostool/phone_home.cpp

COMMON_OBJS = $(CSOURCES:.c=.o) $(COMMON_CXXSOURCES:.cpp=.o)
SLIM_OBJS = $(SLIM_CXXSOURCES:.cpp=.o)
EIDOS_OBJS = $(EIDOS_CXXSOURCES:.cpp=.o)

all: slim eidos

# NOTE: commands in build rules must start with a tab character, which
# will not be conveyed via the web representation of what follows.

# How to link object files together to build slim, using the C++ compiler as
# the linker driver
slim: $(SLIM_OBJS) $(COMMON_OBJS)
    $(CXX) $(CXXFLAGS) -o $@ $^

# How to link object files together to build eidos, using the C++ compiler as
# the linker driver
eidos: $(EIDOS_OBJS) $(COMMON_OBJS)
    $(CXX) $(CXXFLAGS) -o $@ $^

# remove all built files
clean:
    rm -f slim eidos $(COMMON_OBJS) $(SLIM_OBJS) $(EIDOS_OBJS)

# You might not actually need anything below, because `make` has built-in
# rules for building object files from source files of various kinds, based
# on their extensions.

# How to build an object file (x.o) from a corresponding C source file (x.c)
.c.o:
    $(CC) $(INCLUDES) $(CFLAGS) -c -o $@ $<

# How to build an object file from a corresponding C++ source file (x.cpp)
.cpp.o:
    $(CXX) $(INCLUDES) $(CXXFLAGS) -c -o $@ $<

关于通配符的注意事项:我建议不要使用它们,而是明确指定需要在构建中包含哪些源。是的,这成为您必须维护的东西,但它还允许您在树中拥有对构建没有贡献的源文件。这些可能是备份副本、工作副本等。只要您在发布之前通过make 测试构建项目,应该很容易发现遗漏。

【讨论】:

Galik 的回答也很好,但我接受了这个,因为它坚持基本品牌(不是 GNU),我相信这是值得的,并且避免通配符的缺点是不大。概念的解释也很好。感谢您抽出宝贵时间提供这个出色的答案。 所以,这很好用,除了一个与 makefile 无关的奇怪问题,但我会在这里询问以防万一。使用这个 makefile 构建会给我“...的多重定义”链接错误,而使用我的原始 makefile(在我的问题结束时)构建,它在一个 g++ 调用中编译和链接所有内容,却没有。错误都在 gsl/ 目录下的文件中,这些文件取自 GNU 的 GSL。他们使用头文件中定义的内联函数玩奇怪的游戏,然后使用预处理器开关进行静态构建。任何想法为什么会破坏,只是从做出改变? @bhaller,我很清楚这种链接器错误意味着什么,但是如果不查看源代码,我不知道为什么您会在一种情况下得到它们,而在另一种情况下却没有。但是,您是否考虑过让 GSL 成为 external 依赖项,而不是将其直接合并到您的项目中?即使您想将其与您的代码一起打包,您也可以考虑将其构建为一个库,通过它自己的构建系统,然后将该库链接到您的项目。 好的。我们故意将 GSL (的一个子集)作为我们项目的一个组成部分,以避免这种外部依赖。我们软件包的用户是通常不精通技术的生物学家;我们想让构建/安装软件的过程尽可能简单。无论如何,不​​用担心,我会弄清楚链接问题。 :->【参考方案2】:

不完全确定这是否适合您,您的设置中可能会有一些不明显的惊喜。

但这应该让您了解如何构建一个Makefile,使用GNU Make 选择不同的源/对象集。

CC = gcc
CXX = g++
INCLUDES = -iquote./eidos -iquote./gsl -iquote./gsl/rng -iquote./gsl/randist -iquote./gsl/sys -iquote./gsl/specfunc -iquote./gsl/complex
CCFLAGS = -O3 
CXXFLAGS = -O3 -std=c++11
CPPFLAGS = $(INCLUDES)

OUTPUTDIR = ./bin/
MKDIR = mkdir -p $(OUTPUTDIR)

# shell find command is good for multiple level
# directory searching
CSOURCES := $(shell find gsl -name "*.c")

# wildcard selects all matching files
SLIM_SOURCES = \
    $(CSOURCES) \
    $(wildcard core/*.cpp) \
    $(wildcard eidos/*.cpp)

# filter selects one type of file for substitution
# I use it here to prevent source files that don't match the 
# pattern from being included
SLIM_OBJECTS := \
    $(patsubst %.c,%.c.o,$(filter %.c,$(SLIM_SOURCES))) \
    $(patsubst %.cpp,%.cpp.o,$(filter %.cpp,$(SLIM_SOURCES)))

EIDOS_SOURCES = \
    $(CSOURCES) \
    $(wildcard eidostool/*.cpp) \
    $(wildcard eidos/*.cpp)

EIDOS_OBJECTS := \
    $(patsubst %.c,%.c.o,$(filter %.c,$(EIDOS_SOURCES))) \
    $(patsubst %.cpp,%.cpp.o,$(filter %.cpp,$(EIDOS_SOURCES)))

all: $(OUTPUTDIR)/slim $(OUTPUTDIR)/eidos

# I often find a show target useful for complex makefiles
# to figure out if you are setting the variables correctly
show:
    @echo "SLIM_SOURCES: $(SLIM_SOURCES)"
    @echo "SLIM_OBJECTS: $(SLIM_OBJECTS)"
    @echo "EIDOS_SOURCES: $(EIDOS_SOURCES)"
    @echo "EIDOS_OBJECTS: $(EIDOS_OBJECTS)"

$(OUTPUTDIR)/slim: $(SLIM_OBJECTS)
    $(CXX) -o $@ $^ $(LDFLAGS)

$(OUTPUTDIR)/eidos: $(EIDOS_OBJECTS)
    $(CXX) -o $@ $^ $(LDFLAGS)

%.cpp.o : %.cpp
    $(CXX) -c $(CXXFLAGS) $(CPPFLAGS) -o $@ $<

%.c.o : %.c
    $(CC) -c $(CCFLAGS) $(CPPFLAGS) -o $@ $<

clean: 
    $(RM) $(OUTPUTDIR)/slim $(OUTPUTDIR)/eidos $(SLIM_OBJECTS) $(EIDOS_OBJECTS)

【讨论】:

Galik,这个 makefile 的哪些特性依赖于 GNU make? @bhaller 很久没有使用非gnu make了,但我认为所有$(pattsub...)$(filter...)类型的命令都是GNU扩展跨度>

以上是关于如何使用子目录中的源代码为 C 和 C++ 创建 makefile的主要内容,如果未能解决你的问题,请参考以下文章

我的OpenGL学习进阶之旅如何抽取着色器代码到assets目录下的GLSL文件,以及如何通过Java或者C++代码来加载着GLSL文件?

如何使用node.js中的C ++库?

开发环境Ubuntu 中使用 VSCode 开发 C/C++ ③ ( 创建工程目录 | 添加 C++ 源代码 | 代码自动提示 )

区分 C 和 C++ 中的 unix 目录和文件

如何使用非托管 C/C++ 结构和函数

c++ - 如何使用make链接c ++中不同目录中的对象而不在makefile中引用它们?