Makefile study

intro

refer 跟我一起寫Makefile

rules

target ... : prerequisites ...
    command
    ...
    ...
  • target 可以是一個object file(目標文件),也可以是一個執行文件,還可以是一個標籤(label)。對於標籤這種特性,在後續的「偽目標」章節中會有敘述。
  • prerequisites 生成該target所依賴的文件和/或target
  • command 該target要執行的命令(任意的shell命令)

這是一個文件的依賴關係,也就是說,target這一個或多個的目標文件依賴於prerequisites中的文件,其生成規則定義在command中。 prerequisites中如果有一個以上的文件比target文件要新的話,command所定義的命令就會被執行。

save and execute file

我們可以把這個內容保存在名字為「makefile」或「Makefile」的文件中,然後在該目錄下直接輸入命令 make 就可以生成執行文件edit。如果要刪除執行文件和所有的中間目標文件,那麼,只要簡單地執行一下 make clean 就可以了。

  • \ 反斜槓是換行符的意思。這樣比較便於makefile的閱讀。

Example

我們的工程有8個c文件,和3個頭文件,我們要寫一個makefile來告訴make命令如何編譯和鏈接這幾個文件。我們的規則是:

  1. 如果這個工程沒有編譯過,那麼我們的所有c文件都要編譯並被鏈接。
  2. 如果這個工程的某幾個c文件被修改,那麼我們只編譯被修改的c文件,並鏈接目標程序。
  3. 如果這個工程的頭文件被改變了,那麼我們需要編譯引用了這幾個頭文件的c文件,並鏈接目標程序。
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

main.o : main.c defs.h
    cc -c main.c
kbd.o : kbd.c defs.h command.h
    cc -c kbd.c
command.o : command.c defs.h command.h
    cc -c command.c
display.o : display.c defs.h buffer.h
    cc -c display.c
insert.o : insert.c defs.h buffer.h
    cc -c insert.c
search.o : search.c defs.h buffer.h
    cc -c search.c
files.o : files.c defs.h buffer.h command.h
    cc -c files.c
utils.o : utils.c defs.h
    cc -c utils.c
clean :
    rm edit main.o kbd.o command.o display.o \
        insert.o search.o files.o utils.o

執行文件edit和中間目標文件 *.o,依賴文件(prerequisites)就是冒號後面的那些 .c 文件和 .h 文件。每一個 .o 文件都有一組依賴文件,而這些 .o 文件又是執行文件 edit 的依賴文件。依賴關係的實質就是說明了目標文件是由哪些文件生成的,換言之,目標文件是哪些文件更新的。

在定義好依賴關係後,後續的那一行定義了如何生成目標文件的操作系統命令,一定要以一個 Tab 鍵作為開頭。記住,make並不管命令是怎麼工作的,他只管執行所定義的命令。make會比較targets文件和prerequisites文件的修改日期,如果prerequisites文件的日期要比targets文件的日期要新,或者target不存在的話,那麼,make就會執行後續定義的命令。

make是如何工作的

在默認的方式下,也就是我們只輸入 make 命令。那麼

  1. make會在當前目錄下找名字叫Makefilemakefile 的文件。
  2. 如果找到,它會找文件中的第一個目標文件(target),在上面的例子中,他會找到edit 這個文件,並把這個文件作為最終的目標文件。
  3. 如果edit文件不存在,或是edit所依賴的後面的 .o 文件的文件修改時間要比 edit 這個文件新,那麼,他就會執行後面所定義的命令來生成 edit 這個文件。
  4. 如果 edit 所依賴的 .o 文件也不存在,那麼make會在當前文件中找目標為 .o 文件的依賴性,如果找到則再根據那一個規則生成 .o 文件。(這有點像一個堆棧的過程)
  5. 當然,你的C文件和H文件是存在的啦,於是make會生成 .o 文件,然後再用 .o 文件生成make的終極任務,也就是執行文件 edit 了。

通過上述分析,我們知道,像 clean 這種,沒有被第一個目標文件直接或間接關聯,那麼它後面所定義的命令將不會被自動執行,不過,我們可以顯示要 make 執行。即命令—— make clean ,以此來清除所有的目標文件,以便重編譯。

於是在我們編程中,如果這個工程已被編譯過了,當我們修改了其中一個源文件,比如 file.c ,那麼根據我們的依賴性,我們的目標 file.o 會被重編譯(也就是在這個依性關係後面所定義的命令),於是 file.o 的文件也是最新的啦,於是 file.o 的文件修改時間要比 edit 要新,所以 edit 也會被重新鏈接了(詳見 edit 目標文件後定義的命令)。

而如果我們改變了 command.h ,那麼, kdb.ocommand.ofiles.o 都會被重編譯,並且, edit 會被重鏈接。

makefile中使用变量

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

.o 文件的字符串被重複了兩次,如果我們的工程需要加入一個新的 .o 文件,那麼我們需要在兩個地方加(應該是三個地方,還有一個地方在clean中)

objects = main.o kbd.o command.o display.o \
     insert.o search.o files.o utils.o

於是,我們就可以很方便地在我們的makefile中以 $(objects) 的方式來使用這個變量了

objects = main.o kbd.o command.o display.o \
    insert.o search.o files.o utils.o

edit : $(objects)
    cc -o edit $(objects)
main.o : main.c defs.h
    cc -c main.c
kbd.o : kbd.c defs.h command.h
    cc -c kbd.c
command.o : command.c defs.h command.h
    cc -c command.c
display.o : display.c defs.h buffer.h
    cc -c display.c
insert.o : insert.c defs.h buffer.h
    cc -c insert.c
search.o : search.c defs.h buffer.h
    cc -c search.c
files.o : files.c defs.h buffer.h command.h
    cc -c files.c
utils.o : utils.c defs.h
    cc -c utils.c
clean :
    rm edit $(objects)

於是如果有新的 .o 文件加入,我們只需簡單地修改一下 objects 變量就可以了。

讓make自動推導

GNU的make很強大,它可以自動推導文件以及文件依賴關係後面的命令,於是我們就沒必要去在每一個 .o 文件後都寫上類似的命令,因為,我們的make會自動識別,並自己推導命令。

只要make看到一個 .o 文件,它就會自動的把 .c 文件加在依賴關係中,如果make找到一個 whatever.o ,那麼 whatever.c 就會是 whatever.o 的依賴文件。並且 cc -c whatever.c 也會被推導出來

objects = main.o kbd.o command.o display.o \
    insert.o search.o files.o utils.o

edit : $(objects)
    cc -o edit $(objects)

main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h

.PHONY : clean
clean :
    rm edit $(objects)

這種方法,也就是make的隱晦規則

上面文件內容中, .PHONY 表示 clean 是個偽目標文件

clean

一般的風格

clean:
    rm edit $(objects)

更為穩健的做法

.PHONY : clean
clean :
    -rm edit $(objects)

前面說過, .PHONY 表示 clean 是一個偽目標。而在 rm 命令前面加了一個小減號的意思就是,也許某些文件出現問題,但不要管,繼續做後面的事

clean 的規則不要放在文件的開頭,不然,這就會變成make的默認目標,相信誰也不願意這樣。不成文的規矩是——clean從來都是放在文件的最後」。

Makefile裡有什麼

  1. 顯式規則。顯式規則說明了如何生成一個或多個目標文件。這是由Makefile的書寫者明顯指出要生成的文件、文件的依賴文件和生成的命令。
  2. 隱晦規則。由於我們的make有自動推導的功能,所以隱晦的規則可以讓我們比較簡略地書寫 Makefile,這是由make所支持的。
  3. 變量的定義。在Makefile中我們要定義一系列的變量,變量一般都是字符串,這個有點像你C語言中的宏,當Makefile被執行時,其中的變量都會被擴展到相應的引用位置上。
  4. 文件指示。其包括了三個部分
    1. Makefile中引用另一個Makefile,就像C語言中的 include 一樣 include <filename>
    2. 指根據某些情況指定Makefile中的有效部分,就像C語言中的預編譯#if一樣
    3. 定義一個多行的命令。有關這一部分的內容,我會在後續的部分中講述。
  5. 註釋。Makefile中只有行註釋,和UNIX的Shell腳本``一樣,其註釋是用 # 字符,這個就像C/C++中的 // 一樣。如果你要在你的Makefile中使用 #字符,可以用反斜槓進行轉義,如:#` 。

include 前面可以有一些空字符,但是絕不能是 Tab 鍵開始

你有這樣幾個 Makefile: a.mkb.mkc.mk ,還有一個文件叫 foo.make ,以及一個變量 $(bar) ,其包含了 e.mkf.mk ,那麼,下面的語句

include foo.make *.mk $(bar)

equal to

include foo.make a.mk b.mk c.mk e.mk f.mk

make 命令開始時,會找尋 include 所指出的其它 Makefile,並把其內容安置在當前的位置。就好像C/C++的 #include 指令一樣。如果文件都沒有指定絕對路徑或是相對路徑的話,make會在當前目錄下首先尋找,如果當前目錄下沒有找到,那麼,make還會在下面的幾個目錄下找:

  1. 如果 make 執行時,有 -I--include-dir 參數,那麼make就會在這個參數所指定的目錄下去尋找。
  2. 如果目錄 <prefix>/include (一般是: /usr/local/bin/usr/include )存在的話,make 也會去找。

