1.1 多源文件問題
我們平常編寫一些小的程序一般都是修改源代碼後,重新編譯所有的源文件,但是對於大型的項目,這種方法就會有許多的問題。edit−compile−test週期耗費的時間比較長,所以當只修改一個文件時,要避免編譯所有的源文件。
假設有頭文件a.h, b.h , c.h和C的源文件main.c, 2.c , 3.c。它們的關係如下:
/* main.c */
#include "a.h"
...
/* 2.c */
#include "a.h"
#include "b.h"
...
/* 3.c */
#include "b.h"
#include "c.h"
如果c.h修改了,源文件main.c和2.c應該不需要重新編譯,因爲它們不依賴c.h頭文件。而源文件3.c依賴頭文件c.h需要重新編譯。如果b.h修改了,我們忘記重新編譯2.c也會導致程序功能異常。 而make工具可以解決上面這兩個問題。
1.2 make命令和makefiles
make命令有一些內置的默認功能,但是光有這個它自己還是不知道怎麼build程序。我們必須提供一個文件告訴make應用程序的構造,這個文件就是makefile.
Make和makefile提供了強大的功能來管理項目的編譯以及發佈install到指定文件夾。
1.2.1 makefiles語法
一個makefile文件包括一系列的依賴(dependency))關係和規則(rules)。一個依賴有一個目標文件和一系列目標文件依賴的源文件。規則描述了怎麼用依賴文件創建目標文件。通常,一個目標文件是單個可執行文件。Make命令通過讀取makefile來決定哪些源文件和目標文件需要重新編譯,根據比較它們的日期和時間來決定使用來個規則來構建targets。
1.2.2 make的選項與參數
下面爲make 命令經常用到的三個選項:
-k:告訴make當發生錯誤時繼續執行,而不是停在第一個錯誤的位置。
-n:告訴make打印出它將要完成什麼,實際上沒做。
−f <filename>,告訴make用哪個makefile,如果不使用這個選項,make將搜索當前目錄下第一個名爲makefile的文件,如果還不存在,搜索一個文件名爲Makefile。
依賴:指明瞭每個文件怎麼樣關聯源文件。
在makefile文件中,寫依賴的方法爲:目標文件名,冒號,空格或tabs,然後接着是用空格或 tab分開的文件列表,用於創建目標文件。
依賴的例子:
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
解釋:myapp依賴 main.o, 2.o 和 3.o, main.o依賴 main.c 和 a.h,以此類推。
從上面可以很清楚的看到,當b.h改變了,我們需要修改2.o和3.o,當2.o和3.o改變了,需要重新編譯myapp.
當需要編譯多個文件,可以使用目標文件名all.假設應用程序包含一個二進制文件myapp和一個用戶指導頁面myapp.1,可以使用下面的方式指定。
all: myapp myapp.1
規則:是makefile的第二部分內容。告訴我們將如何創建目標文件。
當make命令決定2.o需要重新編譯,使用什麼命令進行編譯呢?也許最簡單的方式是使用gcc –c 2.c(make命令也有許多默認的規則),但是如果需要包含頭文件的目錄以及爲調試設置符號信息怎麼辦呢?可以在makefile當中使用顯示的規則。
重要:所有的規則都必須要以製表符(tab)開頭的行,空格不行。由於多個空格和tab看起來的效果很相同,但在Unix編程當中,tabs和spaces還是有區別的,會導致一些問題。在makefile文件末尾加入空格行會導致make命令不能執行,因爲缺少tab。
例子:一個簡單的 makefile,命名爲Makefile1
myapp: main.o 2.o 3.o
gcc −o myapp main.o 2.o 3.o //該行必須要有一個tab開頭
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
使用$ make −f Makefile1
這是由於make命令分析到目標文件爲myapp,然後查看依賴文件main.o,需要main.c來創建,但是開始沒有創建該文件。
使用下面的命令創建三個空頭文件:
$touch a.h
$touch b.h
$touch c.h
/* main.c */
#include "a.h"
extern void function_two();
extern void function_three();
int main()
{
function_two();
function_three();
exit (EXIT_SUCCESS);
}
/* 2.c */
#include "a.h"
#include "b.h"
void function_two() {
}
/* 3.c */
#include "b.h"
#include "c.h"
void function_three() {
}
$ make −f Makefile1
Make按順序顯示出了它執行的命令
現在修改b.h頭文件:
$ touch b.h
$ make −f Makefile1
Make只顯示了創建依賴於b.h的目標 文件的命令
刪除2.o
$ rm 2.o
$ make −f Makefile1
由上面可以看到make 可以正確的決定執行gcc命令的順序。
在makefile中使用#進行註釋。
1.2.3 makefiles宏
對於文件比較多的項目,可以在makefile中定義宏。
定義:MACRONAME=value
獲取值:$(MACRONAME) or ${MACRONAME}.
宏經常在makefile中用來爲編譯器指定編譯選項。對於發佈版本和調試版本程序需要的信息不同,對於release 版本不需要加入調試信息。對於Makefile1,默認使用的是gcc編譯器,在其他的Unix操作系統,可能使用cc或cc89,如果想讓makefile在不同的操作系統上運行,需要修改makefile文件中的多行。可以使用宏來解決這問題。
Makefile2:
all: myapp
# 指定編譯器
CC = gcc
# 頭文件目錄
INCLUDE = .
# 開發編譯選項
CFLAGS = -g -Wall -ansi
# 發佈版本編譯選項
# 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
$ make −f Makefile2
實際上,make有許多特殊的內部宏,可以讓makefile更簡潔。下面列出一些經常用到的內部宏。
$? |
List of prerequisites changed more recently than the current target. |
$@ |
Name of the current target. |
$< |
Name of the current prerequisite. |
$* |
Name of the current prerequisite, without any suffix. |
Makefile中另外兩個非常重要的特殊字符:
“-“讓make忽略任何錯誤。
“@”讓make在命令執行前不要向標準輸出打印命令。
1.2.4 多目標文件
可以使用”clean”選項來清除不想要的目標文件,用”install”選項來將應用程序移動到不同的目錄中。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
@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
install依賴於myapp.,make需要先生成myapp。
If前面的“@”是讓make不要輸出這些命令。
$ 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
1.2.5 內置規則
Makefile能準確判斷每個命令的執行步驟,make中包括許多內置的規則,當有許多源文件時,可以簡化makefiles。創建一個foo.c文件:
#include <stdlib.h>
#include <stdio.h>
int main()
{
printf("Hello World\n");
exit (EXIT_SUCCESS);
}
使用make進行編譯,不指定makefile.
$ make foo
Make命令知道怎麼調用編譯器,儘管使用的是默認的編譯器cc.
可以使用 make –p來打印內置的規則。
我們可以用自定義的值來改變內置宏,從而改變它的默認行爲。
$ rm foo
$ make CC = gcc CFLAGS = "−Wall −g" foo
1.2.6 後綴規則
有時源文件需要在不同的編譯器上進行編譯:如在MS-DOS或者是Linux的GCC下。在MS-DOS下的源文件後綴名大多爲.cpp而不是C,而Linux下的make命令沒有內置的規則來編譯.cpp文件(有.cc的編譯規則)。
我們需要讓make產生一個新規則來生成 .cpp的目標文件,這時需要添加一個後綴規則。Makefile5:
all: myapp
.SUFFIXES: .cpp
.cpp.o:
$(CC) -xc++ $(CFLAGS) -I$(INCLUDE) -c $<
# 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
@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
執行:
$ cp foo.c bar.cpp
$ make −f Makefile5 bar
gcc −xc++ −g −Wall −ansi −I. −c bar.cpp
−xc++:告訴gcc是一個C++源文件
1.2.7 make管理庫文件lib
當開發一個大的項目,利用一個庫(library)來管理多個編譯版本產品非常方便。庫文件的後綴名爲(.a),它包含許多的目標文件。
Make命令中內置的規則來管理庫:
.c.a:
$(CC) −c $(CFLAGS) $<
$(AR) $(ARFLAGS) $@ $*.o
宏 $(AR) 和 $(ARFLAGS) 指的是命令 ar 和命令選項 rv。含義爲:從一個 .c 文件到一個.a的庫文件需要應用兩條規則。第一條規則:必須編譯源文件爲目標文件;第二條規則:使用ar命令來添加該新的目標文件來生成庫文件。如果目前有一個庫文件fud包含目標文件bas.o,在第一條規則中,$<爲bas.c。在第二條規則中,$@爲fud.a,$*爲bas.
下面讓目標文件2.o 和3.o包含在庫文件 mylib.a,如下Makefile6:
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
# Local Libraries
MYLIB = mylib.a
myapp: main.o $(MYLIB)
$(CC) -o myapp main.o $(MYLIB)
$(MYLIB): $(MYLIB)(2.o) $(MYLIB)(3.o)
main.o: main.c a.h
2.o: 2.c a.h b.h
3.o: 3.c b.h c.h
clean:
-rm main.o 2.o 3.o $(MYLIB)
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
$ rm −f myapp *.o mylib.a
$ make −f Makefile6
gcc −g −Wall −ansi −c main.c −o main.o
gcc −g −Wall −ansi −c 2.c −o 2.o
ar rv mylib.a 2.o
ar: creating mylib.a
c − 2.o
gcc −g −Wall −ansi −c 3.c −o 3.o
ar rv mylib.a 3.o
c − 3.o
gcc −o myapp main.o mylib.a
1.2.8 GCC的兩個選項-JN –MM
-jN可以同時執行N條命令
-MM:gcc會輸出目標文件所依賴的源文件。
$ gcc −MM main.c 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
Make命令知道怎麼調用編譯器,儘管使用的是默認的編譯器cc.
我們可以用自定義的值來改變內置宏,從而改變它的默認行爲。
$ rm foo
$ make CC = gcc CFLAGS = "−Wall −g" foo