实用make最佳实践
一、前言
Make工具最主要也是最基本的功能就是通過makefile文件來描述源程序之間的相互關系并自動維護編譯工作。而makefile 文件需要按照某種語法進行編寫,文件中需要說明如何編譯各個源文件并連接生成可執行文件,并要求定義源文件之間的依賴關系。
然而make的命令"博客精深",對于初學者來說,真是望而生畏,這篇文章不是make詳解,只是講解實用makefile的編寫和使用。
在linux上,如果用gcc一個個編譯源碼,實在很繁瑣,尤其是隨著源代碼的增加,這種繁瑣更是明顯,很多人,包括我,其實要求很簡單,只要把頭文件和cpp或者c文件放在當前目錄或者其他目錄,直接一個命令就可以編譯了,但是make的命令太多了,相當打擊初學者的信心。
常用的make指令其實不多,夠用就行了,接下來開始講解實用makefile的編寫,看完之后大家可以直接下載make工程直接一個make命令完成編譯。
本文相關環境:redhat 5.5 +?g++ version 4.1.2 +?GNU Make 3.81
g++可以支持cpp文件編譯,gcc編譯cpp文件在鏈接時會出現點問題,所以這里統一使用g++。
?
二、Makefile的規則
target ... : prerequisites ...command...... comman如果和target不是同一行,需要在第二行鍵入\t再鍵入command.
target也就是一個目標文件,可以是Object File,也可以是執行文件。還可以是一個標簽(Label)
這是一個文件的依賴關系,也就是說,target這一個或多個的目標文件依賴于prerequisites中的文件,其生成規則定義在command中。
prerequisites中如果有一個以上的文件比target文件要新的話,command所定義的命令就會被執行。這就是Makefile的規則。也就是Makefile中最核心的內容。
?
三、使用make一個個編譯源代碼
?假設我們寫一個簡單的程序,源碼位于/usr/make/main.cpp,代碼如下:
//main.cpp #include <stdio.h> int main(int argc, char** argv) {printf("app startup\n");printf("app stop\n");return 0; }make的最大好處是自動化編譯,于是我們新建/usr/make/makefile,makefile文件內容如下:
main: main.og++ main.o -o main main.o: main.cppg++ -c main.cpp -o main.o clean:rm -rf *.o main保存之后,直接執行make,就可以生成main.o目標文件,main可執行文件了。
然而我們這個程序實在單薄了點,于是我們需要加入一個App類,控制整個程序的啟動和關閉周期。
新建/usr/make/app.h文件,代碼如下:
#ifndef APP_H #define APP_H class App{public:static App& getInstance();bool start();bool shutdown();private:App();App(const App&);App& operator=(const App&);bool m_stopped; };#endif
新建/usr/make/app.cpp文件,代碼如下:
#include "app.h"#include <stdio.h> #include <unistd.h> App& App::getInstance() {static App app;return app; }App::App() {m_stopped = false; }bool App::start() {printf("app startup\n");while (!m_stopped) {printf("app run\n");sleep(5);}return true; }bool App::shutdown() {if (m_stopped == false) {m_stopped = true; }return true; }
修改/usr/make/main.cpp文件,代碼如下:
//main.cpp #include <stdio.h>#include "app.h"int main(int argc, char** argv) {App& app = App::getInstance();if(!app.start()) {printf("app start fail\n");}app.shutdown();return 0; }修改/usr/make/makefile,內容如下:
main: main.o app.og++ main.o app.o -o main main.o: main.cppg++ -c main.cpp -o main.o app.o: app.cppg++ -c app.cpp -o app.o clean:rm -rf *.o main
執行make,./main,發現后臺會每隔5秒打印出"app run",只能ctrl+c結束.
以后我們修改了main.cpp,app.cpp,app.h文件后,我們只要輸入make,就可以自動編譯了,不必每次一個個g++命令去進行繁雜的編譯過程。
這種方式,以后每添加一個源碼文件,我們就要手工修改makefile的內容,添加依賴和命令,仍然顯得不自動。
makeprojectv1下載
?
四、使用make自動推導編譯
?GNU的make很強大,它可以自動推導文件以及文件依賴關系后面的命令,于是我們就沒必要去在每一個[.o]文件后都寫上類似的命令,因為,我們的make會自動識別,并自己推導命令。
?我們的是新的makefile又出爐了:
CPP_SOURCES = $(wildcard *.cpp) CPP_OBJS = $(patsubst %.cpp, %.o, $(CPP_SOURCES))default:compile$(CPP_OBJS):%.o:%.cppg++ -c $< -o $@compile: $(CPP_OBJS)g++ $^ -o mainclean:rm -f $(CPP_OBJS)rm -f main 下面來一步步解析這個makefie的語法. 初始化: wildcard函數功能是展開成一列所有符合由其參數描述的文件名,文件間以空格間隔,本例是產生一個所有以 '.cpp' 結尾的文件的列表,然后存入變量 CPP_SOURCES. patsubst函數是匹配替換的函數,有三個參數,第一個是一個需要匹配的式樣,第二個表示用什么來替換它,第三個是一個需要被處理的由空格分隔的字列,本例是把 CPP_SOURCES的后綴為cpp文件列表,替換成后綴為o的文件列表。 生成目標文件 $(CPP_OBJS):%.o:%.cppg++ -c $< -o $@ 上面的例子中,指明了我們的目標從$(CPP_OBJS)中獲取,"%.o"表明要所有以".o"結尾的目標,也就是"main.o app.o",也就是變量$(CPP_OBJS)集合的模式.
而依賴模式"%.cpp"則取模式"%.o"的"%",也就是"main app",再加上后綴"cpp".于是,我們的依賴目標就是"main.cpp app.cpp"。
而命令中的"$<"和"$@"則是自動化變量,"$<"表示所有的依賴目標集(也就是"main.cpp app.cpp"),"$@"表示目標集(也就是"main.o cpp.o")。
展開之后是:
main.o: main.cppg++ -c main.cpp -o main.o main.o: app.cppg++ -c app.cpp -o app.o鏈接
上述的default目標是是makefile的第一個目標,依賴compile,compile文件不存在,也不會出現,所以會執行compile目標。
default和compile只是makefile一個普通的規則而已,沒有特殊之處。
compile: $(CPP_OBJS)g++ $^ -o main"$<"表示所有的依賴目標集,表示main.o app.o
展開之后是
compile: main.o app.og++ main.o app.o -o main
這種方式,當我們在當前目錄添加cpp文件,不用再改makefile,只要鍵入命令make就可以完成整個編譯過程,這個時候我們可以感受到make的自動化編譯的方便。
makeprojectv2下載
?
五、實用makefile的完善
雖然我們已經使用了makefile的自動推導,但是這個makefile還有有些不足:
1.只能支持cpp文件,不支持c文件
2.生成的目標文件全放在當前目錄,造成當前目錄混亂
3.h文件和cpp文件或者c文件沒有分離,造成目錄里的源碼文件2倍爆炸,而且也不支持多目錄
4.不支持第三方的庫文件,包括include文件和lib文件。
于是,有一個完善的makefile如下:
TARGET = main OBJ_PATH = objsCC = g++ CFLAGS = -Wall -Werror -g LINKFLAGS =#INCLUDES = -I include/myinclude -I include/otherinclude1 -I include/otherinclude2 INCLUDES = -I include #SRCDIR =src/mysrcdir src/othersrc1 src/othersrc2 SRCDIR = src #LIBS = -Llib -lcurl -Llib -lmysqlclient -Llib -llog4cpp LIBS =C_SRCDIR = $(SRCDIR) C_SOURCES = $(foreach d,$(C_SRCDIR),$(wildcard $(d)/*.c) ) C_OBJS = $(patsubst %.c, $(OBJ_PATH)/%.o, $(C_SOURCES))CPP_SRCDIR = $(SRCDIR) CPP_SOURCES = $(foreach d,$(CPP_SRCDIR),$(wildcard $(d)/*.cpp) ) CPP_OBJS = $(patsubst %.cpp, $(OBJ_PATH)/%.o, $(CPP_SOURCES))default:init compile$(C_OBJS):$(OBJ_PATH)/%.o:%.c$(CC) -c $(CFLAGS) $(INCLUDES) $< -o $@$(CPP_OBJS):$(OBJ_PATH)/%.o:%.cpp$(CC) -c $(CFLAGS) $(INCLUDES) $< -o $@init:$(foreach d,$(SRCDIR), mkdir -p $(OBJ_PATH)/$(d);)compile:$(C_OBJS) $(CPP_OBJS)$(CC) $^ -o $(TARGET) $(LINKFLAGS) $(LIBS)clean:rm -rf $(OBJ_PATH)rm -f $(TARGET)install: $(TARGET)cp $(TARGET) $(PREFIX_BIN)uninstall:rm -f $(PREFIX_BIN)/$(TARGET)rebuild: clean compile
?這個makefile的功能是掃描src文件夾的c文件和cpp文件,進行編譯,編譯的命令是g++,編譯選項是-Wall -Werror -g,表示盡可能打印出報錯信息,有錯誤和警告就停止編譯,并且生成調試信息。如果是生產環境,請把-g去掉。最后連接所有目標文件為main。如果所有的c文件和cpp文件沒有main函數入口,鏈接會報錯。
使用這個makefile,需要注意的幾個變量是:
TARGET 表示最后生成的可執行文件的名字,默認生成的main可執行文件在當前工作目錄。 OBJ_PATH 表示編譯過程中產生的目標文件放在那個目錄,默認生成放在在當前目錄的objs目錄下,無需手工mkdir,自動生成。 CC 默認是g++,如果是使用gcc的,請修改,但是gcc在編譯cpp文件后,鏈接時會出現問題,請自行解決。 INCLUDES 配置頭文件的目錄,默認是include目錄,如果放在當前目錄,可以改為"INCLUDES = .",如果要配置多個include目錄,可以按照注釋,自行配置。 SRCDIR 配置c文件和cpp文件的目錄,默認是src目錄,如果喜歡放在當前目錄,可以改為"SRCDIR= .",如果要配置多個源碼目錄,可以按照注釋,自行配置。 LIBS 配置第三方的動態鏈接庫和靜態鏈接庫,當程序使用了第三方庫時,我們編譯是要配置第三方的頭文件目錄,鏈接時就要配置鏈接的庫文件,LIBS配置可以參考注釋,如果不清楚,請看本人博客里的“gcc常用命令”文章? makeprojectv3下載
總結
以上是生活随笔為你收集整理的实用make最佳实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux GDB详解
- 下一篇: GDB多进程调试(转)