《深入BREW開發》——第四章 工程管理(Make File)基礎

第四章 工程管理(Make File)基礎
       什麼是Make File?很多Windows的程序員都不知道這個東西,因爲那些Windows的IDE都爲我們做了這個工作,但我覺得要成爲一名專業的程序員Make File還是要懂。這就好像是我現在懂了C語言,但是我還要去了解編譯器的“內幕”一樣。Make File關係到了整個工程的編譯規則。對於一個大型的工程來說,其中的源文件不計其數,並分別按類型、功能、模塊分別放在若干個目錄中,這就需要我們能夠有一套方便好用的工具來管理這些源文件的編譯和鏈接。因爲,如果每次都將全部的文件編譯一遍,可能會大大的浪費開發時間,所以能夠識別哪些文件需要重新編譯的功能就很有必要了。幸好我們有Make File來幫助我們做這些,不然真是麻煩大了。
       Make File定義了一系列的規則來指定哪些文件需要重新編譯,甚至於進行更復雜的功能操作。Make File帶來的好處就是——“自動化編譯”,只需要一個make命令,整個工程完全自動編譯,極大的提高了軟件開發的效率。make是一個命令工具,是一個解釋Make File中指令的命令工具,一般來說,大多數的IDE都有這個命令,比如:Delphi的make,Visual C++的nmake,Linux下的GNU make等。可以看出,Make File已經是大多數工程管理的標準工具了。
4.1 Make File核心原理
       要掌握一個東西,就應該首先掌握它的核心思想,因此在這裏我們將首先來看看Make File的核心原理——依賴規則。看下面:
    target ... : prerequisites ...
            command
            ...
    target也就是一個目標文件,可以是Object File,也可以是執行文件。還可以是一個標籤(Label),對於標籤這種特性,在後續的“僞目標”中會有敘述。
    prerequisites就是,要生成那個target所需要的文件或是目標。或者理解爲,生成target需要prerequisites。
    command也就是make需要執行的命令。
       這是一個文件的依賴關係,也就是說,target這一個或多個的目標文件依賴於prerequisites中的文件,其生成規則定義在command中。說白一點就是說,prerequisites中如果有一個以上的文件比target文件更新的話,command所定義的命令就會被執行。這就是Make File的規則,也就是Make File中最爲核心的原理。
       完成一個簡單的演示例子,在這個例子中有main.c和command.c兩個源文件,一個defs.h頭文件,main.c和command.c中都包含defs.h頭文件。要求能夠輸出main.o和command.o兩個目標文件。定義的Make規則如下:
edit : main.o command.o
       cc –o edit main.o command.o
      
main.o : main.c defs.h
       cc –c main.c -o main.o
 
command.o : command.c defs.h
       cc –c command.c –o command.o
      
clean :
       rm main.o /
       command.o


       反斜槓(/)是換行的意思,告訴Make程序下面一行於這一行緊接着。clean是用來清除生成的main.o和command.o文件。請注意,在上面的每一行縮進中,Make要求必須使用Tab鍵,不能使用空格做爲縮進量。
       在這個例子中,目標文件(target)包含文件edit和中間目標文件(*.o),依賴文件(prerequisites)就是冒號後面的那些 .c 文件和 .h文件。每一個 .o 文件都有一組依賴文件,而這些 .o 文件又是目標文件edit的依賴文件。依賴關係本質上就是說明了目標文件是由哪些文件生成的。
       在定義好依賴關係後,後續的那一行定義瞭如何生成目標文件的操作系統命令,一定要以一個Tab鍵做爲開頭。記住,make並不管命令是怎麼工作的,他只管執行所定義的命令。make會比較targets文件和prerequisites文件的修改日期,如果prerequisites文件的日期要比targets文件的日期要新,或者target不存在的話,那麼,make就會執行後續定義的命令。
       這裏要說明一點的是,clean不是一個文件,它只不過是一個動作名字,有點像C語言中的標籤(Label)一樣。clean的冒號後什麼也沒有,因此make不會自動去查找clean的依賴性,也就不會自動執行其後所定義的命令。要執行其後的命令,就要在make命令後明顯得指出這個標籤(Label)的名字。這樣的方法非常有用,我們可以在一個Make File中定義不用的編譯或是和編譯無關的命令,比如程序的打包,程序的備份,等等。
