make和makefile,多文件項目管理

GNU Make簡介

大型項目的開發過程當中,每每會劃分出若干個功能模塊,這樣能夠保證軟件的易維護性。mysql

做爲項目的組成部分,各個模塊不可避免的存在各類聯繫,若是其中某個模塊發生改動,那麼其餘的模塊須要相應的更新。若是經過手動去完成這個工做的話,對於小型的項目可能還行,可是對於比較大型的項目就幾乎是不可能的。ios

所以Linux 系統提供了一個自動維護和生成目標程序的工具 make,它能夠根據各個模塊的更改狀況去從新編譯鏈接目標代碼。sql

Make 工具的做用就是實現編譯鏈接過程的自動化。它定義了一種語言,用來描述源文件、目標文件以及可執行文件之間的關係,經過檢查文件的時間戳來決定程序中哪些文件須要更新編譯,併發送相應的命令。併發

咱們在開發項目的時候,將程序劃分爲多個模塊,分解到不一樣的文件中以後。當其中的某一部分發生改變以後,由於其餘文件的目標源文件已經存在,因此編譯器其實不須要編譯所有代碼來生成新的可執行文件,而只須要編譯被改動的源文件,而後鏈接全部的目標文件就能夠了,這在大型的項目開發中是很是重要的,由於這可能將編譯時間從幾小時縮小到幾分鐘。這就是Make所能作的。函數

Makefile文件書寫規範

Makefile 文件描述了整個程序的編譯、鏈接規則,主要包括:程序中哪些源文件須要編譯以及如何編譯,須要建立哪些庫文件以及如何建立這些文件,如何產生最終的可執行文件等。工具

基本規則

# 開始的行是註釋行。ui

若是一行太長能夠用 反斜線 `` 來另起一行,至關於就是一行。spa

Makefile文件的做用是告訴 make工具作什麼,多數狀況下是如何編譯鏈接一個程序:命令行

目標 : 依賴
<tab鍵>命令

目標,每每是程序的中間或者最終生成的文件名,好比目標文件、可執行文件……code

依賴,是指用來產生目標文件的輸入文件名,一個目標每每依賴於一個或多個文件。

命令,是指任何一個文件發生改動以後,須要從新生成目標文件須要執行的命令,這裏能夠有多條命令,可是每一個命令必須單獨佔一行,且須要注意的是,每一個命令的前面必須有一個<tab鍵>,由於make是用過<tab>來識別命令行的,進而完成相應的動做。

例子1,首先是一個簡單的 hello.c的C語言源程序:

#include<stdio.h>
int main()
{
    printf("hello world\n");
    return 0;
}

它的Makefile文件能夠是:

/**
目標 : 依賴
<tab鍵>命令
**/
hello:hello.c
    gcc -o hello hello.c

而後在Makefile和hello.c 所在的目錄下執行 make 命令,就能夠編譯hello.c 生成 hello可執行文件。

例子2,上面那個例子太簡單,只有一個文件,下面的這個例子有三個文件和兩個頭文件。

大體的過程如圖:
圖片描述

它的對應的Makefile 文件是:

example:sort.o compute.o main.o
    gcc sort.o compute.o main.o -o example
sort.o:sort.c lib1.h
    gcc -c sort.c -o sort.o
compute.o:compute.c
    gcc -c compute.c -o compute.o
main.o:main.c lib2.h
    gcc -c main.c -o mian.o

注意最後須要生成的文件,須要在Makefile 裏面寫在最前面,它的大體的執行的邏輯是這樣的:

1) 在命令行輸入 make 命令以後,make命令會首先讀取當前目錄下的 Makefile文件

2) make 會處理 Makefile裏面的第一條規則,也就是上面的 鏈接生成 example可執行文件

3) 完成第一條規則以後,須要首先處理的是 example所依賴的目標文件,也就是 sort.o、compute.o、main.o,目標文件根據其依賴的源文件或者頭文件是否比如今的目標文件更新,或者是目標文件是否存在來決定是否須要從新編譯。目標文件處理完成以後,make纔會決定是否須要從新鏈接生成可執行文件,存在三種狀況:

  1. 若是全部的文件都沒有被編譯,則編譯全部的源文件,並鏈接生成可執行文件
  2. 從新編譯上次執行 make 命令以後修改過的源文件,生成新的目標文件,而後和已經存在的,但此次沒有編譯的文件從新鏈接生成可執行文件
  3. 若是某個頭文件在上次執行 make 命令以後被修改,則從新編譯全部包含這個頭文件的源文件,生成新的目標文件,而後和已經存在的,但此次沒有編譯的文件從新鏈接生成可執行文件

變量的定義和使用

Makefile 裏面能夠定義一個變量來代替一個字符串,這些字符串能夠是目標、依賴或者是命令,以及Makefile 的其餘部分,引用變量的值的時候,只須要使用 $ 就好,例子:

objects=sort.o compute.o main.o
CC=gcc
CFLAGS=-Wall -g
example:$(objects)
    $(CC) &(objects) -o example
sort.o:sort.c lib1.h
    $(CC) $(CFLAGS) -c sort.c -o sort.o
compute.o:compute.c
    $(CC) $(CFLAGS) -c compute.c -o compute.o
mian.o:mian.c lib2.h
    $(CC) $(FLAGS) -c mian.c -o mian.o

變量名是大小寫敏感度的,通常命令相關的變量習慣用大寫(CC),文件相關的變量習慣用小寫(objects),參數相關的變量也習慣使用大寫(CFLAGS)。

若是變量名是單字符,能夠直接使用 $變量名來引用,$C;可是變量名爲多於一個字符的字符串,在引用的時候,必須使用$(變量名) 的形式,好比 $(CFLAGS),不然make工具只會解析第一個字符,好比 $CFLAGS,將只會解析 $C 的變量,後面的FLAGS做爲普通的字符串看待,等價於$(C)FLAGS,因此就可能出錯。

根據變量的定義和展方式是不一樣,能夠將Makefile裏面的變量分爲:

1) 遞歸展開式變量

經過 = 來進行定義,引用的時候進行嚴格的文本替換,變量中對於其餘的變量或者函數的引用在使用時候才進行展開。例子:

A=$(B)
B=$(C)
C=Hello

若是在這個Makefile 裏面存在對變量 A的引用:$(A),那麼在執行make 命令的時候,變量開始替換,首先將變量 A替換爲變量 B,接下來替換爲變量C, 最終替換爲 Hello。

遞歸展開式的優勢是,變量定義的時候能夠引用後續定義的變量。

缺點是,有可能在變量展開時出現無窮的循環,這就很蛋疼了。

2) 直接展開式變量

爲了不遞歸展開式變量存在的問題,因此可使用直接展開式變量,經過:= 進行定義。變量中對於其餘的變量或者函數的引用在定義時候就進行展開。

例子1

A=Hello
B:=$(A)World
A:=HI

由於是在定義的時候就展開,因此,變量B 的值是HelloWorld,而不像遞歸展開式中會是HIWorld(遞歸展開式變量中,A會持續對B形成影響),由於A首先定義爲 Hello,而後定義B,由於在定義時候就展開,因此B的值是HelloWorld,然後面再從新定義A 的話是不會對B在形成影響的。

例子2

B:=$(A)World
A=Hi

則最終 B的值是 World,由於在定義B 的時候A 尚未定義,因此make 會認爲A 是空。

隱含規則

隱含規則是系統或用戶預先定義好的一些特殊規則,主要是一些經常使用的依賴關係和更新命令。

通常規則使用文件的全名,而隱含規則中出現的目標文件和依賴文件都只使用文件的擴展名。若是Makefile 文件裏面沒有顯式給出文件的依賴關係的時候,make 就會根據文件的擴展名找到相應的隱含規則,而後按照隱含規則來更新目標。

例子,隱含規則是:

.c:
    $(CC) $(CFLAGS) -o &@ $<
.c .o:
    $(CC) $(CFLAGS) -c $<

下面給出的Makefile就是使用上面的隱含規則:

objects=sort.o compute.o mian.o
CC=gcc
CFLAGS=-Wall -g
example:$(objects)
    $(CC) $^ -o $@
sort.o:lib1.h
mian.o:lib2.h

僞目標

Makefile 文件中的目標分爲兩類:實目標和僞目標。

實目標是真正要生成的以文件形式存放在磁盤上的目標,上面所講解到的都屬於實目標;而僞目標不要求生成實際的文件,它主要是用於完成一些輔助操做。例子:

clean
    rm example $(objects)

在Makefile 裏面增長了上面的規則以後,在命令裏面輸入命令:make clean 就會執行命令:rm example sort.o compute.o mian.o

可是這種書寫形式不是很嚴謹,由於可能在當前目錄下面存在文件名爲 clean 的文件,由於這時候: 後面沒有依賴文件,因此make 就認爲這個文件是最新的,因此就不會執行 rm example sort.o compute.o mian.o

因此爲了不這種狀況的發生,因此建議使用這種:

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

這樣,無論當前目錄下是否存在文件名爲 clean 的文件,rm example sort.o compute.o mian.o命令都會被執行。

函數

GNU make提供了不少的函數,能夠在Makefile文件中調用這些函數來進行文件名、變量以及命令等的處理。

函數的調用方式與變量相似,使用 $ 符號。

1) patsubst 函數

主要用於對字符串經行運算和分析,格式是:

$(patsubst pattern,replacement,text)

例子1

$(patsubst %.c,%.o,sort.o compute.c main.c)

這個就是輸出與源文件相對應的目標文件列表,輸出爲:

sort.o compute.o main.o

2) dir 函數

主要用於獲取文件的路徑,例子:

$(dir main.c)

若是main.c 在當前目錄下,就會輸出:

