[英]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.hh
和support.o
依賴於root
並且應該被編譯成一個名為tool
的程序,並假設您一直在 hacking在源文件上(這意味着現有tool
現在已經過時)並且想要編譯程序。
要自己做,你可以
檢查support.cc
或support.hh
是否比support.o
新,如果是,請運行類似的命令
g++ -g -c -pthread -I/sw/include/root support.cc
檢查support.hh
或tool.cc
是否比tool.o
新,如果是,請運行類似的命令
g++ -g -c -pthread -I/sw/include/root tool.cc
檢查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
這更容易輸入並且更具可讀性。
請注意
隱式規則和模式規則
我們通常希望所有 C++ 源文件都應該以同樣的方式處理,Make 提供了三種方式來說明這一點:
隱式規則是內置的,下面將討論一些規則。 模式規則以如下形式指定
%.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
請注意
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.c
和tool
)。
注意,沒有必要聲明來源。 中間對象文件是使用隱式規則生成的。 因此,此Makefile
適用於 C 和 C++(以及 Fortran 等)。
另請注意,默認情況下,Makefile 使用$(CC)
作為鏈接器。 $(CC)
不適用於鏈接 C++ 目標文件。 我們僅因此而修改LINK.o
如果要編譯 C 代碼,則不必強制LINK.o
值。
當然,您也可以使用變量CFLAGS
添加編譯標志,並將庫添加到LDLIBS
中。 例如:
CFLAGS = -Wall
LDLIBS = -lm
附注:如果您必須使用外部庫,我建議使用 pkg-config以正確設置CFLAGS
和LDLIBS
:
CFLAGS += $(shell pkg-config --cflags libssl)
LDLIBS += $(shell pkg-config --libs libssl)
細心的讀者會注意到,如果更改了一個標頭,則此Makefile
不會正確重建。 添加這些行來解決問題:
override CPPFLAGS += -MMD
include $(wildcard *.d)
-MMD
允許構建 .d 文件,其中包含有關標頭依賴項的 Makefile 片段。 第二行只是使用它們。
當然,編寫良好的 Makefile 還應該包含clean
和distclean
規則:
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.