4.2 Make的工作方式
       在默認的方式下,我們只輸入make命令,或者使用-f參數指定需要執行的Make File文件,然後make程序會進行如下操作:
    1、make會在當前目錄下找名字叫“Makefile”或“makefile”的文件(注意沒有擴展名)或通過-f指定的Make File文件。
    2、如果找到Make File文件,它會找文件中的第一個目標文件(target),在上面的例子中,他會找到“edit”這個文件,並把這個文件做爲最終的目標文件。
    3、如果edit文件不存在,或是edit所依賴的後面的 .o 文件的文件修改時間要比edit這個文件新,那麼,他就會執行後面所定義的命令來生成edit這個文件。
    4、如果edit所依賴的.o文件也存在,那麼make會在當前文件中找目標爲.o文件的依賴性,如果找到則再根據那一個規則生成.o文件。(這有點像一個堆棧的過程)
    5、最後當然是C文件和H文件都存在的啦,於是make會生成 .o文件,然後再用.o文件生成make的終極任務——目標文件edit了。
       這就是整個make的依賴性,make會一層又一層地去找文件的依賴關係,直到最終編譯出第一個目標文件。如果在找尋的過程中出現錯誤,比如最後被依賴的文件找不到,那麼make就會直接退出並報錯;而對於所定義命令的錯誤,或是編譯不成功,make根本不理。make只管文件的依賴性,即,如果在我找了依賴關係之後,冒號後面的文件還是不在,那麼對不起,我就不工作啦。
       通過上述分析,我們知道,像clean這種,沒有被第一個目標文件直接或間接關聯,那麼它後面所定義的命令將不會被自動執行,不過,我們可以顯示要求make執行。即命令——“make clean”,以此來清除所有的目標文件,以便於重新編譯。於是在上面的例子中,如果這個工程已被編譯過了,當我們修改了其中一個源文件,比如main.c,那麼根據我們的依賴性,我們的目標main.o會被重編譯(也就是在這個依性關係後面所定義的命令),於是main.o的文件也是最新的啦,於是main.o的文件修改時間要比edit要新,所以edit也會被重新生成了(詳見edit目標文件後定義的命令)。而如果我們改變了“defs.h”,那麼,main.o和command.o都會被重新編譯,那麼理所當然的,edit也會被重生成。
       爲了更加清晰的理清Make File的工作方式,現在我們總結一下Make File的各個工作階段及其執行的內容:
    1、讀入所有的Makefile
    2、讀入被include的其它Makefile
    3、初始化文件中的變量
    4、分析所有規則
    5、爲所有的目標文件創建依賴關係鏈
    6、根據依賴關係,決定哪些目標要重新生成
    7、執行生成命令
       1-5步爲第一個階段,6-7爲第二個階段。第一個階段中,如果定義的變量被使用了,那麼,make會把其展開在使用的位置。但make並不會完全馬上展開,make使用的是拖延戰術,如果變量出現在依賴關係的規則中,那麼僅當這條依賴被決定要使用了,變量纔會在其內部展開。
4.3 Make File實例
       或許您會覺得上面的例子也太簡單了,根本不值一提。那麼現在就來一個高階的應用來看看。還記得我們在編譯器基礎一節所作的Test4例子嗎,我們就在這個例子中添加一個makefile 文件(注意沒有擴展名)。
makefile – 一個簡單卻齊全的Make File實例
SHELL = sh
#=====================================================================
# Name:
#    makefile
#
# Description:
#    Makefile to build the $(TARGET) module.
#
#   The following nmake targets are available in this makefile:
#
#     all           - make .elf and .mod image files (default)
#     clean         - delete object directory and image files
#     filename.o    - make object file
#     filename.mix - make C and ASM mix file
#
#   The above targets can be made with the following command:
#
#     make  [target]
#
# Assumptions:
#    1. The ARM ADS 1.0.1 or higher tools are installed
#    2. The run environment of make toos is exist in this OS
#
#----------------------------------------------------------------------------
#=====================================================================
TARGET        = Test4#
SCLFILE       = system.scl#
C_OBJS        = mainapp.o raminit.o
A_OBJS        = bootblock.o
OBJS          = $(C_OBJS) $(A_OBJS)
 
APP_PATH      = .#
 
# set the search path
vpath %.c $(APP_PATH)
vpath %.s $(APP_PATH)
 
#-------------------------------------------------------------------------------
# Target file name and type definitions
#-------------------------------------------------------------------------------
 
EXETYPE    = elf#                # Target image file format
MODULE     = bin#                # Binary module extension
 
#-------------------------------------------------------------------------------
# Target compile time symbol definitions
#-------------------------------------------------------------------------------
 
ARMASM     = -D_ARM_ASM_#
 
#-------------------------------------------------------------------------------
# Software tool and environment definitions
#-------------------------------------------------------------------------------
ARMBIN = $(ARMHOME)/Bin#
ARMBIN := $(subst /,/,$(ARMBIN))#
 
ARMCC   = $(ARMBIN)/armcc#       # ARM ADS ARM 32-bit inst. set ANSI C compiler
ASM     = $(ARMBIN)/armasm#      # ARM ADS assembler
LD      = $(ARMBIN)/armlink#     # ARM ADS linker
HEXTOOL = $(ARMBIN)/fromelf#     # ARM ADS utility to create hex file from image
 
OBJ_CMD    = -o#                 # Command line option to specify output filename
 
#-------------------------------------------------------------------------------
# Processor architecture options
#-------------------------------------------------------------------------------
 
CPU = -cpu ARM7TDMI#             # ARM7TDMI target processor
 
#-------------------------------------------------------------------------------
# ARM Procedure Call Standard (APCS) options
#-------------------------------------------------------------------------------
APCS = -apcs /noswst/interwork
 
#-------------------------------------------------------------------------------
# Additional compile time error checking options
#-------------------------------------------------------------------------------
 
CHK = -fa#                       # Check for data flow anomolies
 
#-------------------------------------------------------------------------------
# Compiler output options
#-------------------------------------------------------------------------------
 
OUT = -c#                        # Object file output only
 
#-------------------------------------------------------------------------------
# Compiler/assembler debug options
#-------------------------------------------------------------------------------
 
DBG = -g#                        # Enable debug
 
#-------------------------------------------------------------------------------
# Compiler optimization options
#-------------------------------------------------------------------------------
 
OPT = -Ospace -O2#               # Full compiler optimization for space
 
#-------------------------------------------------------------------------------
# Compiler code generation options
#-------------------------------------------------------------------------------
 
END = -littleend#                # Compile for little endian memory architecture
ZA = -zo#                       # LDR may only access 32-bit aligned addresses
 
CODE = $(END) $(ZA)#
 
#-------------------------------------------------------------------------------
# Include file search path options
#-------------------------------------------------------------------------------
 
INC = -I.#
 
#-------------------------------------------------------------------------------
# Linker options
#-------------------------------------------------------------------------------
 
LINK_CMD = -o#                    # Command line option to specify output file
                                  # on linking
LIST     = -list $(TARGET).map#   # Direct map and info output to file
INFO     = -elf -map -info sizes,totals,veneers,unused
LINK_OPT = -scatter $(SCLFILE)#   # Use scatter load description file
#-------------------------------------------------------------------------------
# HEXTOOL options
#-------------------------------------------------------------------------------
 
BINFORMAT = -bin#
OUTPUT    = -output#
 
#-------------------------------------------------------------------------------
# Compiler flag definitions
#-------------------------------------------------------------------------------
 
CFLAGS = $(OUT) $(INC) $(CPU) $(APCS) $(CODE) $(CHK) $(DBG) $(OPT)
AFLAGS = $(CPU) $(APCS) $(INC)
 
#----------------------------------------------------------------------------
# Default target
#----------------------------------------------------------------------------
.PHONY : all
all : startup $(TARGET).$(MODULE) complete
 
.PHONY : startup
startup:
       @echo ---------------------------------------------------------------
       @echo Compile startup
       @echo ---------------------------------------------------------------
      
.PHONY : complete
complete:
       @echo ---------------------------------------------------------------
       @echo All have been done
       @echo ---------------------------------------------------------------
      
#----------------------------------------------------------------------------
# Clean target
#----------------------------------------------------------------------------
 
# The object subdirectory, target image file, and target hex file are deleted.
.PHONY : clean
clean :
       @echo ---------------------------------------------------------------
       @echo CLEAN
       -rm -f *.o
       -rm -f *.i
       -rm -f *.dep
       -rm -f $(TARGET).$(EXETYPE)
       -rm $(TARGET).$(MODULE)
       -rm $(TARGET).map
       @echo ---------------------------------------------------------------
        #=====================================================================
#                           DEFAULT SUFFIX RULES
#=====================================================================
SRC_FILE = $(@F:.o=.c)#        # Input source file specification
OBJ_FILE = $(OBJ_CMD) $(@F)#   # Output object file specification
 
.SUFFIXES :
.SUFFIXES : .o .c .s .mix .dep
 
#--------------------------------------------------------------------------
# C code inference rules
#----------------------------------------------------------------------------
%.o:%.c
       @echo ---------------------------------------------------------------
       @echo OBJECT $(@F)
       $(ARMCC) $(CFLAGS) $(OBJ_FILE) $(SRC_FILE)
       @echo ---------------------------------------------------------------
      
%.mix:%.c
       @echo ---------------------------------------------------------------
       @echo OBJECT $(@F)
       $(ARMCC) -S -fs $(CFLAGS) $(INC) $(OBJ_FILE) $<
       @echo ---------------------------------------------------------------
 
#-------------------------------------------------------------------------------
# Assembly code inference rules
#-------------------------------------------------------------------------------
%.o:%.s
       @echo ---------------------------------------------------------------
       @echo OBJECT $(@F)
       $(ARMCC) -ansic -E $(AFLAGS) $(ARMASM) $< > $*.i
       $(ASM) $(AFLAGS) $(OBJ_FILE) $*.i
       @echo ---------------------------------------------------------------
 
#-------------------------------------------------------------------------------
# Depend file inference rules
#-------------------------------------------------------------------------------
%.dep:%.c
       @echo ---------------------------------------------------------------
       $(ARMCC) -ansic -M $< > $*.dep
       @echo ---------------------------------------------------------------
 
#=====================================================================
#                           MODULE SPECIFIC RULES
#=====================================================================
 
#----------------------------------------------------------------------------
# Lib file targets
#----------------------------------------------------------------------------
 
$(TARGET).$(MODULE) : $(TARGET).$(EXETYPE)
       @echo ---------------------------------------------------------------
       @echo TARGET $@
       $(HEXTOOL) $(TARGET).$(EXETYPE) $(BINFORMAT) $(OUTPUT) $(TARGET).$(MODULE)
 
$(TARGET).$(EXETYPE) : $(OBJS)
       @echo ---------------------------------------------------------------
       @echo TARGET $@
       $(LD) $(INFO) $(LINK_OPT) $(LIST) $(LINK_CMD) $(TARGET).$(EXETYPE) $(OBJS)
 
# --------------------------------------------
# C file dependency list
# --------------------------------------------
ifeq ($(MAKECMDGOALS),)
-include $(C_OBJS:.o=.dep)
else
ifeq ($(filter all,$(MAKECMDGOALS)),all)
-include $(C_OBJS:.o=.dep)
endif
endif
 
