天天看點

Makefile編寫 二 ***

變量

  1. Makefile中變量和函數的展開(除規則指令行中的變量和函數以外),是在make讀取makefile檔案時進行的,包括“define”定義的變量。

  2. 變量可以用來代表一個檔案名清單、編譯選項清單、程式運作的選項參數清單、搜尋源檔案的目錄清單、編譯輸出的目錄清單等。

  3. 變量名是不包括“:”、“#”、“=”、前置空白和尾空白的任何字元串。

  4. 變量名是大小寫敏感的。推薦的做法是:在對于内部定義定義的一般變量使用小寫方式,而對于一些參數清單(例如:編譯選項CFLAGS)采用大寫方式。

  5. 自動化變量。像“$<”、“$@”、“$?”、“$*”等。

  6. 變量的引用方式是:“$(VARIABLE_NAME)”或者“${ VARIABLE_NAME }”來引用一個變量的定義。

變量引用我們建議如下:

  1. 引用使用“$(VAR)”格式,無論“VAR”是單字元變量名還是多字元變量名。

  2.  出現在規則指令行中shell變量引用使用shell的“$tmp”格式。

  3. 對出現在指令行中的make變量我們同樣使用“$(CMDVAR)” 格式來引用。

遞歸展開式變量:

  這一類型變量的定義是通過“=”或者使用訓示符“define”定義的。在引用的地方是嚴格的文本替換過程,此變量值的字元串原模原樣的出現在引用它的地方。如果此變量定義中存在對其他變量的引用,這些被引用的變量會在它被展開的同時被展開。就是說在變量定義時,變量值中對其他變量的引用不會被替換展開;而是變量在引用它的地方替換展開的同時,它所引用的其它變量才會被一同替換展開。語言的描述可能比較晦澀,讓我們來看一個例子:

  foo = $(bar)

  bar = $(ugh)

  ugh = Huh?

  all:;echo $(foo)

執行“make”将會列印出“Huh?”。

其優點是:

這種類型變量在定義時,可以引用其它的之前沒有定義的變量

直接展開式變量:

為了避免“遞歸展開式”變量存在的問題和不友善。GNU make支援另外一種風格的變量,稱為“直接展開”式。

  x := foo

  y := $(x) bar

  x := later 

  就等價于:

  y := foo bar

  x := later

"?="操作符:

  被稱為條件指派是因為:隻有此變量在之前沒有指派的情況下才會對這個變量進行指派。例如:

  FOO ?= bar

  其等價于:

  ifeq ($(origin FOO), undefined)

  FOO = bar

  endif

含義是:如果變量“FOO”在之前沒有定義,就給它指派“bar”。否則不改變它的值。

變量的替換引用:

  格式為“$(VAR:A=B)”(或者“${VAR:A=B}”),意思是,替換變量“VAR”中所有“A”字元結尾的字為“B”結尾的字。例如:

foo := a.o b.o c.o
bar := $(foo:.o=.c)      

在這個定義中,變量“bar”的值就為“a.c b.c c.c”。使用變量的替換引用将變量“foo”以空格分開的值中的所有的字的尾字元“o”替換為“c”,其他部分不變。

變量的替換引用其實是函數“patsubst”的一個簡化實作。在GNU make中同時提供了這兩種方式來實作同樣的目的,以相容其它版本make。

另外一種引用替換的技術使用功能更強大的“patsubst”函數。它的格式和上面“$(VAR:A=B)”的格式相類似,不過需要在“A”和“B”中需要包含模式字元“%”。這時它和“$(patsubst A,B $(VAR))”所實作功能相同。例如:

foo := a.o b.o c.o
bar := $(foo:%.o=%.c)      

這個例子同樣使變量“bar”的值為“a.c b.c c.c”。這種格式的替換引用方式比第一種方式更通用。

變量的追加:

  objects = main.o foo.o bar.o utils.o

  objects += another.o

上邊的兩個操作之後變量“objects”的值就為:“main.o foo.o bar.o utils.o another.o”。

overried 訓示符:

  如果通過指令行定義了一個變量,那麼它将替代在Makefile中出現的同名變量的定義。就是說,對于一個在Makefile中使用正常方式(使用“=”、“:=”或者“define”)定義的變量,我們可以在執行make時通過指令行方式重新指定這個變量的值。

  如果不希望指令行指定的變量值替代在Makefile中的變量定義,那麼我們需要在Makefile中使用訓示符“override”來對這個變量進行聲明,像下邊那樣:

  override VARIABLE = VALUE

或者:

  override VARIABLE := VALUE

也可以對變量使用追加方式: 

  override VARIABLE += MORE TEXT

