[原文地址](https://www.jianshu.com/p/442e71755643)
## 簡介
` `makefile文件用于管理和組織代碼工程的編譯和鏈接,其不是可執行文件,其被make工具解析并完成相關動作。
` `Makefile文件里描述的是編譯的時候依賴關系,宏定義,編譯參數,鏈接生成的程序名字等等。
` `等Makefile文件寫好后,需要用make程序來執行Makefile,所以需要先安裝make程序。
```
sudo apt install make -y
```
## 基本語法
### 文件包含
` `語法:include 文件名
` `作用:將其它makefile文件包含進來,組成一個更大的makefile文件,這樣有利于makefile模塊化編程。通常我們將一些配置選項分開成一個獨立的makefile文件,這樣有利于makefile文件的管理,或將模塊代碼的依賴關系和需要編譯的文件信息獨自寫到一個 makefile文件中,最終通過include命令形成一個頂層makefile文件來完成整個工程代碼的編譯和鏈接。
### 變量定義
` `語法:變量名 := 變量值
` `在makefile中,經常先定義一個變量,然后往該變量中追加新的值(通過+=符號),比如先定義一個C_SRCS變量(該值可以為空),然后將代碼文件test1.c和test2.c添加到C_SRCS中,其代碼如下所示:
```
C_SRCS :=
C_SRCS += test1.c test2.c
```
` `在makefile中有一類特殊的變量,其名稱為 **自動變量**,自動變量的值會依據規則中的target 和 prerequisites自動計算其值,自動變量一般以開頭$為起始,下面將列出一些常見的自動變量:
```
$@ 為規則中的target名稱。
$< 為規則中第一個prerequisite名稱
```
### 內置命令
` `Makefile中內置了一些常用的命令。
* 有字符串處理函數
```
1. subst
2. patsubst
3. strip
4. findstring
5. filter
6. filter-out
7. sort
8. word
9. wordlist
10. words
11. firstword
12. lastword
```
* 文件名處理函數
```
1. dir
2. notdir
3. suffix
4. basename
5. addsuffix
6. addprefix
7. join
8. wildcard
9. realpath
10. abspath
```
* 條件處理函數`if`;
* 循環處理函數`foreach`等
**以下是一些常用的函數:**
* [ ] **wildcard 函數**:其語法為$(wildcard pattern),pattern為匹配的模式,比如$(wildcard %.c) 為查找當前路徑下面文件名以.c結尾的文件。
* [ ] foreach 函數:其語法為$(foreach var,list,text),每循環一次var從list中按順序取值一個,然后執行一次text代碼并記錄結果,最終返回所用text代碼運行的結果。比如
dirs := C_DIR S_DIR
file := $(foreach dir,$(dirs),$(wildcard $(dir)/*))
將C_DIR和S_DIR文件夾下面的所有文件添加到file變量中。
* [ ] **dir 函數**:其語法為$(dir names…),用于獲取names中文件夾路徑,比如
$(dir src/foo.c hacks)
將獲得文件夾路徑 src/ ./
* [ ] **notdir 函數**:其語法為$(notdir names…),用于獲取names中除去路徑的信息,比如
$(notdir src/foo.c hacks)
將獲得文件信息 foo.c hacks
* [ ] **basename 函數**:其語法為$(basename names…),用于獲取names中除去后綴信息,比如
$(basename src/foo.c src-1.0/bar hacks)
將獲得信息 src/foo src-1.0/bar hacks
* [ ] **addsuffix 函數**:其語法為$(addsuffix suffix,names…),用于往names中添加后綴信息suffix,比如
$(addsuffix .c,foo bar)
將獲得文件信息 foo.c bar.c
* [ ] **addprefix 函數**:其語法為$(addprefix prefix,names…),用于往names中添加前綴信息prefix,比如
$(addprefix src/,foo bar)
將獲得信息src/foo src/bar
patsubst 函數:其語法為$(patsubst pattern,replacement,text),根據 pattern信息將text替換成replacement,比如
objects = foo.o bar.o baz.o
files = $(patsubst %.o,%.c,$( objects))
將獲得信息 foo.c bar.c baz.c
其可以簡單寫成
objects = foo.o bar.o baz.o
files = $(objects:.o=.c)
## 規則定義
` `規則是makefile中最重要的概念,其告訴make 目標文件的依賴關系,以及如何生成及更新這些目標文件。在makefile文件規則有2種,一種是顯式規則,另一種是隱式規則。
` `顯式規則用于說明 何時及如何重新生成目標,其列出了目標依賴的文件信息,并通過調用命令來創建或更新目標,其語法一般為:
```
targets : prerequisites
??????? recipe
??????? …
```
` `targets為要生成或更新的目標,prerequisites為目標依賴的關系,recipe為生成目標的命令,一個規則可以有多條recipe,比如
```
foo.o : foo.c defs.h
??????? cc -c -g foo.c
其中foo.o為target,foo.c defs.h 為prerequisites,cc -c -g foo.c為recipe。
```
` `隱式規則用于說明 何時及如何來重新生成一類目標文件根據其名稱,其描述了目標是如何依賴于名稱相似的文件(一般來說除去后綴信息,其目標與依賴文件的名稱是一樣的),并調用命令來創建或更新目標,比如
```
%.o : %.c
??????? $(CC) -c $(CFLAGS) $< -o $@
```
` `? 這個隱式規則說明了.o的目標文件依賴于同名的.c文件,其中$< 及 $@為自動變量,$<為第一個prerequisites條件,也就是 目標名稱.c,$@為目標,也就是 目標名稱.o。
` `在makefile中,我們通常要編寫3種隱式規則,第1種為代碼鏈接規則,第2種為源代碼編譯規則,第3種為匯編代碼編譯規則。
## 文件搜索路徑設置
` `? Make命令默認會在當前路徑中搜索prerequisites中的文件,比如頭文件,但我們在寫程序時,經常將頭文件和源文件隔開放在不同的文件夾下,這種該怎么處理呢?1、我們可以通過VPATH變量來解決,2、我們可以通過vpath指令來解決。
**VPATH變量**
` `VPATH變量為所有的prerequisites指定文件路徑,路徑之間可以通過 :或空格隔開,比如
```
**VPATH變量**
VPATH變量為所有的prerequisites指定文件路徑,路徑之間可以通過 :或空格隔開,比如
```
**vpath指令**
` `vpath指令的作用與變量VPATH的作用差不多,但vpath有更多的靈活性,其語法為:
vpath pattern directories
` `pattern為需要查找的文件匹配模式信息,directories為要查找的文件路徑,比如
```
vpath %.h ../headers
```
` `其代表在上一層文件夾headers中查找 .h頭文件信息。
## 實例說明
` `看個最簡單的Makefile的例子,把下面的內容保存到文件Makefile里:
~~~
# 最簡單的Makefile
hello:
@echo "hello makefile"
~~~
**注意**:
1. makefile文件中命令行的行首不能用空格,而要用Tab鍵
2. makefile文件中的字符格式有要求,必須是英文字符,不能有中文字符。
` `然后make一下,如果make的時候當前目錄有叫Makefile的文件,默認執行這個Makefile,如果需要指定其他文件名,需要用make -f。

` `上面Makefile文件里的hello表示目標,這個目標的執行動作是打印一行字符串"hello Makefile"。 我們可以有多個目標,修改Makefile,加入目標hello2:
```
# 最簡單的Makefile
hello:
@echo "hello makefile"
hello2:
@echo "hello linux"
```

` `每個目標可以有相應的依賴項,看下面的例子
```
# 最簡單的Makefile
hello: ready
@echo "hello makefile"
hello2:
@echo "hello linux"
ready:
@echo "i am ready"
```

` `目標可以執行一個空的動作,修改Makefile為下面的內容:
```
# 最簡單的Makefile
all :hello hello2
hello: ready
@echo "hello makefile"
hello2:
@echo "hello linux"
ready:
@echo "i am ready"
```

` `們可以看出Makefile實際上非常簡單,就是一個個目標與依賴結合起來的有序的解析過程。
` `了解了Makefile的執行過程,我們只要把目標的動作改為編譯和鏈接就可以了,下面我們看執行一個編譯動作的命令。
` `linux下編譯c++代碼用g++,c++代碼的文件后綴為.cpp或.cc,編譯c代碼用gcc,c代碼的文件后綴為.c。
鏈接程序也用g++。
** g++的常用參數說明**
* -c 表示編譯代碼
* -o 表示指定生成的文件名
* -g 表示編譯時候加入調試符號信息,debug時候需要這些信息
* -I (大寫i)表示設置頭文件路徑,-I./表示頭文件路徑為./
* -Wall 表示輸出所有警告信息
* -D 表示設置宏定義,-DDEBUG表示編譯debug版本,-DNDEBUG表示編譯release版本
* -O 表示編譯時候的優化級別,有4個級別-O0,-O1,-O2 -O3,-O0表示不優化,-O3表示最高優化級別
* -shared 表示生成動態庫
* -L 指定庫路徑,如-L.表示庫路徑為當前目錄
* -l (小寫L)指定庫名,如-lc表示引用libc.so
` `新裝的ubuntu可能沒有g++,可以先在bash里輸入g++ -v試一下,如果沒有安裝會提示先安裝,這時候跟著提示apt install g++就可以了。
安裝gcc也一樣。
` `看一下最簡單的編譯.cpp代碼的Makefile怎么寫
~~~c
1
2 main : main.o
3 g++ -o $@ $^
4
5 .cpp.o:
6 g++ -c -o $@ $<
~~~
` `解釋一下上面的Makefile:
* 第2行main表示一個目標名為main,依賴為main.o,.o是代碼編譯后生成的obj文件
* 第3行表示目標main的執行動作,
就是執行鏈接程序的命令。
* 第5行.cpp.o表示這是一個目標,作用是把.cpp文件編譯為.o文件
* 第6行是該目標的具體編譯命令,-c表示編譯, -o @表示指定生成文件名
* 看下面的測試:

> 小知識
Makefile里的echo和rm前面帶了@表示不要打印執行該命令時候命令本身的輸出,比如
rm -rf \*.o main在執行的時候會輸出這句命令"rm -rf \*.o main" 如果把rm改為@rm,
再make clean的時候就不會輸出"rm -rf \*.o main"命令本身了。
` `看到這里基本上再回頭看開完整Makefile就能看懂了,需要重點說的是編譯的參數,頭文件路徑和庫引用的寫法,下面我們一步一步寫上注釋
```
~~~c
# 這句是鏈接時候的命令,在g++前面加入了@echo linking $@
# 這樣在鏈接時候就會先輸出linking xxx,
# 這行直接寫g++也是沒有任何問題的
LINK = @echo linking $@ && g++
# 編譯c++代碼時候用的,一樣會顯示compiling xxx
GCC = @echo compiling $@ && g++
# 編譯c代碼時候用gcc
GC = @echo compiling $@ && gcc
# 生成靜態庫時候用ar命令
AR = @echo generating static library $@ && ar crv
# 這是編譯時候的參數設置,下面-g表示編譯時候加入調試信息,
# -DDEBUG表示編譯debug版本
# -W -Wall表示輸出所有警告
# -fPIC是生成dll時候用的
FLAGS = -g -DDEBUG -W -Wall -fPIC
GCCFLAGS =
DEFINES =
# 這里指出頭文件的目錄為./
HEADER = -I./
# 需要引用的庫文件
LIBS =
LINKFLAGS =
# 更多頭文件可以用 += 加進來
#HEADER += -I./
# 如果需要librt.so,就把-lrt加進來
#LIBS += -lrt
# 如果需要寫多線程程序,就需要引用-pthread
#LIBS += -pthread
# 這里是主要需要修改的地方,每一個.c或.cpp對應于這里的一項,
# 如main.cpp對應于main.o
# 多個.o可以用空格分開,也可以像下面這樣用"\"換行,然后寫在新一行
OBJECT := main.o \
# 下面舉個例子,這里編譯表示兩個代碼文件
# OBJECT := main.o \
# other.o
BIN_PATH = ./
TARGET = main
# 鏈接用的,可以看到后面加了$(LIBS),因為只有鏈接時候才需要引用庫文件
$(TARGET) : $(OBJECT)
$(LINK) $(FLAGS) $(LINKFLAGS) -o $@ $^ $(LIBS)
# 編譯cpp代碼用這個目標
.cpp.o:
$(GCC) -c $(HEADER) $(FLAGS) $(GCCFLAGS) -fpermissive -o $@ $<
# 編譯c代碼用這個
.c.o:
$(GC) -c $(HEADER) $(FLAGS) -fpermissive -o $@ $<
# 把生成的$(TARGET)拷貝到$(BIN_PATH)下
install: $(TARGET)
cp $(TARGET) $(BIN_PATH)
clean:
rm -rf $(TARGET) *.o *.so *.a
~~~
```
## 生成動態鏈接庫
` `linux動態鏈接庫的后綴為 .so,生成動態鏈接庫也比windows要方便的多,只要在link的時候加上-shared參數就可以了,下面我們看個例子。
先來看一下完整測試的目錄結構,最上層目錄是test_makefile_so
~~~c
test_makefile_so
├── Makefile #這個是總的Makefile,管理所有子目錄的Makefile
├── bin
│ ├── libfun.so
│ └── main
└── src
├── Makefile
├── fun
│ ├── Makefile
│ ├── fun.cpp
│ └── fun.h
└── main.cpp
3 directories, 8 files
~~~
` `main.cpp的內容
~~~c
#include <stdio.h>
#include "fun.h"
int main(int, char**){
printf("hello so\n");
int a = 1;
int b = 2;
int c = sum( a, b );
printf( "sum: %d + %d = %d\n", a, b, c );
return 0;
}
~~~
` `src/Makefile的內容
~~~c
LINK = @echo linking $@ && g++
GCC = @echo compiling $@ && g++
GC = @echo compiling $@ && gcc
AR = @echo generating static library $@ && ar crv
FLAGS = -g -DDEBUG -W -Wall -fPIC
GCCFLAGS =
DEFINES =
HEADER = -I./
LIBS =
LINKFLAGS =
HEADER += -I./fun
#LIBS += -lrt
#LIBS += -pthread
#這里表示鏈接的時候從bin目錄下找libfun.so
LIBS += -L../bin -lfun
OBJECT := main.o \
#這里加了bin的相對路徑,編完的main會install到bin目錄下
BIN_PATH = ../bin/
TARGET = main
$(TARGET) : $(OBJECT)
$(LINK) $(FLAGS) $(LINKFLAGS) -o $@ $^ $(LIBS)
.cpp.o:
$(GCC) -c $(HEADER) $(FLAGS) $(GCCFLAGS) -fpermissive -o $@ $<
.c.o:
$(GC) -c $(HEADER) $(FLAGS) -fpermissive -o $@ $<
install: $(TARGET)
cp $(TARGET) $(BIN_PATH)
clean:
rm -rf $(TARGET) *.o *.so
~~~
` `fun.h的內容
~~~c
#ifndef __fun_h__
#define __fun_h__
int sum(int a, int b);
#endif//__fun_h__
~~~
` `fun.cpp的內容
~~~c
#include "fun.h"
int sum(int a, int b){
return a+b;
}
~~~
` `fun/Makefile的內容
~~~c
LINK = @echo linking $@ && g++
GCC = @echo compiling $@ && g++
GC = @echo compiling $@ && gcc
FLAGS = -g -DDEBUG -W -Wall -fPIC
GCCFLAGS =
DEFINES =
HEADER = -I./
LIBS =
#修改的地方1: 這里加了-shared,表示生成動態庫
LINKFLAGS = -shared
#HEADER += -I./
#LIBS += -lrt
#LIBS += -pthread
OBJECT := fun.o \ #修改的地方2: 表示編譯fun.cpp
#修改的地方3: 指出了bin的相對路徑
BIN_PATH = ../../bin/
#修改的地方3,生成的文件名叫libfun.so,動態庫一般以lib為前綴
TARGET = libfun.so
$(TARGET) : $(OBJECT)
$(LINK) $(FLAGS) $(LINKFLAGS) -o $@ $^ $(LIBS)
.cpp.o:
$(GCC) -c $(HEADER) $(FLAGS) $(GCCFLAGS) -fpermissive -o $@ $<
.c.o:
$(GC) -c $(HEADER) $(FLAGS) -fpermissive -o $@ $<
install: $(TARGET)
cp $(TARGET) $(BIN_PATH)
clean:
rm -rf $(TARGET) *.o *.so
~~~
先編譯動態庫libfun.so,因為main程序要依賴libfun.so。
在fun目錄下先make install一下,會生成libfun.so,并且自動拷貝到bin目錄下
~~~c
bash$ cd fun
bash$ make install
compiling fun.o
linking libfun.so
cp libfun.so ../../bin/
~~~
切換到src目錄下,再編譯main程序
~~~c
bash$ make clean; make install
rm -rf main *.o *.so
compiling main.o
linking main
cp main ../bin/
~~~
然后切到bin目錄下,運行main程序
~~~c
bash$ cd ../bin
# bin目錄下現在有兩個文件,libfun.so和main
bash$ ls
libfun.so main
# 運行main程序
bash$ ./main
mac:bin tpf$ ./main
hello so
sum: 1 + 2 = 3 #這行是由libfun.so里的函數執行的
~~~
- 第1章 電腦操作篇
- 1.1 電腦高清壁紙下載地址
- 1.2 音樂外鏈在線獲取
- 1.3 markdown,js等表格生成神器
- 1.4 在線使用文檔,表格,演示文檔
- 1.5 開發在線工具
- 1.5.1 toolbox
- 1.5.2 菜鳥工具
- 1.6 vs code遠程調試
- 1.7 windows批處理命令
- 1.8 windows安裝cygwin運行linux指令
- 1.9 windows下某些程序運行慢
- 1.10 win下為鼠標右鍵添加新項目
- 1.11 win上自己常用的開發軟件
- 1.12 win下vscode配置
- 第2章 Electron 用前端技術開發跨平臺桌面應用
- 2.1 介紹
- 2.2 入門鏈接地址
- 2.3 cnpm使用
- 第3章 Git使用
- 3.1 介紹
- 3.2 同步GitHub的基本使用方法
- 3.3 同步Gitee的基本使用方法
- 3.4 獲取當前git分支
- 3.5 LF和CRLF換行的轉換
- 第4章 HTML,CSS,JS
- 4.1 HTML速查列表
- 第5章 python使用
- 5.1 文件操作
- 5.2 一句話建立服務器
- 第6章 我的女友叫Linux
- 6.1 使用shell寫俄羅斯方塊
- 6.2 那些有趣的shell
- 6.2.1 40個有趣的LInux命令行
- 6.2.2 命令行下的網易云搜索播放器
- 6.2.3 從網上獲取一條語句并顯示
- 6.3 在linux上寫匯編
- 6.4 在linux終端連接另一臺linux
- 6.5 makefile文件的編寫
- 6.6 deepin掛載遠程文件夾到本地文件夾
- 6.7 本地lnux和遠程linux進行文件拷貝
- 6.8 超好用的linux下的ssh管理工具(electerm)
- 6.9 那些不重要的技巧
- 6.10 linux文件加密
- 6.11 論文畫圖軟件gnuplot
- 6.12 自定義mrun命令用于執行當前路徑下的run文件
- 6.13 fish shell后臺運行程序
- 第7章 在線工具收集
- 7.1 各種編程語言的在線編輯運行
- 7.2 html js 在線嘗試
- 第8章 搭建自己的私有云盤
- 第9章 linux下的一些軟件
- 9.1 remarkable--markdown文件輕量編輯器
- 9.2 gnuplot畫圖軟件
- 9.3 Graphviz繪圖(流程圖,狀態圖)
- 第10章 TCL腳本編程
- 10.1 基礎教程
- 10.2 在tcl腳本文件其他tcl腳本文件運行
- 10.3 在tcl腳本文件中調用bash/fish
- 10.4 TCL培訓教程
- 10.5 tcl腳本參數傳遞
- 第11章 看云的使用
- 11.1 markdown添加公式
- 11.2 看云在linux本地編輯腳本
- 第12章 Go語言在linux下的使用
- 12.1 簡介
- 12.2 調用自己的包
- 12.3 Go語言學習的資料
- 12.4 golang使用flag完成命令行解析
- 12.5 Golang文件操作大全
- 12.5.1 創建空文件
- 12.5.2 Truncate文件
- 12.5.3 得到文件信息
- 12.5.4 重命名和移動
- 12.5.5 刪除文件
- 12.5.6 打開和關閉文件
- 12.5.7 檢查文件是否存在
- 12.5.8 檢查讀寫權限
- 12.5.9 改變權限、擁有者、時間戳
- 12.5.10 硬鏈接和軟鏈接
- 12.5.11 復制文件
- 12.5.12 跳轉到文件指定位置(Seek)
- 12.5.13 寫文件
- 12.5.14 快寫文件
- 12.5.15 使用緩存寫
- 12.5.16 讀取最多N個字節
- 12.5.17 文件追加內容
- 12.6 操作CSV文件
- 第13章 搜集資源的一些方法
- 13.1 電子書_電子課本
- 第14章 EndNote的使用
- 14.1 安裝
- 14.2 文件檢索