(五):嵌套執行
在大一些的項目裡面,所有源代碼不會只放在同一個目錄,一般各個功能模塊的源代碼都是分開的,各自放在各自目錄下,並且頭文件和.c源文件也會有各自的目錄,這樣便於項目代碼的維護。這樣我們可以在每個功能模塊目錄下都寫一個Makefile,各自Makefile處理各自功能的編譯鏈接工作,這樣我們就不必把所有功能的編譯鏈接都放在同一個Makefile裡面,這可使得我們的Makefile變得更加簡潔,並且編譯的時候可選擇編譯哪一個模塊,這對分塊編譯有很大的好處。
現在我所處於工程目錄樹如下:
├── include
│ ├── common.h
│ ├── ipc
│ │ └── ipc.h
│ └── tools
│ ├── base64.h
│ ├── md5.h
│ └── tools.h
├── Makefile
├── src
│ ├── ipc
│ │ ├── inc
│ │ ├── Makefile
│ │ └── src
│ │ └── ipc.c
│ ├── main
│ │ ├── inc
│ │ ├── Makefile
│ │ └── src
│ │ ├── main.c
│ │ └── main.c~
│ └── tools
│ ├── inc
│ ├── Makefile
│ └── src
│ ├── base64.c
│ ├── md5.c
│ └── tools.c
└── tags
13 directories, 16 files
這樣組織項目源碼要比之前合理一些,那這樣怎麼來寫Makefile呢?我們可以在每個目錄下寫一個Makefile,通過最頂層的Makefile一層一層的向下嵌套執行各層Makefile。那麼我們最頂層的Makefile簡單點的話可以這樣寫:
# top Makefile for xxx
all :
>---$(MAKE) -C src
tags:
>---ctags -R
clean :
>---$(MAKE) -C src clean
.PHONY : all clean tags
命令:
>---$(MAKE) -C src
就是進入src目錄繼續執行該目錄下的Makefile。然後src目錄下的Makefile在使用同樣的方法進入下一級目錄tools、main、ipc,再執行該目錄下的Makefile。其實這樣有些麻煩,我們可以直接從頂層目錄進入最後的目錄執行make。再加入一些偽目標完善下,我們的頂層Makefile就出來了:
# Top Makefile for C program
# Copyright (C) 2014 shallnew \at 163 \dot com
all :
>---$(MAKE) -C src/ipc
>---$(MAKE) -C src/tools
>---$(MAKE) -C src/main
tags:
>---ctags -R
help:
>---@echo "===============A common Makefilefor c programs=============="
>---@echo "Copyright (C) 2014 liuy0711 \at 163\dot com"
>---@echo "The following targets aresupport:"
>---@echo
>---@echo " all - (==make) compile and link"
>---@echo " obj - just compile, withoutlink"
>---@echo " clean - clean target"
>---@echo " distclean - clean target and otherinformation"
>---@echo " tags - create ctags for vimeditor"
>---@echo " help - print help information"
>---@echo
>---@echo "To make a target, do 'make[target]'"
>---@echo "========================= Version2.0 ======================="
obj:
>---$(MAKE) -C src/ipc obj
>---$(MAKE) -C src/tools obj
>---$(MAKE) -C src/main obj
clean :
>---$(MAKE) -C src/ipc clean
>---$(MAKE) -C src/tools clean
>---$(MAKE) -C src/main clean
distclean:
>---$(MAKE) -C src/ipc distclean
>---$(MAKE) -C src/tools distclean
>---$(MAKE) -C src/main distclean
.PHONY : all clean distclean tags help
當我們這樣組織源代碼時,最下面層次的Makefile怎麼寫呢?肯定不可以將我們上一節的Makefile(version 1.1)直接拷貝到功能模塊目錄下,需要稍作修改。不能所有的模塊都最終生成各自的可執行文件吧,我們目前是一個工程,所以最後只會生成一個可執行程序。我們這樣做,讓主模塊目錄生成可執行文件,其他模塊目錄生成靜態庫文件,主模塊鏈接時要用其他模塊編譯產生的庫文件來生成最終的程序。將上一節Makefile稍作修改得出編譯庫文件Makefile和編譯可執行文件Makefile分別如下:
# A Makefile to generate archive file
# Copyright (C) 2014 shallnew \at 163 \dot com
CFLAGS += -g -Wall -Werror -O2
CPPFLAGS += -I. -I./inc -I../../include
# SRC_OBJ = $(patsubst %.c, %.o, $(wildcard *.c))
SRC_FILES = $(wildcard src/*.c)
SRC_OBJ = $(SRC_FILES:.c=.o)
SRC_LIB = libtools.a
all : $(SRC_LIB)
$(SRC_LIB) : $(SRC_OBJ)
>---$(AR) rcs $@ $^
>---cp $@ ../../libs
obj : $(SRC_OBJ)
# clean target
clean:
>---$(RM) $(SRC_OBJ) $(SRC_LIB)
distclean:
>---$(RM) $(SRC_OBJ) $(SRC_LIB) tags *~
.PHONY : all obj clean disclean
# A Makefile to generate executive file
# Copyright (C) 2014 shallnew \at 163 \dot com
CFLAGS += -g -Wall -Werror -O2
CPPFLAGS += -I. -I./inc -I../../include
LDFLAGS += -lpthread -L../../libs -ltools -lipc
# SRC_OBJ = $(patsubst %.c, %.o, $(wildcard *.c))
SRC_FILES = $(wildcard src/*.c)
SRC_OBJ = $(SRC_FILES:.c=.o)
SRC_BIN = target_bin
all : $(SRC_BIN)
$(SRC_BIN) : $(SRC_OBJ)
>---$(CC) -o $@ $^ $(LDFLAGS)
obj : $(SRC_OBJ)
# clean target
clean:
>---$(RM) $(SRC_OBJ) $(SRC_BIN) $(SRC_BIN).exe
distclean:
>---$(RM) $(SRC_OBJ) $(SRC_BIN) $(SRC_BIN).exe tags*~
.PHONY : all obj clean disclean
最後在頂層執行:
# make clean
make -C src/ipc clean
make[1]: Entering directory`/home/Myprojects/example_make/version-3.0/src/ipc'
rm -f src/ipc.o libipc.a
make[1]: Leaving directory`/home/Myprojects/example_make/version-3.0/src/ipc'
make -C src/tools clean
make[1]: Entering directory `/home/Myprojects/example_make/version-3.0/src/tools'
rm -f src/base64.o src/md5.o src/tools.o libtools.a
make[1]: Leaving directory`/home/Myprojects/example_make/version-3.0/src/tools'
make -C src/main clean
make[1]: Entering directory`/home/Myprojects/example_make/version-3.0/src/main'
rm -f src/main.o target_bin target_bin.exe
make[1]: Leaving directory`/home/Myprojects/example_make/version-3.0/src/main'
# make
make -C src/ipc
make[1]: Entering directory`/home/Myprojects/example_make/version-3.0/src/ipc'
cc -g -Wall -Werror -O2 -I. -I./inc-I../../include -c -o src/ipc.osrc/ipc.c
ar rcs libipc.a src/ipc.o
cp libipc.a ../../libs
make[1]: Leaving directory `/home/Myprojects/example_make/version-3.0/src/ipc'
make -C src/tools
make[1]: Entering directory`/home/Myprojects/example_make/version-3.0/src/tools'
cc -g -Wall -Werror -O2 -I. -I./inc-I../../include -c -o src/base64.osrc/base64.c
cc -g -Wall -Werror -O2 -I. -I./inc -I../../include -c -o src/md5.o src/md5.c
cc -g -Wall -Werror -O2 -I. -I./inc-I../../include -c -o src/tools.osrc/tools.c
ar rcs libtools.a src/base64.o src/md5.o src/tools.o
cp libtools.a ../../libs
make[1]: Leaving directory`/home/Myprojects/example_make/version-3.0/src/tools'
make -C src/main
make[1]: Entering directory`/home/Myprojects/example_make/version-3.0/src/main'
cc -g -Wall -Werror -O2 -I. -I./inc-I../../include -c -o src/main.osrc/main.c
cc -o target_bin src/main.o -lpthread -L../../libs -ltools-lipc
make[1]: Leaving directory`/home/Myprojects/example_make/version-3.0/src/main'
#
最後生成了可執行程序文件。這樣的話一個工程的各個模塊就變得獨立出來了,不但源碼分開了,而且各自有各自的Makefile,並且各個功能模塊是可獨立編譯的。
我們發現頂層Makefile還有可以改進的地方,就是在進入下一層目錄是要重複寫多次,如下:
>---$(MAKE) -C src/ipc
>---$(MAKE) -C src/tools
>---$(MAKE) -C src/main
每增加一個目錄都要在多個偽目標裡面加入一行,這樣不夠自動化啊,於是我們想到shell的循環語 句,我們可以在每條規則的命令處使用for循環。如下:
DIR = src
SUBDIRS = $(shell ls $(DIR))
all :
@for subdir in $(SUBDIRS); \
do $(MAKE) -C $(DIR)/$$subdir; \
done
這樣懶人有可以高興很久了。不過還有問題:
上面for循環會依次進入系統命令ls列出的目錄,但我們對每個目錄的make順序可能有要求,在該項目當中,main目錄下的Makefile必須最後執行,因為最終的鏈接需要其他目錄編譯生成的庫文件,否則會執行失敗。並且在當前的Makefile中,當子目錄執行make出現錯誤時,make不會退出。在最終執行失敗的情況下,我們很難根據錯誤的提示定位出具體是是那個目錄下的Makefile出現錯誤。這給問題定位造成了很大的困難。為了避免這樣的問題,在命令執行錯誤後make退出。
所以將剛才的Makefile修改為如下
DIR = src
SUBDIRS = $(shell ls $(DIR))
all :
>---@for subdir in $(SUBDIRS); \
>---do $(MAKE) -C $(DIR)/$$subdir || exit 1; \
>---done
這樣在執行出錯時立馬退出,但這樣還是沒有解決問題,編譯錯誤還是會出現。那怎麼解決呢?
我們可以通過增加規則來限制make執行順序,這樣就要用到偽目標,對每一個模塊我們都為他寫一條規則,每個模塊名稱是目標,最後需要執行的模塊目標又是其他模塊的目標,這樣就限制了make順序。在執行到最後需要執行的目標時,發現存在依賴,於是先更新依賴的目標,這樣就不會出錯了。並且這樣的話,我們還可以對指定模塊進行編譯,比如我只修改了tools模塊,我只想看看我修改的這個模塊代碼是否可以編譯通過,我可以在編譯時這樣:
# make tools
make -C src/tools
make[1]: Entering directory`/home/Myprojects/example_make/version-2.1/src/tools'
cc -g -Wall -Werror -O2 -I. -I./inc-I../../include -c -o src/base64.o src/base64.c
cc -g -Wall -Werror -O2 -I. -I./inc-I../../include -c -o src/md5.osrc/md5.c
cc -g -Wall -Werror -O2 -I. -I./inc-I../../include -c -o src/tools.osrc/tools.c
ar rcs libtools.a src/base64.o src/md5.o src/tools.o
cp libtools.a ../../libs
make[1]: Leaving directory`/home/Myprojects/example_make/version-2.1/src/tools'
還有另外一種方法也可以解決此問題,就是手動列出需要進入執行的模塊名稱(這裡就是目錄了),把最後需要執行的模塊放在最後,這樣for循環執行時最後需要編譯鏈接的模塊就放在最後了,不會像我們之前那樣make是按照使用系統命令ls列出模塊目錄的順序來執行。ls列出目錄是按照每個目錄的名稱來排序的,我們總不能要求寫代碼的時候最後執行的模塊的名稱必須是以z開頭的吧,總之不現實。
我們的頂層Makefile又進化了,也是這一節最終Makefile:
# Top Makefile for C program
# Copyright (C) 2014 shallnew \at 163 \dot com
DIR = src
MODULES = $(shell ls $(DIR))
# MODULES = ipc main tools
all : $(MODULES)
$(MODULES):
>---$(MAKE) -C $(DIR)/$@
main:tools ipc
obj:
>---@for subdir in $(MODULES); \
>---do $(MAKE) -C $(DIR)/$$subdir $@; \
>---done
clean :
>---@for subdir in $(MODULES); \
>---do $(MAKE) -C $(DIR)/$$subdir $@; \
>---done
distclean:
>---@for subdir in $(MODULES); \
>---do $(MAKE) -C $(DIR)/$$subdir $@; \
>---done
tags:
>---ctags -R
help:
>---@echo "===============A common Makefilefor c programs=============="
>---@echo "Copyright (C) 2014 liuy0711 \at 163\dot com"
>---@echo "The following targets aresupport:"
>---@echo
>---@echo " all - (==make) compile and link"
>---@echo " obj - just compile, withoutlink"
>---@echo " clean - clean target"
>---@echo " distclean - clean target and otherinformation"
>---@echo " tags - create ctags for vimeditor"
>---@echo " help - print help information"
>---@echo
>---@echo "To make a target, do 'make[target]'"
>---@echo "========================= Version2.0 ======================="
.PHONY : all clean distclean tags help