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

        我們在之前的 makefile 學(xué)習(xí)中,其目標(biāo)文件(.o)只依賴于源文件(.c)。那么如果在源文件中還包含有頭文件,此時編譯器如何編譯源文件和頭文件呢?我們來看看編譯行為帶來的缺陷:1、預(yù)處理器將頭文件中的代碼直接插入源文件;2、編譯器只通過預(yù)處理后的源文件產(chǎn)生目標(biāo)文件;3、規(guī)則中以源文件為依賴,命令就可能無法執(zhí)行。

成都創(chuàng)新互聯(lián)主要從事做網(wǎng)站、網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)景谷,10年網(wǎng)站建設(shè)經(jīng)驗,價格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):028-86922220

        我們來看看下面的 makefile 有沒有問題

makefile 源碼

OBJS := func.o main.o

hello.out : $(OBJS)
    @gcc -o $@ $^
    @echo "Target File ==> $@"

$(OBJS) : %.o : %.c
    @gcc -o $@ -c $^

func.h 源碼

#ifndef _FUNC_H_
#define _FUNC_H_

#define HELLO "Hello D.T."

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"

int main()
{
    foo();

    return 0;
}

        我們來看看編譯結(jié)果

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

        我們看到已經(jīng)正確實現(xiàn)了字符串的打印,那么我們接下來在 func.h 源文件中想要改掉這個字符串為 Software 呢?試試看能不能修改成功

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

        我們看到在重新編譯的時候,它并沒有因為頭文件的改變而改變,我們在 makefile 中又沒有進(jìn)行頭文件的相關(guān)添加,改掉頭文件中的內(nèi)容肯定是不動的。下來我們在模式規(guī)則中加上頭文件,在 %.c 后加上 func.h,再來看看編譯結(jié)果

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

        我們看到直接添加之后,編譯出錯了。因為 -c 后面的目標(biāo)中含有頭文件,所以不能直接進(jìn)行編譯。我們可以只編譯 %.o 后面的第一依賴 %.c,這樣就不會去編譯 func.h 頭文件了,將下面的 $^ 改為 $< ,我們來看看效果

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

        我們看到已經(jīng)正確改過來了。經(jīng)過上面的實驗,我們看到:頭文件作為依賴條件出現(xiàn)于每個目標(biāo)對應(yīng)的規(guī)則中,當(dāng)頭文件改動時,任何源文件都將會被重新編譯(編譯低效);當(dāng)項目中頭文件巨大時,makefile 將很難維護(hù)。那么我們的頭腦中不禁會冒出這么個想法:通過命令對自動生成對頭文件的依賴;將生成的依賴自動包含進(jìn) makefile 中;當(dāng)頭文件改動后,自動確認(rèn)需要重新編譯的文件。那么此時我們還需要知道一個命令,Linux 中的 sed 命令。sed 是一個流編輯器,用于流文本的修改(增、刪、查、改);它可用于流文本中的字符串替換,其字符串替換方式為:sed 's:src:des:g',具體格式如下

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

        sed 同樣也支持正則表達(dá)式,在 sed 中可以用正則表達(dá)式匹配替換目標(biāo),并且可以使用匹配的目標(biāo)生成替換結(jié)果。格式如下

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

        下來我們以代碼為例來看看 sed 命令是如何使用的

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

        再來看看 gcc 關(guān)鍵編譯選項,獲取目標(biāo)的完整依賴關(guān)系:gcc -M test.c;獲取目標(biāo)的部分依賴關(guān)系:gcc -MM test.c。makefile 如下

.PHONY : test

test :
    gcc -M main.c

        編譯結(jié)果如下

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

        我們看到 -M 是獲取了它的所有依賴關(guān)系,再來試試 -MM 呢

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

        我們看到 -MM 后,它只依賴與 main.c func.h。我們可以拆分目標(biāo)的依賴,即將目標(biāo)的完整依賴差分為多個部分依賴。格式如下

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

        我們來做個實驗

.PHONY : a b c

test : a b

test : b c

test :
    @echo "$^"

        我們來打印看看目標(biāo) test 的依賴都有哪些,編譯結(jié)果如下

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

        那么我們思考下:如何將 sed 和 gcc -MM 用于 makefile,并自動生成依賴關(guān)系呢?

        我們再來看看 makefile 中的 include 關(guān)鍵字,它類似于 C 語言中的 include,是將其它文件的內(nèi)容原封不動的搬入當(dāng)前文件。make 對 include 關(guān)鍵字的處理方式是在當(dāng)前目錄下搜索或指定搜索目標(biāo)文件。如果搜索一成功,便將文件內(nèi)容搬入當(dāng)前 makefile 中;如果搜索失敗,將會產(chǎn)生警告,以文件名作為目標(biāo)查找并執(zhí)行對應(yīng)規(guī)則,當(dāng)文件名對應(yīng)的規(guī)則不存在時,最終產(chǎn)生錯誤。格式如下

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

        下來還是以代碼為例來進(jìn)行說明

.PHONY : test

include test.txt

all :
    @echo "this is $@"

test.txt :
    @echo "test.txt"
    @touch test.txt

        我們在第 3 行包含 test.txt,可是當(dāng)前目錄下并沒有 test.txt,然后觸發(fā) test.txt 的規(guī)則。因而會打印出 test.txt,然后再創(chuàng)建 test.txt,我們來看看編譯結(jié)果

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

        我們看到確實是創(chuàng)建了一個 test.txt 文件。那么在 makefile 中命令的執(zhí)行是:1、規(guī)則中的每個命令默認(rèn)是在一個新的進(jìn)程中執(zhí)行(Shell);2、可以通過接續(xù)符(;)將多個命令組合成一個命令;3、組合的命令依次在同一個進(jìn)程中被執(zhí)行;4、set -e 指定發(fā)生錯誤后立即退出執(zhí)行。那么我們看看下面的代碼會實現(xiàn)想要的功能嗎?

.POHONY : all

all :
    mkdir test
    cd test
    mkdir subtest

        我們來看看編譯結(jié)果自動生成依賴關(guān)系(十)

        我們看到在當(dāng)前目錄下創(chuàng)建了目錄,但是 subtest 目錄卻不是在 test 目錄下創(chuàng)建的,這是怎么回事呢?在第一條命令執(zhí)行時創(chuàng)建了目錄 test,此時這個進(jìn)程已經(jīng)關(guān)閉了;在第二條命令執(zhí)行時,執(zhí)行的是另一個進(jìn)程,雖然它已經(jīng)進(jìn)入到目錄 test 中,但是隨著這個進(jìn)程的關(guān)閉,又回到了當(dāng)前目錄;第三個進(jìn)程是重新創(chuàng)建了目錄 subtest。那么如何解決這個問題呢?直接利用 set -e 和 接續(xù)符來解決

.PHONY : test

all :
    set -e; \
    mkdir test; \
    cd test; \
    mkdir subtest

        看看編譯結(jié)果

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

        那么我們之前思考問題的初步思路是:1、通過 gcc -MM 和 sed 得到 .dep 依賴文件(目標(biāo)的部分依賴),技術(shù)點(diǎn)是規(guī)則中命令的連續(xù)執(zhí)行;2、通過 include 指令包含所有的 .dep 依賴文件。技術(shù)點(diǎn)是當(dāng) .dep 依賴文件不存在時,使用規(guī)則自動生成。下面我們來看看解決方案是怎樣的

ONY : all clean

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

SRCS := $(wildcard *.c)
DEPS := $(SRCS:.c=.dep)

include $(DEPS)

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

clean :
    $(RM) $(DEPS)

        我們來看看編譯結(jié)果

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

        我們先來分析下,在執(zhí)行 make all 前,它先通過 include 包含 $(DEPS),通過 $(DEPS) 觸發(fā)模式規(guī)則,進(jìn)而創(chuàng)建文件夾。我們看到在前面出現(xiàn)兩個沒有文件夾的信息,其實這條信息是可以隱藏的。我們在 include 前面加上 - 就 OK,來看看效果

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

        我們看到并沒打印出前面的兩條信息了。那么我們再來思考下:如何組織依賴文件相關(guān)的規(guī)則與源碼編譯相關(guān)的規(guī)則,進(jìn)而形成功能完整的 makefile  程序呢?我們?nèi)绾卧?makefile 中組織 .dep 文件到指定目錄呢?初步想法是當(dāng) include 發(fā)現(xiàn) .dep 文件不存在時:1、通過規(guī)則和命令創(chuàng)建 deps 文件;2、將所有 .dep 文件創(chuàng)建到 deps 文件夾;3、.dep 文件中記錄目標(biāo)文件的依賴關(guān)系。

        我們下來看看初步的代碼設(shè)計是怎樣的

.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))

include $(DEPS)

all :
    @echo "all"

$(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' > $@

clean :
    $(RM) $(DIR_DEPS)

         我們來看看編譯結(jié)果,是不是都將所有的 .dep 文件放入一個 deps 文件中

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

        我們看到已經(jīng)實現(xiàn)效果了。我們仔細(xì)看看 make 有一個警告,說 main.dep 被修改了,也就是說 main.dep 被重新創(chuàng)建了。那么我們來分析下,為什么一些 .dep 依賴文件會被重復(fù)創(chuàng)建多次呢?deps 文件夾的時間屬性會因為依賴文件創(chuàng)建而發(fā)生改變,make 發(fā)現(xiàn) deps 文件夾比對應(yīng)的目標(biāo)更新,于是乎就觸發(fā)相應(yīng)的規(guī)則重新解析和執(zhí)行命令。那么我們知道了原因,此時這個方案該如何優(yōu)化呢?我們可以使用 ifeq 動態(tài)決定 .dep 目標(biāo)的依賴,具體 makefile 如下

.PHONY : all clean

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

DIR_DEPS := deps

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


all : 
    @echo "all"

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

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

$(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)

        我們再次編譯看看

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

        我們看到它還是報了這樣的錯誤,有可能是編譯器的優(yōu)化造成的。思路是正確的。下來我們來看看 include 的一些鮮為人知的秘密。

        A、 使用減號(-)不但關(guān)閉了 include 發(fā)出的警告,同時將關(guān)閉了錯誤;當(dāng)錯誤發(fā)生時 make 將忽略這些錯誤!以代碼為例來進(jìn)行分析說明

.PHONY : all

include test.txt

all :
    @echo "this is all"

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

        我們來編譯看看

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

        我們看到不但發(fā)出警告,而且報錯了。下來我們來在 include 前面加上 - 試試

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

        這樣它也不報錯了,直接就通過了,我們還以為 makefile 寫的對著呢。這便是第一個暗黑操作。下來看看第二個暗黑操作

        B、如果 include 觸發(fā)規(guī)則創(chuàng)建了文件,之后還會發(fā)生什么?以代碼為例來進(jìn)行分析說明

.PHONY : all

include test.txt

all :
    @echo "this is all"

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

        看看編譯結(jié)果

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

        我們進(jìn)行直接 make 的時候,發(fā)現(xiàn)它輸出的 this is other,并不是我們所期望的 this is all。這是為什么呢?因為在 include 的時候,直接將 test.txt 鋪開在這,此時會觸發(fā)規(guī)則。makefile 就變成了下面這樣

.PHONY : all

other : 
    @echo "creating $@ ..."
    @echo "this is other"

all :
    @echo "this is all"

        我們在直接 make 的時候,它默認(rèn)執(zhí)行的是第一個目標(biāo),因此便會輸出 this is other,只有當(dāng)我們 make all 的時候才會輸出 this is all。這便是 include 的第二個暗黑操作了,下面繼續(xù)看看第三個

        C、如果 include 包含的文件存在,之后會發(fā)生什么呢?以代碼為例來進(jìn)行分析說明

.PHONY : all

-include test.txt

all :
    @echo "this is all"

test.txt : b.txt
    @echo "this is $@"

        在當(dāng)前目錄下創(chuàng)建一個 b.txt 文件,看看編譯結(jié)果

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

        我們看到同樣也執(zhí)行了 test.txt 的相應(yīng)的規(guī)則。看看下面這個 makefile 將會輸出什么

.PHONY : all

-include test.txt

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

        看看結(jié)果

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

        我們看到它最后輸出的 all 的依賴是 c.txt,不應(yīng)該覺得奇怪嗎?我們明明在 all 后面沒有依賴啊。再來看看生成的 test.txt 文件,它的內(nèi)容是 all : c.txt,因此輸出的結(jié)果是我們意想不到的。那么我們關(guān)于 include 便有了這幾條總結(jié):1、當(dāng)目標(biāo)文件不存在時,以文件名查找規(guī)則并執(zhí)行;2、當(dāng)目標(biāo)文件不存在時且查找到的規(guī)則中創(chuàng)建了目標(biāo)文件,將創(chuàng)建成功的目標(biāo)文件包含進(jìn)當(dāng)前的 makefile 中;3、當(dāng)目標(biāo)文件存在,將目標(biāo)文件包含進(jìn)當(dāng)前 makefile,以目標(biāo)文件名查找是否有相應(yīng)規(guī)則,YES 的話則比較規(guī)則的依賴關(guān)系來決定是否執(zhí)行規(guī)則的命令,NO 的話則 NULL(無操作)。4、當(dāng)目標(biāo)文件存在且目標(biāo)名對應(yīng)的規(guī)則被執(zhí)行,規(guī)則中的命令更新了目標(biāo)文件,make 重新包含目標(biāo)文件,替換之前包含的內(nèi)容。目標(biāo)文件未被更新,便是 NULL(無操作)。

        經(jīng)過了這么多的知識點(diǎn)的探索,此時已經(jīng)具備實現(xiàn)之前的想法的能力了。想要實現(xiàn)的具體格式如下

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

        下面我們就根據(jù)這個來編寫相關(guān)的 makefile。

func.h 源碼

#ifndef FUNC_H
#define FUNC_H

#define HELLO "hello Makefile"

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

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

makefile 源碼

.PHONY : all clean

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

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

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 $^

$(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)

        編譯結(jié)果如下

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

        我們看到已經(jīng)自動生成了,并且最后的結(jié)果也是我們想要的,那么我們?nèi)绻?func.h 中改變字符串,看看結(jié)果是否也會改變

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

        我們看到在編譯的時候報錯了,原因是只能編譯 .c 文件,.h 頭文件不參與編譯,這時我們便要用到預(yù)定義函數(shù) filter 了。因此我們需要在 makefile 第37 行將它改為 $(CC) -o $@ -c $(filter %.c, $^);再來看看效果

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

        我們看到也成功的替換掉了。這時我們基本上已經(jīng)完成我們之前的想法了,那么在實際開發(fā)中,肯定需要時不時的添加頭文件,我們再來在 func.h 中包含一個頭文件 define.h,在 define.h 文件中定義字符串 hello-makefile,看看結(jié)果是否會跟著改變

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

        我們看到字符串并沒有發(fā)生改變,再來看看 func.dep 和 main.dep 中是否包含了 define.h

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

        也沒有包含,按理說不應(yīng)該,因為我們在 func.h 中包含了 define.h,那么在 func.c 和 main.c 中肯定也就包含了 define.h。下來我們來分析下這個,當(dāng) .dep 文件生成后,如果動態(tài)的改變頭文件間的依賴關(guān)系,那么 make 可能無法檢測到這個改變,進(jìn)而做出錯誤的編譯決策。解決方案便是:1、將依賴文件名作為目標(biāo)加入自動生成的依賴關(guān)系中;2、通過 include 加載依賴文件時判斷是否執(zhí)行規(guī)則;3、在規(guī)則執(zhí)行時重新生成依賴關(guān)系文件;4、最后加載新的依賴文件。解決方法是在 sed 命令后加上 $@,看看編譯效果,順便我們再來加上 rebuild。

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

        我們看到已經(jīng)正確實現(xiàn)了,我們來看看在 deps 文件下的 .dep 文件是否包含 define.h 呢?

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

        確實是包含了 define.h,我們再來加上 new.h,看看是否還會有效

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

        我們看到 new.h 同樣也包含進(jìn)去了。通過對綜合示例的學(xué)習(xí),總結(jié)如下:1、makefile 中可以將目標(biāo)的依賴拆分寫到不同的地方;2、include 關(guān)鍵字能夠觸發(fā)相應(yīng)規(guī)則的執(zhí)行;3、如果規(guī)則的執(zhí)行導(dǎo)致依賴更新,可能導(dǎo)致再次解釋執(zhí)行相應(yīng)規(guī)則;4、依賴文件也需要依賴于源文件得到正確的編譯決策;5、自動生成文件間的依賴關(guān)系能夠提高 makefile 的移植性。

        歡迎大家一起來學(xué)習(xí) makefile,可以加我QQ:243343083。

本文標(biāo)題:自動生成依賴關(guān)系(十)
瀏覽地址:http://bm7419.com/article0/pcgpoo.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供靜態(tài)網(wǎng)站、外貿(mào)建站、網(wǎng)站設(shè)計、品牌網(wǎng)站設(shè)計、網(wǎng)站建設(shè)用戶體驗

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

外貿(mào)網(wǎng)站建設(shè)