如果有文件沒有找到的話,make會生成一條警告信息,但不會馬上出現致命錯誤。它會繼續載入其它的文件,一旦完成makefile的讀取,make會再重試這些沒有找到,或是不能讀取的文件,如果還是不行,make才會出現一條致命信息。如果你想讓make不理那些無法讀取的文件,而繼續執行,你可以在include前加一個減號 -。如:

-include <filename>

其表示,無論include過程中出現什麼錯誤,都不要報錯繼續執行。和其它版本make兼容的相關命令是sinclude,其作用和這一個是一樣的。

環境變量MAKEFILES, BUG happen

如果你的當前環境中定義了環境變量 MAKEFILES ,那麼,make會把這個變量中的值做一個類似於 include 的動作。這個變量中的值是其它的Makefile,用空格分隔。只是,它和 include 不同的是,從這個環境變量中引入的 Makefile目標不會起作用,如果環境變量中定義的文件發現錯誤,make也會不理。

但是在這裡我還是建議不要使用這個環境變量,因為只要這個變量一被定義,那麼當你使用make時,所有的Makefile都會受到它的影響,這絕不是你想看到的。在這裡提這個事,只是為了告訴大家,也許有時候你的Makefile出現了怪事,那麼你可以看看當前環境中有沒有定義這個變量。

make的工作方式

  1. 讀入所有的Makefile。
  2. 讀入被include的其它Makefile。
  3. 初始化文件中的變量。
  4. 推導隱晦規則,並分析所有規則。
  5. 為所有的目標文件創建依賴關係鏈。
  6. 根據依賴關係,決定哪些目標要重新生成。
  7. 執行生成命令。

1-5步為第一個階段

第一個階段中,如果定義的變量被使用了,那麼,make會把其展開在使用的位置。但make並不會完全馬上展開,make使用的是拖延戰術,如果變量出現在依賴關係的規則中,那麼僅當這條依賴被決定要使用了,變量才會在其內部展開。

6-7為第二個階段

書寫規則

規則包含兩個部分

  1. 依賴關係
  2. 生成目標的方法

在Makefile中,規則的順序是很重要的,因為,Makefile中只應該有一個最終目標,其它的目標都是被這個目標所連帶出來的,所以一定要讓make知道你的最終目標是什麼。一般來說,定義在Makefile中的目標可能會有很多,但是第一條規則中的目標將被確立為最終的目標。如果第一條規則中的目標有很多個,那麼,第一個目標會成為最終的目標。make所完成的也就是這個目標。

規則舉例

foo.o: foo.c defs.h       # foo模塊
    cc -c -g foo.c
  • foo.o 是我們的目標
  • foo.c , defs.h 目標所依賴的源文件
  • 命令 cc -c -g foo.c
  1. 文件的依賴關係, foo.o 依賴於 foo.cdefs.h 的文件,如果 foo.cdefs.h 的文件日期要比 foo.o 文件日期要新,或是 foo.o 不存在,那麼依賴關係發生。
  2. 生成或更新 foo.o 文件,就是那個cc命令。它說明了如何生成 foo.o 這個文件。(當然,foo.c文件includedefs.h文件)

規則的語法

targets : prerequisites
    command
    ...

or

targets : prerequisites ; command
    command
    ...
  • targets是文件名,以空格分開,可以使用通配符。一般來說,我們的目標基本上是一個文件,但也有可能是多個文件。
  • command是命令行,如果其不與 target:prerequisites 在一行,那麼,必須以 Tab 鍵開頭,如果和prerequisites在一行,那麼可以用分號做為分隔。
  • prerequisites 也就是目標所依賴的文件(或依賴目標)。如果其中的某個文件要比目標文件要新,那麼,目標就被認為是「過時的」,被認為是需要重生成的

一般來說,make會以UNIX的標準Shell,也就是 /bin/sh 來執行命令。

在規則中使用通配符

make支持三個通配符:

1.* 2. ? 3. ~

example

clean:
    rm -f *.o

這個例子說明了通配符也可以在我們的規則中,目標print依賴於所有的 .c 文件。其中的 $? 是一個自動化變量

print: *.c
    lpr -p $?
    touch print
objects = *.o

上面這個例子,表示了通配符同樣可以用在變量中。並不是說 *.o 會展開,不!objects的值就是 *.o 。Makefile中的變量其實就是C/C++中的宏。如果你要讓通配符在變量中展開,也就是讓objects的值是所有 .o 的文件名的集合,那麼,你可以這樣:

objects := $(wildcard *.o)
  1. 列出一確定文件夾中的所有 .c 文件
objects := $(wildcard *.c)
  1. 列出(1)中所有文件對應的 .o 文件,在(3)中我們可以看到它是由make自動編譯出的
$(patsubst %.c,%.o,$(wildcard *.c))
  1. 由(1)(2)兩步,可寫出編譯並鏈接所有 .c.o 文件
objects := $(patsubst %.c,%.o,$(wildcard *.c))
foo : $(objects)
    cc -o foo $(objects)

文件搜尋

  1. VPATH

    Makefile文件中的特殊變量 VPATH 就是完成這個功能的,如果沒有指明這個變量,make只會在當前的目錄中去找尋依賴文件和目標文件。如果定義了這個變量,那麼,make就會在噹噹前目錄找不到的情況下,到所指定的目錄中去找尋文件了。

    VPATH = src:../headers
    

    上面的的定義指定兩個目錄, src../headers,make會按照這個順序進行搜索。目錄由冒號分隔。(當然,當前目錄永遠是最高優先搜索的地方)

  2. vpath

    另一個設置文件搜索路徑的方法是使用make的「vpath」關鍵字(注意,它是全小寫的),這不是變量,這是一個make的關鍵字,這和上面提到的那個VPATH變量很類似,但是它更為靈活。它可以指定不同的文件在不同的搜索目錄中。這是一個很靈活的功能。它的使用方法有三種:

    1. vpath <pattern> <directories>`` 為符合模式 的文件指定搜索目錄`。
    2. vpath <pattern> 清除符合模式<pattern>的文件的搜索目錄。
    3. vpath 清除所有已被設置好了的文件搜索目錄。

    vapth使用方法中的<pattern>需要包含 % 字符。 % 的意思是匹配零或若干字符,(需引用 % ,使用 \ )例如, %.h 表示所有以 .h 結尾的文件

    • <pattern>指定了要搜索的文件集
    • <directories>則指定了< pattern>的文件集的搜索的目錄。例如:
    vpath %.h ../headers
    

    該語句表示,要求make在「../headers」目錄下搜索所有以 .h 結尾的文件。(如果某文件在當前目錄沒有找到的話)

    我們可以連續地使用vpath語句,以指定不同搜索策略。如果連續的vpath語句中出現了相同的<pattern>,或是被重複了的<pattern>,那麼,make會按照vpath語句的先後順序來執行搜索。如:

    vpath %.c foo
    vpath %.c blish
    vpath %.c bar
    

    其表示 .c 結尾的文件,先在「foo」目錄,然後是「blish」,最後是「bar」目錄。

    vpath %.c foo:bar
    vpath %   blish
    

    .c 結尾的文件,先在「foo」目錄,然後是「bar」目錄,最後才是「blish」目錄。

偽目標

clean:
    rm *.o temp

正像我們前面例子中的「clean」一樣,既然我們生成了許多文件編譯文件,我們也應該提供一個清除它們的「目標」以備完整地重編譯而用。 (以「make clean」來使用該目標)

當然,「偽目標」的取名不能和文件名重名,不然其就失去了「偽目標」的意義了 為了避免和文件重名的這種情況,我們可以使用一個特殊的標記「.PHONY」來顯式地指明一個目標是「偽目標」,向make說明,不管是否有這個文件,這個目標就是「偽目標」。

.PHONY : clean

只要有這個聲明,不管是否有「clean」文件,要運行「clean」這個目標, 只有「make clean」這樣

.PHONY : clean
clean :
    rm *.o temp

偽目標一般沒有依賴的文件。但是,我們也可以為偽目標指定所依賴的文件。偽目標同樣可以作為「默認目標」,只要將其放在第一個。一個示例就是,如果你的Makefile需要一口氣生成若干個可執行文件,但你只想簡單地敲一個make完事,並且,所有的目標文件都寫在一個Makefile中,那麼你可以使用「偽目標」這個特性:

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

我們知道,Makefile中的第一個目標會被作為其默認目標。我們聲明了一個「all」的偽目標,其依賴於其它三個目標。由於默認目標的特性是,總是被執行的,但由於「all」又是一個偽目標,偽目標只是一個標籤不會生成文件,所以不會有「all」文件產生。於是,其它三個目標的規則總是會被決議。也就達到了我們一口氣生成多個目標的目的

.PHONY : all 聲明了「all」這個目標為「偽目標

偽目標同樣也可成為依賴。看下面的例子

.PHONY : cleanall cleanobj cleandiff

cleanall : cleanobj cleandiff
    rm program

cleanobj :
    rm *.o

cleandiff :
    rm *.diff

make cleanall」將清除所有要被清除的文件。「cleanobj」和「cleandiff」這兩個偽目標有點像「子程序」的意思。我們可以輸入「make cleanall」和「make cleanobj」和「make cleandiff」命令來達到清除不同種類文件的目的

多目標

Makefile的規則中的目標可以不止一個,其支持多目標,有可能我們的多個目標同時依賴於一個文件,並且其生成的命令大體類似。於是我們就能把其合併起來。當然,多個目標的生成規則的執行命令不是同一個,這可能會給我們帶來麻煩,不過好在我們可以使用一個自動化變量 $@,這個變量表示著目前規則中所有的目標的集合,這樣說可能很抽象,還是看一個例子吧。

bigoutput littleoutput : text.g
    generate text.g -$(subst output,,$@) > $@

上述規則等價於:

bigoutput : text.g
    generate text.g -big > bigoutput
littleoutput : text.g
    generate text.g -little > littleoutput

其中, -$(subst output,,$@) 中的 $ 表示執行一個Makefile的函數,函數名為subst,後面的為參數。關於函數,將在後面講述 這裡的這個函數是替換字符串的意思, $@ 表示目標的集合,就像一個數組,$@ 依次取出目標,並執於命令。

  • $ 表示執行一個Makefile的函數
  • $@ 表示目標的集合, 就像一個數組,$@ 依次取出目標,並執於命令。

靜態模式

靜態模式可以更加容易地定義多目標的規則,可以讓我們的規則變得更加的有彈性和靈活

<targets ...> : <target-pattern> : <prereq-patterns ...>
    <commands>
    ...
  • targets 定義了一系列的目標文件,可以有通配符。是目標的一個集合。
  • target-parrtern 是指明了 targets 的模式,也就是的目標集模式
  • prereq-parrterns 是目標的依賴模式,它對target-parrtern形成的模式再進行一次依賴目標的定義。

這樣描述這三個東西,可能還是沒有說清楚,還是舉個例子來說明一下吧。如果我們的<target-parrtern>定義成 %.o ,意思是我們的<target>;集合中都是以 .o 結尾的,而如果我們的<prereq-parrterns>定義成 %.c ,意思是對<target-parrtern>所形成的目標集進行二次定義,其計算方法是,取<target-parrtern>模式中的 % (也就是去掉了 .o 這個結尾),並為其加上 .c 這個結尾,形成的新集合。

所以,我們的「目標模式」或是「依賴模式」中都應該有 % 這個字符,如果你的文件名中有 % 那麼你可以使用反斜槓 \ 進行轉義,來標明真實的 % 字符。

example

objects = foo.o bar.o

all: $(objects)

$(objects): %.o: %.c
    $(CC) -c $(CFLAGS) $< -o $@
  • $< 表示所有的依賴目標集 e.g foo.c
  • $@ 表示目標集 e.g:foo.o

上面的例子中,指明了我們的目標從$object中獲取, %.o 表明要所有以 .o 結尾的目標,也就是 foo.o bar.o ,也就是變量 $object 集合的模式,而依賴模式 %.c 則取模式 %.o% ,也就是 foo bar ,並為其加下 .c 的後綴,於是,我們的依賴目標就是 foo.c bar.c 。而命令中的 $<$@ 則是 自動化變量$< 表示所有的依賴目標集(也就是 foo.c bar.c ), $@ 表示目標集(也就是「foo.o bar.o」)。於是,上面的規則展開後等價於下面的規則:

foo.o : foo.c
    $(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
    $(CC) -c $(CFLAGS) bar.c -o bar.o

試想,如果我們的 %.o 有幾百個,那麼我們只要用這種很簡單的「靜態模式規則」就可以寫完一堆規則,實在是太有效率了。「靜態模式規則」的用法很靈活,如果用得好,那會是一個很強大的功能。再看一個例子

files = foo.elc bar.o lose.o

$(filter %.o,$(files)): %.o: %.c
    $(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
    emacs -f batch-byte-compile $<

$(filter %.o,$(files))表示調用Makefile的filter函數,過濾「$files」集,只要其中模式為「%.o」的內容。其它的內容,我就不用多說了吧。這個例子展示了Makefile中更大的彈性

自動生成依賴性

在Makefile中,我們的依賴關係可能會需要包含一系列的頭文件,比如,如果我們的main.c中有一句 #include "defs.h" ,那麼我們的依賴關係應該是

main.o : main.c defs.h

但是,如果是一個比較大型的工程,你必需清楚哪些C文件包含了哪些頭文件,並且,你在加入或刪除頭文件時,也需要小心地修改Makefile,這是一個很沒有維護性的工作。為了避免這種繁重而又容易出錯的事情,我們可以使用C/C++編譯的一個功能。大多數的C/C++編譯器都支持一個「-M」的選項,即自動找尋源文件中包含的頭文件,並生成一個依賴關係。例如,如果我們執行下面的命令

cc -M main.c

output

main.o : main.c defs.h

於是由編譯器自動生成的依賴關係,這樣一來,你就不必再手動書寫若干文件的依賴關係,而由編譯器自動生成了。需要提醒一句的是,如果你使用GNU的C/C++編譯器,你得用 -MM 參數,不然, -M 參數會把一些標準庫的頭文件也包含進來。

gcc -M main.c

output

main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \
    /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \
    /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h \
    /usr/include/bits/types.h /usr/include/bits/pthreadtypes.h \
    /usr/include/bits/sched.h /usr/include/libio.h \
    /usr/include/_G_config.h /usr/include/wchar.h \
    /usr/include/bits/wchar.h /usr/include/gconv.h \
    /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h \
    /usr/include/bits/stdio_lim.h

gcc -MM main.c

output

main.o: main.c defs.h

那麼,編譯器的這個功能如何與我們的Makefile聯繫在一起呢。因為這樣一來,我們的Makefile也要根據這些源文件重新生成,讓 Makefile自已依賴於源文件?這個功能並不現實,不過我們可以有其它手段來迂迴地實現這一功能。GNU組織建議把編譯器為每一個源文件的自動生成的依賴關係放到一個文件中,為每一個 name.c 的文件都生成一個 name.d 的Makefile文件, .d 文件中就存放對應 .c 文件的依賴關係。

於是,我們可以寫出 .c 文件和 .d 文件的依賴關係,並讓make自動更新或生成 .d 文件,並把其包含在我們的主Makefile中,這樣,我們就可以自動化地生成每個文件的依賴關係了。

這裡,我們給出了一個模式規則來產生 .d 文件:

%.d: %.c
    @set -e; rm -f $@; \
    $(CC) -M $(CPPFLAGS) $< >; $@.$$$$; \
    sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ >; $@; \
    rm -f $@.$$$$
  1. 這個規則的意思是,所有的 .d 文件依賴於 .c 文件
  2. rm -f $@ 的意思是刪除所有的目標,也就是 .d 文件
  3. 第二行的意思是,為每個依賴文件 $< ,也就是 .c 文件生成依賴文件,$@ 表示模式 %.d 文件,如果有一個C文件是name.c,那麼 % 就是 name$$$$ 意為一個隨機編號,第二行生成的文件有可能是「name.d.12345
  4. 第三行使用sed命令做了一個替換,關於sed命令的用法請參看相關的使用文檔
  5. 第四行就是刪除臨時文件

總而言之,這個模式要做的事就是在編譯器生成的依賴關係中加入 .d 文件的依賴,即把依賴關係:

from main.o : main.c defs.h to main.o main.d : main.c defs.h

於是,我們的 .d 文件也會自動更新了,並會自動生成了,當然,你還可以在這個 .d 文件中加入的不只是依賴關係,包括生成的命令也可一併加入,讓每個 .d 文件都包含一個完賴的規則。一旦我們完成這個工作,接下來,我們就要把這些自動生成的規則放進我們的主Makefile中。我們可以使用Makefile的「include」命令,來引入別的Makefile文件(前面講過),例如:

sources = foo.c bar.c
include $(sources:.c=.d)

上述語句中的 $(sources:.c=.d) 中的 .c=.d 的意思是做一個替換,把變量$(sources) 所有 .c 的字串都替換成 .d ,關於這個「替換」的內容,在後面我會有更為詳細的講述。當然,你得注意次序,因為 include按次序來載入文件,最先載入的 .d 文件中的目標會成為默認目標

書寫命令

每條規則中的命令和操作系統Shell的命令行是一致的。make會一按順序一條一條的執行命令,每條命令的開頭必須以 Tab 鍵開頭,除非,命令是緊跟在依賴規則後面的分號後的。在命令行之間中的空格或是空行會被忽略,但是如果該空格或空行是以Tab鍵開頭的,那麼make會認為其是一個空命令

我們在UNIX下可能會使用不同的Shell,但是make的命令默認是被 /bin/sh ——UNIX的標準Shell解釋執行的。除非你特別指定一個其它的Shell。 Makefile中, # 是註釋符,很像C/C++中的 // ,其後的本行字符都被註釋

顯示命令

通常,make會把其要執行的命令行在命令執行前輸出到屏幕上。當我們用 @ 字符在命令行前,那麼,這個命令將不被make顯示出來,最具代表性的例子是,我們用這個功能來向屏幕顯示一些信息。如

@echo 正在編譯XXX模塊......

當make執行時,會輸出「正在編譯XXX模塊......」字串,但不會輸出命令,如果沒有「@」,那麼,make將輸出

echo 正在編譯XXX模塊......
正在編譯XXX模塊......

如果make執行時,帶入make參數 -n--just-print ,那麼其只是顯示命令,但不會執行命令,這個功能很有利於我們調試我們的Makefile,看看我們書寫的命令是執行起來是什麼樣子的或是什麼順序的。

而make參數 -s--silent--quiet 則是全面禁止命令的顯示

命令執行

當依賴目標新於目標時,也就是當規則的目標需要被更新時,make會一條一條的執行其後的命令。需要注意的是 如果你要讓上一條命令的結果應用在下一條命令時,你應該使用分號分隔這兩條命令。比如你的第一條命令是cd命令, 你希望第二條命令得在cd之後的基礎上運行,那麼你就不能把這兩條命令寫在兩行上,而應該把這兩條命令寫在一行上,用分號分隔。如

  • example1

    exec:
        cd /home/hchen
        pwd
    
  • exmaple2

    exec:
        cd /home/hchen; pwd
    

當我們執行 make exec 時,第一個例子中的cd沒有作用,pwd會打印出當前的Makefile目錄,而第二個例子中,cd就起作用了,pwd會打印出 /home/user/

make一般是使用環境變量SHELL中所定義的系統Shell來執行命令,默認情況下使用UNIX的標準Shell——/bin/sh來執行命令。但在MS-DOS下有點特殊,因為MS-DOS下沒有SHELL環境變量,當然你也可以指定。如果你指定了UNIX風格的目錄形式,首先,make會在SHELL所指定的路徑中找尋命令解釋器,如果找不到,其會在當前盤符中的當前目錄中尋找,如果再找不到,其會在PATH環境變量中所定義的所有路徑中尋找。MS-DOS中,如果你定義的命令解釋器沒有找到,其會給你的命令解釋器加上諸如 .exe.com.bat.sh 等後綴

命令出錯

每當命令運行完後,make會檢測每個命令的返回碼,如果命令返回成功,那麼make會執行下一條命令,當規則中所有的命令成功返回後,這個規則就算是成功完成了。如果一個規則中的某個命令出錯了(命令退出碼非零),那麼make就會終止執行當前規則,這將有可能終止所有規則的執行

有些時候,命令的出錯並不表示就是錯誤的。例如mkdir命令,我們一定需要建立一個目錄,如果目錄不存在,那麼mkdir就成功執行,萬事大吉,如果目錄存在,那麼就出錯了。我們之所以使用mkdir的意思就是一定要有這樣的一個目錄,於是我們就不希望mkdir出錯而終止規則的運行。

為了做到這一點,忽略命令的出錯,我們可以在Makefile的命令行前加一個減號 - (在Tab鍵之後),標記為不管命令出不出錯都認為是成功的。如:

clean:
    -rm -f *.o

還有一個全局的辦法是,給make加上 -i 或是 --ignore-errors 參數,那麼,Makefile中所有命令都會忽略錯誤。而如果一個規則是以 .IGNORE 作為目標的,那麼這個規則中的所有命令將會忽略錯誤。這些是不同級別的防止命令出錯的方法,你可以根據你的不同喜歡設置。

還有一個要提一下的make的參數的是 -k 或是 --keep-going ,這個參數的意思是,如果某規則中的命令出錯了,那麼就終目該規則的執行,但繼續執行其它規則。

嵌套執行make

在一些大的工程中,我們會把我們不同模塊或是不同功能的源文件放在不同的目錄中,我們可以在每個目錄中都書寫一個該目錄的Makefile,這有利於讓我們的Makefile變得更加地簡潔,而不至於把所有的東西全部寫在一個Makefile中,這樣會很難維護我們的Makefile,這個技術對於我們模塊編譯和分段編譯有著非常大的好處。

before continue, that talk about assign

  • Lazy Set

    VARIABLE = value

    Normal setting of a variable - values within it are recursively expanded when the variable is used, not when it’s declared

  • Immediate Set

    VARIABLE := value

    Setting of a variable with simple expansion of the values inside - values within it are expanded at declaration time.

  • Set If Absent

    VARIABLE ?= value

    Setting of a variable only if it doesn’t have a value

  • Append

    VARIABLE += value

    Appending the supplied value to the existing value (or setting to that value if the variable didn’t exist)

例如,我們有一個子目錄叫subdir,這個目錄下有個Makefile文件,來指明了這個目錄下文件的編譯規則。那麼我們總控的Makefile可以這樣書寫

subsystem:
    cd subdir && $(MAKE)
subsystem:
    $(MAKE) -C subdir

定義$(MAKE)宏變量的意思是,也許我們的make需要一些參數,所以定義成一個變量比較利於維護。這兩個例子的意思都是先進入「subdir」目錄,然後執行make命令。

我們把這個Makefile叫做「總控Makefile」,總控Makefile的變量可以傳遞到下級的Makefile中(如果你顯示的聲明),但是不會覆蓋下層的Makefile中所定義的變量,除非指定了 -e 參數。

如果你要傳遞變量到下級Makefile中,那麼你可以使用這樣的聲明:

export <variable ...>;

如果你不想讓某些變量傳遞到下級Makefile中,那麼你可以這樣聲明:

unexport <variable ...>;

example

export variable = value

其等價於:

variable = value
export variable

其等價於(not exact):

the := sign will expand in assign, the = only expand in using

export variable := value

其等價於(not exact):

variable := value
export variable

example2

export variable += value

其等價於:

variable += value
export variable

如果你要傳遞所有的變量,那麼,只要一個export就行了。後面什麼也不用跟,表示傳遞所有的變量

需要注意的是,有兩個變量,一個是 SHELL ,一個是 MAKEFLAGS ,這兩個變量不管你是否export,其總是要傳遞到下層 Makefile中

特別是 MAKEFLAGS 變量,其中包含了make的參數信息,如果我們執行「總控Makefile」時有make參數或是在上層 Makefile中定義了這個變量,那麼 MAKEFLAGS 變量將會是這些參數,並會傳遞到下層Makefile中,這是一個系統級的環境變量。

但是make命令中的有幾個參數並不往下傳遞,它們是 -C , -f ,-h, -o-W (有關Makefile參數的細節將在後面說明),如果你不想往下層傳遞參數,那麼,你可以這樣來:

subsystem:
    cd subdir && $(MAKE) MAKEFLAGS=

如果你定義了環境變量 MAKEFLAGS ,那麼你得確信其中的選項是大家都會用到的,如果其中有 -t , -n-q 參數,那麼將會有讓你意想不到的結果,或許會讓你異常地恐慌。

還有一個在「嵌套執行」中比較有用的參數, -w 或是 --print-directory 會在make的過程中輸出一些信息,讓你看到目前的工作目錄。比如,如果我們的下級make目錄是「/home/user/gnu/make」,如果我們使用 make -w 來執行,那麼當進入該目錄時,我們會看到

make: Entering directory `/home/user/gnu/make'.

當你使用 -C 參數來指定make下層Makefile時, -w 會被自動打開的。如果參數中有 -s--slient )或是 --no-print-directory ,那麼, -w 總是失效的。

定義命令包

如果Makefile中出現一些相同命令序列,那麼我們可以為這些相同的命令序列定義一個變量。定義這種命令序列的語法以 define 開始,以 endef 結束,如

define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef

這裡,「run-yacc」是這個命令包的名字,其不要和Makefile中的變量重名。在 defineendef 中的兩行就是命令序列。這個命令包中的第一個命令是運行Yacc程序,因為Yacc程序總是生成「y.tab.c」的文件,所以第二行的命令就是把這個文件改改名字。還是把這個命令包放到一個示例中來看看吧。

foo.c : foo.y
    $(run-yacc)

我們可以看見,要使用這個命令包,我們就好像使用變量一樣。在這個命令包的使用中,命令包「run-yacc」中的 $^ 就是 foo.y $@ 就是 foo.c (有關這種以 $ 開頭的特殊變量,我們會在後面介紹),make在執行命令包時,命令包中的每個命令會被依次獨立執行。

使用變量

在Makefile中的定義的變量,就像是C/C++語言中的宏一樣,他代表了一個文本字串,在Makefile中執行的時候其會自動原模原樣地展開在所使用的地方。其與C/C++所不同的是,你可以在Makefile中改變其值。在Makefile中,變量可以使用在「目標」,「依賴目標」, 「命令」或是Makefile的其它部分中。

變量的命名字可以包含字符、數字,下劃線(可以是數字開頭),但不應該含有 :#= 或是空字符空格回車等)。變量是大小寫敏感的,「foo」、「Foo」和「FOO」是三個不同的變量名。傳統的Makefile的變量名是全大寫的命名方式,但我推薦使用大小寫搭配的變量名,如: MakeFlags。這樣可以避免和系統的變量衝突,而發生意外的事情。

有一些變量是很奇怪字串,如 $<$@ 等,這些是自動化變量

變量的基礎

變量在聲明時需要給予初值,而在使用時,需要給在變量名前加上 $ 符號,但最好用小括號 () 或是大括號 {} 把變量給包括起來。如果你要使用真實的 $ 字符,那麼你需要用 $$ 來表示。

變量可以使用在許多地方,如規則中的「目標」、「依賴」、「命令」以及新的變量中。先看一個例子

objects = program.o foo.o utils.o
program : $(objects)
    cc -o program $(objects)

$(objects) : defs.h

變量會在使用它的地方精確地展開,就像C/C++中的宏一樣,例如:

foo = c
prog.o : prog.$(foo)
    $(foo)$(foo) -$(foo) prog.$(foo)

展開後得到:

prog.o : prog.c
    cc -c prog.c

當然,千萬不要在你的Makefile中這樣幹,這裡只是舉個例子來表明Makefile中的變量在使用處展開的真實樣子。可見其就是一個「替代」的原理。

另外,給變量加上括號完全是為了更加安全地使用這個變量,在上面的例子中,如果你不想給變量加上括號,那也可以,但我還是強烈建議你給變量加上括號。

變量中的變量

在定義變量的值時,我們可以使用其它變量來構造變量的值,在Makefile中有兩種方式來在用變量定義變量的值。

先看第一種方式,也就是簡單的使用 = 號,在 = 左側是變量,右側是變量的值`,右側變量的值可以定義在文件的任何一處,也就是說,右側中的變量不一定非要是已定義好的值,其也可以使用後面定義的值。如:

foo = $(bar)
bar = $(ugh)
ugh = Huh?

all:
    echo $(foo)

我們執行「make all」將會打出變量 $(foo) 的值是 Huh? ( $(foo) 的值是 $(bar) , $(bar) 的值是 $(ugh) , $(ugh) 的值是 Huh? )可見,變量是可以使用後面的變量來定義的

這個功能有好的地方,也有不好的地方,好的地方是,我們可以把變量的真實值推到後面來定義,如:

CFLAGS = $(include_dirs) -O
include_dirs = -Ifoo -Ibar

當 CFLAGS 在命令中被展開時,會是 -Ifoo -Ibar -O 。但這種形式也有不好的地方,那就是遞歸定義,如:

CFLAGS = $(CFLAGS) -O

或:

A = $(B)
B = $(A)

這會讓make陷入無限的變量展開過程中去,當然,我們的make是有能力檢測這樣的定義,並會報錯。還有就是如果在變量中使用函數,那麼,這種方式會讓我們的make運行時非常慢,更糟糕的是,他會使用得兩個make的函數「wildcard」和「shell」發生不可預知的錯誤。因為你不會知道這兩個函數會被調用多少次。

為了避免上面的這種方法,我們可以使用make中的另一種用變量來定義變量的方法。這種方法使用的是 := 操作符,如:

x := foo
y := $(x) bar
x := later

其等價於:

y := foo bar
x := later

值得一提的是,這種方法,前面的變量不能使用後面的變量,只能使用前面已定義好了的變量。如果是這樣:

y := $(x) bar
x := foo

那麼,y的值是「bar」,而不是「foo bar」。

上面都是一些比較簡單的變量使用了,讓我們來看一個複雜的例子,其中包括了make的函數、條件表達式和一個系統變量「MAKELEVEL」的使用:

ifeq (0,${MAKELEVEL})
cur-dir   := $(shell pwd)
whoami    := $(shell whoami)
host-type := $(shell arch)
MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}
endif

關於條件表達式和函數,我們在後面再說,對於系統變量「MAKELEVEL」,其意思是,如果我們的make有一個嵌套執行的動作(參見前面的「嵌套使用make」),那麼,這個變量會記錄了我們的當前Makefile的調用層數。

下面再介紹兩個定義變量時我們需要知道的,請先看一個例子,如果我們要定義一個變量,其值是一個空格,那麼我們可以這樣來:

nullstring :=
space := $(nullstring) # end of the line

nullstring是一個Empty變量,其中什麼也沒有,而我們的space的值是一個空格。因為在操作符的右邊是很難描述一個空格的,這裡採用的技術很管用,先用一個Empty變量來標明變量的值開始了,而後面採用「#」註釋符來表示變量定義的終止,這樣,我們可以定義出其值是一個空格的變量。請注意這裡關於「#」的使用,註釋符「#」的這種特性值得我們注意,如果我們這樣定義一個變量

dir := /foo/bar    # directory to put the frobs in

dir這個變量的值是「/foo/bar」,後面還跟了4個空格,如果我們這樣使用這樣變量來指定別的目錄——「$(dir)/file」那麼就完蛋了。

還有一個比較有用的操作符是 ?= ,先看示例:

FOO ?= bar

其含義是,如果FOO沒有被定義過,那麼變量FOO的值就是「bar」,如果FOO先前被定義過,那麼這條語將什麼也不做,其等價於:

ifeq ($(origin FOO), undefined)
    FOO = bar
endif

變量高級用法

這裡介紹兩種變量的高級使用方法,第一種是變量值的替換。

我們可以替換變量中的共有的部分,其格式是 $(var:a=b) 或是 ${var:a=b} ,其意思是,把變量「var」中所有以「a」字串「結尾」的「a」替換成「b」字串。這裡的「結尾」意思是「空格」或是「結束符」。

還是看一個示例吧:

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

這個示例中,我們先定義了一個 $(foo) 變量,而第二行的意思是把 $(foo) 中所有以 .o 字串「結尾」全部替換成 .c ,所以我們的 $(bar) 的值就是「a.c b.c c.c」。

另外一種變量替換的技術是以「靜態模式」(參見前面章節)定義的,如:

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

這依賴於被替換字串中的有相同的模式,模式中必須包含一個 % 字符,這個例子同樣讓 $(bar) 變量的值為「a.c b.c c.c」。

第二種高級用法是——「把變量的值再當成變量」。先看一個例子:

x = y
y = z
a := $($(x))

在這個例子中,$(x)的值是「y」,所以$($(x))就是$(y),於是$(a)的值就是「z」。(注意,是「x=y」,而不是「x=$(y)」)

我們還可以使用更多的層次:

x = y
y = z
z = u
a := $($($(x)))

這裡的 $(a) 的值是「u」,相關的推導留給讀者自己去做吧。

讓我們再複雜一點,使用上「在變量定義中使用變量」的第一個方式,來看一個例子:

x = $(y)
y = z
z = Hello
a := $($(x))

這裡的 $($(x)) 被替換成了 $($(y)) ,因為 $(y) 值是「z」,所以,最終結果是: a:=$(z) ,也就是「Hello」。

再複雜一點,我們再加上函數:

x = variable1
variable2 := Hello
y = $(subst 1,2,$(x))
z = y
a := $($($(z)))

這個例子中, $($($(z))) 擴展為 $($(y)) ,而其再次被擴展為 $($(subst 1,2,$(x)))$(x) 的值是「variable1」,subst函數把「variable1」中的所有「1」字串替換成「2」字串,於是,「variable1」變成 「variable2」,再取其值,所以,最終, $(a) 的值就是 $(variable2) 的值——「Hello」。(喔,好不容易)

在這種方式中,或要可以使用多個變量來組成一個變量的名字,然後再取其值:

first_second = Hello
a = first
b = second
all = $($a_$b)

這裡的 $a_$b 組成了「first_second」,於是, $(all) 的值就是「Hello」。

再來看看結闔第一種技術的例子:

a_objects := a.o b.o c.o
1_objects := 1.o 2.o 3.o

sources := $($(a1)_objects:.o=.c)

這個例子中,如果 $(a1) 的值是「a」的話,那麼, $(sources) 的值就是「a.c b.c c.c」;如果 $(a1) 的值是「1」,那麼 $(sources) 的值是「1.c 2.c 3.c」。

再來看一個這種技術和「函數」與「條件語句」一同使用的例子:

ifdef do_sort
    func := sort
else
    func := strip
endif

bar := a d b g q c

foo := $($(func) $(bar))

這個示例中,如果定義了「do_sort」,那麼: foo := $(sort a d b g q c) ,於是 $(foo) 的值就是 「a b c d g q」,而如果沒有定義「do_sort」,那麼: foo := $(strip a d b g q c) ,調用的就是strip函數。

當然,「把變量的值再當成變量」這種技術,同樣可以用在操作符的左邊:

dir = foo
$(dir)_sources := $(wildcard $(dir)/*.c)
define $(dir)_print
lpr $($(dir)_sources)
endef

這個例子中定義了三個變量:「dir」,「foo_sources」和「foo_print」。

追加變量值

我們可以使用 += 操作符給變量追加值,如:

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

於是,我們的 $(objects) 值變成:「main.o foo.o bar.o utils.o another.o」(another.o被追加進去了)

使用 += 操作符,可以模擬為下面的這種例子:

objects = main.o foo.o bar.o utils.o
objects := $(objects) another.o

所不同的是,用 += 更為簡潔。

如果變量之前沒有定義過,那麼, += 會自動變成 = ,如果前面有變量定義,那麼 += 會繼承於前次操作的賦值符。如果前一次的是 := ,那麼 += 會以 := 作為其賦值符,如:

variable := value
variable += more

等價於:

variable := value
variable := $(variable) more

但如果是這種情況:

variable = value
variable += more

由於前次的賦值符是 = ,所以 += 也會以 = 來做為賦值,那麼豈不會發生變量的遞補歸定義,這是很不好的,所以make會自動為我們解決這個問題,我們不必擔心這個問題。

override 指示符

如果有變量是通常make的命令行參數設置的,那麼Makefile中對這個變量的賦值會被忽略。如果你想在Makefile中設置這類參數的值,那麼,你可以使用「override」指示符。其語法是:

override <variable>; = <value>;
override <variable>; := <value>;

當然,你還可以追加:

override <variable>; += <more text>;

對於多行的變量定義,我們用define指示符,在define指示符前,也同樣可以使用override指示符,如:

override define foo
bar
endef

多行變量

還有一種設置變量值的方法是使用define關鍵字。使用define關鍵字設置變量的值可以有換行,這有利於定義一系列的命令(前面我們講過「命令包」的技術就是利用這個關鍵字)。

define指示符後面跟的是變量的名字,而重起一行定義變量的值,定義是以endef 關鍵字結束。其工作方式和「=」操作符一樣。變量的值可以包含函數、命令、文字,或是其它變量。因為命令需要以[Tab]鍵開頭,所以如果你用define定義的命令變量中沒有以 Tab 鍵開頭,那麼make 就不會把其認為是命令

下面的這個示例展示了define的用法:

define two-lines
echo foo
echo $(bar)
endef

環境變量

make運行時的系統環境變量可以在make開始運行時被載入到Makefile文件中,但是如果Makefile中已定義了這個變量,或是這個變量由make命令行帶入,那麼系統的環境變量的值將被覆蓋。(如果make指定了「-e」參數,那麼,系統環境變量將覆蓋Makefile中定義的變量)

因此,如果我們在環境變量中設置了 CFLAGS 環境變量,那麼我們就可以在所有的Makefile中使用這個變量了。這對於我們使用統一的編譯參數有比較大的好處。如果Makefile中定義了CFLAGS,那麼則會使用Makefile中的這個變量,如果沒有定義則使用系統環境變量的值,一個共性和個性的統一,很像「全局變量」和「局部變量」的特性。

當make嵌套調用時(參見前面的「嵌套調用」章節),上層Makefile中定義的變量會以系統環境變量的方式傳遞到下層的Makefile 中。當然,默認情況下,只有通過命令行設置的變量會被傳遞。而定義在文件中的變量,如果要向下層Makefile傳遞,則需要使用exprot關鍵字來聲明。(參見前面章節)

當然,我並不推薦把許多的變量都定義在系統環境中,這樣,在我們執行不用的Makefile時,擁有的是同一套系統變量,這可能會帶來更多的麻煩。

目標變量

前面我們所講的在Makefile中定義的變量都是「全局變量」,在整個文件,我們都可以訪問這些變量。當然,「自動化變量」除外,如 $< 等這種類量的自動化變量就屬於「規則型變量」,這種變量的值依賴於規則的目標和依賴目標的定義。

當然,我也同樣可以為某個目標設置局部變量,這種變量被稱為「Target-specific Variable」,它可以和「全局變量」同名,因為它的作用範圍只在這條規則以及連帶規則中,所以其值也只在作用範圍內有效。而不會影響規則鏈以外的全局變量的值。

其語法是:

<target ...> : <variable-assignment>;

<target ...> : overide <variable-assignment>

<variable-assignment>;可以是前面講過的各種賦值表達式,如 =:=+= 或是 ?= 。第二個語法是針對於make命令行帶入的變量,或是系統環境變量。

這個特性非常的有用,當我們設置了這樣一個變量,這個變量會作用到由這個目標所引發的所有的規則中去。如:

prog : CFLAGS = -g
prog : prog.o foo.o bar.o
    $(CC) $(CFLAGS) prog.o foo.o bar.o

prog.o : prog.c
    $(CC) $(CFLAGS) prog.c

foo.o : foo.c
    $(CC) $(CFLAGS) foo.c

bar.o : bar.c
    $(CC) $(CFLAGS) bar.c

在這個示例中,不管全局的 $(CFLAGS) 的值是什麼,在prog目標,以及其所引發的所有規則中(prog.o foo.o bar.o的規則), $(CFLAGS) 的值都是 -g

模式變量

在GNU的make中,還支持模式變量(Pattern-specific Variable),通過上面的目標變量中,我們知道,變量可以定義在某個目標上。模式變量的好處就是,我們可以給定一種「模式」,可以把變量定義在符合這種模式的所有目標上。

我們知道,make的「模式」一般是至少含有一個 % 的,所以,我們可以以如下方式給所有以 .o 結尾的目標定義目標變量:

%.o : CFLAGS = -O

同樣,模式變量的語法和「目標變量」一樣:

<pattern ...>; : <variable-assignment>;

<pattern ...>; : override <variable-assignment>;

override同樣是針對於系統環境傳入的變量,或是make命令行指定的變量。

使用條件判斷

使用條件判斷,可以讓make根據運行時的不同情況選擇不同的執行分支。條件表達式可以是比較變量的值,或是比較變量和常量的值。

example1, 判斷 $(CC) 變量是否 gcc

libs_for_gcc = -lgnu
normal_libs =

foo: $(objects)
ifeq ($(CC),gcc)
    $(CC) -o foo $(objects) $(libs_for_gcc)
else
    $(CC) -o foo $(objects) $(normal_libs)
endif

可見,在上面示例的這個規則中,目標 foo 可以根據變量 $(CC) 值來選取不同的函數庫來編譯程序。

我們可以從上面的示例中看到三個關鍵字: ifeqelseendif

  • ifeq 的意思表示條件語句的開始,並指定一個條件表達式,表達式包含兩個參數,以逗號分隔,表達式以圓括號括起
  • else 表示條件表達式為假的情況
  • endif 表示一個條件語句的結束,任何一個條件表達式都應該以 endif 結束。

當我們的變量 $(CC) 值是 gcc 時,目標 foo 的規則是:

foo: $(objects)
    $(CC) -o foo $(objects) $(libs_for_gcc)

而當我們的變量 $(CC) 值不是 gcc 時(比如 cc ),目標 foo 的規則是:

foo: $(objects)
    $(CC) -o foo $(objects) $(normal_libs)

當然,我們還可以把上面的那個例子寫得更簡潔一些:

libs_for_gcc = -lgnu
normal_libs =

ifeq ($(CC),gcc)
    libs=$(libs_for_gcc)
else
    libs=$(normal_libs)
endif

foo: $(objects)
    $(CC) -o foo $(objects) $(libs)

語法

條件表達式的語法為:

  1. ifeq is equal?
  2. ifneq is not equal?
  3. ifdef is define?
  4. ifndef is not define?
<conditional-directive>
<text-if-true>
endif

以及:

<conditional-directive>
<text-if-true>
else
<text-if-false>
endif

其中 <conditional-directive> 表示條件關鍵字,如 ifeq 。這個關鍵字有四個。

第一個是我們前面所見過的 ifeq

ifeq (<arg1>, <arg2>)
ifeq '<arg1>' '<arg2>'
ifeq "<arg1>" "<arg2>"
ifeq "<arg1>" '<arg2>'
ifeq '<arg1>' "<arg2>"

比較參數 arg1arg2 的值是否相同。當然,參數中我們還可以使用make的函數。如:

ifeq ($(strip $(foo)),)
<text-if-empty>
endif

這個示例中使用了 strip 函數,如果這個函數的返回值是空(Empty),那麼 <text-if-empty> 就生效。

第二個條件關鍵字是 ifneq 。語法是:

ifneq (<arg1>, <arg2>)
ifneq '<arg1>' '<arg2>'
ifneq "<arg1>" "<arg2>"
ifneq "<arg1>" '<arg2>'
ifneq '<arg1>' "<arg2>"

其比較參數 arg1arg2 的值是否相同,如果不同,則為真。和 ifeq 類似。

第三個條件關鍵字是 ifdef 。語法是:

ifdef <variable-name>

如果變量 <variable-name> 的值非空,那到表達式為真。否則,表達式為假。當然, <variable-name> 同樣可以是一個函數的返回值。注意, ifdef 只是測試一個變量是否有值,其並不會把變量擴展到當前位置。還是來看兩個例子:

示例一:

bar =
foo = $(bar)
ifdef foo
    frobozz = yes
else
    frobozz = no
endif

示例二:

foo =
ifdef foo
    frobozz = yes
else
    frobozz = no
endif

第一個例子中, $(frobozz) 值是 yes ,第二個則是 no。

第四個條件關鍵字是 ifndef 。其語法是:

ifndef <variable-name>

這個我就不多說了,和 ifdef` 是相反的意思。

<conditional-directive> 這一行上,多餘的空格是被允許的,但是不能以 Tab 鍵做為開始(不然就被認為是命令)。而註釋符 # 同樣也是安全的。 elseendif 也一樣,只要不是以 Tab 鍵開始就行了。

特別注意的是,make是在讀取Makefile時就計算條件表達式的值,並根據條件表達式的值來選擇語句,所以,你最好不要把自動化變量(如 $@ 等)放入條件表達式中,因為自動化變量是在運行時才有的。

而且為了避免混亂,make不允許把整個條件語句分成兩部分放在不同的文件中。

使用函数

在Makefile中可以使用函數來處理變量,從而讓我們的命令或是規則更為的靈活和具有智能。make所支持的函數也不算很多,不過已經足夠我們的操作了。函數調用後,函數的返回值可以當做變量來使用。

函數的調用語法

函數調用,很像變量的使用,也是以 $ 來標識的,其語法如下:

$(<function> <arguments>)

或是

${<function> <arguments>}

這裡, <function> 就是函數名,make支持的函數不多。 <arguments> 為函數的參數,參數間以逗號 , 分隔,而函數名和參數之間以「空格」分隔函數調用以 $ 開頭,以圓括號或花括號把函數名和參數括起。感覺很像一個變量,函數中的參數可以使用變量,為了風格的統一,函數和變量的括號最好一樣,如使用 $(subst a,b,$(x)) 這樣的形式,而不是 $(subst a,b, ${x}) 的形式。因為統一會更清楚,也會減少一些不必要的麻煩。

還是來看一個示例

comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))

在這個示例中, $(comma) 的值是一個逗號。 $(space) 使用了 $(empty) 定義了一個空格, $(foo) 的值是 a b c$(bar) 的定義用,調用了函數 subst ,這是一個替換函數,這個函數有三個參數,第一個參數是被替換字串,第二個參數是替換字串,第三個參數是替換操作作用的字串。這個函數也就是把 $(foo) 中的空格替換成逗號,所以 $(bar)的值是 a,b,c

字符串處理函數

subst

$(subst <from>,<to>,<text>)

    名稱:字符串替換函數

    功能:把字串 <text> 中的 <from> 字符串替換成 <to> 。

    返回:函數返回被替換過後的字符串。

    示例:

         $(subst ee,EE,feet on the street)

