Linux 下的make命令與Makefile

  1.  概述

博客內容包含linux下make命令的使用與makefile的書寫規則等,希望通過本文檔使讀者對make命令makefile文件有進一步瞭解,由於鄙人經驗學識有限文檔中會有描述不準確以及理解偏差,歡迎讀者指正。[email protected]

  1. 從一隻貓說起hello kitty
  2. linux系統中的make命令與makefile文件
  1. 在linux系統中make是一個非常重要的編譯命令,不管是自己進行項目開發還是安裝應用軟件,我們都經常要用到make或makeinstall。利用make工具,我們可以將大型的開發項目分解成爲多個更易於管理的模塊,一個工程中的源文件不計數,其按類型、功能、模塊分別放在若干個目錄中,makefile定義了一系列的規則來指定,哪些文件需要先編譯,哪些文件需要後編譯,哪些文件需要重新編譯,甚至於進行更復雜的功能操作,因爲makefile就像一個Shell腳本一樣,其中也可以執行操作系統的命令。makefile 帶來的好處就是“自動化編譯”,一旦寫好,只需要一個make命令,整個工程完全自動編譯,極大的提高了軟件開發的效率。

  1. 當make 命令被執行時,它會掃描當前目錄下Makefile或makefile文件找到目標以及其依賴。如果這些依賴自身也是目標,繼續爲這些依賴掃描Makefile 建立其依賴關係,然後編譯它們。一旦主依賴編譯之後,然後就編譯主目標,假設你對某個源文件進行了修改,你再次執行make 命令,它將只編譯與該源文件相關的目標文件,因此,編譯完最終的可執行文件節省了大量的時間。

  • Make命令的參數

    -f:指定“makefile”文件;

    -i:忽略命令執行返回的出錯信息;

    -s:沉默模式,在執行之前不輸出相應的命令行信息;

    -r:禁止使用build-in規則;

    -n:非執行模式,輸出所有執行命令,但並不執行;

    -t:更新目標文件;

    -q:make操作將根據目標文件是否已經更新返回"0"或非"0"的狀態信息;

    -p:輸出所有宏定義和目標文件描述;

    -d:Debug模式,輸出有關文件和檢測時間的詳細信息。

    -C dir:在讀取makefile 之前改變到指定的目錄dir;

    -I dir:當包含其他makefile文件時,利用該選項指定搜索目錄;

    -h:help文擋,顯示所有的make選項;

    -w:在處理makefile之前和之後,都顯示工作目錄。

  • make命令隱藏了什麼

    linux 編譯hello_kitty 只需要簡單的make hello_kitty

    上述過程可分解爲四部分,預處理(Propressing),編譯(Compilation),彙編(Assembly),鏈接(Linking).
    如下圖所示:


     

  • 預編譯
  • 預編譯器cpp 將hello_kitty.c與stdio.h編譯成.i 文件,c++的源代碼文件擴展名爲cpp或cxx,頭文件擴展名爲hpp,而與編譯後的文件爲.ii。預編譯過程相當於執行

     gcc -E hello_kitty.c -o hello_kitty.i

    預編譯過程主要處理以#開頭的預處理指令,#include #define等,處理過程如下:

    1 將所有#define刪除,並展開所有宏定義

    2 處理所有條件預編譯指令#if #ifdef #elif #else #endif等

    3 處理#include 預編譯指令,將包含的文件插入到預編譯指令的位置(遞歸進行,所包含的文件可能包含其他文件)

    4 刪除所有註釋// /* */

    5 添加行號和文件名標識,如:#2 hello_kitty.c 2 ,用於編譯時編譯器產生調試信息和編譯時產生的錯誤和警告時能顯示行號。

    6 保留說有的#pragma編譯器指令

    經過預編譯的.i文件,不包含任何宏定義,並且所包含的文件也被插入進來。

  • 編譯
  • 編譯過程就是將預編譯產生的.i文件經過一系列的詞法分析,語法分析,語意分析,優化後產生彙編代碼文件,.s (詳情可查閱《編譯原理》或《編譯系統透視》)相當於

    gcc -S hello_kitty.i -o hello_kitty.s

    高版本的GCC將預編譯和編譯合爲一步,後臺調用ccl來完成,預編譯和編譯,我們可以用

    ccl hello_kitty.c

    gcc -S hello_kitty.c -o hello_kittu.s

    都可以得到hello_kitty.s文件

    gcc只是GCC編譯器後臺程序的包裝,他會根據不同的參數來掉用後臺程序

    如ccl cclplus jcl 等(深入學習可參考《深入分析gcc》或《自制編譯器》)

  • 彙編
  • 彙編器將彙編代碼轉換成成機器指令,每一條彙編語句對應一條或幾條機器指令,根據彙編指令和機器指令一一翻譯的過程,彙編過程我們可以用

    as hello_kitty.s -o hello_kitty.o

    gcc -c hello_kitty.s -o hello_kitty.o來完成。

    或者使用gcc 命令從c源文件直接生成目標文件

    gcc -c hello_kitty.c -o hello_kitty.o 

  • 鏈接
  • 將庫文件與目標文件鏈接成可執行文件的過程。

  • make命令的運行
  • make最簡單的用法就是直接在命令行下輸入make命令,make命令會找當前目錄的makefile來執行,一切都是自動的。或者make targetfile ,但也有時你也許只想讓make重編譯某些文件,而不是整個工程,而又有的時候你有幾套編譯規則,想在不同的時候使用不同的編譯規則,等等,本章節就是講述如何使用make命令的使用。

  • make的退出碼
  • make命令執行後有三個退出碼:

    0 表示成功執行。

    1 如果make運行時出現任何錯誤,其返回1。

    2 如果你使用了make的“-q”選項,並且make使得一些目標不需要更新,那麼返回2。

  • 指定makefile
  • GNU make找尋默認的Makefile的規則是在當前目錄下依次找三個文件“GNUmakefile”、“makefile”和“Makefile”。其按順序找這三個文件,一旦找到,就開始讀取這個文件並執行。

    當前,我們也可以給make命令指定一個特殊名字的Makefile。要達到這個功能,我們要使用make的-f 或是--file 參數(--makefile 參數也行)。例如,我們有個makefile的名字是“hchen.mk”,那麼,我們可以這樣來讓make來執行這個文件:make -f hchen.mk

  • 指定目標
  • make的最終目標是makefile中的第一個目標,而其它目標一般是由這個目標連帶出來的。這是make的默認行爲。當然,你的makefile中的第一個目標是由許多個目標組成,你可以指示make,讓其完成你所指定的目標。要達到這一目的很簡單,需在make命令後直接跟目標的名字就可以完成(如前面提到的“make hello_kitty”形式)任何在makefile中的目標都可以被指定成終極目標,甚至沒有被我們明確寫出來的目標也可以成爲make的終極目標,也就是說,只要make可以找到其隱含規則推導規則,那麼這個隱含目標同樣可以被指定成終極目標。

    有一個make的環境變量叫MAKECMDGOALS,這個變量中會存放你所指定的終極目標的列表,如果在命令行上,你沒有指定目標,那麼,這個變量是空值。這個變量可以讓你使用在一些比較特殊的情形下。比如下面的例子:

    sources = foo.c bar.c

    ifneq ( $(MAKECMDGOALS),clean)

    include $(sources:.c=.d)

    endif

    基於上面的這個例子,只要我們輸入的命令不是“make clean”,那麼makefile會自動包含“foo.d”和“bar.d”這兩個makefile。

    使用指定終極目標的方法可以很方便地讓我們編譯我們的程序,例如下面這個例子:

    .PHONY: all

    all: pro1 pro2 pro3 pro4

    從這個例子中,我們可以看到,這個makefile中有四個需要編譯的程序——“pro1”,“pro2”,

    “pro3”和“pro4”,我們可以使用“make all”命令來編譯所有的目標(如果把all置成第一個目標,那麼只需執行“make”),我們也可以使用“make pro2”來單獨編譯目標“pro2”

    即然make可以指定所有makefile中的目標,那麼也包括“僞目標”,我們可以根據這種性質來讓我們的makefile根據指定的不同的目標來完成不同的事。在Unix世界中,軟件發佈時,特別是GNU這種開源軟件的發佈時,其makefile都包含了編譯、安裝、打包等功能。

    我們可以參照這種規則來書寫我們的makefile中的目標。

    all:這個僞目標是所有目標的目標,其功能一般是編譯所有的目標。

    clean:這個僞目標功能是刪除所有被make創建的文件。

    install:這個僞目標功能是安裝已編譯好的程序,其實就是把目標執行文件拷貝到指定的目標中去。

    print:這個僞目標的功能是例出改變過的源文件。

    tar:這個僞目標功能是把源程序打包備份。也就是一個tar文件。

    dist:這個僞目標功能是創建一個壓縮文件,一般是把tar文件壓成Z文件。或是gz文件。

    TAGS:這個僞目標功能是更新所有的目標,以備完整地重編譯使用。

    check和test:這兩個僞目標一般用來測試makefile的流程。

    如果你要書寫這種功能,最好使用這種名字命名你的目標,這樣規範一些,規範的好處就是——不用解釋,大家都明白。

  • 檢查規則
  • 有時候,我們不想讓我們的makefile中的規則執行起來,我們只想檢查一下我們的命令,或是執行的序列。於是我們可以使用make命令的下述參數:

    -n, --just-print, --dry-run, --recon 不執行參數,這些參數只是打印命令,不管目標是否更新,把規則和連帶規則下的命令打印出來,但不執行,這些參數對於我們調

    試makefile很有用處。

    -t, --touch 這個參數的意思就是把目標文件的時間更新,但不更改目標文件。也就是說,make假裝編譯目標,但不是真正的編譯目標,只是把目標變成已編譯過的狀態。

    -q, --question 這個參數的行爲是找目標的意思,也就是說,如果目標存在,那麼其什麼也不會輸出,當然也不會執行編譯,如果目標不存在,其會打印出一條出錯信息。

    -W <file>, --what-if=<file>, --assume-new=<file>, --new-file=<file> 

    這個參數需要指定一個文件。一般是是源文件(或依賴文件),Make會根據規則推導來運行依賴於這個文件的命令,一般來說,可以和“-n”參數一同使用,來查看這個依賴文件所發生的規則命令。

    另外一個很有意思的用法是結合-p 和-v 來輸出makefile被執行時的信息。

  • make命令的參數
  • 下面列舉了所有GNU make 3.80版的參數定義。其它版本和產商的make大同小異,不過其它產

    商的make的具體參數還是請參考各自的產品文檔。

    -b, -m 這兩個參數的作用是忽略和其它版本make的兼容性。

    -B, --always-make 認爲所有的目標都需要更新(重編譯)。

    -C <dir>, --directory=<dir> 指定讀取makefile的目錄。如果有多個“-C”參數,make的解釋是後面的路徑以前面的作爲相對路徑,並以最後的目錄作爲被指定目錄。

    如:“make -C ~/fany/test/prog”。

    -debug[=<options>] 輸出make的調試信息。它有幾種不同的級別可供選擇,如果沒有參數,那就是輸出最簡單的調試信息。下面是<options>的取值:

    a: 也就是all,輸出所有的調試信息。(會非常的多)

    b: 也就是basic,只輸出簡單的調試信息。即輸出不需要重編譯的目標。

    v: 也就是verbose,在b選項的級別之上。輸出的信息包括哪個makefile被解析,不需要被重編譯的依賴文件(或是依賴目標)等。

    i: 也就是implicit,輸出所以的隱含規則。

    j: 也就是jobs,輸出執行規則中命令的詳細信息,如命令的PID、返回碼等。

    m: 也就是makefile,輸出make讀取makefile,更新makefile,執行makefile的信息。

    -d 相當於“–debug=a”。

    -e, --environment-overrides 指明環境變量的值覆蓋makefile中定義的變量的值。

    -f=<file>, --file=<file>, --makefile=<file> 指定需要執行的makefile。

    -h, --help 顯示幫助信息。

    -i , --ignore-errors 在執行時忽略所有的錯誤。

    -I <dir>, --include-dir=<dir> 指定一個被包含makefile的搜索目標。可以使用多個“-I”參數來指定多個目錄。

    -j [<jobsnum>], --jobs[=<jobsnum>] 指同時運行命令的個數。如果沒有這個參數,make運行命令時能運行多少就運行多少。如果有一個以上的“-j”參數,那麼僅最後一個“-j”纔是

    有效的。

    -k, --keep-going 出錯也不停止運行。如果生成一個目標失敗了,那麼依賴於其上的目標就不會被執行了。

    -l <load>, --load-average[=<load>], -max-load[=<load>] 指定make運行命令的負載。

    -n, --just-print, --dry-run, --recon 僅輸出執行過程中的命令序列,但並不執行。

    -o <file>, --old-file=<file>, --assume-old=<file> 不重新生成的指定的<file>,即使這個目標的依賴文件新於它。

    -p, --print-data-base 輸出makefile中的所有數據,包括所有的規則和變量。這個參數會讓一個簡單的makefile都會輸出一堆信息。如果你只是想輸出信息而不想執行makefile,

    你可以使用“make -qp”命令。如果你想查看執行makefile前的預設變量和規則,你可以使用“make –p –f /dev/null”。這個參數輸出的信息會包含着你的makefile文件的文件名和行號,所以,用這個參數來調試你的makefile會是很有用的,特別是當你的環境變量很複雜的時候。

    -q, --question 不運行命令,也不輸出。僅僅是檢查所指定的目標是否需要更新。如果是0則說明要更新,如果是2則說明有錯誤發生。

    -r, --no-builtin-rules 禁止make使用任何隱含規則。

    -R, --no-builtin-variabes 禁止make使用任何作用於變量上的隱含規則。

    -s, --silent, --quiet 在命令運行時不輸出命令的輸出。

    -S, --no-keep-going, --stop 取消“-k”選項的作用。因爲有些時候,make的選項是從環境變量“MAKEFLAGS”中繼承下來的。所以你可以在命令行中使用這個參數來讓環境變量中的“-k”選項失效。

    -t, --touch 相當於UNIX的touch命令,只是把目標的修改日期變成最新的,也就是阻止生成目標的命令運行。

    -v, --version 輸出make程序的版本、版權等關於make的信息。

    -w, --print-directory 輸出運行makefile之前和之後的信息。這個參數對於跟蹤嵌套式調用make時很有用。

    --no-print-directory 禁止“-w”選項。

    -W <file>, --what-if=<file>, --new-file=<file>, --assume-file=<file> 假定目標<file>;需要更新,如果和“-n”選項使用,那麼這個參數會輸出該目標更新時的運行動作。如果沒有“-n”那麼就像運行UNIX的“touch”命令一樣,使得<file>;的修改時間爲當前時間。

    --warn-undefined-variables 只要make發現有未定義的變量,那麼就輸出警告信息。

  • make的隱含規則
  • “隱含規則"也就是一種慣例,make會按照這種“慣例”來運行,哪怕我們的Makefile中沒有書寫這樣的規則。例如,把.c文件編譯成.o文件這一規則,你根本就不用寫出來,make會自動推導出這種規則,並生成我們需要的.o 文件。

    “隱含規則”會使用一些我們系統變量,我們可以改變這些系統變量的值來定製隱含規則的運行時的參數。如系統變量CFLAGS 可以控制編譯時的編譯器參數。我們還可以通過“模式規則”的方式寫下自己的隱含規則。用“後綴規則”來定義隱含規則會有許多的限制。使用“模式規則”會更回得智能和清楚,但“後綴規則”可以用來保證我們Makefile的兼容性。我們瞭解了“隱含規則”,可以讓其爲我們更好的服務,也會讓我們知道一些“約定俗成”了的東西,而不至於使得我們在運行Makefile時出現一些我們覺得莫名其妙的東西。有時候“隱含規則”也會給我們造成不小的麻煩。只有瞭解了它,我們才能更好地使用它。

  • 使用隱含規則
  • 如果要使用隱含規則生成你需要的目標,你所需要做的就是不要寫出這個目標的規則。那麼,make會試圖去自動推導產生這個目標的規則和命令,如果make可以自動推導生成這個目標的規則和命令,那麼這個行爲就是隱含規則的自動推導。當然,隱含規則是make事先約定好的一些東西。

    例如,我們有下面的一個Makefile:

    hello_kitty : hello.o kitty.o

    cc -o hello_kitty hello.o kitty.o $(CFLAGS) $(LDFLAGS)

    我們可以注意到,這個Makefile中並沒有寫下如何生成hello.o和kitty.o這兩目標的規則和命令。因爲make的“隱含規則”功能會自動爲我們自動去推導這兩個目標的依賴目標和生成命令。make會在自己的“隱含規則”庫中尋找可以用的規則,如果找到,那麼就會使用。如果找不到,那麼就會報錯。在上面的那個例子中,make調用的隱含規則是,把.o 的目標的依賴文件設置成.c ,並使用C的編譯命令cc -c $(CFLAGS) hello.c 來生成hello.o 的目標。也就是說,我們完全沒有必要寫下下面的兩條規則:

    hello.o : hello.c

    cc -c hello.c $(CFLAGS)

    kitty.o : kitty.c

    cc -c kitty.c $(CFLAGS)

    因爲,這已經是“約定”好了的事了,make和我們約定好了用C編譯器cc 生成.o 文件的規則,這就是隱含規則。當然,如果我們爲.o 文件書寫了自己的規則,那麼make就不會自動推導並調用隱含規則,它會按照我們寫好的規則地執行。還有,在make的“隱含規則庫”中,每一條隱含規則都在庫中有其順序,越靠前的則是越被經常使用的,所以,這會導致我們有些時候即使我們顯示地指定了目標依賴,make也不會管。

    如下面這條規則:

    hello.o : hello.p

    依賴文件hello.p (Pascal程序的源文件)有可能變得沒有意義。如果目錄下存在了hello.c 文件,那麼我們的隱含規則一樣會生效,並會通過hello.c 調用C的編譯器生成hello.o 文件。因爲,在隱含規則中,Pascal的規則出現在C的規則之後,所以,make找到可以生成hello.o的C的規則就不再尋找下一條規則了。如果你確實不希望任何隱含規則推導,那麼,你就不要只寫出“依賴規則”,而不寫命令。

  • 隱含規則列表
  • 這裏我們將講述所有預先設置(也就是make內建)的隱含規則,如果我們不明確地寫下規則,那麼,make就會在這些規則中尋找所需要規則和命令。當然,我們也可以使用make的參數-r或--no-builtin-rules選項來取消所有的預設置的隱含規則。當然,即使是我們指定了-r參數,某些隱含規則還是會生效,因爲有許多的隱含規則都是使用了“後綴規則”來定義的,所以,只要隱含規則中有“後綴列表”(也就一系統定義在目標.SUFFIXES的依賴目標),那麼隱含規則就會生效。默認的後綴列表是:.out, .a, .ln, .o, .c, .cc,.C, .p, .f, .F, .r, .y, .l, .s, .S, .mod, .sym, .def, .h, .info,.dvi,.tex,.texinfo,.texi,.txinfo,.w,.ch.web,.sh,.elc,.el。常用的隱含規則如下:

    1. 編譯C程序的隱含規則。

    <n>.o 的目標的依賴目標會自動推導爲<n>.c ,並且其生成命令是

    $(CC) -c $(CPPFLAGS) $(CFLAGS)

    2. 編譯C++程序的隱含規則。

    <n>.o 的目標的依賴目標會自動推導爲<n>.cc 或是<n>.C ,並且其生成命令是

    $(CXX) -c $(CPPFLAGS) $(CFLAGS) 。

    3. 編譯Pascal程序的隱含規則。

    <n>.o 的目標的依賴目標會自動推導爲<n>.p ,並且其生成命令是

    $(PC) -c $(PFLAGS) 。

  • 隱含規則使用的變量
  • 在隱含規則中的命令中,基本上都是使用了一些預先設置的變量。你可以在你的makefile中改變這些變量的值,或是在make的命令行中傳入這些值,或是在你的環境變量中設置這些值,無論怎麼樣,只要設置了這些特定的變量,那麼其就會對隱含規則起作用。當然,你也可以利用make的-R或--no-builtin-variables 參數來取消你所定義的變量對隱含規則的作用。

    例如,第一條隱含規則——編譯C程序的隱含規則的命令是$(CC) -c $(CFLAGS) $(CPPFLAGS)Make默認的編譯命令是cc,如果你把變量$(CC)重定義成gcc,把變量$(CFLAGS)重定義成-g,

    那麼,隱含規則中的命令全部會以gcc -c -g $(CPPFLAGS)的樣子來執行了。

    我們可以把隱含規則中使用的變量分成兩種:一種是命令相關的,如CC;一種是參數相的關,如CFLAGS。下面是所有隱含規則中會用到的變量:

    •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文法分析器參數

  • 定義模式規則
  • 你可以使用模式規則來定義一個隱含規則。一個模式規則就好像一個一般的規則,只是在規則中,目標的定義需要有% 字符。% 的意思是表示一個或多個任意字符。在依賴目標中同樣可以使用% ,只是依賴目標中的% 的取值,取決於其目標。有一點需要注意的是,% 的展開發生在變量和函數的展開之後,變量和函數的展開發生在make載入Makefile時,而模式規則中的% 則發生在運行時。

  • 模式規則介紹
  • 模式規則中,至少在規則的目標定義中要包含%,否則,就是一般的規則。目標中的% 定義表示對文件名的匹配,%表示長度任意的非空字符串。

    例如:%.c 表示以.c 結尾的文件名(文件名的長度至少爲3),而s.%.c 則表示以s.開頭,.c 結尾的文件名(文件名的長度至少爲5)。

    如果%定義在目標中,那麼,目標中的%的值決定了依賴目標中的%的值,也就是說,目標中的模式的%決定了依賴目標中%的樣子。

    例如有一個模式規則如下:

    %.o : %.c ; <command ......>;

    其含義是,指出了怎麼從所有的.c 文件生成相應的.o 文件的規則。如果要生成的目標是a.ob.o ,那麼%c 就是a.c b.c 。

    一旦依賴目標中的% 模式被確定,那麼,make會被要求去匹配當前目錄下所有的文件名,一旦找到,make就會規則下的命令,所以,在模式規則中,目標可能會是多個的,如果有模式匹配出多個目標,make就會產生所有的模式目標,此時,make關心的是依賴的文件名和生成目標的命令這兩件事。

  • 模式規則示例
  • 下面這個例子表示了,把所有的.c 文件都編譯成.o 文件.

    %.o : %.c

    $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

    其中,$@ 表示所有的目標的挨個值,$< 表示了所有依賴目標的挨個值。這些奇怪的變量我們叫“自動化變量”,後面會詳細講述。

  • 自動化變量
  • 在上述的模式規則中,目標和依賴文件都是一系列的文件,那麼我們如何書寫一個命令來完成從不同的依賴文件生成相應的目標?因爲在每一次的對模式規則的解析時,都會是不同的目標和依賴文件。自動化變量就是完成這個功能的。在前面,我們已經對自動化變量有所提涉,相信你看到這裏已對它有一個感性認識了。所謂自動化變量,就是這種變量會把模式中所定義的一系列的文件自動地挨個取出,直至所有的符合模式的文件都取完了。這種自動化變量只應出現在規則的命令中。

    下面是所有的自動化變量及其說明:

    •$@ : 表示規則中的目標文件集。在模式規則中,如果有多個目標,那麼,$@ 就是匹配於目標中模式定義的集合。

    •$% : 僅當目標是函數庫文件中,表示規則中的目標成員名。例如,如果一個目標是foo.a(bar.o),那麼,$%就是bar.o,$@就是foo.a。如果目標不是函數庫文件那麼,其值爲空。

    •$< : 依賴目標中的第一個目標名字。如果依賴目標是以模式(即%)定義的,那麼$<將是符合模式的一系列的文件集。注意,其是一個一個取出來的。

    •$? : 所有比目標新的依賴目標的集合。以空格分隔。

    •$^ : 所有的依賴目標的集合。以空格分隔。如果在依賴目標中有多個重複的,那個這個變量會去除重複的依賴目標,只保留一份。

    •$+ : 這個變量很像$^ ,也是所有依賴目標的集合。只是它不去除重複的依賴目標。

    •$* : 這個變量表示目標模式中% 及其之前的部分。如果目標是dir/a.foo.b,並且目標的模式是a.%.b,那麼,$*的值就是dir/a.foo。

    這個變量對於構造有關聯的文件名是比較有較。如果目標中沒有模式的定義,那麼$*也就不能被推導出,但是,如果目標文件的後綴是make所識別的,那麼$*就是除了後綴的那一部分。例如:如果目標是foo.c,因爲.c是make所能識別的後綴名,所以,$*的值就是foo。這個特性是GNUmake的,很有可能不兼容於其它版本的make,所以,你應該儘量避免使用$*,除非是在隱含規則或是靜態模式中。如果目標中的後綴是make所不能識別的,那麼$*就是空值。

    當你希望只對更新過的依賴文件進行操作時,$?在顯式規則中很有用,例如,假設有一個函數庫文件叫lib,其由其它幾個object文件更新。那麼把object文件打包的比較有效率的Makefile規則是:

    lib : foo.o bar.o lose.o win.o

    ar r lib $?

    在上述所列出來的自動量變量中。四個變量($@ 、$< 、$% 、$* )在擴展時只會有一個文件,而另三個的值是一個文件列表。這七個自動化變量還可以取得文件的目錄名或是在當前目錄下的符合模式的文件名,只需要搭配上D 或F 字樣。這是GNUmake中老版本的特性,在新版本中,我們使用函數dir或notdir就可以做到了。D的含義就是Directory,就是目錄,F的含義就是File,就是文件。下面是對於上面的七個變量分別加上D 或是F 的含義:

    $(@D) 表示$@ 的目錄部分(不以斜槓作爲結尾),如果$@ 值是dir/foo.o ,那麼$(@D)就是dir,而如果$@中沒有包含斜槓的話,其值就是.(當前目錄)。

    $(@F) 表示$@ 的文件部分,如果$@ 值是dir/foo.o ,那麼$(@F) 就是foo.o ,$(@F)相當於函數$(notdir $@) 。

    $(*D), $(*F) 和上面所述的同理,也是取文件的目錄部分和文件部分。對於上面的那個例子,$(*D) 返回dir ,而$(*F) 返回foo

    $(%D), $(%F) 分別表示了函數包文件成員的目錄部分和文件部分。這對於形同archive(member)形式的目標中的member中包含了不同的目錄很有用。

    $(<D), $(<F) 分別表示依賴文件的目錄部分和文件部分。

    $(^D), $(^F) 分別表示所有依賴文件的目錄部分和文件部分。(無相同的)

    $(+D), $(+F) 分別表示所有依賴文件的目錄部分和文件部分。(可以有相同的)

    $(?D), $(?F) 分別表示被更新的依賴文件的目錄部分和文件部分。

    最後想提醒一下的是,對於$< ,爲了避免產生不必要的麻煩,我們最好給$後面的那個特定字符都加上圓括號,比如,$(<) 就要比$< 要好一些。

    還得要注意的是,這些變量只使用在規則的命令中,而且一般都是“顯式規則”和“靜態模式規則”其在隱含規則中並沒有意義。

  • 模式的匹配
  • 一般來說,一個目標的模式有一個有前綴或是後綴的% ,或是沒有前後綴,直接就是一個% 。

    因爲% 代表一個或多個字符,所以在定義好了的模式中,我們把%所匹配的內容叫做“莖”,例如%.c所匹配的文件“test.c”中“test”就是“莖”。因爲在目標和依賴目標中同時有%時,依賴目標的“莖”會傳給目標,當做目標中的“莖”。當一個模式匹配包含有斜槓(實際也不經常包含)的文件時,那麼在進行模式匹配時,目錄部分會首先被移開,然後進行匹配,成功後,再把目錄加回去。在進行“莖”的傳遞時,我們需要知道這個步驟。例如有一個模式e%t ,文件src/eat 匹配於該模式,於是src/a 就是其“莖”,如果這個模式定義在依賴目標中,而被依賴於這個模式的目標中又有個模式c%r,那麼,目標就是src/car 。(“莖”被傳遞)

  • 重載內建隱含規則
  • 你可以重載內建的隱含規則(或是定義一個全新的),例如你可以重新構造和內建隱含規則不同的命令,如:

    %.o : %.c

    $(CC) -c $(CPPFLAGS) $(CFLAGS) -D$(date)

    你可以取消內建的隱含規則,只要不在後面寫命令就行。如:

    %.o : %.s

    同樣,你也可以重新定義一個全新的隱含規則,其在隱含規則中的位置取決於你在哪裏寫下這個規則。

  • 隱含規則搜索算法
  • 比如我們有一個目標叫T。下面是搜索目標T的規則的算法。

    請注意,在下面,我們沒有提到後綴規則,原因是,所有的後綴規則在Makefile被載入內存時,會被轉換成模式規則。如果目標是archive(member)的函數庫文件模式,那麼這個算法會被運行兩次,第一次是找目標T,如果沒有找到的話,那麼進入第二次,第二次會把member 當作T來搜索。

    1. 把T的目錄部分分離出來。叫D,而剩餘部分叫N。(如:如果T是src/foo.o ,那麼,D就是src/ ,N就是foo.o )

    2. 創建所有匹配於T或是N的模式規則列表

    3. 如果在模式規則列表中有匹配所有文件的模式,如% ,那麼從列表中移除其它的模式。

    4. 移除列表中沒有命令的規則。

    5. 對於第一個在列表中的模式規則:

    (a) 推導其“莖”S,S應該是T或是N匹配於模式中% 非空的部分。

    (b) 計算依賴文件。把依賴文件中的% 都替換成“莖”S。如果目標模式中沒有包含斜框字符,而把D加在第一個依賴文件的開頭。

    (c) 測試是否所有的依賴文件都存在或是理當存在。(如果有一個文件被定義成另外一個規則的目標文件,或者是一個顯式規則的依賴文件,那麼這個文件就叫“理當存在”)

    (d) 如果所有的依賴文件存在或是理當存在,或是就沒有依賴文件。那麼這條規則將被採用,退出該算法。

    6. 如果經過第5步,沒有模式規則被找到,那麼就做更進一步的搜索。對於存在於列表中的第一個模式規則:

    (a) 如果規則是終止規則,那就忽略它,繼續下一條模式規則。

    (b) 計算依賴文件。(同第5步)

    (c) 測試所有的依賴文件是否存在或是理當存在。

    (d) 對於不存在的依賴文件,遞歸調用這個算法查找他是否可以被隱含規則找到。

    (e) 如果所有的依賴文件存在或是理當存在,或是就根本沒有依賴文件。那麼這條規則被採用,退出該算法。

    (f) 如果沒有隱含規則可以使用,查看.DEFAULT 規則,如果有,採用,把.DEFAULT的命令給T使用。一旦規則被找到,就會執行其相當的命令,而此時,我們的自動化變量的值纔會生成。

     

  •  

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