Makefile快速入門

編譯概述

編譯基礎:

使用GCC編譯程序時可以分為4個階段:

(1)預處理(pre-processing) -E .c---->.i -I (Include) 將源文件生成中間文件
(2)編譯(compiling) -S .i---->.s 將中間文件生成匯編
(3)匯編(Assembling) -c .s--->.o 將匯編轉(zhuǎn)換成機器代碼
(4)鏈接(Linking) .o--->可執(zhí)行文件 -L(Link) 匯集成可執(zhí)行文件

基本用法:

gcc 【options】 【filenames】

常用選項:

-c 只是編譯,不生成可執(zhí)行文件,將.c文件生成.o文件
-o outputfile 確定輸出文件的名字為outputfile
-g 產(chǎn)生gdb所需要的符號信息,用于對源代碼的調(diào)試
-O 優(yōu)化編譯鏈接,編譯鏈接時間會比較慢
-O2 比-O更好的優(yōu)化編譯鏈接,編譯鏈接時間會更加慢
-Wall 輸出所有警告信息
-w 關(guān)閉所有警告信息
-Idirname 將dirname的內(nèi)容加入到程序頭文件目錄列表中,在預處理階段使用。I意指Include
-Ldirmane 將dirname的目錄加入到程序的庫文件搜索目錄列表中,這是鏈接中使用的參數(shù)。L意指Link

makefile:

makefile文件和make工具一起使用,用于控制工程項目的編譯和鏈接,也可以用來編寫手冊頁和程序的安裝。

make工具用于解釋執(zhí)行makefile文件中的內(nèi)容。

makefile文件中通常包含源文件和目標文件的依賴關(guān)系以及從源文件生成目標文件的規(guī)則。

make工具可以根據(jù)makefile判斷哪些文件需要被重新編譯,目標文件的構(gòu)建順序等。

規(guī)則:

在講述makefile之前,先來粗略地看一看makefile的規(guī)則。

target ... : prerequisites ...
    command
    ...
    ...

target:可以是一個object file(目標文件),也可以是一個執(zhí)行文件,還可以是一個標簽(label)。對于標簽這種特性,在后續(xù)的“偽目標”章節(jié)中會有敘述。

prerequisites:生成該target所依賴的文件和/或target

command:該target要執(zhí)行的命令(任意的shell命令),一定要以tab開頭

這是一個文件的依賴關(guān)系,也就是說,target這一個或多個的目標文件依賴于prerequisites中的文件,其生成規(guī)則定義在command中。即prerequisites中如果有一個以上的文件比target文件要新的話,command所定義的命令就會被執(zhí)行。

這就是makefile的規(guī)則,也就是makefile中最核心的內(nèi)容。

靜態(tài)模式:

靜態(tài)模式可以更加容易地定義多目標的規(guī)則,可以讓我們的規(guī)則變得更加的有彈性和靈活。我們還是先來看一下語法:

<targets ...> : <target-pattern> : <prereq-patterns ...>
    <commands>
    ...

例子如下:

objects = foo.o bar.o

all: $(objects)

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

如果我們的 %.o 有幾百個,靜態(tài)模式規(guī)則可以寫完一堆規(guī)則,實在是太有效率了。

同名目標:

target1: dep1

target1: dep2
    cmd2

這種情況下,這兩個相同的target1會被合并成:

target1: dep1 dep2
    cmd2

但如果第一條規(guī)則本身也帶一個命令的話, makefile就無法合并, 給出警告,并用后面的規(guī)則替代前面的規(guī)則:

target1: dep1
    cmd1
target1: dep2
    cmd2

最后生成的是, 其實就是后一條替代了前一條,然后給出警告:

target1: dep2
    cmd2

可以參考Makefile 的重復目標-arley-ChinaUnix博客

常用函數(shù):

函數(shù)調(diào)用,很像變量的使用,也是以 $ 來標識的,其語法如下:

$(<function> <arguments>)
#或者
${<function> <arguments>}

