1. 程式人生 > >自己除錯通過的一個通用makefile模板

自己除錯通過的一個通用makefile模板

這個是從本人的QQ空間轉過來的

模板資料夾的下載地址:


 這個模板是之前公司的一個牛人寫的,我這個連門都沒入的菜鳥因為沒有專案需求,所以一直沒有花時間去研究。可惜好景不長,醬油沒打幾天,就需要我來單挑linux了,網上找了很多模板,都不盡如人意,沒辦法,只好硬著頭皮來啃這個模板了,由於水平有限,有錯漏的地方歡迎指出交流

======================================
補記:
MAKE := make -r -R -s
其中-rR引數是使用隱含規則(以免make自做聰明), 
-s引數是禁止命令的回顯(比如gcc命令輸出的 gcc -c -o .... 等字樣)

rules.mk中,每條執行前都有
$(make_debug) 字樣,這個是可以在common.mk中選擇是否顯示執行的make命令的,比如:
make[1]:正在離開目錄 `/data/mktest/aptt3/lib_bar' make[1]: 正在進入目錄 `/data/mktest/aptt3/lib_foo'  
======================================

檔案結構:
Makefile
  | -- makefiles
  |       | -- rules.mk / common.mk / arm.mk / i386.mk
  | -- third_party
  | -- 
includes
  | -- 
app_test
  | -- 
lib_bar / lib_foo

由於這個make模板的功能很強大,我們就跟著make的執行過程分析,所以檔案會交叉:


首先是頂層makefile:

#宣告路徑和包含 

export TOP_DIR := $(realpath .) include $(TOP_DIR)/makefiles/common.mk

然後一般我們執行的都是make all,所以直接看all依賴:
# 依次執行make_in_list中的make all:@$(call make_in_list, $(make_list), all)

先看 
make_list變數:
make_list := lib_bar
make_list += lib_foo
make_list += app_test
注意主檔案鎖在的命令要放最後,因為主檔案依賴於前面兩個目錄所產生的庫 

然後看 
make_in_list函式,在common.mk檔案中:
# make -r -R -s -C lib_mcu all -j 1
#-C -- 指定路徑
#-r/R -- 禁止隱含規則
#-s -- 不顯示命令輸出
#-j -- 同時執行命令的個數
make_in_list = list="$(1)"; for p in $$list; do $(MAKE) -C $$p $(2) -j $(jobnums); done 

展開來就是:
for p in make_list; do make -c p all -j 1; done

不難理解:分別執行make_list路徑的make

我們以lib_foo命令為例說明: 
 
由於內容少,我就直接貼完了
#這裡好理解,指定命令,包含common.mk 
TOP_DIR = $(realpath ../) include $(TOP_DIR)/makefiles/common.mk ifeq (arm, $(ARCH)) else ifeq (i386, $(ARCH)) endif
#這裡指定本目錄索要關聯的Lib,注意這裡指的是lib_xxx目錄,而不是第三方lib,後面會從程式碼中說明
libs = 
#指定本目錄生成的lib名,一般和命令名一直 
lib_name = foo
#自定義的cflag引數 
defines += -DDEBUG
#
debug_ar_lib是依賴,實現在下面的rules.mk中 all:debug_ar_lib include $(TOP_DIR)/makefiles/rules.mk

好了,我們進rules.mk看看
在看實現程式碼前,我們注意到檔案開頭有
.init. : $(init_dirs);
這個是由一個include自動呼叫的:見最後:
ifneq (help, $(findstring help, $(MAKECMDGOALS))) ifneq (clean, $(findstring clean, $(MAKECMDGOALS))) -include .init. endif endif 

這個規則乾的事很簡單:
# mkdir -p -- 目錄不存在則建立 $(init_dirs): $(make_debug)$(call echo_make_info, 'mkdir', [email protected]) $(make_debug)mkdir -p [email protected]

而 
init_dirs變數則在common.mk中,我就不貼了,就是指定build資料夾來放置編譯的依賴檔案和中間檔案
echo_make_info函式也在common.mk中,用printf來列印輸出資訊 <用printf的好處是可以格式化資料>
# 列印make資訊 echo_make_info = printf " [%s] %-8s -> %-16s %s %s...\n" $(ARCH) $(1) $(lib_name) $(2) 
這裡我們注意到,格式化是實體是5個,而可變引數卻只有4個,因為當其中一個可變引數是用空格隔開的時候,會多佔一個%s,最後的'...'不知道是自動擴充套件%s, 還是裝飾,剛想到,還未驗證。

#然後看進debug_ar_lib
debug_ar_lib : $(cur_ar_lib) ;

先到commond看cur_ar_lib的定義:
# 根據makefile指定的lib_name合成lib的名字 cur_ar_lib = lib$(lib_name).$(ar_suffix)
ar_suffix ?= $(ARCH).a
其中lib_name就是子目錄設定的引數foo,這裡就直接合成了libfoo.i386.o 
繼續回rule中:
# make目錄中的c,cpp原始檔 (ar_objs在common.mk中) #echo_make_info -- 列印make資訊 ([email protected] - 源) $(cur_ar_lib) : $(ar_objs) $(make_debug)$(call echo_make_info, 'make', [email protected])# 列印make資訊 $(make_debug)$(AR) $(arflags) [email protected] $^# 建立庫 最後一行是用ar將生成的.o檔案合成.a庫檔案

o檔案怎麼生成的呢,繼續看 
$(ar_objs)
# 從build目錄中濾除主檔案main的obj物件,makefile的語法網上很多,這裡我就不詳細介紹了,baidu一下就出來了
# 因為rule是通用規則,所以主檔案Make的時候也會呼叫這個規則,而main檔案是不參加庫的合成的,所以這裡要從o檔案堆中濾除main.o檔案。注意,如果主檔案不是main.c,那麼這裡的名字也要做對應的修改 
ar_objs := $(filter-out $(build_dir)/main.$(obj_suffix),$(cur_objs))
繼續看cur_objs是怎麼定義的:
# 編譯的結果放入".build"中間目錄中 -- 含有編譯步驟(rules.mk的開頭有對應規則) cur_objs := $(addprefix $(build_dir)/, $(cur_c_sources:%.c=%.$(obj_suffix))) cur_objs += $(addprefix $(build_dir)/, $(cur_cpp_sources:%.cpp=%.$(obj_suffix)))可見,obj是從c/cpp的源中提取出來的,然後給加上build_dir字首,也就是將生成的o檔案放入build_dir中 

# build的定義:由於.開頭的檔案會隱藏,所以我去掉了.
# 這裡要注意一點,由於這個rule是從子目錄中包含進來的,所以路徑始終還是在子目錄中,所以這裡的build是在子目錄下建立的
# o檔案儲存的中間目錄 build_dir := build
# 提取c檔案 cur_c_sources := $(wildcard *.c) # 提取cpp檔案 cur_cpp_sources := $(wildcard *.cpp) 

這個時候初學者一定會問了,rule的最後是將o檔案連線為a庫檔案,cur_objs只是取出了o檔案,那麼o檔案是怎麼生成的呢?
o檔案的生成就是cur_objs := 這一行中,最後有 %.c=%.$(obj_suffix)

# c檔案編譯為o的規則 $(build_dir)/%.$(obj_suffix) : %.cpp $(make_debug)$(call echo_make_info, 'make', [email protected])# 顯示make資訊 $(make_debug)$(CPP) $(cppflags) $(defines) -c -o [email protected] $<# g++ # cpp檔案編譯為o的規則 $(build_dir)/%.$(obj_suffix) : %.c $(make_debug)$(call echo_make_info, 'make', [email protected]) $(make_debug)$(CC) $(cflags) $(defines) -c -o [email protected] $<# gcc

從這裡就可以看出,子目錄make中的defines的作用就是加上定製的cflags,順便看下預設的cflags:
defines += -DARCH_$(ARCH) # g++引數 #-std=c++0x -- 支援c++11標準 (gcc 4.3以上, 4.7以上引數修改為c++11) cppflags += -Wall -Werror -std=c++0x -g -O0 -I$(TOP_DIR) -I. $(pkg_cflags) cppflags += -I$(third_party_inc_dir)
# gcc引數 #-Wall -- 警告當錯誤處理 #-g -O0 -- 優化等級 #-I -- 指定標頭檔案地址 #  cflags += -Wall -Werror -g -O0  cflags += -Wall -g -O0 -I$(TOP_DIR) $(pkg_cflags) cflags += -I$(third_party_inc_dir)
-I指示了系統標頭檔案的查詢目錄,如果有指定定義的標頭檔案,則也需要新增到這裡
如我就做了如下修改:
-I$(top_inc_dir),
top_inc_dir := $(TOP_DIR)/includes
加入了一個公共includes,現在我也明白了linux為什麼喜歡把標頭檔案扔一起了

