1. 程式人生 > >Makefile中的偽目標和變數定義格式

Makefile中的偽目標和變數定義格式

本節我們討論一個Makefile中的一個重要的特殊目標:偽目標。

偽目標是這樣一個目標:它不代表一個真正的檔名,在執行make時可以指定這個目標來執行其所在規則定義的命令,有時我們也可以將一個偽目標稱為標籤。使用偽目標有兩點原因:1. 避免在我們的Makefile中定義的只執行命令的的目標(此目標的目的為了執行執行一列命令,而不需要建立這個目標)和工作目錄下的實際檔案出現名字衝突。2. 提高執行make時的效率,特別是對於一個大型的工程來說,編譯的效率也許你同樣關心。以下就這兩個問題我們進行分析討論:

1. 如果我們需要書寫這樣一個規則:規則所定義的命令不是去建立目標檔案,而是使用make指定具體的目標來執一些特定的命令。像下邊那樣:

clean: 

rm *.o temp 

規則中rm不是建立檔案clean的命令,只是刪除當前目錄下的所有.o檔案和temp檔案。在工作目錄下不存在clean這個檔案時,我們輸入make clean後,rm *.o temp總會被執行。這是我們的初衷。

但當前工作目錄下存在檔案clean時情況就不一樣了,在我們輸入make clean時。規則沒有依賴檔案,所以目標被認為是最新的而不去執行規則作定義的命令,命令rm將不會被執行。這並不是我們的初衷。為了避免這個問題,我們可以將目標clean明確的宣告為偽目標。將一個目標宣告為偽目標需要將它作為特殊目標.PHONY

的依賴。如下:

.PHONY : clean 

這樣目標clean就是一個偽目標,無論當前目錄下是否存在clean這個檔案。我們輸入make clean之後。rm命令都會被執行。而且,當一個目標被宣告為偽目標後,make在執行此規則時不會試圖去查詢隱含規則來建立這個目標。這樣也提高了make的執行效率,同時我們也不用擔心由於目標和檔名重名而使我們的期望失敗。在書寫偽目標規則時,首先需要宣告目標是一個偽目標,之後才是偽目標的規則定義。目標clean書寫格式應該如下:

.PHONY: clean 

clean: 

rm *.o temp 

2. 偽目標的另外一使用場合在make

的並行和遞迴執行過程中。此情況下一般存在一個變數,其定義為所有需要make的子目錄。對多個目錄進行make的實現方式可以在一個規則中可以使用shell的迴圈來完成。如下:

SUBDIRS = foo bar baz 

subdirs: 

for dir in $(SUBDIRS); do \ 

$(MAKE) -C $$dir; \ 

done 

但這種實現方法存在以下幾個問題。1. 當子目錄執行make出現錯誤時,make不會退出。就是說,在對某一個目錄執行make失敗以後,會繼續對其他的目錄進行make。在最終執行失敗的情況下,我們很難根據錯誤的提示定位出具體是是那個目錄下的Makefile出現錯誤。這給問題定位造成了很大的困難。為了避免這樣的問題,我們可以在命令列部分加入錯誤的監測,在命令執行錯誤後make退出。不幸的是,如果在執行make時使用了-k選項,此方式將失效。2. 外一個問題就是使用這種shell的迴圈方式時,沒有用到make對目錄的並行處理功能,因為規則的命令是一條完整的shell命令,不能被並行的執行。

我們可以通過偽目標方式來克服以上實現方式所存在的兩個問題。

SUBDIRS = foo bar baz 

.PHONY: subdirs $(SUBDIRS) 

subdirs: $(SUBDIRS) 

$(SUBDIRS): 

$(MAKE) -C [email protected] 

foo: baz 

上邊的實現中使用了一個沒有命令列的規則foo: baz,用來限制子目錄的make順序。此規則的含義時在處理foo目錄之前,需要等待baz目錄處理完成。在書寫一個並行執行makeMakefile時,目錄的處理順序是需要特別注意的。

一般情況下,一個偽目標不作為一個另外一個目標檔案的依賴。這是因為當一個目標檔案的依賴包含偽目標時,每一次在執行這個規則時偽目標所定義的命令都會被執行(因為它是規則的依賴,重建規則目標檔案時需要首先重建它的依賴)。當偽目標沒有作為任何目標(此目標是一個可被建立或者已存在的檔案)的依賴時,我們只能通過make的命令列選項明確指定這個偽目標,來執行它所定義的命令。例如我們的make clean

Makefile中,偽目標可以有自己的依賴。在一個目錄下如果需要建立多個可執行程式,我們可以將所有程式的重建規則在一個Makefile中描述。因為Makefile中第一個目標是終極目標,約定的做法是使用一個稱為all的偽目標來作為終極目標,它的依賴檔案就是那些需要建立的程式。下邊就是一個例子:

#sample 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 

執行make時,目標all被作為終極目標。為了完成對它的更新,make會建立(不存在)或者重建(已存在)目標all的所有依賴檔案(prog1prog2prog3)。當需要單獨更新某一個程式時,我們可以通過make的命令列選項來明確指定需要重建的程式。(例如: “make prog1)。當一個偽目標作為另外一個偽目標依賴時,make將其作為另外一個偽目標的子例程來處理(可以這樣理解:其作為另外一個偽目標的必須執行的部分,就行C語言中的函式呼叫一樣)。下邊的例子就是這種用法:

.PHONY: cleanall cleanobj cleandiff 

cleanall : cleanobj cleandiff 

rm program 

cleanobj : 

rm *.o 

cleandiff : 

rm *.diff 

cleanobjcleandiff這兩個偽目標有點像子程式的意思(執行目標clearall時會觸發它們所定義的命令被執行)。我們可以輸入make cleanallmake cleanobjmake cleandiff命令來達到清除不同種類檔案的目的。例子首先通過特殊目標.PHONY聲明瞭多個偽目標,它們之間使用空各分割,之後才是各個偽目標的規則定義。

說明:

通常在清除檔案的偽目標所定義的命令中rm使用選項–f--force)來防止在缺少刪除檔案時出錯並退出,使make clean過程失敗。也可以在rm之前加上-來防止rm錯誤退出,這種方式時make會提示錯誤資訊但不會退出。為了不看到這些討厭的資訊,需要使用上述的第一種方式。

另外make存在一個內嵌隱含變數RM,它被定義為:RM = rm –f。因此在書寫clean規則的命令列時可以使用變數$(RM)來代替rm,這樣可以免出現一些不必要的麻煩!這是我們推薦的用法


  一般在我們書寫Makefile時,各部分變數引用的格式我們建議如下:
1. make變數(Makefile中定義的或者是make的環境變數)的引用使用“$(VAR)”格式。

2. 出現在規則命令列中shell變數(一般為執行命令過程中的臨時變數,它不屬於Makefile變數,而是一個shell變數)引用使用shell的“$tmp”格式。
3. 對出現在命令列中的make變數我們同樣使用“$(CMDVAR)” 格式來引用。

       MakeFile中給變數賦值有以下兩種方式

1.遞迴展開式,使用=直接定義,例子如下:

               foo = $(bar)
               bar = $(ugh)
               ugh = Huh?
               all:;echo $(foo)
  執行“make”將會打印出“Huh?”。整個變數的替換過程時這樣的:首先“$(foo)”被替換為“$(bar)”,接下來 “$(bar)”被替換為“$(ugh)”,最後“$(ugh)”被替換為“Hug?”。整個替換的過程是在執行“echo $(foo)”是進行的。

  這種方式的缺點是

           缺點1:使用此風格的變數定義,可能會由於出現變數遞迴定義而導致make陷入到無限的變數展開過程中,最終使make執行失敗.
           缺點2:這種風格的變數定義中如果使用了函式,那麼包含在變數值中的函式總會在變數被引用的地方執行(變數被展開時)。

2.直接展開式

這種風格的變數使用“:=”來定義變數。在使用“:=”定義變數時,變數值中對另外變數的引用或者函式的引用在定義時被展開(對變數進行替換)。


x := foo
y := $(x) bar
x := later
就等價於:
y := foo bar
x := later
需要CFLAGS := $(include_dirs) -O
include_dirs := -Ifoo -Ibar
由於在變數“include_dirs”的定義出現在“CFLAGS”定義之後。因此在“CFLAGS”的定義中,“include_dirs”的值為空。“CFLAGS”的值為“-O”而不是“-Ifoo -Ibar -O”。注意的是:此風格變數在定義時就完成了對所引用的變數的展開,因此它不能實現對其後定義變數的引用。

變數的替換引用,格式為“$(VAR:A=B)”(或者“${VAR:A=B}”),
 意思是,替換變數“VAR”中所有“A”字元結尾的字為“B”結尾的字。
 “結尾”的含義是空格之前(變數值多個字之間使用空格分開)。
 而對於變數其它部分的“A”字元不進行替換。

     自動化變數

