Easymake 使用說明
介紹
Easymake 是一個在linux系統中 C/C++ 開發的通用 makefile。在一個簡單的 C/C++ 程序中使用 easymake,你甚至可以不寫一行 makefile 代碼來生成目標文件。
Easymake 包含以下功能:
- 自動掃描 C/C++ 源文件。
- 自動生成和維護依賴關系,加快編譯時間。
- 支持簡單的單元測試,可以很方便地管理多個程序入口(main 函數)。
- 完美地支持
VPATH
變量。
我將在后面的例子中一步步地向你展示如何使用 easymake 來構建你的應用程序。別看文檔這么長,下面一節的內容中大部分是在講一個簡單的 C/C++ 程序的開發。就像 easymake 的名字一樣,easymake 是非常易學易用的。
開始學習 Easymake
在這一節中將展示如何在一個簡單的程序中使用 easymake。接下來讓我們一個加法程序,用戶輸入兩個數字,然后程序輸出這兩個數字相加的結果。這個程序的源代碼可以在 samples/basics
目錄中找到。
C/C++ 代碼
這個程序很簡單,所以這里跳過程序設計環節。這里直接展示程序的 C/C++ 代碼,然后再轉入我們的正題。
File: main.cpp
#include <iostream>
#include "math/add.h"
using namespace std;
int main(){
cout<<"please enter two integer:"<<endl;
int a,b;
cin>>a>>b;
cout<<"add("<<a<<","<<b<<") returns "<<add(a,b)<<endl;
}
File: math/add.h
#ifndef ADD_H
#define ADD_H
int add(int,int);
#endif
File: math/add.cpp
#include "math/add.h"
int add(int a,int b){
return a+b;
}
使用 Easymake 來構建程序
代碼很簡單,可以直接使用命令行來構建程序。如果你對 makefile 的語法熟悉,你也可以很快地寫出一個 makefile 來做完成這個事情。那么如何使用 easymake 來構建這個程序呢?先別急,接下來將使用剛才提到的三種方法來構建程序,希望你能清晰地了解和比較這三種方式。
使用命令行構建
g++ -c -o main.o main.cpp
g++ -c -o add.o math/add.cpp -I.
g++ -o target main.o add.o
或者也可以只用一條命令 g++ -o target main.cpp math/add.cpp -I.
來構建程序。
然后輸入 ls
和 ./target
,就可以觀察到程序的執行結果了:
[root@VM_6_207_centos basics]# ls
add.o bin main.cpp main.o makefile math target
[root@VM_6_207_centos basics]# ./target
please enter two integer:
5
3
add(5,3) returns 8
自己寫一個 makefile 構建程序
創建一個新的 Makefile 文件,代碼如下:
target: main.o add.o
g++ -o target main.o add.o
main.o: main.cpp
g++ -c -o main.o main.cpp -I.
add.o: math/add.cpp
g++ -c -o add.o math/add.cpp -I.
結果基本是一樣的:
[root@VM_6_207_centos basics]# make
g++ -c -o main.o main.cpp -I.
g++ -c -o add.o math/add.cpp -I.
g++ -o target main.o add.o
[root@VM_6_207_centos basics]# ls
add.o main.cpp main.o makefile math target
[root@VM_6_207_centos basics]# ./target
please enter two integer:
8
9
add(8,9) returns 17
使用 makefile 的好處就是,如果能很好地確定依賴關系,那么就不需要在每次構建時把所有的源文件都重新編譯一次。但是隨着項目的代碼的增長,即使在一個良好的模塊化設計中,手工維護依賴關系也是一件很繁瑣而且很容易出錯的工作。例如,假設我們需要增加一個 multiply.cpp
和 multiply.h
文件,讓程序支持乘法計算的功能,那么我必須修改我們的 makefile 才能構建新的程序。另外,如果頭文件 add.h
被修改了,multiply.cpp
就不需要重新編譯,所以我們應該在 makefile 中增加 .cpp 文件和 .h 文件之間的依賴關系的代碼。到這里,我想你也會覺得我們應該有一個通用的 makefile 來幫助我們自動維護依賴關系了吧。
使用 easymake 構建程序
在這個例子中,包含 easymake.mk
文件就足夠了。把我們的 Makefile 修改成下面的代碼:
include ../../easymake.mk
在命令行中輸入 make
構建我們的程序。接下來我們給你展示一些細節來幫助你理解 makefile 是如何構建程序的。
[root@VM_6_207_centos basics]# ls
main.cpp makefile math
[root@VM_6_207_centos basics]# make
g++ -c -o bin/main.o main.cpp -I.
entry detected
g++ -c -o bin/math/add.o math/add.cpp -I.
g++ -o bin/target bin/main.o bin/math/add.o
BUILD_ROOT/TARGET: bin/target
ENTRY: main.cpp
[root@VM_6_207_centos basics]# ./bin/target
please enter two integer:
3
5
add(3,5) returns 8
你也許也已經注意到,和之前的方式相比,主要的不同就是輸出中的 entry detected
,BUILD_ROOT/TARGET: bin/target
和 ENTRY: main.cpp
。bin/target
就是我們的程序。至於這里的entry,會在后面講到。
現在可以看一下當前的目錄結構:
[root@VM_6_207_centos basics]# tree .
.
├── bin
│ ├── easymake_current_entry_file
│ ├── easymake_detected_entries
│ ├── easymake_entries_tmp.d
│ ├── main.d
│ ├── main.o
│ ├── math
│ │ ├── add.d
│ │ └── add.o
│ └── target
├── main.cpp
├── makefile
└── math
├── add.cpp
└── add.h
3 directories, 12 files
Easymake 使用 bin
目錄作為 BUILD_ROOT
,用來存放生成的文件,這樣一來我們的源文件目錄也不會被污染。這里面的 *.d
和 easy_make_*
文件都是由 easymake 額外生成用來維護依賴關系的。*.d
的文件其實也算是 makefile 的一部分,例如 main.d 文件的內容如下:
[root@VM_6_207_centos basics]# cat bin/main.d
bin/main.o: main.cpp math/add.h
math/add.h:
這些依賴關系是 easymake 自動生成的,所以每當 math/add.h
被修改了,main.o
就會重新生成。事實上,你不需要關注這些細節來使用 easymake,所以我們就忽略這些額外生成的文件吧。如果你有興趣,可以查看 easymake.mk
的源代碼,我覺得代碼的注釋得已經足夠幫助你理解了。
用戶選項
如果你想使用 gcc 編譯器的 -O2
優化選項和鏈接器的 -static
選項來構建這個程序。那么你需要增加幾行代碼來修改編譯和鏈接選項。下面是修改后的 makefile:
COMPILE_FLAGS += -O2
LINK_FLAGS += -static
include ../../easymake.mk
然后重新構建程序:
[root@VM_6_207_centos basics]# make clean
rm -f \$(find bin -name \*.o)
rm -f \$(find bin -name \*.d)
rm -f \$(find bin -name \*.a)
rm -f \$(find bin -name \*.so)
rm -f \$(find bin -name \*.out)
rm -f bin/target
[root@VM_6_207_centos basics]# make
g++ -c -o bin/main.o main.cpp -O2 -I.
entry detected
g++ -c -o bin/math/add.o math/add.cpp -O2 -I.
g++ -o bin/target bin/main.o bin/math/add.o -static
BUILD_ROOT/TARGET: bin/target
ENTRY: main.cpp
除些以外,還有更多可供設置的選項,使用 make help
命令你就可以看到它們。注意 basic settings 和 user settings 兩部分的內容即可,其他部分可以忽略。
[root@VM_6_207_centos basics]# make help
---------------------
basic settings:
SETTINGS_ROOT : build_settings
BUILD_ROOT : bin
TARGET : target
VPATH :
CPPEXT : cpp
CEXT : c
GCC : gcc
GXX : g++
LINKER : g++
---------------------
user settings files:
build_settings/entry_list
build_settings/compile_flags
build_settings/compile_search_path
build_settings/link_flags
build_settings/link_search_path
---------------------
user settings:
ENTRY_LIST :
ENTRY :
COMPILE_FLAGS : -O2
COMPILE_SEARCH_PATH : .
LINK_FLAGS : -static
LINK_SEARCH_PATH :
CPPSOURCES : main.cpp math/add.cpp
CSOURCES :
---------------------
internal informations:
...
...
...
用來測試的程序入口
現在我們需要給程序增加一個乘法運算功能,首先寫一個 C++ 函數來做乘法運算,然后,在我們修改 main.cpp
的代碼之前,我們應該測試一下這個這個 C++ 函數的功能,確保新增加的乘法模塊的邏輯是正確的。下面的例子會告訴你如果使用 easymake 來完成這項工作,你可以在 samples/entries
文件夾中找到這個例子的代碼。
編寫乘法模塊的代碼
File math/multiply.h
:
#ifndef MULTIPLY_H
#define MULTIPLY_H
#include "stdint.h"
int64_t multiply(int32_t,int32_t);
#endif
File math/multiply.cpp
:
#include "math/multiply.h"
int64_t multiply(int32_t a,int32_t b){
return (int64_t)a*(int64_t)b;
}
編寫測試代碼
在命令行中輸入 mkdir test
和 vim test/multiply.cpp
然后編寫我們的代碼。為了簡單起見,這里僅僅是在 main
函數中打印了 8 乘 8 的結果。
#include "math/multiply.h"
#include <iostream>
using namespace std;
int main(){
cout<<"multiply(8,8)="<<multiply(8,8)<<endl;
}
構建測試程序
現在直接輸入命令 make
和 ./bin/target
就可以看到測試程序的輸出了。
[root@VM_6_207_centos entries]# make
g++ -c -o bin/main.o main.cpp -O2 -I.
entry detected
g++ -c -o bin/math/add.o math/add.cpp -O2 -I.
g++ -c -o bin/math/multiply.o math/multiply.cpp -O2 -I.
g++ -c -o bin/test/multiply.o test/multiply.cpp -O2 -I.
entry detected
g++ -o bin/target bin/math/add.o bin/math/multiply.o bin/test/multiply.o -static
BUILD_ROOT/TARGET: bin/target
ENTRY: test/multiply.cpp
[root@VM_6_207_centos entries]# ./bin/target
multiply(8,8)=64
[root@VM_6_207_centos entries]#
注意到 main.cpp
和 test/multiply.cpp
都有被成功編譯,但是只有 test/multiply.cpp
被鏈接到目標文件中,而且輸出中 ENTRY
對應的值也變成了 test/multiply.cpp
。在 easymake,全體一個包含 main
函數定義的源文件都會被自動檢測到,並且被當作程序入口文件(ENTRY
)。在眾多入口文件當中,只有一個會被選中,其他文件不會被鏈接到目標文件中。另外注意這里的 ENTRY
所表示的文件名對應的文件也可以不存在,在某些場景中,例如生成動態庫 so 文件,就需要選擇這個 ENTRY
來阻止其他入口文件被鏈接到目標文件中。
現在你肯定是在納悶,easymake 是如何知道要選擇 test/multiply.cpp
而不是 main.cpp
的?是不是很神奇?其實這里使用的是入口文件的最后修改時間。如果有多個入口文件,而且用戶沒有顯式地聲明使用哪個入口,那么 easymake 就會自動選擇最新的那個計算器文件。
如果你需要顯式地聲明 ENTRY
,以選擇 main.cpp
為例,可以輸入命令 make ENTRY=main.cpp
或者 make ENTRY=m
:
[root@VM_6_207_centos entries]# make ENTRY=main.cpp
g++ -o bin/target bin/main.o bin/math/add.o bin/math/multiply.o -static
BUILD_ROOT/TARGET: bin/target
ENTRY: main.cpp
到這里已經完成了乘法模塊的測試,接下來可以修改 main.cpp
的代碼來整合我們的新模塊了。為了簡潔,接下來的步驟就不在這里贅述了,如果有需要,可以查看 samples/entries
目錄中的代碼。
原文及代碼下載
最新的代碼和文檔請前往此處下載 https://github.com/roxma/easymake 。有任何BUG或者改進建議請聯系我 roxma@qq.com