Dieser Artikel wurde von ProgChild erstellt.


Folgende Themen werden von diesem Artikel berührt:


Druckversion des Artikels


1 Einleitung
Der Begriff GNU Autotools fasst eine Gruppe an Programmen zusammen, mit denen sich unter verschiedenen Unix-Derrivaten Programme kompilieren lassen. Dabei übernehmen sie die Konfiguration, die Überprüfung auf Abhängigkeiten, und das anschließende Build. Zu den GNU Autotools zählen normalerweise GNU Make, Autoconf, GNU libtool und GNU Automake

Voraussetzungen, um den Artikel zu verstehen, sind einfache C-Kentnisse und ein wenig Erfahrung mit der Shell. Natürlich sollten die oben genannten Programme auf einem lauffähigen System installiert sein, außerdem wäre es nicht schlecht, weil ich es in einem Beispiel verwende, GTK+ installiert zu haben.

2 Einfaches Kompilieren


2.1 Ein einfaches Programm


Wir erstellen ein simples C Programm, das aus mehreren Dateien besteht.

C++:
1
2
3
4
5
6
7
8
/* main.c */
#include "greeting.h"
 
int main (int argc, char* argv[]) {
    greeting_do ();
 
    return 0;
}

C++:
1
2
3
4
5
6
7
8
/* greeting.c */
#include <stdio.h>

#include "greeting.h"

 
void greeting_do (void) {
    printf("Hello,\nHow are you?\n");
}

C++:
1
2
3
4
5
6
7
8
/* greeting.h */

#ifndef TOOLSPROG_GREETING_H

#define TOOLSPROG_GREETING_H
 
void greeting_do (void);

#endif

2.2 build.sh
Das Programm wollen wir nun übersetzen. Die einfachste Möglichkeit ist, sich eine Shell zu greifen und den Compiler von Hand aufzurufen. Da wir nicht immer alles neu eintippen wollen, schreiben wir alle Befehle in ein Shell-Script. Zum Kompilieren verwende ich hier in den Beispielen den GCC. Den Compiler gibt es für eigentlich jede Plattform, darum ist er für unsere Demonstrationszwecke gut geeignet.

Code:
#!/bin/sh
# build.sh
gcc -o greeting.o -c greeting.c
gcc -o main.o -c main.c
gcc -o prog1 main.o greeting.o

Das Script machen wir ausführbar und rufen es auf. Das geht mit folgenden Shell Befehlen.
Code:
$ chmod a+x build.sh
$ ./build.sh



Nach kurzer Zeit ist unser Programm kompiliert und wir können es benutzten.
Code:
$ ./prog1
Hello,
How are you?



Unser simples Buildsystem besteht nun aus einem Shell script, das unser Programm erzeugt. Als erstes werden die Dateien greeting.c und main.c kompiliert. Danach werden die erstellten Object Dateien zum Programm prog1 zusammen gelinkt.

3 GNU Make


3.1 Warum Make?
Unser Programm wird anstandslos kompiliert und läuft wunderbar. Naja... es tut halt das, was es soll. Warum sollte man jetzt also noch mehr Zeit investieren, um ein komplexeres Build System zu benutzen, als unsere build.sh?

Stellen wir uns einfach mal vor, unser Programm würde nicht aus drei, sondern aus 300 Dateien bestehen. Wir haben einen Fehler in der Datei greeting.c gefunden und korrigiert. Jetzt wollen wir das Programm testen. Unser simples Script würde alle 300 Dateien neu kompilieren und wir müssten eine halbe Stunde warten, bevor wir das Programm testen könnten. Es würde vollkommen ausreichen, nur die Datei greeting.c zu kompilieren und dann Alles zusammenzulinken, aber unser Script ist einfach zu dumm dafür. Sicherlich könnten wir dem Script ein wenig mehr Intelligenz einhauchen, aber für unsere Zwecke gibt es schon eine Lösung, nämlich GNU Make...

3.2 Aufbau eines Makefiles
Make generiert die Dateien neu, deren Quellcode sich geändert hat. Dazu müssen wir eine Zieldatei und ihre Quelldateien angeben. Wir können so viele Zieldateien in einem Makefile eintragen, wie wir möchten. Die Struktur dafür sieht so aus:

Code:
Zieldatei: Quelldatei1 Quelldatei2 ...
    Anweisung1
    Anweisung2
    ...