# --------------------------------------------
# ASM file dependency list
# --------------------------------------------
bootblock.o : bootblock.s
bootblock.o : system.h


 
       可不要小看這短短的200多行Make File啊,正所謂“麻雀雖小,五臟俱全”,它可是該有的都有了。我們可以對它進行擴展,生成一個非常自動化的Make系統來。接下來我將逐行解釋這個文件中的內容。
       最頂行的SHELL = sh是告訴make程序,使用的SHELL程序是sh.exe。在本例中使用的是cygwin環境下的shell程序和命令的。cygwin是用來在windows操作系統下模擬unix命令和操作方式的實用程序,通過Internet您可以很容易的找到這個程序以及GNU Make程序,不過ARM編譯器就需要我們自己購買了。這是我很對不起各位讀者的地方,雖然這個例子是在我的機器上測試通過的,但是各位讀者朋友們要搭建這個環境就沒那麼容易了。這個環境中主要有三個要素:cygwin應用、GNU Make以及ARM編譯器。
       接下來是註釋並說明這個文件的,在Make File中“#”號表示後面緊接的是註釋,作用於從“#”號之後的一行,千萬不要幻想使用“#”註釋多行語句,在Make File中沒有可以註釋多行的語句。接下來就進入了正式的Make File執行語句了。
4.3.1變量的定義
TARGET       = Test4#
SCLFILE       = system.scl#
C_OBJS        = mainapp.o raminit.o
A_OBJS        = bootblock.o
OBJS          = $(C_OBJS) $(A_OBJS)
 
APP_PATH      = .#


       這裏面定義了5個Make File變量,爲了能夠容易區分變量,通常在Make File中都使用全大寫字符串做爲變量名,當然這不是必須的,只是一個約定俗成的習慣而已。如果需要使用定義的變量則使用$()來飲用這個變量的值,如上面的OBJS = $(C_OBJS) $(A_OBJS)一句中就使用了C_OBJS和A_OBJS兩個變量。如果我們要使用真實的“$”字符,那麼我們需要用“$$”來表示。如果引用的變量沒有定義,不要害怕,Make不會報錯,Make會認爲這個變量是空的。這裏也提醒各位讀者注意,要認真的使用變量名,不要弄錯了,因爲Make不會報告未定義變量的任何錯誤。
       Make File中的變量定義是不分先後的,比如上面的C_OBJS和A_OBJS可以定義在OBJS變量之後,所得到的結果是一樣的。這是一個十分具有迷惑性的方式,我不建議使用這種方式,雖然它可能讓我們覺得很“自由”。爲了避免這種情況,可以使用“:=”號來爲變量賦值,這種方式只能使用已經定義好的變量,如果變量在前面沒有定義,則使用空值來代替。例如:
       A = $(B)
       B = debug
       此時,A的值是debug。如果使用
       A := $(B)
       B = debug
       則此時A的值是空,因爲在A變量之前B還沒有定義。與之對應的賦值操作符還有“?=”,它的作用是首先判斷這個變量有沒有在前面被定義過,如果沒有定義則給這個變量賦值,否則使用已經定義的值。
       在這裏,OBJS變量也可以採用如下的方式實現:
       OBJS = $(C_OBJS)
OBJS += $(A_OBJS)
其中使用“+=”來進行變量的連接。
       這六個變更的意義依次是:目標文件的名稱、鏈接器使用的Scatter-Loading描述文件名、C語言的.o文件、彙編語言的.o文件、全部.o文件和.c文件的路徑。
4.3.2搜索路徑
# set the search path
vpath %.c $(APP_PATH)
vpath %.s $(APP_PATH)


       vpath是Make用來設置指定文件類型搜索路徑的,上面兩句分別設置了.c和.s的源文件搜索路徑。在大型項目中,由於多個路徑都存在源文件(.c),因此可以設置多個同擴展名的vpath,並按照所設置路徑的先後順序進行源文件的搜索。注意,這裏可給我們提供了一種設置路徑優先級的方法。例如,在A路徑中有一個main.c,在B路徑中也有一個main.c,那麼我們就可以通過優先設置那一個路徑的vpath %.c,來決定優先編譯哪個路徑的mian.c了。這種設置方法有的時候是很有用的,各位讀者可以根據系統各自不同的需求來使用。
4.3.3編譯器變量的定義
#-------------------------------------------------------------------------------
# Target file name and type definitions
#-------------------------------------------------------------------------------
 
EXETYPE    = elf#                # Target image file format
MODULE     = bin#                # Binary module extension
 
#-------------------------------------------------------------------------------
# Target compile time symbol definitions
#-------------------------------------------------------------------------------
 
ARMASM     = -D_ARM_ASM_#
 
#-------------------------------------------------------------------------------
# Software tool and environment definitions
#-------------------------------------------------------------------------------
ARMBIN = $(ARMHOME)/Bin#
ARMBIN := $(subst /,/,$(ARMBIN))#
 
ARMCC   = $(ARMBIN)/armcc#       # ARM ADS ARM 32-bit inst. set ANSI C compiler
ASM     = $(ARMBIN)/armasm#      # ARM ADS assembler
LD      = $(ARMBIN)/armlink#     # ARM ADS linker
HEXTOOL = $(ARMBIN)/fromelf#     # ARM ADS utility to create hex file from image
 