這里, 就是函數(shù)名,make支持的函數(shù)不多。 為函數(shù)的參數(shù),參數(shù)間以逗號 , 分隔,而函數(shù)名和參數(shù)之間以“空格”分隔。函數(shù)調(diào)用以 $ 開頭,以圓括號或花括號把函數(shù)名和參數(shù)括起。感覺很像一個變量,是不是?函數(shù)中的參數(shù)可以使用變量,為了風格的統(tǒng)一,函數(shù)和變量的括號最好一樣,如使用 $(subst a,b,$(x)) 這樣的形式,而不是 $(subst a,b, ${x}) 的形式。因為統(tǒng)一會更清楚,也會減少一些不必要的麻煩。

patsubst

格式:$(patsubst pattern,replacement,text)

名稱:模式字符串替換函數(shù)

功能:查找text中的單詞(單詞以“空格”、“Tab”或“回車”“換行”分隔)是否符合模式pattern,如果匹配的話,則以replacement替換。這里,pattern可以包括通配符“%”,表示任意長度的字串。如果replacement中也包含“%”,那么,replacement中的這個“%”將是pattern中的那個“%”所代表的字串。(可以用“\”來轉(zhuǎn)義,以“%”來表示真實含義的“%”字符)

返回:函數(shù)返回被替換過后的字符串。

示例:$(patsubst %.c,%.o, a.c b.c)把字串“a.c b.c”符合模式[%.c]的單詞替換成[%.o],返回結(jié)果是“a.o b.o”

notdir

格式:$(notdir names)

名稱:將參數(shù)中的路徑去掉

功能:從文件名序列<names>中取出非目錄部分。非目錄部分是指最后一個反斜杠(“/”)之后的部分。

返回:返回文件名序列<names>的非目錄部分。

示例:$(notdir src/foo.c hacks)返回值是“foo.c hacks”。

wildcard

格式:$(wildcard PATTERN...)

功能:擴展通配符

示例:$(wildcard .c ./foo/.c) 返回值是“foo.c hacks”。搜索當前目錄及./foo/下所有以.c結(jié)尾的文件,生成一個以空格間隔的文件名列表。

foreach

格式:$(foreach <var>,<list>,<text>)

名稱:用來做循環(huán)用

功能:這個函數(shù)的意思是,把參數(shù) 中的單詞逐一取出放到參數(shù) 所指定的變量中,然后再執(zhí)行 所包含的表達式。每一次 會返回一個字符串,循環(huán)過程中, 的所返回的每個字符串會以空格分隔,最后當整個循環(huán)結(jié)束時, 所返回的每個字符串所組成的整個字符串(以空格分隔)將會是foreach函數(shù)的返回值。

示例

names := a b c d
files := $(foreach n,$(names),$(n).o)

上面的例子中, $(name) 中的單詞會被挨個取出,并存到變量 n 中, $(n).o 每次根據(jù) $(n) 計算出一個值,這些值以空格分隔,最后作為foreach函數(shù)的返回,所以, $(files) 的值是 a.o b.o c.o d.o 。

info

格式:$(info text...)

功能:打印處text的內(nèi)容,相當于printf,常用于調(diào)試

示例:$(info "some text")打印 "some text"

filter

格式$(filter suffix…,$(SOURCES))

功能:目標串中找出符合匹配規(guī)則的

示例

sources := foo.c bar.c baz.s ugh.h 
foo: $(sources) 
cc $(filter %.c %.s,$(sources)) -o foo 

#使用“$(filter %.c %.s,$(sources))”的返回值給 cc 來編譯生成目標“foo”,函數(shù)返回
#值為“foo.c bar.c baz.s” 

filter-out

格式$(filter-out SUFFIX…,$(SOURCES))

功能:從目標串中過濾掉符合匹配規(guī)則的

示例

objects=main1.o foo.o main2.o bar.o 
mains=main1.o main2.o 
$(filter-out $(mains),$(objects)) 

#實現(xiàn)了去除變量“objects”中“mains”定義的字串(文件名)功能。它的返回值
#為“foo.o bar.o”。

自動變量:

$@ :表示目標文件

$^ :表示所有的依賴文件

$< :表示第一個依賴文件

$? :表示比目標還要新的依賴文件列表

目標變量:

為某個目標設置局部變量,這種變量被稱為“Target-specific Variable”,它可以和“全局變量”同名,因為它的作用范圍只在這條規(guī)則以及連帶規(guī)則中,所以其值也只在作用范圍內(nèi)有效。而不會影響規(guī)則鏈以外的全局變量的值。

其語法是:

<target ...> : <variable-assignment>;

<target ...> : overide <variable-assignment>

variable-assignment;可以是前面講過的各種賦值表達式,如 =:= 、 += 或是 ?= 。第二個語法是針對于make命令行帶入的變量,或是系統(tǒng)環(huán)境變量。

這個特性非常的有用,當我們設置了這樣一個變量,這個變量會作用到由這個目標所引發(fā)的所有的規(guī)則中去。如:

prog : CFLAGS = -g
prog : prog.o foo.o bar.o
    $(CC) $(CFLAGS) prog.o foo.o bar.o

prog.o : prog.c
    $(CC) $(CFLAGS) prog.c

foo.o : foo.c
    $(CC) $(CFLAGS) foo.c

bar.o : bar.c
    $(CC) $(CFLAGS) bar.c

在這個示例中,不管全局的 $(CFLAGS) 的值是什么,在prog目標,以及其所引發(fā)的所有規(guī)則中(prog.o foo.o bar.o的規(guī)則), $(CFLAGS) 的值都是 -g。

還有一個具體的示例:recipes/Makefile at master · chenshuo/recipes (github.com)

學以致用:

原始版本:

main.out:main.o tool1.o tool2.o
    gcc main.o tool1.o tool2.o -o main.out
main.o:main.c
    gcc main.c -c -o main.o
tool1.o:tool1.c
    gcc tool1.c -c -o tool1.o
tool2.o:tool2.c
    gcc tool2.c -c -o tool2.o
PHONY:clean
clean:
    rm -rf *.o *.out

加入變量:

OBJS = main.o tool1.o tool2.o
CC = gcc
CFLAGS += -c  -Wall -g
main.out:$(OBJS)
    $(CC) $(OBJS) -o main.out
main.o:main.c
    $(CC) main.c  $(CFLAGS) -o main.o
tool1.o:tool1.c
    $(CC) tool1.c $(CFLAGS) -o tool1.o
tool2.o:tool2.c
    $(CC) tool2.c $(CFLAGS) -o tool2.o
clean:
    $(RM) *.o *.out -r

自動變量:

OBJS = main.o tool1.o tool2.o
CC = gcc
CFLAGS += -c  -Wall -g

main.out:$(OBJS)
    $(CC) $(OBJS) -o $@
main.o:main.c
    $(CC) $^  $(CFLAGS) -o $@
tool1.o:tool1.c
    $(CC) $^  $(CFLAGS) -o $@
tool2.o:tool2.c
    $(CC) $^  $(CFLAGS) -o $@
clean:
    $(RM) *.o *.out -r

隱含規(guī)則:

OBJS = main.o tool1.o tool2.o
CC = gcc
CFLAGS += -c  -Wall -g
main.out:$(OBJS)
    $(CC) $(OBJS) -o $@
%.o:%.c
    $(CC) $^  $(CFLAGS) -o $@
clean:
    $(RM) *.o *.out -r
all:clean main.out
    @echo "clean first then compile then link"
    @echo $(OBJS)

OBJS := $(patsubst %.c,%.o,$(wildcard *.c))
CC = gcc
CFLAGS += -c  -Wall -g

main.out:$(OBJS);@echo "link"
    $(CC) $^ -o $@
    
%.o:%.c;@echo "complie"
    $(CC) $^ $(CFLAGS) -o $@

PHONY:clean
clean:
    $(RM) *.o *.out -r

練習題:

given:

all:ef cd
    @echo 123
cd:
    @echo 456
ef:
    @echo 789

輸出:

789
456
123

given:

all:
    @echo 123
cd:
    @echo 456
ef:
    @echo 789

輸出 :

123

given:

all:clean main.out
    @echo "clean first then compile then link"
main.out:tool1.o tool2.o main.o
    @echo "link"
    gcc main.o tool1.o tool2.o -o main.out
main.o:main.c
    @echo "link"
    gcc main.c -c -o main.o
tool1.o:tool1.c
    @echo "link"
    gcc tool1.c -c -o tool1.o
tool2.o:tool2.c
    @echo "link"
    gcc tool2.c -c -o tool2.o

PHONY:clean
clean:
    rm -rf *.o *.out

輸出:

rm -rf *.o *.out
link
gcc tool1.c -c -o tool1.o
link
gcc tool2.c -c -o tool2.o
link
gcc main.c -c -o main.o
link
gcc main.o tool1.o tool2.o -o main.out
clean first then compile then link

最佳實踐:

工程目錄如下,請使用makefile構(gòu)建工程:

├── include
│   ├── base
│   │   ├── BaseTypes.h
│   │   ├── InterfaceDef.h
│   │   ├── Keywords.h
│   │   └── stdc.h
│   ├── BaseMacro.h
│   ├── Cent.h
│   ├── common.h
│   └── Dollar.h
├── Makefile
├── README.md
├── src
│   ├── Cent.cpp
│   ├── common.cpp
│   └── Dollar.cpp
└── test
    ├── CentTest.cpp
    ├── DollarTest.cpp
    ├── FormatTest.cpp
    └── main.cpp

https://github.com/yanxicheung/usd

Q&A:

一個Makefile如何生成若干個可執(zhí)行文件?

如果你的Makefile需要一口氣生成若干個可執(zhí)行文件,但你只 想簡單地敲一個make完事,并且,所有的目標文件都寫在一個Makefile中,那么你可以使用“偽目標”這個特性:

all : prog1 prog2 prog3
.PHONY : all

prog1 : prog1.o utils.o
    cc -o prog1 prog1.o utils.o

prog2 : prog2.o
    cc -o prog2 prog2.o

prog3 : prog3.o sort.o utils.o
    cc -o prog3 prog3.o sort.o utils.o

Makefile中的第一個目標會被作為其默認目標。我們聲明了一個“all”的偽目標,其依賴于其它三個目標。

由于默認目標的特性是,總是被執(zhí)行的,但由于“all”又是一個偽目標,偽目標只是一個標簽不 會生成文件,所以不會有“all”文件產(chǎn)生。

于是,其它三個目標的規(guī)則總是會被決議。也就達到了我們一口 氣生成多個目標的目的。 .PHONY : all 聲明了“all”這個目標為“偽目標”。

(注:這里的顯式 “.PHONY : all” 不寫的話一般情況也可以正確的執(zhí)行,這樣make可通過隱式規(guī)則推導出, “all” 是一 個偽目標,執(zhí)行make不會生成“all”文件,而執(zhí)行后面的多個目標。建議:顯式寫出是一個好習慣。)

Refrence:

  1. linux make makefile 內(nèi)置變量 默認變量
  2. [makefile @,^, <,?](https://www.cnblogs.com/gamesun/p/3323155.html
  3. 跟我一起寫Makefile
  4. Makefile中.PHONY的作用
  5. Makefile 中:= ?= += =的區(qū)別
  6. Makefile的靜態(tài)模式%.o : %.c
  7. makefile內(nèi)置變量及自動變量
  8. Makefile 語法及使用筆記_丨匿名用戶丨的博客-CSDN博客_makefile判斷文件大小
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • makefile的作用:簡化編譯的過程,通過腳本來執(zhí)行編譯命令1、注釋:‘#’開頭:如: #這是一個注釋 2、執(zhí)行...
    閑人_999c閱讀 1,197評論 0 0
  • 最近工作編譯程序一直在用別人寫的Makefile,但是沒有系統(tǒng)的學習過,趁著放假學一波 0x00 Makefile...
    MachinePlay閱讀 1,482評論 0 0
  • 1、什么是 Makefile 一個企業(yè)級項目,通常會有很多源文件,有時也會按功能、類型、模塊分門別類的放在不同的目...
    zwb_jianshu閱讀 135評論 0 0
  • 書寫規(guī)則 規(guī)則包含兩個部分,一個是依賴關(guān)系,一個是生成目標的方法。在Makefile中,規(guī)則的順序是很重要的,因為...
    Stan_Z閱讀 1,706評論 0 6
  • 隱含規(guī)則 在我們使用Makefile時,有一些我們會經(jīng)常使用,而且使用頻率非常高的東西,比如,我們編譯C/C++的...
    Stan_Z閱讀 498評論 0 0

友情鏈接更多精彩內(nèi)容