這裡有另外一個引數:pkg_cflags,理解起來有點複雜:

這裡通過子目錄傳遞下來的lib_name和libs的設定給新增同的引數
如果定義了lib_name,則說明該子目錄自己會編譯成一個庫檔案,所以PKG的路徑就設定為當前路徑
# test -z -- 判斷lib_name是否為空 pkg_cflags += $(shell test -z $(lib_name) || (export PKG_CONFIG_PATH=.;pkg-config \  --define-variable=prefix=$(TOP_DIR)/$(dir_name) \  --define-variable=ARCH=$(ARCH) --cflags $(lib_name)))
#這裡是lib的引數,指定了PKG搜尋lib的路徑,由於是本身,所以意義不大 
pkg_libs += $(shell test -z $(lib_name) || (export PKG_CONFIG_PATH=.;pkg-config \  --define-variable=prefix=$(TOP_DIR)/$(dir_name) \  --define-variable=ARCH=$(ARCH) --libs $(lib_name))) # 搜尋libs庫
# 當子目錄有依賴其他庫的時候,會設定libs,所以這裡就是一個迴圈,依次加入lib庫的搜尋路徑 
pkg_cflags += $(shell list='$(libs)'; for p in $$list; do \  export PKG_CONFIG_PATH=$(TOP_DIR)/lib_$$p;pkg-config \  --define-variable=prefix=$(TOP_DIR)/lib_$$p \  --define-variable=ARCH=$(ARCH) --cflags $$p;done)
# 這裡將lib的make引數傳入 
pkg_libs += $(shell list='$(libs)'; for p in $$list; do \  export PKG_CONFIG_PATH=$(TOP_DIR)/lib_$$p;pkg-config \  --define-variable=prefix=$(TOP_DIR)/lib_$$p \   --define-variable=ARCH=$(ARCH) --libs $$p;done) 關於pkgconfig,自己baidu下吧,我的理解也不是很深,和pkg配合的還有一個pc檔案,我也只是仿造已有的進行修改的

最後這裡是將lib的標記加入make引數中,注意庫的連線是在建立依賴檔案d檔案的時候進行的
# lib標記,建立依賴檔案時使用 libflags += -L$(third_party_lib_dir)  libflags += $(pkg_libs)

這裡有一個檔案,libflags雖然給出了第三方庫的路徑,但是如果我程式中要使用第三方庫,則目前看來是需要將引數寫進libs中,但從上面很明顯的看出寫入libs會給make加入多餘的引數,所以我給libflags加了擴充套件:
libflags += $(third_party_lib)

然後third_party_lib由子目錄的make傳入就可以了

剛才大家應該都發現了,在gcc編譯的時候,只用了cflags引數,但是沒有連線lib,其實lib的連線是自動進行的,在rule檔案中用include進行了自動依賴:

# 自動建立依賴檔案 auto_deps = 0 ifeq (,$(MAKECMDGOALS)) auto_deps = 1 else ifeq (all, $(findstring all, $(MAKECMDGOALS))) auto_deps = 1 endif ifeq (1, $(auto_deps)) -include $(cur_deps) endif

同樣看cur_deps:
# 依賴檔案(rules.mk中用include自動呼叫依賴物件的編譯) cur_deps := $(addprefix $(build_dir)/, $(cur_c_sources:%.c=%.$(dep_suffix))) cur_deps += $(addprefix $(build_dir)/, $(cur_cpp_sources:%.cpp=%.$(dep_suffix)))

和上面的gcc一樣吧,然後看執行:
# 依賴檔案建立規則 $(build_dir)/%.$(dep_suffix) : %.cpp $(make_debug)$(call echo_make_info, make, [email protected]) $(make_debug)set -e; rm -f [email protected]; \ $(CPP) -MM $(cppflags) $(defines) $(libflags) $< > [email protected]$$$$; \ sed 's,\($*\)\.o[ :]*,$(build_dir)/\1.$(obj_suffix) [email protected] : ,g' < [email protected]$$$$ > [email protected]; \ rm -f [email protected]$$$$ $(build_dir)/%.$(dep_suffix) : %.c $(make_debug)$(call echo_make_info, make, [email protected]) $(make_debug)set -e; rm -f [email protected]; \ $(CC) -MM $(cflags) $(defines) $(libflags) $< > [email protected]$$$$; \ sed 's,\($*\)\.o[ :]*,$(build_dir)/\1.$(obj_suffix) [email protected] : ,g' < [email protected]$$$$ > [email protected]; \ rm -f [email protected]$$$$ 

關於set的用法,baidu吧,要解釋起來又是一堆文字了
實際上,很多地方的寫法都是固定的,這裡這是將Make流程解釋一下,以便知道如果需要修改,則該改哪裡,一些過於繞口的語言,作為初學者來說,可以暫時放過,等以後有時間了再來消化。

=============================================================

以上,就是
$(cur_ar_lib) : $(ar_objs)
的執行過程,
最後:
$(make_debug)$(AR) $(arflags) [email protected] $^# 建立庫好了,子目錄的執行過程就分析完了,然後我們看看主檔案的makefile有什麼不同:
#TOPDIR = ../ include $(TOP_DIR)/makefiles/common.mk ifeq (arm, $(ARCH)) else ifeq (i386, $(ARCH)) endif lib_name = app_test libs += bar foo main_bin := test_app.$(ARCH).elf defines += -DDEBUG #all:$(main_bin) example install all:$(main_bin) #$(main_bin): main.cpp $(cur_ar_lib)  $(main_bin): main.cpp $(cur_ar_lib) $(make_debug)$(call echo_make_info, 'make', [email protected]) $(make_debug)$(CPP) $(cppflags) -o [email protected] $^ $(libflags)  .PHONY: $(main_bin) include $(TOP_DIR)/makefiles/rules.mk

幾乎一樣,就是libs中指定了主檔案所需要的庫,libs的處理剛才我們再講cflags的引數的時候也一起提過了,主要是給pkg用的,然後main_bin指定了生成的應用程式的名字

這裡要注意的是,all建立主庫的依賴直接用了 
$(cur_ar_lib),而不是debug_ar_lib

相關推薦

自己除錯通過一個通用makefile模板

這個是從本人的QQ空間轉過來的 模板資料夾的下載地址: 這個模板是之前公司的一個牛人寫的,我這個連門都沒入的菜鳥因為沒有專案需求,所以一直沒有花時間去研究。可惜好景不長,醬油沒打幾天,就需要我來單挑linux了,網上找了很多模板,都不盡如人意,沒辦法,只好硬著頭皮來啃

專案管理級別的自動萬能通用makefile模板:t-makefile (freetoo)

  專案管理級別的自動萬能通用makefile模板:t-makefile (freetoo)   t-makefile原始碼及示例專案下載連結(會不定期更新): https://github.com/freetoo/t-makefile  

t-makefile:自動萬能通用makefile模板(freetoo 碼客 盧益貴)

t-makefile:自動萬能通用makefile模板(freetoo 碼客 盧益貴)   關鍵字: make makefile shell find grep wildcard notdir patsubst findstring wo

分享一個通用makefile

適合寫一些小程式的時候使用,方便快捷 c語言 c++ 混編亦可 CROSSCOMPILER := EXE := test MKDIR := mkdir RM := rm -rf CC := $(CROSSCOMPILER)gcc CFLAG := CXX :=

自己除錯通過的libsvm測試程式碼

     [heart_scale_label,heart_scale_inst]= libsvmread('heart_scale'); >> model=svmtrain(heart_scale_label,heart_scale_inst) [predicted_label, accurac

【linux】-Makefile簡要知識+一個通用Makefile

[toc] --- ## Makefile  meke命令一般用於編譯程式,而make命令都依賴於 Makefile 檔案。  最簡單的Makefile如下: ```Makefile hello: hello.c gcc -o hello hello.c clean:

一個通用makefile

client nbsp -name ons common func exec been cal DBUG=FALSESRC_GENERIC=mysqlParam.cpp mysqlStmt.cpp mysqlReqst.cpp mysqlPoolMgr.cpp UnitTe

自己動手編寫一個Linux偵錯程式系列之4 ELF檔案格式與DWARF除錯格式

目錄 在上一節中,你已經聽說了DWARF除錯格式,它是程式的除錯資訊,是一種可以更好理解原始碼的方式,而不只是解析程式。今天我們將討論原始碼級除錯資訊的細節,以準備在本教程後面的部分中使用它。 系列索引 準備工作 斷點的設定 暫存器和記憶體 ELF檔案格式

從0開始寫一個通用Makefile檔案

     什麼是Makefile檔案       一個工程中的原始檔不計其數,其按型別、功能、模組分別放在若干個目錄中,makefile定義了一系列的規則來指定,哪些檔案需要先編譯,哪些檔案需要後編譯,哪些檔案需要重新編譯,

POI實現一個通用的Excel讀取模板

    POI是Apache基金會的提供的java實現的一套用於讀取Excel、Word、PPT等文件的庫,在實際專案中可能 很多地方都會用到Excel的讀取,比如Excel的匯入,我們不可能每個地方都單獨實現一套Excel的讀取方法,這時候就需要封裝一個通用的類,只要有讀取

一個通用Makefile(轉載)

轉載於 : http://www.cnblogs.com/lidabo/p/4521123.html 一 makefile的作用     Makefile是用於自動編譯和連結的,一個工程有很多檔案組成,每一個檔案的改變都會導致工程的重新連結,但是不是所有的檔案都需要重新編

向大家推薦一個C/C++通用Makefile

本文推薦了一個用於對 C/C++ 程式進行編譯和連線以產生可執行程式的通用 Makefile。 在使用 Makefile 之前,只需對它進行一些簡單的設定即可;而且一經設定,即使以後對源程式檔案有所增減一般也不再需要改動 Makefile。因此,即便是一個沒有學習過 Makefile 書寫規則的人,也可以

一個自己寫的矩陣快速冪模板

/* n階方形矩陣快速冪模板,如果m*n的矩陣可以考慮將m,n存入結構體matrix中 函式分為3個,分別是矩陣相乘,轉化單位矩陣,快速冪。 測試結果 AC BY SHU_ON

通過泛型寫一個通用的單例

寫一個通用的單例,並且進行資料初始化以及單例的釋放 public class TSingleton<T> where T : new() { static T m_instanc

[linux]一個通用驅動Makefile-V2-支援編譯多目錄

[toc] --- ## 前言 該 Makefile 已經通過基於核心 **Linux5.4** 版本驗證通過。 因為編寫這通用驅動 Makefile 時遇到了標頭檔案指定路徑失敗的問題。使用過 **ccflags-y 、INCDIR 、EXTRA_CFLAGS 、-L** 等等引數都無效。就是因為我使

android:如何通過自定義工程模板讓新建的工程都默認支持lambda表達式

wan tro idt ref height 代碼 spa span oid 首先參考這篇文章:自定義Android Studio工程模板,了解如何自定義模板然後結合我們上一篇文章 android: 在android studio中使用retrolambda的步驟的要點,修

自己動手寫一個自動登錄腳本gg

簡單 只需要 自己 不同 enum -s class rep 使用 1.下載一個sshpass工具 2.安裝sshpass,安裝到tools文件夾 3.把tools文件夾的路徑加入到/etc/bashrc vim /etc/bashrc

自己打造了一個簡單的站長工具

spl logs images fan net ref 分享 style image 自己打造了一個簡單的站長工具   站長工具:www.fanguzai.net/ 自己打造了一個簡單的站長工具

mktime很慢就自己去實現一個

tdi ati 十分 ace += timestamp src clas [] mktime很慢就自己去實現一個吧

EF架構~終於自己架構了一個相對完整的EF方案

context src config .cn 存儲 cte 分享 insert 變化 EF4.1學了有段時間了,沒有靜下來好好研究它的架構,今天有空正好把它的架構及數據操作這段拿出來,希望給大家帶來幫助,對我自己也是一種總結:P 從圖中可以看到,我們用的是MVC3進行程