[email protected]
代表規則中的目標檔名。如果目標是一個文件(Linux中,一般稱.a檔案為文件),那麼它代表這個文件的檔名。在多目標的模式規則中,它代表的是哪個觸發規則被執行的目標檔名。
$%
規則的目標檔案是一個靜態庫檔案時,代表靜態庫的一個成員名。例如,規則的目標是“foo.a(bar.o)”,那麼,“$%”的值就為“bar.o”,“[email protected]”的值為“foo.a”。如果目標不是函式庫檔案,其值為空。
$<
規則的第一個依賴檔名。如果是隱含規則,則它代表通過目標指定的第一個依賴檔案。
$?
所有比目標檔案更新的依賴檔案列表,空格分割。如果目標是靜態庫檔名,代表的是庫成

員(.o檔案)的更新情況。
$^
規則的所有依賴檔案列表,使用空格分隔。如果目標是靜態庫檔名,它所代表的只能是所有庫成員(.o檔案)名。一個檔案可重複的出現在目標的依賴中,變數“$^”只記錄它的一次引用情況。就是說變數“$^”會去掉重複的依賴檔案。
$+
類似“$^”,但是它保留了依賴檔案中重複出現的檔案。主要用在程式連結時,庫的交叉引用場合。

$(@D)
代表目標檔案的目錄部分(去掉目錄部分的最後一個斜槓)。如果“[email protected]”是“dir/foo.o”,那麼“$(@D)”的值為“dir”。如果“[email protected]”不存在斜槓,其值就是“.”(當前目錄)。注意它和函式“dir”的區別!
$(@F)
目標檔案的完整檔名中除目錄以外的部分(實際檔名)。如果“[email protected]”為“dir/foo.o”,那麼“$(@F)”只就是“foo.o”。“$(@F)”等價於函式“$(notdir [email protected])”。
$(%D)
$(%F)

當以如“archive(member)”形式靜態庫為目標時,分別表示庫檔案成員“member”名中的目錄部分和檔名部分。它僅對這種形式的規則目標有效。
$(<D)
$(<F)

分別表示規則中第一個依賴檔案的目錄部分和檔名部分。
$(^D)
$(^F)

分別表示所有依賴檔案的目錄部分和檔案部分(不存在同一檔案)。
$(+D)
$(+F)

分別表示所有依賴檔案的目錄部分和檔案部分(可存在重複檔案)。
$(?D)
$(?F)

分別表示被更新的依賴檔案的目錄部分和檔案部分。

相關推薦

Makefile目標變數定義格式

本節我們討論一個Makefile中的一個重要的特殊目標:偽目標。 偽目標是這樣一個目標:它不代表一個真正的檔名,在執行make時可以指定這個目標來執行其所在規則定義的命令,有時我們也可以將一個偽目標稱為標籤。使用偽目標有兩點原因:1. 避免在我們的Makefile中定義的

makefile=、:=+=的區別

有感 分析 eight c語言 區別 all ont 總結 mil 經常有人分不清= 、:=和+=的區別 這裏我總結下做下詳細的分析: 首先你得清楚makefile的運行環境,因為我是linux系統,那麽我得運行環境是shell 在Linux的shell裏,shell

Makefile的-CM=解析

轉自:https://www.aliyun.com/jiaocheng/144874.html 摘要:在進行嵌入式開發過程中,經常需要編寫和執行Makefile,且在大型專案開發過程中,一般也都是使用Makefile來進行管理、編譯、執行的,所以對Makefile的讀寫是嵌入式軟體工程師必

Java的SafeVarargs變數引數

有些語言在編譯時強制執行型別,但忘記了執行時的型別。這被稱為型別擦除。 例如,在C中,編譯器將確保程式碼完全是型別證明的。因此生成的位元組碼不會擔心執行時的型別資訊。 就像一枚硬幣的兩面,另一面。有些語言在執行時進行型別檢查(也可能在編譯時)。這被稱為具體化reification。 例如

演示-JQuery元素類選擇器

1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 2 <html xmlns="http

makefile目標的依賴該怎麼寫?

注意,這篇博文 並不是makefile的教程! 並不是makefile的教程! 並不是makefile的教程! 僅僅是在學習makefile過程中關於如何寫依賴的一個感悟。 makefile的學習文件可參考: http://download.csdn.net/detail/

Android的通知定義通知佈局

