簡體   English   中英

如何制作一個簡單的 C++ Makefile

[英]How to make a SIMPLE C++ Makefile

我們需要使用 Makefile 為我們的項目整合所有內容,但我們的教授從未向我們展示過如何去做。

我只有一個文件a3driver.cpp 驅動程序從位置"/user/cse232/Examples/example32.sequence.cpp"導入一個類。

而已。 其他所有內容都包含在.cpp中。

我將如何制作一個簡單的 Makefile 來創建一個名為a3a.exe的可執行文件?

由於這是針對 Unix 的,因此可執行文件沒有任何擴展名。

需要注意的一點是root-config是一個提供正確編譯和鏈接標志的實用程序; 以及用於針對 root 構建應用程序的正確庫。 這只是與本文檔的原始受眾相關的一個細節。

讓我成為寶貝

或者你永遠不會忘記你第一次被制造

一個關於make的介紹性討論,以及如何編寫一個簡單的makefile

什么是制作? 我為什么要關心?

名為Make的工具是一個構建依賴管理器。 也就是說,它負責了解需要以什么順序執行哪些命令,以便從源文件、目標文件、庫、頭文件等的集合中獲取您的軟件項目——其中一些可能已經改變最近——並將它們變成正確的最新版本的程序。

實際上,您也可以將 Make 用於其他事情,但我不打算談論這個。

一個簡單的 Makefile

假設您有一個目錄,其中包含: tool tool.cc tool.o support.cc support.hhsupport.o依賴於root並且應該被編譯成一個名為tool的程序,並假設您一直在 hacking在源文件上(這意味着現有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

呸! 多么麻煩! 有很多事情要記住,也有很多犯錯的機會。 (順便說一句——這里展示的命令行的細節取決於我們的軟件環境。這些在我的電腦上工作。)

當然,您可以每次都運行所有三個命令。 這會起作用,但它不能很好地擴展到大量軟件(比如在我的 MacBook 上從頭開始編譯需要 15 分鍾以上的 DOGS)。

相反,您可以像這樣編寫一個名為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即可。 它將自動執行上面顯示的三個步驟。

此處未縮進的行具有“目標:依賴項”的形式,並告訴 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實例替換為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 $(OBJS) $(LDLIBS)

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

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

這更容易輸入並且更具可讀性。

請注意

  1. 我們仍在明確說明每個目標文件和最終可執行文件的依賴關系
  2. 我們必須為兩個源文件顯式鍵入編譯規則

隱式規則和模式規則

我們通常希望所有 C++ 源文件都應該以同樣的方式處理,Make 提供了三種方式來說明這一點:

  1. 后綴規則(在 GNU make 中被認為已過時,但為了向后兼容而保留)
  2. 隱含規則
  3. 模式規則

隱式規則是內置的,下面將討論一些規則。 模式規則以如下形式指定

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

這意味着目標文件是通過運行顯示的命令從 C 源文件生成的,其中“自動”變量$<擴展為第一個依賴項的名稱。

內置規則

Make 有一大堆內置規則,這意味着很多時候,一個項目可以通過一個非常簡單的 makefile 來編譯,確實如此。

C 源文件的 GNU make 內置規則就是上面展示的規則。 同樣,我們使用類似$(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),但您也可以將目標命名為 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

請注意

  1. 源文件不再有任何依賴行!?!
  2. .depend 和depend 有一些奇怪的魔法
  3. 如果你確實make然后ls -A你會看到一個名為.depend的文件,其中包含看起來像 make 依賴行的東西

其他閱讀

了解錯誤和歷史記錄

Make 的輸入語言對空格敏感。 特別是,依賴項之后的操作行必須以 tab 開頭 但是一系列空格看起來是一樣的(確實有些編輯器會默默地將制表符轉換為空格,反之亦然),這會導致 Make 文件看起來正確但仍然無法工作。 這在早期被確定為一個錯誤,但是(故事是這樣的)它沒有被修復,因為已經有 10 個用戶。

(這是從我為物理研究生寫的 wiki 帖子中復制的。)

我一直認為通過詳細的示例更容易學習,所以這就是我對 makefile 的看法。 對於每個部分,您都有一個不縮進的行,它顯示該部分的名稱,后跟依賴項。 依賴項可以是其他部分(將在當前部分之前運行)或文件(如果更新將導致當前部分在您下次運行時再次運行make<\/code> )。

這是一個簡單的示例(請記住,我在應該使用制表符的地方使用了 4 個空格,堆棧溢出不會讓我使用制表符):

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

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

為什么大家都喜歡列出源文件? 一個簡單的 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

你有兩個選擇。

選項 1:最簡單的 makefile = NO MAKEFILE。<\/strong>

將“a3driver.cpp”重命名為“a3a.cpp”,然后在命令行中寫入:

nmake a3a.exe

我用了 Friedmud 的答案 我研究了一段時間,這似乎是一個很好的開始方式。 此解決方案還具有添加編譯器標志的明確定義的方法。 我再次回答,因為我進行了更改以使其在我的環境 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++ 庫中鏈接有關的錯誤。 這個配置解決了這個問題。

我建議(注意縮進是一個 TAB):

tool: tool.o file1.o file2.o
    $(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@

或者

LINK.o = $(CXX) $(LDFLAGS) $(TARGET_ARCH)
tool: tool.o file1.o file2.o

后一個建議稍微好一點,因為它重用了 GNU Make 隱式規則。 但是,為了工作,源文件必須與最終可執行文件具有相同的名稱(即: tool.ctool )。

注意,沒有必要聲明來源。 中間對象文件是使用隱式規則生成的。 因此,此Makefile適用於 C 和 C++(以及 Fortran 等)。

另請注意,默認情況下,Makefile 使用$(CC)作為鏈接器。 $(CC)不適用於鏈接 C++ 目標文件。 我們僅因此而修改LINK.o 如果要編譯 C 代碼,則不必強制LINK.o值。

當然,您也可以使用變量CFLAGS添加編譯標志,並將庫添加到LDLIBS中。 例如:

CFLAGS = -Wall
LDLIBS = -lm

附注:如果您必須使用外部庫,我建議使用 pkg-config以正確設置CFLAGSLDLIBS

CFLAGS += $(shell pkg-config --cflags libssl)
LDLIBS += $(shell pkg-config --libs libssl)

細心的讀者會注意到,如果更改了一個標頭,則此Makefile不會正確重建。 添加這些行來解決問題:

override CPPFLAGS += -MMD
include $(wildcard *.d)

-MMD允許構建 .d 文件,其中包含有關標頭依賴項的 Makefile 片段。 第二行只是使用它們。

當然,編寫良好的 Makefile 還應該包含cleandistclean規則:

clean:
    $(RM) *.o *.d

distclean: clean
    $(RM) tool

請注意, $(RM)相當於rm -f ,但最好不要直接調用rm

all規則也很受歡迎。 為了工作,它應該是你文件的第一條規則:

all: tool

您還可以添加install規則:

PREFIX = /usr/local
install:
    install -m 755 tool $(DESTDIR)$(PREFIX)/bin

DESTDIR為空。 用戶可以將其設置為在替代系統上安裝您的程序(對於交叉編譯過程是必需的)。 多個發行版的包維護者也可以更改PREFIX以便在/usr中安裝您的包。

最后一句話:不要將源文件放在子目錄中。 如果您真的想這樣做,請將此Makefile保存在根目錄中並使用完整路徑來標識您的文件(即subdir/file.o )。

總而言之,您的完整 Makefile 應如下所示:

LINK.o = $(CXX) $(LDFLAGS) $(TARGET_ARCH)
PREFIX = /usr/local
override CPPFLAGS += -MMD
include $(wildcard *.d)

all: tool
tool: tool.o file1.o file2.o
clean:
    $(RM) *.o *.d
distclean: clean
    $(RM) tool
install:
    install -m 755 tool $(DESTDIR)$(PREFIX)/bin

您的 Make 文件將有一個或兩個依賴關系規則,具體取決於您是使用單個命令編譯和鏈接,還是使用一個用於編譯的命令和一個用於鏈接的命令。

依賴是一棵規則樹,看起來像這樣(注意縮進必須是 TAB):

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM