Makefile學習筆記

1. 概述

1.1 前言

之前在Linux下寫C/C++都是直接輸命令行,雖然有使用make的經歷雷恃,但沒有自己動手寫過Makefile疆股。最近看一些開源項目代碼,突然對Makefile很感興趣倒槐,于是花了幾天時間學習和實驗旬痹,將心得整理在此,便于以后深入讨越。
學習過程中主要是參考了《跟我一起寫Makefile》和GenericMakefile两残。

1.2 準備

使用Ubuntu 14.04,make版本為3.81把跨,g++版本為4.8.2人弓。在test目錄下新建circle.h, square.h兩個頭文件,circle.cpp, square.cpp, test.cpp三個源文件着逐,每個文件內容如下:

Circle.h

#ifndef __CIRCLE__H__
#define __CIRCLE__h__
 
#define PI 3.14

class Circle {
    public:
        Circle(void);
};

#endif

Circle.cpp

#include <iostream>
#include <cstdlib>
#include "circle.h"

using namespace std;

Circle::Circle(void) {
    cout << "Circle" << endl;
}

Square.h

#ifndef __SQUARE__H__
#define __SQUARE__H__

class Square {
    public:
        Square(void);
};

#endif

Square.cpp

#include <iostream>
#include <cstdlib>
#include "square.h"

using namespace std;

Square::Square(void) {
    cout << "Square" << endl;
}

test.cpp

#include <iostream>
#include <cstdlib>
#include "circle.h"
#include "square.h"

using namespace std;

int main() {
    Circle c;
    Square s;
    cout << PI << endl;
    return 0;
}

定義了circle和square兩個簡單的類崔赌,以及一個宏PI,在test中簡單測試耸别。

1.3 簡單的Makefile

直接看一個簡單粗暴易理解的Makefile:

test: circle.o square.o test.o
    @echo "Linking .o files" 
    g++ -o test circle.o square.o test.o

circle.o: circle.cpp circle.h
    @echo "compiling circle.o"
    g++ -c circle.cpp

square.o: square.cpp square.h
    @echo "compiling square.o"
    g++ -c square.cpp

test.o: test.cpp circle.h square.h
    @echo "compiling test.o"
    g++ -c test.cpp

.PHONY: clean
clean:
    -rm *.o
    -rm test

概括地講健芭,Makefile里定義了一系列規(guī)則,每條規(guī)則由目標太雨、依賴和命令三部分組成吟榴,比如在關于circle.o的規(guī)則里,circle.o是目標囊扳,circle.h和circle.cpp是依賴吩翻,@echo "compiling circle.o"和g++ -c circle.cpp是命令。

make的核心是通過比較目標文件和依賴文件的時間戳锥咸,決定是否執(zhí)行命令狭瞎,可以說展開來就是一個if-else結構。當目標文件不是比所有依賴文件都要“新”的時候搏予,才需要執(zhí)行命令熊锭。還是以circle.o那條規(guī)則舉例,第一次運行時,circle.o不存在碗殷,于是執(zhí)行g++ -c circle.cpp創(chuàng)建circle.o精绎;之后運行時,若circle.h和circle.cpp都沒被修改锌妻,那它們都比circle.o要“舊”代乃,沒必要重新生成circle.o。

此外關于Makefile的一些零碎知識點:

  • 每條規(guī)則前面都要用tab縮進
  • 第一條規(guī)則的目標是“終極目標”仿粹,也就是直接執(zhí)行make時默認使用的規(guī)則搁吓,比如此處就是test
  • 關于@:用echo xxx會輸出“echo xxx”,用@echo xxx才會會出“xxx”
  • 關于-:刪除不存在的文件會出錯導致make終止吭历,前面加上-表示忽略可能的錯誤
  • clean并不是目標文件堕仔,而是希望make執(zhí)行清除操作;通過.PHONY把clean標記成偽目標晌区,避免了當前目錄下真的有文件clean時摩骨,由于沒有更“新”的依賴文件,導致清除操作不執(zhí)行

輸入make和make clean朗若,可以看到效果:

最簡單的Makefile

1.4 Visualize

用圖形來思考的話仿吞,Makefile里定義了一棵表示文件依賴關系的樹,目標文件相當于parent node捡偏,依賴文件相當于許多child node。要求parent node的最后修改時間晚于所有child node的最后修改時間峡迷,不滿足這個條件時就需要執(zhí)行命令银伟,重新修正這棵樹。

1.5 g++選項

g++編譯選項非常多绘搞,這里只記錄目前用到的:

  • -c 只激活預處理彤避、編譯和匯編,生成.o結尾的obj文件
  • -o 輸出文件
  • -I 后面加頭文件搜索目錄
  • -MM 生成文件關聯(lián)信息
  • -MMD 類似于-MM,但將輸出導入到同名的.d文件里

-c夯辖、-o琉预、-I都很熟悉,-MM蒿褂、-MMD有些陌生圆米,動手試一試就知道了。

使用-MM

使用-MM時輸入test.cpp啄栓,輸出編譯目標test.o的依賴文件娄帖,沒有新文件生成。

使用-MMD

使用-MMD輸入test.cpp昙楚,依賴文件信息會輸入到自動創(chuàng)建的文件test.d中近速,這里用了-c是因為單用-MMD時g++編譯后還會嘗試鏈接,所以用-c告訴g++只進行編譯。不過這里即使不用-c削葱,雖然會報錯奖亚,但test.d文件還是會正常創(chuàng)建的。

注意-MM和-MMD輸出的內容和Makefile里的“目標: 依賴”部分格式是完全相同的析砸,之后會用到這個性質昔字。

2. 變量與函數(shù)

2.1 變量

Makefile里可以定義變量,使用時用$(變量)獲得變量的值干厚,比如定義變量:

TARGET = test
OBJS = test.o circle.o square.o
    CXX = g++

那么使用變量的規(guī)則:

$(TARGET): $(OBJS)
        $(CXX) -o $(TARGET) $(OBJS)

就相當于:

test: test.o circle.o square.o
    g++ -o test test.o circle.o square.o

2.2 wildcard notdir patsubst

Makefile支持通配符, *.h和 *cpp分別表示所有的頭文件和源文件李滴,但是規(guī)則里不能這么寫,需要展開成具體形式蛮瞄。對此Makefile提供了wildcard函數(shù)所坯,wildcard返回已經存在的、使用空格分開的挂捅、匹配此模式的所有文件列表芹助,比如:

SRCS = $(wildcard *.cpp)

則SRCS的值就是“circle.cpp square.cpp test.cpp"。

類似的可以得到所有.h文件闲先,但是make第一次執(zhí)行時還沒有.o文件状土,要怎么給OBJS賦值呢?此時可以用patsubst函數(shù)伺糠,patsubst起到替換的作用蒙谓,比如:

SRCS = $(wildcard *.cpp)
OBJS = $(patsubst %.cpp, %.o, $(SRCS))

則OBJS的值就是“circle.o square.o test.o”。

最后训桶,notdir的作用就是去掉目錄信息累驮,使得文件列表里只有文件名。

2.3 隱含規(guī)則

其實到這里為止舵揭,需要時再查點資料谤专,對于日常的自娛自樂已經足夠hack出夠用的Makefile了。想把事情做得更加優(yōu)雅午绳,可以使用隱含規(guī)則置侍。

SRCS = $(wildcard *.cpp)
OBJS = $(patsubst %.cpp, %.o, $(SRCS))

TARGET = test
CXX = g++

$(TARGET): $(OBJS)
    $(CXX) -o $(TARGET) $(OBJS)

circle.o: circle.cpp circle.h

square.o: square.cpp square.h

test.o: test.cpp

.PHONY: clean
clean:
    -rm *.o
    -rm $(TARGET)

這里circle.o、square.o和test.o三條規(guī)則都只定義了目標和依賴拦焚,而沒有寫命令蜡坊,但是這個時候Makefile可以正常工作。因為make能自動推導出一些簡單的規(guī)則赎败,比如用.cpp文件生成.o文件算色。

另外需要注意,不寫命令和空命令是不同的螟够,具體來說:

SRCS = $(wildcard *.cpp)
OBJS = $(patsubst %.cpp, %.o, $(SRCS))

TARGET = test
CXX = g++

$(TARGET): $(OBJS)
    $(CXX) -o $(TARGET) $(OBJS)

circle.o: circle.cpp circle.h ;

square.o: square.cpp square.h ;

test.o: test.cpp ;

.PHONY: clean
clean:
    -rm *.o
    -rm $(TARGET)

執(zhí)行make會報錯灾梦,因為空命令相當于明確地告訴make峡钓,不希望使用隱含規(guī)則。

2.4 自動化變量

實際項目里文件之間的依賴關系非常復雜若河,手工維護每條規(guī)則的話實在無法愉快地玩耍能岩,這時候可以把部分工作交給程序,Makefile里最主要的自動化變量是:

  • $@ 規(guī)則的目標文件名
  • $^ 規(guī)則的依賴文件列表
  • $< 規(guī)則的第一個依賴文件

直接來看使用自動化變量的Makefile例子:

    test: circle.o square.o test.o
        $(CXX) -o $@ $^
    %.o: %.cpp
        $(CXX) -c $<

%是Makefile規(guī)則里使用的通配符萧福,相當于 *拉鹃,所以這里一條“%.o: %.cpp”的規(guī)則相當于“circle.o: circle.cpp”、“square.o: square.cpp”鲫忍、“test.o: test.cpp”三條規(guī)則膏燕,非常省事。

具體地說悟民,在上面的兩條規(guī)則里坝辫,$@是"test", $^是"circle.o square.o test.o",$<是具體規(guī)則對應的.cpp文件射亏,比如circle.o近忙,$<就是circle.cpp。

3. 自動依賴

3.1 問題

其實上面那個Makefile是有問題的智润,單有“%.o: %.cpp”的模式規(guī)則是不夠的:

%.o: %.cpp
    $(CXX) -c $<

顯然的及舍,當.h文件更新而.cpp文件未更新時,.o文件不會更新窟绷。

比較naive的解決方案是直接在依賴里添加頭文件:

HDR = $(wildcard *.h)
%.o: %.cpp $(HDR)
    $(CXX) -c $<

但這種方法的問題是修改一個.h,所有的.o文件都會被波及锯玛。比如只修改circle.h,運行make時與circle.h無關的square.o也會重新生成兼蜈。

3.2 多條規(guī)則匹配

在給出解決方案之前更振,我們首先岔開一下,研究一下多條規(guī)則同時匹配時饭尝,make是如何處理的,修改剛才的Makefile為:

HDRS = $(wildcard *.h)
SRCS = $(wildcard *.cpp)
OBJS = $(patsubst %.cpp, %.o, $(SRCS))

TARGET = test
CXX = g++
CXXFLAGS = 

$(TARGET): $(OBJS)
    $(CXX) -o $(TARGET) $(OBJS)

circle.o: circle.cpp circle.h
    @echo "using specific rule"
    $(CXX) -c circle.cpp

%.o: %.cpp
    @echo "using generic rule"
    $(CXX) -c $< $

.PHONY: clean
clean:
    -rm *.o
    -rm $(TARGET)

要生成circle.o,既可以使用具體規(guī)則献宫,也可以使用模式規(guī)則钥平,此時make會如何選擇呢?

具體規(guī)則與模式規(guī)則

可以看到姊途,make選擇了具體規(guī)則涉瘾。事實上,3.81以下版本的make會使用第一條匹配的規(guī)則捷兰,以上的make會優(yōu)先匹配具體規(guī)則立叛,所以現(xiàn)在這種寫法能保證circle.o總是用具體規(guī)則生成。

這個問題在Stack Overflow上也有討論:
http://stackoverflow.com/questions/11455182/when-multiple-pattern-rules-match-a-target

3.3 解決方案

這樣就能得到比較滿意的方案了:

HDRS = $(wildcard *.h)
SRCS = $(wildcard *.cpp)
OBJS = $(patsubst %.cpp, %.o, $(SRCS))
DEPS = $(patsubst %.cpp, %.d, $(SRCS))

TARGET = test
CXX = g++

$(TARGET): $(OBJS)
    $(CXX) -o $(TARGET) $(OBJS)

-include $(DEPS)

%.o: %.cpp
    $(CXX) -MMD -c $<

.PHONY: clean

clean:
    -rm *.o
    -rm *.d
    -rm *.gch
    -rm $(TARGET)

include的作用是包含文件贡茅,這里就是那些.d文件的內容秘蛇。
運行make其做,結果正常:

這里寫圖片描述

修改circle.h里定義的PI為3.1415,再次運行make:

這里寫圖片描述

比較兩張圖可以看到赁还,與circle.h無關的square.o沒有被重新創(chuàng)建妖泄。
另外,.gch文件是為了編譯器為了提高速度而設計的文件艘策,clean的時候需要一并刪除蹈胡,否則可能干擾正常編譯。

4. 通用Makefile

4.1 自己寫的Makefile

邊學邊寫朋蔫,自己做了一個通用的罚渐,多目錄情況下自動生成依賴的Makefile,還是挺有成就感的驯妄。
假設頭文件放在HDR_DIR下荷并,源文件放在SRC_DIR下,在BIN_DIR下生成可執(zhí)行文件富玷,并創(chuàng)建鏈接文件TARGET璧坟。

TARGET = main
BIN_NAME = main

HDR_DIR = ./include
SRC_DIR = ./src
OBJ_DIR = ./obj
BIN_DIR = ./bin

CXX = g++
CXXFLAGS = -g -Wall

HDRS = $(wildcard $(HDR_DIR)/*.h)
SRCS = $(wildcard $(SRC_DIR)/*.cpp)
OBJS = $(patsubst %.cpp, $(OBJ_DIR)/%.o, $(notdir $(SRCS)))
DEPS = $(patsubst %.o, %.d, $(OBJS))

.PHONY: all
all: dir $(TARGET)

$(TARGET): $(BIN_DIR)/$(BIN_NAME)
    -ln -s $(BIN_DIR)/$(BIN_NAME) $(TARGET)

$(BIN_DIR)/$(BIN_NAME): $(OBJS)
    $(CXX) $(CXXFLAGS) $^ -o $@

-include $(DEPS)

$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp
    $(CXX) $(CXXFLAGS) -I $(HDR_DIR) -c -MMD $< -o $@

.PHONY: dir
dir:
    -mkdir $(OBJ_DIR)
    -mkdir $(BIN_DIR)

.PHONY: print
print:
    @echo HDRS = $(HDRS)
    @echo SRCS = $(SRCS)
    @echo OBJS = $(OBJS)
    @echo DEPS = $(DEPS)

.PHONY: clean
clean:
    -rm -r $(OBJ_DIR)
    -rm -r $(BIN_DIR)
    -rm $(TARGET)

4.2 Github上的通用Makefile

GenericMakefile提供了功能強大的C/C++項目Makefile,只需要修改很少一部分信息就可以用在各種項目里赎懦,非常值得閱讀雀鹃。

GenericMakefile:https://github.com/mbcrawfo/GenericMakefile

5. 學習總結

這里順便記錄一下自己的學習過程:

  • 感性認識Makefile
    • Makefile解決了什么問題?——編譯自動化
    • Makefile里有什么励两?——規(guī)則和變量
  • 粗略了解make工作原理黎茎,寫最簡單的Makefile
    • 怎樣定義最簡單的規(guī)則?——目標当悔、依賴傅瞻、命令
    • 怎樣使用變量?——直接定義盲憎,使用時加$取值
    • 何時執(zhí)行命令嗅骄?——比較目標和依賴的時間戳
    • 借助make完成特定操作?——定義偽目標
  • 使用高級特性
    • 獲取文件列表饼疙?——wildcard與patsubst
    • 處理目錄信息溺森?——notdir
    • 怎樣少寫些規(guī)則?——隱含規(guī)則與模式規(guī)則
    • 使用自動化變量窑眯?——$@屏积、$^、$<
  • 實現(xiàn)自動依賴
    • 為什么需要自動依賴磅甩?——避免手工維護代碼依賴關系
    • 怎樣生成自動依賴炊林?——g++生成依賴文件,include引入Makefile
  • 看各種項目的Makefile卷要,重點閱讀GenericMakefile
    • 頭文件與源文件在不同目錄下渣聚?——用g++的-I參數(shù)增加頭文件搜索目錄
    • 處理多平臺等復雜情形独榴?——使用ifeq-else-endif結構

最后,初次使用markdown編輯器饵逐,感覺相當好用括眠。

6. 參考資料

  1. 跟我一起寫Makefile:wiki.ubuntu.org.cn/跟我一起寫Makefile
  2. GenericMakefile:https://github.com/mbcrawfo/GenericMakefile
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市倍权,隨后出現(xiàn)的幾起案子掷豺,更是在濱河造成了極大的恐慌,老刑警劉巖薄声,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件当船,死亡現(xiàn)場離奇詭異,居然都是意外死亡默辨,警方通過查閱死者的電腦和手機德频,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缩幸,“玉大人壹置,你說我怎么就攤上這事”硪辏” “怎么了钞护?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長爆办。 經常有香客問我难咕,道長,這世上最難降的妖魔是什么距辆? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任余佃,我火速辦了婚禮,結果婚禮上跨算,老公的妹妹穿的比我還像新娘爆土。我一直安慰自己,他們只是感情好诸蚕,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布步势。 她就那樣靜靜地躺著,像睡著了一般挫望。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上狂窑,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天媳板,我揣著相機與錄音,去河邊找鬼泉哈。 笑死蛉幸,一個胖子當著我的面吹牛破讨,可吹牛的內容都是我干的。 我是一名探鬼主播奕纫,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼提陶,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了匹层?” 一聲冷哼從身側響起隙笆,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎升筏,沒想到半個月后撑柔,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡您访,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年铅忿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片灵汪。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡檀训,死狀恐怖,靈堂內的尸體忽然破棺而出享言,到底是詐尸還是另有隱情峻凫,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布担锤,位于F島的核電站蔚晨,受9級特大地震影響,放射性物質發(fā)生泄漏肛循。R本人自食惡果不足惜铭腕,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望多糠。 院中可真熱鬧累舷,春花似錦、人聲如沸夹孔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽搭伤。三九已至只怎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間怜俐,已是汗流浹背身堡。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拍鲤,地道東北人贴谎。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓汞扎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親擅这。 傳聞我的和親對象是個殘疾皇子澈魄,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內容