如何制作一个SIMPLE C ++ Makefile?

我们被要求使用一个Makefile来为我们的项目整合所有东西,但是我们的教授从来没有告诉我们如何去做。

我只有一个文件, a3driver.cpp 。 驱动程序从位置"/user/cse232/Examples/example32.sequence.cpp"导入一个类。

就是这样,其他所有内容都包含在.cpp

我将如何去做一个简单的Makefile来创建一个名为a3a.exe的可执行文件?


从我为物理学毕业生写的维基文章中复制。

由于这是用于unix的可执行文件没有扩展名。

有一点需要注意的是, root-config是一个提供正确编译和链接标志的工具; 以及针对root用户构建应用程序的正确库。 这只是与此文档的原始受众相关的详细信息。

让我宝贝

或者你永远不会忘记你第一次成功

关于make的介绍性讨论,以及如何编写一个简单的makefile

什么是Make? 我为什么要关心?

称为make的工具是一个构建依赖管理器。 也就是说,它需要了解哪些命令需要按照什么样的顺序执行,以便从源文件,目标文件,库,头文件等集合中获取软件项目 - - 其中一些最近可能已经更改---并将它们变成正确的最新版本的程序。

其实你也可以用make来做其他的事情,但我不会谈论这个。

一个简单的Makefile

假设你有一个目录,其中包含: tool tool.cc tool.o support.cc support.hhsupport.o ,它们依赖于root并且应该被编译到一个名为tool的程序中,并且假设你一直在黑客攻击对源文件(这意味着现有的tool现在已经过时)并且想要编译程序。

要自己做到这一点,你可以

1)检查support.ccsupport.hh是否比support.o更新,如果是,则运行类似命令

g++ -g -c -pthread -I/sw/include/root support.cc

2)检查support.hhtool.cc是否比tool.o更新,如果是的话运行一个类似的命令

g++ -g  -c -pthread -I/sw/include/root tool.cc

3)检查tool.o是否比tool更新,如果是的话运行一个类似的命令

g++ -g tool.o support.o -L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint 
  -lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz -Wl,-framework,CoreServices 
  -Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root -lm -ldl

唷! 真是麻烦! 有很多事情要记住,有几次犯错误的机会。 (BTW--这里展示的命令行的细节取决于我们的软件环境,这些都可以在我的电脑上运行。)

当然,你可以每次运行所有三个命令。 这是可行的,但不能很好地适应大量的软件(例如DOGS,它可以在MacBook上从头开始编译超过15分钟)。

相反,您可以编写一个名为makefile如下所示:

tool: tool.o support.o
    g++ -g -o tool tool.o support.o -L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint 
        -lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz -Wl,-framework,CoreServices 
        -Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root -lm -ldl

tool.o: tool.cc support.hh
    g++ -g  -c -pthread -I/sw/include/root tool.cc

support.o: support.hh support.cc
    g++ -g -c -pthread -I/sw/include/root support.cc

只需在命令行键入make 。 这将自动执行上述三个步骤。

这里的非缩进行的形式为“target:dependencies”,并告诉make如果任何依赖项比目标更新,则应该运行关联的命令(缩进行)。 这就是依赖行描述了需要重建以适应各种文件变化的逻辑。 如果support.cc发生更改,则意味着必须重建support.o ,但tool.o可以单独保留。 当support.o更改tool必须重建。

与每个依赖关系行关联的命令是通过选项卡(见下文)设置的,以修改目标(或至少触摸它来更新修改时间)。

变量,内置规则和其他好东西

在这一点上,我们的makefile只是简单地记住需要做的工作,但我们仍然必须弄清楚并输入完整的每一个需要的命令。 它不一定是这样的:make是一个强大的语言,包含变量,文本处理函数和一整套内置的规则,可以让我们更容易。

变量

访问make变量的语法是$(VAR)

分配给make变量的语法是: VAR = A text value of some kind (或VAR := A different text value but ignore this for the moment )。

你可以在规则中使用变量,比如我们的makefile的改进版本:

CPPFLAGS=-g -pthread -I/sw/include/root 
LDFLAGS=-g
LDLIBS=-L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint 
       -lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz 
       -Wl,-framework,CoreServices -Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root 
       -lm -ldl

tool: tool.o support.o
    g++ $(LDFLAGS) -o tool tool.o support.o $(LDLIBS) 

tool.o: tool.cc support.hh
    g++ $(CPPFLAGS) -c tool.cc

support.o: support.hh support.cc
    g++ $(CPPFLAGS) -c support.cc

