Makefile入門


1.Makefile概述:

  什么是makefile?或許很多Winodws的程序員都不知道這個東西,因為那些Windows的IDE都為你做了這個工作,但我覺得要作一個好的和professional的程序員,makefile還是要懂。這就好像現在有這么多的HTML的編輯器,但如果你想成為一個專業人士,你還是要了解HTML的標識的含義。特別在Unix下的軟件編譯,你就不能不自己寫makefile了,會不會寫makefile,從一個側面說明了一個人是否具備完成大型工程的能力。

  因為,makefile關系到了整個工程的編譯規則。一個工程中的源文件不計數,其按類型、功能、模塊分別放在若干個目錄中,makefile定義了一系列的規則來指定,哪些文件需要先編譯,哪些文件需要后編譯,哪些文件需要重新編譯,甚至於進行更復雜的功能操作,因為makefile就像一個Shell腳本一樣,其中也可以執行操作系統的命令。

  makefile帶來的好處就是——“自動化編譯”,一旦寫好,只需要一個make命令,整個工程完全自動編譯,極大的提高了軟件開發的效率。make是一個命令工具,是一個解釋makefile中指令的命令工具,一般來說,大多數的IDE都有這個命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可見,makefile都成為了一種在工程方面的編譯方法。

  在這篇文檔中,將以C/C++的源碼作為我們基礎,所以必然涉及一些關於C/C++的編譯的知識,相關於這方面的內容,還請各位查看相關的編譯器的文檔。這里所默認的編譯器是UNIX下的GCC和CC。

2.Makefile規則(顯示規則, 隱晦規則, 變量定義, 文件指示, 注釋)

   2.1Makefile基本編寫格式:

target ... : prerequisites ...
    command
    ...
    ...

  target:    目標文件,可以是Object File,也可以是執行文件。還可以是一個標簽(Label)

  prerequisites: 要生成那個target所依賴的文件或是目標。

  command:   也就是make需要執行的命令。(任意的Shell命令)

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

  2.2編寫規則說明:

  1. 顯示規則 :: 說明如何生成一個或多個目標文件(包括 生成的文件, 文件的依賴文件, 生成的命令)
  2. 隱晦規則 :: make的自動推導功能所執行的規則($@ $* $^ ...)
  3. 變量定義 :: Makefile中定義的變量
  4. 文件指示 ::其包括了三個部分,一個是在一個Makefile中引用另一個Makefile,就像C語言中的include一樣;另一個是指根據某些情況指定Makefile中的有效部分,就像C語言  中的預編譯#if一樣;還有就是定義一個多行的命令。有關這一部分的內容,我會在后續的部分中講述。
  5. 注釋     :: Makefile只有行注釋 "#", 如果要使用或者輸出"#"字符, 需要進行轉義, "\#"

  簡單Makefile展示如下:

include ../Make.defines

PROGS =    test01 \
           test02

all:    ${PROGS}
test01:    test01.o
        ${CC} ${CFLAGS} -o $@ test01.o ${LIBS}
test02:    test02.o
        ${CC} ${CFLAGS} -o $@ test02.o ${LIBS}
        
clean:
        rm -f ${PROGS} ${CLEANFILES}

   事例中幾點注意如下:

  1.首行導入了其他的Makefile相關文件,事例假定此文件為二級目錄,導入文件為一級目錄下的Make.define文件

  2.第二行顯示定義了變量PROGS, test01后面的 \為換行符,在參數較多時方便代碼查看。

  3.all表示一個標簽,在此例中表示所有目標,即${PROGS}所指定的所有執行文件。clean同理。

  4.命令行一定要以TAB開頭(Makefile規定)

  5.命令行定義中使用了隱晦規則,$@表示目標文件,在此例中即指代test01或test02.

  6.命令行中的變量均為Make.define文件中定義,表示編譯所用的參數
  

3.Makefile工作方式

  為方便說明,簡化上述Makefile如下:

include ../Make.defines

test01:    test01.o
        ${CC} ${CFLAGS} -o $@ test01.o ${LIBS}
        
clean:
        rm -f ${PROGS} ${CLEANFILES}

  在默認的方式下,在make命令后:

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

 4.Makefile中一些雜項說明

  4.1 文件包含

  Makefile支持文件包含功能,類似於c/c++中的#include功能,最開始的例子已經使用,實例模型可以參考。其具體使用方法介紹如下:

  include <filename>        #  filename可以是當前操作系統Shell的文件模式(可以保含路徑和通配符)

   在include前面可以有一些空字符,但是絕不能是[Tab]鍵開始。include和<filename>;可以用一個或多個空格隔開。舉個例子,你有這樣幾個Makefile:a.mk、b.mk、c.mk,還有一個文件叫foo.make,以及一個變量$(bar),其包含了e.mk和f.mk,那么,下面的語句:

    include foo.make *.mk $(bar)

    等價於:

    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,其作用和這一個是一樣的。

  4.2 幾種賦值符號差異

  Makefile中賦值符號中四種(=  :=  ?= +=),區別如下:

  1. = 是最基本的賦值,他會將整個Makefile展開后,將最后一個值付給變量。

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

    在上例中,y的值將會是 xyz bar ,而不是 foo bar 。

  2. := 是覆蓋之前的值,無需全部展開,在賦值處直接覆蓋。

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

   在上例中,y的值將會是 foo bar ,而不是 xyz bar 了。

  3. ?= 是如果沒有被賦值過就賦予等號后面的值(容易理解)
  4. += 是添加等號后面的值(字面含義,直接追加)

  4.3 編譯參數整理

  4.3.1: GCC參數整理:  

  在Makefile編譯命令行中(即commend),第一個例子的${CCFLAG}. 變量中定義了一些編譯器需要處理的工作(如:自動導出頭文件包含 -M; 添加GDB調試:-g; 優化等級:-o ~ -o2), gcc選項如下

  用法:gcc [選項] 文件...
  選項:
    -pass-exit-codes         在某一階段退出時返回最高的錯誤碼
    --help                   顯示此幫助說明
    --target-help            顯示目標機器特定的命令行選項
    (使用‘-v --help’顯示子進程的命令行參數)
    -dumpspecs               顯示所有內建 spec 字符串
    -dumpversion             顯示編譯器的版本號
    -dumpmachine             顯示編譯器的目標處理器
    -print-search-dirs       顯示編譯器的搜索路徑
    -print-libgcc-file-name  顯示編譯器伴隨庫的名稱
    -print-file-name=<庫>    顯示 <庫> 的完整路徑
    -print-prog-name=<程序>  顯示編譯器組件 <程序> 的完整路徑
    -print-multi-directory   顯示不同版本 libgcc 的根目錄
    -print-multi-lib         顯示命令行選項和多個版本庫搜索路徑間的映射
    -print-multi-os-directory 顯示操作系統庫的相對路徑
    -Wa,<選項>               將逗號分隔的 <選項> 傳遞給匯編器
   -Wp,<選項>               將逗號分隔的 <選項> 傳遞給預處理器
    -Wl,<選項>               將逗號分隔的 <選項> 傳遞給鏈接器

    -Wall         打開所有編譯警告
    -Xassembler <參數>       將 <參數> 傳遞給匯編器
    -Xpreprocessor <參數>    將 <參數> 傳遞給預處理器
    -Xlinker <參數>          將 <參數> 傳遞給鏈接器
    -combine                 將多個源文件一次性傳遞給匯編器
    -save-temps              不刪除中間文件
    -pipe                    使用管道代替臨時文件
    -time                    為每個子進程計時
    -specs=<文件>            用 <文件> 的內容覆蓋內建的 specs 文件
    -std=<標准>              指定輸入源文件遵循的標准
    --sysroot=<目錄>         將 <目錄> 作為頭文件和庫文件的根目錄
    -B <目錄>                將 <目錄> 添加到編譯器的搜索路徑中
    -b <機器>                為 gcc 指定目標機器(如果有安裝)
    -V <版本>                運行指定版本的 gcc(如果有安裝)
    -v                       顯示編譯器調用的程序
    -###                     與 -v 類似,但選項被引號括住,並且不執行命令
    -E                       僅作預處理,不進行編譯、匯編和鏈接
    -S                       編譯到匯編語言,不進行匯編和鏈接
    -c                       編譯、匯編到目標代碼,不進行鏈接
    -o <文件>                輸出到 <文件>
    -x <語言>                指定其后輸入文件的語言允許的語言包括:c c++ assembler none
                           ‘none’意味着恢復默認行為,即根據文件的擴展名猜測
                           源文件的語言

  以 -g、-f、-m、-O、-W 或 --param 開頭的選項將由 gcc 自動傳遞給其調用的不同子進程。若要向這些進程傳遞其他選項,必須使用 -W<字母> 選項。

  4.3.2: make參數整理(摘自跟我一起寫Makefile)

下面列舉了所有GNU make 3.80版的參數定義。其它版本和產商的make大同小異,不過其它產商的make的具體參數還是請參考各自的產品文檔。

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

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

“-C <dir>;”
“--directory=<dir>;”
指定讀取makefile的目錄。如果有多個“-C”參數,make的解釋是后面的路徑以前面的作為相對路徑,並以最后的目錄作為被指定目錄。如:“make –C ~hchen/test –C prog”等價於“make –C ~hchen/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”才是有效的。(注意這個參數在MS-DOS中是無用的)

“-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發現有未定義的變量,那么就輸出警告信息。

  4.4 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”參數一同使用,來查看這個依賴文件所發生的規則命令。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM