2014年11月17日 星期一

Extending Automake Rules

最近在試著把 node.js 放進公司的專案當中。

人生就是有那麼多的 but,就是會有專案跟你用的 building system 不一致,你被迫要 merge 兩個不同的 building system。

node.js 的 building system 沒有考慮過被當作 sub-project 的狀況,無法在 source code 以外的資料夾進行編譯。用比較精確的說法來說,就是 node.js 的 building system 沒有區分 build tree 和 source tree,無法進行 VPATH builds (又稱作 parallel builds,因為 parallel build 常會讓人誤以為是分散式編譯,所以大家才會又發明一個饒口的 VPATH builds)

這其實是滿不好的設計。以前遇到這類型的專案,大多都是為了政治因素, 不給其他專案用的反智現象。但是 node.js 應該只是開發者不了解這個需求,或者還沒有人嘗試過使用 node.js 成為子專案,所以也沒有人回報這個需求。

首先先看了這篇:Third-Party MakefileS

當中說到,利用 SUBDIRS 和 DIST_SUBDIRS 可以讓 make 進入 sub-project 執行 Makefile。這方法其實只解決了一半問題,在 node.js 以及大多數的 open source project都會面臨到另外一個問題。

大多數 open source project developers 通常會將 ${builddir} 和 ${srcdir} 分開,也就是說,程式碼和編譯的資料夾,是不同的地方。如果用 SUBDIRS 和 DIST_SUBDIRS,則 make 會在 ${builddir} 去找只存在 ${srcdir} 的  sub-project。

舉例來說,如果我的專案叫做 utils,然後我把 node.js 放到 utils/nodejs 資料夾下,然後在 utils/Makefile.am 這樣寫:

SUBDIRS = nodejs

接下來編譯的時候,建立一個 build-utils 資料夾,然後執行

cd build-utils
../utils/configure
make all

則在 make 的時候,make 會跟我說找不到 nodejs 資料夾。訊息如下:

Making all in nodejs
/bin/sh: line 0: cd: nodejs: No such file or directory  

這是因為 nodejs 是在 utils 底下,而不是在 build-utils 底下,所以 make 自然找不到。這時候有兩種解法。

第一招 - Makefile Adapter

如果使用的是 GNU make 的話,一種方式是增加一個新的 Makefile,讓 make 優先讀取,而這個優先讀取的 Makefile,去 include 原來的 Makefile。

這是一種 Adapter 的概念,用自己的 Makefile,去包裝他人的 Makefile。

如果不使用 -f 指定 makefile 的話,make 讀取 makefile 的順序是 GNUmakefile, makefile, 和 Makefile,所以大多數的專案都會使用大寫的 Makefile,好讓有需要時,小寫的 makefile 派上用場。

其實 Makefile 是大寫還是小寫,也可以看出一個 open source project 功力,通常很多人使用的專案會知道要用大寫的版本。

這時候要讓 VPATH builds 裡面生出能夠優先讀取的 Makefile,所以通常會在 source tree 當中增加一個 makefile.in 或 GNUmakefile.in,然後在 configure.ac 當中利用 AC_CONFIG_FILES 生成 VPATH builds 的 makefile/GNUmakefile。如果原生的 makefile 檔名是 Makefile,則 makefile.in/GNUmakefile.in 的內容應該如下:

# First, include the real Makefile
include Makefile

# Then, define the other targets needed by Automake Makefiles. (A rule-wrapper to original Makefile)
.PHONY: dvi pdf ps info html check
dvi pdf ps info html:
check: test

這個新的 Makefile.am,要把原生 Makefile 的 rule 轉接到 Automake 的 rule 上,做一個 rule adapter。

第二招 - Makefile Proxy

第二招原理也是很像,主要的目的是在避免 include Makefile。因為有些專案就是那麼白目,make file 的檔名會用 makefile 或者是 GNUmakefile,使得 Makefile Adapter 無法使用。

Makefile proxy 的做法是將原來的 makefile 改名,然後利用 make -f 的方式,執行真正的 Makefile。舉例來說,可以將原來的 makefile 改名為 makefile.real,然後讓 Makefile.am 當中去執行 $(MAKE) -f makefile.real。

為了要保留原來 Automake 所提供的參數,所以通常會在 $(MAKE) -f makefile.real 之後,加上 `$(AM_MAKEFLAGS) target`

Makefile.am 會像這樣:

all-local:
  cd subdir && $(MAKE) -f $(srcdir)/Makefile.real $(AM_MAKEFLAGS) all


不論是哪一招,都無法克服一個問題就是:如果這個專案不 support VPATH builds,則不管是哪一招都沒有漂亮的解決方案。如果該專案要支援 VPATH builds (而且通常你會需要支援),則需要重寫該專案的 Makefile