把 feet on the street 中的 ee 替換成 EE ,返回結果是 fEEt on the strEEt 。

patsubst

$(patsubst <pattern>,<replacement>,<text>)


    名稱:模式字符串替換函數。

    功能:查找 <text> 中的單詞(單詞以「空格」、「Tab」或「回車」「換行」分隔)是否符合模式 <pattern> ,如果匹配的話,則以 <replacement> 替換。
    這裡, <pattern> 可以包括通配符 % ,表示任意長度的字串。如果 <replacement> 中也包含 % ,那麼, <replacement> 中的這個 % 
    將是 <pattern> 中的那個 % 所代表的字串。(可以用 \ 來轉義,以 \% 來表示真實含義的 % 字符)

    返回:函數返回被替換過後的字符串。

    示例:

        $(patsubst %.c,%.o,x.c.c bar.c)

把字串 x.c.c bar.c 符合模式 %.c 的單詞替換成 %.o ,返回結果是 x.c.o bar.o

備註:這和我們前面「變量章節」說過的相關知識有點相似。如 $(var:<pattern>=<replacement>;) 相當於 $(patsubst <pattern>,<replacement>,$(var)) ,而 $(var: <suffix>=<replacement>) 則相當於 $(patsubst %<suffix>,%<replacement>,$(var))

例如有:

objects = foo.o bar.o baz.o,

那麼, $(objects:.o=.c)$(patsubst %.o,%.c,$(objects)) 是一樣的。

strip

$(strip <string>)

    名稱:去空格函數。

    功能:去掉 <string> 字串中開頭和結尾的空字符。

    返回:返回被去掉空格的字符串值。

    示例:

        $(strip a b c )

    把字串 " a b c " 去到開頭和結尾的空格,結果是 "a b c" 。
    

findstring

$(findstring <find>,<in>)

    名稱:查找字符串函數

    功能:在字串 <in> 中查找 <find> 字串。

    返回:如果找到,那麼返回 <find> ,否則返回空字符串。

    示例:

        $(findstring a,a b c)
        $(findstring a,b c)
    第一個函數返回 a 字符串,第二個返回空字符串

filter

$(filter <pattern...>,<text>)

    名稱:過濾函數

    功能:以 <pattern> 模式過濾 <text> 字符串中的單詞,保留符合模式 <pattern> 的單詞。可以有多個模式。

    返回:返回符合模式 <pattern> 的字串。

    示例:

        sources := foo.c bar.c baz.s ugh.h
        foo: $(sources)
            cc $(filter %.c %.s,$(sources)) -o foo

    $(filter %.c %.s,$(sources)) 返回的值是 foo.c bar.c baz.s 。

filter-out

$(filter-out <pattern...>,<text>)

    名稱:反過濾函數

    功能:以 <pattern> 模式過濾 <text> 字符串中的單詞,去除符合模式 <pattern> 的單詞。可以有多個模式。

    返回:返回不符合模式 <pattern> 的字串。

    示例:

        objects=main1.o foo.o main2.o bar.o
        mains=main1.o main2.o

    $(filter-out $(mains),$(objects)) 返回值是 foo.o bar.o 。

sort

$(sort <list>)

    名稱:排序函數
    功能:給字符串 <list> 中的單詞排序(升序)。
    返回:返回排序後的字符串。
    示例: $(sort foo bar lose) 返回 bar foo lose 。
    備註: sort 函數會去掉 <list> 中相同的單詞。

word

$(word <n>,<text>)

    名稱:取單詞函數
    功能:取字符串 <text> 中第 <n> 個單詞。(從一開始)
    返回:返回字符串 <text> 中第 <n> 個單詞。如果 <n> 比 <text> 中的單詞數要大,那麼返回空字符串。
    示例: $(word 2, foo bar baz) 返回值是 bar 。

wordlist

$(wordlist <ss>,<e>,<text>)

    名稱:取單詞串函數
    功能:從字符串 <text> 中取從 <ss> 開始到 <e> 的單詞串。 <ss> 和 <e> 是一個數字。
    返回:返回字符串 <text> 中從 <ss> 到 <e> 的單詞字串。如果 <ss> 比 <text> 中的單詞數要大,那麼返回空字符串。如果 <e> 大於 <text> 的單詞數,那麼返回從 <ss> 開始,到 <text> 結束的單詞串。
    示例: $(wordlist 2, 3, foo bar baz) 返回值是 bar baz 。

words

$(words <text>)

    名稱:單詞個數統計函數
    功能:統計 <text> 中字符串中的單詞個數。
    返回:返回 <text> 中的單詞數。
    示例: $(words, foo bar baz) 返回值是 3 。
    備註:如果我們要取 <text> 中最後的一個單詞,我們可以這樣: $(word $(words <text>),<text>) 。

firstword

$(firstword <text>)

    名稱:首單詞函數——firstword。
    功能:取字符串 <text> 中的第一個單詞。
    返回:返回字符串 <text> 的第一個單詞。
    示例: $(firstword foo bar) 返回值是 foo。
    備註:這個函數可以用 word 函數來實現: $(word 1,<text>) 。

以上,是所有的字符串操作函數,如果搭配混合使用,可以完成比較複雜的功能。這裡,舉一個現實中應用的例子。我們知道,make使用 VPATH 變量來指定「依賴文件」的搜索路徑。於是,我們可以利用這個搜索路徑來指定編譯器對頭文件的搜索路徑參數 CFLAGS ,如:

override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH)))

如果我們的 $(VPATH) 值是 src:../headers ,那麼 $(patsubst %,-I%,$(subst :, ,$(VPATH))) 將返回 -Isrc -I../headers ,這正是ccgcc搜索頭文件路徑的參數。

文件名操作函數

下面我們要介紹的函數主要是處理文件名的。每個函數的參數字符串都會被當做一個或是一系列的文件名來對待

dir

$(dir <names...>)

    名稱:取目錄函數——dir。
    功能:從文件名序列 <names> 中取出目錄部分。目錄部分是指最後一個反斜槓( / )之前的部分。如果沒有反斜槓,那麼返回 ./ 。
    返回:返回文件名序列 <names> 的目錄部分。
    示例: $(dir src/foo.c hacks) 返回值是 src/ ./ 。

notdir

$(notdir <names...>)

    名稱:取文件函數——notdir。
    功能:從文件名序列 <names> 中取出非目錄部分。非目錄部分是指最後一個反斜槓( / )之後的部分。
    返回:返回文件名序列 <names> 的非目錄部分。
    示例: $(notdir src/foo.c hacks) 返回值是 foo.c hacks 。

suffix

$(suffix <names...>)

    名稱:取後綴函數——suffix。
    功能:從文件名序列 <names> 中取出各個文件名的後綴。
    返回:返回文件名序列 <names> 的後綴序列,如果文件沒有後綴,則返回空字串。
    示例: $(suffix src/foo.c src-1.0/bar.c hacks) 返回值是 .c .c。

basename

$(basename <names...>)

    名稱:取前綴函數——basename。
    功能:從文件名序列 <names> 中取出各個文件名的前綴部分。
    返回:返回文件名序列 <names> 的前綴序列,如果文件沒有前綴,則返回空字串。
    示例: $(basename src/foo.c src-1.0/bar.c hacks) 返回值是 src/foo src-1.0/bar hacks 。

addsuffix

$(addsuffix <suffix>,<names...>)

    名稱:加後綴函數——addsuffix。
    功能:把後綴 <suffix> 加到 <names> 中的每個單詞後面。
    返回:返回加過後綴的文件名序列。
    示例: $(addsuffix .c,foo bar) 返回值是 foo.c bar.c 。

addprefix

$(addprefix <prefix>,<names...>)

    名稱:加前綴函數——addprefix。
    功能:把前綴 <prefix> 加到 <names> 中的每個單詞後面。
    返回:返回加過前綴的文件名序列。
    示例: $(addprefix src/,foo bar) 返回值是 src/foo src/bar 。

join

$(join <list1>,<list2>)

    名稱:連接函數——join。
    功能:把 <list2> 中的單詞對應地加到 <list1> 的單詞後面。如果 <list1> 的單詞個數要比 <list2> 的多,那麼, <list1> 中的多出來的單詞將保持原樣。如果 <list2> 的單詞個數要比 <list1> 多,那麼, <list2> 多出來的單詞將被覆制到 <list1> 中。
    返回:返回連接過後的字符串。
    示例: $(join aaa bbb , 111 222 333) 返回值是 aaa111 bbb222 333 。

foreach

foreach函數和別的函數非常的不一樣。因為這個函數是用來做循環用的,Makefile中的foreach函數幾乎是仿照於Unix標準Shell(/bin/sh)中的for語句,或是C-Shell(/bin/csh)中的 foreach 語句而構建的。它的語法是:

$(foreach <var>,<list>,<text>)

這個函數的意思是,把參數 <list> 中的單詞逐一取出放到參數 <var> 所指定的變量中,然後再執行 <text> 所包含的表達式。每一次 <text> 會返回一個字符串,循環過程中, <text> 的所返回的每個字符串會以空格分隔,最後當整個循環結束時, <text> 所返回的每個字符串所組成的整個字符串(以空格分隔)將會是 foreach 函數的返回值。

所以, <var> 最好是一個變量名, <list> 可以是一個表達式,而 <text> 中一般會使用 <var> 這個參數來依次枚舉 <list> 中的單詞。舉個例子:

names := a b c d

files := $(foreach n,$(names),$(n).o)

上面的例子中, $(name) 中的單詞會被挨個取出,並存到變量 n 中, $(n).o 每次根據 $(n) 計算出一個值,這些值以空格分隔,最後作為 foreach 函數的返回,所以, $(files) 的值是 a.o b.o c.o d.o

注意,foreach中的 <var> 參數是一個臨時的局部變量,foreach函數執行完後,參數 <var> 的變量將不在作用,其作用域只在 foreach 函數當中。

if

if函數很像GNU的make所支持的條件語句——ifeq(參見前面所述的章節),if函數的語法是:

$(if <condition>,<then-part>)

或是

$(if <condition>,<then-part>,<else-part>)

可見,if 函數可以包含「else」部分,或是不含。即if函數的參數可以是兩個,也可以是三個。 <condition> 參數是if的表達式,如果其返回的為非空字符串,那麼這個表達式就相當於返回真,於是, <then-part> 會被計算,否則 <else-part> 會被計算。

而if函數的返回值是,如果 <condition> 為真(非空字符串),那個 <then-part> 會是整個函數的返回值,如果 <condition> 為假(空字符串),那麼 <else-part> 會是整個函數的返回值,此時如果 <else-part> 沒有被定義,那麼,整個函數返回空字串。

所以, <then-part><else-part> 只會有一個被計算。

call

call函數

call函數是唯一一個可以用來創建新的參數化的函數。你可以寫一個非常複雜的表達式,這個表達式中,你可以定義許多參數,然後你可以call函數來向這個表達式傳遞參數。其語法是:

$(call <expression>,<parm1>,<parm2>,...,<parmn>)

當make執行這個函數時, <expression> 參數中的變量,如 $(1)$(2) 等,會被參數 <parm1><parm2> <parm3> 依次取代。而 <expression> 的返回值就是 call 函數的返回值。例如:

reverse =  $(1) $(2)

foo = $(call reverse,a,b)

那麼, foo 的值就是 a b 。當然,參數的次序是可以自定義的,不一定是順序的,如:

reverse =  $(2) $(1)

foo = $(call reverse,a,b)

此時的 foo 的值就是 b a

需要注意:在向 call 函數傳遞參數時要尤其注意空格的使用。 call 函數在處理參數時,第2個及其之後的參數中的空格會被保留,因而可能造成一些奇怪的效果。因而在向call函數提供參數時,最安全的做法是去除所有多餘的空格。

origin

origin 函數不像其它的函數,他並不操作變量的值,他只是告訴你你的這個變量是哪裡來的?其語法是:

$(origin <variable>)

注意, <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> 是一個命令運行中的自動化變量。關於自動化變量將在後面講述。

這些信息對於我們編寫Makefile是非常有用的,例如,假設我們有一個Makefile其包了一個定義文件Make.def,在 Make.def中定義了一個變量「bletch」,而我們的環境中也有一個環境變量「bletch」,此時,我們想判斷一下,如果變量來源於環境,那麼我們就把之重定義了,如果來源於Make.def或是命令行等非環境的,那麼我們就不重新定義它。於是,在我們的Makefile中,我們可以這樣寫:

ifdef bletch
    ifeq "$(origin bletch)" "environment"
        bletch = barf, gag, etc.
    endif
endif

當然,你也許會說,使用 override 關鍵字不就可以重新定義環境中的變量了嗎?為什麼需要使用這樣的步驟?是的,我們用 override 是可以達到這樣的效果,可是 override 過於粗暴,它同時會把從命令行定義的變量也覆蓋了,而我們只想重新定義環境傳來的,而不想重新定義命令行傳來的。

shell

shell函數也不像其它的函數。顧名思義,它的參數應該就是操作系統Shell的命令。它和反引號「`/``」是相同的功能。這就是說,shell函數把執行操作系統命令後的輸出作為函數返回。於是,我們可以用操作系統命令以及字符串處理命令awk,sed等等命令來生成一個變量,如:

contents := $(shell cat foo)
files := $(shell echo *.c)

注意,這個函數會新生成一個Shell程序來執行命令,所以你要注意其運行性能,如果你的Makefile中有一些比較複雜的規則,並大量使用了這個函數,那麼對於你的系統性能是有害的。特別是Makefile的隱晦的規則可能會讓你的shell函數執行的次數比你想像的多得多。

控制make的函數

make提供了一些函數來控制make的運行。通常,你需要檢測一些運行Makefile時的運行時信息,並且根據這些信息來決定,你是讓make繼續執行,還是停止。

$(error <text ...>)

產生一個致命的錯誤, <text ...> 是錯誤信息。注意,error函數不會在一被使用就會產生錯誤信息,所以如果你把其定義在某個變量中,並在後續的腳本中使用這個變量,那麼也是可以的。例如:

示例一:

ifdef ERROR_001
    $(error error is $(ERROR_001))
endif

示例二:

ERR = $(error found an error!)

.PHONY: err

err: $(ERR)

示例一會在變量 ERROR_001 定義了後執行時產生 error 調用,而示例二則在目錄 err 被執行時才發生 error 調用。

$(warning <text ...>)

這個函數很像 error 函數,只是它並不會讓make退出,只是輸出一段警告信息,而make繼續執行。

example

dir exist?

This approach will work:

foo.bak: foo.bar
    echo "foo"
    if [ -d "~/Dropbox" ]; then echo "Dir exists"; fi

Or

foo.bak: foo.bar
    echo "foo"
    if [ -d "~/Dropbox" ]; then \
        echo "Dir exists"; \
    fi

Others

Passing additional variables from command line to make

You have several options to set up variables from outside your makefile:

  • From environment - each environment variable is transformed into a makefile variable with the same name and value.

You may also want to set -e option (aka --environments-override) on, and your environment variables will override assignments made into makefile (unless these assignments themselves use the override directive . However, it’s not recommended, and it’s much better and flexible to use ?= assignment (the conditional variable assignment operator, it only has an effect if the variable is not yet defined):

FOO?=default_value_if_not_set_in_environment

Note that certain variables are not inherited from environment:

  • MAKE is gotten from name of the script

  • SHELL is either set within a makefile, or defaults to /bin/sh (rationale: commands are specified within the makefile, and they’re shell-specific).

  • From command line - make can take variable assignments as part of his command line, mingled with targets:

make target FOO=bar

But then all assignments to FOO variable within the makefile will be ignored unless you use the override directive in assignment. (The effect is the same as with -e option for environment variables).

  • Exporting from the parent Make - if you call Make from a Makefile, you usually shouldn’t explicitly write variable assignments like this:
# Don't do this!
target:
        $(MAKE) -C target CC=$(CC) CFLAGS=$(CFLAGS)

Instead, better solution might be to export these variables. Exporting a variable makes it into the environment of every shell invocation, and Make calls from these commands pick these environment variable as specified above.

# Do like this
CFLAGS=-g
export CFLAGS
target:
        $(MAKE) -C target

You can also export all variables by using export without arguments.

refer Passing additional variables from command line to make

some variable

命令的變數

  • AR 函式庫打包程式。預設命令是ar
  • AS 組合語言編譯程序。預設命令是as
  • CC C語言編譯程序。預設命令是cc
  • CXX C++語言編譯程序。預設命令是g++
  • CO 從 RCS檔中擴充檔程式。預設命令是co
  • CPP C程式的預處理器(輸出是標準輸出設備)。預設命令是$(CC) –E
  • FC Fortran 和 Ratfor 的編譯器和預處理程式。預設命令是f77
  • GET 從SCCS檔中擴充檔的程式。預設命令是get
  • LEX Lex方法分析器程式(針對於C或Ratfor)。預設命令是lex
  • PC Pascal語言編譯程序。預設命令是pc
  • YACC Yacc文法分析器(針對於C程式)。預設命令是yacc
  • YACCR Yacc文法分析器(針對於Ratfor程式)。預設命令是yacc –r
  • MAKEINFO 轉換Texinfo原始檔案(.texi)到Info檔程式。預設命令是makeinfo
  • TEX 從TeX原始檔案建立TeX DVI檔的程式。預設命令是tex
  • TEXI2DVI 從Texinfo原始檔案建立軍TeX DVI 檔的程式。預設命令是texi2dvi
  • WEAVE 轉換Web到TeX的程式。預設命令是weave
  • CWEAVE 轉換C Web 到 TeX的程式。預設命令是cweave
  • TANGLE 轉換Web到Pascal語言的程式。預設命令是tangle
  • CTANGLE 轉換C Web 到 C。預設命令是ctangle
  • RM 刪除檔命令。預設命令是rm –f

命令參數變數

  • ARFLAGS 函式庫打包程式AR命令的參數。預設值是rv
  • ASFLAGS 組合語言編譯器參數。(當明顯地調用.s.S檔案時)。
  • CFLAGS C語言編譯器參數。
  • CXXFLAGS C++語言編譯器參數。
  • COFLAGS RCS命令參數。
  • CPPFLAGS C預處理器參數。( C 和 Fortran 編譯器也會用到)。
  • FFLAGS Fortran語言編譯器參數。
  • GFLAGS SCCS get程式參數。
  • LDFLAGS 鏈結器參數。(如:ld
  • LFLAGS Lex文法分析器參數。
  • PFLAGS Pascal語言編譯器參數。
  • RFLAGS Ratfor 程式的Fortran 編譯器參數。
  • YFLAGS Yacc文法分析器參數。

extra

MAKELEVEL - 如果我們的make有一個嵌套執行的動作(參見前面的「嵌套使用make」),那麼,這個變量會記錄了我們的當前Makefile的調用層數