Android中的通知(Notification)是Android中的重要一部分,應用程式通過通知來提醒使用者或者向用戶傳達資訊,下面讓我們來看一下怎麼在我們的程式中使用通知和自定義通知的佈局。 首先我們來看一下怎麼向通知欄中傳送一個通知。由於各個版本的And

【轉】Makefile的$(error)$(warning)

摘自《GNU+Makefile中文手冊》整理翻譯:徐海兵 Makefile中的$(error)和$(warning) make 提供了兩個控制 make 執行方式的函式。通常它們用在 Makefile 中,當 make執行過程中檢測到某些錯誤是為使用者提供訊

makefile的冒號等號解析

Makefile裡的=顯然是賦值的用法風格1: 遞迴擴充套件變數(recursively expanded variable)變數定義格式是,變數和值之間用等號,即 =例如:foo = $(bar)bar = $(ugh)ugh = Huh?all:;echo $(fo

Django的日期處理註意事項定義時間格式轉換

python django datetime 我們在用Django創建models時,常常會涉及時間日期字段的處理,Django裏日期相關Field有DateTimeField、DateField和TimeField三種類型,看似簡單,但其中有一些容易出錯的地方需要註意;另外,如果不習慣Djang

OraclePL/SQL之常量變數定義、遊標(游標)的使用

PL/SQL常量和變數的定義 變數的資料型別:char、varchar2、date、number、boolean、long 常量定義:isshow boolean :=true; 說明變數:說明變數名、資料型別和長度後用分號結束說明語句。例:e_name varchar2(20);

MySQL變數定義變數的賦值使用(轉)

說明:現在市面上定義變數的教程和書籍基本都放在儲存過程上說明,但是儲存過程上變數只能作用於begin...end塊中,而普通的變數定義和使用都說的比較少,針對此類問題只能在官方文件中才能找到講解。 前言 MySQL儲存過程中,定義變數有兩種方式:  1、使用set或select直接賦值,變數名以@開頭 例如:

定義一個包含私有成員變數函式的類,再定義一個內部類,在內部類函式訪問外部成員變數,並呼叫外部函式。在外部類函式建立內部類物件,呼叫內部類函式

public class Test5 {         //定義包含私有成員變數和函式         private int a = 201320883;         private voi

static成員變數定義宣告

類中static成員變數的定義和宣告:在VS2010中,只能在類外定義同時賦值,在類中宣告。不能再類中宣告時賦值!! #include<iostream> using namespace std; class A { public: static int a

MySQL儲存過程declareset定義變數的區別

  在儲存過程中常看到declare定義的變數和@set定義的變數。簡單的來說,declare定義的類似是區域性變數,@set定義的類似全域性變數。  1、declare定義的變數類似java類中的區域性變數,僅在類中生效。即只在儲存過程中的begin和end之間生效。  2

varlet定義變數在迴圈的不同

var arr = []; for(var i=0; i<10; i++) { arr[i] = function () { console.log(i); } } con

makefile變數定義的空格

comma:= , empty:= # space變數用兩個空變數作為識別符號,當中是一個空格 space:= $(empty) $(empty) foo:= a b c bar:= $(subst $(space),$(comma),$(foo)) haha :@echo

mysql儲存過程 declare set 定義變數的區別

mysql儲存過程中,定義變數有兩種方式: 1.使用set或select直接賦值,變數名以 @ 開頭. 例如:set @var=1; 可以在一個會話的任何地方宣告,作用域是整個會話,稱為會話變數。 2.以 DECLARE 關鍵字宣告的變數,只能在儲存過程中使用,稱為儲存過

在vim使用cscope查詢呼叫、定義函式變數的地方

在vim中用了一陣子ctags,確實美中不足。ctags只能根據呼叫函式的地方查詢定義該函式的地方,不能根據定義函式的地方查詢都有哪些地方呼叫了 該函式。於是又學習了cscope。Cscope在主頁上說它具有毋庸置疑的UNIX血統,早在PDP-11的時代就已經在貝爾實驗室開發出來了。我的 Linux是S

C語言INT64型別GCC的long long的定義,及sprintf格式字串的定義

d,lx,ld,,lu,這幾個都是輸出32位的 hd,hx,hu,這幾個都是輸出16位資料的, hhd,hhx,hhu,這幾個都是輸出8位的, lld,ll,llu,llx,這幾個都是輸出64位的, printf( "%llu ",.....) %llu  是64位無符號 %llx才是64位16進位制數