一個C++專案的Makefile編寫-Tony與Alex的對話系列
一個C++專案的Makefile編寫-Tony與Alex的對話系列
:轉載時請以超連結形式標明文章原始出處和作者資訊及本宣告
http://bigwhite.blogbus.com/logs/1205156.html
Tony : Hey Alex, How are you doing?
Alex : 不怎麼樣。(顯得很消沉的樣子)
Tony : Oh , Really ? What is the matter?
Alex : 事情是這樣的。最近有一個Unix下的C++專案要求我獨自完成,以前都是跟著別人做,現在讓自己獨立完成,還真是不知道該怎麼辦,就連一個最簡單的專案的Makefile都搞不定。昨晚看了一晚上資料也沒有什麼頭緒。唉!!
Tony : 別急,我曾經有一段時間研究過一些關於Makefile的東西,也許能幫得上忙,來,我們一起來設計這個專案的Makefile。
Alex : So it is a deal。(一言為定)
Tony : 我們現在就開始吧,給我拿把椅子過來。
(Tony坐在Alex電腦的旁邊)
Tony : 把你的專案情況大概給我講講吧。
Alex : No Problem ! 這是一個“半成品”專案,也就是說我將提供一個開發框架供應用開發人員使用,一個類似MFC的東西。
Tony : 繼續。
Alex : 我現在頭腦中的專案目錄結構是這樣的:
APL (Alex's Programming Library)
-Make.properties
-Makefile(1)
-include //存放標頭檔案
-Module1_1.h
-Module1_2.h
-Module2_1.h
-Module2_2.h
-src //存放原始檔
-Makefile(2)
-module1
-Module1_1.cpp
-Module1_2.cpp
-Makefile(3)
-module2
-Module2_1.cpp
-Module2_2.cpp
-Makefile(3)
-...
-lib //存放該Project依賴的庫檔案,型如libxxx.a
-dist //存放該Project編譯連線後的庫檔案libapl.a
-examples //存放使用該“半成品”搭建的例子應用的源程式
Makefile(4)
-appdemo1
-Makefile(5)
-src //存放應用原始碼
-include
-bin //存放應用可執行程式
-appdemo2
-Makefile(5)
-src //存放應用原始碼
-include
-bin //存放應用可執行程式
-...
Tony : I got it!
Alex : 下面我們該如何做呢?
Tony : 我們來分析一下各個Makefile的作用。你來分析一下各個級別目錄下的Makefile的作用是什麼呢?
Alex : (思考了一會兒)我想應該是這樣的吧。
Makefile(3)負責將其module下的.cpp原始檔編譯為同名.o檔案,同時其phony target "clean"負責刪除該目錄下的所有.o檔案;
Makefile(2)負責呼叫src目錄下所有module的Makefile檔案。
Makefile(1)負責先呼叫src中的Makefile生成靜態庫檔案,然後呼叫examples中的Makefile構建基於該框架的應用。
至於Make.properties,定義通用的目錄資訊變數、編譯器引數變數和通用的依賴關係。
Tony : 說得很好。我們一點一點來,先從src中每個module下的Makefile著手,就如你所說在每個module下的Makefile負責將該module下的.cpp檔案編譯為同名的.o檔案。
Alex : 好的,我來寫吧,這個我還是能搞定的。看下面:
module1下的Makefile如下:
#
# Makefile for module1
#
all : Module1_1.o Module1_2.o
Module1_1.o : Module1_1.cpp
g++ -c $^ -I ../../include
Module1_2.o : Module1_2.cpp
g++ -c $^ -I ../../include
clean :
rm -f *.o
module2下的Makefile如下:
#
# Makefile for module2
#
all : Module2_1.o Module2_2.o
Module2_1.o : Module2_1.cpp
g++ -c $^ -I ../../include
Module2_2.o : Module2_2.cpp
g++ -c $^ -I ../../include
clean :
rm -f *.o
make一下,順利產生相應的.o檔案。
/*=============================================================
Note: 關於$^、$$@ -- “$@”表示目標的集合,就像一個陣列,“$@”依次取出目標,並執於命令。
$^ -- 所有的依賴目標的集合。以空格分隔。如果在依賴目標中有多個重複的,那個這個變數會去除重複的依賴目標,只保留一份。
$< -- 依賴目標中的第一個目標名字
舉例: Module1_1.o Module1_2.o : Module1_1.cpp Module1_2.cpp
則$@ -- Module1_1.o Module1_2.o
$^ -- Module1_1.cpp Module1_2.cpp
$< -- Module1_1.cpp
==============================================================*/
Tony : Well done! 不過發現什麼問題了麼?
Alex : 什麼問題?
Tony : 存在重複的東西。在重構中我們知道如果兩個子類中都定義相同的介面函式,我們會將其pull up到基類中。同樣我們可以重構我們的Makefile,把一些重複的東西拿到外層去。
Alex : (似乎略微明白了一些)我想有三處重複:a)查詢標頭檔案的路徑是重複的; b)g++這個字串可以用一個變數定義代替 c)編譯器的編譯引數可以也定義到一個變數中。我知道Make工具支援include一個檔案,我們就建立一個公用的檔案來存放一些通用的東西吧。
Tony : 沒錯,Just do it.
Alex : 就按我原先的想法,把這些公共的部分放到Make.properties中吧。
#
# Properties for demo's Makefile
#
MAKEFILE = Makefile
BASEDIR = $(HOME)/proj/demo
####################
# Directory layout #
####################
SRCDIR = $(BASEDIR)/src
INCLUDEDIR = $(BASEDIR)/include
LIBDIR = $(BASEDIRE)/lib
DISTDIR = $(BASEDIR)/dist
####################
# Compiler options #
# F_ -- FLAG #
####################
CC = g++
# Compiler search options
F_INCLUDE = -I$(INCLUDEDIR)
F_LIB = -L $(LIBDIR)
CFLAGS =
CPPFLAGS = $(CFLAGS) $(F_INCLUDE)
然後修改一下,各個module中的Makefile檔案,以module1為例,修改後如下:
#
# Makefile for module1
#
include ../../Make.properties
all : Module1_1.o Module1_2.o
Module1_1.o : Module1_1.cpp
$(CC) -c $^ $(CPPFLAGS)
Module1_2.o : Module1_2.cpp
$(CC) -c $^ $(CPPFLAGS)
clean :
rm -f *.o
Tony : 其實這兩個Makefile中還有一個隱含的重複的地方
Alex : 你是指依賴規則麼?
Tony : 嗯,這個依賴規則在src中的各個module中都會用得到的。
Alex : 沒錯,我也是這麼想的,我現在就把這個規則抽取出來,然後你來評審一下。我想利用make工具的傳統的“字尾規則”來定義通用依賴規則,我在Make.properties加入下面的變數定義:
####################
# Common depends #
####################
DEPS = .cpp.o
然後還是以module1為例,修改module1的Makefile後如下:
#
# Makefile for module1
#
include ../../Make.properties
all : Module1_1.o Module1_2.o
$(DEPS):
$(CC) -c $^ $(CPPFLAGS)
clean :
rm -f *.o
Tony : 基本滿足需求。我們可以進行上一個層次的Makefile的設計了。我們來設計Makefile(2)。Alex,你來回顧一下Makefile(2)的作用。
/*=============================================================
Note: 關於字尾規則的說明
字尾規則中所定義的字尾應該是make 所認識的,如果一個字尾是make 所認識的,那麼這個規則就是單字尾規則,而如果兩個
連在一起的字尾都被make 所認識,那就是雙字尾規則。例如:".c"和".o"都是make 所知道。因而,如果你定義了一個規則是
".c.o"那麼其就是雙字尾規則,意義就是".c"是原始檔的字尾,".o"是目標檔案的字尾, ".c.o"意為利用.c檔案構造同名.o檔案。
==============================================================*/
Alex : No Problem! 正如前面說過的Makefile(2)負責呼叫src目錄下所有module子目錄下的Makefile檔案,並負責將各個module下的.o檔案打包為libdemo.a檔案放到dist目錄中。所以存在簡單的依賴關係就是libdemo.a依賴各個module子目錄下的.o檔案,而前面的Makefile(3)已經幫我們解決了.o檔案的生成問題了,即我們只需要逐個在各module子目錄下make即可。我的Makefile(2)檔案設計如下:
#
# Makefile for src directory
#
include ../Make.properties
TARGET = libdemo.a
####################
# Subdirs define #
####################
MODULE1_PATH = module1
MODULE2_PATH = module2
SUBDIRS = $(MODULE1_PATH) $(MODULE2_PATH)
####################
# Objects define #
####################
MODULE1_OBJS = $(MODULE1_PATH)/Module1_1.o $(MODULE1_PATH)/Module1_2.o
MODULE2_OBJS = $(MODULE2_PATH)/Module2_1.o $(MODULE2_PATH)/Module2_2.o
DEMO_OBJS = $(MODULE1_OBJS) $(MODULE2_OBJS)
all : subdirs $(TARGET)
cp $(TARGET) $(DISTDIR)
subdirs:
@for i in $(SUBDIRS); do
echo "===>$$i";
(cd $$i &&$(MAKE) -f $(MAKEFILE)) || exit 1;
echo "<===$$i";
done
$(TARGET) : $(DEMO_OBJS)
ar -r $@ $^
clean:
@for i in $(SUBDIRS); do
echo "===>$$i";
(cd $$i &&$(MAKE) clean -f $(MAKEFILE)) || exit 1;
echo "<===$$i";
done
rm -f $(DISTDIR)/$(TARGET)
Tony : Alex你的進步真的是很大,分析問題的能力提高的很快,方法也不錯。這個設計的缺點在於一旦新增了一個module子目錄,這個Makefile檔案就需要改動,不過改起來倒不是很難。有機會可以再想想,使這個Makefile更加通用。
Alex : 我記住了。我們繼續麼?
Tony : 歇一回吧^_^。
/*=============================================================
Alex and Tony are having a short break.
==============================================================*/
Tony : 你的咖啡味道真不錯。
Alex : 這可是朋友從巴西帶回來的極品咖啡豆,經過我精心研磨而成的。
Tony : 想不到你在這方面還有研究。
Alex : 呵呵。
Tony : Let's go on 。有了Makefile(2),後面的工作就輕鬆多了。
Alex : 現在我的信心也很足,我來設計Makefile(1),它負責先呼叫src中的Makefile生成靜態庫檔案,然後呼叫examples中的Makefile構建基於該框架的應用。我還是按照Makefile(2)的思路走,看我的Makefile(1):
#
# Makefile for whole project
#
include Make.properties
SRC_PATH = src
EXAMPLES_PATH = examples
SUBDIRS = $(SRC_PATH) $(EXAMPLES_PATH)
all : subdirs
subdirs:
@for i in $(SUBDIRS); do
echo "===>$$i";
(cd $$i && $(MAKE) -f $(MAKEFILE)) || exit 1;
echo "<===$$i";
done
clean:
@for i in $(SUBDIRS); do
echo "===>$$i";
(cd $$i && $(MAKE) clean -f $(MAKEFILE)) || exit 1;
echo "<===$$i";
done
執行一下,由於examples目錄下的Makefile還是空的,所以沒有成功。
Tony : 有了前面的經驗,相信完成examples目錄下的兩個Makefile對你來說不成問題。
Alex : I could not agree with you any more(Alex臉上滿是笑容),我來完成它。
每個appdemoX下的Makefile(5)我設計成這樣:
#
# Makefile for appdemoX
#
include ../../Make.properties
TARGET = appdemoX
SRC = ./src/appdemoX.cpp
all :
$(CC) -o $(TARGET) $(SRC) $(CPPFLAGS) -L $(DISTDIR) -ldemo
mv $(TARGET).exe ./bin
clean :
rm -f ./src/*.o ./bin/$(TARGET).exe
而examples目錄下的Makefile(4)的樣子如下:
#
# Makefile for examples directory
#
include ../Make.properties
EXAMPLE1_PATH = appdemo1
EXAMPLE2_PATH = appdemo2
SUBDIRS = $(EXAMPLE1_PATH) $(EXAMPLE2_PATH)
all : subdirs
subdirs:
@for i in $(SUBDIRS); do
echo "===>$$i";
(cd $$i &&$(MAKE) -f $(MAKEFILE)) || exit 1;
echo "<===$$i";
done
clean:
@for i in $(SUBDIRS); do
echo "===>$$i";
(cd $$i &&$(MAKE) clean -f $(MAKEFILE)) || exit 1;
echo "<===$$i";
done
Tony : 可以,不知不覺間,我們的工作已經接近尾聲,剩下的工作就是細節了,包括編譯器引數的細化等。
Alex : 在Makefile(1)中加上install,tar等目標,使使用者得到有更多的功能。十分感謝你的指導。
Tony : 那晚上去原味齋吧,想烤鴨了^_^。
/*=============================================================
Note : Makefile常識
a) "=" vs ":="
例子:
C_OPTIONS = $(C_EXTRA_OPTION) -O2
C_EXTRA_OPTION = -g
cfoo: foo.c exam.c
gcc $(C_OPTIONS) -o $@ $^
=>gcc -g -O2 -o cfoo foo.c exam.c
C_OPTIONS := $(C_EXTRA_OPTION) -O2
C_EXTRA_OPTION = -g
cfoo: foo.c exam.c
gcc $(C_OPTIONS) -o $@ $^
=>gcc -O2 -o cfoo foo.c exam.c
大家發現不同了,Why? 使用“=”賦值的變數在使用時才被展開,並且每使用一次就會展開一次,其值每次展開的時候有可能是不同的,就如第一個C_OPTION由於在使用時展開,所以C_EXTAR_OPTION定義的位置不影響C_OPTION的值。而使用“:=”進行賦值的變數,則在賦值的時候就被展開,並且僅僅展開一次,從此以後其值將不會發生任何變化,就第二個C_OPTION由於定義時展開所以由於定義時看不到C_EXTAR_OPTION所以值為-02,而不是-g -02。
b) wildcard 函式
在 GNU Make 裡有一個叫 'wildcard' 的函式,它有一個引數,功能是展開成一列所有符合由其引數描述的檔名,檔案間以空格間隔。你可以像下面所示使用這個命令:SOURCES = $(wildcard *.cpp) <=> SOURCES = xx.cpp yy.cpp ... zz.cpp
==============================================================*/
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/24790158/viewspace-1040579/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 編寫一個 Makefile 檔案,對階段專案一的程式碼進行自動化編譯編譯
- Linux下的makefile編寫 ——陳皓《跟我一起寫Makefile》學習筆記(二)Linux筆記
- C++ hpp檔案的編寫C++
- Windows 專案的 CMakeLists 編寫Windows
- C++ 的函式分檔案編寫C++函式
- 如何編寫一個前端框架之一-專案結構(譯)前端框架
- 編寫VCS執行使用的makefile遇到的問題
- 編寫一個非常精美的Flutter Todo-List專案Flutter
- 解密一個話費慢充的灰產專案解密
- 專案啟動指令碼的編寫指令碼
- 適用於 Go 專案的 Makefile 指南Go
- 開源專案:採用 Laravel 編寫的一個聊天應用 Laravel-ChatLaravel
- [WPF]動手寫一個簡單的訊息對話方塊
- React入門系列 - 2 編寫第一個Hello world的React程式React
- React入門系列 – 2 編寫第一個Hello world的React程式React
- 一個簡單的BypassUAC編寫
- 用原生Go寫一個自己的部落格-搭建專案(一)Go
- 用C++編寫一個簡單的釋出者和訂閱者C++
- 用C++編寫一個簡單的員工工資管理系統~C++
- Python呼叫C++編寫的方法PythonC++
- python(建立一個school的簡單django專案以及相關配置、完成school專案的models模板與檢視views的編寫—index(首頁)、add(新增學生))PythonDjangoViewIndex
- 跟我一起寫Makefile
- 管理多個專案的主要挑戰與應對方法
- Linux C++ 開發2 - 編寫、編譯、執行第一個程式LinuxC++編譯
- C++寫一個簡單的JSON解析C++JSON
- 寫一個簡單的 Linux Shell (C++)LinuxC++
- 08_第一個相對完整的驅動實踐編寫
- 從 0 到 1 再到 100, 搭建、編寫、構建一個前端專案前端
- Makefile 專案構建最佳化原理與應用
- 【Java】手工編寫JavaWeb專案!JavaWeb
- Linux驅動開發筆記(一):helloworld驅動原始碼編寫、makefile編寫以及驅動編譯Linux筆記原始碼編譯
- 寫好一個專案不容易
- 本來寫的一個 API 介面專案,現在抽空改成一個後臺許可權管理專案API
- 前端專案框架搭建隨筆—input元件的編寫前端框架元件
- 前端專案框架搭建隨筆---input元件的編寫前端框架元件
- 前端專案框架搭建隨筆---Tab元件的編寫前端框架元件
- ?用 Laravel 開發的一個輕鬆的 Markdown 文件編輯專案Laravel
- 08 常用:寫入 讀取檔案格式為:alex|123
- 對專案管理的一點思考專案管理