c++ - 如何避免忘记 make/CMake 中的依赖关系?
问题描述
我是 C++ 新手,正在尝试掌握诸如 make/CMake 之类的构建系统的窍门。来自 Go,似乎存在一个持续的风险,即如果你忘记做一件小事,你的二进制文件就会变得陈旧。特别是,我找不到记住在 make/CMake 中更新依赖项/先决条件的最佳实践。我希望我遗漏了一些明显的东西。
例如,假设我有一个可以编译的基本 makefile main.cpp
:
CFLAGS = -stdlib=libc++ -std=c++17
main: main.o
clang++ $(CFLAGS) main.o -o main
main.o: main.cpp
clang++ $(CFLAGS) -c main.cpp -o main.o
主.cpp:
#include <iostream>
int main() {
std::cout << "Hello, world\n";
}
到目前为止,一切都很好; make
按预期工作。但是假设我有一些其他的仅标题库,称为cow.cpp
:
#include <iostream>
namespace cow {
void moo() {
std::cout << "Moo!\n";
}
}
我决定通过`include“cow.cpp”moo()
从内部调用:main.cpp
#include <iostream>
#include "cow.cpp"
int main() {
std::cout << "Hello, world\n";
cow::moo();
}
但是,我忘记更新 in 的依赖main.o
项makefile
。make
这个错误在运行和重新运行二进制的明显测试期间没有发现./main
,因为整个cow.cpp
库直接include
在main.cpp
. 所以一切看起来都很好,并按Moo!
预期打印出来。
但是当我更改cow.cpp
为 printBark!
而不是 时Moo!
,运行make
不会做任何事情,现在我的./main
二进制文件已经过时了,并且Moo!
仍然从./main
.
我很想知道有经验的 C++ 开发人员如何使用更复杂的代码库来避免这个问题。也许如果你强迫自己将每个文件拆分为一个头文件和一个实现文件,你至少能够快速纠正所有这些错误?这似乎也不是万无一失的。因为头文件有时包含一些内联实现。
我的示例使用make
而不是CMake
,但它看起来CMake
有相同的依赖关系列表问题target_link_libraries
(尽管传递性有点帮助)。
作为一个相关问题:似乎显而易见的解决方案是构建系统只查看源文件并推断依赖关系(它可以只进入一级并依赖 CMake 来处理传递性)。有没有理由这不起作用?是否有实际执行此操作的构建系统,或者我应该自己编写?
谢谢!
解决方案
首先,您需要在Makefile
.
这可以通过函数来完成
SOURCES := $(wildcard *.cpp)
DEPENDS := $(patsubst %.cpp,%.d,$(SOURCES))
wich 将获取所有*.cpp
文件的名称并替换并附加扩展名*.d
来命名您的依赖项。
然后在你的代码中
-include $(DEPENDS)
-
Makefile
如果文件不存在,告诉不要抱怨。如果它们存在,它们将被包含并根据依赖关系正确重新编译您的源代码。
最后,可以使用以下选项自动创建依赖项:-MMD -MP
用于创建对象文件的规则。在这里您可以找到完整的解释。生成依赖项的是MMD
;MP
是为了避免一些错误。如果要在更新系统库时重新编译,请MD
使用MMD
.
在您的情况下,您可以尝试:
main.o: main.cpp
clang++ $(CFLAGS) -MMD -MP -c main.cpp -o main.o
如果您有更多文件,最好使用单一规则来创建目标文件。就像是:
%.o: %.cpp Makefile
clang++ $(CFLAGS) -MMD -MP -c $< -o $@
你也可以看看这两个很好的答案:
在您的情况下,更合适的Makefile
应该如下所示(可能有一些错误,但请告诉我):
CXX = clang++
CXXFLAGS = -stdlib=libc++ -std=c++17
WARNING := -Wall -Wextra
PROJDIR := .
SOURCEDIR := $(PROJDIR)/
SOURCES := $(wildcard $(SOURCEDIR)/*.cpp)
OBJDIR := $(PROJDIR)/
OBJECTS := $(patsubst $(SOURCEDIR)/%.cpp,$(OBJDIR)/%.o,$(SOURCES))
DEPENDS := $(patsubst $(SOURCEDIR)/%.cpp,$(OBJDIR)/%.d,$(SOURCES))
# .PHONY means these rules get executed even if
# files of those names exist.
.PHONY: all clean
all: main
clean:
$(RM) $(OBJECTS) $(DEPENDS) main
# Linking the executable from the object files
main: $(OBJECTS)
$(CXX) $(WARNING) $(CXXFLAGS) $^ -o $@
#include your dependencies
-include $(DEPENDS)
#create OBJDIR if not existin (you should not need this)
$(OBJDIR):
mkdir -p $(OBJDIR)
$(OBJDIR)/%.o: $(SOURCEDIR)/%.cpp Makefile | $(OBJDIR)
$(CXX) $(WARNING) $(CXXFLAGS) -MMD -MP -c $< -o $@
编辑以回答评论
作为另一个问题,将 DEPENDS 定义重写为 just 有什么问题DEPENDS := $(wildcard $(OBJDIR)/*.d)
吗?
好问题,我花了一段时间才明白你的意思
从这里
$(wildcard pattern…)
此字符串在生成文件中的任何位置使用,由与给定文件名模式之一匹配的现有文件名的空格分隔列表替换。如果没有现有文件名与模式匹配,则从通配符函数的输出中省略该模式。
因此,wildcard
返回与模式匹配的文件名列表。patsubst
作用于字符串,它不关心那些字符串是什么:它被用作创建依赖项的文件名的一种方式,而不是文件本身。在Makefile
我发布的示例中,DEPENDS
实际上在两种情况下使用:在使用make clean
和使用include
so 进行清理时,在这种情况下它们都可以工作,因为您没有DEPENDS
在任何规则中使用。存在一些差异(我尝试运行,您也应该确认)。DEPENDS := $(patsubst $(SOURCEDIR)/%.cpp,$(OBJDIR)/%.d,$(SOURCES))
如果您运行没有对应文件的make clean
依赖项,则不会被删除,而它们会随着您的更改而被删除。相反,您可能包含与您的文件无关的依赖项。*.d
*.cpp
*.cpp
我问了这个问题:让我们看看答案。
如果.d
文件被粗心删除但.o
文件仍然存在,那么我们就有麻烦了。在原始示例中,如果main.d
被删除然后cow.cpp
随后被更改,make
则不会意识到它需要重新编译main.o
,因此它永远不会重新创建依赖文件。有没有办法在.d
不重新编译目标文件的情况下廉价地创建文件?如果是这样,那么我们可能会/.d
在每个 make 命令上重新创建所有文件?
又是一个好问题。
是的你是对的。其实这是我的一个错误。发生这种情况是因为规则
main: main.o
$(CXX) $(WARNING) $(CFLAGS) main.o -o main
实际上应该是:
main: $(OBJECTS)
$(CXX) $(WARNING) $(CXXFLAGS) $^ -o $@
因此,只要其中一个对象更改,它就会重新链接(更新可执行文件),并且只要他们的cpp
文件更改,它们就会更改。
一个问题仍然存在:如果您删除了依赖项而不是对象,并且只更改了一个或多个头文件(而不是源文件),那么您的程序不会更新。
我还更正了答案的前一部分。
编辑 2
要创建依赖项,您还可以向您的 : 添加新规则Makefile
:
这是一个示例。
推荐阅读
- events - Cloud Foundry - 如何订阅/观看 firehose 事件?
- r - seq.int(0, to0 - from, by) 中的错误:'to' 必须是有限数
- python - 为 Python 列表中的每个机场制作首字母缩写词?
- javascript - 在 node.js 分析器结果中解释 * 和 ~
- .net-core - 如何在 MacOS 上运行 nswag 代码生成脚本
- django - Django Postgres DeserializationError:安装夹具时出现问题
- vtiger - Vtiger 7中的自定义字段
- php - 使用 PHP + cURL 自动登录
- sockets - dart中readClosed是什么意思
- c++ - 使用 getline() 读取输入文件并输出到输出文件的 C++ 问题