makefile-- 自動生成依賴關(guān)系 示例

自動生成依賴關(guān)系


1、編譯行為帶來的缺陷

  • 預(yù)處理器將頭文件中的代碼直接插入源文件
  • 編譯器只通過預(yù)處理后的源文件產(chǎn)生目標(biāo)文件
  • 因此,規(guī)則中以源文件為依賴,命令可能無法執(zhí)行

示例1觀察以下makefile文件是否正確:當(dāng)修改func.h中宏HELLO的內(nèi)容后,執(zhí)行make命令發(fā)現(xiàn),編譯器無法更新main.c和func.c,進(jìn)而無法更新執(zhí)行的結(jié)果:原因在于func.h中更新的內(nèi)容無法自動更新到func.c和main.c文件中,進(jìn)而導(dǎo)致編譯的hello.out文件結(jié)果無任何變化。

func.h

#ifndef FUNC.H
#define FUNC.H

#define HELLO "hello makefile"

void foo();

#endif

func.c

#include "stdio.h"
#include "func.h"

void foo()
{
    printf("void foo():%s\n",HELLO);
    
}

main.c

#include "stdio.h"
#include "func.h"

extern void foo();

int main()
{
    foo();
    
    return 0;
}

makefile

OBJS := func.o main.o

hello.out := $(OBJS)
    @gcc -o $@ $^
    @echo "Target File => $@"
    
$(OBJS) : %.o : %.c
    @gcc -o $@ -c $^
11--1-1.PNG

解決方法1:修改makefile文件--將頭文件作為依賴條件出現(xiàn)于每個目標(biāo)對應(yīng)的規(guī)則中

OBJS := func.o main.o

hello.out := $(OBJS)
    @gcc -o $@ $^
    @echo "Target File => $@"
    
$(OBJS) : %.o : %.c func.h
    @gcc -o $@ -c $<
11--1-2.PNG

優(yōu)點(diǎn):

頭文件的更改會更新到相關(guān)的源文件中,并更新到最終的目標(biāo)文件

缺點(diǎn):

  • 當(dāng)頭文件改動,任何源文件都將被重新編譯(編譯低效)
  • 當(dāng)項目中頭文件數(shù)量巨大時,makefile將很難維護(hù)

解決方案2:

  • 通過命令自動生成對頭文件的依賴
  • 將生成的依賴自動包含進(jìn)makefile中
  • 當(dāng)頭文件改動后,自動確認(rèn)需要重新編譯的文件

針對解決方案2需要使用的技術(shù):

(1) linux的sed命令

  • sed是一個流編輯器,用于流文本的修改(增/刪/改/查)

  • sed可用于流文本中的字符串替換

  • sed的字符串替換方式為:sed 's:src:des:g'


    11——1.png

    例如執(zhí)行下列語句:

echo "test=>abc+abc+abc" | sed 's:abc:xyz:g'

test的內(nèi)容將變?yōu)閤yz+xyz+xyz

sed的正則表達(dá)式支持

  • 在sed中可以用正則表達(dá)式匹配替換目標(biāo)
  • 并且可以使用匹配的目標(biāo)生成替換結(jié)果
    例如
sed 's,\(.*\)\.o[ :]*,objs/\1.o : ,g'

示例2--sed用法

11--2.PNG

(2) 編譯器依賴生成選項gcc -MM(gcc -M)

  • gcc -M des
    獲取目標(biāo)des的完整依賴關(guān)系
  • gcc -MM des
    獲取目標(biāo)des的部分依賴關(guān)系
    (-E 僅對依賴關(guān)系做初步解析)


    11-2-gcc M gcc MM.PNG

(3)makefile中的include關(guān)鍵字

  • 類似C語言中的include
  • 將其他文件的內(nèi)容原封不動的搬入當(dāng)前文件
  • 語法:include filename
    例如:
    include foo.make
    include *.mk
    include $(var)

make對include關(guān)鍵字的處理方式

在當(dāng)前目錄搜索或指定目錄搜索目標(biāo)文件

  • 搜索成功:將文件內(nèi)容搬入當(dāng)前makefile中
  • 搜索失?。寒a(chǎn)生警告
    -- 以文件名作為目標(biāo)查找并執(zhí)行相應(yīng)規(guī)則
    -- 當(dāng)文件名對應(yīng)的規(guī)則不存在時,最終產(chǎn)生錯誤

示例3-1--include用法--目標(biāo)文件不存在,目標(biāo)規(guī)則不存在--無操作,直接報錯

.PHONY : all

include test.txt

all :
    @echo "this is $@"
11-3-1.PNG

示例3-2--include用法--當(dāng)目標(biāo)文件不存在,目標(biāo)規(guī)則存在----以文件名查找規(guī)則,并執(zhí)行

.PHONY : all

include test.txt

all :
    @echo "this is $@"
    
test.txt :
    @echo "this is $@"
11-3-2.PNG

示例3-3--include用法--目標(biāo)文件存在,目標(biāo)規(guī)則存在

.PHONY : all

include test.txt

all :
    @echo "this is $@"
    
test.txt :
    @echo "creating $@"
    @echo "other : ; @echo "this is other" " > test.txt

--執(zhí)行make命令,將執(zhí)行makefile中的第一條規(guī)則


![11-3-3.PNG](https://upload-images.jianshu.io/upload_images/10834716-7e0bc3e0a74793c1.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

--執(zhí)行make all命令將執(zhí)行all規(guī)則


11-3-4.PNG

include 暗黑操作一:

  • 使用減號(-)不但關(guān)閉了include發(fā)出的警告,同時關(guān)閉了錯誤;當(dāng)錯誤發(fā)生時make將忽略做這些錯誤!

示例3-5-1--include不使用(-)會報告所有的錯誤和警告

.PHONY : all

include test.txt

all :
    @echo "this is $@"
11-3-5-1.PNG

示例3-5-2--include(-)關(guān)閉了include發(fā)出的警告和錯誤

.PHONY : all

include test.txt

all :
    @echo "this is $@"
11-3-5-2.PNG

include 暗黑操作二:

  • 如果include觸發(fā)規(guī)則創(chuàng)建了文件,之后還會執(zhí)行規(guī)則中的命令,然后重新執(zhí)行include后的命令

示例3-6-1-include執(zhí)行的規(guī)則中不存在依賴;則會將規(guī)則直接包含進(jìn)makefile

.PHONY : all

-include test.txt

all :
    @echo "this is $@"
    
test.txt :
    @echo "creating $@ ..."
    @echo "other"
11-3-6--1.PNG

示例3-6-2--include執(zhí)行時先判斷規(guī)則是否存在,如果存在會再檢查依賴是否是最新的,如果依賴比當(dāng)前規(guī)則要新,會直接執(zhí)行依賴;否則直接執(zhí)行規(guī)則
示例3-6-2
makefile

.PHONY : all

-include test.txt

all :
    @echo "this is $@"
    
test.txt : b.txt
    @echo "creating $@ ..."
    @echo "all : c.txt" > test.txt

test.txt

all : a.txt

示例3-6-2 當(dāng)規(guī)則文件比依賴文件內(nèi)容要新--test.txt比b.txt時間戳更新,執(zhí)行make all結(jié)果如下:

11-3-6--2.PNG

示例3-6-2 當(dāng)依賴文件比規(guī)則文件內(nèi)容要新--b.txt比test.txt時間戳更新,執(zhí)行make all結(jié)果如下:

11-3-6--3.PNG

include的總結(jié):

  • 當(dāng)目標(biāo)文件不存在----以文件名查找規(guī)則,并執(zhí)行

  • 當(dāng)目標(biāo)文件不存在,且查找到的規(guī)則中創(chuàng)建了目標(biāo)文件----將創(chuàng)建成功的目標(biāo)文件包含進(jìn)當(dāng)前makefile

  • 當(dāng)目標(biāo)文件存在,將目標(biāo)文件包含進(jìn)當(dāng)前makefile;并以目標(biāo)文件名查找是否有相應(yīng)規(guī)則--如果有,比較規(guī)則的依賴關(guān)系,決定是否執(zhí)行規(guī)則的命令;否則,無操作。

  • 當(dāng)目標(biāo)文件存在,且目標(biāo)名對應(yīng)的規(guī)則被執(zhí)行

    如果規(guī)則中的命令更新了目標(biāo)文件--make重新包含目標(biāo)文件,替換之前包含的內(nèi)容
    如果目標(biāo)文件未被更新,無操作。

(4) makefile中命令的執(zhí)行機(jī)制

  • 規(guī)則中的每個命令默認(rèn)是在一個新的進(jìn)程中執(zhí)行(Shell)
  • 可以通過接續(xù)符(;)將多個命令組合成一個命令
  • 組合的命令依次在同一個進(jìn)程中被執(zhí)行
  • set -e指定發(fā)生錯誤后立即退出執(zhí)行
    示例4-1--規(guī)則中命令的執(zhí)行--無接續(xù)符--規(guī)則中的每個命令默認(rèn)是在一個新的進(jìn)程中執(zhí)行
.PHONY : all

all :
    mkdir test
    cd test
    mkdir subtest
11--4--1.PNG

示例4-2--規(guī)則中命令的執(zhí)行--接續(xù)符--組合的命令依次在同一個進(jìn)程中被執(zhí)行

.PHONY : all

all :
    set -e;\
    mkdir test;\
    cd test;\
    mkdir subtest
11--4--2.PNG

解決方案2的初步思路

  • 通過gcc -MM和sed得到.dep依賴文件(目標(biāo)的部分依賴)
    技術(shù)點(diǎn):規(guī)則中命令的連續(xù)執(zhí)行
  • 通過include指令包含所有的.dep依賴文件
    技術(shù)點(diǎn):當(dāng).dep依賴文件不存在時,使用規(guī)則自動生成
  • 當(dāng)include發(fā)現(xiàn).dep文件不存在時
    通過規(guī)則和命令創(chuàng)建deps文件
    將所有.dep文件創(chuàng)建到deps文件夾
    .dep文件中記錄目標(biāo)文件的依賴關(guān)系
    代碼部分設(shè)計如下:
$(DIR_DEPS):
    $(MKDIR) $@

$(DIR_DEPS)/%.dep : $(DIR_DEPS) %.c
    @echo "Creating $@ ..."
    @set -e;\
    $(CC) -MM -E $(filter %.c,$^)) | sed 's,\(.*\)\.o[ :]*,objs/\1.o : ,g' > $@