OBJ_CMD    = -o#                 # Command line option to specify output filename


       在這段代碼中主要定義了各種使用的變量,EXETYPE和MODULE分別是鏈接生成的文件的擴展名;ARMASM是給編譯器使用的符號定義變量;ARMBIN是ARM編譯器所在文件路徑;接下來的是使用的ARM編譯器可執行程序名。這裏主要說明一下“ARMBIN := $(subst /,/,$(ARMBIN))#”語句,subst是Make字符串替換函數,原形是:
       $(subst <from>,<to>,<text>)
       意義是將<text>字符串中的<from>字符串替換成<to>字符串。在這裏是將“/”替換成“/”來表示路徑分割符,在Make File中將“/”做爲路徑的分割符的。
       在緊接着的代碼都是定義編譯器和鏈接器命令的變量,這裏就不一一的介紹了,有興趣的讀者可以參考ARM編譯器的幫助文檔。
4.3.4依賴規則
#----------------------------------------------------------------------------
# Default target
#----------------------------------------------------------------------------
.PHONY : all
all : startup $(TARGET).$(MODULE) complete
 
.PHONY : startup
startup:
       @echo ---------------------------------------------------------------
       @echo Compile startup
       @echo ---------------------------------------------------------------
      
.PHONY : complete
complete:
       @echo ---------------------------------------------------------------
       @echo All have been done
       @echo ---------------------------------------------------------------


       這裏的all是整個Make File中的第一個依賴關係,根據前面的敘述我們知道,make會從這個依賴關係開始執行。all是make的一個目標,由於all只是一個標籤,並沒有與之相關的文件,因此我們稱all爲一個“僞目標”。“.PHONY”用來顯式地指明一個目標是“僞目標”,目的是向make說明不管是否有這個文件,這個目標都是“僞目標”。通常在沒有和“僞目標”同名的文件情況下,是不需要使用“.PHONY”來顯式聲明一個“僞目標”的。
       我們可以看到all目標有三個子目標:startup、$(TARGET).$(MODULE)和complete。make將按照這個順序執行三個子目標。startup和complete兩個目標顯示了開始和結束的提示信息,echo是在屏幕上輸出信息的命令。默認情況下make將顯示其執行的命令,在命令前面加上“@”符號將不會輸出命令信息。如startup部分如果不加“@”則會有如下結果輸出:
       echo ---------------------------------------------------------------
       ---------------------------------------------------------------
       echo Compile startup
       Compile startup
       echo ---------------------------------------------------------------
       ---------------------------------------------------------------
 
       接下來讓我們看看$(TARGET).$(MODULE)規則:
#----------------------------------------------------------------------------
# Lib file targets
#----------------------------------------------------------------------------
 
$(TARGET).$(MODULE) : $(TARGET).$(EXETYPE)
       @echo ---------------------------------------------------------------
       @echo TARGET $@
       $(HEXTOOL) $(TARGET).$(EXETYPE) $(BINFORMAT) $(OUTPUT) $(TARGET).$(MODULE)
 
$(TARGET).$(EXETYPE) : $(OBJS)
       @echo ---------------------------------------------------------------
       @echo TARGET $@
       $(LD) $(INFO) $(LINK_OPT) $(LIST) $(LINK_CMD) $(TARGET).$(EXETYPE) $(OBJS)


       $(TARGET).$(MODULE)依賴於$(TARGET).$(EXETYPE),當$(TARGET).$(EXETYPE)文件比$(TARGET).$(MODULE)文件要新的時候,就會執行$(TARGET).$(MODULE)命令,執行生成$(TARGET).$(MODULE)文件的命令。
       $(TARGET).$(EXETYPE)依賴於$(OBJS)中包含的各個.o文件。如果有任何一個.o文件比$(TARGET).$(EXETYPE)文件要新,則執行鏈接命令。$@表示目標文件集,在本例中也就是$(TARGET).$(EXETYPE) : $(OBJS)中的$(TARGET).$(EXETYPE)。
       接下來就到了.o文件的以來關係了,這部分是一個Make File的主要處理的部分,在正式介紹它們之前我們先看下面的定義:
#=====================================================================
#                           DEFAULT SUFFIX RULES
#=====================================================================
SRC_FILE = $(@F:.o=.c)#        # Input source file specification
OBJ_FILE = $(OBJ_CMD) $(@F)#   # Output object file specification
 
.SUFFIXES :
.SUFFIXES : .o .c .s .mix .dep


       SRC_FILE = $(@F:.o=.c)定義了輸入的源文件文件名,$(@F)表示$@中的文件名部分,
$(@F:.o=.c)就是將$(@F)中的.o替換成.c,從而得到了源文件名。注意,這一句需要在執行命令的時候纔會進行真正的展開,因此在這裏只是定義了一個變量。OBJ_FILE定義了一個輸出目標.o文件的變量。
       .SUFFIXES是用來通知make新的擴展名。.SUFFIXES:語句是清空默認的文件擴展名識別。.SUFFIXES : .o .c .s .mix .dep聲明瞭五個文件擴展名:
       .o    —— C編譯器生成的文件
       .c    —— C語言源文件
       .s    —— 彙編語言源文件
       .dep —— .c和.s文件的頭文件依賴關係
       .mix —— 由.c生成C和彙編語句混合文件的擴展名
#--------------------------------------------------------------------------
# C code inference rules
#----------------------------------------------------------------------------
%.o:%.c
       @echo ---------------------------------------------------------------
       @echo OBJECT $(@F)
       $(ARMCC) $(CFLAGS) $(OBJ_FILE) $(SRC_FILE)
       @echo ---------------------------------------------------------------
      
%.mix:%.c
       @echo ---------------------------------------------------------------
       @echo OBJECT $(@F)
       $(ARMCC) -S -fs $(CFLAGS) $(INC) $(OBJ_FILE) $<
       @echo ---------------------------------------------------------------


       上面定義了.o和.mix文件的默認依賴關係,在這裏固定了它們和源文件.c之間的關係。“%”號是通配符,%.o代表任何一個.o文件。%.o:%.c指定相應的.o文件依賴於.c文件。$<表示依賴的文件名,這裏是指%.c所匹配的文件。.mix文件是將C語句“翻譯”成對應的彙編語句,這個文件可以用來分析C源文件的效率。
#-------------------------------------------------------------------------------
# Assembly code inference rules
#-------------------------------------------------------------------------------
%.o:%.s
       @echo ---------------------------------------------------------------
       @echo OBJECT $(@F)
       $(ARMCC) -ansic -E $(AFLAGS) $(ARMASM) $< > $*.i
       $(ASM) $(AFLAGS) $(OBJ_FILE) $*.i
       @echo ---------------------------------------------------------------


       上面定義了.o文件與.s文件的依賴關係。$(ARMCC) -ansic -E $(AFLAGS) $(ARMASM) $< > $*.i語句的意思是將彙編語句首先使用編譯器進行預編譯處理,這樣處理的好處是我們可以在彙編文件中使用C的預編譯指令了,比如#define和#include等。處理之後的文件通過“>”重定位符號輸出到$*.i文件中。$*表示目標的沒有擴展名部分,比如dir/main.o :mian.c則$*等於dir/main部分。
4.3.5 .Dep文件
#-------------------------------------------------------------------------------
# Depend file inference rules
#-------------------------------------------------------------------------------
%.dep:%.c
       @echo ---------------------------------------------------------------
       $(ARMCC) -ansic -M $< > $*.dep
       @echo ---------------------------------------------------------------


       上面定義了.dep文件的依賴關係。.dep文件是由編譯器輸出的符合make依賴規則的.o文件與.h文件的依賴性規則定義的文本文件。這裏就是生成.dep文件的命令部分。在Make File中會通過include命令包含.dep文件(include還可以包含其他的Make File文件),從而引入目標文件與頭文件之間的依賴關係,典型的.dep文件內容如下:
mainapp.o:      mainapp.c
mainapp.o:      system.h


       下面來看看怎樣包含.dep文件:
# --------------------------------------------
# C file dependency list
# --------------------------------------------
ifeq ($(MAKECMDGOALS),)
-include $(C_OBJS:.o=.dep)
else
ifeq ($(filter all,$(MAKECMDGOALS)),all)
-include $(C_OBJS:.o=.dep)
endif
endif


       ifneq(ifeq)是Make File的條件判斷語句,在Make File中有六個條件判斷的關鍵字:ifeq、ifneq、ifdef、ifndef、else和endif。ifeq的意思表示條件語句的開始,並指定一個條件表達式,表達式包含兩個參數,以逗號分隔,表達式以圓括號括起。else表示條件表達式爲假的情況。endif表示一個條件語句的結束,任何一個條件表達式都應該以endif結束。ifeq是判斷條件相等,ifneq是判斷條件不等。ifdef/ifndef是判斷變量是/否被定義。
       $(filter all,$(MAKECMDGOALS))是從變量MAKECMDGOALS中過濾出all字符串。MAKECMDGOALS變量中會存放我們所指定的終極目標的列表,如果在命令行上,我們沒有指定目標,那麼這個變量是空值。這個終極目標是指我們在調用make命令時輸入的,例如執行make clean,則MAKECMDGOALS變量值就是clean。filter是make的過濾函數,與之對應的還有filter –out的反過濾函數,它們的函數原型如下:
       $(filter <pattern...>,<text>)
       $(filter-out <pattern...>,<text>)
       filter函數以<pattern>模式過濾<text>字符串中的單詞,保留符合模式<pattern>的單詞。filter-out函數以<pattern>模式過濾<text>字符串中的單詞,去除符合模式<pattern>的單詞。
       這裏ifeq ($(MAKECMDGOALS),)判斷用戶指定的目標是空時則執行include命令。否則在目標是all的時候也執行include命令。如果include的文件不存在,make會查找該文件的依賴關係,在這裏則產生了.dep的依賴關係並執行生成.dep文件的命令。在命令前面加“-”表示忽略執行命令時產生的錯誤。
       這部分就完成了一個由include觸發的.dep的生成.dep到包含.o與.h依賴關係的整個過程,具有很強的參考價值。
4.3.6清除規則
#----------------------------------------------------------------------------
# Clean target
#----------------------------------------------------------------------------
 
# The object subdirectory, target image file, and target hex file are deleted.
.PHONY : clean
clean :
       @echo ---------------------------------------------------------------
       @echo CLEAN
       -rm -f *.o
       -rm -f *.i
       -rm -f *.dep
       -rm -f $(TARGET).$(EXETYPE)
       -rm $(TARGET).$(MODULE)
       -rm $(TARGET).map
       @echo ---------------------------------------------------------------
       


       這裏就是調用了一些前面講過的命令,清除生成的文件。rm是一個Shell程序,不同的Shell會有不同的命令,各位讀者可以根據自己不同的運行環境進行相應的修改。
4.4 Make File符號說明
       在上一節中介紹了大部分Make File中的符號的意義和用法,這裏爲了方便各位查找,將最常用的一些Make File符號列舉出來。
4.4.1關鍵詞
關鍵詞 用途
define 定義一個“數據包”,是用enddef做結尾,可以包含多行的命令。
ifeq/ifneq 條件判斷,可以搭配else使用,endif結尾。原型:ifeq(Arg1,Arg2)。
ifdef/ifndef 變量是否定義的條件判斷,可以搭配else使用,endif結尾。原型:ifdef Var。
= 變量賦值語句。如果右值包含另一個變量,則可以在後面定義這個變量。
:= 變量賦值語句。如果右值包含另一個變量,則只能引用已定義的變量。
?= 條件賦值語句。如果此變量未定義才重新賦值。
+= 爲當前變量追加內容。
% 通配符
vpath 設置搜索路徑,原型vpath %.x <path>,x表示文件擴展名
/ 換行符
@ 放在命令前面隱藏命令輸出
- 放在命令前面忽略命令錯誤
: 依賴規則定義符,使用方式:目標:依賴
override 用來指示即便此變量是由make的命令行參數設置的,也使用新的賦值。因爲默認情況下Makefile中對這個變量的賦值會被忽略。
.PHONY 顯式聲明僞目標
.SUFFIXES 聲明擴展名


4.4.2 Make函數
       按功能字符串、函數名排序:
函數原型 描述
$(subst <from>,<to>,<text>) 把字串<text>中的<from>字符串替換成<to>。
$(patsubst <pattern>,<replacement>,<text>) 查找<text>中的單詞(單詞以“空格”、“Tab”或“回車”“換行”分隔)是否符合模式<pattern>,如果匹配的話,則以<replacement>替換。這裏,<pattern>可以包括通配符“%”,表示任意長度的字串。如果<replacement>中也包含“%”,那麼,<replacement>中的這個“%”將是<pattern>中的那個“%”所代表的字串。(可以用“/”來轉義,以“/%”來表示真實含義的“%”字符)
$(strip <string>) 去掉<string>字串中開頭和結尾的空字符。
$(findstring <find>,<in>) 在字串<in>中查找<find>字串。
$(filter <pattern...>,<text>) 以<pattern>模式過濾<text>字符串中的單詞,保留符合模式<pattern>的單詞。可以有多個模式。
$(filter-out <pattern...>,<text>) 以<pattern>模式過濾<text>字符串中的單詞,去除符合模式<pattern>的單詞。可以有多個模式。
$(sort <list>) 給字符串<list>中的單詞排序(升序)。
$(word <n>,<text>) 取字符串<text>中第<n>個單詞。(從一開始)
$(wordlist <s>,<e>,<text>) 從字符串<text>中取從<s>開始到<e>的單詞串。<s>和<e>是一個數字。
$(words <text>) 統計<text>中字符串中的單詞個數。
$(firstword <text>) 取字符串<text>中的第一個單詞。
$(dir <names...>) 從文件名序列<names>中取出目錄部分。目錄部分是指最後一個反斜槓(“/”)之前的部分。如果沒有反斜槓,那麼返回“./”。
$(notdir <names...>) 從文件名序列<names>中取出非目錄部分。非目錄部分是指最後一個反斜槓(“/”)之後的部分。
$(suffix <names...>) 從文件名序列<names>中取出各個文件名的後綴。
$(basename <names...>) 從文件名序列<names>中取出各個文件名的前綴部分。
$(addsuffix <suffix>,<names...>) 把後綴<suffix>加到<names>中的每個單詞後面。
$(addprefix <prefix>,<names...>) 把前綴<prefix>加到<names>中的每個單詞後面。
$(join <list1>,<list2>) 把<list2>中的單詞對應地加到<list1>的單詞後面。如果<list1>的單詞個數要比<list2>的多,那麼,<list1>中的多出來的單詞將保持原樣。如果<list2>的單詞個數要比<list1>多,那麼,<list2>多出來的單詞將被複制到<list2>中。
$(foreach <var>,<list>,<text>) 把參數<list>中的單詞逐一取出放到參數<var>所指定的變量中,然後再執行<text>所包含的表達式。每一次<text>會返回一個字符串,循環過程中,<text>的所返回的每個字符串會以空格分隔,最後當整個循環結束時,<text>所返回的每個字符串所組成的整個字符串(以空格分隔)將會是foreach函數的返回值。
$(if <condition>,<then-part>) $(if <condition>,<then-part>,<else-part>),<condition>參數是if的表達式,如果其返回的爲非空字符串,那麼這個表達式就相當於返回真,於是,<then-part>會被計算,否則<else-part>會被計算。
$(call <expression>,<parm1>,<parm2>,<parm3>...) call函數是唯一一個可以用來創建新的參數化的函數。我們可以寫一個非常複雜的表達式,這個表達式中,我們可以定義許多參數,然後我們可以用call函數來向這個表達式傳遞參數。當make執行這個函數時,<expression>參數中的變量,如$(1),$(2),$(3)等,會被參數<parm1>,<parm2>,<parm3>依次取代。而<expression>的返回值就是call函數的返回值。
$(origin <variable>) origin函數不像其它的函數,他並不操作變量的值,他只是告訴我們這個變量是哪裏來的。<variable>是變量的名字,不應該是引用。所以我們最好不要在<variable>中使用“$”字符。Origin函數會以其返回值來告訴我們這個變量的“出生情況”,下面,是origin函數的返回值:
“undefined”
      如果<variable>從來沒有定義過,origin函數返回這個值“undefined”。
 default”
      如果<variable>是一個默認的定義,比如“CC”這個變量,這種變量我們將在後面講述。
