Build-Systeme Teil 5: GNU Autotools



  • 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.

    /* main.c */
    #include "greeting.h"
    
    int main (int argc, char* argv[]) {
        greeting_do ();
    
        return 0;
    }
    
    /* greeting.c */
    #include <stdio.h>
    
    #include "greeting.h"
    
    void greeting_do (void) {
        printf("Hello,\nHow are you?\n");
    }
    
    /* 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.

    #!/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.

    $ chmod a+x build.sh
    $ ./build.sh
    

    Nach kurzer Zeit ist unser Programm kompiliert und wir können es benutzten.

    $ ./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:

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

    Beispiel:

    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.

    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.

    # 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

    $ 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.

    $ 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.

    $ 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.

    VARIABLEN_NAME = Variblen Inhalt
    

    Einfügen können wir den Variableninhalt an beliebiger Stelle im Makefile auf folgende Weise.

    $(VARIABLEN_NAME)
    

    Schreiben wir unser Makefile also auf folgende Weise um.

    # 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.

    $ 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.

    $ 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.

    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:

    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.

    @VARIABLEN_NAME@
    

    Nun sieht unsere makefile.in so aus:

    # 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 .

    $ 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.

    #                                               -*- 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.

    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.

    $ 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.

    $ ./configure
    

    Jetzt können wir, wie gewohnt Make aufrufen.

    $ 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 .

    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.

    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.

    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.

    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.

    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.

    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.

    /* 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...

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

    ... und testen...

    ./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.

    # 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.

    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.

    $ 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.

    $ ./configure
    [...]
    $ make
    [...]
    $ ./prog6 
    Hello,
    How are you?
    

    Zum aufräumen können wir einfach folgendes aufrufen.

    $ make clean
    

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

    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 .

    # 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.

    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.

    $ 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.

    $ 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.

    #!/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.

    # 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.

    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...

    $ 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.

    $ ./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.

    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.



  • interessanter Artikel.
    Aber ist es heutzutage überhaupt noch sinnvoll, in neuen Projekten, die Autotools zu verwenden? Ich denke ganz klar: nein!

    Inzwischen gibt es bessere Tools wie z.B. cmake, die längst nicht so aufwendig in der Handhabung sind.
    Ein paar Vorteile:

    • braucht nur einen c++ Compiler, keine sonstigen Abhängigkeiten
    • einfacher Syntax
    • besitzt ein test framework
    • kann für viele IDEs Projektdateien erstellen (MSVC, KDevelop...)

    Selbst die KDE-Entwickler sind für die Version 4 auf CMake umgestiegen.

    PS: Es wird schon einen Grund haben, warum die Autotools den Spitznamen "auto-hell" besitzen 😉



  • Eberhardt schrieb:

    interessanter Artikel.
    Aber ist es heutzutage überhaupt noch sinnvoll, in neuen Projekten, die Autotools zu verwenden? Ich denke ganz klar: nein!

    Das richtige Werkzeug für die richtige Aufgabe.

    Eberhardt schrieb:

    [*]braucht nur einen c++ Compiler, keine sonstigen Abhängigkeiten

    "nur"? Ein C++ Compilier ist schon eine ziemlich große Voraussetzung, meinst du nicht?

    Das einzige, was ein anderes Buildsystem bringen kann ist eine für die Konfiguration und das Build einheitliche Syntax und das Reduzieren von Abhängigkeiten. Wenn man ein Tool mit den Möglichkeiten der Autotools erstellen will, wird es genauso komplex, wie die Autotools selber.

    Wenn ein Buildsystem einfacher ist, werden einem selber bestimmte Freiheiten genommen, die man unter Umständen mit ziemlich viel Aufwand umgehem muss.

    Ich würde nicht generell sagen, dass die Autotools ausgedient haben.



  • Sehr guter Artikel! 👍
    Wurde auch Zeit, dass mal die GNU Autotools besprochen werden. Die sind einfach Standard unter UNIX.



  • Super Artikel!
    Eine der besten Einführungen in die autotools, die ich bisher gelesen habe. Hätte es die gegeben, als ich mich unlängst mit der Thematik beschäftigt habe, hätte mir das einige Stunden erspart.

    @Eberhardt
    cmake ist ja schon ganz cool, aber ist es wirklich auf allen Systemen so verfügbar wie die Autotools?



  • TheBigW@Work schrieb:

    @Eberhardt
    cmake ist ja schon ganz cool, aber ist es wirklich auf allen Systemen so verfügbar wie die Autotools?

    kommt darauf an wie du "so verfügbar" definierst. CMake gibt es für viele Plattformen: http://cmake.org/HTML/Download.html
    und spätestens mit KDE4 wird die Verfügbarkeit noch besser werden.

    ProgChild schrieb:

    "nur"? Ein C++ Compilier ist schon eine ziemlich große Voraussetzung, meinst du nicht?

    für die Autotools braucht man ja wohl auch einen c/c++ Compiler oder? Dafür muss man noch automake Version xy, etc. installieren 👎

    ProgChild schrieb:

    Wenn ein Buildsystem einfacher ist, werden einem selber bestimmte Freiheiten genommen, die man unter Umständen mit ziemlich viel Aufwand umgehem muss.

    ja, ist das richtig. Gerade Installer sind mit cmake nicht so einfach wie mit den autotools. Aber inzwischen gibt es eine große Auswahl an fertigen cmake Modulen, mit denen man sehr viel anstellen kann (wenn auch nicht soviel wie mit den autotools). Auf die Freiheiten verzichte ich gerne, wenn ich dafür einen einfachen Syntax, IDE Projektdatei-Generierung, kein Versionschaos etc. bekomme.



  • Eberhardt schrieb:

    ProgChild schrieb:

    "nur"? Ein C++ Compilier ist schon eine ziemlich große Voraussetzung, meinst du nicht?

    für die Autotools braucht man ja wohl auch einen c/c++ Compiler oder? Dafür muss man noch automake Version xy, etc. installieren 👎

    Ein C Compiler reicht, soweit ich weis. C != C++ 😉



  • Vielen Dank für diesen grandiosen Artikel! 👍
    Die GNU Autotools sind wohl mit das verfrickelste was mir bis jetzt untergekommen ist. 😮
    Ich steh' aber nunmal auf gefrickel! Habe aber nicht die Nerven mir die unzähligen infopages des "Autotools Book" anzutuen...

    Eine minimale Einführung, - und dann auch noch auf Deutsch! Genau das habe ich gesucht!
    Nachdem man deinen Artikel gelesen hat kann man die man-/infopages auch besser überfliegen da man ein recht präzises Bild davon hat was man eigentlich sucht. 💡



  • schöne übersicht über makefiles 👍
    eigentlich hasse ich diese dinge ja, aber manchmal braucht man die leider.
    ...und weil ich bestimmt mal wieder makefiles basteln muss, hab ich deinen artikel ge-bookmark-t 😉
    :xmas2:

    btw: vielleicht hat mal einer lust was über 'ant' zu schreiben?



  • @ten! Eh, gibts doch schon! Build-Systeme Teil 5 ist nicht umsonst Teil 5. Schau einfach mal ins Magazin-Verzeichnis:
    http://www.c-plusplus.net/forum/viewtopic-var-t-is-133193.html



  • Artchi schrieb:

    @ten! Eh, gibts doch schon! Build-Systeme Teil 5 ist nicht umsonst Teil 5. Schau einfach mal ins Magazin-Verzeichnis:
    http://www.c-plusplus.net/forum/viewtopic-var-t-is-133193.html

    danke, den hab ich voll übersehen...
    :xmas2:



  • Weis jemand zufällig, wie der Dateiname für make dist-* zusammengesetzt wird? Bei mir gibt#s da am Anfang ein überflüssiges '-' wodurch make dist-* nicht funktioniert

    configure.in

    #                                               -*- Autoconf -*-
    # Process this file with autoconf to produce a configure script.
    
    AC_PREREQ(2.61)
    AC_INIT("spaceshooter-sfml", 1.0pre, "bugs@coders-nemesis.eu")
    AM_INIT_AUTOMAKE
    AC_CONFIG_SRCDIR([Main.cpp])
    
    # Checks for programs.
    AC_PROG_CXX
    
    # Checks for libraries.
    
    # Checks for header files.
    AC_CHECK_HEADERS([libintl.h])
    
    # Checks for typedefs, structures, and compiler characteristics.
    AC_HEADER_STDBOOL
    AC_C_CONST
    AC_C_INLINE
    AC_TYPE_SIZE_T
    
    # Checks for library functions.
    AC_HEADER_STDC
    AC_CHECK_FUNCS([memset sqrt])
    
    AC_CONFIG_FILES([Makefile])
    AC_OUTPUT
    
    $ make dist-gzip
    { test ! -d -spaceshooter-sfml--1.0pre || { find -spaceshooter-sfml--1.0pre -type d ! -perm -200 -exec chmod u+w {} ';' && rm -fr -spaceshooter-sfml--1.0pre; }; }
    test -d -spaceshooter-sfml--1.0pre || mkdir -spaceshooter-sfml--1.0pre
    mkdir: Ungültige Option -- s
    „mkdir --help“ gibt weitere Informationen.
    make: *** [distdir] Fehler 1
    


  • darthdespotism schrieb:

    Weis jemand zufällig, wie der Dateiname für make dist-* zusammengesetzt wird? Bei mir gibt#s da am Anfang ein überflüssiges '-' wodurch make dist-* nicht funktioniert

    configure.in

    AC_INIT("spaceshooter-sfml", 1.0pre, "bugs@coders-nemesis.eu")
    

    Was sollen denn die Anführungszeichen da? Kann es sein, dass es daran liegt, dass die da nicht hingehören.

    Aber das hier ist bei Weitem weder der richtige Thread, noch das richtige Forum, um sowas zu fragen.



  • OK du hast wohl recht dass das hier Off-Topic ist, sorry.

    Deine Diagnose war richtig 😉 danke


Anmelden zum Antworten