我们被要求使用Makefile把我们项目的所有东西整合在一起,但我们的教授从来没有教过我们怎么做。

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

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

我该如何制作一个简单的Makefile来创建一个名为a3a.exe的可执行文件?


当前回答

你有两个选择。

选项1:最简单的makefile = NO makefile。

将“a3driver.cpp”重命名为“a3a.cpp”,然后在命令行中写道:

nmake a3a.exe

就是这样。如果你正在使用GNU Make,请使用" Make "或"gmake"或其他什么。

选项2:2行makefile。

a3a.exe: a3driver.obj
    link /out:a3a.exe a3driver.obj

其他回答

为什么每个人都喜欢列出源文件?一个简单的find命令可以很容易地解决这个问题。

下面是一个非常简单的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

我一直认为通过一个详细的示例更容易学习,因此下面是我对makefile的看法。对于每个section,你都有一行不缩进的行,它显示了section的名称和依赖项。依赖关系可以是其他节(将在当前节之前运行)或文件(如果更新将导致下次运行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

我之所以展示第一个示例,是因为它展示了makefiles的强大功能。如果需要编译另一个文件,只需添加另一个部分。下面是一个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。

Make文件将有一个或两个依赖规则,这取决于您是使用单个命令进行编译和链接,还是使用一个编译命令和一个链接命令。

依赖是一个规则树,看起来像这样(注意缩进必须是一个制表符):

main_target : source1 source2 etc
    command to build main_target from sources

source1 : dependents for source1
    command to build source1

目标命令后面必须有空行,命令前面不能有空行。makefile中的第一个目标是总体目标,只有当第一个目标依赖于其他目标时才构建其他目标。

你的makefile看起来是这样的。

a3a.exe : a3driver.obj 
    link /out:a3a.exe a3driver.obj

a3driver.obj : a3driver.cpp
    cc a3driver.cpp

因为这是针对Unix的,所以可执行文件没有任何扩展。

需要注意的一点是,root-config是一个提供正确编译和链接标志的实用程序;以及针对root构建应用程序的正确库。这只是与本文档的原始读者相关的一个细节。

让我成为宝贝

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

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

Make是什么?我为什么要在意?

这个叫做Make的工具是一个构建依赖管理器。也就是说,它负责了解需要以何种顺序执行哪些命令,以便从源文件、目标文件、库、头文件等集合中获取软件项目(其中一些可能最近发生了更改),并将它们转换为程序的正确的最新版本。

实际上,你也可以用Make来做其他事情,但我不打算讨论这个。

一个简单的Makefile

假设您有一个包含:tool tool的目录。cc的工具。o的支持。cc的支持。还有支持。O,它们依赖于root,并被编译成一个名为tool的程序,假设您一直在入侵源文件(这意味着现有的工具现在已经过时),并希望编译该程序。

你可以自己做这件事

Check if either support.cc or support.hh is newer than support.o, and if so run a command like g++ -g -c -pthread -I/sw/include/root support.cc Check if either support.hh or tool.cc are newer than tool.o, and if so run a command like g++ -g -c -pthread -I/sw/include/root tool.cc Check if tool.o is newer than tool, and if so run a command like 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

唷!真麻烦!要记住的东西很多,犯错误的机会也很多。(顺便说一句——这里展示的命令行细节取决于我们的软件环境。这些程序可以在我的电脑上运行。)

当然,您也可以每次都运行这三个命令。这是可行的,但它不能很好地扩展到一个实质性的软件(比如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如果任何依赖项比目标更新,就应该运行相关的命令(缩进的行)。也就是说,依赖项行描述了需要重新构建的逻辑,以适应各种文件中的更改。如果支持。Cc改变意味着支持。O必须重建,但工具。可以让阿独处。当支持。O更改工具必须重建。

与每个依赖项行关联的命令由一个选项卡(见下文)设置,应该修改目标(或者至少触摸它以更新修改时间)。

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

此时,我们的makefile只是记住需要做的工作,但是我们仍然必须完整地找出并键入每个所需的命令。其实不必如此:Make是一种功能强大的语言,具有变量、文本操作函数和一大堆内置规则,可以让我们更容易地实现这一点。

使变量

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

为Make变量赋值的语法是:VAR =某种类型的文本值 (或VAR:=一个不同的文本值,但暂时忽略它)。

你可以在规则中使用变量,比如改进版的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),它将opat的所有实例替换为文本中的npat。

利用这一点,我们可以:

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 $(OBJS) $(LDLIBS)

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

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

这样打字更容易,可读性更强。

请注意,

我们仍然显式地声明每个目标文件和最终可执行文件的依赖关系 我们必须为两个源文件显式地键入编译规则

隐式规则和模式规则

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

后缀规则(在GNU版本中被认为过时,但为了向后兼容而保留) 隐式规则 模式的规则

隐式规则是内置的,下面将讨论一些隐式规则。模式规则以类似的形式指定

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

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

内置规则

Make有一大堆内置规则,这意味着通常情况下,一个项目可以通过一个非常简单的makefile来编译。

针对C源文件的GNU make内置规则就是上面展示的那个。类似地,我们用$(CXX) -c $(CPPFLAGS) $(CFLAGS)这样的规则从c++源文件创建目标文件。

使用$(LD) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)链接单个对象文件,但这在我们的例子中不起作用,因为我们想链接多个对象文件。

内置规则使用的变量

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

要使用的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),但您也可以将目标命名为get,这是使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

请注意,

源文件不再有任何依赖行!?! 有一些奇怪的魔法与。依赖和依赖有关 如果你做make,那么ls -A你会看到一个名为.depend的文件,其中包含类似make依赖行的内容

其他阅读

GNU制作手册 递归的Make被认为是有害的,这是一种常见的编写makefile的方法,不是最优的,以及如何避免它。

了解bug和历史笔记

Make的输入语言是空格敏感的。特别是,依赖项后面的操作行必须以制表符开始。但是一系列空格看起来可能是一样的(确实有编辑器会无声地将制表符转换为空格,反之亦然),这将导致Make文件看起来是正确的,但仍然不能工作。这在早期被确定为一个错误,但(故事是这样的)它没有被修复,因为已经有10个用户了。

(这篇文章摘自我为物理研究生写的一篇维基文章。)

我用了弗雷德穆德的答案。我研究了一段时间,这似乎是一个开始的好方法。这个解决方案还有一个定义良好的添加编译器标志的方法。我又回答了一遍,因为我做了一些改变,让它在我的环境中工作,Ubuntu和g++。有时,更多的实际例子是最好的老师。

appname := myapp

CXX := g++
CXXFLAGS := -Wall -g

srcfiles := $(shell find . -maxdepth 1 -name "*.cpp")
objects  := $(patsubst %.cpp, %.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

makefile看起来非常复杂。我正在使用一个,但它生成了一个与g++库中没有链接有关的错误。这个配置解决了这个问题。