觀察上述代碼,(DIR_DEPS)/%.dep :(DIR_DEPS) %.c 語句存在問題:當(dāng)?shù)谝淮螆?zhí)行時會創(chuàng)建deps文件夾和對應(yīng)的.dep文件,而第二次deps文件夾內(nèi)容會被新的.dep文件進(jìn)行更新,導(dǎo)致第一次生成的.dep文件因為依賴更新而會被重復(fù)執(zhí)行。
解決方法如下:使用ifeq動態(tài)決定.dep目標(biāo)的依賴,實現(xiàn)代碼如下:

ifeq ("$(wildcard $(DIR_DEPS))","")
$(DIR_DEPS)/%.dep : $(DIR_DEPS) %.c
else
$(DIR_DEPS)/%.dep : %.c
endif

示例5
func.h

#ifndef FUNC_H
#define FUNC_H

#define HELLO "hello world"

void foo();

#endif

func.c

#include "stdio.h"
#include "func.h"

void foo()
{
    printf("void foo():%s\n",HELLO);    
}

main.c

#include "stdio.h"
#include "func.h"

extern void foo();

int main()
{
    foo();

    return 0;
}

makefile

.PHONY : all clean

MKDIR := mkdir
RM := rm -rf
CC := gcc

DIR_DEPS := deps

SRCS := $(wildcard *.c)
DEPS := $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/,$(DEPS))

ifeq ("$(MAKECMDGOALS)","all")
-include $(DEPS)
endif

ifeq ("$(MAKECMDGOALS)","")
-include $(DEPS)
endif

all :
    @echo "$@"
    
$(DIR_DEPS) :
    $(MKDIR) $@
    
ifeq ("$(wildcard $(DIR_DEPS))","")
$(DIR_DEPS)/%.dep : $(DIR_DEPS) %.c
else
$(DIR_DEPS)/%.dep : %.c
endif

    @echo "Creating $@ ..."
    @set -e;\
    $(CC) -MM -E $(filter %.c,$^) | sed 's,\(.*\)\.o[ :]*,objs/\1.o : ,g' > $@
    
clean :
    $(RM) $(DIR_DEPS)

