2008年8月19日 星期二

Makefile - 簡單教學

在寫大量的程式時,通常要編譯、組譯~
可能要打一堆指令,這一個時候就出現了Makefile的東西了~

先寫一個簡單的東西吧,以下是一個簡單的Makefile的內容
food.hfood.cppmain.cpp請參考這裡
Makefile

# Program, flags, etc.
ASM = g++
ASMFLAGS =
OBJ=food.o main.o
TARGET=main

.PHONY: everything clean all

everything: $(TARGET)

clean:
  -rm -fv $(OBJ) $(TARGET)

all: clean everything

food.o: food.cpp food.h
  $(ASM) -c $< -o $@

main.o: main.cpp
  $(ASM) -c $< -o $@

main: $(OBJ)
  @$(ASM) $(OBJ) -o $@


這裡要解釋一下,


target : prerequisites
  command

0. 想要得到target,需要執行命令command
1. target依賴於prerequisites,當prerequisites中至少有一個檔案比target檔新時,command才會被執行,而且command前面的空格是用Tab鍵產生的
2. 以字元#開頭的行是注釋。
3. =是用來定義變數,這裡ASM與ASMFLAGS就是兩個變數
4. .PHONY應該指的是make後面可以接的參數指令,那就表示它後面的名字並不是檔案,而僅僅是一種行為的標號,例如make clean、make everything、make all
5. $@表示target的第一個名字,也就是代表目前的標的(target)
6. $<表示prepequisites的第一個名字
7. $?同一個規則的所有先決條件名,但是只有原始程式碼改過的比obj檔新才會符合,也就是比target還新的先決條件檔案。
8. $*同一個規則的第一個先決條件,但是不包含副檔名
9. $^所有先決條件,但是有的make像solaris make, M$ nmake可能不認得這個自動變數。(在nmake是用$**表示)
10. 使用變數用$(VAR)或者${VAR}都可以。
11. 如果要用$,請多加一個$變成$,在Shell Command會用到Shell 變數此時就要加$。
12. 在要執行的指令前面加@,則不會顯示編譯的過程喔~像上面的例子的最後一行,就不會出現編譯的過程哩
13. 若直接輸入make,這時make程式會從第一個名字所代表的動作執行。在本例中,第一個標號是everything,所以make和make everything是一樣的。
14. 在要執行的指令前面加-,表示即使該行指令出錯,也不會中斷Makefile的執行。

當我們要產生執行檔時或是有修改檔案後,要重新編譯,我們就打以下指令

[root@host ~]# make


當我們要刪除執行檔和物件檔時,我們就打以下指令

[root@host ~]# make clean


你看,是不是就很節省時間啊~
而且,現在有很多程式都可以幫我們產生makefile,像是QT就是啦,不過,我們至少要知道它們是怎麼運作的,這樣之後要改也比較方便啊~

[2009.02.28 補充]
若只在指令列上打
$ make
則會尋找Makefile的內容來執行。
若有多個makefile的檔案,則可以使用-f的參數來指定是要使用哪一個makefile
$ make -f makefile
若目錄中有多個資料夾,而每一個資料夾均有一個makefile,則也可以使用make -f的方式由上一層的makefile來控制下面的所有的makefile
下面有一個例子:
情況是makefile.server負責產生server的執行檔
而makefile.client負責產生client的執行檔
我們們直接透過Makefile來完成所有的動作
Makefile
.PHONY: everything all clean clean_target

everything:
make -f makefile.server
make -f makefile.client

all: clean_target everything

clean:
make -f makefile.server clean
make -f makefile.client clean

clean_target:
make -f makefile.server clean_target
make -f makefile.client clean_target


[2009.04.01 補充]
可以使用include的指令喔~
自己的內隱規則
因為可能有的時候你希望做些dependency檢查,或者加上一些gcc 用的旗標,不是很單純的編譯而已你可以給make自訂的內隱規則
樣式規則(pattern rule)
你可以用pattern rule來做一些自定的內隱規則。像這樣
  %.o : %.c prog.h
$(CC) $(CFLAGS) $(DEBUG_FLAG) -c -o $@ $<

%表示所有相對於後面先決條件的檔名的意思,他不是*,因為他 有一對一的相對應關係,foo.o 就要找foo.c,foo1.o就要找foo1.c, 所以他不是*。所以上面的意思是所有碰到要.o的target時,去找相 對應的.c檔,並根據先決條件prog.h做檢查,如果找不到prog.h就不 做下去了。%不只可以表現主檔名,其實可以表現任一個相對應的字串, 所以叫pattern,你可以用s.%.c,不只用&.c,其中對應到%的子字串叫stem

[2009.04.02 補充]
-i   忽略指令執行返回的出錯資訊。
-s   沉默模式,在執行之前不輸出相應的指令行資訊。
-r   禁止使用build-in規則。
-n   非執行模式,輸出所有執行指令,但並不執行。
-t   更新目標文件。
-q   make操作將根據目標文件是否已經更新返回"0"或非"0"的狀態資訊。
-p   輸出所有巨集定義和目標文件描述。
-d   Debug模式,輸出有關文件和檢測時間的詳細資料。

Linux下make標誌位的常用選項與Unix系統中稍有不同,下面我們只列出了不同部分:
-C dir   在讀取 makefile 之前改變到指定的目錄dir。
-I dir   當包含其他 makefile文件時,利用該選項指定搜尋目錄。
-M dir    改變目錄後,再回到原本的目錄 
-h   help文擋,顯示所有的make選項。
-w   在處理 makefile 之前和之後,都顯示工作目錄。

遞迴make
在Makefile裡面叫make來用,請用$(MAKE),例如我們常在source code的 最上層打個make, 它會自動跑到底下所有的目錄去,裡面都有個Makefile,然後每個都做make。 例如
all:
cd user;$(MAKE) $@
cd kernel;$(MAKE) $@


[2009.04.03 補充]
進入每一個子目錄執行Makefile
Makefile例:

SUBDIR = B C
.PHONY: alldir $(SUBDIR)

alldir: $(SUBDIR)

$(SUBDIR):
$(MAKE) -C $@
B: C


這個 Makefile 會依照 SUBDIR 變數裡所包含的資料夾名稱, 一一進去執行make, 需要注意的是第 9 行的敘述, 這是一個處理相依性的例子, "B: C"的意思是, 編譯 B 之前, 要先編譯 C.

[2009.04.06 補充]
以下這一個例子是說明如何在Makefile中使用shell script到達到進入每一個子目標執行其目錄的Makefile,要注意的一點是若要使用到shell的$必需使用$$來表達
# 列出所有的子目錄
SUBDIRS = lib client/version01 server/01_iterative_server server/02_concurrent_server

# 列出所有的make後面可接的指令
INSTR   = everything all clean distclean

.PHONY: $(INSTR)

# 透過shell的指令到每一個子目錄尋找Makefile並執行其makefile
$(INSTR): $(SUBDIRS)
for i in $(SUBDIRS); do (cd $$i && make $@);done


[2009.04.12 補充]
指派巨集的優先順序(Priority of Macro Assignments)

如果以不同方式定義的巨集互相有衝突時(也就是不同的來源定義了相同的巨集名稱),make會採用哪個定義呢? 比如說,如果在描述檔裡頭就定義了DIR,其內容為/usr/proj,但是在目前的環境變數中,也有一個叫做DIR的環境變數,其內容為/usr/proj/lib,那麼make會採用哪一個定義呢? 下面就是優先權的順序,從優先權最大到優先權最小:

1.在使用make指令時,所定義的巨集,但是是位於make指令之後,相較於第2項,雖然同樣都是在命令列上,但是因為順序上的差異引發了微小的不同.
2.在描述檔裡頭的巨集定義.
3.目前的環境變數. 在Korn shell和Bourne shell裡頭,如果把巨集定義在make指令之前的話,此巨集相當於環境變數.
4.make內部的(default,內定的)已有的定義.

說明define-endef指令
在Makefile中若有一些指令複合指令常常一起出現,那麼我們就可以透過define-endef指令它們合併成一個指令,例:
define run-yacc
echo "Hello World"
mv $< $@
endef

foo.c:foo.y
$(run-yacc)

如此一來,當我要執行run-yacc時,他就會轉換成執行
echo "Hello World"

mv $< $@ 這兩個指令 控制語法
除了ifeq, ifneq還有ifdef, ifndef, endif, else, define-endef, include,很像c裡面的前置處理
例子:
ifeq ($(CC),gcc)
libs = "good"
else
libs = "not good"
endif

ifneq ($(CC),gcc)
lib = "bad"
else
lib = "not bad"
endif

default:
echo $(libs)
echo $(lib)
echo $(CC)

[2015.03.18 補充]
今天追了Makefile的code,
記錄一下兩個小技巧

因為makefile在編譯的過程中,
會有很多指令,
就算全部讓他顯示出來,
仍會出現太多資訊的情況,能不能像windows一樣,有pause的指令呢?
事情是是有的。
read -p "Press [Enter] key to start backup..."

另外一個是一個很神奇的東西,
就是原本指令只可以執行在command這一個區塊裡面,
然而,我今天看到一個非正規的方式,我覺得是非常不好的,
但是,還是記錄下來。
CP_UICONFIG := $(shell cp $(ROOTFS_PATH)/glue/mk.uicompo $(shell pwd))
這一道指令看似是在做變數給值的動作,實際上是在做cp的動作,
重點是,這一道指令可以不用放在command的區域,
可以放在Makefile一開始設定變數的區域~
害我今天一直找不到檔案在哪裡被更動的@@

另外同時記錄Makefile的四種等號
=
直接assign變數

:=
Makefile會先把整個Makefile展開後,再決定Makefile的變數值。
例如:
x = foo
y = $(x) bar
x = xyz

最後結果y變數會是xyz bar

若要以當時的變數值來設定的話,就要用:=來處理
x := foo
y := $(x) bar
x := xyz

此例最後y變數為foo bar

?=
當變數沒有被定義時,則使用右邊的數值,
若有被定義時,則使用原本的數值

參考資料:
跟我一起寫Makefile
參考資料:文魁資訊-自己動手寫作業系統

[2008.09.19 補充]
Makefile撰寫
Makefile 語法簡介
make 命令和 makefile
Makefile學習教程: 跟我一起寫 Makefile
如何寫一個簡單的 Makefile [1][2]
Tutorial - Makefile
Make - a tutorial
Linux/Unix環境下的make和makefile詳解
GNU make中文手册

沒有留言: