寫Makefile真的是一件非常痛苦的事情,的確非常痛苦。而更痛苦的是當需要將代碼移植到別的系統上時,這就夠你喝一壺的。再說了,作為程序員的我們,是不是更應該投入更多的精力到業務邏輯的編寫與處理中呢,而並不是和Makefile糾結呢。
不知道你是否閱讀過一些Linux平台上開源的C或者C++項目,當你編譯這些項目的時候,只需./configure
、 make
和make install
就可以把程序編譯完成並安裝到系統中。你是否想過,這些開源的項目的編譯和安裝怎麼如此的干淨利落。
我們覺的寫Makefile很麻煩,我們羨慕那些開源項目簡單的編譯安裝步驟。那麼如何讓寫Makefile變得簡單一些呢?這個時候大師們就編寫了一些能夠自動根據系統生成Makefile文件的工具。這篇文章,我就對這些工具進行簡單的總結,並結合一個簡單的例子進行實踐。
大師們寫的工具主要有哪些呢?如下所示:
如果你想使用這些工具,就先看看你的系統上有沒有正確安裝這些工具(我使用的是Ubuntu)。
which autoconf
如果沒有安裝,則執行以下語句安裝就OK了。
sudo apt-get install autoconf
autoconf是一個用於生成可以自動地配置軟件源碼包,用以適應多種UNIX類系統的shell腳本工具,其中autoconf需要用到m4,便於生成腳本。automake是一個從Makefile.am文件自動生成Makefile.in的工具。為了生成Makefile.in,automake還需用到perl,由於automake創建的發布完全遵循GNU標准,所以在創建中不需要perl。libtool是一款方便生成各種程序庫的工具。
對於工具的簡單介紹就到此結束,接下來講講如何使用這些工具來生成一個Makefile文件。
使用上述的工具生成Makefile文件的步驟基本是死的,只需要在每步按照我們的需求進行適當的配置即可生成一個“漂亮”的Makefile文件。具體的步驟如下:
這些命令之間的關系如下圖所示:
以上就是生成一個完整makefile的主要步驟。當然了,在實際項目中,會根據需要進行微調。下面我就拿最經典的Hello World程序進行一個簡單的演示。
從Hello World開始
測試代碼:HelloWorld.cpp
源碼如下:
#include <iostream>using namespace std;int main(){
cout<<"Hello World"<<endl;
return 0;}
運行autoscan命令,生成的文件列表如下: -rw-rw-r-- 1 jelly jelly 96 Jun 13 22:05 HelloWorld.cpp
-rw-rw-r-- 1 jelly jelly 0 Jun 13 22:23 autoscan.log
-rw-rw-r-- 1 jelly jelly 475 Jun 13 22:23 configure.scan
重命名configure.scan文件為configure.ac,修改configure.ac文件為如下樣子: # -*- Autoconf -*-# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.69])
AC_INIT(HelloWorld, 1.0, [email protected])
AC_CONFIG_SRCDIR([HelloWorld.cpp])
AC_CONFIG_HEADERS([config.h])
AM_INIT_AUTOMAKE(HelloWorld, 1.0)# Checks for programs.
AC_PROG_CXX
# Checks for libraries.# Checks for header files.# Checks for typedefs, structures, and compiler characteristics.# Checks for library functions.
AC_CONFIG_FILES([Makefile])
AC_OUTPUT
執行aclocal命令,生成的文件列表如下: -rw-rw-r-- 1 jelly jelly 96 Jun 13 22:05 HelloWorld.cpp
-rw-rw-r-- 1 jelly jelly 0 Jun 13 22:23 autoscan.log
-rw-rw-r-- 1 jelly jelly 497 Jun 13 22:58 configure.ac
drwxr-xr-x 2 jelly jelly 4096 Jun 13 23:03 autom4te.cache
-rw-rw-r-- 1 jelly jelly 39670 Jun 13 23:03 aclocal.m4
執行autoheader命令,生成config.h.in文件
執行autoconf命令,生成的文件列表如下: -rw-rw-r-- 1 jelly jelly 96 Jun 13 22:05 HelloWorld.cpp
-rw-rw-r-- 1 jelly jelly 0 Jun 13 22:23 autoscan.log
-rw-rw-r-- 1 jelly jelly 497 Jun 13 22:58 configure.ac
-rw-rw-r-- 1 jelly jelly 39670 Jun 13 23:03 aclocal.m4
drwxr-xr-x 2 jelly jelly 4096 Jun 13 23:08 autom4te.cache
-rwxrwxr-x 1 jelly jelly 135929 Jun 13 23:08 configure
可以看到,生成了可執行的configure文件。
在Project目錄下新建Makefile.am文件,Makefile.am文件的內容如下: AUTOMARK_OPTIONS=foreign
noinst_PROGRAMS=HelloWorldHelloWorld_SOURCES=HelloWorld.cpp
關於如何編寫Makefile.am文件,我在下一節總結。
運行automake命令,就會得到Makefile.in文件
執行automake命令時,提示有些文件不存在,我們直接touch即可。
touch NEWS README ChangeLog AUTHORS
執行configure生成Makefile
現在得到我們需要的Makefile文件了,接下來,你應該怎麼做了。
如何編寫Makefile.am
上面對於一個簡單的HelloWorld程序使用autoconf和automake工具成功的生成了Makefile文件,對於編寫Makefile.am文件一直沒有詳細說明,這裡就對如何編寫Makefile.am文件進行詳細的說明。
Makefile.am是一種比Makefile更高層次的規則。只需指定要生成什麼目標,它由什麼源文件生成,要安裝到什麼目錄等即可。automake會根據我們寫的Makefile.am來自動生成Makefile.in。Makefile.am中定義的宏和目標會指導automake生成指定的代碼。常見的文件編譯類型有下面幾種:
PROGRAMS;表示可執行文件
LIBRARIES;表示庫文件
LTLIBRARIES;這也是表示庫文件,前面的LT表示libtool
HEADERS;頭文件
DATA;數據文件,不能執行。
下面就對編譯成可執行文件、動態庫文件和靜態庫文件常用的寫法進行簡單介紹。
編譯可執行文件
比如這樣的一個Makefile.am文件:
bin_PROGRAMS = client
client_SOURCES = key.c connect.c client.c main.c session.c hash.c
client_CPPFLAGS = -DCONFIG_DIR=\"$(sysconfdir)\" -DLIBRARY_DIR=\"$(pkglibdir)\"
client_LDFLAGS = -export-dynamic -lmemcached
noinst_HEADERS = client.h
INCLUDES = -I/usr/local/libmemcached/include/
client_LDADD = $(top_builddir)/sx/libsession.a \
$(top_builddir)/util/libutil.a
每個字段具體的含義如下:
名稱 含義
bin_PROGRAMS 表示指定要生成的可執行應用程序文件,這表示可執行文件在安裝時需要被安裝到系統中;如果只是想編譯,不想被安裝到系統中,可以用noinst_PROGRAMS來代替
client_SOURCES 表示生成可執行應用程序所用的源文件,這裡注意,client_是由前面的bin_PROGRAMS指定的,如果前面是生成example,那麼這裡就是example_SOURCES,其它的類似標識也是一樣
client_CPPFLAGS 這和Makefile文件中一樣,表示C語言預處理參數,這裡指定了DCONFIG_DIR,以後在程序中,就可以直接使用CONFIG_DIR。不要把這個和另一個CFLAGS混淆,後者表示編譯器參數
client_LDFLAGS 連接的時候所需庫文件的標識,這個也就是對應一些如-l,-shared等選項
noinst_HEADERS 這個表示該頭文件只是參加可執行文件的編譯,而不用安裝到安裝目錄下。如果需要安裝到系統中,可以用include_HEADERS來代替
INCLUDES 鏈接時所需要的頭文件
client_LDADD 鏈接時所需要的庫文件,這裡表示需要兩個庫文件的支持
SUBDIRS 表示在處理本目錄之前需要遞歸處理哪些子目錄
編譯動態庫文件
想要編譯XXX.so文件,需要用_PROGRAMS類型,這裡一個關於安裝路徑要注意的問題是,我們一般希望將動態庫安裝到lib目錄下,只需要寫成lib_PROGRAMS就可以了,因為前面的lib表示安裝路徑(為什麼?稍後講),但是automake不允許這麼直接定義,可以采用下面的辦法,也是將動態庫安裝到lib目錄下。
projectlibdir=$(libdir) //新建一個目錄,該目錄就是lib目錄
projectlib_PROGRAMS=project.so
project_so_SOURCES=xxx.c
project_so_LDFLAGS=-shared -fpic //GCC編譯動態庫的選項
編譯靜態庫文件
對於下面的一個Makefile.am文件:
noinst_LTLIBRARIES = libutil.a
noinst_HEADERS = inaddr.h util.h compat.h pool.h xhash.h url.h device.h
libutil_a_SOURCES = access.c config.c datetime.c hex.c inaddr.c log.c device.c pool.c rate.c sha1.c stanza.c str.c xhash.c
對上述Makefile.am文件的解釋如下:
名稱 含義
noinst_LTLIBRARIES 這裡要注意用的是LTLIBRARIES,另外還有LIBRARIES,兩個都表示庫文件。前者表示libtool庫,用法上基本是一樣的。如果需要安裝到系統中的話,用lib_LTLIBRARIES。一般推薦使用libtool庫編譯目標,因為automake包含libtool,這對於跨平台可移植的庫來說,肯定是一個福音。
libutil_a_LIBADD 靜態庫編譯連接時需要其它的庫的話,采用XXXX_LIBADD選項
頭文件我們一般需要導入一些*.h的頭文件,如果你在Makefile.am中沒有標識需要導入的頭文件,可能在make dist打包的時候出現問題,頭文件可能不會被打進包裡面。 #可以將頭文件引入
include_HEADERS=../include/common.h ../include/sum.h ../include/get.h ../include/val.h
make install,頭文件默認會被安裝到linux系統/usr/local/include。
數據文件 data_DATA = data1 data2
在上面說到一個問題,在編譯動態庫文件時:
寫成lib_PROGRAMS就可以了,因為前面的lib表示安裝路徑,為什麼?
這裡涉及到編寫Makefile.am文件時,安裝路徑的問題。
安裝路徑
在默認的情況下,執行make install命令,則會將文件安裝到$(prefix) = /usr/local路徑下。我們可以通過./configure --prefix=xxx的方式來進行修改。基於此,系統還定義了以下一些路徑變量:
名稱 值
bindir $(prefix)/bin
libdir $(prefix)/lib
datadir $(prefix)/share
sysconfdir $(prefix)/etc
includedir $(prefix)/include
那麼現在你就應該明白以下知識點了:
bin_PROGRAMS表示將生成的可執行文件安裝到$(bindir)目錄下
lib_LTLIBRARIES表示將靜態庫安裝到$(libdir)目錄下
上面說的projectlib_PROGRAMS表示安裝到$(projectlibdir)所表示的目錄下
如果我們在Makefile.am文件中定義了一個新的路徑:
sysdatedir = $(prefix)/sysdate
sysdate_DATA = data1 data2
此時data1和data2就會作為數據文件安裝到$(prefix)/sysdate路徑下。現在關於安裝路徑的文件就應該明白了吧。
打包
一切搞定以後,可以正常的生成Makefile文件,編譯生成的程序或庫也沒有任何問題了,此時我們可能需要對源文件進行打包。使用make dist命令就可以完成自動打包任務,自動打包包含的內容如下:
所有源文件
所有的Makefile.am文件
configure讀取的文件
Makefile.am中包含的文件
EXTRA_DIST指定的文件
采用dist及nodist指定的文件,如可以將某一源文件指定為不打包: nodist_client_SOURCES = client.c
使用make dist命令之後,就會在當前目錄下生成一個在AC_INIT中定義的軟件名稱和版本號的tar.gz壓縮包。
總結
總結的好長的一篇文章,文章雖長,但講的不深,入門足夠。更多的相關知識只有通過更多在實際項目中錘煉,閱讀更多的相關文檔學習了。