【发布时间】:2019-01-12 18:54:49
【问题描述】:
我是 C++ 新手,正在尝试掌握诸如 make/CMake 之类的构建系统的窍门。从 Go 开始,如果你忘记做一件小事,你的二进制文件就会变得陈旧,这似乎是一个持续的风险。特别是,我找不到记住在 make/CMake 中更新依赖项/先决条件的最佳实践。我希望我遗漏了一些明显的东西。
例如,假设我有一个只编译 main.cpp 的基本 makefile:
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
main.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" 从main.cpp 内部调用moo():
#include <iostream>
#include "cow.cpp"
int main() {
std::cout << "Hello, world\n";
cow::moo();
}
但是,我忘记在makefile 中更新main.o 的依赖项。这个错误在运行make和重新运行二进制./main的明显测试期间没有发现,因为整个cow.cpp库直接是main.cpp中的included。所以一切看起来都很好,Moo! 按预期打印出来了。
但是当我将cow.cpp 更改为打印Bark! 而不是Moo! 时,运行make 不会执行任何操作,现在我的./main 二进制文件已过时,并且Moo! 仍在打印来自./main。
我很想知道有经验的 C++ 开发人员如何使用更复杂的代码库来避免这个问题。也许如果你强迫自己将每个文件拆分为一个头文件和一个实现文件,你至少能够快速纠正所有这些错误?这似乎也不是万无一失的。因为头文件有时包含一些内联实现。
我的示例使用make 而不是CMake,但看起来CMake 在target_link_libraries 中存在相同的依赖关系列表问题(尽管传递性有所帮助)。
作为一个相关问题:似乎显而易见的解决方案是构建系统只查看源文件并推断依赖关系(它可以只进入一层并依赖 CMake 来处理传递性)。有没有理由这不起作用?是否有实际执行此操作的构建系统,或者我应该自己编写?
谢谢!
【问题讨论】:
-
你能显示你的 CMakeLists.txt
-
不相关的;您自己控制构建,但如果您使用 IDE,IDE 很可能会看到 cow.cpp 的 cpp 扩展名并决定像提供的任何其他 cpp 文件一样编译和链接它。出于这个原因,并且因为人们希望编译和链接 cpp 文件,最好不要将 cpp 扩展名用于您打算包含的任何内容。使用 hpp、impl 或非 cpp 的东西。
-
比你想象的还要糟糕。
make天真地使用文件时间戳来确定依赖关系是否已更改,因此更改文件内容而不更改时间戳比依赖代码更新将导致 make 什么都不做,而碰撞时间戳而不更改内容将导致 make 重建。因此,如果您仍在使用基于make的构建系统,您唯一的选择是在任何构建之前执行make clean(或从先前构建中手动删除工件),即强制重建。 -
同时构建和处理任意项目的完整依赖树并非易事,可能比简单构建它需要更长的时间。
-
Make 和 CMake 是完全不同的东西(尽管 CMake 能够生成 Make 项目)。请在问题中只留下这些工具之一。由于几乎整个帖子都是关于 Make,我建议删除 CMake(从标签和问题中)。否则,您的帖子中有几个不相关的问题,但在 Stack Overflow 上,我们希望看到每个问题帖子一个问题。