owned this note
owned this note
Published
Linked with GitHub
How to use makefile
===
## 基礎觀念
### 介紹:makefile 是什麼?
makefile 會告訴 make 該如何建立應用程式
### 不用 make 該如何編譯程式?
我們有 main.c 2.c 3.c
* step1:產生所有.c檔的目標檔
`# gcc -c main.c`:產生 main.o 目標檔,而#為 root 模式
```clike=
//main.c
#include "a.h"
```
`# gcc -c 2.c`:產生2.o目標檔
```clike=
//2.c
#include "a.h"
#include "b.h"
```
`# gcc -c 3.c`:產生3.o目標檔
```clike=
//3.c
#include "b.h"
#include "c.h"
```
* step2:製作執行檔
`# gcc -o main main.o 2.o 3.o`:其中的 main 為我們想建立的執行檔名稱
* step3:執行執行檔
`# ./main`
### 使用 make 該如何編譯?
* step1
`# vi makefile`:先用vim編寫文件(以下只列出大概格式,並非實際例子)
```
main: main.o 2.o 3.o
gcc -o main main.o 2.o 3.o
main.o: main.c
gcc -c main.c
2.o: 2.c a.h
gcc -c 2.c
3.o: 3.c b.h
gcc -c 3.c
```
* step2
`# make`:進行編譯
* step3
`# ./main`:執行執行檔
### makefile 第1步:了解核心觀念
核心觀念:
```
target : 目標檔1 目標檔2
<tab>gcc -o 欲建立執行檔的名稱 目標檔1 目標檔2
```
舉例:
```
myapp : main.o 2.o 3.o
gcc -o myapp 2.o 3.o
```
### makefile 第2步:知道相依性項目
透過以下指令也可看到當前自己的程式碼的相依性項目,要把所有.c檔羅列出來
`$ gcc -MM main.o 2.c 3.c `
```
main.o : main.c a.h
2.o : 2.c a.h b.h
3.o : 3.c b.h c.h
```
所有相依性項目可以寫成
```
myapp : main.o 2.o 3.o
main.o : main.c a.h
2.o : 2.c a.h b.h
3.o : 3.c b.h c.h
```
***TODO:可以補充樹狀圖***
這時候如果改變 b.h,那麼2.o 3.o 會受到影響,因此myapp也會受到影響

(樹狀圖工具)graphviz
`sudo apt-get install graphviz`
[GraphViz for discrete math students](http://graphs.grevian.org/graph/4993093174558720)
```
digraph{
"main.c"->"main.o"
"a.h"->"main.o"
"2.c"->"2.o"
"b.h"->"2.o"
"a.h"->"2.o"
"b.h"->"3.o"
"3.c"->"3.o"
"c.h"->"3.o"
"main.o"->"myapp"
"2.o"->"myapp"
"3.o"->"myapp"
}
```
### makefile 第3步:當target很多怎麼辦
舉例:
```
all : myapp myapp.1
```
產生 myapp 執行檔,myapp1 執行手冊
### makefile 第4步:實作 makefile,但檔名使用Makefile1
##### step1:以下為`$vi Makefile1`頁面
```
myapp : main.o 2.o 3.o
gcc -o myapp main.o 2.o 3.o
main.o : main.c a.h
gcc -c main.c
2.o : 2.c a.h b.h
gcc -c 2.c
3.o : 3.c b.h c.h
gcc -c 3.c
```
##### step2:執行 make 命令,但是程式碼不存在,跳出錯誤訊息
`$ make -f Makefile1`
技巧:-f參數後面是接 filename,代表告訴 make 他心中的 makefile 是 Makefile1
跳出 make:***No rule to make target 'main.c' need by main.o
##### step3:利用 touch 產生空白標頭檔
`$ touch a.h`
`$ touch b.h`
`$ touch c.h`
##### step4:編譯程式 main.c 2.c 3.c
```clike=
/*main.c*/
#include <stdio.h>
#include "a.h"
extern void function_two();
extern void function_three();
int main()
{
function_two();
function_three();
}
```
```clike=
/*2.c*/
#include "a.h"
#include "b.h"
void function_two(){
}
```
```clike=
/*3.c*/
#include "b.h"
#include "c.h"
void function_three(){
}
```
##### step5:再使用 make 編譯程式
`$ make -f Makefile1`
```
gcc -c main.c
gcc -c 2.c
gcc -c 3.c
gcc -o myapp main.o 2.o 3.o
```
##### step6:如果 b.h 有修改過
影響了 2.o 3.o myapp
`$ touch b.h`
`$ make -f Makefile1`
```
gcc -c 2.c
gcc -c 3.c
gcc -o myapp main.o 2.o 3.o
```
### makefile 第5步:技巧分享
`$ make -j4`:同時執行4個命令
`$ make -k`:編譯發生錯誤仍然執行
`$ make -n`:只印出將進行工作,而不去真正編譯
在 makefile 內打#是註解
## 進階觀念
### makefile 進階第1步:了解變數目的
適用於不同編譯器
可以簡化makefile
### makefile 進階第2步:了解變數定義
MACRONAME=value
1.左邊為變數名稱,右邊為變數內容
2.變數名稱最好都用大寫
3.=兩側不能出現':',兩側可以有空格
4.value 變成空白代表變數被清空
5.變數名稱左邊不可以有 tab
### makefile 進階第3步:變數存取
`$(MACRONAME) or ${MACRONAME} `
### makefile 進階第4步:以變數簡化makefile
original
`$ vi makefile`
```
main : main.o haha.o sin.o cos.o
gcc -o main main.o haha.o sin.o cos.o -lm
clean :
rm -f main main.o haha.o sin.o cos.o
```
now:define the LIBS and OBJS
`$ vi makefile`
```
LIBS = -lm
OBJS = main.o haha.o sin.o cos.o
main : $(OBJS)
gcc -o main $(OBJS) $(LIBS)
clean :
rm -f main $(OBJS)
```
### makefile 進階第5步:嘗試定義變數以環境變數 CFLAGS 為例
法1:在命令列定義
`$ make clean main "CFLAGS=-Wall"`
法2:在 makefile 內定義
`$ vi makefile`
```
LIBS = -lm
OBJS = main.o haha.o sin_value.o cos_value.o
CFLAGS = -Wall
main: ${OBJS}
gcc -o main ${OBJS} ${LIBS}
clean:
rm -f main ${OBJS}
```
<重點>環境變數取用規則:
make 指令列後面加上的環境變數為優先;
makefile 裡面指定的環境變數第二;
shell 原本具有的環境變數第三。
### makefile 進階第6步:實作一個含有變數的 makefile
目標項目 all 只會產生 myapp。執行 make 建立目標項目 myapp。
`$ vi Makefile2`
```
all: myapp
# Which compiler
CC = gcc
# Where are include files kept
INCLUDE = .
# Options for development (-g Produce debugging information)
CFLAGS = -g -Wall -ansi
# Options for release
# CFLAGS = -O -Wall -ansi
myapp: main.o 2.o 3.o
$(CC) -o myapp main.o 2.o 3.o
main.o: main.c a.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c main.c
2.o: 2.c a.h b.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c 2.c
3.o: 3.c b.h c.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c 3.c
```
執行:
`$ rm *.o myapp` 應該是移除所有.o檔,還有 myapp 執行檔
`$ make -f Makefile2` 用-f參數去收到 makefile2
gcc -I. -g -Wall -ansi -c main.c
gcc -I. -g -Wall -ansi -c 2.c
gcc -I. -g -Wall -ansi -c 3.c
gcc -o myapp main.o 2.o 3.o
注意:以下3個被變數所取代
```
all: myapp
INCLUDE = .
CFLAGS = -g -Wall -ansi
```
### makefile 進階第7步:make 常用的語法
#### 一些符號定義
* :=
變數的值決定於它在 Makefile 中的位置,而不是整個 Makefile 展開後最終的值
* ?=
若變數未定義,則替它指定新的值。否則,採用原有的值
如︰FOO ?= bar
若 FOO 未定義,則 FOO = bar;若 FOO 已定義,則 FOO 的值維持不變。
* +=
此時 CFLAGS 的值就變成 -Wall -g -O2
```
CFLAGS = -Wall -g
CFLAGS += -O2
```
#### 自動化變數
* $? 代表需要重建(被修改)的相依性項目。
* $@ 目前的目標項目檔名。
* $< 第一個必要條件的檔名。
* $* 工作目標的主檔名。
* $^ 所有必要條件的檔名, 並以空格隔開這些檔名。 (這份清單已經拿掉重複的檔名)
例題:以 $@ 代表目前的目標 (target) 項目。
[guest@test guest]# vi makefile
```
LIBS = -lm
OBJS = main.o haha.o sin_value.o cos_value.o
CFLAGS = -Wall
main: ${OBJS}
gcc -o $@ ${OBJS} ${LIBS} #在此 $@ 即 main,也就是執行檔名稱
clean:
rm -f main ${OBJS}
```
#### 特殊字元
makefile 中兩個特別字元,可以加在要執行的命令之前:
* -:make 會忽略命令的錯誤。
```
如果希望產生一個目錄,但希望忽略錯誤,可能是因為該目錄已經存在。
-mkdir /usr/local/repository
如果希望清除目標檔案,但希望忽略錯誤,可能是因為該檔案不存在。
clean:
-rm main.o 2.o 3.o
```
* @ :不要顯示執行的指令,因執行 make 指令後會在終端機印出正在執行的指令
```
判斷式 if 起始為符號 @,讓 make 在執行該法則時,停止印出標準輸出的文字。
install: myapp
@if [ -d $(INSTDIR) ]; \
then \
...;\
fi
```
#### 萬用字元
makefile 中所用的萬用字元是 % ,代表所有可能的字串,前後可以接指定的字串來表示某些固定樣式的字串。例如 %.c 表示結尾是 .c 的所有字串。因此我們改寫 makefile 如下
```clike=
CC = gcc
OBJS = a.o b.o c.o
all: test
%.o: %.c
$(CC) -c -o $@ $<
test: $(OBJS)
$(CC) -o test &^
```
試著重建上述語法:未必正確
```clike=
all:test
a.o:a.c
gcc -c -o main ?
b.o:b.c
gcc -c -o main ?
c.o:c.c
gcc -c -o main ?
test:a.o b.o c.o
gcc -o test ?
```
## 深入觀念:多重目標項目(target)
### 在 makefile 建立多個目標項目,用 make 去指定要哪個目標項目
例題:沿上例,加入 clean 目標項目,以移除不想要的目的檔(object)。
建立編譯的規則
[guest@test guest]# vi makefile
main: main.o haha.o sin_value.o cos_value.o
gcc -o main main.o haha.o sin_value.o cos_value.o -lm
clean:
rm -f main main.o haha.o sin_value.o cos_value.o
#%* clean 冒號之後是空白。目標項目永遠會被認為過期,所以它的法則永遠會被執行。*)
測試標的 clean:
[guest@test guest]# make clean
rm -f main main.o haha.o sin_value.o cos_value.o
先清除目標檔再編譯程式 main : make clean main 會先幫你清空.o檔跟執行檔,再幫你編譯一次
[guest@test guest]# make clean main
rm -f main main.o haha.o sin_value.o cos_value.o
cc -c -o main.o main.c
cc -c -o haha.o haha.c
cc -c -o sin_value.o sin_value.c
cc -c -o cos_value.o cos_value.c
gcc -o main main.o haha.o sin_value.o cos_value.o -lm
### 尚未理解
沿上例,再加入 install 目標項目,將完成的應用程式安裝到不同的目錄。
```
makefile 新的版本 Makefile3:
all: myapp
# Which compiler
CC = gcc
# Where to install
INSTDIR = /usr/local/bin
# Where are include files kept
INCLUDE = .
# Options for development
CFLAGS = -g -Wall -ansi
# Options for release
# CFLAGS = -O -Wall -ansi
myapp: main.o 2.o 3.o
$(CC) -o myapp main.o 2.o 3.o
main.o: main.c a.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c main.c
2.o: 2.c a.h b.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c 2.c
3.o: 3.c b.h c.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c 3.c
clean:
-rm main.o 2.o 3.o
install: myapp
# 每個命令都會啟動一個新的 shell,所以必須加上反斜線(\),讓所有 script 命令在同一行,且在同一個 shell 中執行。
@if [ -d $(INSTDIR) ]; \
then \
cp myapp $(INSTDIR);\
chmod a+x $(INSTDIR)/myapp;\
chmod og-w $(INSTDIR)/myapp;\
echo "Installed in $(INSTDIR)";\
else \
echo "Sorry, $(INSTDIR) does not exist";\
fi
&&:前個命令成功,才執行下個命令
@if [ -d $(INSTDIR) ]; \
then \
cp myapp $(INSTDIR) &&\
chmod a+x $(INSTDIR)/myapp && \
chmod og-w $(INSTDIR/myapp && \
echo "Installed in $(INSTDIR)" ;\
else \
echo "Sorry, $(INSTDIR) does not exist" ; false ; \
fi
執行
$ rm *.o myapp
$ make -f Makefile3
gcc -I. -g -Wall -ansi -c main.c
gcc -I. -g -Wall -ansi -c 2.c
gcc -I. -g -Wall -ansi -c 3.c
gcc -o myapp main.o 2.o 3.o
$ make -f Makefile3
make: Nothing to be done for 'all'.
$ rm myapp
$ make -f Makefile3 install
gcc -o myapp main.o 2.o 3.o
Installed in /usr/local/bin
$ make -f Makefile3 clean
rm main.o 2.o 3.o
$
```
```
請解釋如下 makefile 片段,假設變數 INSTDIR=mydir。
install: myapp
@if [ -d $(INSTDIR) ]; \
then \
cp myapp $(INSTDIR);\
chmod a+x $(INSTDIR)/myapp;\
chmod og-w $(INSTDIR)/myapp;\
echo "Installed in $(INSTDIR)";\
else \
echo "Sorry, $(INSTDIR) does not exist";\
fi
Sol. 1.目標項目 install 的相依性項目有 myapp; 2. @ 表示不會在標準輸出上,顯示要執行的命令; 3.如果 mydir 是一個目錄,則執行複製 myapp 到目錄 mydir、改變 mydir/myapp 的屬性,所有使用者加上可執行屬性、其他使用者及同群組使用者刪除可寫屬性;螢幕輸出 Installed in mydir; 4.其他(即 mydir 不是一個目錄,則螢幕輸出 Sorry, mydir does not exist; 5.因為每個命令都會啟動一個新的 shell,所以必須加上反斜線(\),讓所有 script 命令在同一行,且在同一個 shell 中執行。
如下 makefile ,&& \ 代表意義為何?
chmod a+x $(INSTDIR)/myapp && \
chmod og-w $(INSTDIR/myapp && \
Sol. && 表示前個命令成功,才執行下個命令,\ 讓 script 命令在同一行,且在同一個 shell 中執行。
```
## 技巧
==解說==:使用 .PHONY: clean,會把 clean 當作一個 fake 項目,不認為這是要產生的檔案,因此 makefile 不會去檢查目錄中是否有 clean 檔案,提升 make 的效率
```cmake=
.PHONY: clean
clean:
rm *.o
```
==解說==:
* @ 不要顯示執行的指令
因執行 make 指令後會在終端機印出正在執行的指令
* '-' 表示即使該行指令出錯,也不會中斷執行
而 make 只要遇到任何錯誤就會中斷執行。但像是在進行 clean 時,也許根本沒有任何檔案可以 clean,因而 rm 會傳回錯誤值,因而導致 make 中斷執行。我們可以利用 - 來關閉錯誤中斷功能,讓 make 不會因而中斷。
```cmake=
.PHONY: clean
clean:
@echo "Clean..."
-rm *.o
```
reference
[make 命令和 makefile](https://dywang.csie.cyut.edu.tw/dywang/linuxProgram/node49.html)
[Makefile 語法和示範](https://hackmd.io/s/SySTMXPvl#)
[2016q3 Homework01 (raytracing)](https://hackmd.io/s/HyHhgcv6#graphviz)
[Makefile 語法簡介](http://tetralet.luna.com.tw/?op=ViewArticle&articleId=185)