首頁 > 軟體

Makefile 及其工作原理

2020-06-16 16:46:34

用這個方便的工具來更有效的執行和編譯你的程式。

當你需要在一些原始檔改變後執行或更新一個任務時,通常會用到 make 工具。make 工具需要讀取一個 Makefile(或 makefile)檔案,在該檔案中定義了一系列需要執行的任務。你可以使用 make 來將原始碼編譯為可執行程式。大部分開源專案會使用 make 來實現最終的二進位制檔案的編譯,然後使用 make install 命令來執行安裝。

本文將通過一些基礎和進階的範例來展示 makeMakefile 的使用方法。在開始前,請確保你的系統中安裝了 make

 

基礎範例

依然從列印 “Hello World” 開始。首先建立一個名字為 myproject 的目錄,目錄下新建 Makefile 檔案,檔案內容為:

  1. say_hello:
  2.         echo"Hello World"

myproject 目錄下執行 make,會有如下輸出:

  1. $ make
  2. echo"Hello World"
  3. HelloWorld

在上面的例子中,“say_hello” 類似於其他程式語言中的函數名。這被稱之為目標target。在該目標之後的是預置條件或依賴。為了簡單起見,我們在這個範例中沒有定義預置條件。echo ‘Hello World' 命令被稱為步驟recipe。這些步驟基於預置條件來實現目標。目標、預置條件和步驟共同構成一個規則。

總結一下,一個典型的規則的語法為:

  1. 目標:預置條件
  2. <TAB>步驟

作為範例,目標可以是一個基於預置條件(原始碼)的二進位制檔案。另一方面,預置條件也可以是依賴其他預置條件的目標。

  1. final_target: sub_target final_target.c
  2.         Recipe_to_create_final_target
  3.        
  4. sub_target: sub_target.c
  5.         Recipe_to_create_sub_target

目標並不要求是一個檔案,也可以只是步驟的名字,就如我們的例子中一樣。我們稱之為“偽目標”。

再回到上面的範例中,當 make 被執行時,整條指令 echo "Hello World" 都被顯示出來,之後才是真正的執行結果。如果不希望指令本身被列印處理,需要在 echo 前新增 @

say_hello:
        @echo "Hello World"

重新執行 make,將會只有如下輸出:

  1. $ make
  2. HelloWorld

接下來在 Makefile 中新增如下偽目標:generateclean

  1. say_hello:
  2.         @echo"Hello World"
  3. generate:
  4.         @echo"Creating empty text files..."
  5.         touchfile-{1..10}.txt
  6. clean:
  7.         @echo"Cleaning up..."
  8.         rm*.txt

隨後當我們執行 make 時,只有 say_hello 這個目標被執行。這是因為Makefile 中的第一個目標為預設目標。通常情況下會呼叫預設目標,這就是你在大多數專案中看到 all 作為第一個目標而出現。all 負責來呼叫它他的目標。我們可以通過 .DEFAULT_GOAL 這個特殊的偽目標來覆蓋掉預設的行為。

Makefile 檔案開頭增加 .DEFAULT_GOAL

  1. .DEFAULT_GOAL := generate

make 會將 generate 作為預設目標:

  1. $ make
  2. Creatingempty text files...
  3. touchfile-{1..10}.txt

顧名思義,.DEFAULT_GOAL 偽目標僅能定義一個目標。這就是為什麼很多 Makefile 會包括 all 這個目標,這樣可以呼叫多個目標。

下面刪除掉 .DEFAULT_GOAL,增加 all 目標:

  1. all: say_hello generate
  2. say_hello:
  3.         @echo"Hello World"
  4. generate:
  5.         @echo"Creating empty text files..."
  6.         touchfile-{1..10}.txt
  7. clean:
  8.         @echo"Cleaning up..."
  9.         rm*.txt

執行之前,我們再增加一些特殊的偽目標。.PHONY 用來定義這些不是檔案的目標。make 會預設呼叫這些偽目標下的步驟,而不去檢查檔名是否存在或最後修改日期。完整的 Makefile 如下:

  1. .PHONY: all say_hello generate clean
  2. all: say_hello generate
  3. say_hello:
  4.         @echo"Hello World"
  5. generate:
  6.         @echo"Creating empty text files..."
  7.         touchfile-{1..10}.txt
  8. clean:
  9.         @echo"Cleaning up..."
  10.         rm*.txt

make 命令會呼叫 say_hellogenerate

  1. $ make
  2. HelloWorld
  3. Creatingempty text files...
  4. touchfile-{1..10}.txt

clean 不應該被放入 all 中,或者被放入第一個目標中。clean 應當在需要清理時手動呼叫,呼叫方法為 make clean

  1. $ make clean
  2. Cleaning up...
  3. rm*.txt

現在你應該已經對 Makefile 有了基礎的了解,接下來我們看一些進階的範例。

 

進階範例

 

變數

