Makefile 7——自動生成依賴關系 三顆星

后面會介紹gcc獲得源文件依賴的方法,gcc這個功能就是為make而存在的。我們采用gcc的-MM選項結合sed命令。使用sed進行替換的目的是為了在目標名前加上“objs/”前綴。gcc的-E選項,預處理。在生成依賴關系時,其實并不需要gcc編譯源文件,只要預處理就可以獲得依賴關系了。通過-E選項,可以避免生成依賴關系時gcc發出警告,以及提高依賴關系的生成效率。

現在,已經找到自動生成依賴關系的方法了,那么如何將其整合到我們complicated項目的Makefile中呢?自動生成的依賴信息不能直接出現在Makefile中,因為不能動態地改變Makefile中的內容,此時我們需要通過創建依賴關系文件的方式。假設依賴關系的文件以“.dep”結尾,因此我們新創建一個deps文件,用來存放依賴關系文件信息。

Makefile如下:

 1 .PHONY: all clean
 2 
 3 MKDIR = mkdir
 4 RM = rm
 5 RMFLAGS = -rf
 6 
 7 CC=gcc
 8 
 9 DIR_OBJS=objs
10 DIR_EXES=exes
11 DIR_DEPS=deps
12 
13 DIRS =$(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
14 EXE=complicated
15 EXE:=$(addprefix $(DIR_EXES)/,$(EXE))
16 SRCS=$(wildcard *.c)
17 OBJS=$(SRCS:.c=.o)
18 OBJS:=$(addprefix $(DIR_OBJS)/,$(OBJS))
19 DEPS=$(SRCS:.c=.dep)
20 DEPS:=$(addprefix $(DIR_DEPS)/,$(DEPS))
21 
22 all:$(DIRS) $(DEPS) $(EXE)
23 $(DIRS):
24     $(MKDIR) $@
25 $(EXE):$(OBJS)
26     $(CC) -o $@ $^
27 $(DIR_OBJS)/%.o:%.c 
28     $(CC) -o $@ -c $^
29 $(DIR_DEPS)/%.dep:%.c
30     @echo "Creating $@ ..."
31     @set -e;\
32     $(RM) $(RMFLAGS) $@.tmp;\
33     $(CC) -E -MM $^ >$@.tmp;\
34     sed 's,\(.*\)\.o[:]*,objs/\1.o:,g' <$@.tmp >$@;\
35     $(RM) $(RMFLAGS) $@.tmp
36 clean:
37     $(RM) $(RMFLAGS) $(DIRS) 

(這個Makefile廢了不少力氣才想明白。。。)

和之前的complicated項目的Makefile相比:

1,增加了deps文件夾

2,刪除了目標文件創建規則中的foo.h依賴,并將規則中的$<變回了$^

3,增加了了DEPS變量用于存放文件

4,為all目標增加了$(DEPS)

5,增加了一個用于創建依賴關系問價你的規則。在這個規則中,使用了gcc的-E和-MM選項來獲取依賴關系。在生成最終的依賴關系文件之前,使用了一個由$@.tmp表示的臨時文件,且在依賴文件生成以后將其刪除。set -e的作用是告訴shell,在生成依賴關系文件的過程中如果出現任何錯誤就直接退出。shell異常退出的最終表現就是make會告訴我們出錯了,從而停止后續的make工作。如果不設置這一行,當構建依賴出錯時,make還會繼續后面的工作并最終出錯,這并不是我們希望看到的。讀者可以測試故意在源文件或者頭文件中植入錯誤并去掉set -e選項觀察make的行為和加上set -e有上面不同。

這里還有幾個知識點需要補充。

1.對于規則中的每一條命令,make都是在一個新的shell上運行它的。

2.如果希望多個命令在同一個shell中運行,可以用“;”將這些命令連起來。

3.當命令很長時,可以用“\”將一個命令書寫成多行。

為了更好的理解第一點,我們做一個實驗。現假設需要創建一個test目錄,然后在這個test目錄下再創建一個subtest子目錄。編寫Makefile如下:

?

1 .PHONY:all
2 all:
3     @mkdir test
4     @cd    test
5     @mkdir subtest

?

可以看到test和subtest是同級目錄并非父子目錄,然后用上面提到的知識點更改Makefile:

1 .PHONY:all
2 all:
3     @mkdir test;\
4     cd    test;\
5     mkdir subtest

?

這樣就可以達到目的了。不過你可能會想,為什么這里后面的cd和最后一個mkdir不需要在前面加上@呢?那么我們加上試試呢?

?

如果使用了分號“ ;”,表示命令在同一個shell中運行,而且使用“ \”鏈接一條命令,既然是一條命令,自然不能夠識別后面的@cd或者@mkdir,因為最開始的mkdir使用@,讓終端不顯示執行的指令,后面的cd和mkdir是在前面操作的情況下進行的?,此時,直接使用命令即可。

還有一個需要注意的地方:

如同

EXE=complicated
EXE:=$(addprefix $(DIR_EXES)/,$(EXE))
這樣的Makefile,為什么第二個賦值我們是用:=而不是直接=呢?這也是需要注意的小細節,這個在之前的隨筆中已經說過,要是用=,會導致無限遞歸,為什么呢?因為EXE在復制號左邊,而右邊又有$(EXE)(EXE的引用),這樣會無限調用,make報錯。不信你可以試試。
最后,來到最難的一個東西:
sed 's,\(.*\)\.o[:]*,objs/\1.o:,g' <$@.tmp >$@;
這個語句才是最難的,也是最費力的。
首先要簡單說明一下linux中sed的用法:
1.簡介
sed是非交互式的編輯器。它不會修改文件,除非使用shell重定向來保存結果(這里這個復雜命令就用了重定向來更改)。默認情況下,所有的輸出行都被打印到屏幕上。
sed編輯器逐行處理文件(或輸入),并將結果發送到屏幕。具體過程如下:首先sed把當前正在處理的行保存在一個臨時緩存區中(也稱為模式空間),然后處理臨時緩沖區中的行,完成后把該行發送到屏幕上。sed每處理完一行就將其從臨時緩沖區刪除,然后將下一行讀入,進行處理和顯示。處理完輸入文件的最后一行后,sed便結束運行。sed把每一行都存在臨時緩沖區中,對這個副本進行編輯,所以不會修改原文件。
2.定址
定址用于決定對哪些行進行編輯。地址的形式可以是數字、正則表達式、或二者的結合。如果沒有指定地址,sed將處理輸入文件的所有行

3.命令與選項

sed命令告訴sed如何處理由地址指定的各輸入行,如果沒有指定地址則處理所有的輸入行。

此處sed引用此博客, 參考鏈接:http://www.cnblogs.com/edwardlost/archive/2010/09/17/1829145.html

3.1 sed命令

?命令?功能
?a\

?在當前行后添加一行或多行。多行時除最后一行外,每行末尾需用“\”續行

?c\?用此符號后的新文本替換當前行中的文本。多行時除最后一行外,每行末尾需用"\"續行
?i\?在當前行之前插入文本。多行時除最后一行外,每行末尾需用"\"續行
?d?刪除行
?h?把模式空間里的內容復制到暫存緩沖區
?H?把模式空間里的內容追加到暫存緩沖區
?g?把暫存緩沖區里的內容復制到模式空間,覆蓋原有的內容
?G?把暫存緩沖區的內容追加到模式空間里,追加在原有內容的后面
?l?列出非打印字符
?p?打印行
?n?讀入下一輸入行,并從下一條命令而不是第一條命令開始對其的處理
?q?結束或退出sed
?r?從文件中讀取輸入行
?!?對所選行以外的所有行應用命令
?s?用一個字符串替換另一個
?g?在行內進行全局替換
??
?w?將所選的行寫入文件
?x?交換暫存緩沖區與模式空間的內容
?y?將字符替換為另一字符(不能對正則表達式使用y命令)

?

3.2 sed選項

?選項?功能
?-e?進行多項編輯,即對輸入行應用多條sed命令時使用
?-n?取消默認的輸出
?-f?指定sed腳本的文件名
?
?
?
?
4.退出狀態
sed不向grep一樣,不管是否找到指定的模式,它的退出狀態都是0。只有當命令存在語法錯誤時,sed的退出狀態才不是0。
?
?
?
5.正則表達式元字符
?與grep一樣,sed也支持特殊元字符,來進行模式查找、替換。不同的是,sed使用的正則表達式是括在斜杠線"/"之間的模式。
如果要把正則表達式分隔符"/"改為另一個字符,比如o,只要在這個字符前加一個反斜線,在字符后跟上正則表達式,再跟上這個字符即可。例如:sed -n '\o^Myop' datafile
?

?

?元字符?功能?示例
?^?行首定位符?/^my/? 匹配所有以my開頭的行
?$?行尾定位符?/my$/? 匹配所有以my結尾的行
?.?匹配除換行符以外的單個字符?/m..y/? 匹配包含字母m,后跟兩個任意字符,再跟字母y的行
?*?匹配零個或多個前導字符?/my*/? 匹配包含字母m,后跟零個或多個y字母的行
?[]?匹配指定字符組內的任一字符?/[Mm]y/? 匹配包含My或my的行
?[^]?匹配不在指定字符組內的任一字符?/[^Mm]y/? 匹配包含y,但y之前的那個字符不是M或m的行
?\(..\)?保存已匹配的字符?1,20s/\(you\)self/\1r/? 標記元字符之間的模式,并將其保存為標簽1,之后可以使用\1來引用它。最多可以定義9個標簽,從左邊開始編號,最左邊的是第一個。此例中,對第1到第20行進行處理,you被保存為標簽1,如果發現youself,則替換為your。
?&?保存查找串以便在替換串中引用?s/my/**&**/??符號&代表查找串。my將被替換為**my**
?\<?詞首定位符?/\<my/? 匹配包含以my開頭的單詞的行
?\>?詞尾定位符?/my\>/? 匹配包含以my結尾的單詞的行
?x\{m\}?連續m個x?/9\{5\}/ 匹配包含連續5個9的行
?x\{m,\}?至少m個x?/9\{5,\}/? 匹配包含至少連續5個9的行
?x\{m,n\}?至少m個,但不超過n個x?/9\{5,7\}/? 匹配包含連續5到7個9的行
?
1.并不是只有 / 可作為模式分割符,很多符合如 , ; 都可以,尤其是模式中有 / 時使用其他分割符更方便,這里這個復雜例子使用逗號,做模式分隔符;
sed 's,\(.*\)\.o[:]*,objs/\1.o:,g' <$@.tmp >$@;
現在來分解這個復雜表達式,首先,sed s表示我們想用一個字符串替換另一個字符串,這也是我們使用sed的原因,它的s命令就
可以達到這個效果。
's,\(.*\)\.o[:]*,objs/\1.o:,g'第一次分解,此時需要知道,單引號是一對的,即s前面的'和g后面的'是一個整體單引號,
這也是sed命令的基礎,至于單引號和雙引號有什么區別,可百度谷歌或者必應。(但是我之前測試的單引號和雙引號并不是我搜索所顯示的那樣,后面再試試吧)
繼續分解,s,中s是替換字符串的意思,這個在上面的表格中可以查詢到,逗號,表示模式分隔符,在這種有/出現的字符串中,我們選擇了逗號,作為分隔符號。
所以下一次分解應該倒下一個逗號處,
\(.*\)\.o[:]*,
這里首先看 .* 它表示匹配任意字符,\( \)是一個整體,也是通過上面的表格得到的,然后轉義字符\和.o在一起,把.的作用(匹配除換行符的單個字符)變成普通的.(就是一個字符.),那么這一句話就是
操作字符串所有有.o的且在.0后面(可以有空格)匹配:的零個或多個字符串。
objs/\1.o:,g
這里要解釋的是\1.o 這里用了轉義字符\加上1,這表示什么呢?尤其是這個1,表示的就是前面\( \)內的字符串,這是組
的概念,如何知道是第幾組呢?前面的第一個\(\)的就是第一組,用轉義字符\1表示,依次類推。g在sed中表示行內全局替換
這樣,我們做一個假設例子來說明。
abc.o : 用這個代表
\(.*\)\.o[:]*
后objs/\1.o:,g之后呢,abc.o :變成了 objs/abc.o: 這里相當于給前面的通用匹配加上了objs/前綴,并且把:和.o之前的空格去掉了
最后這個<$@.tmp >$@;這不屬于sed的內容了,屬于linux和Makefile的東西,$@.tmp重定向輸入給前面的sed替換操作,
$@代表目標在Makefile中,$@.tmp是前面的Makefile生成的,<重定向,看方向是輸入,
就是把$@.tmp重定向輸入給sed,經過sed替換之后,再輸出重定向 > 到$@,這個是目標。
這樣再回過頭去看之前那個Makefile就可以看懂了。
 
 
 
 
 
?

轉載于:https://www.cnblogs.com/yangguang-it/p/6818664.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/455747.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/455747.shtml
英文地址,請注明出處:http://en.pswp.cn/news/455747.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

JavaScript使用場景

JavaScript嵌入頁面的方式 1、行間事件&#xff08;主要用于事件&#xff09; <input type"button" name"" onclick"alert(ok&#xff01;);">2、頁面script標簽嵌入 <script type"text/javascript">var a 你好&#…

集合添加元素python_Python 集合(Set)

Python 集合&#xff08;Set&#xff09; 在本文中&#xff0c;您將學習關于Python集的所有內容;如何創建它們、添加或刪除其中的元素&#xff0c;以及在Python中對集合執行的所有操作。 Python中的集合是什么&#xff1f; 集合是項目的無序集合。每個元素都是唯一的&#xff0…

一個極其高效的虛擬機內存冗余消除機制:UKSM

Linux內核機制KSM(Kernel Samepage Merging)能合并KVM虛擬機之間相同內存的頁面&#xff0c;被CentOS, RHEL之類的服務器內核廣泛采用&#xff0c;但是其速度很慢。UKSM(Ultra KSM)是國人在此基礎上的極大改進。通過使用了更高級的算法&#xff0c;UKSM的新特性包括&#xff1a…

【分享】 codeReview 的重要性

研發都知道代碼 Review 的重要性&#xff0c;在代碼 Review 也越來越受大家重視&#xff0c;我參與了大量的代碼 Review&#xff0c;明顯地感受到有效的代碼 Review 不但能提高代碼的質量&#xff0c;更能促進團隊溝通協作&#xff0c;建立更高的工程質量標準&#xff0c;無論對…

FFMPEG功能

FFMPEG功能1&#xff0e; 視頻音頻格式轉換Ffmpeg能使用任何支持的格式和協議作為輸入&#xff1a;*比如你可以輸入YUV文件&#xff1a;ffmpeg -i /tmp/test%d.Y /tmp/out.mpg 它將要使用如下文件&#xff1a; /tmp/test0.Y, /tmp/test0.U, /tmp/test0.V,/tmp/test1.Y, /tmp…

線程02

2019獨角獸企業重金招聘Python工程師標準>>> 線程中有幾個方法需要我們區分 1 sleep方法是表示線程執行到這的時候只是暫時處于“睡眠”狀態&#xff0c;在這種狀態下線程是不會釋放CPU資源的&#xff0c;當到達休眠時間后&#xff0c;線程繼續“起來”干活。當線程…

@postconstruct注解方法沒有執行_把對象的創建交給spring來管理(注解IOC)

自動按照類型注入/** * 賬戶的業務層實現類 * * 曾經XML的配置&#xff1a; * <bean id"accountService" class"com.itheima.service.impl.AccountServiceImpl" * scope"" init-method"" destroy-method""> * <pro…

解決-ubuntu 安裝redis無法啟動

解決-ubuntu 安裝redis無法啟動 環境 Ubuntu 16.04 Port 6379 Redis version 5:4.0.9-1 1-安裝 apt install redis2-安裝完成-自啟時提示啟動服務失敗 Errors were encountered while processing:redis-serverredis E: Sub-process /usr/bin/dpkg returned an error code (1…

JavaScript中的數據類型轉換

js數據類型轉換 使用&#xff1a;Number&#xff08;&#xff09;、parseInt() 和parseFloat&#xff08;&#xff09; 做類型轉換 Number()強轉一個數值(包含整數和浮點數)。*parseInt()強轉整數&#xff0c;*parseFloat&#xff08;&#xff09;強轉浮點數函數isNaN()檢測參…

web.xml(8)_jsp-config

13.jsp-config jsp-config元素主要用來設定JSP的相關配置,<jsp:config>包含<taglib>和<jsp-property-group>兩個子元素.當中<taglib>元素 在JSP 1.2時就已經存在了;而<jsp-property-group>是JSP 2.0新增的元素. taglib :對標記庫描寫敘述符文件&…

CABAC之手把手教你編碼

首先要說明的是CABAC的生命期是SLICE,因此本篇所講的也是一個SLICE里CABAC的流程,其次對于我們來說場模式幾乎用不到,所以本文的編碼流程只使用幀模式,因此實際上用到的表只有277個, 當然如果我寫成399, 不是說里面所有表都用到的. 這里只是聲明一下這個問題, 如果大家實際操作…

python網絡攻擊代碼_Python-python網絡編程寫arp攻擊代碼

from scapy.all import ARP,send,arping import sys,re,random,time stdoutsys.stdout ip IPADDR"192.168.1.102" 網關 gateway_ip"192.168.1.1" tmp[] 偽造網關mac地址 for i in range(0,6): tmp.append(str("%02x"%random.randint(0x01,0xfe))…

Kubernetes初步學習

今天分享如題&#xff1a; Kubernetes 本篇內容源于工作項目需要自學 但K8s確實現在十分的主流so推薦給大家 最近更新緩慢由于工作太忙惹&#xff0c;忙里偷閑整理愿分享能與君共勉&#x1f4aa; 大家新年快樂&#x1f389; &#x1f508;言歸正題&#xff0c;相信很多朋友…

手機界面常見的的九宮格

手機界面常見的的九宮格 手機界面常見的的九宮格 首先布局的話需要用到一個mainactivity和一個item的布局目前用得最多也最熟悉的還是LinearLayout布局所以&#xff0c;一下也是&#xff0c;按套路就不過多贅述了。 <GridView android:layout_width"wrap_content"…

JavaScript中的運算符

js運算符 算 字 賦 比 邏 位 它算術運算符 - * / --字符串連接 賦值運算 - %比較運算符 < > > < ! !邏輯運算符 && || !位運算 ^ & | << >>其它運算符 ? : 三元運算符 delete&#xff1…

CABAC編碼

H&#xff0e;264&#xff0f;AVC標準采用了很多新技術和新方法&#xff0c;大大提高了視頻編碼效率&#xff0c;其中CABAC便是H&#xff0e;264&#xff0f;AVC采用的新型熵編碼方法之一。CABAC采用了高效的算術編碼思想&#xff0c;同時充分考慮了視頻流相關統計特性&#xf…

【教程分享】Jmeter入門教程

好&#xff01;回歸學長每周的教程分享&#xff01; PART2 >今天又來分享Jmter 因為最近好像有相關工作內容 提前準備資修一下 分享僅供參考- JMeter的作用對軟件做壓力測試 1.能夠對HTTP和FTP服務器進行壓力和性能測試&#xff0c; 也可以對任何數據庫進行同樣的測試&…

linux 特殊shell變量

特殊變量 環境變量&#xff1a; 系統本身運行需要由linux系統提前創建好的一類變量 主要用于用戶的工作環境&#xff0c;包括&#xff08;用戶的宿主目錄&#xff0c;命令的查找路徑&#xff0c;用戶的當前目錄&#xff0c;登錄的終端等&#xff09;環境變量的值由操作系統本身…

JavaScript中的循環

js循環 程序中進行有規律的重復性操作&#xff0c;需要用到循環語句。 break 和 continue 語句對循環中的代碼執行提供了更嚴格的控制。 for循環 for(var i0;i<len;i){...... }while循環 var i0;while(i<8){......i;}for-in 語句 for-in 語句是嚴格的迭代語句&…

快速傅里葉變換python_FFT快速傅里葉變換的python實現過程解析

FFT是DFT的高效算法&#xff0c;能夠將時域信號轉化到頻域上&#xff0c;下面記錄下一段用python實現的FFT代碼。 # encodingutf-8 import numpy as np import pylab as pl # 導入和matplotlib同時安裝的作圖庫pylab sampling_rate 8000 # 采樣頻率8000Hz fft_size 512 # 采樣…