./  //使用相對路徑的形式

3) notdir 函數

抽取文件名中除了路徑以外的其餘字符,例子:

$(notdir /home/perfect/Mywork/C/main.c ./Makefile

輸出是:

main.c Makefile

4) suffix 函數

獲取文件名的後綴:

$(suffix ./main.c)

輸出結果是:

.c  //也就是 ./main.c上的後綴

通用Makefile文件

能夠看出,編寫一個Makefile仍是很複雜的。

下面給出一個通用的Makefile 文件,其做者是應該的Gorge Foot,之因此說它是通用的,主要是由於它不須要通過修改就能夠應用於大部分的項目之中:

######################################
# Copyright (c) 1997 George Foot (george.foot@merton.ox.ac.uk)
# All rights reserved.
######################################
#目標(可執行文檔)名稱,庫(譬如stdcx,iostr,mysql等),頭文件路徑
DESTINATION := test
LIBS := 
INCLUDES := .


RM := rm -f
#C,CC或CPP文件的後綴
PS=cpp
# GNU Make的隱含變量定義
CC=g++
CPPFLAGS = -g -Wall -O3 -march=i486
CPPFLAGS += $(addprefix -I,$(INCLUDES))
CPPFLAGS += -MMD

#如下部分無需修改
SOURCE := $(wildcard *.$(PS))
OBJS := $(patsubst %.$(PS),%.o,$(SOURCE))
DEPS := $(patsubst %.o,%.d,$(OBJS))
MISSING_DEPS := $(filter-out $(wildcard $(DEPS)),$(DEPS))
MISSING_DEPS_SOURCES := $(wildcard $(patsubst %.d,%.$(PS),$(MISSING_DEPS)))

.PHONY : all deps objs clean rebuild

all : $(DESTINATION)

deps : $(DEPS)
         $(CC) -MM -MMD $(SOURCE)

objs : $(OBJS)

clean :
         @$(RM) *.o
         @$(RM) *.d
         @$(RM) $(DESTINATION)

rebuild: clean all 

ifneq ($(MISSING_DEPS),)
$(MISSING_DEPS) :
         @$(RM) $(patsubst %.d,%.o,$@)
endif

-include $(DEPS)

$(DESTINATION) : $(OBJS)
         $(CC) -o $(DESTINATION) $(OBJS) $(addprefix -l,$(LIBS))
#結束

簡介(經過研究這個Makefile 文件能夠很好的理解Makefile的規則):

  • 原做者是Gorge Foot,寫這個Makefile的時候仍是一個學生

  • ":="賦值,和"="不一樣的是,":="在賦值的同時,會將賦值語句中全部的變量就地展開,也就是說,A:=$(B)後,B的值的改變再也不影響A

  • 隱含規則。GUN Make在不特別指定的狀況下會使用諸如如下編譯命令:$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@,這也是爲何這個Makefile最後一個命令沒有添加$(CPPFLAGS)的緣由,由於缺省是包含這個變量的

  • 函數和變量很類似:"$ (函數名,空格,一列由逗號分隔的參數)"

  • SOURCES = $(wildcard .cpp) 列出工做目錄下文件名知足".cpp"條件的文件,以空格分隔,並將列表賦給SOURCE變量

  • patsubst函數:3個參數。功能是將第三個參數中的每一項(由空格分隔)符合第一個參數描述的部分替換成第二個參數制定的值

  • addprefix函數:2個參數。將源串(第2個參數,由空格分隔)中的每一項添加前綴(第1個參數)

  • filter-out函數:2個參數。從第二串中過濾掉包含在第一個串中的項

  • $(CC) -MM -MMD $(SOURCE) : 對每一個源文件生成依賴(dependence,Make經過依賴規則來判斷是否須要從新編譯某個文件),"D"生成".d"文件,-MM表示去掉 depends裏面的系統的頭文件(使用<>包含的頭文件)(若使用-M則所有包含,事實上,系統頭文件被修改的可能性極小,不須要執行依賴檢查)

  • .PHONY,不檢查後面制定各項是否存在同名文件

  • ifneg...else...endif,Makefile中的條件語句

  • -include $(DEPS) : 將DEPS中的文件包含進來,"-"表示忽略文件不存在的錯誤

  • @$(RM) *.o : 開頭的"@"表示在Make的時候,不顯示這條命令(GNU Make缺省是顯示的)

  • all : 做爲第一個出現的目標項目,Make會將它做爲主要和缺省項目("make"就表示"make all")

  • deps : 只生成依賴文件(.d文件)

  • objs : 爲每個源碼程序生成或更新 '.d' 文件和'.o'文件

  • clean : 刪除全部'.d','.o'和可執行文件

  • rebuild : clean而後重建

  • 內部變量$@, $< $^ : 分別表示目標名(:前面的部分,好比all),依靠列表(:後面的部分)中的第一個依靠文件,全部依靠文件

相關文章
相關標籤/搜索