Beispiel:
Code:
hello: hello.c
    gcc -o hello hello.c



Die Zieldatei muss immer am Anfang einer Zeile stehen. Danach folgt der Doppelpunkt gefolgt von den Quelldateien. Hier müssen dann alle Dateien angegeben werden, die den Inhalt der Zieldatei beeinflussen. Zum Beispiel sollten, zusätzlich zu der eigentlichen Quellcode Datei, alle eingebundenen Headerdateien, die man selbst ändert angegeben werden.

In den folgenden Zeilen werden alle Befehle aufgeführt, die benötigt werden, um die Zieldatei zu erstellen. Alle Befehle müssen mit einem oder mehreren Tabs eingerückt werden. Wichtig dabei ist, dass es echte Tabs und keine Leerzeichen sind!!!

Bei der Zieldatei muss es sich übrigens nicht immer um eine wirkliche Datei handeln. Folgender Abschnitt in einem Makefile kann dazu genutzt werden, um das Verzeichnis nach dem Build wieder aufzuräumen.

Code:
clean:
    rm -f hello

3.3 Unser Makefile
Erstellen wir nun das Makefile für unser Programm und speichern es unter dem Namen makefile ab.

Code:
1
2
3
4
5
6
7
8
9
10
11
12
# makefile
prog2: main.o greeting.o
    gcc -o prog2 main.o greeting.o
 
main.o: main.c greeting.h
    gcc -o main.o -c main.c
 
greeting.o: greeting.c greeting.h
    gcc -o greeting.o -c greeting.c
 
clean:
    rm -f prog2 main.o greeting.o

Der Aufruf von Make lautet nun wie folgt
Code:
$ make -f makefile Zieldatei



Wenn die Datei, wie in unserem Fall, Makefile oder makefile heißt, kann man auf die Angabe des Dateinamens auch verzichten. Make benutzt dann die Datei aus dem aktuellen Verzeichnis. Lässt man die Zieldatei weg, baut Make die erste Zieldatei, die es finden kann. Praktisch bedeutet das, dass folgender Befehl unser Programm erzeugt.

Code:
$ make
gcc -o main.o -c main.c
gcc -o greeting.o -c greeting.c
gcc -o prog2 main.o greeting.o

Und folgender Befehl räumt alles wieder auf.

Code:
$ make clean
rm -f prog2 main.o greeting.o

3.4 Variablen
Das ist schonmal ein großer Fortschritt. Ändern wir die Datei main.c werden nur die Dateien main.o und prog2 neu erzeugt. Ändern wir aber die Datei greeting.h wird auch noch die Datei greeting.o erzeugt. Make ist in der Lage, die Abhängigkeiten der einzelnen Dateien festzustellen und ruft die Befehle in der richtigen Reihenfolge auf.

Unser Makefile ist allerdings noch reichlich unflexibel. Wenn wir zum Beispiel den Namen der Executable ändern möchten, oder den Aufruf des Compilers, müssen wir durch das ganze Makefile wandern und den entsprechenden Text ändern. In GNU Make gibt es dafür eine einfach Lösung: Variablen. Haben wir eine Variable deklariert, können wir sie einfach überall einfügen und müssen nur an einer Stelle das Makefile ändern. Die Deklaration sieht folgendermaßen aus.
Code:
VARIABLEN_NAME = Variblen Inhalt



Einfügen können wir den Variableninhalt an beliebiger Stelle im Makefile auf folgende Weise.
Code:
$(VARIABLEN_NAME)



Schreiben wir unser Makefile also auf folgende Weise um.
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# makefile
BIN = prog3
OBJS = main.o greeting.o
CC = gcc
CFLAGS = -O2
LDFLAGS = -s
 
$(BIN): $(OBJS)
    $(CC) $(LDFLAGS) -o $(BIN) $(OBJS)
 
main.o: main.c greeting.h
    $(CC) $(CFLAGS) -o main.o -c main.c
 
greeting.o: greeting.c greeting.h
    $(CC) $(CFLAGS) -o greeting.o -c greeting.c
 
clean:
    rm -f $(BIN) $(OBJS)



Testen wir also nun unser neues Makefile.
Code:
$ make
gcc -O2 -o main.o -c main.c
gcc -O2 -o greeting.o -c greeting.c
gcc -s -o prog3 main.o greeting.o



Hinzugekommen sind noch die Compiler Argumente -O2 und -s . Diese Argumente sorgen dafür, dass der Compiler das Programm optimiert und die Debugging Symbole entfernt.

GNU Make kann noch viel mehr. Zum Beispiel if-Bedingungen. Wer mehr über GNU Make wissen will, kann einfach mal in die Hilfe oder ins Internet schaun.
Code:
$ info make



4 GNU Autoconf


4.1 Portabilität durch Autoconf
Mit dem bis jetzt gesammelten Wissen lässt sich unser Programm schon unter den meisten Linux Distributionen übersetzen. Aber was würde zum Beispiel passieren, wenn auf unserem Zielsystem der GCC nicht installiert ist? Oder wie sieht es aus, wenn wir eine bestimmte Header-Datei einbinden, die auf unterschiedlichen Systemen in unterschiedlichen Verzeichnissen liegt? Wir müssten für jedes Betriebssystem ein eigenes Makefile erstellen. Früher oder später kommt man also zu dem Schluss, dass eine generelle Lösung her muss. Und hier kommt Autoconf ins Spiel.

4.2 Funktionsweise
Wie kann uns also Autoconf bei unserem Problem helfen? Zuallererst benennen wir die Datei makefile in makefile.in um. In diese Datei fügen wir spezielle Variablen ein, die Autoconf dann später ersetzt und uns eine gültige makefile generiert. Dafür erstellt Autoconf ein Shell Script, das auf dem Zielsystem aufgerufen wird und die Aufgabe erledigt.

Als weiteres Feature erstellt uns Autoconf eine Header Datei config.h, in der, je nach Systemkonfiguration, bestimmte Makros definiert sind oder nicht.

Die Aufgabe für uns ist jetzt also, eine configure.in Datei zu erstellen, mit deren Hilfe das configure Shell Script erstellt wird. Außerdem müssen wir die Datei makefile.in so anpassen, dass es mit Autoconf zusammen arbeitet.

4.3 M4
Noch eine kleine Information zur Funktionsweise von Autoconf. Autoconf basiert auf GNU M4. M4 ist ein Makro Prozessor. Unsere configure.in ist also ein M4 Script. Praktisch bedeutet das, dass die configure.in schon unser Shell Script ist. Nur dass bestimmte Makros durch anderen Script Code noch ersetzt werden, bevor das Script zum Einsatzt kommt. In unserer configure.in können wir also zusätzlich zu den Autoconf Makros noch alle Shell Befehle verwenden.

Makros haben die folgende Form.
Code:
MAKRO(Arg1, Arg2, ... , ArgN)



Sollten wir als Argument einen längeren Text benutzen wollen, sollten wir diesen in eckige Klammern setzen. Wenn wir nämlich in diesem Text ein Komma benutzen, dann würde M4 dieses Komma als Ende des Arguments interpretieren, was wir nicht wollen. Sollten wir zusätzlich Makronamen in unserem Text verwenden und nicht wollen, dass diese durch den Inhalt des Makros ersetzt werden, müssen wir doppelte eckige Klammern verwenden.

Beispiel:
Code:
MAKRO(Argument, [Ein langes Argument], [[MAKRO ist ein Makro]])



4.4 Configure
So viel zur Theorie. Jetzt kommt die praktische Arbeit. Als erstes passen wir unser Makefile an. Dafür fügen wir die Autoconf Variablen der folgenden Form ein.
Code:
@VARIABLEN_NAME@



Nun sieht unsere makefile.in so aus:
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# makefile
BIN = prog4
OBJS = main.o greeting.o
CC = @CC@
CFLAGS = @CFLAGS@ @DEFS@
LDFLAGS = @LDFLAGS@ @LIBS@
 
$(BIN): $(OBJS)
    $(CC) $(LDFLAGS) -o $(BIN) $(OBJS)
 
main.o: main.c greeting.h
    $(CC) $(CFLAGS) -o main.o -c main.c
 
greeting.o: greeting.c greeting.h
    $(CC) $(CFLAGS) -o greeting.o -c greeting.c
 
clean:
    rm -f $(BIN) $(OBJS)



Als nächstes öffnen wir eine Shell, wechseln in unser Programmverzeichnis und starten das Programm autoscan .
Code:
$ autoscan



Jetzt erscheint in dem Verzeichnis die Datei configure.scan. Diese Datei enthält das Grundgerüst für unser Autoconf Script und sollte, wie folgt, aussehen.

Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#                                               -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
 
AC_PREREQ(2.59)
AC_INIT(FULL-PACKAGE-NAME, VERSION, BUG-REPORT-ADDRESS)
AC_CONFIG_SRCDIR([greeting.c])
AC_CONFIG_HEADER([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

Wir bennenen die Datei in configure.in um und bearbeiten sie, wie folgt.

Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
dnl configure.in
AC_PREREQ(2.59)
AC_INIT(Prog4, 1.0, noreply@mail.com)
AC_CONFIG_SRCDIR([main.c])
AC_CONFIG_HEADER([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

In Zeile (2) legen wir fest, welche Autoconf-Version mindestens benötigt wird, um aus der configure.in die Datei configure zu erstellen. Zeile (3) initialisiert Autoconf und legt ein paar generelle Informationen, wie Programmname und -version fest. Zeile (8) sucht nach dem auf dem System installierten C Compiler. In Zeile (18) werden die zu erstellenden Dateien festgelegt und in Zeile (19) werden die Dateien dann endlich erzeugt.

Erstellen wir nun endlich unser Script! Dazu sind folgende Befehle nötig.
Code:
$ aclocal
$ autoheader
$ autoconf



Als erstes werden die benutzten Autoconf Makros im aktuellen Verzeichnis zwischengespeichert. Dann erstellen wir die Datei config.h.in . Zu guter Letzt erstellen wir das Script configure .

Mit folgendem Befehl erstellen wir nun unser Makefile.
Code:
$ ./configure



Jetzt können wir, wie gewohnt Make aufrufen.
Code:
$ make
gcc -g -O2 -o main.o -c main.c
gcc -g -O2 -o greeting.o -c greeting.c
gcc  -o prog4 main.o greeting.o



4.5 The Power of Autoconf
Ziehen wir doch ein wenig Nutzen aus unserem bisherigen Wissen. Wir wollen ein Programm schreiben, das uns den Sinus einer bestimmten Zahl anzeigt. Des Weiteren wollen wir die Information mit GTK+ anzeigen, wenn es installiert ist. Beginnen wir mit der configure.in .

Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
dnl configure.in
AC_PREREQ(2.59)
AC_INIT(Prog5, 1.0, noreply@mail.com)
AC_CONFIG_SRCDIR([main.c])
AC_CONFIG_HEADER([config.h])
 
# Checks for programs.
AC_PROG_CC
PKG_PROG_PKG_CONFIG([0.14.0])
 
# Checks for libraries.
AC_CHECK_LIB(m, sinf, , AC_MSG_ERROR([Math lib not found]))
PKG_CHECK_MODULES(GTK,
                  [gtk+-2.0 >= 2.4.0],
                  [AC_DEFINE([HAVE_GTK],,[GTK+ is supported])],
                  [AC_MSG_RESULT(no)])
LIBS="$LIBS $GTK_LIBS"
CFLAGS="$CFLAGS $GTK_CFLAGS"
 
# Checks for header files.
AC_CHECK_HEADER([math.h],,[AC_MSG_ERROR([math.h not found])])
 
# Checks for typedefs, structures, and compiler characteristics.
 
# Checks for library functions.
 
AC_CONFIG_FILES([makefile])
AC_OUTPUT

Schauen wir uns an, was da im Detail passiert.

Code:
AC_CHECK_LIB(m, sinf, , AC_MSG_ERROR([Math lib not found]))

Wir überprüfen ob sich die Funktion sinf in der Library m befindet.

Wenn die Library gefunden wird, wird der Variable LIBS die Bibliothek m hinzugefügt.

Sollte die Library nicht gefunden werden, geben wir mit dem Makro AC_MSG_ERROR eine Fehlermeldung aus und beenden das Script.

Code:
AC_CHECK_HEADER([math.h],,[AC_MSG_ERROR([math.h not found])])

Wir überprüfen, ob auf dem System die Datei math.h vorhanden ist. Wenn wir die Datei nicht finden, beenden wir das Script mit einer Fehlermeldung.

Code:
PKG_PROG_PKG_CONFIG([0.14.0])

Nun müssen wir GTK+ konfigurieren. Dazu benutzen wir das Programm pkg-config. Als erstes testen wir, dass pkg-config in einer Version größer als 0.14 installiert ist.

Code:
PKG_CHECK_MODULES(GTK,
                  [gtk+-2.0 >= 2.4.0],
                  [AC_DEFINE([HAVE_GTK],,[GTK+ is supported])],
                  [AC_MSG_RESULT(no)])

Hier testen wir mit Hilfe von pkg-config, ob wir GTK+ verwenden können. Als erstes legen wir fest, dass allen Konfigurationsvariablen das Prefix "`GTK"' vorangestellt wird.

Dann überprüfen wir, ob GTK+ in einer Version größer als 2.4 vorhanden ist. Ist dies der Fall, definieren wir in der config.h , den Makro HAVE_GTK , andernfalls geben wir den Text "`no"' aus.

Code:
LIBS="$LIBS $GTK_LIBS"
CFLAGS="$CFLAGS $GTK_CFLAGS"

Hier werden den normalen Konfigurations-Variablen die gerade ermittelten Konfigurations-Daten hinzugefügt. Beachtet das vorher festgelegte Prefix "`GTK"'.

Erstellen wir nun unser Programm.
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/* main.c */
#include <stdio.h>
#include <math.h>

#ifdef HAVE_CONFIG_H

#include "config.h"
#endif

#ifdef HAVE_GTK

#include <gtk/gtk.h>
#endif
 
void sin_info (float rad) {
    float val;
    val = sinf (rad); /* berechne den sinus von rad */

#ifdef HAVE_GTK

    {
    GtkWidget* dlg;
    dlg = gtk_message_dialog_new (NULL,
                                  GTK_DIALOG_MODAL,
                                  GTK_MESSAGE_INFO,
                                  GTK_BUTTONS_OK,
                                  "sin(%f) = %f", rad, val);  
    gtk_dialog_run (GTK_DIALOG (dlg));
    gtk_widget_destroy (dlg);
    }
#else
    printf("sin(%f) = %f\n", rad, val);
#endif
}
 
int main (int argc, char* argv[]) {
#ifdef HAVE_GTK
    gtk_init (&argc, &argv);
#endif
 
    sin_info (0.5 * 3.141593);
 
    return 0;
}



Ich schätze, das Programm ist selbsterklärend. Jeder sollte nun auch in der Lage sein, das Makefile dafür selber zu schreiben. Also Kompilieren...

Code:
$ aclocal
$ autoheader
$ autoconf
$ ./configure
[...]
$ make
[...]

... und testen...
Code:
./prog5



5 GNU Automake


5.1 Makefiles aus dem Automaten
Eigentlich haben wir ja nun alles, was wir brauchen. Wir können unsere Programm ohne viel Aufwand auf vielen Betriebssystemen zum Laufen bringen. Warum sollte man jetzt noch mehr lernen? Stellen wir uns nochmal vor, wir hätten ein Programm mit 300 Dateien. Hierfür ein Makefile zu schreiben und auch noch dafür zu sorgen, dass alle Abhängigkeiten richtig eingehalten werden, ist ganz schön lästig. Wer möchte bitte alle Dateien eines Programms öffnen, um zu schauen, welche Headerdateien diese benutzen?

Wir kommen also zu dem Schluss, dass es nicht schlecht wäre, wenn uns jemand diese Arbeit abnehmen würde. Unser Programm der Wahl ist Automake.

5.2 Makefile.am
Nehmen wir also wieder unserer einfaches Beispiel vom Anfang. Wir erstellen nun eine Datei Makefile.am , aus der unsere Datei Makefile.in generiert wird.

Code:
# Makefile.am
bin_PROGRAMS = prog6
prog6_SOURCES = main.c greeting.c greeting.h

Das sieht doch ziemlich einfach aus. In Zeile (1) legen wir fest, wie die Programme heißen, die wir erstellen wollen. In Zeile (2) Teilen wir Automake mit, welche Quelldateien zu unserem Programm gehören.

Damit wir Automake nutzen können, müssen wir die Datei configure.in, wie folgt, anpassen.
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
dnl configure.in
AC_PREREQ(2.59)
AC_INIT(Prog6, 1.0, noreply@mail.com)
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([main.c])
AC_CONFIG_HEADER([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



Automake benötigt noch zusätzliche Scripte und Dateien, um arbeiten zu können. Legen wir diese also an.

Code:
1
2
3
4
5
6
7
8
9
10
$ aclocal
$ autoheader
$ touch NEWS README AUTHORS ChangeLog
$ automake --add-missing
configure.in: installing `./install-sh'
configure.in: installing `./missing'
Makefile.am: installing `./INSTALL'
Makefile.am: installing `./COPYING'
Makefile.am: installing `./depcomp'
$ autoconf

Jetzt können wir unser Programm einfach kompilieren und testen.
Code:
$ ./configure
[...]
$ make
[...]
$ ./prog6
Hello,
How are you?



Zum aufräumen können wir einfach folgendes aufrufen.
Code:
$ make clean



Das ist allerdings nicht das einzige spezial Target, dass uns automake generiert. Hier sind die wichtigsten:

Code:
clean       Verzeichnis aufräumen
distclean   wie clean, nur gründlicher
install     Programm installieren
dist        Tarball mit dem Quelltext erstellen

5.3 Unterverzeichnisse
So langsam wird's unübersichtlich in unserem Verzeichnis. Autoconf und Automake haben so viele Dateien angelegt, dass wir schon Mühe haben, unsere Quellcode Dateien zu finden. Darum wollen wir diese Dateien jetzt in das Unterverzeichnis src verschieben.

Wir erstellen also das Verzeichnis src und verschieben die Dateien Makefile.am , main.c , greeting.c und greeting.h hinein.

Jetzt erstellen wir im Hauptverzeichnis eine neue Makefile.am .
Code:
# Makefile.am
SUBDIRS = src



In der Datei geben wir einfach alle Unterverzeichnisse an, in denen weitere Makefiles liegen.

Da unser configure Script auch die Datei Makefile im Verzeichnis src erzeugen soll, müssen wir unsere Autoconf Datei, wie folgt, anpassen.
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
dnl configure.in
AC_PREREQ(2.59)
AC_INIT(Prog7, 1.0, noreply@mail.com)
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([src/main.c])
AC_CONFIG_HEADER([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
                 src/Makefile])
AC_OUTPUT



Das Programm erstellen wir mit den üblichen Kommandos.

Code:
$ autoconf
$ automake
$ ./configure
[...]
$ make

5.4 Anmerkung
Noch eine kleine Ergänzung. Automake erstellt die Makefiles so, dass es in der Regel reicht, nur die Makefiles aufzurufen, wenn sie einmal erstellt wurden.
Code:
$ make



Das bedeutet praktisch, dass das Makefile sich selber neu generiert, wenn die Dateien configure.in oder Makefile.am geändert wurden.

Außerdem ist es nicht nötig, dass auf dem System, auf dem das Programm kompiliert werden soll, Autoconf und Automake vorhanden sind. Sind erstmal die Dateien configure und Makefile.in erstellt, wird nur noch Make benötigt.

6 Libraries


6.1 Einleitung
Wir haben schon so einiges geschafft. Nachdem es im letzten Kapitel nochmal einfacher wurde, schauen wir uns jetzt noch ein etwas komplexeres Thema an. Nämlich Bibliotheken oder Libraries. Unser Ziel ist es, alle Funktionen aus der Datei greeting.c in eine Library auszulagern.

6.2 Libtool
Zum Erstellen der Libraries werden wir Libtool verwenden. Aber warum zum Teufel brauchen wir schon wieder ein neues Tool? Naja, wenn wir statische Libraries erstellen, ist das meistens kein Problem. Da hängt es lediglich vom Compiler ab, wie die Dateien übersetzt und gelinkt werden müssen. Aber wenn wir dynamische Libraries{Dynamische Libraries haben meistens die Endung .so} verwenden, wird's schwierig, denn nun hängt es vom Compiler und vom Betriebssystem ab, wie die Dateien kompiliert werden müssen. Damit wir nicht für jedes Betriebssystem ein eigenes Makefile schreiben müssen, lassen wir uns von Libtool helfen.

Würden wir unser Programm von Hand übersetzen, würde das mit der Hilfe von Libtool, wie folgt, aussehen.
Code:
1
2
3
4
5
6
7
8
#!/bin/sh
# build.sh
libtool --mode=compile gcc -o greeting.o -c greeting.c
libtool --mode=link gcc -o libgreeting.la greeting.lo \
    -rpath /usr/local/lib -lm
 
gcc -o main.o -c main.c
libtool --mode=link gcc -o prog8 libgreeting.la main.o



Wir haben vor fast alle Compileraufrufe, den Befehl libtool vorangestellt. Libtool filtert unseren Compiler aufruf und fügt, je nach Betriebssystem, noch entsprechende Compilerflags ein. Es ist schon verwunderlich, dass wir nun, statt Dateien mit der Endung .so und .o , Dateien mit den Endungen .la und .lo erstellen. Diese Dateien sind in Wirklichkeit keine Libraries beziehungsweise Objektdateien, sondern Wrapperscripts, die von Libtool erstellt werden, um uns den Umgang mit den Libraries zu vereinfachen. Die eigentlichen Dateien liegen im versteckten Verzeichnis .libs . Weil Libtool diese ganzen Scripts verwendet, müssten wir auch Libtool benutzen, um das Programm und die Libraries auf dem System zu installieren. Da wir aber Automake verwenden wollen, kümmern wir uns nicht weiter darum, sondern schauen uns stattdessen an, wie wir Libtool in Automake verwenden.

6.3 Automake
Also passen wir unsere Makefile.am an.
Code:
# src/Makefile.am
lib_LTLIBRARIES = libgreeting.la
libgreeting_la_SOURCES = greeting.c greeting.h
 
bin_PROGRAMS = prog9
prog9_SOURCES = main.c
prog9_LDADD = libgreeting.la



Ich denke, das Script erklärt sich von selbst. Wir erstellen die Library und linken sie in unser Programm. Mit dem Makro prog9_LDADD linken wir die neue Library in unser Programm.

In unserem configure Script müssen wir außerdem Libtool initialisieren.
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
dnl configure.in
AC_PREREQ(2.59)
AC_INIT(Prog7, 1.0, noreply@mail.com)
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([src/main.c])
AC_CONFIG_HEADER([config.h])
 
# Checks for programs.
AC_PROG_CC
AC_PROG_LIBTOOL
 
# Checks for libraries.
 
# Checks for header files.
 
# Checks for typedefs, structures, and compiler characteristics.
 
# Checks for library functions.
 
AC_CONFIG_FILES([Makefile
                 src/Makefile])
AC_OUTPUT



War ja nun nicht so schwer. Also kompilieren wir...
Code:
$ aclocal
$ autoconf
$ libtoolize
$ ./configure
[...]
$ make



Wie wir sehen, müssen wir einmalig das Programm libtoolize aufrufen, um ein paar Scripts zu erstellen, die Automake benötigt.

Auch wenn es sich hier um ein wirklich komplexes Thema handelt, ist es mit Automake ziemlich einfach, das Programm zum Laufen zu bekommen.

6.4 Statische Libraries
Wollen wir jetzt statt dynamischer Libraries, statische, können wir dies ohne Probleme mit unserem bisher erstellten Script machen.
Code:
$ ./configure --enable-shared=no
$ make clean
$ make



Wir schalten einfach die Erstellung der dynamischen Libraries beim Konfigurieren aus.

Wollen wir komplett auf dynamische Libraries verzichten, können wir auch komplett auf Libtool verzichten. Wer also keine dynamischen Libraries braucht, entfehrnt alle libtool-speziefischen Aufrufe und erstellt in der Datei Makefile.am die Libraries auf folgende Weise.
Code:
lib_LIBRARIES = libgreeting.a
libgreeting_a_SOURCES = greeting.c greeting.h



7 Fazit
Autotools sind cool ;-)

Zumindest sind sie ein mächtiges Werkzeug, um plattformunabhängige Programme zu erstellen. Wenn man ein Programm nur für ein oder zwei Plattformen erstellt kann man natürlich auch einfach nur Make benutzen, um die Programme zu erstellen. Es kann aber nie schaden, auf zukünftige Portierungen vorbereitet zu sein.

Ich hoffe ich konnte euch einen kleinen Einblick in die Autotools vermitteln und es hat euch Spaß gemacht, den Artikel zu lesen. Es gibt noch etliche Dinge, die mit den Autotools möglich sind, die ich hier nicht vorstellen kann. Wer sich also weiter mit dem Thema beschäftigen möchte findet in den Info-Documents zu den einzelnen Tools jede Menge weiterer Informationen.

_________________
Download des Artikels als PDF und den Sourcecode gibt es hier.

Sie können Kommentare zu diesem Artikel im Forum schreiben. (Eine Registrierung ist nicht notwendig.)

Logo-Design: MastaMind Webdesign