Makefile 有什麼奇技淫巧?

GNU/Make 的 Makefile 有什麼不為人所知的奇技淫巧卻可以提升效率?


$ make love
make: *** No rule to make target `love". Stop.

$ make money
make: *** No rule to make target `money". Stop.

$ make -j8 america great again
...


只有兩三個源代碼,那 makefile 隨便寫,文件一多,搞依賴都可以搞死人。

我剛開是手寫 Makefile,後來發現越寫越臃腫,特別是你開始搞頭文件依賴生成的時候,想要寫一個完善的 makefile 確實是十分啰嗦的事情,最後項目里弄個 Makefile 模板,然後拷貝來拷貝去,各個項目 include 再稍微改一下。

是能做事情,但總覺得不夠完美,試用了一下 automake 之類的,每次要弄兩遍,也覺得挺不爽的,所以我一直再思考有沒有更好的辦法,更簡易的方式?

後來我自己用 python 寫了個小工具,來代替 make,來徹底簡化項目構建這個事情,最開始只有一點基礎功能,後面隨著功能完善,部門內逐步開始用來代替 makefile,我自己平時也用它來構建日常項目,確實讓我自己的工作效率確實得到了提升。

這個腳本是 2009年時候寫的,斷斷續續的修修補補,如今有如下功能:

  • 模型簡單:設定源文件,設定編譯參數和輸出目標就行了,emake為你打點好一切。
  • 依賴分析:快速分析源代碼所依賴的頭文件,決定是否需要重新編譯。
  • 輸出模式:可執行、靜態庫(.a)、動態庫(.so/.dll)。
  • 多核編譯:輕鬆實現並行編譯,加速項目構建。
  • 精簡緊湊:只有唯一的一個 emake.py 文件。
  • 交叉編譯:構建 iOS 項目 ,安卓項目,等等。
  • 語言支持 C / C++ / ObjC / ObjC++ / ASM
  • 工具支持 gcc / mingw / clang
  • 運行系統 Windows / Linux / Mac OS X / FreeBSD
  • 方便的交叉編譯,輕鬆構建 Android NDK / iOS / asm.js 項目
  • 你見過最簡單的構建系統,比 Gnu Make / CMake 都簡單很多

多年間我的團隊在不同操作系統下用它構建過:服務端項目、客戶端項目、iOS項目、安卓項目 和 Flash項目,這些項目都穩健的跑在生產環境中,為海量用戶提供服務。

歡迎進一步了解:

skywind3000/emake

特點之一,該腳本除了支持獨立的工程文件外,對於中小型測試類項目,還支持直接把項目信息寫在源代碼的宏里:

#include &
#include "foobar.h"

//! flag: -O2, -Wall
//! exe: foo.cpp, bar.cpp, utils.cpp
int main(void)
{
printf("Hello, World !!
");
foo();
bar();
return 0;
}

通過 //! ,定義工程信息,一個簡短的工程就描述好了,目標為可執行文件,以及相關源文件和編譯參數。

然後:

$ emake main.cpp

你就能得到 main (.exe) 了,配置到 NotePad++, Vim 里成為一個快捷鍵是十分容易的。

VC那種為幾個小型測試項目就要搞一大堆中間文件和目錄,顯得又傻又臃腫。對 CMake 之類,隨便寫點啥,再小的項目也要佔一個獨立文件夾,又弄出一堆臨時東西來,每加一個源文件進去就要重新生成 Makefile,看著都煩。

我們這裡連工程文件都省掉了,源代碼直接內嵌工程信息,對於中小測試類項目,這種寫法既直觀也易懂,不會存在再小的項目都要獨佔一個目錄這種事情,項目大了你在分出獨立的工程文件來也沒問題。

特點之二,配置導入。

針對不同的本地庫,你可以在 emake.ini 預定義一些編譯參數(頭文件和庫文件路徑等):

[ffmpeg]
include=d:/dev/local/opt/ffmpeg/include
lib=d:/dev/local/opt/ffmpeg/lib
link=avcodec, avdevice, avfilter, avformat, avutil, postproc, swscale

[qt]
include=D:/Dev/Qt/sdk/4.8.3-mingw/include;D:/Dev/Qt/sdk/4.8.3-mingw/include/QtGui
lib=D:/Dev/Qt/sdk/4.8.3-mingw/lib
link=stdc++, ole32, gdi32, wsock32, opengl32, gdi32, glu32, ws2_32, uuid, oleaut32, winmm, imm32, winspool, QtCore4, QtGui4, QtGuid4

一次性配置後,工程文件或者源代碼里你可以直接一個:

//! import: qt, ffmpeg

這些庫的編譯參數就會加到你的工程裡面來,比如你要寫 java 的 jni,每個人機器上的 jdk 路徑都不一樣,每次都寫死 include/lib 路徑到工程文件里,既多餘又不太方便移植,通過預定義,你可以直接一個 」import: jdk8" 然後你就可以 include jdk 的 jni.h 了。不需要每個用到 jdk的項目都配置一大堆 jdk 的絕對路徑。

這就是兩個簡化工程描述的例子。


什麼?Makefile有什麼東西不是奇技淫巧?尤其你看看GNU擴展以後的那個syntax,官方doc里你隨機選一個小標題出來搞不好都是很多人不知道的「奇技淫巧」。

還嫌不夠?要不要試試看經常一起出現的用於「簡化」Makefile的autotools,學學牛逼的宏語言m4?


用cmake效率高。


這樓是曬電腦樓嗎

make -j 54

要玩野路子的 mapreduce 並發寫 makefile 是個很好用的手段

C 我肯定用 premake5,一些奇葩項目才手寫


我都是寫configure.ac http://makefile.am,估計沒幾個人會寫這東西了。。。

我一般都是make -j8,有種說髒話的快感:-p


哦,奇技淫巧啊……

拿make -j8做簡易任務調度啊。

你有個簡單的類似mapreduce的任務,需要對著25份數據分別跑個map,然後最後合起來跑個reduce。然後你還希望一直保持8進程並行跑,怎麼辦呢?

寫個makefile表達一下這些任務之間的依賴關係,然後make -j8就好了。

就算這些任務根本不產生中間文件也一樣,比如它們可以是對著伺服器發請求或者刪特定文件,無所謂,反正你只需要指定依賴關係然後控制並行度就能調度它們了,寫一堆.PHONY target互相依賴然後make -j8。如果你每個任務完了以後touch一個標記文件你就支持ctrl+c切斷程序然後需要的時候繼續跑了。


這兩個應該挺常見的?

g++ -MM a.cpp &> a.dep 生成依賴

$@ $^ 一個代表依賴文件 一個代表目標文件


%的匹配。可以省略很多代碼。

熟練運用$@ $^ $&<等。配合上一條使用更加。

各種函數,比如addprefix、addsuffix、shell。這時就可以用Makefile來批量跑數據,或者調參了。(如https://github.com/sunziping2016/VeryTinyCnn/blob/master/Makefile)

還有需要子make的。子make產生出來的文件對應的依賴如果不寫在父文件內就不能觸發。可是寫在父文件內又意味著依賴寫了兩處。可加進PHONY也不對。這時用一個FORCE空項目就行了,也就是強制遞歸到子文件內,至於繼續更不更新還是看時間戳。(比如https://github.com/sunziping2016/xv6-improved/blob/master/Makefile)

其實只要清楚make是基於文件系統時間戳的調度。當有些依賴實在難以描述的話,你可以touch一下文件來輔助。(比如https://github.com/sunziping2016/xv6-improved/blob/master/Makefile)


現在有各種 gmake 的替代品, 例如 ant, sbt, ninja, rake, bower, grunt, webpack ...

還有各種生成 makefile 的工具, 例如 configure, autoconf, autoreconf, cmake, scons, mkmf ...

(圖出自 configure script)

但其實單用 gmake 就能做很多事情, 而且經常簡單得多...

Makefile 里你可以用 -include 包含子 makefile, 你可以做幾個常用的, 例如指定 .less 生成 .css 文件的規則, include 一下就好.

C 文件里經常包含一些頭文件, 如果頭文件變化了, 所有 include 了這個 header 的源文件都要重新編譯 -- 這時怎麼寫 makefile 呢?

gcc 有個 -MMD 的選項可以生成源文件和頭文件之間的依賴關係描述 .d 文件, 然後這些依賴文件是可以 include 到 Makefile 里的, 所以你只需要這樣寫就行了:

-include *.d

CFLAGS += -MMD -MP -march=native
src_files = $(shell ls -1 {src,lib}/*.c | grep -v test)
obj_files = $(addsuffix .o, $(basename src_files))

my-target: $(obj_files)
cc $(CFLAGS) $(LDFLAGS) -o my-target

makefile 每個 task 內容里的每一行只能寫一個 shell 腳本命令, 如果要執行多個命令, 可以用分號連起來, 如果一行太長了, 可以拆行用反斜線連起來

some-task:
foo;
bar

其實上面的文檔里都講到, 不算什麼奇技淫巧...

附帶一點前端用 make 構建的經驗:

Using GNU Make as a frontend build tool

Simple Front-end Builds With Makefiles · west.io


用distcc,分散式編譯。

https://github.com/distcc/distcc


有點跑題了,但是,OIer 日常:

你可以不寫 Makefile 直接用 make

你可以只在 Makefile 里寫一行就直接用 make

CXXFLAGS="-Wall -g"

但是你需要顯式指定目標。這樣比如你寫了個 foo.cpp 就可以直接 make foo。

但是人生苦短,為什麼不用 cmake 呢(逃)


1. 學會把make搭配ccache使用;

2. 學會把稍大的項目分子目錄,子目錄擁有自己的Makefile。


我所以為的 Makefile:

all: foo.txt

foo.txt: bar.txt
cp bar.txt $@

bar.txt:
echo "hello world" &> $@

clean:
rm foo.txt bar.txt

.PHONY: all clean

後來我看到別人寫的 Makefile:

CC = $(CROSSPREFIX)gcc
LD = $(CC)
CFLAGS = -O2 -W -Wall
LDFLAGS =

PROGS = $(patsubst %.c,%$(BINEXT), $(wildcard *.c))
SCRIPTS = rkunsign rkparametersblock rkmisc rkpad rkparameters

all: $(PROGS) $(SCRIPTS)

%$(BINEXT): %.c
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)

再後來我看到 Android Open Source Project 里的 Makefile:

LOCAL_PATH := $(call my-dir)

CFLAGS := -O2 -W -Wall
LDFLAGS :=

include $(CLEAR_VARS)
LOCAL_SRC_FILES := rkcrc.c
LOCAL_MODULE := rkcrc
LOCAL_CFLAGS := $(CFLAGS)
LOCAL_LDFLAGS := $(LDFLAGS)
include $(BUILD_HOST_EXECUTABLE)

include $(CLEAR_VARS)
LOCAL_SRC_FILES := rkparametersblock
LOCAL_MODULE := rkparametersblock
LOCAL_REQUIRED_MODULES := rkcrc
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_IS_HOST_MODULE := true
include $(BUILD_PREBUILT)


強烈推薦學習並且使用cmake,Github越來越多的開源項目都用它,雖然它的命令也很多,但是只需要掌握基礎用法就可以勝任項目開發。


我一直是手寫,覺得挺好的,header放一起,lib放一起,每個模塊寫一個makefile,最後寫個總的makefile,一級調用一級,感覺蠻好的


試試blade


用scons方便太多,優點很多,makefile各有各的特色


CMake吧,廣泛使用了


沒有看到說wildcard的,就補充一下吧


推薦閱讀:

如何用C++寫一個簡單的小遊戲?
為什麼 C/C++ 的宏一直沒怎麼發展?
malloc是從系統堆裡面動態申請空間,那與char *申請的空間有什麼區別?
不用虛析構函數也不會造成內存泄漏的原因是什麼?
C/C++靜態庫中的函數在這個靜態庫被使用時還有被inline的可能嗎?

TAG:計算機科學 | CC | Makefile | make | X編程語言有什麼奇技淫巧 |