通用的 makefile 小工具分享 - Easymake 使用說明

Easymake 使用說明

介紹

Easymake 是一個在linux系統中 C/C++ 開發的通用 makefile。在一個簡單的 C/C++ 程序中使用 easymake,你甚至能夠不寫一行 makefile 代碼來生成目標文件。linux

Easymake 包含如下功能:ios

  • 自動掃描 C/C++ 源文件。
  • 自動生成和維護依賴關係,加快編譯時間。
  • 支持簡單的單元測試,能夠很方便地管理多個程序入口(main 函數)。
  • 完美地支持 VPATH 變量。

我將在後面的例子中一步步地向你展現如何使用 easymake 來構建你的應用程序。別看文檔這麼長,下面一節的內容中大部分是在講一個簡單的 C/C++ 程序的開發。就像 easymake 的名字同樣,easymake 是很是易學易用的。git

開始學習 Easymake

在這一節中將展現如何在一個簡單的程序中使用 easymake。接下來讓咱們一個加法程序,用戶輸入兩個數字,而後程序輸出這兩個數字相加的結果。這個程序的源代碼能夠在 samples/basics 目錄中找到。github

C/C++ 代碼

這個程序很簡單,因此這裏跳過程序設計環節。這裏直接展現程序的 C/C++ 代碼,而後再轉入咱們的正題。shell

File: main.cppvim

#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.hcentos

#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.cppmultiply.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 detectedBUILD_ROOT/TARGET: bin/targetENTRY: main.cppbin/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,用來存放生成的文件,這樣一來咱們的源文件目錄也不會被污染。這裏面的 *.deasy_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 settingsuser 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 testvim 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.cpptest/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

相關文章
相關標籤/搜索