Autoconf

Linux Autoconf

Also wenn man eine kleine Anwendung programmiert hat und dann irgendwann einmal in die Verlegenheit kommt, diese übers Internet zu verteilen, dann stellt sich meistens die Frage, wie man nun seine eigenen, auf den lokalen Rechner zugeschnittenen Einstellungen auf andere Rechner übertragen können soll. Das Werkzeug autoconf bietet hier dem Programmierer eine allgemeine Möglichkeit, die zumindest in der Unix-Welt funktioniert.

In der Regel sind es ja einige Programme, Bibliotheken und Dateien, die man benötigt, um aus einem Quelltext eine lauffähige Anwendung zu machen. Und zwar werden dabei diese Systemabhängigen Daten nicht nur in Makefiles, sondern auch im Programm selbst, also im Quellcode, verwendet. Üblich ist dabei, denjenigen, der die Anwendung auf seinem Rechner übersetzen will, zunächst ein Konfigurations-Script ausführen zu lassen:

./configure
make
su
make install

Diesen Kanon an Shellbefehlen trifft man immer wieder. Das liegt nicht zuletzt daran, dass es zur Erstellung eines solchen Konfigurations-Scriptes ein passendes GNU-Tool gibt. ;-) Und zwar autoconf. Damit kann man aus einer Beschreibungsdatei, der "configure.ac", ein so genanntes "configure"-Script erzeugen, welches extrem portabel ist und daher auf fast allen Unix-Varianten läuft. Man ist also nicht auf Linux beschränkt. Dieses erzeugte "configure" kann der Anwender dann auf seinem eigenen Rechner starten und es werden die notwendigen Parameter in der Regel automatisch gefunden und in bestimmte Dateien wie beispielsweise ein Makefile eingebaut. Dabei ist man allerdings nicht auf Makefiles beschränkt.

Prinzipiell kann man sagen, dass man mit der Datei configure.ac ein Shell-Script programmiert, welches sich einige Eingabedateien nimmt und dort bestimmte Zeichenketten, die mit einem @ anfangen und einem @ aufhören, durch die auf diesem Rechner gefundenen Werte ersetzt. Die so erzeugten Ausgabedateien werden dann beim Übersetzen des Programms verwendet. Ein Beispiel wäre ein Makefile wie dieses:

# This will become src/backend/admin/Makefile.
# PROGS
SHELL		= /bin/sh
CC		= gcc
CHECKERGCC	= checkergcc
INSTALL		= /usr/bin/install -c
DEBUGOPTS       = -ansi -pedantic -ggdb -Wall
OPTS            = -ansi -Wall -O2
INCLUDES	= -I./ -I../../lib/

#-Wno-unused
LOCAL_OBJ	= cfg.o help.o params.o usercfg.o dircfg.o shares.o cleanup.o
GLOBAL_OBJ	=  ../../lib/file.o ../../lib/user.o ../../lib/text_list.o\
		 ../../lib/ini_list.o ../../lib/misc.o ../../lib/strings.o

TARGET		= taja
prefix		= /usr
exec_prefix	= ${prefix}
INSTALLDIR	= ${exec_prefix}/sbin

all: $(TARGET)

$(TARGET): main.o $(LOCAL_OBJ) $(GLOBAL_OBJ)
	$(CC) $(OPTS) -o $(TARGET) main.o $(LOCAL_OBJ) 
$(GLOBAL_OBJ)

%.o: %.c %.h
	$(CC) $(OPTS) $(INCLUDES) -c $<

debug:
	for file in $$(ls -1 *.c) ; do $(CC) $(DEBUGOPTS) $(INCLUDES) -c $$file ; done

efence: debug
	$(CC) -lefence -o $(TARGET) main.o $(LOCAL_OBJ) $(GLOBAL_OBJ)

njamd: debug
	$(CC) -lnjamd -o $(TARGET) main.o $(LOCAL_OBJ) $(GLOBAL_OBJ)

checker:
	for file in $$(ls -1 *.c) ; do $(CHECKERGCC) $(INCLUDES) -c $$file ; done
	$(CHECKERGCC) -o $(TARGET) main.o $(LOCAL_OBJ) $(GLOBAL_OBJ)

install:
	$(INSTALL) -d -o root -m 0755 $(INSTALLDIR)
	$(INSTALL) -s -o root -m 0750 $(TARGET) $(INSTALLDIR)/$(TARGET)

uninstall:
	rm -f $(INSTALLDIR)/$(TARGET)

clean:
	rm -f *~ *.o $(TARGET)

.PHONY: all debug efence njamd checker install uninstall clean

Einfach anfangen

Eine genauere Anleitung zur Erstellung von Makefiles findest du [DOC:START:92]hier[DOC:END]. In diesem Fall würde man dem Anwender vielleicht einräumen wollen, dem Programm einen anderen Namen geben zu dürfen und auch das Zielverzeichnis beliebig zu ändern. Der Compiler hingegen und das Programm "install" müssen auf dem Zielsystem gesucht bzw. aus den dort vorhandenen Programmen so ausgewählt werden, dass genau das Ergebnis zu erwarten ist, welches man haben will. Damit man nun nicht wirklich ein ganzes Shell-Script von Hand schreiben muss, stellt autoconf Makros zur Verfügung, die es ermöglichen, mit vergleichsweise einfachen Befehlen in der configure.ac relativ umfangreiche Funktionen in das configure-Script zu integrieren. Diese Makros beginnen mit "AC_" und werden dann von m4, dem GNU Makro Prozessor, herausgefiltert und durch Script-Code ersetzt.

Ein guter Start für die Erzeugung eines eigenen configure.ac ist das Programm "autoscan". Es ist zusammen mit autoconf im Debian-Paket "autoconf" enthalten. Dieses Programm untersucht das aktuelle Verzeichnis und die Unterverzeichnisse nach Quellcode-Dateien der gängigen Programmiersprachen und erzeugt schon mal als Vorschlag eine Datei namens "configure.scan". Diese sollte man allerdings noch anpassen, um eine vernünftige configure.ac zu erhalten. Man kann configure.scan aber schon in configure.ac umbenennen und autoconf aufrufen, um schnell ein configure-Script zum Probieren zu erhalten.

Anpassen des Makefiles

Nun macht man sich daran, alle Teile des Makefiles, die vom Zielsystem abhängen, mit Makronamen zu versehen und die Werte durch diese Namen zu ersetzen. Dabei sollte man nicht die zwei @-Zeichen am Anfang und Ende des Makros vergessen. Die so entstandene Datei speichert man unter dem Namen "Makefile.in". Hier ist als Beispiel das obige Makefile geändert worden:

# This will become src/backend/admin/Makefile.
# PROGS
SHELL		= @SHELL@
CC		= @CC@
CHECKERGCC	= @CHECKERGCC@
INSTALL		= @INSTALL@
DEBUGOPTS       = -ansi -pedantic -ggdb -Wall
OPTS            = -ansi -Wall -O2
INCLUDES	= -I./ -I../../lib/

#-Wno-unused
LOCAL_OBJ	= cfg.o help.o params.o usercfg.o dircfg.o shares.o cleanup.o
GLOBAL_OBJ	=  ../../lib/file.o ../../lib/user.o ../../lib/text_list.o\
		 ../../lib/ini_list.o ../../lib/misc.o ../../lib/strings.o

TARGET		= @ADMIN_PROG_NAME@
prefix		= @prefix@
exec_prefix	= @exec_prefix@
INSTALLDIR	= @sbindir@

all: $(TARGET)

$(TARGET): main.o $(LOCAL_OBJ) $(GLOBAL_OBJ)
	$(CC) $(OPTS) -o $(TARGET) main.o $(LOCAL_OBJ) 
$(GLOBAL_OBJ)

%.o: %.c %.h
	$(CC) $(OPTS) $(INCLUDES) -c $<

debug:
	for file in $$(ls -1 *.c) ; do $(CC) $(DEBUGOPTS) $(INCLUDES) -c $$file ; done

efence: debug
	$(CC) -lefence -o $(TARGET) main.o $(LOCAL_OBJ) $(GLOBAL_OBJ)

njamd: debug
	$(CC) -lnjamd -o $(TARGET) main.o $(LOCAL_OBJ) $(GLOBAL_OBJ)

checker:
	for file in $$(ls -1 *.c) ; do $(CHECKERGCC) $(INCLUDES) -c $$file ; done
	$(CHECKERGCC) -o $(TARGET) main.o $(LOCAL_OBJ) $(GLOBAL_OBJ)

install:
	$(INSTALL) -d -o root -m 0755 $(INSTALLDIR)
	$(INSTALL) -s -o root -m 0750 $(TARGET) $(INSTALLDIR)/$(TARGET)

uninstall:
	rm -f $(INSTALLDIR)/$(TARGET)

clean:
	rm -f *~ *.o $(TARGET)

.PHONY: all debug efence njamd checker install uninstall clean

Grundlegendes Layout von configure.ac

Nun geht es darum, eine Datei "configure.ac" so zu schreiben, dass daraus per autoconf ein configure-Script erzeugt werden kann, welches für alle Makronamen des Makefiles einen Wert findet, der an dessen Stelle verwendet werden soll. Im Prinzip besteht die configure.ac selbst aus Makros (also quasi Befehlen - ich spreche jetzt der Einfachheit halber von Befehlen), die für die Abfragen zuständig sind, bestimmte Variablen mit Werten füllen und einem "Rahmen", der mit einer Initialisierung beginnt und der Verarbeitung von Makefiles und anderen Dateien des Projektes endet, wobei dann die Variablen in diese Dateien geschrieben werden. Das Grundlegende Layout sieht also so aus:

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

# Hier werden die Abfragen gemacht und die Werte festgelegt,
# die in die Dateien des Projektes eingetragen werden sollen. 

AC_OUTPUT

Dies erscheint zunächst trivial. Am Anfang wird der Name des Projektes und die Version sowie eine Mail-Adresse für Fehlermeldungen festgelegt. Am Ende steht lediglich ein Befehl, der besagt, dass die Dateien des Projektes mit den Werten gefüllt werden sollen.

Zu konfigurierende Dateien festlegen

Damit das configure-Script weiß, welches Makefile.in es verwenden soll, um daraus ein Datei Makefile zu bauen, müssen die *.in Dateien explizit angegeben werden. Dafür gibt es den Befehl

AC_CONFIG_FILES([Makefile])

Diesen Befehl verwendet man in der Regel am unteren Ende der Datei unmittelbar vor "AC_OUTPUT". Damit weiß autoconf, dass es im gleichen Verzeichnis eine Datei "Makefile.in" geben muss, aus der eine Datei "Makefile" unter Ersetzung der dort vorhandenen Makros erzeugt werden soll. Man kann auch eine Liste von Dateien angeben, die jeweils von Leerzeichen getrennt werden:

AC_CONFIG_FILES([Makefile src/Makefile man/Makefile])

Dann erwartet das erzeugte configure-Script die Existenz der Dateien Makefile.in, src/Makefile.in und man/Makefile.in. Will man hingegen unabhängig von der Namenskonvention für die Eingabe- und Ausgabedateien sein und beispielsweise eine Datei Makefile.man.in an eine andere Stelle im Verzeichnisbaum kopieren, dann sollte man nur noch eine Datei pro AC_CONFIG_FILES angeben:

AC_CONFIG_FILES([Makefile.man.in:man/Makefile])

Man hat aber die Möglichkeit, mehrere AC_CONFIG_FILES hintereinander zu verwenden.

Standard Makros

Es gibt einige Makronamen, die jedes configure-Script kennt und auch verwendet. Sie beziehen sich auf so grundlegende Dinge wie die Frage nach der aktuellen Shell oder dem Installations-Verzeichnis. Hier habe ich mal eine kleine, unvollständige Liste erstellt:

@prefix@ # Ein Verzeichnis (/usr/local), unterhalb dessen alle erstellten Programme installiert werden sollen.
@exec_prefix@ # Hier werden architektur-Abhängige Programme installiert.
@bindir@ # Anwender-Programme landen hier: $exec_prefix/bin
@sbindir@ # System-Programme landen hier: $exec_prefix/sbin

Weitere Standardmakros findet man in der [LINK:START:80]Doku[LINK:END] zu autoconf.

Meldungen ausgeben

Nicht nur bei den ersten Gehversuchen ist es wichtig, hier und da eine Meldung auszugeben. Im Prinzip kann man einfach Shell Script-Code in die configure.ac schreiben, sollte sich dabei jedoch sehr zurück halten. Denn schließlich soll das configure-Script ja portabel sein. Es gibt für Ausgaben spezielle Befehle:

AC_MSG_NOTICE([Hinweis])
AC_MSG_WARN([Warnung])
AC_MSG_ERROR([Fehler])

Wie sonst auch üblich, führt eine Fehlermeldung zum Abbruch des Programms. Sie sollte also bei denjenigen Ereignissen verwendet werden, die eine Übersetzung des Programms unmöglich machen. Also beispielsweise beim Fehlen eines Compilers oder einer Bibliothek.

Makrowerte setzen

Gerade am Anfang ist es vielleicht sinnvoll, zunächst den Makros zu Testzwecken feste Werte zuzuweisen. So kann man dann schon mal schauen, ob das Ersetzen der Makros in den Makefiles funktioniert. Mit

AC_SUBST(CHECKERGCC,[checkergcc])

wird in diesem Fall einem Makro, welches im Makefile als @CHECKERGCC@ auftaucht, mit dem Wert "checkergcc" gefüllt. In der vom configure-Script erzeugten Makefiledatei steht also nicht mehr das Makro, sondern der hier angegebene Wert "checkergcc".

Existenz und Pfade von Programmen abfragen

Wird von dem selbst entwickelten Programm ein anderes Programm aufgerufen oder für die Übersetzung benötigt, kann man dieses im Pfad suchen. Man verwendet dann

AC_PATH_PROG([MOUNT],[mount])

um beispielsweise das Programm "mount" zu suchen und dessen Pfad ersetzt dann das Makro @MOUNT@ in den Makefiles. Um abzufragen, ob dieses Programm gefunden wurde, ist etwas Shell-Code nötig:

if test -z $ac_cv_path_MOUNT ; then
   AC_MSG_ERROR([**** Could not find the program mount 
                 needed for this software! ****])
fi 

Man sollte aber von Shellspezifischen Dingen Abstand nehmen. Deshalb wird hier der "test" Befehl verwendet und kein anderes Konstrukt. Die Variable, die hier abgefragt wird, heißt ac_cv_path_MOUNT und ich kann nur empfehlen, im Zweifelsfall in das configure-Script rein zu schauen, wie bestimmte Variablen benannt werden. Der Makroname kommt auf jeden Fall im Namen der Variablen vor, so dass man mit einer einfachen Suche nach dem Makronamen auskommt. Natürlich gibt es einige Programme, nach denen fast immer gesucht wird, weshalb es für diese spezielle Befehle gibt:

AC_PROG_CC # Sucht nach einem C-Compiler.
AC_PROG_INSTALL # Sucht nach dem Programm install.
AC_PROG_LN_S # Schaut nach, ob man symbolische Links anlegen kann.

Weitere derartiger Befehle gibt's in der Dokumentation zu autoconf. Hier können sicher nicht alle Möglichkeiten in Gänze aufgelistet werden.

Existenz von Dateien

Ganz ähnlich wie bei Programmen verhält es sich bei Dateien. So könnte man in die Verlegenheit kommen, überprüfen zu müssen, ob eine bestimmte Datei auf dem Zielrechner vorhanden ist. Hier ist ein Beispiel, welches nachsieht, ob die Samba Konfigurations-Datei /etc/samba/smb.conf vorhanden ist:

 AC_CHECK_FILE(/etc/samba/smb.conf,
                [AC_SUBST(SMB_CONF,[/etc/samba/smb.conf])],
                [AC_CHECK_FILE(/etc/smb.conf,[AC_SUBST(SMB_CONF,[/etc/smb.conf])],
                [AC_MSG_ERROR([**** Could not find a smb.conf file.
		 Make shure it lives in /etc or /etc/samba or give me the path via
		 ./configure SMB_CONF=/your/path/to/smb.conf. ****])])])

Das sieht zunächst sehr wild aus, besteht aber im Wesentlichen aus dem einen neuen Befehl

AC_CHECK_FILE(Datei_mit_Pfad,[Aktion_falls_gefunden],[Aktion_falls_nicht_gefunden])

und einer Verschachtelung. Wenn hier die Datei /etc/samba/smb.conf gefunden wird, wird das Makro SMB_CONF mit dem Wert /etc/samba/smb.conf belegt. Andernfalls wird nochmal geschaut, ob es die Datei /etc/smb.conf gibt. Ist dies nicht der Fall, gibt es eine Fehler-Ausgabe und die Verarbeitung bricht ab. Wie hier in der Fehlermeldung angedeutet, kann man ein solches Makro auch beim Aufruf von configure mit einem Wert belegen. Man muss nur aufpassen, dass man diesen dann nicht überschreibt.

Wie bei den Programmen gibt es auch Dateien, die recht häufig gesucht werden. So gibt es für die Suche nach C/C++ Header-Dateien einen gesonderten Befehl:

AC_CHECK_HEADERS([fcntl.h libintl.h locale.h stdlib.h string.h unistd.h])

Wie man sieht, wird dabei eine von Leerzeichen getrennte Liste der benöigten Header-Dateien angegeben.

Bibliotheken untersuchen

Bei Bibliotheken spielt sicherlich nicht nur deren Existenz eine Rolle. Darüber hinaus gibt es die Möglichkeit zu überprüfen, ob eine angegebene Bibliothek eine zu benutzende Funktion bereit stellt:

AC_CHECK_LIB(efence,malloc)

Dies überprüft, ob es eine Bibliothek libefence gibt, die eine Funktion malloc bereit stellt. In der Shell-Variablen ac_cv_lib_efence_malloc steht dann in diesem Fall des Ergebnis dieser Abfrage.

Kommandozeilen-Optionen und Hilfe anpassen

Also prinzipiell kann man beim Aufruf von configure die selbst definierten Makronamen einfach mit Werten belegen, indem man beispielsweise "./configure SMB_CONF=/etc/wo/anders/smb.conf" verwendet. Damit der Mensch, der dieses configure-Script verwendet, davon auch weiß, sollte man über diese Möglichkeit in der Hilfe informieren.

AC_ARG_VAR(SMB_CONF,[the path to the samba configuration file])

setzt einen entsprechenden Eintrag in die Hilfe von configure. Man kann aber auch weiter reichende Optionen hinzufügen. So ist es möglich, einzelne Module oder Funktionalitäten bei der Übersetzung zu aktivieren oder zu deaktivieren. Als Beispiel sei hier eine option "--with-devtools" eingeführt:

AC_ARG_WITH(devtools,
        AC_HELP_STRING([--with-devtools],
               [check for development tools not necessary for a simple compile (default is NO)]),
                ac_cv_use_devtools=yes, ac_cv_use_devtools=no)

Damit kann dann festgelegt werden, dass nach weiteren Programmen gesucht werden soll, die man in diesem speziellen Fall aber nur benötigt, wenn man sich ernsthaft mit der Entwicklung dieser Software beschäftigt. Hier sind zwei verschiedene autoconf Befehle zu sehen. Einmal schaltet man eine gewünschte Option mit AC_ARG_WITH ein und erweitert dann die Hilfe mit dem Befehl AC_HELP_STRING. Wird die Kommandozeilen-Option "--with-devtools" beim Aufruf angegeben, dann steht in der Shell-Variablen ac_cv_use_devtools der Wert "yes". Der Vollständigkeit halber sei erwähnt, dass es auch einen Befehl AC_ARG_ENABLE gibt, mit welchem man eine Option "--enable-*" bauen kann.

Diese Darstellung ist zwar nicht alles umfassend, aber ich hoffe, dass du damit zumindest einen Einstieg gefunden hast. Ich jedenfalls kann die [LINK:START:80]GNU-Manuals[LINK:END] als HTML-Version zum Durchsuchen empfehlen und kann dir nur raten, auch mal in die configure.ac's anderer Leute rein zu schauen. Manchmal heißen die auch configure.in, was aber etwas verwirrt, da diese Datei nicht vom configure-Script, sondern von autoconf verarbeitet wird.

Artikelbewertung: 
No votes yet
War dieser Artikel hilfreich?