今天晚上學習了一下 makefile 的使用, 使用的工具是微軟的 nmake.
以下內容全部是摸索出來的, 官方文檔還沒來得及看.
1. 注釋
#
# Copyright (c) Microsoft Corporation. All rights reserved.
#
# You may only use this code if you agree to the terms of the Windows Research Kernel Source Code License agreement (see License.txt).
# If you do not agree to the terms, do not use the code.
#
2. 定義、使用變數
格式如下:
變數名=內容
$(變數名)
變數內容可以為多行, 在每一行後加上反斜線 '\' 即可.
變數也可以在 nmake 的參數裡定義, 格式為 'nmake 變數名=內容'.
同時還可以使用環境變數, 如 $(SYSTEMROOT)、$(LIB)、$(PATH) 等
還可以使用部分內建的變數, 如 $(CC)、$(AS)、$(MAKE)、$(MAKEDIR)
有趣的是, cc、as、make 均為 GNU 的名稱.
另外, 變數名區分大小寫, 環境變數名被全部變成了大寫.
舉例:
subargs=/$(MAKEFLAGS) ntos=$(MAKEDIR) pub=$(MAKEDIR)\..\..\public
3. 預先處理指令
格式:
!if 運算式
...
!else if defined(變數名)
...
!else
...
!endif
!ERROR 錯誤資訊
!include 另一個 makefile 檔案
注意運算式裡可以使用 &&、||、! 等邏輯運算子.
defined 運算子表示判斷某變數是否定義
舉例:
!if defined(x86) && !defined(amd64)
subargs = $(subargs) targ=i386 topobj=$(MAKEDIR)\BUILD\obji386
targ = i386
lmachine = x86
!else if !defined(x86) && defined(amd64)
subargs = $(subargs) targ=amd64 topobj=$(MAKEDIR)\BUILD\objamd64
targ = amd64
lmachine = amd64
!else
!ERROR Usage: nmake (x86=|amd64=) [clean]
!endif
4. 定義任務
一個 makefile 裡可以定義多個任務, 格式如下:
default:
____# commands here
task_a:
____# commands here
task_b:
____# commands here
default 表示預設任務.
通過 nmake [任務名], nmake 會執行不同的命令序列. 比如輸入命令 nmake task_b, 就會執行 task_b 下面的命令序列. 如果不指定任務名, 那麼會執行 default 任務.
注意任務名似乎不能為一個字元長, 我也不知道為什麼.
另外, 每個任務後面可以有多行命令, 但是每行前必須至少有一個空格或者一個 Tab, 否則會出錯. 由於百度會吃空格, 我在這裡用底線代替.
還可以定義任務之間的依賴關係, 格式如下:
task_c: task_a task_b
____# private commands here
nmake 會在 task_a、task_b 執行完畢後再執行 task_c.
注意依賴項必須和任務名寫在同一行, 中間用空格分隔.
只要可能, nmake 會自動根據任務的依賴關係找出一個合適的執行順序.
如果出現循環相依性, nmake 會報錯:
aa: bb
bb: aa
NMAKE : fatal error U1071: cycle in dependency tree for target 'aa'
Stop.
還可以一次定義多個任務:
task_d task_e task_f:
____@echo $(@R)
上面兩行定義了 task_d、task_e、task_f 三個任務, 均使用 echo 列印任務名.
echo 前面的 @ 是批處理命令列首碼, 這種用法從 DOS 開始就有了, 表示不輸出本命令列.
@R 參數表示任務名首碼, 在這裡就等於任務名
任務名首碼是指, 比如有一個任務叫做 'abcd.efgh', 那麼它的首碼是 'abcd'.
5. 變數內容替換
For example, 我們有下面兩條語句.
var_a = aa_bb_cc_dd_ee
var_b = head_$(var_a:_=_split_)_tail
大家猜猜 var_b 是什麼?
有了前面的知識, 大家不妨自己寫個 makefile 試一試:
var_a = aa_bb_cc_dd_ee
var_b = head_$(var_a:_=_split_)_tail
default:
____@echo $(var_b)
6. 一個常用的技巧
OBJ = obj$(targ)
modules = rtl config ex ob se mm ke ps io\iomgr io cache lpc dbgk raw fstub fsrtl wmi perf init
buildtargets = $(modules: =.build ).build
cleantargets = $(modules: =.clean ).clean
default: checktoolpath $(buildtargets) kernel
checktoolpath:
____checktoolarch $(lmachine)
kernel:
____cd $(MAKEDIR)\BUILD
________@$(MAKE) $(subargs) module=BUILD
____@cd $(MAKEDIR)
$(buildtargets):
____cd $(MAKEDIR)\$(@R)\BUILD
________@$(MAKE) $(subargs) module=$(@R)
____@cd $(MAKEDIR)
clean: $(cleantargets) clean0
clean0:
____-del $(MAKEDIR)\BUILD\$(OBJ)\** $(MAKEDIR)\BUILD\EXE\** /Q
$(cleantargets):
____cd $(MAKEDIR)\$(@R)\BUILD
________@$(MAKE) $(subargs) module=$(@R) clean
____@cd $(MAKEDIR)
簡單解釋一下.
modules = rtl config ex ob se mm ke ps io\iomgr io cache lpc dbgk raw fstub fsrtl wmi perf init
buildtargets = $(modules: =.build ).build
這兩行, 表示把 modules 裡面所有空格替換成 '.build ', 並在後面加上了 '.build'.
於是 buildtargets = rtl.build config.build (...省略...) init.build
$(buildtargets):
____cd $(MAKEDIR)\$(@R)\BUILD
________@$(MAKE) $(subargs) module=$(@R)
____@cd $(MAKEDIR)
這就定義了 rtl.build、config.build 等等任務, 我們把 rtl.build 展開看看:
rtl.build:
____cd $(MAKEDIR)\rtl\BUILD
________@$(MAKE) $(subargs) module=rtl
____@cd $(MAKEDIR)
default: checktoolpath $(buildtargets) kernel
這樣, nmake 會先 checktoolpath, 再依次 build 各個模組, 最後執行 kernel 任務.