Trong BASH người ta ít khi dùng nó cho mụch đích lập trình đa luồng thay vào
đó là các ngôn ngữ mạnh hơn (như C/C++). Tuy nhiên, BASH vẩn có khả
năng để cho người lập trình viết trong kiểu kiến trúc này.
Thí dụ của
Rocky Bernstein sau đây trích từ hồ sơ mở “Advanced
Bash-Scripting Guide"
Trong chế độ mặc định thì lệnh này
sẽ hiển thị tóm lược danh sách các thiết bị. Các tham số có thể được dùng
để xuất ra các thông tin có thể cần dùng cho các chương trình khác gọi nó.
Hoạt động của lệnh make gắn chặt và
phụ thuộc vào một tập tin đặc biệt. Tập tin này chứa các chỉ thị và các mô
tả cho make hoạt động. Nó được gọi là tập tin kiến tạo (makefile).
Thao tác tác động của lệnh
make nhằm cập nhật hoá một hay nhiều đối tượng được
gọi là kiến tạo
Vì khối lượng thông tin rất lớn,
phần này chỉ trình bày những kiến thực thực tế nhất thường được dùng để tạo các
tập tin kiến tạo
Theo văn bản hướng dẫn của GNU, make là một tiện ích có khả năng tự xác định
xem những phần nào của một chương trình cần được dịch (hay dịch lại) và từ đó
đưa ra các mệnh lệnh để chuyển dịch chúng. phiên bản GNU make được phát triển
bởi Richard Stallman và Roland McGrath. Sau đó, kể từ phiên bản 3.76 nó
được "trông coi" bởi Paul D. Smith.
Lưu ý: trong thí dụ trên bắt buộc lệnh
echo phải được chừa lề bằng một kí tự
<tab> duy nhất
A19.2.2 Các thành tố của một tập tin kiến tạo
Một
tập tin kiến tạo có thể bao gồm các thành tố sau đây:
- Các quy định (rule) hay còn gọi là các hiển quy (explicit rule) : cho phép khi nào và cách nào để
tạo ra hay tái
tạo lại một hay nhiều tập tin. Các tập tin này gọi là các tập tin
đích (hay ngắn gọn là đích ). Nó cũng liệt kê
các tập tin mà các đích phụ thuộc vào, các tập tin đó gọi là các tiền đề.
Ngoài ra, trong nó cũng có thể có các lệnh đùng để tạo ra hay cập nhật các
đích.
- Các quy định ngầm (implicit rule) : Cho phép khi nào và cách nào để tái tạo
một lớp các tập tin dựa trên tên của chúng. Nó mô tả cách thức để một đích có
thể phụ phụ vào một tập tin khi mà tên của tập tin này tương tự với đích
(chẳng hạn chỉ khác nhau phần tên mở rộng) cũng như là cung cấp các lệnh để
tạo ra hay cập nhật các đích này.
- Các định nghiã biến: là các dòng để khai báo biến
- Các dòng bị chú: Các dòng hay chuỗi kí tự bắt đầu bằng dấu #
là dòng bi chú.
- Các lệnh thi hành: Đây là một bộ phận của một đích. Mỗi dòng lệnh
này phải được bắt đầu bằng một kí tự nhảy bước (tab), sau đó là các lệnh. Các
lệnh này được make xem như là lệnh hệ thống và sẽ gửi ra hệ vỏ để thi
hành. Hệ vỏ sẽ có toàn quyền để thực thi các lệnh đó.
- Các định hướng (directive) : Là các lệnh yều cầu make thực thi một thao
tác đặc biệt trong khi make đọc tập tin kiến tạo. Các thao tác bao gồm:
- Đọc một tập tin kiến tạo khác
- Quyết định (dựa vào giá trị của các biến) xem khi nào bỏ qua hay khi nào
dùng đến một bộ phận của tập tin kiến tạo
- Xác định biến trực tiếp từ một chuỗi kí tự có nhiều dòng
Bộ phận quan trọng nhất của các định hướng là các
điều kiện (conditional) : Cung cấp sự phân nhánh cho dòng điều
khiển cho lệnh make từ các tập tin kiến tạo.
Thí dụ: để tiện theo dõi thí dụ này, các dòng dược đánh số. Tuy nhiên trong thực tế các makefile sẽ không có các con số dòng này 1 #This is the remarked line
2 #file name : Myporgram
3 #Name: ABC
4
5 PROG = Myprogram # initialize variable PROG
6 OBJS = main.obj io.obj # list of object files (define varibale line)
7
8 RM = /bin/rm -f
9
10 # Configuration:
11 INCLUDES= -I../include -I/usr/include
12
13 CC = gcc # name of compiler
14 LD = gcc # name of linker
15
16 # Compiler-dependent section
17 %if $(CC) == gcc # if compiler is bcc (conditional line)
18 CFLAGS = $(INCLUDES) -g -wall
19 %elif $(CC) == icc # else if compiler is icc
20 LD = icc
21 CFLAGS = $(INCLUDES) –g
22
23 %else # conditional "else"
24 % abort Unsupported CC==$(CC) # compiler is not supported
25 %endif # conditional "endif"
26
17 all: $(PROG) #top level rule: support "make all" command (see phony target)
28
29 $(PROG): $(OBJS) #rule for linking (two lines: this line define dependency)
30 $(LD) $(OBJS) -o $(PROG) #defind command to execute the rule
31
32 # rule for "main.o".
33 main.o: main.c file1.h file2.h
34 $(CC) $(CFLAGS) -c main.c
35
36 # rule for "file1.o".
37 file1.o: file1.c file1.h
38 $(CC) $(CFLAGS) -c file1.c
39
40 # rule for "file2.o".
41 file2.o: file2.c file2.h
42 $(CC) $(CFLAGS) -c file2.c
43
44 # rule for cleaning re-compilable files: "make clean"
45 clean:
46 $(RM) $(PROG) $(OBJS)
Trong thí dụ trên thì:
- các cặp dòng 33-34, 37-38, 41-42, và 45-46 là các quy định mà
- Dòng đầu khai báo tên của các đối tượng nằm trong quy định. Các tên
nằm sau dấu hai chấm là các tập tin mà các đối tượng đó phụ thuộc vào (phải có
để trình dịch hay lệnh make có thể hoạt động đúng)
- Các dòng tiếp theo là dòng lệnh sẽ được gửi ra hệ vỏ để thực thi.
- Các dòng 17,19,23,24, và 25 là các định hướng điều
kiện (conditional directive)
- Các dòng 5,6,8,11, 13,14, 18, 20, 21 ,.. là các dòng khai báo biến và/hay
định nghiã giá trị của chúng
- Các dòng bắt đầu bằng dấu # là dòng bị chú make sẽ bỏ qua
A19.2.3 Thực thi một lệnh make:
Việc thực thi lệnh make sẽ tùy theo các cài đặt có trong tập tin kiến tạo.
Tuy nhiên, cách đơn giản nhất là thử nhập lệnh make không cần tham số.
Ngoài các tham số cho sẵn, các tập tin kiến tạo có thể cung ứng nhiều
đối số khác nhau tùy theo
đích và các đích nhằm tạo ra dđ^'i số cho lệnh make này gọi là đích giả
(phony target) (hay ngắn gọn là đích nếu không sợ bị nhầm lẫn). Các đích giả này
sẽ được định nghiã từ bên trong các tập tin kiến tạo (xem thêm phần đích ) Dù sao, những
đối số chuẩn hay được dùng là: (xem thêm đích
giả )
-
all : tạo ra tất cả các đích ở mức cao nhất mà tập tin kiến tiến có thể làm
được.
-
clean : Xóa tất cả các tập tin mà thường chúng được làm ra từ việc thi hành
lệnh make
-
install : Chép các tập tin khả thi vào trong các thư mục đúng chỗ của nó
(nơi mà người dùng thường tìm tới các lệnh) đồng thời chép các tập phụ trợ
vào các thư mục mà mà các chương trình tạo được ra bởi make sẽ
tìm tới chúng.
-
check hay
test : Tiến hành các thử nghiệm (như là kiểm tra các điều kiện vận
hành) lên chương trình mà makefile sẽ tạo ra.
Tóm lại cú pháp gọi lênh make là:
make [<Tham_số>]
[<Các_Đích>]
Thí dụ Giả sử một makefile tên là "myMakefile" có hỗ trợ hai dích là
clean và all. Thì nó có thể được gọi bằng cách
make -f myMakefile clean all
A19.2.3 Viết một tập tin kiến tạo
Phần này sẽ hướng dẫn sơ lược cách
tạo ra một tập tin kiến tạo. Tuy nhiên, vì khối
lượng thông tin khổng lồ, chúng ta chỉ tóm tắt ngắn gọn những yếu tố thiết yếu
để có thể bắt đầu viết một tập tin kiến tạo. Phần đào sâu thêm sẽ đòi hỏi các
bạn đọc xem thêm tài liệu tham khảo chính của GNU về make tại trang WEB
hhttp://www.gnu.org/software/make/manual/make.html
A19.2.3.1 Biến, giá trị biến và các hàm hỗ trợ
Biến hay con gọi là macro trong lệnh make được hiểu như là một tên thay thế
cho một chuỗi kí tự, chuồi này gọi là giá trị của biến. Tên của một biến có thể
xuất hiện ở mọi nơi mọi thành phần của một makefile và sẽ được thay bằng giá trị
của nó khi thực thi.Nội dung của biến có thể là bất kì có thể tên cuả một tập
tin hay nhiều tập tin hay có thể chỉ là một phần của các tham số ...
Quy định về tên biến: Tên biến có thể là chuỗi kí từ bất kì miễn
là không chứa các kí tự :, #, =, hay là không thể bắt đầu hay kết thúc bằng các
kí tự khoảng trắng (whitespace). Tài liệu đặc tả về lệnh make có khuyên
rằng nên đạt tên biến chỉ dùng các kí tự chữ, số và dấu gạch dưới
(underscore) đồng thời tránh dùng chữ viết hoa (các biến viết hoa thường được
dùng trong các quy định ngầm -- nếu dùng trùng tên có thể tạo ra các hiệu ứng
cuỡng trị (override) lên các biến của quy định ngầm)
Để biểu thị giá trị của biến
X thì có thể viết là
${X} hay $(X)
A19.2.3.1.1 Gán gía trị cho biến:
- =
Thí dụ:
SOURCE = main.c main.h file1.c file1.h
Với cách
gán giá trị này thì tên biến có thể được khai triển hồi quy. Nghiã
là giá trị của biến có thể được trung chuyển thông qua các tên biến gán bởi
dấu =.
Thi dụ:
one = $(two)
two = $(three)
three = "ABC"
Khi đó, giá trị của biến three
sẽ được chuyển dịch sang cho biến one tức là nó
sẽ có giá trị là ABC
- :=
Thí dụ:
OBJS = main.o file1.o
Cách gán giá trị này sẽ
không cho phép khai triển hồi quy. Nói cách khác gán giá trị kiểu này là chỉ cho
phép b>khai triển đơn giản. Các gán đơn giản này sẽ có mặt úng dụng riêng
trình bày ở phần sau.
- Dùng định hướng
define
<Các_dòng_kí_tự>
endef
Cách này cho phép gán nội dung của nhiều dòng chữ
<Các_dòng_kí_tự> lên một
biến. Thường được dùng để gán các chuỗi mệnh lệnh vào giá trị của một biến.
Thí dụ:
define two-lines
echo file1
echo $(file2)
endef
Khi thực thi (gửi ra hệ vỏ) thì sẽ tương đương với hai lệnh thi hành liền nhau
(tức là echo file1; echo ${file2} )
Lưu ý: khi dùng cách này để định nghiã thì các dấu kí tự đặc biệt như là kí tự
$ các dấu ngoặc và tên các biến đều trở thành thành phần của giá trị biến
được định nghiã bởi định hướng
define.
- ${<Tên_biến>:<phần_muốn_bỏ>=<phần_thay_vào>} hay
$(<Tên_biến>:<phần_muốn_bỏ>=<phần_thay_vào>) Các khai báo này sẽ cho
phép dựng biến mới với nội dung tương tự như các biến đã có mà chỉ đổi phần
tiếp vỹ ngữ. Điều này khác tiện lợi cho việc khai báo biến mới với nội
dung chỉ khác phần mở rộng tên các tập tin
Thí dụ:
file1 := a.o b.o c.o
file2 := $(file1:.o=.c)
sẽ cho ra biến file2 với nội dung là a.c b.c c.c
Bị chú: Thực ra, đây là ứng dụng dạng viết ngắn của hàm patsubstr (xem phần
sau)
A19.2.3.1.2 Các hàm và phép toán hỗ trợ thay đổi nội dụng biến:
A19.2.3.2.2.1 Phép toán
- += Sẽ cho phép nối thêm vào nội dụng sẵn có của biến một giá trị
trống (space) và giá trị mới gán vào
Thí dụ:
OBJS = main.o file1.c
OBJS += file2.o
giá trị của OBJS sẽ là main.o file1.o file2.o
A19.2.3.2.2.2 Các hàm giúp truy cập giá trị của biến:
make hỗ trợ một số hàm nhằm thay đổi các giá trị của dòng văn bản, hay nội dung của biến.
Lưu ý: Các dấu phẩy, dấu kí tự trắng và các dấu ngoặc sẽ không
được có mặt trong nội dung dòng văn bản cũng như nội dung các tham số của hàm.
Để vượt qua chướng ngại này thì có thể viết gián tiếp
Thí dụ:
comma:= ,
empty:=
space:= $(empty) $(empty)
file1:= a b c
file2:= $(subst $(space),$(comma),$(file1))
# file2 is now `a,b,c'.
Các hàm hỗ trợ quan trọng bao gồm:
- subst hàm này cho phép thay thế một bộ phận của dòng kí tự bởi
bộ phận thay thế.
Cú Pháp:
$(subst
<phần_đục_bỏ>,<phần_thay_vào>,<Dòng_văn_bản>)
Thí dụ:
$(subst a,CC,the hat on the cat)
sẽ đổi dòng the hat on the cat thành dòng
the hCCt on the cCCt
- patsubst sẽ tìm và thay thế các từ thoả mãn một dạng thức bằng từ mới trong dòng văn bản cho sẵn.
Cú Pháp:
$(patsubst
<Dạng_thức>,<Từ_thay_thế>,<Dòng_văn_bản>
Lưu ý:
Khác với các dạng thúc trong BASH, dạng thức ở đây chỉ giới hạn trong một từ
(word) -- từ trong lệnh make là một chuỗi kí tự trong dòng văn bản con được ngăn cách với từ
khác bởi các kí tự trắng --
make chỉ hỗ trợ một siêu ki tự. Đó là : kí
tự % sẽ cho phép ứng hợp với mọi loại kí tự theo sau
có độ dài từ 1 kí tự trở lên (cho đến kết thúc từ đang
được cứu xét) (gần tương tự như siêu kí tự
* trong BASH nhưng siêu kí tự
* không bị gới hạn trong biên giới của 1 từ);
Thí dụ:
$(patsubst %.c,%.o,x.c.c file2.c) sẽ thay các từ kết thúc bởi
.c thành từ đó nhưng kết thúc là
.o và hàm này cho ra giá trị
x.c.o
file2.o.
Các dạng viết tắt của hàm patsubst bao gồm -
$(<Tên_biến>:<Dạng_thức>=<Từ_thay_thế>) sẽ tương đương với
$(patsubstr
<Dạng_thức>,<Từ_thay_thế>,$(<Tên_Biến>)).
- $(<Tên_biến>:<Tiếp_vỹ_ngữ>=<Từ_thay_thế>) sẽ tương đương với
$(patsubst %<Tiếp_vỹ_ngữ>,%<Từ_thay_thế>,$(<Tên_biến>)).
Thí dụ: có biến source= main.c file1.c file2.c
thì có thể nhận về tên các
tập tin đối tượng .c bằng cách viết
objs= $(source:.c=.o).
Hoàn toàn
tương đương với cách viết
objs= $(patsubst %c, %o, $(source))
-
$(strip string) Hàm này sẽ xoá tất cả các kí tự trống giữa các từ trong
string và thay bằng một kí tự trống duy nhất nằm giữa các từ đó.
-
$(findstring
<Giá_trị>,<Dòng_văn_bản>) : Tìm
<Giá_trị>
trong <Dòng_văn_bản>.
Thí dụ
$(findstring a,a b c) sẽ trả về giá trị
a; trong khi $(findstring a,b c)
sẽ trả về giá trị '' (chuỗi kí tự trống)
-
$(filter
<Các_dạng_thức>,
<Dòng_văn_bản>) xoá tất cả các từ không tương hợp với
<Các_dạng_thức> và chỉ trả về phần còn lại
tương hợp với
<Các_dạng_thức> trong dòng văn bản.
Ở đây các dạng thức được viết ngăn cách nhau bằng một kí tự trống
Thí du: Trong một tập tin kiến tạo có ba dòng
source := file1.c file2.c def.h
mydat.dat mydat.s
file1: $(sources)
cc $(filter %.c %.h,$(source)) -o file1
dòng lệnh thứ ba sẽ có giá trị là
cc file1.c file2.c
def.h -o file1
-
$(filter-out
<Các_dạng
thức>,<Dòng_văn_bản>) ngược lại với hàm filter,
hàm này sẽ chỉ trả về phần còn lại của dòng văn bản sau khi đã loại bỏ các
từ nào tương hợp với các dạng thức
-
$(sort <Dòng_văn_bản>)
Xếp thứ tự theo lối từ điển và xoá các từ trùng lặp trong
<Dòng_văn_bản>
Thí du:
$(sort file1 file2 main file1)
sẽ trả về giá trị file2 file1 main
-
$(dir <Các_Tên>)
: tương tự lệnh
dirname trong
BASH, hàm trả về tên phần thư mục chứa tên cuối cùng.
Thí dụ:
$(dir /home/myname/file1.c /home/myname/file2.c test)
sẽ trả về giá trị
file1.c file2.c
-
$(notdir
<Các_tên>) Tương tự như lệnh
basename trong BASH, hàm trả về các tên nhưng cắt bỏ phần tên thư mục chứa giá trị cuối của từ.
Thí dụ:
$(notdir src/file1.c /home/myname/file2.c test)
trả về file1.c file2.c test.
-
$(suffix
<Các_tên>)
Chỉ trả về các tiếp vỹ ngữ của <Các_tên>
mà bắt đầu bằng dấu chấm (tương ứng với phần tên mở rộng của các tập tin)
Thí dụ:
$(suffix file1.c /home/myName/file2.c mydata.dat)
trả về .c .dat
-
$(basename
<Các_tên>)
Ngược với hàm suffix, hàm này trả về tất cả ngoại trừ phần tên
mở rộng (tức là phần
cuối của tên bắt đầu từ dấu chấm cuối cùng)
Thí dụ:
$(basename home/myname/file1.c mydat.dat)
trả về home/myname/file1 mydat.
Lưu ý: hàm basename này có nội dung trả về hoàn toàn khác với hàm
basename trong
BASH.
-
$(addsuffix
<Tiếp_vỹ_ngữ>,<Các_tên>)
: Thêm <Tiếp_vỹ_ngữ>
vào <Các_tên>
Thí dụ:
$(addsuffix .c,file1 file2)
trả về file1.c file2.c.
-
$(addprefix
<Tiếp_đầu_ngữ>
, <Các_tên>)
Thêm <Tiếp_đầu_ngữ>
vào trong <Các_tên>
Thí du:
$(addprefix /usr/src/,file1 file2)
cho kết quả /usrsrc/file1 /usrsrc/file2.
-
$(wildcard
<Dạng_thức>) Dùng để gán giá trị cho biến các tên tập tin nhưng
dùng kí tự phỏng định
Thí dụ:
OBJS= $(wildcard *.c)
cho kết quả là tất cả các tên có
phần mở rộng là .c
A19.2.3.2.2.3 Các hàm thông dụng khác có tác động lên biến bao gồm:
- $(if condition,then-part[,else-part])
: make sẽ đánh giá
condition bằng cách cắt bỏ các kí tự trống ở trước và sau
condition rồi sau đó
khai triển (expand) tham số này (thường là tên biến). Nếu
condition có giá trị
false (tức đó là một string
độ dài bằng 0) thì phần
else-part(nếu có) sẽ được
khai triển; ngược lại phần
then-part sẽ được
khai triển.
Thí dụ:
INCLUDE_FLG += -Iinclude $(if ($BUILDSRC, -Iinclude2, -I$(src)/include)
- $(foreach var,list,text)
: khai triển đối số
list, sau đó lần lượt gán lên
var các giá trị của
mỗi từ có trong list theo thứ tự và thực thi
text.
Thí dụ1:
dirs := a b c d
files := $(foreach dir,$(dirs),$(wildcard $(dir)/*))
Sẽ cho kết quả nội dung của biến file lần lượt là
$(wildcard a/*),
$(wildcard b/*), $(wildcard c/*), và $(wildcard d/*). Mệnh đề
for trên sẽ
tương đương với việc gán
files := $(wildcard a/* b/* c/* d/*)
Thí dụ2:
LINK_HEDERS := $(for header, $(LINK_HEADERS),
include/asm/$(header))
- $(call
<Tên_biến>,<Tham_Số1>,<Tham_Số2>,...)
: Khi thực thi
(expand) hàm này, make
sẽ tạm thời gán các giá trị
<Tham_Số1>,
<Tham_Số2>, ... vào thành giá trị của các
biến tham số mặc định
$1, $2, ... Sau đó nó sê tiến hành
khai triển giá trị của
biến <Tên_biến>. Do đó, nếu
<Tên_biến> có nội dung chứa đựng các giá trị
biến $1, $2 ,.. thì nó sẽ làm ảnh hưởng tới nội dung tương ứng của
<Tên_biến>
Thí dụ:
reverse = $(2) $(1)
file1 = $(call reverse,a,b)
kết quả là file1 = b a
- $(shell
<Lệnh> Hàm này sẽ tiến hành thực thi
<Lệnh> thông qua hệ vỏ và
hiển thị ngỏ ra sau khi thư thi sẽ được trả về
Thí du:
contents := $(shell cat file1)
A19.2.3.2.3 Cưỡng trị (override) và kháng cưỡng trị của biến khi gọi lệnh make
Giả sử có biến X đã được gán giá trị trong tập kiến tạo
là Makefile. Nay
người dùng muốn thay đổi giá trị mặc định này của
X mà không muốn điều chỉnh nội
dung tập tin kiến tạo thì thay vì gọi
make
<Các_Tham_Số> có thể dùng cách gọi
dạng
make X='<giá_trị>'
<Các_Tham_Số> để thay đổi nó. Biến
X trong thao tác này
được gọi là bị cưỡng trị. Trong trường hợp này, tham số
X sẽ được cài đặt
giá trị ban đầu mới (<giá_trị>) thay vì giá trị khởi động sẵn có trong Makefile.
Make chỉ hỗ trợ một số tham biến được phép cưỡng trị hầu hết là để dùng với các
tham số của các lệnh trình dịch (hường thấy nhất là biến
CFLAGS ).
Sau đây là
danh sách các biến (mặc định) quan trọng có thể bị cưỡng quyền:
-
ASFLAGS
: Biến thêm vào để dùng với hợp ngữ (assembler) (khi gọi với tập tin .s hay .S).
-
CFLAGS : Biến thêm vào để dùng với trình dịch C.
-
CXXFLAGS
: Biến thêm vào để dùng với trình dịch C++ .
-
COFLAGS
: Biến thêm vào để dùng với trình RCS co program.
-
CPPFLAGS
: Biến thêm vào để dùng với chương trình tiền xử lý C và các chương trình dùng
tới nó (như là trình dịch C và Fortran).
-
FFLAGS
Biến thêm vào để dùng với trình dịch Fortran.
-
GFLAGS Biến thêm vào để dùng với trình SCCS .
-
LDFLAGS
Biến thêm vào để dùng với trình dịch mà nó hỗ trợ gọi bộ liên kết (linker) `ld'.
-
LFLAGS Biến thêm vào để dùng với trình Lex.
-
YFLAGS Biến thêm vào để dùng với trình Yacc.
-
PFLAGS Biến thêm vào để dùng với trình dịch Pascal.
-
RFLAGS Biến thêm vào để dùng với trình dịch Fortran lên các chương trình Ratfor.
-
LINTFLAGS
Biến thêm vào để dùng với trình lint.
Thí dụ trong một Makefile ta đã định nghiã
CFLAGS = -g để dùng trong
lệnh cc -c $(CFLAGS) file1.c . Nhưng trong một số trường hợp người dùng có thể đổi
giá trị tham số này thành -g -O bằng cách gọi lệnh
make dạng:
make CFLAGS='-g -O'
hay là
make CFLAG:='-g -O' Nếu chỉ muốn dùng phép gán đơn giản
Gần như ngược lại trong
nhiều trường hợp rất đặc biệt, ở một số dòng cụ thể nào đó của Makefile, người
ta không cho phép lệnh make áp dụng thao tác cưỡng
trị
lên những dòng mà có biến có giá trị đặc biệt đó thì có thể dùng thao tác kháng
lại tính cưỡng trị thông qua định hướng cưỡng trị (overide directive)
theo cú pháp:
override
<Tên_biến>
= <Giá_trị>
Việc
thiết kế định hướng override là nhằm cho người viết makefile có thể điều chỉnh
các đối số mà người dùng gửi vào.
A19.2.3.3 Quy định: Đây là
thành phần quan trọng nhất của một Makefie. Như tên gọi các quy định sẽ quy
định cách thức cũng như sự phụ thuộc và cách tạo ra các đích tức là các tập tin, các
chương trình mà người lập trình muốn có.
Cú pháp chung của một quy định là:
<Các_Đích>: <Các_Tiền_Đề>
<Khối_Lệnh>
Trong đó:
- <Các_Đích> (targets) trường hợp tổng quát là tên của các tập tin ngăn cách
nhau bởi kí tự trống. Trường hợp đặc biệt
<Các_Đích> không phải là tên tập tin
sẽ là các đích giả (phony target)
- <Các_Tiền_Đề>
là tên của các tập tin mà các tập tin đích phụ thuộc vào.
Mọi lệnh cần thiết để tạo nên một tiền đề sẽ phải được thực thi hoàn toàn
trước khi bắt đầu thi hành các lệnh để tạo ra một tập tin đích tương ứng
- <Khối_Lệnh> luôn luôn bắt đầu với một kí tự TAB (kí tự nhây bước). Các
lệnh ngăn cách nhau bởi dấu chấm phẩy (;) sẽ được gởi ra trình bao để thi hành
Lưu ý:
Bắt đầu của dòng
<Khối_Lệnh> phải là một kí tự nhảy bước (<TAB>) và chỉ một mà
thôi
Danh sách tên các tập tin trong
<Các_Đích> và trong
<Các_Tiền_Đề> đều có thể dùng kí
tự phỏng định để miêu tả cùng một lúc nhiều tập tin vào trong danh sách. Tuy
nhiên, điều này không đúng khi dùng trong định nghiã biến (Thí dụ: định
nghiã
OBJS = *.o sẽ không được hiểu là các tập tin có phần nối dài
là .o mà để đạt điều này phải dùng hàm hỗ trợ cho nội dung biến tức là phải khai
báo thành
OBJS = $(wildcard *.o)
Các dòng quá dài có thể cắt ra thành nhiều dòng con ngắn hơn bằng cách chèn
dấu nghiêng về \ chỗ bị cắt ở cuối mỗi dòng con đó
Mỗi quy định sẽ cung cấp cho
make hai thông tin: Khi nào các đích đã bị
quá hạn (out-of-date) và làm thế nào để cập nhật chúng khi cần. Một đích
được xem là quá hạn khi nó cũ hơn bất kì một trong các tập tin tiền đề hoặc
khi nó không có mặt. Khối lệnh là phương thức để tái tạo các đích đã quá hạn
và đây là các lệnh mà hệ vỏ sẽ thi hành. Tất cả các lệnh để kiến tạo
Thí dụ:
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o
\
insert.o search.o files.o utils.o
A19.2.3.2.1: Quy định ngầm:
Để tiện lợi và giảm công sức cho việc viết mã, một số lớn các quy định được
cung cấp sẵn. Các quy định đó sẽ được
make tìm kiếm và áp dụng tự động
một khi make tìm thấy một tên tập tin trong makefile (như là tên của một đích
hay tên của một tiền đề) mà lại không có một quy định nào để chỉ ra cách tạo
nên tập tin đó.
Thí dụ: Một makefiel có nội dung:
CFLAGS = -c -O
LDFLAGS = -g
myprog: myprog.o file1.o
cc -o myprogram file1.o myprog.o
$(CFLAGS) $(LDFLAGS)
Vì trong tập tin có đề cập tới
file1.o nhưng lại không
chỉ ra làm thế nào để có file1.o nên make sẽ tự động tìm trong các quy định ngầm
của nó mà áp dụng (Trường hợp này dưạ trên mệnh lệnh là
cc nên make sẽ
tự tìm file1.c
và dịch ra thành file1.o)
A19.2.3.2.2 Các quy định ngầm thông dụng:
- Dịch các chương trình C :
<Tên>.o sẽ được tự động làm ra từ
<Tên>.c qua mệnh lệnh dạng:
$(CC) -c $(CPPFLAGS) $(CFLAGS)'
- Dịch các chương trình C++ : <Tên>.o sẽ được tự động làm ra từ
<Tên>.cc, <Tên>.cpp hay
<Tên>.C qua mệnh lệnh dạng:
$(CXX) -c $(CPPFLAGS)
$(CXXFLAGS)
- Dịch các chương trình Pascal :
<Tên>.o sẽ được tự động làm ra từ
<Tên>.p qua mệnh lệnh dạng:
$(PC) -c $(PFLAGS)
- Dịch các chương trình Fortran và Raftor : Tuỳ theo sự có mặt của tên mở
rộng, <Tên>.o sẽ được tự động làm ra:
-
.f
Qua mệnh lệnh dạng: $(FC) -c $(FFLAGS)
- .F Qua mệnh lệnh dạng: $(FC) -c $(FFLAGS) $(CPPFLAGS)
- .r Qua mệnh lệnh dạng:
$(FC) -c $(FFLAGS) $(RFLAGS)
-
Dịch các chương trình hợp ngữ và tiền xử lý : Tuỳ theo sự có mặt của
tên mở rộng, <Tên>.o sẽ được tự động làm ra:
-
.s từ trình dịch as qua mệnh lệnh dạng
$(AS) $(ASFLAGS)
-
.S từ các bộ tiền xử lý C, cpp qua mệnh lệnh dạng
$(CPP) $(CPPFLAGS)
-
Liên kết (link) một tập tin đối tượng:
<Tên> được tự động từ
<Tên>.o bằng cách
chạy linker (bộ liên kết) qua trình dịch C bằng mệnh lệnh
$(CC) $(LDFLAGS) <Tên>.o $(LOADLIBES) $(LDLIBS)
-
Xem thêm chi tiết hỗ trợ trong các tài liệu tra cứu của make (liệt kê trong
phần tài liệu tham khảo)
Trong đó có các biến mà make đã định nghiã sẵn.
Khi cần người viết makefile có thể định nghiã lại. Một số biến bao gồm
- AS
Trình dịch cho các tập tin hợp ngữ mặc định là as.
- CC
Trình dịch cho chương trình C mặc định là cc.
- CXX
Trình dịch cho chương trình C++ mặc định là g++.
- CPP
Chương trình để chạy bộ tiền xử lý C với các kết quả cho ra ngỏ ra chuẩn mặc
định là $(CC) -E.
- FC
Trình dịch cho chương trình Fortran và Ratfor mặc định là `f77'.
- PC
Trình dịch cho chương trình Pascal mặc định là pc
- ASFLAGS
Tham số cho hợp ngữ (khi gọi lên các tập tin.s hay .S).
- CFLAGS
Tham số cho trình dịch C.
- CXXFLAGS Tham số cho trình dịch C++.
- CPPFLAGS
Tham số cho bộ tiền xử lí C (C preprocessor) và các chương trình nào dùng nó (trình
dịch C và Fortran).
- FFLAGS
Tham số cho trình dịch Fortran.
- LDFLAGS
Tham số cho trình dịch khi nó gọi bộ liên kết (linker) ld.
- PFLAGS
Tham số cho trình dịch Pascal.
- RFLAGS Tham số cho trình dịch Fortran cho các chương trình Ratfor.
A19.2.3.3 Đích giả : Đich giả là đích mà thực sự tên của nó không phải là
tên của tập tin. Đích giả tạo ra nhằm chỉ để thực thi những thao tác riêng biệt
mà người viết makefile tạo ra.
Thi dụ1:
clean:
rm *.o *.tmp
Trong trường hợp này,
nếu không tồn tại tập tin với tên
clean, thì lệnh
rm của quy định
clean sẽ thi
hành mỗi lần nhập make clean. Tuy nhiên, nếu như tồn tại một tập tin với
tên clean thì do tập tin này không bị ảnh hưởng bởi các tiền đề (không có ìệt kê
tên của các tập tin tiền đề của quy định
clean) nên lệnh gọi bởi quy định này
(rm) sẽ không được thực thi vì
make cho rằng không có gì mới để cập nhật.
Để
tránh trường hợp này, có thể dùng đích giả đặc biệt cung cấp sẵn bởi make là
.PHONY. Với tên này thì quy định sẽ được thực thi bất kể tập tin
clean có
tồn tại hay không:
Thí dụ2: Thay vì dùng tên clean hãy dùng
.PHONY làm tên
đích
.PHONY:
clean
clean:
rm *.o *.tmp
Một ứng dụng khác của đích giả được trình bay trong các thí dụ sau:
Thí dụ3: thực thi các tập tin kiến tạo trong các thư mục con thông qua một
quy định bằng cách viết:
SUBDIRS = dir1 dir2 dir3
subdirs:
for dir in $(SUBDIRS); do \
$(MAKE) -C $$dir; \
done
Cách viết trên có điểm yếu là nếu xãy ra một lỗi bất kì trong quá trình thi
hành các makefile con trong vòng lặp thì
make (mẹ) vẩn tiếp tục chạy mà không
dừng lại ở vị trí lỗi. Đồng thời, sự hỗ trợ việc chạy song song các
quy định của make cũng không xãy ra (vì tất cả gom chung lại trong một quy định)
nên ảnh hưỏng đến hiệu năng của việc thực thi
make. Cách viết khắc phục hai
yếu điểm này là tận dụng đích giả
.PHONY
SUBDIRS = dir1 dir2 dir3
.PHONY: subdirs $(SUBDIRS)
subdirs: $(SUBDIRS)
$(SUBDIRS):
& $(MAKE) -C $@
dir1: dir3
Theo cách viết này thì dir1 sẽ không thể được chạy cho đến khi
dir3 được hoàn
tất
Lưu ý: Đích giả có thể "phụ thuộc" vào nhiều tiền đề. Mỗi tiền đề đến lượt nó
lại là một đích có lệnh thực thi riêng..
Thí dụ3:
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
với cách viết này thì người dùng có thể gọi để
tạo những đích khác nhau chẳng hạn như
make all để tạo ra
prog1 prog2 và prog3 .
Nhưng cũng có thể gọi make prog2 chỉ để tạo
prog2 mà thôi.
Ngoài ra, make còn cung cấp các tên đích có chức năng khác
ít phổ biến như .PRECIOUS,
.INTERMEDIATE, .SECONDARY, .IGNORE, .EXPORT_ALL_VARIABLES ,
... (xem thêm
taì liệu tra cứu của GNU)
A19.2.3.4 Các biến thông dụng
A19.2.3.4.1
VPATH: biến
VPATH là một biến toàn cục của
make dùng để
chỉ ra danh sách các thư mục mà make sẽ tìm kiếm. Biến này sẽ bao gồm tên
các thư mục có chứa các tập tin tiền đề mà chúng không có mặt trong thư mục hiện
hoạt; đồng thời nó cũng có thể là tên các thư mục chứa các tập tin đích của các
quy định. Một khi tên một tập tin dù là đích hay là tiền đề mà không có mặt
trong thư mục hiện hoạt thì make sẽ tìm kiếm nó thông qua biến
VPATH.
Tương tự như biến PATH trong BASH, VPATH nối tên các thư mục bởi các dấu :
Thí dụ:
VPATH = /src:../include
Xin xem thêm các biến khác như của make trong các
tài liệu tra cứu chính thức của
GNU về make
AA19.2.3.4.2 Biến tự động (Automatic Variable)
Việc tạo ra các biến tự dộng nhằm thoả mản nhu cầu tự động hoá các thao tác
và giảm thiểu mã cần viết cho makefile. Thí dụ người lập trình có bộ mã
nguồn C là A.c B.c, C.c., ... Việc thường thấy trong quá trình dịch là tạo ra
các tệp đối tượng tương ứng
A.o, B.o, C.o, ... Rồi sau cùng, có thể liên kết nó
thành chương trình thực dụng. Các biến này sẽ giúp tự động hoán đổi tên (trong
trường hợp này là việc thay đổi các tên mở rộng cuả các tập tin)
Lưu ý: Các biến tự động chỉ có giá trị và hữu hiệu nội trong các câu
lệnh. Nếu sử dụng các biến này bên ngoài các câu lệnh thì chúng chỉ có giá trị
rỗng.
Danh sách các biến tự động phổ biến bao gồm
- $@ : Có nội dung là tên tập tin đích của quy định tương ứng. Trong
trường hợp quy định là quy định dạng thức có nhiều đích thì
$@ sẽ là tên của
đích nào làm nguyên nhân cho sự thực thi mệnh lệnh của quy định
- $< : Có nội dung là tên của tập tin tiền lệ đầu tiên. Nếu đích
có lệnh thực thi của nó nằm trong một quy định ngầm, thì biến này sẽ là tập
tin tiền lệ đầu tiên được thêm vào bởi quy định ngầm
- $? : Tên của tất cả các tệp tiền lệ nào mới hơn tập tin đích và là
danh sách được viết ngăn cách các tên với nhau bởi các kí tự trắng. (Đây
là tên bao gồm cả tên thư mục chứa của các tệp tiền lệ)
- $^ : Tên của tất cả các tệp tiền lệ và là danh sách được viết
ngăn cách các tên với nhau bởi các kí tự trắng. Tuy nhiên, nó s bỏ đi
những tên nào trùng lặp
- $+ : Tương tự như $^, nhưng ở đây các tên trùng lặp được lặp lại
theo đúng thứ tự mà chúng được liệt kê ttrong makefile. Điều này hữu
dụng chủ yếu trong các lệnh liên kết, nó có ý nghiã khi mà nó lặp lại tên của
các thư viện trong một thứ tự riêng nào đó.
- $* : Phần gốc (stem) mà một quy định ngầm tương thích. Nếu
đích là dir/a.file1.b và dạng thức đích là
a.%.b thì phần gốc là
dir/file1. Phần gốc
này hữu dụng để tạo ra các tên của những tệp liên hệ. Trong một quy định dạng
thức tĩnh, phần gốc là bộ phân của tên tập tin mà nó tương thích với
% trong
đích đạng thức.
- $(@D) : Phần tên của một thư mục của một đích mà đã bị xoá đi phần bắt
đầu từ kí tự nghiên tới (slash) Thí dụ nếu giá trị của
$@ là dir/file1.o thì
$(@D) là dir. Khi mà
$@ không có dấu nghiên tới thì
$(@D) là dấu chấm .
- $(@F) : Phần tên ngắn của tên của một đích. Nếu giá trị của
$@ là
dir/file1.c thì $(@F)
có giá trị là file1.c
Lưu ý:
$(@F) tương đương với
$(notdir $@)
Thí dụ:( trích từ
http://www.opussoftware.com/tutorial/TutMakefile.htm )
1 CC=g++
2 CFLAGS=-c -Wall
3 LDFLAGS=
4 SOURCES=main.cpp hello.cpp factorial.cpp
5 OBJECTS=$(SOURCES:.cpp=.o)
6 EXECUTABLE=hello
7
8 all: $(SOURCES) $(EXECUTABLE)
9 $(EXECUTABLE): $(OBJECTS)
10 $(CC) $(LDFLAGS) $(OBJECTS) -o $@
11
12 .cpp.o:
13 $(CC) $(CFLAGS) $< -o $@
Dòng 10 tương đương với:
$(CC) $(LDFLAGS) $(OBJECTS) -o
$(EXECUTABLE)
Dòng 12: quy định áp dụng cho các tệp có cùng tên mở rộng
.cpp và .o lệnh thực
thi là (dòng 13): $(CC) $(CFLAGS) $< -o $@ (biên dịch từ
.cpp sang .o cho
mọi tập tin có tên mở rộng là .cpp)
Thí dụ2:
VPATH = /src:../headers
file1.o : file1.c defs.h hack.h
cc -c $(CFLAGS) $< -o $@
(dòng lệnh này tương đương với
cc -c $(CFLAGS) file1.c -o
file1.o )
A19.2.3.5 Các định hướng thông dụng của
make
A19.2.3.5.1 include : định hướng này có tác dụng lệnh cho
make ngưng đọc
tập tin kiến tạo hiện tại và chuyển sang đọc các tập tin kiến tạo khác trước
khi tiếp tục các dòng kế tiếp. Định hướng này dùng để tổ chức một chương trình
lớn bao gồm nhiều chương trình nhỏ trong nhiều thư mục và mỗi chương trình nhỏ
này có các makefile riêng (thí dụ như trường hợp makefile của mã nguồn nhạt
nhân)
Cú pháp:
include <Tên_các_tập_tin>
A19.2.3.5.2
export :
Dùng để xuất các biến
vào trong các tập tin kiến tạo con. Khi gặp lệnh này
make sẽ thêm vào tên và giá
trị của các biến được xuất ra vào trong môi trường của tập tin kiến tạo con. Đặc
biệt biến MAKFLAGS luôn luôn được xuất dùng chung cho các tập tin kiến tạo con.
Cú pháp:
- export
<Tên_Biến>
- export
<Tên_Biến>
= <Giá_trị>
- export
<Tên_Biến>
:= <Giá_trị>
Đặc biệt nếu chỉ viết:
export không có
<Tên_Biến> đi kèm có nghiã là
yêu cầu make xuất tất cả các biến hiện có trong tập tin kiến tạo hiện tại, ngoại
trừ các tên biến đã được ngăn chận bởi lệnh
unexport
A19.2.3.5.3
unexport: là định nhướng để ngăn không cho một tên biến được xuất ra
cho các tập tin kiến tạo con.
Cú Pháp:
unexport
<Tên_Biến>
A19.2.3.5.4 define
:
Dùng để định nghiã các dòng lệnh mà chúng đại diện bởi một tên biến và biến này
được định nghiã riêng biệt.
Cú pháp:
define
<Tên>
<Các_dòng_lệnh>
endef
Thí dụ: Định nghiã một khối lệnh hay được sử dụng:
define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef
Khi cần dùng trong một quy định sẽ gọi chẳng hạn
như :
file1.c :
file1.y
$(run-yacc)
A19.2.3.5.5
override :
Việc
thiết kế định hướng
override là nhằm cho người viết makefile có thể điều chỉnh
các đối số mà người dùng gửi vào. Có thể áp
dụng trong trường hợp ngăn người dùng gửi/gán các giá trị không mong muốn lên
các biến khi gọi lệnh make. (Xem thêm
phần A19.2.3.2.3) Với định hướng này thì
tùy theo cách gán giá trị ta có các cú pháp sau:
- override
<Tên_biến> =
<Giá_trị>
- override
<Tên_biến>
:= <Giá_trị>
- override
<Tên_biến> +=
<Giá_trị>
A19.2.3.5.6 Các định hướng điều kiện (conditional)
: Là định hướng khiến cho một phần của tập tin makefile được thực thi hay bỏ
qua tùy theo giá trị của các biến.
Trong thí dụ của phần A19.2.2
thì các dòng 17,19,23,24, và 25 mô tả các định hướng (dạng lệnh if)
Thí dụ:
ifeq ($(CC),gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif
Cú pháp:
-
<Định_Hướng_Điều_Kiện>
<Các_dòng_mã nguồn_nếu_đúng>
endif
- <Định_Hướng_Điều_Kiện>
<Các_dòng_mã nguồn_nếu_đúng>
else
<Các_dòng_mã nguồn_nếu_sai>
endif
- <Định_Hướng_Điều_Kiện1>
<Các_dòng_mã nguồn_nếu_đúng1>
else <Định_Hướng_Điều_Kiện2>
<Các_dòng_mã nguồn_nếu_đúng2>
else
<Các_dòng_mã nguồn_nếu_sai>
endif
Trong đó
<Định_Hướng_Điều_Kiệ> thuôc về
một trong các dạng-
ifeq
<String1>
<String2>
(hay còn viết dạng ifeq(<String1>,<String2>)
) Nếu <String1>
bằng <String2>
- ifneq
<String1>
<String2>
. Nếu <String1>
không bằng <String2>
- ifdef
<Tên_Biến>
Nếu <Tên_Biến>
đã được dịnh nghiã
- ifndef
<Tên_Biến>
Nếu <Tên_Biến> chưa được định nghiã
Lưu ý:
<String2> ở đây nếu không có mặt sẽ xem như là so sánh với chỗi kí tự trống
(Thí dụ
ifdef ($file2, ) )
A19.2.4 Các
quy định đặc biệt: Là các quy định mà thành phần
của nó có thể không có mặt hoặc có nhiều hơn 1 thành phần của cùng loại. Bao gồm
A19.2.4.1 Quy định thiếu lệnh và tiền đề :
Nếu một quy định A chỉ có tên mà không có mặt của dòng lệnh và các tiền đề
thì make sẽ tiến hành thực thi các quy định chứa quy định A như là tiền đề một
cách vô điều kiện.
Thí dụ:
clean: FORCE
rm -f *.o;
touch *
FORCE:
Ở đây FORCE thuộc vào trường hợp này và do đó khi gọi
make clean thì
dòng lệnh rm -f *.o; touch * sễ được thực thi mà không cần kiểm tra lại sự cập
nhật của các tập tin liên đới. Quy định viết cách này tưong đương với
đích giả .PHONY
A19.2.4.2 Quy định với đích là tập tin trống: Đây
lcũng là một cách trình bày khác tương tự với đích giả dùng để thực thi một số
lệnh cài sẵn khi được yêu cầu. Khác với
đích thông thường hay đích giả, tập tin đích
ở đây là hiện hữu nhưng nội dung của nó không cần được quan tâm và thường là tập
tin có độ dài bằng 0. Mục đích của tập tin này là để lưu giữ thời điểm
cuối cùng mà nó được truy cập để dùng trong mụch đích kiểm tra sự phụ thuộc hay
sự cập nhật hoá. Dòng lệnh của quy định này bao gồm lệnh
touch (để cài lại
dấu thời gian (time stamp) )
Thí dụ: Lệnh sau đây dùng để hiển thị tập tin mã nguồn nào có sự thay đổi
(cập nhật) mỗi khi gọi lệnh
make update
update: file1.c file2.c
cat $?
touch update
A19.2.4.3 Quy định đa đích : Cách viết này tương đương
với viết nhiều quy định và những quy định này có chung tiền đề và câu lệnh.
Hơn thế nữa, có thể vận dụng các
biến tự động và
các hàm để
tạo thêm sự linh hoạt.
Thí dụ: Quy định sau đây sẽ cho phép trích các dòng có
chứa chữ long của tập tin
mytext.dat viết vào trong tập tin
longprint khi gọi make
longprint; và tương ứng trích các dòng của
mytext.dat có chứa chữ
short vào trong
tập tin shortprint khi nhập lệnh
make shortprint
longprint, shortprint :
mytext.dat
grep -$(subst print,,$@)
mytext.dat > $@
A19.2.4.4 Quy định dạng thức (pattern rule) : Để mở rộng nội hàm hay thu
gọn cách viết cho tên (tập tin) đích và tên tiền đề của quy định người ta đặt
thêm một siêu kí tự là %. Kí tự nàu tương thích với dãy kí tự bất kì khác trống
(hãy so sánh với siêu kí tư * và, + trong dạng thức của lệnh
grep).
Thêm vào đó, nếu kí tự % có mặt trong tiền đề thì nó phải tương ứng với kí tự %
(nếu có trong đích)
Thí dụ khi viết
%o : %c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
Thì quy định trên sẽ chỉ ra cách tạo tập tin
<tên>.o từ tập tin cùng tên <Tên>.c
thông qua dòng lệnh
A19.2.4.5 Quy định dạng thức tĩnh (static patern rule) :
Cách viết này đưọc tạo ra
nhằm đặc biệt xử lý trường hợp nhiều đích và cấu trúc các tên tiền đề cho mỗi
đích dựa vào tên của đích tương ứng. Phương tiện này tổng quát hơn là các quy
định nhiều đích thông thường vì nó không đòi hỏi các đích phải có cùng tiền đề.
Các tên tiền đề chỉ phải tương tự nhau (mà không cần trùng lặp)
Cú pháp:
<Các_đích> :
<Dạng_thức_đích> :
<Các_dạng_thức_tiền_đề>
<Dòng_lệnh>
Trong đó,
- <Các_đích> là danh sách các đích mà quy định sẽ áp dụng lên. Tên các
tập đích này có thể dùng kí tự phỏng định.
- <Dạng_thức_đích> và
<Các_dạng_thức_tiền_đề> chỉ ra cách thức để xác định
tiền đề cho mỗi đích. Mỗi đích sẽ tương hợp với
<Dạng_thức_đích> để
trích ly ra một phần của tên đích, phần tương hợp này được gọi là gốc
(stem). Gốc sẽ được thay vào trong mỗi tên
<Các_dạng_thức_tiền_đề> để tạo ra các tên tiền đề. (Mỗi tên cho một dạng thức)
- Mỗi dạng thức thường chỉ chứa một kí tự
%. Khi
<Dạng_thức_đích> tương hợp
với một đích, thì % có thể ứng với bộ phận nào đó của tên đích này.
- Các tên tiền đề cho từng đích được tạo (tìm) ra bằng cách thay thế gốc lên
kí tự % trong mỗi dạng thức tiền đề. Trường hợp một dạng thức tiền đề
không chứa kí tự % thì khi đó, tiền đề này không đổi (tên) cho mọi đích.
Thí dụ:
objects = file1.o file2.o
all: $(objects)
$(objects): %.o: %.c
$(CC)
-c $(CFLAGS) $< -o $@
Đối với đích thứ nhất
file1.o thì gốc là
file1 tương ứng với kí tự
% trong %.o.
Do đó, % sẽ bị thay bởi
file1 trong dạng thức
%.c và câu lệnh sẽ tương đương với
$(CC) -c $(CFLAGS) file1.c -o file1.o
Tương tự, cho đích file2.o.
A19.2.5 Lưu ý về tập tin tiền đề:
Trong các makefile, nhiều quy định khi các tập
tin đối tượng (obj) phụ thuộc vào một số tập header. (thí dụ main.c sử
dụng def.h thông qua lệnh #include). Do đó trong tập tin kiến tạo sẽ viết thành
main.o: def.h.h
Quy định này nhằm cập nhật lại main.o mỗi lần def.h thay đổi. Trong các chưong
trình lớn các quy định như vậy sẽ đòi hỏi sự cẩn thận khi thêm hay bớt các câu
lệnh #include. Để tránh tình trạng khó khăn này, các trình dịch C thường
hỗ trợ tham só -M. Chẳng hạn như lệnh:
cc -M main.c
sẽ tạo thành :
main.o : def.h
mà không cần người viết makefile pải viết quy định đó ra. Điều này sẽ giảm thiểu
các khó khăn về tập tiền đề.
A19.3 Các Thí dụ:
Thí dụ1 Một trong những tập tin kiến tạo
phức tạp nhất là tập tin Makefile của hạt nhân Linux. Như một bài tập hãy tìm
tất cả các đích giả của tập tin này (HD: Dùng lệnh
grep để trích ly ra các
dòng PHONY từ Makefile) Từ đó biết được các đối số nào có thể dùng được để gọi
từ make (các đối số thông dụng là mrproper, clean, dep, modules,
modules_install, all)
Sử dụng để dịch một hạt nhân cho Linux:
Đối với một mã nguồn hạt nhân mới có thể dùng cách sau đây để cấu hình và cài
đặt lên máy theo thứ tự:;:
- make mrpoper : để xoá sạch và điều chỉnh lại các giá trị cấu hình mặc
định
- make menuconfig : để tái cấu hình (thêm bớt chức năng cho hạt nhân)
- make dep : tạo các mối liên hệ tương thuộc (nhiều hạt nhân nay đã bỏ qua
không dùng đích này)
- make bzImgaes (hay đôi khi là make compressed) đẻ tạo một tập tin ảnh
của hạt nhân
- make modules: để tạo ra toàn bộ các tệp thư viện và các bộ điều vận cần
thiết cho hạt nhân
- make modules_install: để cài đặt lên các thư mục hạt nhân vưà được tạo
ra vào đúng chỗ của nó
Sau đó có thể chép tập tin bzImage vào trong thư mục khởi động (thường là
/boot) và dùng lệnh mkinitrd để tạo ra ổ RAM khởi động (initial RAM
disk) và cuối cùng tạo ra một trình đơn con trong tập tin cấu hình của bộ
tâi khởi động (boot loader) thường là LiLo hay Grub.
Thí dụ2: Như một sự khởi đầu thí dụ sau đây trích từ trang
http://mrbook.org/tutorials/make/
của Hector Urtubia
Chương trình tính 5! bao gồm tổng cộng 4 tập tin mã nguồn (well! hơi nhiều
cho 1 chương trình bé như thế. Như đây là thí dụ minh họa)
Nội dụng
main.cpp:
#include <iostream.h>
#include "functions.h"
int main(){
print_hello();
cout << endl;
cout << "The factorial of 5 is " << factorial(5) << endl;
return 0;
}
Nội dung function.h:
void print_hello();
int factorial(int n);
Nội dung factorial.cpp
#include "functions.h"
int factorial(int n){
if(n!=1){
return(n * factorial(n-1));
}
else return 1;
}
Nội dung hello.cpp
#include <iostream.h>
#include "functions.h"
void print_hello(){
cout << "Hello World!";
}
Và nội dung Makefile:
CC=g++
CFLAGS=-c -Wall
LDFLAGS=
SOURCES=main.cpp hello.cpp factorial.cpp
OBJECTS=$(SOURCES:.cpp=.o)
EXECUTABLE=hello
all: $(SOURCES) $(EXECUTABLE)
$(EXECUTABLE): $(OBJECTS)
$(CC) $(LDFLAGS) $(OBJECTS) -o $@
.cpp.o:
$(CC) $(CFLAGS) $< -o $@
Thí dụ3: Makefile kết cấu với hai chương trình
và ba tập nguồn. Thí dụ này lấy từ
http://www.cs.toronto.edu/~reid/csc209/02f/examples/make/make.html của
Karen Reid năm 2002. Thí dụ có điều
chỉnh vài chi tiết so với thí dụ nguyên thủy có thể được điều chỉnh để xử lý
nhiều chưong trình trong Linux
Nội dung của
Makefile
CFLAGS = -g -Wall
CC = gcc
LIBS = -lm
INCLUDES =
OBJS = a.o b.o c.o
SRCS = a.c b.c c.c prog1.c prog2.c
HDRS = abc.h
all: prog1 prog2
# The variable $@ has the value of the target. In this case $@ = psort
prog1: prog1.o ${OBJS}
${CC} ${CFLAGS} ${INCLUDES} -o $@ prog1.o ${OBJS} ${LIBS}
prog2: prog2.o ${OBJS}
${CC} ${CFLAGS} -o $@ prog2.o ${OBJS} ${LIBS}
.c.o:
${CC} ${CFLAGS} ${INCLUDES} -c $<
depend:
makedepend ${SRCS}
clean:
rm *.o core *~
tar:
tar cf code.tar Makefile *.c *.h testfile1
print:
more Makefile $(HDRS) $(SRCS)
Nội dung abc.h:
void a();
void b();
void c();
Nội dung a.c:
#include <stdio.h>
void
a() {
printf("function a\n");
}
Nội dung b.c:
#include <stdio.h>
void
a() {
printf("function b\n");
}
Nội dung c.c:
#include <stdio.h>
void
a() {
printf("function c\n");
}
Nội dung prog1.c
#include <stdio.h>
#include <stdlib.h>
#include "abc.h"
int main()
{
printf("Program 1 \n");
a();
b();
c();
exit(0);
}
Nội dung prog2.c
#include <stdio.h>
#include <stdlib.h>
#include "abc.h"
int main()
{
printf("Program 2 \n");
a();
b();
c();
exit(0);
}