在之前的範例中,大部分目標和預置條件是已經固定了的,但在實際專案中,它們通常用變數和模式來代替。

定義變數最簡單的方式是使用 = 操作符。例如,將命令 gcc 賦值給變數 CC

  1. CC =gcc

這被稱為遞回擴充套件變數,用於如下所示的規則中:

  1. hello: hello.c
  2.     ${CC} hello.c -o hello

你可能已經想到了,這些步驟將會在傳遞給終端時展開為:

  1. gcc hello.c -o hello

${CC}$(CC) 都能對 gcc 進行參照。但如果一個變數嘗試將它本身賦值給自己,將會造成死迴圈。讓我們驗證一下:

  1. CC =gcc
  2. CC = ${CC}
  3. all:
  4.     @echo ${CC}

此時執行 make 會導致:

  1. $ make
  2. Makefile:8:***Recursive variable 'CC' references itself (eventually).  Stop.

為了避免這種情況發生,可以使用 := 操作符(這被稱為簡單擴充套件變數)。以下程式碼不會造成上述問題:

  1. CC :=gcc
  2. CC := ${CC}
  3. all:
  4.     @echo ${CC}

 

模式和函數

下面的 Makefile 使用了變數、模式和函數來實現所有 C 程式碼的編譯。我們來逐行分析下:

  1. #Usage:
  2. #make        # compile all binary
  3. #make clean  # remove ALL binaries and objects
  4. .PHONY = all clean
  5. CC =gcc                        # compiler to use
  6. LINKERFLAG =-lm
  7. SRCS := $(wildcard *.c)
  8. BINS := $(SRCS:%.c=%)
  9. all: ${BINS}
  10. %:%.o
  11.         @echo"Checking.."
  12.         ${CC} ${LINKERFLAG} $<-o $@
  13. %.o:%.c
  14.         @echo"Creating object.."
  15.         ${CC}-c $<
  16. clean:
  17.         @echo"Cleaning up..."
  18.         rm-rvf *.o ${BINS}
  • # 開頭的行是評論。
  • .PHONY = all clean 行定義了 allclean 兩個偽目標。
  • 變數 LINKERFLAG 定義了在步驟中 gcc 命令需要用到的引數。
  • SRCS := $(wildcard *.c)$(wildcard pattern) 是與檔名相關的一個函數。在本範例中,所有 “.c”字尾的檔案會被存入 SRCS 變數。
  • BINS := $(SRCS:%.c=%):這被稱為替代參照。本例中,如果 SRCS 的值為 'foo.c bar.c',則 BINS的值為 'foo bar'
  • all: ${BINS} 行:偽目標 all 呼叫 ${BINS} 變數中的所有值作為子目標。
  • 規則:

    1. %:%.o
    2.   @echo"Checking.."
    3.   ${CC} ${LINKERFLAG} $&lt;-o $@

    下面通過一個範例來理解這條規則。假定 foo 是變數 ${BINS} 中的一個值。% 會匹配到 foo%匹配任意一個目標)。下面是規則展開後的內容:

    1. foo: foo.o
    2.   @echo"Checking.."
    3.   gcc-lm foo.o -o foo

    如上所示,%foo 替換掉了。$<foo.o 替換掉。$<用於匹配預置條件,$@ 匹配目標。對 ${BINS} 中的每個值,這條規則都會被呼叫一遍。

  • 規則:

    1. %.o:%.c
    2.   @echo"Creating object.."
    3.   ${CC}-c $&lt;

    之前規則中的每個預置條件在這條規則中都會都被作為一個目標。下面是展開後的內容:

    1. foo.o: foo.c
    2.   @echo"Creating object.."
    3.   gcc-c foo.c
  • 最後,在 clean 目標中,所有的二進位制檔案和編譯檔案將被刪除。

下面是重寫後的 Makefile,該檔案應該被放置在一個有 foo.c 檔案的目錄下:

  1. #Usage:
  2. #make        # compile all binary
  3. #make clean  # remove ALL binaries and objects
  4. .PHONY = all clean
  5. CC =gcc                        # compiler to use
  6. LINKERFLAG =-lm
  7. SRCS := foo.c
  8. BINS := foo
  9. all: foo
  10. foo: foo.o
  11.         @echo"Checking.."
  12.         gcc-lm foo.o -o foo
  13. foo.o: foo.c
  14.         @echo"Creating object.."
  15.         gcc-c foo.c
  16. clean:
  17.         @echo"Cleaning up..."
  18.         rm-rvf foo.o foo

關於 Makefile 的更多資訊,GNU Make 手冊提供了更完整的說明和範例。


via: https://opensource.com/article/18/8/what-how-makefile

作者:Sachin Patil 選題:lujun9972 譯者:Zafiry 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出

Linux公社的RSS地址:https://www.linuxidc.com/rssFeed.aspx

本文永久更新連結地址https://www.linuxidc.com/Linux/2018-09/154071.htm


IT145.com E-mail:sddin#qq.com