對于追加方式需要說明的是:變量在定義時使用了“override”,則後續對它值進行追加時,也需要使用帶有“override”訓示符的追加方式。否則對此變量值的追加不會生效。

  其存在的目的是為了使使用者可以改變或者追加那些使用make的指令行指定的變量的定義。

例如:無論指令行指定那些編譯參數,編譯時必須打開“-g”選項,那麼在Makefile中編譯選項“CFLAGS”應該這樣定義:

  override CFLAGS += -g

這樣,在執行make時無論在指令行中指定了那些編譯選項(“指定CFLAGS”的值),編譯時“-g”參數始終存在。

多行定義

定義變量的另外一種方式是使用“define”訓示符。它定義一個包含多行字元串的變量,我們就是利用它的這個特點實作了一個完整指令包的定義。使用“define”定義的指令包可以作為“eval”函數的參數來使用。

本文的前些章節已經不止一次的提到并使用了“define”。相信大家已經有所了解。本節就“define”定義變量從以下幾個方面來讨論:

1. “define”定義變量的文法格式:以訓示符“define”開始,“endif”結束,之間的所有内容就是所定義變量的值。所要定義的變量名字和訓示符“define”在同一行,使用空格分開;訓示符所在行的下一行開始一直到“endif”所在行的上一行之間的若幹行,是變量值。

  define two-lines

  echo foo

  echo $(bar)

  endef

如果将變量“two-lines”作為指令包執行時,其相當于:

  two-lines = echo foo; echo $(bar)

它把變量“two-lines”的值作為一個完整的shell指令行來處理(是使用分号“;”分開的在同一行中的兩個指令而不是作為兩個指令行來處理),保證了變量完整。

2. 變量的風格:使用“define”定義的變量和使用“=”定義的變量一樣,屬于“遞歸展開”式的變量,兩者隻是在文法上不同。是以“define”所定義的變量值中,對其它變量或者函數引用不會在定義變量時進行替換展開,其展開是在“define”定義的變量被展開的同時完成的。

3. 可以套嵌引用。因為是遞歸展開式變量,是以在嵌套引用時“$(x)”将是變量的值的一部分。

4.變量值中可以包含:換行符、空格等特殊符号(注意如果定義中某一行是以[Tab]字元開始時,當引用此變量時這一行會被作為指令行來處理)。

5.可以使用“override”在定義時聲明變量:這樣可以防止變量的值被指令行指定的值替代。例如:

  override define two-lines

  foo

  $(bar)

系統環境變量

  make在運作時,系統中的所有環境變量對它都是可見的。在Makefile中,可以引用任何已定義的系統環境變量。(這裡我們區分系統環境變量和make的環境變量,系統環境變量是這個系統所有使用者所擁有的,而make的環境變量隻是對于make的一次執行過程有效,以下正文中出現沒有限制的“環境變量”時預設指的是“系統環境變量”,在特殊的場合我們會區分兩者)正因為如此,我們就可以設定一個命名為“CFLAGS”的環境變量,用它來指定一個預設的編譯選項。就可以在所有的Makefile中直接使用這個變量來對c源代碼就行編譯。通常這種方式是比較安全的,但是它的前提是大家都明白這個變量所代表的含義,沒有人在Makefile中把它作其他的用途。當然了,你也可以在你的Makefile中根據你的需要對它進行重新定義。

使用環境變量需要注意以下幾點:

1. 在Makefile中對一個變量的定義或者以make指令行形式對一個變量的定義,都将覆寫同名的環境變量(注意:它并不改變系統環境變量定義,被修改的環境變量隻在make執行過程有效)。而make使用“-e”參數時,Makefile和指令行定義的變量不會覆寫同名的環境變量,make将使用系統環境變量中這些變量的定義值。

2. make的遞歸調用中,所有的系統環境變量會被傳遞給下一級make。預設情況下,隻有環境變量和通過指令行方式定義的變量才會被傳遞給子make程序。 

  我們不推薦使用環境變量的方式來完成普通變量的工作,特别是在make的遞歸調用中。任何一個環境變量的錯誤定義都對系統上的所有make産生影響,甚至是毀壞性的。因為環境變量具有全局的特征。是以盡量不要污染環境變量,造成環境變量名字污染。我想大多數系統管理者都明白環境變量對系統是多麼的重要。

我們來看一個例子,結束本節。假如我們的機器名為“server-cc”;我們的Makefile内容如下:

  # test makefile

  HOSTNAME = server-http

  …………

  .PHONY : debug

  debug :

         @echo “hostname is : $( HOSTNAME)”

         @echo “shell is $(SHELL)”

1. 執行“make debug”将顯示:

hostname is : server-http

shell is /bin/sh

2. 執行“make –e debug”;将顯示:

hostname is : server-cc

3. 執行“make –e HOSTNAEM=server-ftp”;将顯示:

      hostname is : server-ftp

記住:除非必須,否則在你的Makefile中不要重置環境變量“SHELL”的值。因為一個不正确的指令行解釋程式可能會導緻規則定義的指令執行失敗,甚至是無法執行!當需要重置它時,必須有充分的理由和配套的規則指令來适應這個新指定的指令行解釋程式。

目标指定變量

在Makefile中定義一個變量,那麼這個變量對此Makefile的所有規則都是有效的。它就像是一個“全局的”變量。

另外一個特殊的變量定義就是所謂的“目标指定變量(Target-specific Variable)”。此特性允許對于相同變量根據目标指定不同的值,有點類似于自動化變量。目标指定的變量值隻在指定它的目标的上下文中有效,對于其他的目标沒有影響。就是說目标指定的變量具有隻對此目标上下文有效的“局部性”。

設定一個目标指定變量的文法為:

  TARGET ... : VARIABLE-ASSIGNMENT 

  TARGET ... : override VARIABLE-ASSIGNMENT

一個多目标指定的變量的作用域是所有這些目标的上下文,它包括了和這個目标相關的所有執行過程。

目标指定變量的一些特點:

1. “VARIABLE-ASSIGNMENT”可以使用任何一個有效的指派方式,“=”(遞歸)、“:=”(靜态)、“+=”(追加)或者“?=”(條件)。

2. 使用目标指定變量值時,目标指定的變量值不會影響同名的那個全局變量的值。就是說目标指定一個變量值時,如果在Makefile中之前已經存在此變量的定義(非目标指定的),那麼對于其它目标全局變量的值沒有變化。變量值的改變隻對指定的這些目标可見。

3. 目标指定變量和普通變量具有相同的優先級。就是說,當我們使用make指令行的方式定義變量時,指令行中的定義将替代目标指定的同名變量定義(和普通的變量一樣會被覆寫)。另外當使用make的“-e”選項時,同名的環境變量也将覆寫目标指定的變量定義。是以為了防止目标指定的變量定義被覆寫,可以使用第二種格式,使用訓示符“override”對目标指定的變量進行聲明。

4. 目标指定的變量和同名的全局變量屬于兩個不同的變量,它們在定義的風格(遞歸展開式和直接展開式)上可以不同。

5. 目标指定的變量變量會作用到由這個目标所引發的所有的規則中去。例如:

  prog : CFLAGS = -g

  prog : prog.o foo.o bar.o

這個例子中,無論Makefile中的全局變量“CFLAGS”的定義是什麼。對于目标“prog”以及其所引發的所有(包含目标為“prog.o”、“foo.o”和“bar.o”的所有規則)規則,變量“CFLAGS”值都是“-g”。

使用目标指定變量可以實作對于不同的目标檔案使用不同的編譯參數。看一個例子:

  # sample Makefile

  CUR_DIR = $(shell pwd)

  INCS := $(CUR_DIR)/include

  CFLAGS := -Wall –I$(INCS)

  EXEF := foo bar

  .PHONY : all clean

  all : $(EXEF)

  foo : foo.c

  foo : CFLAGS+=-O2

  bar : bar.c

  bar : CFLAGS+=-g

  ………..

  $(EXEF) : debug.h

  $(CC) $(CFLAGS) $(addsuffix .c,$@) –o $@

  clean :

         $(RM) *.o *.d $(EXES)

這個Makefile檔案實作了在編譯程式“foo”使用優化選項“-O2”但不使用調試選項“-g”,而在編譯“bar”時采用了“-g”但沒有“-O2”。這就是目标指定變量的靈活之處。目标指定變量的其它特性大家可以修改這個簡單的Makefile來進行驗證!

模式指定變量

  模式指定變量(Pattern-specific Variable)。使用目标定變量定義時,此變量被定義在某個具體目标和由它所引發的規則的目标上。而模式指定變量定義是将一個變量值指定到所有符合此模式的目标上。對于同一個變量如果使用追加方式,通常對于一個目标,它的局部變量值是:(為所有規則定義的全局值)+(引發它所在規則被執行的目标所指定的值)+(它所符合的模式指定值)+(此目标所指定的值)。這個大家也不需要深入了解。

設定一個模式指定變量的文法和設定目标變量的文法相似:

  PATTERN ... : VARIABLE-ASSIGNMENT

  PATTERN ... : override VARIABLE-ASSIGNMENT

和目标指定變量文法的唯一差別就是:這裡的目标是一個或者多個“模式”目标(包含模式字元“%”)。例如我們可以為所有的.o檔案指定變量“CFLAGS”的值: