[X] Build-Systeme Teil 5: GNU Autotools
-
1. Einleitung
Der Begriff GNU Autotools fasst eine Gruppe an Progammen zusammen, mit denen sich unter verschiedenen Unix Derrivaten Programme compilieren lassen. Dabei übernehmen sie die Configuration, die prüfung der Abhängigkeiten, und das anschließende Build. Zu den GNU Autotools zählen normalerweise GNU Make, Autoconf, GNU libtool und GNU Automake
Voraussetzung, um den Artikel zu versten 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 einigen Beispielen verwende, GTK+ installiert zu haben.
2. Einfaches Compilieren
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 1; }
/* 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 übersetzten. 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 Compilieren 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 mit 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 compiliert 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 compiliert. Danach werden die erstellten Object Dateien zum Programm prog1 zusammen gelinkt.
3. GNU Make
3.1. Warum Make?
Unser Programm wird anstandslos compiliert und läuft wunder bar. Naja... es tut halt das, was es soll. Warum sollte man jetzt also noch mehr Zeit investieren, um ein komplexeres Build System zu benutzten, 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 korregiert. Jetzt wollen wir das Programm testen. Unser simples Script würde alle 300 Dateien compilieren 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 compilieren und dann Alles zusammen zu linken, 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 existiert schon 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 sie 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 selber ä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 Datein main.o und prog2 neu erzeugt. Änder 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 unflexiebel. Wenn wir zum Beispiel den Namen der Executeable ä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 Variablen Inhalt 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 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 entfehrnt.
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? Zu allererst 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.
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 das 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.
4.3. 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 folgender 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 instalierten 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 das 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.4. The Power of Autoconf
Ziehen wir doch ein wenig Nutzen aus unserem bisherigem Wissen. Wir wollen ein Progamm 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 benutzten 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 Configurationsvariablen das Prefix "`GTK"' vorangestellt wird.
Dann überprüfen wir, ob GTK+ in einer Version größer al 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 Configurations-Variablen die gerade ermittelten Configurations 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 1; }
Ich schätzte, das Programm ist selbsterklärend. Jeder sollte nun auch in der Lage sein, das Makefile dafür selber zu schreiben. Also Compilieren...
$ 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 Betreibssystemen 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 schaun, welche Headerdateien diese benutzten?
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ätztliche 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 compilieren und testen.
$ ./configure [...] $ make [...] $ ./prog6 Hello, How are you?
Zum aufräumen können wir einfach folgenes 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 wirds 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 compiliert 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} verweden, wirds schwierig, denn nun hängt es vom Compiler und von Betriebssystem ab, wie die Dateien compiliert werden müssen. Damit nicht für jedes Betriebssystem ein eigenes Makefile schreiben müssen, lassen wir uns von Libtool helfen.
Würden wir unser Programm von Hand übersetzten 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 Objektdatein, 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üsten wir auch Libtool benutzten, 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 compilieren 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 an 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 dynmischen 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 Plattform unabhängige Programme zu erstellen. Wenn man ein Programm nur für ein oder zwei Platformen erstellt kann man natürlich auch einfach nur Make benutzten, 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.
-
Das ist der Artikel zu den GNU Autotools. Ich hoffe, man kann alles Nachvollziehen.
Wäre gut, wenn jemand mal überprüft, ob Alles klappt und die Beschreibungen überall ausreichend sind.
-
Hi,
gefällt mir gut, dein Artikel. Diese Woche sollte ich noch Zeit finden, um die Beispiele mal durchzutesten.
Werde meinen Entwurf von damals noch mal durchschauen, ob ich was finde, was bei dir evtl. noch fehlt und dazupassen würde.
Nur ändere bitte den Titel noch auf: "Build-Systeme Teil 5: GNU Autotools" , dann haben wir alle Artikel der Build-System Reihe schön durchnummeriertGrüße
GPC
-
ProgChild schrieb:
Beispiel:
hello: hello.c gcc -o hello hello.c
Don'T do this at home, kids. Es ist zwar nur ein Beispiel, aber dadurch wird man möglicherweise dazu angeleitet, es tatsächlich mal so zu machen. Das ist aber nicht sonderlich erweiterbar, bzw bedarf erhöhter Wartung beim erweitern. Ich habe mir angewöhnt, auch für die popeligsten Single-Source-File-Kompilate immer den Zwischenschritt einer Objektdatei einzufügen:
hello: hello.o hello.c: hello.c
oder ausführlich:
hello: hello.o gcc -o hello hello.o hello.o: hello.c gcc -o hello.o -c test.c
Das hat den entscheidenden Vorteil, daß ich, wenn ich eine neue Datei für das Binary hello benötige, auch lediglich ein neues Target hinzufügen muss und lediglich dasHaupt-Target so erweitern muss, daß das hinzugefügte Target eine Dependency wird. Man hat ja normalerweise nicht von Anfang an 300 Dateien
Ansonsten fehlen mir ein wenig die Eigenarten von M4, mit denen die meisten Anfänger, mich eingeschlossen, PRobleme haben. Zum Beispiel daß man im Zweifel lieber zwei als nur ein Paar Brackets um Literale setzt, damit der M4-Interpreter nicht irgendwann anfängt, Literale zu interpretieren.
-
Ich habe mir den Artikel jetzt nicht kopmlett durchgelesen, aber könnte es sein das Autotools nicht Compilerabhängig arbeitet? Oder warum sehe ich überall gcc??
-
Artchi schrieb:
Ich habe mir den Artikel jetzt nicht kopmlett durchgelesen, aber könnte es sein das Autotools nicht Compilerabhängig arbeitet?
-Nein.-Gerade-durch-Autotools-wirst-du-compilerunabhängig.-
Edit: Das nicht übersehen, also genau umgekehrt. Ja, die Autotools sind nicht compilerabhängig.Artchi schrieb:
Oder warum sehe ich überall gcc??
Irgendeinen Compiler muss man doch nehmen. Später in den Autotools-Beispielen siehst du auch keinen gcc mehr, sondern nur noch $(CC), das normalerweise nach /usr/bin/cc zeigt, also auf den Standard-C-Compiler des Systems. Durch geschicktes Setzen von Umgebungsvariablen kann dann der Anwender jeden x-beliebigen Compiler einsetzen, von Comeau bis ICC.
-
Also der MSVC ginge auch? Gibts irgendwie eine Übersicht welche Compiler suppported werden? Und werden auch PCHs unterstützt?
Es wäre so eine kompakte stichwortartige Featureaufzählung nicht übel.
Ein Link zu einer Website zu den Autotools wäre auch nicht schlecht.
-
Artchi schrieb:
Ein Link zu einer Website zu den Autotools wäre auch nicht schlecht.
hehe, es gibt keine offizielle Webpräsenz, aber ich verweise Leute immer auf: http://sourceware.org/autobook/autobook/autobook_toc.html
-
Artchi schrieb:
Also der MSVC ginge auch? Gibts irgendwie eine Übersicht welche Compiler suppported werden? Und werden auch PCHs unterstützt?
GNU Make selbst ist es scheißegal, was für ein Kommando aufgerufen wird. Ob die Autotools einen MSVC automatisch erkennen würden, kann ich dir nicht sagen, aber mit dem Setzen der Variablen CC, CFLAGS und LIBS kriegt man sicherlich auch den MSVC dazu, etwas zu kompilieren. Die Frage ist dann nur, ob das für den Anwender nicht ein wenig aufwändig ist, wenn er genausogut den GCC benutzen könnte.
GPC schrieb:
hehe, es gibt keine offizielle Webpräsenz
http://www.gnu.org/software/autoconf/
http://www.gnu.org/software/automake/
http://www.gnu.org/software/libtool/
Die sehen für mich offiziell genug aus
-
tommie-lie schrieb:
GPC schrieb:
hehe, es gibt keine offizielle Webpräsenz
http://www.gnu.org/software/autoconf/
http://www.gnu.org/software/automake/
http://www.gnu.org/software/libtool/
Die sehen für mich offiziell genug ausNa jo, mit "offiziell" meinte ich www.autotools.org oder so... aber wurscht.
-
GPC schrieb:
Na jo, mit "offiziell" meinte ich www.autotools.org oder so... aber wurscht.
400 USD und sie ist dein, dann kannst du sie ja auf gnu.org umleiten
-
tommie-lie schrieb:
Artchi schrieb:
Also der MSVC ginge auch? Gibts irgendwie eine Übersicht welche Compiler suppported werden? Und werden auch PCHs unterstützt?
GNU Make selbst ist es scheißegal, was für ein Kommando aufgerufen wird. Ob die Autotools einen MSVC automatisch erkennen würden, kann ich dir nicht sagen, aber mit dem Setzen der Variablen CC, CFLAGS und LIBS kriegt man sicherlich auch den MSVC dazu, etwas zu kompilieren.
Also doch nicht Compiler-unabhängig? Ich meine nur, wenn ich nur auf GCC Compiler beschränkt bin, dann ist das ja legitim. Aber dann sollte man nicht sagen, das man Compiler A bis Z benutzen kann?
Die Frage ist dann nur, ob das für den Anwender nicht ein wenig aufwändig ist, wenn er genausogut den GCC benutzen könnte.
Versteh ich nicht. Ich will MSVC benutzen. Hat ja seinen Grund warum es mehrere Compiler gibt.
Fazit: Autotools ist mehr was für Linux/Unix-Umgebungen? Habe auch in der Autotools-Doku auf die schnelle nur was von Cygwin gelesen für Windows. Also für einen Windows-Entwickler ist Autotools eher uninteressant.
-
Artchi schrieb:
Also doch nicht Compiler-unabhängig?
Theoretisch sind die autotools vollständig plattformunabhängig. In der Praxis ist plattformunabhängigkeit auf diesem Level reine Augenwischerei. Wie gesagt, Make kommt mit MSVC klar, aber ob die Autotools ihn automatisch erkennen und passende Makefile generieren, kann ich dir nicht sagen, ich habe das noch nie ausprobiert und erst recht nicht für irgendwelche Spezialitäten, die nur Windows-Compiler oder MSVC im Speziellen kann.
Artchi schrieb:
Fazit: Autotools ist mehr was für Linux/Unix-Umgebungen?
Zumindest für POSIX-Umgebungen mit einigen GNU-Extensions, ja. Nativ unter Windows wird das Teil ohnehin nicht laufen, und in einer Cygwin-Umgebung dürfte gcc der Standardcompiler sein.
Artchi schrieb:
Habe auch in der Autotools-Doku auf die schnelle nur was von Cygwin gelesen für Windows. Also für einen Windows-Entwickler ist Autotools eher uninteressant.
Im Prinzip ja. Wie gesagt, es geht mit einigen Umwegen und Aufwand, aber es macht irgendwie keinen Sinn.
-
Artchi schrieb:
Versteh ich nicht. Ich will MSVC benutzen. Hat ja seinen Grund warum es mehrere Compiler gibt.
Fazit: Autotools ist mehr was für Linux/Unix-Umgebungen? Habe auch in der Autotools-Doku auf die schnelle nur was von Cygwin gelesen für Windows. Also für einen Windows-Entwickler ist Autotools eher uninteressant.
Die Autotools setzten zum compilieren eine funktionsfähige Bash compatible Shell und GNU Make voraus. Es ist für Windows nutzer interessant, wenn man mal eine Library oder ein Programm auf Windows portieren will und wissen will, wie die Datei jetzt genau erstellt wird. Wenn man nur und ausschließlich für Windows programmiert, würd ich mir das nicht antun.
-
tommie-lie schrieb:
GPC schrieb:
Na jo, mit "offiziell" meinte ich www.autotools.org oder so... aber wurscht.
400 USD und sie ist dein, dann kannst du sie ja auf gnu.org umleiten
haha, da hol ich mir doch lieber 'n Satz Winterreifen^^ :p
-
tommie-lie schrieb:
Ansonsten fehlen mir ein wenig die Eigenarten von M4, mit denen die meisten Anfänger, mich eingeschlossen, PRobleme haben. Zum Beispiel daß man im Zweifel lieber zwei als nur ein Paar Brackets um Literale setzt, damit der M4-Interpreter nicht irgendwann anfängt, Literale zu interpretieren.
Edit: Vorschlag 2:
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 das 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 benutzten wollen, sollten wir diesen in eckige klammern setzten. Wenn wir nämlich in diesem Text ein Komma benutzten, 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]])
-
1. Einleitung
Der Begriff GNU Autotools fasst eine Gruppe an Progammen zusammen, mit denen sich unter verschiedenen Unix Derrivaten Programme compilieren lassen. Dabei übernehmen sie die Configuration, die prüfung der Abhängigkeiten, und das anschließende Build. Zu den GNU Autotools zählen normalerweise GNU Make, Autoconf, GNU libtool und GNU Automake
Voraussetzung, um den Artikel zu versten 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 einigen Beispielen verwende, GTK+ installiert zu haben.
2. Einfaches Compilieren
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 1; }
/* 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 übersetzten. 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 Compilieren 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 mit 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 compiliert 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 compiliert. Danach werden die erstellten Object Dateien zum Programm prog1 zusammen gelinkt.
3. GNU Make
3.1. Warum Make?
Unser Programm wird anstandslos compiliert und läuft wunder bar. Naja... es tut halt das, was es soll. Warum sollte man jetzt also noch mehr Zeit investieren, um ein komplexeres Build System zu benutzten, 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 korregiert. Jetzt wollen wir das Programm testen. Unser simples Script würde alle 300 Dateien compilieren 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 compilieren und dann Alles zusammen zu linken, 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 existiert schon 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 sie 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 selber ä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 Datein main.o und prog2 neu erzeugt. Änder 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 unflexiebel. Wenn wir zum Beispiel den Namen der Executeable ä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 Variablen Inhalt 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 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 entfehrnt.
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? Zu allererst 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 das 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 benutzten wollen, sollten wir diesen in eckige klammern setzten. Wenn wir nämlich in diesem Text ein Komma benutzten, 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 folgender 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 instalierten 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 das 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 bisherigem Wissen. Wir wollen ein Progamm 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 benutzten 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 Configurationsvariablen das Prefix "`GTK"' vorangestellt wird.
Dann überprüfen wir, ob GTK+ in einer Version größer al 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 Configurations-Variablen die gerade ermittelten Configurations 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 1; }
Ich schätzte, das Programm ist selbsterklärend. Jeder sollte nun auch in der Lage sein, das Makefile dafür selber zu schreiben. Also Compilieren...
$ 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 Betreibssystemen 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 schaun, welche Headerdateien diese benutzten?
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ätztliche 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 compilieren und testen.
$ ./configure [...] $ make [...] $ ./prog6 Hello, How are you?
Zum aufräumen können wir einfach folgenes 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 wirds 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 compiliert 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} verweden, wirds schwierig, denn nun hängt es vom Compiler und von Betriebssystem ab, wie die Dateien compiliert werden müssen. Damit nicht für jedes Betriebssystem ein eigenes Makefile schreiben müssen, lassen wir uns von Libtool helfen.
Würden wir unser Programm von Hand übersetzten 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 Objektdatein, 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üsten wir auch Libtool benutzten, 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 compilieren 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 an 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 dynmischen 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 Plattform unabhängige Programme zu erstellen. Wenn man ein Programm nur für ein oder zwei Platformen erstellt kann man natürlich auch einfach nur Make benutzten, 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.
-
So das ist jetzt der Ergänzte Artikel. Ich möchte das Beispiel für Make nicht ändern, da es nur die Funktionsweise von Make demonstrieren soll, nicht aber zu kompliziert werden soll, als nötig. Die Makefiles werden ja schnell später noch Komplex.
-
1. Einleitung
Der Begriff GNU Autotools fasst eine Gruppe an Progammen zusammen, mit denen sich unter verschiedenen Unix Derrivaten Programme compilieren 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
Voraussetzung, um den Artikel zu versten 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 Beispielen verwende, GTK+ installiert zu haben.
2. Einfaches Compilieren
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 1; }
/* 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 übersetzten. 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 Compilieren 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 mit 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 compiliert 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 compiliert. Danach werden die erstellten Object Dateien zum Programm prog1 zusammen gelinkt.
3. GNU Make
3.1. Warum Make?
Unser Programm wird anstandslos compiliert und läuft wunder bar. Naja... es tut halt das, was es soll. Warum sollte man jetzt also noch mehr Zeit investieren, um ein komplexeres Build System zu benutzten, 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 korregiert. Jetzt wollen wir das Programm testen. Unser simples Script würde alle 300 Dateien neu compilieren 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 compilieren und dann Alles zusammen zu linken, 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 sie 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 selber ä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 Datein main.o und prog2 neu erzeugt. Änder 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 unflexiebel. Wenn wir zum Beispiel den Namen der Executeable ä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? Zu allererst 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 das 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 benutzten wollen, sollten wir diesen in eckige klammern setzten. Wenn wir nämlich in diesem Text ein Komma benutzten, 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 folgender 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 instalierten 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 Progamm 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 benutzten 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 Configurationsvariablen 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 Configurations-Variablen die gerade ermittelten Configurations 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 1; }
Ich schätzte, das Programm ist selbsterklärend. Jeder sollte nun auch in der Lage sein, das Makefile dafür selber zu schreiben. Also Compilieren...
$ 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 Betreibssystemen 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 schaun, welche Headerdateien diese benutzten?
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ätztliche 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 compilieren und testen.
$ ./configure [...] $ make [...] $ ./prog6 Hello, How are you?
Zum aufräumen können wir einfach folgenes 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 wirds 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 compiliert 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} verweden, wirds schwierig, denn nun hängt es vom Compiler und von Betriebssystem ab, wie die Dateien compiliert werden müssen. Damit nicht für jedes Betriebssystem ein eigenes Makefile schreiben müssen, lassen wir uns von Libtool helfen.
Würden wir unser Programm von Hand übersetzten 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 Objektdatein, 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üsten wir auch Libtool benutzten, 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 compilieren 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 an 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 dynmischen 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 Platformen erstellt kann man natürlich auch einfach nur Make benutzten, 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.
-
Hier ist nochmal eine überarbeitete Form. Ich hab die Beispiele nochmal selber durchgetestet. Wenn keine weiteren Einwände bestehen, würd ich den Artikel gerne für die Rechtschreibeprüfung freigeben.