“environment”
      如果<variable>是一個環境變量,並且當Makefile被執行時,“-e”參數沒有被打開。
file”
      如果<variable>這個變量被定義在Makefile中。
“command line”
      如果<variable>這個變量是被命令行定義的。
 “override”
      如果<variable>是被override指示符重新定義的。
“automatic”
      如果<variable>是一個命令運行中的自動化變量。
$(error <text ...>) 產生一個致命的錯誤,<text ...>是錯誤信息。退出Make執行。
$(warning <text ...>) 輸出一段警告信息,而make繼續執行。
$(shell <command>) 使用Shell執行<command>命令


 
4.4.3自動化變量
       自動化變量通常用來在依賴規則的命令行中表示規則的一部分。通常依賴規則是如下定義方式:Target : Prerequisites。
$@ 表示規則中的目標文件集Target。在模式(即"%")規則中,如果有多個目標,那麼,"$@"就是匹配於目標中模式定義的集合。
$% 僅當目標是函數庫文件中,表示規則中的目標成員名。例如,如果一個目標是"foo.a(bar.o)",那麼,"$%"就是"bar.o","$@"就是"foo.a"。如果目標不是函數庫文件(Unix下是[.a],Windows下是[.lib]),其值爲空。
$< 依賴目標中的第一個目標名字。如果依賴目標是以模式(即"%")定義的,那麼"$<"將是符合模式的一系列的文件集。注意,其是一個一個取出來的。
$? 所有同目標相比更新的依賴目標的集合。以空格分隔。
$^ 所有的依賴目標的集合。以空格分隔。如果在依賴目標中有多個重複的,那個這個變量會去除重複的依賴目標,只保留一份。
$+ 這個變量很像"$^",也是所有依賴目標的集合。只是它不去除重複的依賴目標。
$* 這個變量表示目標模式中"%"及其之前的部分。如果目標是"dir/a.foo.b",並且目標的模式是"a.%.b",那麼,"$*"的值就是"dir/a.foo"。這個變量對於構造有關聯的文件名是比較有效。如果目標中沒有模式的定義,那麼"$*"也就不能被推導出,但是,如果目標文件的後綴是make所識別的,那麼"$*"就是除了後綴的那一部分。例如:如果目標是"foo.c",因爲".c"是make所能識別的後綴名,所以,"$*"的值就是"foo"。這個特性是GNU make的,很有可能不兼容於其它版本的make,所以,我們應該儘量避免使用"$*",除非是在隱含規則或是靜態模式中。如果目標中的後綴是make所不能識別的,那麼"$*"就是空值。


       上面七個自動化變量可以加上D(Directory)或F(File Name)來分別表示路徑和文件名部分。例如,$(@F)表示"$@"的文件部分,如果"$@"值是"dir/foo.o",那麼"$(@F)"就是"foo.o","$(@F)"相當於函數"$(notdir $@)";$(@D) 表示"$@"的目錄部分(不以斜槓做爲結尾),如果"$@"值是"dir/foo.o",那麼"$(@D)"就是"dir",而如果"$@"中沒有包含斜槓的話,其值就是"."(當前目錄)。
4.5 小結
       Make是用來進行工程管理的一個非常有效的機制,它核心的思想是通過定義不同的依賴關係,實現工程編譯的管理,不必每次編譯的時候都將全部的源文件重新編譯,而只需要編譯從上次編譯時直到現在已經修改的源文件。雖然現代的集成開發環境(IDE)已經不再需要我們直接修改Makefile,但是在一些沒有IDE開發環境領域(如嵌入式系統或操作系統的開發)還是需要直接使用Make進行工程管理的。況且,通過對Make的瞭解可以貫通整個程序開發的過程,這對於希望掌握程序開發來龍去脈的相關人員來說是十分有必要的。還有一點是現在關於Make的文檔和書籍基本上沒在市面上見到,就算是有提到也不是十分的詳細,因此希望這裏可以提供給各位讀者一個相對系統和詳細的介紹。
思考題
       Make與編譯器之間有什麼樣的關係?

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/Gemsea/archive/2007/01/26/1495204.aspx

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章