11-5.PNG

自動生成依賴關(guān)系的解決方案:

  • 將依賴文件名作為目標(biāo)加入自動生成的依賴關(guān)系中
  • 通過include加載依賴文件時判斷是否執(zhí)行規(guī)則
  • 在規(guī)則執(zhí)行時重新生成依賴關(guān)系文件
  • 最后加載新的依賴文件

示例6--最終代碼實現(xiàn):

11-2最終文件依賴關(guān)系.png

define.h

#ifndef DEFINE_H
#define DEFINE_H

#define HELLO "hello world"

#endif

func.h

#ifndef FUNC_H
#define FUNC_H

#include "define.h"

void foo();

#endif

func.c

#include "stdio.h"
#include "func.h"

void foo()
{
    printf("void foo():%s\n",HELLO);    
}

main.c

#include "stdio.h"
#include "func.h"

extern void foo();

int main()
{
    foo();

    return 0;
}

makefile

.PHONY : all clean

MKDIR := mkdir
RM := rm -rf
CC := gcc

DIR_DEPS := deps
DIR_EXES := exes
DIR_OBJS := objs

DIRS:= $(DIR_DEPS) $(DIR_EXES) $(DIR_OBJS)

EXE := app.out
EXE := $(addprefix $(DIR_EXES)/,$(EXE))

SRCS := $(wildcard *.c)
OBJS := $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/,$(OBJS))
DEPS := $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/,$(DEPS))



all : $(DIR_OBJS) $(DIR_EXES) $(EXE)

ifeq ("$(MAKECMDGOALS)","all")
-include $(DEPS)
endif

ifeq ("$(MAKECMDGOALS)","")
-include $(DEPS)
endif

$(EXE) : $(OBJS)
    $(CC) -o $@ $^
    @echo "Success! Target => $@"

$(DIR_OBJS)/%.o : %.c
    $(CC) -o $@ -c $(filter %.c,$^) 

$(DIRS) :
    $(MKDIR) $@

ifeq ("$(wildcard $(DIR_DEPS))","")
$(DIR_DEPS)/%.dep : $(DIR_DEPS) %.c
else
$(DIR_DEPS)/%.dep : %.c
endif

    @echo "Creating $@ ..."
    @set -e;\
    $(CC) -MM -E $(filter %.c,$^) | sed 's,\(.*\)\.o[ :]*,objs/\1.o $@: ,g' > $@

clean :
    $(RM) $(DIRS)
11-6.PNG

小結(jié):

  • makefile中可以將目標(biāo)的依賴拆分寫到不同的地方
  • include關(guān)鍵字能夠觸發(fā)相應(yīng)規(guī)則的執(zhí)行
  • 如果規(guī)則的執(zhí)行到政治依賴更新,可能導(dǎo)致再次解釋執(zhí)行相應(yīng)規(guī)則
  • 依賴文件也需要依賴于源文件得到正確的編譯決策
  • 自動生成文件間的依賴關(guān)系能夠提高makefile的移植性
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 來自陳浩的一片老文,但絕對營養(yǎng)。 示例工程:3 個頭文件*.h,和 8 個 C 文件*.c。 初 編譯過程,源文件...
    周筱魯閱讀 4,786評論 0 17
  • makefile關(guān)系到整個工程的編譯規(guī)則,一個工程中的源文件不計其數(shù),按其類型、功能、模塊分別放在若干的目錄當(dāng)中,...
    Joe_HUST閱讀 1,985評論 0 3
  • 1.Makefile規(guī)范 target 這 一 個 或 多 個 的 目 標(biāo) 文 件 依 賴 于prerequisi...
    G風(fēng)閱讀 2,088評論 0 3
  • 文/文子先生 自由的白襯衫 忘不了中分頭的傻樣 瞇起眼的小黑鏡 任由拖鞋嘀嗒響 流氓的口哨 伴著嘻哈小曲兒嘰喳唱 ...
    文子先生閱讀 393評論 0 3
  • 1980年版的《中國古典文學(xué)名著題解》,是我打開書籍的啟蒙。伴我案頭大概有快三十年了吧。后來讀的很多書,都是受它的...
    大樹壹棵閱讀 173評論 0 0

友情鏈接更多精彩內(nèi)容