如何避免忘记make/CMake中的依赖关系? [英] How to avoid forgetting dependencies in make/CMake?

查看:104
本文介绍了如何避免忘记make/CMake中的依赖关系?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是C ++的新手,正在尝试摆脱make/CMake之类的构建系统的困扰.来自围棋,似乎一直存在着这样的风险:如果您忘记做一点事情,您的二进制文件将变得过时.特别是,我找不到记住记住在make/CMake中更新依赖项/先决条件的最佳实践.我希望我缺少明显的东西.

I am new to C++ and am trying to get the hang of build systems like make/CMake. Coming from Go, it seems that there is a constant risk that if you forget to do a little thing, your binaries will become stale. In particular, I can't find a best practice for remembering to keep dependencies/prerequisites updated in make/CMake. I'm hoping I am missing something obvious.

例如,假设我有一个基本的makefile,可以编译main.cpp:

For example, suppose I have a basic makefile that just compiles 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

main.cpp:

#include <iostream>

int main() {
    std::cout << "Hello, world\n";
}

到目前为止,一切都很好. make可以正常工作.但是,假设我还有其他名为cow.cpp的仅标头的库:

So far so good; make works as expected. But suppose I have some other header-only library called cow.cpp:

#include <iostream>

namespace cow {
    void moo() {
        std::cout << "Moo!\n";
    }
}

然后我决定通过"cow.cpp"从main.cpp内部调用moo():

And I decide to call moo() from within main.cpp via `include "cow.cpp":

#include <iostream>
#include "cow.cpp"

int main() {
    std::cout << "Hello, world\n";
    cow::moo();
}

但是,我忘记了更新makefilemain.o的依赖项.在运行make并重新运行二进制文件./main的明显测试期间,不会发现此错误,因为整个cow.cpp库直接在main.cpp中的include d中.因此,一切似乎都很好,并且Moo!已按预期打印出来.

However, I forget to update the dependencies for main.o in makefile. This mistake is not revealed during the obvious testing period of running make and rerunning the binary ./main, because the whole cow.cpp library is directly included in main.cpp. So everything seems fine, and Moo! is printed out as expected.

但是当我将cow.cpp更改为打印Bark!而不是Moo!时,运行make不会执行任何操作,现在我的./main二进制文件已过期,并且仍然打印Moo!来自./main.

But when I change cow.cpp to print Bark! instead of Moo!, then running make doesn't do anything and now my ./main binary is out of date, and Moo! is still printed from ./main.

我很好奇,有经验的C ++开发人员如何通过更加复杂的代码库来避免此问题.也许,如果您强迫自己将每个文件拆分为一个头文件和一个实现文件,那么您至少能够迅速纠正所有此类错误?这似乎也不是防弹的.因为头文件有时包含一些内联实现.

I'm very curious to hear how experienced C++ devs avoid this problem with much more complicated codebases. Perhaps if you force yourself to split every file into a header and an implementation file, you'll at least be able to quickly correct all such errors? This doesn't seem bulletproof either; since header files sometimes contain some inline implementations.

我的示例使用make而不是CMake,但是看起来CMaketarget_link_libraries中具有相同的依赖项列表问题(尽管传递性有所帮助).

My example uses make instead of CMake, but it looks like CMake has the same dependency listing problem in target_link_libraries (though transitivity helps a bit).

作为一个相关问题:似乎显而易见的解决方案是构建系统仅查看源文件并推断依赖项(它可以进入一个级别并依靠CMake来处理传递性).有什么理由不起作用吗?是否有一个实际上可以执行此操作的构建系统,或者我应该编写自己的构建系统?

As a related question: it seems like the obvious solution is for the build system to just look at the source files and infer dependencies (it can just go one level in and rely on CMake to handle transitivity). Is there a reason this doesn't work? Is there a build system that actually does this, or should I write my own?

谢谢!

推荐答案

首先,您需要引用Makefile中的依赖项文件.

First of all you will need to reference the dependencies file in your Makefile.

这可以通过功能

SOURCES := $(wildcard *.cpp)
DEPENDS := $(patsubst %.cpp,%.d,$(SOURCES))

wich将使用所有*.cpp文件的名称,并替换并附加扩展名*.d来命名您的依赖项.

wich will take the name of all *.cpp files and substitute and append the extension *.d to name your dependency.

然后在您的代码中

-include $(DEPENDS)

-告诉Makefile不要抱怨文件不存在.如果存在它们,它们将被包括在内,并根据依赖关系正确地重新编译您的源代码.

- tells the Makefile to not complain if the files do not exist. If they exist they will be included and recompile your sources properly according to the dependencies.

最后,可以使用以下选项自动创建依赖项:-MMD -MP用于创建对象文件的规则. 此处,您可以找到完整的说明.生成依赖项的是MMDMP是为了避免某些错误.如果要在更新系统库时重新编译,请使用MD而不是MMD.

Finally the dependencies can be created automatically with the options: -MMD -MP for the rules to create the objects file. Here you can find a complete explanation. What generates the dependencies is MMD; MP is to avoid some errors. If you want to recompile when system libraries are updated use MD instead of MMD.

您可以尝试以下方法:

main.o: main.cpp
    clang++ $(CFLAGS) -MMD -MP -c main.cpp -o main.o

如果文件更多,最好有一个规则来创建目标文件.像这样:

If you have more files it is better to have a single rule to create object files. Something like:

%.o: %.cpp Makefile 
    clang++ $(CFLAGS) -MMD -MP -c $< -o $@

您也可以看看这2个绝佳答案:

You can take a look also at this 2 great answers:

  • one
  • two

在您的情况下,更合适的Makefile应该如下所示(可能会有一些错误,但请告诉我):

In your case a more suitable Makefile should look like the following (there might be some errors but let me know):

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定义改写为DEPENDS := $(wildcard $(OBJDIR)/*.d)是否有问题?

EDIT to answer comments As another question, is there any problem with rewriting the DEPENDS definition as just DEPENDS := $(wildcard $(OBJDIR)/*.d)?

很好的问题,我花了一段时间了解您的观点

Nice question, it took me a while to see your point

此处

$(wildcard pattern…)该字符串用于makefile中的任何位置, 替换为以空格分隔的现有文件名称列表, 匹配给定的文件名模式之一.如果没有现有的文件名 匹配某个模式,则该模式会从输出中省略 通配符功能.

$(wildcard pattern…) This string, used anywhere in a makefile, is replaced by a space-separated list of names of existing files that match one of the given file name patterns. If no existing file name matches a pattern, then that pattern is omitted from the output of the wildcard function.

因此,wildcard返回与模式匹配的文件名列表. patsubst作用于字符串,它并不关心这些字符串是什么:它被用作创建依赖项文件名的方法,而不是文件本身.在我发布的Makefile示例中,实际上在两种情况下使用了DEPENDS:用make cleaninclude进行清洁时,在这种情况下它们都可以工作,因为您在任何规则中都没有使用DEPENDS.有一些差异(我尝试运行,您也应该确认).使用DEPENDS := $(patsubst $(SOURCEDIR)/%.cpp,$(OBJDIR)/%.d,$(SOURCES)),如果您运行make clean依赖项*.d,这些依赖项没有对应的*.cpp文件,则它们将随您的更改而被删除.相反,您可能会包含与您的*.cpp文件无关的依赖项.

So wildcard return a list of the file names matching the pattern. patsubst acts on strings, it does not care about what are those strings: it is used as a way to create the file names of the dependencies, not the files themselves. In the Makefile example that I posted DEPENDS is actually use in two cases: when cleaning with make clean and with include so in this case they both work because you are not using DEPENDS in any rule. There are some differences (I tried to run and you should too to confirm). With DEPENDS := $(patsubst $(SOURCEDIR)/%.cpp,$(OBJDIR)/%.d,$(SOURCES)) if you run make clean dependencies *.d that do not have a correspondent *.cpp file will not be removed while they will with your change. On the contrary you might include dependencies not relevant to your *.cpp file.

我问了这个问题:让我们看看答案.

I asked this questions: let's see the answers.

如果从粗心中删除了.d文件,但仍然保留.o文件,则我们有麻烦了.在原始示例中,如果删除main.d然后更改了cow.cpp,则make不会意识到它需要重新编译main.o,因此它将永远不会重新创建依赖项文件.有没有一种方法可以廉价地创建.d文件而无需重新编译目标文件?如果是这样,那么我们大概可以在每个make命令上重新创建所有/.d文件吗?

If the .d files get deleted from carelessness but the .o files remain, then we are in trouble. In the original example, if main.d is deleted and then cow.cpp is subsequently changed, make won't realize it needs to recompile main.o and thus it will never recreate the dependency file. Is there a way to cheaply create the .d files without recompiling the object files? If so then we could probably recreate all the /.d files on every make command?

很好的问题.

是的,您是对的.实际上,这是我的错误.发生这种情况是由于规则

Yes, you are right. Actually it was an error of mine. This happens because of the rule

main: main.o
    $(CXX) $(WARNING) $(CFLAGS) main.o -o main 

实际上应该是:

main: $(OBJECTS)
    $(CXX) $(WARNING) $(CXXFLAGS) $^ -o $@

,以便只要其中一个对象发生更改,它就会重新链接(可执行文件会更新),并且只要它们的cpp文件发生更改,它们就会更改.

so that it got relinked (the executable is updated) whenever one of the objects change and they will change whenever one their cpp file change.

仍然存在一个问题:如果您删除依赖关系而不删除对象,并且仅更改一个或多个头文件(而不更改源文件),则程序不会更新.

One problem remains: if you delete your dependencies but not the objects, and change only one or more header files (but not the sources) then your program is not updated.

我也纠正了答案的前一部分.

I corrected also the previous part of the answer.

编辑2 要创建依赖关系,您还可以在Makefile中添加新规则: 此处是一个示例.

EDIT 2 To create the dependencies you can also add a new rule to your Makefile: here is an example.

这篇关于如何避免忘记make/CMake中的依赖关系?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