原網址: http://markuskimius.wikidot.com/programming:tut:autotools

原文理解 & 操作

編寫 hello.c

#include <stdio.h>

int main()
{
    printf("Hello\n");

    return 0;
}

Makefile

all: hello

clean:
        rm -f hello *.o

產生 configure.ac

autoscan
mv configure.{scan,ac}

產生 configure

autoconf

重命名 MakefileMakefile.in, 把後者當成 template 產生 Makefile

mv Makefile{,in}

產生 Makefile & build .c file

./configure
checking for gcc... gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables... 
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
configure: creating ./config.status
config.status: creating Makefile
config.status: error: cannot find input file: `config.h.in'

ls
autom4te.cache  autoscan.log  config.log  config.status  configure  configure.ac  hello.c  Makefile  Makefile.in

make clean all
rm -f hello *.o
cc     hello.c   -o hello

如果在 autoscan 的階段, Makefile 不存在的話, autoscan 產生的 configure.ac 如下

#                                               -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ([2.69])
AC_INIT([FULL-PACKAGE-NAME], [VERSION], [BUG-REPORT-ADDRESS])
AC_CONFIG_SRCDIR([hello.c])
AC_CONFIG_HEADERS([config.h])

# Checks for programs.
AC_PROG_CC

# Checks for libraries.

# Checks for header files.

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.

AC_OUTPUT

存在的話, configure.ac 內容如下

#                                               -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ([2.69])
AC_INIT([FULL-PACKAGE-NAME], [VERSION], [BUG-REPORT-ADDRESS])
AC_CONFIG_SRCDIR([hello.c])
AC_CONFIG_HEADERS([config.h])

# Checks for programs.
AC_PROG_CC

# Checks for libraries.

# Checks for header files.

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.

AC_CONFIG_FILES([Makefile])
AC_OUTPUT

多了這行, 代表會去找 Makefile.in 作為 template 產生 Makefile

AC_CONFIG_FILES([Makefile])

另外, 在 configure 時說少了 config.h.in

config.status: error: cannot find input file: `config.h.in'

可在執行過 configure 後用 autoheader 產生

./configure
...
...
...
./autoheader

另外, 如果 configure 不去找 config.h.in 的話 (早期的 autoconf 的行為是這樣的), 在 configure.ac 中加上這行

AC_CONFIG_HEADER(config.h)

在 source code 改成這樣以後, 還可以讓 autoscan 幫我們檢查有沒有需要加到 configure.ac 的檢查

* hello.c: A program to show the time since the Epoch */
 
#include <stdio.h>
#include <sys/time.h>
 
int main(int argc, char* argv[])
{
   double sec;
   struct timeval tv;
 
   gettimeofday(&tv, NULL);
   sec = tv.tv_sec;
   sec += tv.tv_usec / 1000000.0;
 
   printf("%f\n", sec);
 
   return 0;
}

重新 scan 一次

$ autoscan
configure.ac: warning: missing AC_CHECK_FUNCS([gettimeofday]) wanted by: hello.c:11
configure.ac: warning: missing AC_CHECK_HEADERS([sys/time.h]) wanted by: hello.c:4

# 記得把新產生的 configure.scan 更名為 configure.ac
$ mv configure.{scan,ac}
$ autoconf
# 有了新的相容性檢查, 自然 config.h.in 中的開關數量也會不同, 所以 config.h.in 也要更新
$ autoheader

這樣, configure.ac 會更自動更新, config.h.in 也會自動加上新的開關. 再次運行 configure

$ ./configure 
checking for gcc... gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables... 
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
checking how to run the C preprocessor... gcc -E
checking for grep that handles long lines and -e... /usr/bin/grep
checking for egrep... /usr/bin/grep -E
checking for ANSI C header files... yes
checking for sys/types.h... yes
checking for sys/stat.h... yes
checking for stdlib.h... yes
checking for string.h... yes
checking for memory.h... yes
checking for strings.h... yes
checking for inttypes.h... yes
checking for stdint.h... yes
checking for unistd.h... yes
checking sys/time.h usability... yes
checking sys/time.h presence... yes
checking for sys/time.h... yes
checking for gettimeofday... yes
configure: creating ./config.status
config.status: creating Makefile
config.status: creating config.h

檢查項目更多了. 有了新的 configure.ac 及新的 config.h.in, 我們就可以通過裡面的 HAVE_GETTIMEOFDAY 確認是否有 gettimeofday() 這個 function, HAVE_SYS_TIME_H 確認是否有 sys/time.h 這個 header. 更新後的 source code 如下

/* hello.c: A program to show the time since the Epoch */
 
#include <stdio.h>
#include "config.h"
 
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#else
#include <time.h>
#endif
 
double get_sec_since_epoch()
{
   double sec;
 
   #ifdef HAVE_GETTIMEOFDAY
      struct timeval tv;
 
      gettimeofday(&tv, NULL);
      sec = tv.tv_sec;
      sec += tv.tv_usec / 1000000.0;
   #else
      sec = time(NULL);
   #endif
 
   return sec;
}
 
int main(int argc, char* argv[])
{
   printf("%f\n", get_sec_since_epoch());
 
   return 0;
}

改完 source code 以後, 可以再運行 autoscan 以確保沒有 portability 的問題

$ autoscan
$

接下來, 配合 Automake 強化 Makefile. 概念差不多

  1. 建立 Makefile.am
  2. Makefile.am 作為 template 給 Automake 產生 Makefile.in
  3. Makefile.in 作為 template 在 configure 過程產生 Makefile

可惜的是, Makefile.am 沒法自動產生, 內容如下

bin_PROGRAM=hello
hello_SOURCES=hello.c

執行 automake (回報的 error message 會因版本不同而不同)

$ automake
configure.ac: error: no proper invocation of AM_INIT_AUTOMAKE was found.
configure.ac: You should verify that configure.ac invokes AM_INIT_AUTOMAKE,
configure.ac: that aclocal.m4 is present in the top-level directory,
configure.ac: and that aclocal.m4 was recently regenerated (using aclocal)
Makefile.am: error: required file './INSTALL' not found
Makefile.am:   'automake --add-missing' can install 'INSTALL'
Makefile.am: error: required file './NEWS' not found
Makefile.am: error: required file './README' not found
Makefile.am: error: required file './AUTHORS' not found
Makefile.am: error: required file './ChangeLog' not found
Makefile.am: error: required file './COPYING' not found
Makefile.am:   'automake --add-missing' can install 'COPYING'
Makefile.am: error: required file './depcomp' not found
Makefile.am:   'automake --add-missing' can install 'depcomp'
/usr/share/automake-1.16/am/depend2.am: error: am__fastdepCC does not appear in AM_CONDITIONAL
/usr/share/automake-1.16/am/depend2.am:   The usual way to define 'am__fastdepCC' is to add 'AC_PROG_CC'
/usr/share/automake-1.16/am/depend2.am:   to 'configure.ac' and run 'aclocal' and 'autoconf' again
/usr/share/automake-1.16/am/depend2.am: error: AMDEP does not appear in AM_CONDITIONAL
/usr/share/automake-1.16/am/depend2.am:   The usual way to define 'AMDEP' is to add one of the compiler tests
/usr/share/automake-1.16/am/depend2.am:     AC_PROG_CC, AC_PROG_CXX, AC_PROG_OBJC, AC_PROG_OBJCXX,
/usr/share/automake-1.16/am/depend2.am:     AM_PROG_AS, AM_PROG_GCJ, AM_PROG_UPC
/usr/share/automake-1.16/am/depend2.am:   to 'configure.ac' and run 'aclocal' and 'autoconf' again

automake 會需要 configure.ac 的配合, 所以請我們要加上 AM_INIT_AUTOMAKE, 但, 因為 autoconf 裡面並沒有相對應的 macro, 所以, 還需要 aclocal 為我們產生 aclocal.m4 提供這些 macros.

先來處理第一個問題, 這個跟原文的錯誤訊息不太一樣, 需要自行解決. 在 configure.ac 中加上 AM_INIT_AUTOMAKE

...
AM_INIT_AUTOMAKE
...

再次執行 automake, 錯誤還是一樣, 因為我們沒有給它正確的參數, 參考這頁.

雖說 AM_INIT_AUTOMAKE 需要一個 list 參數, 但這個值其實可以自動從 AC_INIT 中取得, 修改 configure.ac 中的 AC_INIT 參數後能解決這個問題. 從

AC_INIT([FULL-PACKAGE-NAME], [VERSION], [BUG-REPORT-ADDRESS])

改為下面這行, 然後再次執行 automake, 可以發現不再回報上面那個錯誤.

AC_INIT([hello], [0.1], [http://example.com])

另外, 看起來原文中提到的 AC_CONFIG_HEADER 要換成 AM_CONFIG_HEADER 這個部份在我系統上的版本不需變動.

接下來, 處理一堆 missing 的 files. 可從 /usr/share/automake-1.16/ 目錄下 copy 迥來. INSTALL 裡說明了怎麼安裝這個 package 的步驟, 會需要修改一下. COPYING 則是有關 license 的宣告, 也需要替換. 或是, 執行 automake 時加上參數讓它自動加上這些 missing 的檔案

$ automake --add-missing
Makefile.am: error: required file './NEWS' not found
Makefile.am: error: required file './README' not found
Makefile.am: error: required file './AUTHORS' not found
Makefile.am: error: required file './ChangeLog' not found

剩下的, 就只能手動產生了

$ touch NEWS README AUTHORS ChangeLog

再執行一次 automake 產生 Makefile.in

$ automake
$

因為修改了 configure.ac, 我們需要再次執行 autoconf, 但因為加了它不知道的 macros, 我們得先執行 aclocal 產生出 aclocal.m4

$ aclocal

然後再次執行 autoconf 更新 configure => ./configure => make

$ autoconf
$ ./configure 
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /usr/bin/mkdir -p
checking for gawk... no
checking for mawk... mawk
checking whether make sets $(MAKE)... yes
checking whether make supports nested variables... yes
checking for gcc... gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables... 
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
checking whether gcc understands -c and -o together... yes
checking whether make supports the include directive... yes (GNU style)
checking dependency style of gcc... gcc3
checking how to run the C preprocessor... gcc -E
checking for grep that handles long lines and -e... /usr/bin/grep
checking for egrep... /usr/bin/grep -E
checking for ANSI C header files... yes
checking for sys/types.h... yes
checking for sys/stat.h... yes
checking for stdlib.h... yes
checking for string.h... yes
checking for memory.h... yes
checking for strings.h... yes
checking for inttypes.h... yes
checking for stdint.h... yes
checking for unistd.h... yes
checking sys/time.h usability... yes
checking sys/time.h presence... yes
checking for sys/time.h... yes
checking for gettimeofday... yes
checking that generated files are newer than configure... done
configure: creating ./config.status
config.status: creating Makefile
config.status: creating config.h
config.status: executing depfiles commands
$ make
make  all-am
make[1]: Entering directory '/home/derekdai/Projects/test-autotools'
gcc -DHAVE_CONFIG_H -I.     -g -O2 -MT hello.o -MD -MP -MF .deps/hello.Tpo -c -o hello.o hello.c
mv -f .deps/hello.Tpo .deps/hello.Po
gcc  -g -O2   -o hello hello.o  
make[1]: Leaving directory '/home/derekdai/Projects/test-autotools'

這個 PDF 也不錯 Embedded Linux Conference 2016GNU Autotools: a tutorial, 裡面說到

  • configure.ac 本質上也就是 shell 的語法, 但會通過 M4 的 macro 展開過程 (成為完整的 shell script)
  • Makefile.am 其實就是 Makefile, 用的也是 Makefile 的語法, 只不過, automake 會展開及補全 (依特定的 name 或 name pattern 比對).

碰到的問題

autoconf 提供了 AC_CONFIG_FILE(), 配合 AC_SUBST() 將指定的變數在 config file (例如 ).

但, 參考了 $prefix 的變數展開時間點其實非常的晚, 且相關變數在 configure (configureconfigure.ac 本質上是 shell script) 中初始如下

# Installation directory options.
# These are left unexpanded so users can "make install exec_prefix=/foo"
# and all the variables that are supposed to be based on exec_prefix
# by default will actually change.
# Use braces instead of parens because sh, perl, etc. also accept them.
# (The list follows the same order as the GNU Coding Standards.)
bindir='${exec_prefix}/bin'
sbindir='${exec_prefix}/sbin'
libexecdir='${exec_prefix}/libexec'
datarootdir='${prefix}/share'
datadir='${datarootdir}'
sysconfdir='${prefix}/etc'
sharedstatedir='${prefix}/com'
localstatedir='${prefix}/var'
runstatedir='${localstatedir}/run'
includedir='${prefix}/include'
oldincludedir='/usr/include'
docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
infodir='${datarootdir}/info'
htmldir='${docdir}'
dvidir='${docdir}'
pdfdir='${docdir}'
psdir='${docdir}'
libdir='${exec_prefix}/lib'
localedir='${datarootdir}/locale'
mandir='${datarootdir}/man'

所以如 bindir 展開時結果會是 ${exec_prefix}/bin 而非 /usr/bin, 因為是被單引號所括起來的.

這時, 如果使用 AC_CONFIG_FILE() 並在 config file 中參考了 libexecdir, 例如 @[email protected], 那麼最終這個 config file 帶的值會是 ${exec_prefix}/libexec 而非 /usr/libexec.

並且, 因為在 make 的過程中能指定 $prefix, 所以不應在 configure 的過程中試圖展開任何參考了 $prefix 的變數 (每一次 make 的執行都可能變動 $prefix).

那麼, 如果要在產生的 config 中寫入最終展開的路徑, 而非含帶變數的路徑, 怎麼做呢? 這篇有討論到, 在 make 中通常會對變數進行遞迴展開, 並且 $prefix 最終值是在 make 運行過程中確定的, 所以, 方案之一是用 make + sed 自行替換, 且因為每次 make 的執行都可能變動 $prefix, 所以如果不保在上次的 $prefix 跟這次的進行比對的話, 就需要配合 .PHONE 讓 rule 每次都被執行, 舉個例孑.

我想用 org.freedesktop.portal.IBus.service.in 產生 org.freedesktop.portal.IBus.service

[D-BUS Service]
Name=org.freedesktop.portal.IBus
Exec=SCIM_LIBEXECDIR/scim-launcher -f ibus -e all

其中, SCIM_LIBEXECDIR 是在 configure.ac 中初始為

SCIM_LIBEXECDIR="$libdir/scim$SCIM_EPOCH"

因為 AC_CONFIG_FILES() 不可用, 我們改由在 Makefile.am 中以自訂 rule 來完成

.PHONY: gen_dbus_service

gen_dbus_service:
        sed -e "s^SCIM_LIBEXECDIR^$(SCIM_LIBEXECDIR)^" $(top_srcdir)/org.freedesktop.portal.IBus.service.in >org.freedesktop.portal.IBus.service

之所以不循 @[email protected] 的慣例是因為 Makefile.am => Makefile.in, 然後在 Makefile.inconfigure 轉換成 Makefile 時是以 AC_CONFIG_FILES() 完成的, 此時如果寫成 @[email protected], 那結果的 Makefilesed 命令中的 @[email protected] 就會被展開, 最終就沒法正確 match 到 org.freedesktop.portal.IBus.service.in 中的 @[email protected].