这是一个更具可读性,但仍然需要大量的输入

做功能

GNU make支持从文件系统或系统上的其他命令访问信息的各种功能。 在这种情况下,我们感兴趣的是$(shell ...) ,它扩展为参数的输出, $(subst opat,npat,text)npat替换$(subst opat,npat,text)所有opat实例。

利用这个优势让我们:

CPPFLAGS=-g $(shell root-config --cflags)
LDFLAGS=-g $(shell root-config --ldflags)
LDLIBS=$(shell root-config --libs)

SRCS=tool.cc support.cc
OBJS=$(subst .cc,.o,$(SRCS))

tool: $(OBJS)
    g++ $(LDFLAGS) -o tool tool.o support.o $(LDLIBS) 

tool.o: tool.cc support.hh
    g++ $(CPPFLAGS) -c tool.cc

support.o: support.hh support.cc
    g++ $(CPPFLAGS) -c support.cc

这是更容易打字和更具可读性。

注意到

  • 我们仍然明确指出每个目标文件和最终可执行文件的依赖关系
  • 我们必须明确地为两个源文件输入编译规则
  • 隐式和模式规则

    我们通常会期望所有的c ++源文件都应该以相同的方式处理,并提供三种方式来说明这一点

  • 后缀规则(在GNU make中被认为是过时的,但为了向后兼容而保留)
  • 隐含的规则
  • 模式规则
  • 隐式规则是内置的,其中一些将在下面讨论。 模式规则以类似的形式指定

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

    这意味着通过运行显示的命令从c源文件生成目标文件,其中“自动”变量$<扩展为第一个依赖项的名称。

    内置规则

    Make有大量的内置规则,这实际上意味着一个项目可以通过一个非常简单的makefile进行编译。

    GNU make c源文件的内置规则是上面展示的规则。 同样,我们使用像$(CXX) -c $(CPPFLAGS) $(CFLAGS)这样的规则从c ++源文件创建目标文件。

    单个对象文件使用$(LD) $(LDFLAGS) no $(LOADLIBES) $(LDLIBS) ,但这在我们的情况下不起作用,因为我们要链接多个对象文件。

    内置规则使用的变量

    内置规则使用一组标准变量,允许您指定本地环境信息(如在何处查找ROOT包含文件),而无需重新编写所有规则。 我们最感兴趣的是:

  • CC - 要使用的c编译器
  • CXX - 要使用的c ++编译器
  • LD - 要使用的链接器
  • CFLAGS - c源文件的编译标志
  • CXXFLAGS - c ++源文件的编译标志
  • CPPFLAGS - c预处理器的标志(通常包括在命令行中定义的文件路径和符号),由c和c ++使用
  • LDFLAGS - 链接器标志
  • LDLIBS - 链接库
  • 一个基本的Makefile

    通过利用内置的规则,我们可以将我们的makefile简化为:

    CC=gcc
    CXX=g++
    RM=rm -f
    CPPFLAGS=-g $(shell root-config --cflags)
    LDFLAGS=-g $(shell root-config --ldflags)
    LDLIBS=$(shell root-config --libs)
    
    SRCS=tool.cc support.cc
    OBJS=$(subst .cc,.o,$(SRCS))
    
    all: tool
    
    tool: $(OBJS)
        $(CXX) $(LDFLAGS) -o tool $(OBJS) $(LDLIBS) 
    
    tool.o: tool.cc support.hh
    
    support.o: support.hh support.cc
    
    clean:
        $(RM) $(OBJS)
    
    distclean: clean
        $(RM) tool
    

    我们还添加了几个执行特殊操作的标准目标(如清理源目录)。

    请注意,如果在没有参数的情况下调用make,它将使用文件中找到的第一个目标(在本例中为all),但是您也可以命名目标以获取在此情况下使make clean删除目标文件的原因。

    我们仍然拥有硬编码的所有依赖关系。

    一些神秘的改进

    CC=gcc
    CXX=g++
    RM=rm -f
    CPPFLAGS=-g $(shell root-config --cflags)
    LDFLAGS=-g $(shell root-config --ldflags)
    LDLIBS=$(shell root-config --libs)
    
    SRCS=tool.cc support.cc
    OBJS=$(subst .cc,.o,$(SRCS))
    
    all: tool
    
    tool: $(OBJS)
        $(CXX) $(LDFLAGS) -o tool $(OBJS) $(LDLIBS) 
    
    depend: .depend
    
    .depend: $(SRCS)
        $(RM) ./.depend
        $(CXX) $(CPPFLAGS) -MM $^>>./.depend;
    
    clean:
        $(RM) $(OBJS)
    
    distclean: clean
        $(RM) *~ .depend
    
    include .depend
    

    注意到

  • 源文件不再有任何依赖线!!!
  • 有一些与.depend和depend相关的奇怪的魔法
  • 如果你确实make那么ls -A你会看到一个名为.depend的文件,其中包含看起来像是依赖行的东西
  • 其他阅读

  • GNU make手册
  • 递归制作考虑在编写比最佳化的makefile通用的方式上有害,以及如何避免它。
  • 知道错误和历史笔记

    make的输入语言对空白敏感。 特别是依赖关系之后的行为行必须以制表符开头。 但是一系列空间可能看起来是一样的(事实上有些编辑器会将制表符默认转换为空格,反之亦然),这会导致make文件看起来正确,但仍然无法正常工作。 这被确定为一个早期的错误,但(故事会)并没有修复,因为已经有10个用户。


    我一直认为用一个详细的例子可以更容易地学习,所以这里是我对makefile的看法。 对于每个部分,您都有一行没有缩进的行,它显示了依赖关系后面的部分的名称。 依赖关系可以是其他部分(将在当前部分之前运行)或文件(如果更新会导致当前部分在您下次运行make时再次运行)。

    这里有一个简单的例子(请记住,我使用了4个空格,我应该使用一个标签,Stack Overflow不会让我使用标签):

    a3driver: a3driver.o
        g++ -o a3driver a3driver.o
    
    a3driver.o: a3driver.cpp
        g++ -c a3driver.cpp
    

    当你输入make ,它会选择第一部分(a3driver)。 a3driver取决于a3driver.o,因此它将转到该部分。 a3driver.o取决于a3driver.cpp,因此它只会在a3driver.cpp自上次运行后发生更改时才运行。 假设它已经(或从未运行),它将编译a3driver.cpp到一个.o文件,然后返回到a3driver并编译最终的可执行文件。

    由于只有一个文件,它甚至可以简化为:

    a3driver: a3driver.cpp
        g++ -o a3driver a3driver.cpp
    

    我展示第一个例子的原因是它显示了makefile的强大功能。 如果你需要编译另一个文件,你可以添加另一个部分。 下面是一个secondFile.cpp(它加载一个名为secondFile.h的头文件)的例子:

    a3driver: a3driver.o secondFile.o
        g++ -o a3driver a3driver.o secondFile.o
    
    a3driver.o: a3driver.cpp
        g++ -c a3driver.cpp
    
    secondFile.o: secondFile.cpp secondFile.h
        g++ -c secondFile.cpp
    

    这样,如果你改变了secondFile.cpp或secondFile.h中的内容并重新编译,它只会重新编译secondFile.cpp(而不是a3driver.cpp)。 或者,如果您在a3driver.cpp中更改某些内容,则不会重新编译secondFile.cpp。

    如果您对此有任何疑问,请告知我们。

    包含名为“all”的部分和名为“clean”的部分也是传统的。 “all”通常会构建所有的可执行文件,“clean”将删除“构建文件”,如.o文件和可执行文件:

    all: a3driver ;
    
    clean:
        # -f so this will succeed even if the files don't exist
        rm -f a3driver a3driver.o
    

    编辑:我没有注意到你在Windows上。 我认为唯一的区别是将-o a3driver更改为-o a3driver.exe


    为什么每个人都喜欢列出源文件? 一个简单的查找命令可以很容易地处理。

    这里有一个简单的C ++ Makefile的例子。 只需将其放在包含.C文件的目录中,然后键入make ...

    appname := myapp
    
    CXX := clang++
    CXXFLAGS := -std=c++11
    
    srcfiles := $(shell find . -name "*.C")
    objects  := $(patsubst %.C, %.o, $(srcfiles))
    
    all: $(appname)
    
    $(appname): $(objects)
        $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $(appname) $(objects) $(LDLIBS)
    
    depend: .depend
    
    .depend: $(srcfiles)
        rm -f ./.depend
        $(CXX) $(CXXFLAGS) -MM $^>>./.depend;
    
    clean:
        rm -f $(objects)
    
    dist-clean: clean
        rm -f *~ .depend
    
    include .depend
    
    链接地址: http://www.djcxy.com/p/61437.html

    上一篇: How to make a SIMPLE C++ Makefile?

    下一篇: What is the purpose of .PHONY in a makefile?