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


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM