autotools mini howto

Die GNU autotools

Die GNU autotools sind eine Sammlung von tools, die das Erstellen von angepassten Makefiles erleichtert.
Hier gibt es ein Mini-Howto zum Umgang mit diesen tools.

Am Anfang war der c code..

Für den Anfang starten wir mit dem einfachsten source code. Ein Programm, welches nur eine Zeile Text ausgibt und nur aus einer c Datei besteht:

#include <stdlib.h>
#include <stdio.h>

int main (void) {
   printf("hello.. :-)\n");
   return 0;
}
Natürlich lässt sich so ein einfaches Beispiel direkt übersetzen, auch ohne Makefile:
gcc -o hello main.c

Wenn allerdings viele .c Dateien ins Spiel kommen oder Bibliotheken verlinkt werden müssen, dann wird es schon schwieriger.
Dann gilt es herauszufinden, wo die header Dateien der Bibliotheken liegen, wo die Bibliotheken selbst liegen,
welche Optionen der Linker dafür benötigt.
Noch schwieriger würde es mit den verschiedenen compilern, Varianten der Betriebssystem und Auswahl des Installationsortes.
Hier kommen dann Systeme wie die autotools oder cmake zur Anwendung.

Die Benutzung der autotools am Beispielprogramm hello

Wenn wir ein configure script erstellen wollen, dann benötigen wir dafür eine Datei namens configure.in
aus der das configure script erstellt wird. Dabei hilft autoscan, welches uns ein Template namens configure.scan aus unserem source code erstellt:

autoscan

Die erstellte Datei configure.scan sieht nun so aus:

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

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

# Checks for programs.
AC_PROG_CC

# Checks for libraries.

# Checks for header files.
AC_CHECK_HEADERS([stdlib.h])

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.

AC_OUTPUT

Diese Datei benennen wir nach configure.in um:

-bash-3.00# mv configure.scan configure.in
-bash-3.00# ls -al
insgesamt 12
drwxr-xr-x 2 root root   57  2. Apr 13:15 .
drwxr-xr-x 5 root root 4096  2. Apr 13:05 ..
-rw-r--r-- 1 root root    0  2. Apr 13:09 autoscan.log
-rw-r--r-- 1 root root  495  2. Apr 13:09 configure.in
-rw-r--r-- 1 root root  101  2. Apr 11:42 main.c

Jetzt wird configure.in editiert, indem

durch sinnvolle Einträge ersetzt werden.
Zusätzlich kommen die Zeilen AM_INIT_AUTOMAKE und AC_CONFIG_FILES([Makefile]) hinzu, da wir später automake benutzen wollen.

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

AC_PREREQ([2.63])
# our program is called 'hello', its version is 0.0.1;
# the address for bug reports is 'deine.emailadresse@deinedomain.de'
AC_INIT([hello], [0.0.1], [deine.emailadresse@deinedomain.de])
# prepare for automake
AM_INIT_AUTOMAKE

AC_CONFIG_SRCDIR([main.c])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_FILES([Makefile])

# Checks for programs.
# we're using the C compiler
AC_PROG_CC
# AC_PROG_CXX
# AC_PROG_OBJC,
# AM_PROG_AS
# AM_PROG_GCJ
# AM_PROG_UPC

# Checks for libraries.

# Checks for header files.
AC_CHECK_HEADERS([stdlib.h])

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.

AC_OUTPUT

Jetzt benutzen wir aclocal, um aclocal.m4 zu erstellen. aclocal scannt configure.in (wir hätten diese Datei auch configure.ac nennen können)
und schreibt die dort von uns verwendeten macros in die Datei aclocal.m4.
Jedesmal wenn configure.in erweitert wurde, muss also aclocal ausgeführt werden.

-bash-3.00# aclocal
-bash-3.00# ls -al
insgesamt 48
drwxr-xr-x 3 root root    95  2. Apr 13:37 .
drwxr-xr-x 5 root root  4096  2. Apr 13:05 ..
-rw-r--r-- 1 root root 33817  2. Apr 13:37 aclocal.m4
drwxr-xr-x 2 root root    51  2. Apr 13:37 autom4te.cache
-rw-r--r-- 1 root root     0  2. Apr 13:09 autoscan.log
-rw-r--r-- 1 root root   789  2. Apr 13:22 configure.in
-rw-r--r-- 1 root root   101  2. Apr 11:42 main.c

Als nächstes wird autoconf benutzt, um das configure script zu erstellen:

-bash-3.00# autoconf
-bash-3.00# ls -al
insgesamt 208
drwxr-xr-x 3 root root    111  2. Apr 13:39 .
drwxr-xr-x 5 root root   4096  2. Apr 13:05 ..
-rw-r--r-- 1 root root  33817  2. Apr 13:37 aclocal.m4
drwxr-xr-x 2 root root     81  2. Apr 13:39 autom4te.cache
-rw-r--r-- 1 root root      0  2. Apr 13:09 autoscan.log
-rwxr-xr-x 1 root root 163596  2. Apr 13:39 configure
-rw-r--r-- 1 root root    789  2. Apr 13:22 configure.in
-rw-r--r-- 1 root root    101  2. Apr 11:42 main.c

Viele Programme nutzen eine Datei config.h, in der definiert ist, welche header Dateien und Libraries auf dem Zielsystem vorhanden sind.
Das configure script script prüft die Datei config.h.in und ersetzt darin die gefundenen

#undef HAVE_<ID>
mit
#define HAVE<ID>
falls vorhanden. Ein Template erstellt uns autoheader aus unserer configure.in:

-bash-3.00# autoheader
-bash-3.00# ls -al
insgesamt 212
drwxr-xr-x 3 root root    129  2. Apr 13:59 .
drwxr-xr-x 5 root root   4096  2. Apr 13:05 ..
-rw-r--r-- 1 root root  33817  2. Apr 13:37 aclocal.m4
drwxr-xr-x 2 root root     81  2. Apr 13:39 autom4te.cache
-rw-r--r-- 1 root root      0  2. Apr 13:09 autoscan.log
-rw-r--r-- 1 root root   1369  2. Apr 13:44 config.h.in
-rwxr-xr-x 1 root root 163596  2. Apr 13:39 configure
-rw-r--r-- 1 root root    789  2. Apr 13:22 configure.in
-rw-r--r-- 1 root root    101  2. Apr 11:42 main.c

Die erzeugte config.h.in sieht nun so aus:

/* config.h.in.  Generated from configure.in by autoheader.  */

/* Define to 1 if you have the  header file. */
#undef HAVE_INTTYPES_H

/* Define to 1 if you have the  header file. */
#undef HAVE_MEMORY_H

/* Define to 1 if you have the  header file. */
#undef HAVE_STDINT_H

/* Define to 1 if you have the  header file. */
#undef HAVE_STDLIB_H

/* Define to 1 if you have the  header file. */
#undef HAVE_STRINGS_H

/* Define to 1 if you have the  header file. */
#undef HAVE_STRING_H

/* Define to 1 if you have the  header file. */
#undef HAVE_SYS_STAT_H

/* Define to 1 if you have the  header file. */
#undef HAVE_SYS_TYPES_H

/* Define to 1 if you have the  header file. */
#undef HAVE_UNISTD_H

/* Name of package */
#undef PACKAGE

/* Define to the address where bug reports for this package should be sent. */
#undef PACKAGE_BUGREPORT

/* Define to the full name of this package. */
#undef PACKAGE_NAME

/* Define to the full name and version of this package. */
#undef PACKAGE_STRING

/* Define to the one symbol short name of this package. */
#undef PACKAGE_TARNAME

/* Define to the version of this package. */
#undef PACKAGE_VERSION

/* Define to 1 if you have the ANSI C header files. */
#undef STDC_HEADERS

/* Version number of package */
#undef VERSION

Nachdem soweit nun fast alles automatisch erzeugt werden konnte, wird es jetzt Zeit etwas selbst zu tun.

Die Datei Makefile.am muss erstellt werden. Der wichtigste Inhalt der Datei ist, welche source code Dateien zu welchem Programm gehören.
Zeilen mit einer Raute '#' sind Kommentare.

Zusätzlich weisen wir automake an, nicht ein *.tar.gz zu packen,
sondern ein (besser packendes) *.tar.bz2 als make dist Target anzulegen:

# Makefile.am example
AUTOMAKE_OPTIONS = dist-bzip2 no-dist-gzip
bin_PROGRAMS	= hello
hello_SOURCES	= main.c

Anschließend führen wir automake aus,
um aus Makefile.am das Template für das Makefile Makefile.in zu erzeugen:

-bash-3.00# touch {NEWS,README,AUTHORS,ChangeLog}
-bash-3.00# automake --add-missing
Makefile.am: installing `./INSTALL'
Makefile.am: installing `./COPYING' using GNU General Public License v3 file
Makefile.am:     Consider adding the COPYING file to the version control system
Makefile.am:     for your code, to avoid questions about which license your project uses.
-bash-3.00# ls -al
insgesamt 240
drwxr-xr-x 3 root root   4096  2. Apr 14:16 .
drwxr-xr-x 5 root root   4096  2. Apr 13:05 ..
-rw-r--r-- 1 root root  33817  2. Apr 13:37 aclocal.m4
-rw-r--r-- 1 root root      0  2. Apr 14:15 AUTHORS
drwxr-xr-x 2 root root     81  2. Apr 13:39 autom4te.cache
-rw-r--r-- 1 root root      0  2. Apr 13:09 autoscan.log
-rw-r--r-- 1 root root      0  2. Apr 14:15 ChangeLog
-rw-r--r-- 1 root root   1369  2. Apr 13:44 config.h.in
-rwxr-xr-x 1 root root 163596  2. Apr 13:39 configure
-rw-r--r-- 1 root root    789  2. Apr 13:22 configure.in
lrwxrwxrwx 1 root root     33  2. Apr 14:16 COPYING -> /usr/share/automake-1.10b/COPYING
lrwxrwxrwx 1 root root     33  2. Apr 14:16 INSTALL -> /usr/share/automake-1.10b/INSTALL
-rw-r--r-- 1 root root    101  2. Apr 11:42 main.c
-rwxr--r-- 1 root root    109  2. Apr 14:09 Makefile.am
-rw-r--r-- 1 root root  19400  2. Apr 14:16 Makefile.in
-rw-r--r-- 1 root root      0  2. Apr 14:15 NEWS
-rw-r--r-- 1 root root      0  2. Apr 14:15 README

Das Paket ist nun fertig vorbereitet, Zeit für einen Test.

-bash-3.00# ./configure --prefix=/usr
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /bin/mkdir -p
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking for gcc... gcc
checking for C compiler default output file name... a.out
checking whether the C compiler works... yes
checking whether we are cross compiling... no
checking for suffix of executables...
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 for style of include used by make... GNU
checking dependency style of gcc... gcc3
checking how to run the C preprocessor... gcc -E
checking for grep that handles long lines and -e... /bin/grep
checking for egrep... /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 for stdlib.h... (cached) yes
configure: creating ./config.status
config.status: creating Makefile
config.status: creating config.h
config.status: executing depfiles commands
-bash-3.00# make
make  all-am
make[1]: Entering directory `/usr/src/autotools_howto'
gcc -DHAVE_CONFIG_H -I.     -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c
mv -f .deps/main.Tpo .deps/main.Po
gcc  -g -O2   -o hello main.o
make[1]: Leaving directory `/usr/src/autotools_howto'

Perfekt. Unser source code compiliert ohne Probleme und die Datei config.h wird ebenso erstellt.
Aber wie macht man daraus nun ein Paket, welches man zum Download anbieten kann? Dafür gibt es das make Target 'make dist'.

-bash-3.00# make dist
{ test ! -d "hello-0.0.1" || { find "hello-0.0.1" -type d ! -perm -200 -exec chmod u+w {} ';' && rm -fr "hello-0.0.1"; }; }
test -d "hello-0.0.1" || mkdir "hello-0.0.1"
find "hello-0.0.1" -type d ! -perm -777 -exec chmod a+rwx {} \; -o \
          ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \
          ! -type d ! -perm -400 -exec chmod a+r {} \; -o \
          ! -type d ! -perm -444 -exec /bin/sh /usr/src/install-sh -c -m a+r {} {} \; \
        || chmod -R a+r "hello-0.0.1"
tardir=hello-0.0.1 && /bin/sh /usr/src/missing --run tar chof - "$tardir" | bzip2 -9 -c >hello-0.0.1.tar.bz2
{ test ! -d "hello-0.0.1" || { find "hello-0.0.1" -type d ! -perm -200 -exec chmod u+w {} ';' && rm -fr "hello-0.0.1"; }; }

Nach dem 'make dist' findet sich ein Paket hello-0.0.1.tar.bz2 im Ordner. Hier endet das erste Beispiel.

Die Abfolge und Abhängigkeiten im Überblick

Ein zweites Beispiel anhand eines bestehenden Source Pakets ohne configure script

Das tool 'scan' aus dem linuxtv-dvb-apps Paket hat kein configure script und ist deswegen ideal als zweites Beispiel.
Das original Makefile wird nicht benötigt und wird deswegen gelöscht.

wget http://linuxtv.org/hg/dvb-apps/archive/tip.tar.bz2
tar xfj tip.tar.bz2
cd dvb-apps-<VERSION>/util/scan
rm Makefile

Als erstes wird autoscan aufgerufen und configure.in erzeugt.

-bash-3.00# autoscan -I atsc
-bash-3.00# mv configure.scan configure.in 

Nach dem Editieren sieht configure.in so aus:

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

AC_PREREQ([2.63])
AC_INIT([scan], [cvs-7de0663facd9], [http://www.linuxtv.org])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([scan.c])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_FILES([Makefile])

# Checks for programs.
AC_PROG_CC

# Checks for libraries.

# Checks for header files.
AC_CHECK_HEADERS([fcntl.h stdint.h stdlib.h string.h sys/ioctl.h unistd.h])

# Checks for typedefs, structures, and compiler characteristics.
AC_C_INLINE
AC_TYPE_UINT16_T
AC_TYPE_UINT32_T
AC_TYPE_UINT8_T

# Checks for library functions.
AC_FUNC_ERROR_AT_LINE
AC_FUNC_MALLOC
AC_CHECK_FUNCS([memset strcasecmp strdup strtoul])

AC_OUTPUT

Wie im ersten Beispiel werden aclocal, autoheader und autoconf ausgeführt.

-bash-3.00# aclocal
-bash-3.00# autoheader
-bash-3.00# autoconf

Wir benötigen die Dateien COPYING, README, INSTALL,..
COPYING wird zur originalen Datei verlinkt. INSTALL wird neu erzeugt,
da die Installation mit configure script etwas anders verläuft als mit dem originalen Makefile.

-bash-3.00# ln -s ../../COPYING .
-bash-3.00# touch {NEWS,README,AUTHORS,ChangeLog}

Natürlich wird auch wieder ein Makefile.am benötigt:

-bash-3.00# export PACKAGE="scan"
-bash-3.00# export PACKAGE_OWNER="http://www.linuxtv.org"
-bash-3.00# SRCFILES=$(ls *.{c,h})
-bash-3.00# echo "# Makefile.am for $PACKAGE" >> Makefile.am
-bash-3.00# echo "# please send bug reports to $PACKAGE_OWNER" >> Makefile.am
-bash-3.00# echo "#" >> Makefile.am
-bash-3.00# echo "#" >> Makefile.am
-bash-3.00# echo "AUTOMAKE_OPTIONS	= dist-bzip2 no-dist-gzip" >> Makefile.am
-bash-3.00# echo    "bin_PROGRAMS	= $PACKAGE" >> Makefile.am
-bash-3.00# echo -n $PACKAGE"_SOURCES	="  >> Makefile.am 
-bash-3.00# for source in $SRCFILES; do
-bash-3.00#    echo -n " $source" >> Makefile.am
-bash-3.00# done
-bash-3.00# echo "" >> Makefile.am

Unser fertiges Makefile.am wird noch um atsc_psip_section.(c/h) erweitert, die erst vom Makefile erstellt werden.

-bash-3.00# more Makefile.am
# Makefile.am for scan
# please send bug reports to http://www.linuxtv.org
#
#
AUTOMAKE_OPTIONS= dist-bzip2 no-dist-gzip
bin_PROGRAMS= atsc_psip_section.h scan
scan_SOURCES= diseqc.c diseqc.h dump-vdr.c dump-vdr.h dump-zap.c dump-zap.h list.h lnb.c lnb.h scan.c scan.h section.c section.h
scan_SOURCES+=atsc_psip_section.c 

atsc_psip_section.h:
	perl section_generate.pl atsc_psip_section.pl
	make


CLEANFILES = atsc_psip_section.c atsc_psip_section.h

HINWEIS: Es gibt diesmal zwei Makefile Targets, atsc_psip_section.h und scan.
Zuerst wird

perl section_generate.pl atsc_psip_section.pl
aufgerufen, um atsc_psip_section.(c,h) zu generieren.
Danach ruft das Makefile sich selbst auf, beim zweiten Aufruf wird jedoch das erste Target atsc_psip_section.h ausgelassen,
weil diese Datei bereits vorhanden und aktuell ist.

Nachdem die Vorarbeiten fertig sind, kann mit automake Makefile.in erzeugt werden.
Die Option --add-missing kopiert bzw. verlinkt fehlende Dateien wie INSTALL und COPYING (default: GPL).

-bash-3.00# automake --add-missing
configure.in:6: installing `./install-sh'
configure.in:6: installing `./missing'
Makefile.am: installing `./INSTALL'
Makefile.am: installing `./depcomp'

Als letztes nun noch Test und Erzeugung des Pakets.

-bash-3.00# ./configure
-bash-3.00# make
make  all-am
make[1]: Entering directory `/usr/src/autotools_test/test/dvb-apps-7de0663facd9/util/scan'
perl section_generate.pl atsc_psip_section.pl
make
make[2]: Entering directory `/usr/src/autotools_test/test/dvb-apps-7de0663facd9/util/scan'
make  all-am
make[3]: Entering directory `/usr/src/autotools_test/test/dvb-apps-7de0663facd9/util/scan'
gcc -DHAVE_CONFIG_H -I.     -g -O2 -MT diseqc.o -MD -MP -MF .deps/diseqc.Tpo -c -o diseqc.o diseqc.c
mv -f .deps/diseqc.Tpo .deps/diseqc.Po
gcc -DHAVE_CONFIG_H -I.     -g -O2 -MT dump-vdr.o -MD -MP -MF .deps/dump-vdr.Tpo -c -o dump-vdr.o dump-vdr.c
mv -f .deps/dump-vdr.Tpo .deps/dump-vdr.Po
gcc -DHAVE_CONFIG_H -I.     -g -O2 -MT dump-zap.o -MD -MP -MF .deps/dump-zap.Tpo -c -o dump-zap.o dump-zap.c
mv -f .deps/dump-zap.Tpo .deps/dump-zap.Po
gcc -DHAVE_CONFIG_H -I.     -g -O2 -MT lnb.o -MD -MP -MF .deps/lnb.Tpo -c -o lnb.o lnb.c
mv -f .deps/lnb.Tpo .deps/lnb.Po
gcc -DHAVE_CONFIG_H -I.     -g -O2 -MT scan.o -MD -MP -MF .deps/scan.Tpo -c -o scan.o scan.c
mv -f .deps/scan.Tpo .deps/scan.Po
gcc -DHAVE_CONFIG_H -I.     -g -O2 -MT section.o -MD -MP -MF .deps/section.Tpo -c -o section.o section.c
mv -f .deps/section.Tpo .deps/section.Po
gcc -DHAVE_CONFIG_H -I.     -g -O2 -MT atsc_psip_section.o -MD -MP -MF .deps/atsc_psip_section.Tpo -c -o atsc_psip_section.o atsc_psip_section.c
mv -f .deps/atsc_psip_section.Tpo .deps/atsc_psip_section.Po
gcc  -g -O2   -o scan diseqc.o dump-vdr.o dump-zap.o lnb.o scan.o section.o atsc_psip_section.o
make[3]: Leaving directory `/usr/src/autotools_test/test/dvb-apps-7de0663facd9/util/scan'
make[2]: Leaving directory `/usr/src/autotools_test/test/dvb-apps-7de0663facd9/util/scan'
make[1]: Leaving directory `/usr/src/autotools_test/test/dvb-apps-7de0663facd9/util/scan'
-bash-3.00# make dist
{ test ! -d "scan-cvs-7de0663facd9" || { find "scan-cvs-7de0663facd9" -type d ! -perm -200 -exec chmod u+w {} ';' && rm -fr "scan-cvs-7de0663facd9"; }; }
test -d "scan-cvs-7de0663facd9" || mkdir "scan-cvs-7de0663facd9"
find "scan-cvs-7de0663facd9" -type d ! -perm -777 -exec chmod a+rwx {} \; -o \
          ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \
          ! -type d ! -perm -400 -exec chmod a+r {} \; -o \
          ! -type d ! -perm -444 -exec /bin/sh /usr/src/autotools_test/test/dvb-apps-7de0663facd9/util/scan/install-sh -c -m a+r {} {} \; \
        || chmod -R a+r "scan-cvs-7de0663facd9"
tardir=scan-cvs-7de0663facd9 && /bin/sh /usr/src/autotools_test/test/dvb-apps-7de0663facd9/util/scan/missing --run tar chof - "$tardir" | bzip2 -9 -c >scan-cvs-7de0663facd9.tar.bz2
{ test ! -d "scan-cvs-7de0663facd9" || { find "scan-cvs-7de0663facd9" -type d ! -perm -200 -exec chmod u+w {} ';' && rm -fr "scan-cvs-7de0663facd9"; }; }

Links zum Thema