|
|
Todd Brunhoff (Tektronix) entwickelte im Zusammenhang mit dem Projekt Athena (MIT) eine Technik, mit der installationsunabhängig Makefiles beschrieben werden können. In einer installationsunabhängigen Imakefile-Datei wird der Zusammenhang Ziel-Quelle inklusive der zugehörigen Aktionen unter Verwendung von vordefinierten C-Makros und Konstanten definiert. In den installationsabhängigen template-Dateien werden diese C-Makros und Konstanten (CFLAGS, CC, ...) definiert. imake erzeugt dann, ausgehend von einer Imakefile-Datei, auf jeder Installation ein korrektes Makefile. In diesem Artikel wird imake aus X11 Release 5 beschrieben.
Generiert und installiert man Software unter Unix, dann hat man neben dem üblichen Geplänkel mit den Fehlern der Utilities das Problem, daß die Schnittmenge der Gemeinsamkeiten der verschiedenen Ziel-Plattformen gegen die leere Menge strebt. Es gibt umso weniger Gemeinsamkeiten, je mehr Fremdsoftware auf den Rechnern installiert ist. Exemplarisch seien einige Probleme aufgeführt:
Jeder, der Software portiert, muß seine Makefiles an die jeweilige Installation anpassen. Es wäre doch wesentlich arbeitssparender, wenn die in den Makefiles benötigte Information aus zentral gehaltenen, für die jeweilige Installation zugeschnittenen ``Datenbanken'' stammen würde. Im Zusammenhang mit der Entwicklung von X machten sich die Entwickler am MIT u.a. auch über die Portierungsmechanismen Gedanken. Folgende Prinzipien sollten bei der Entwicklung eines Werkzeugs berücksichtigt werden:
Als Lösung wird imake angeboten -- eine Koproduktion aus Konfigurationsdateien, C-Makros, make und dem C-Präprozessor cpp.
Abbildung 1 zeigt ein Imakefile für xcalc (von John H. Bradley, University of Pennsylvania),
$ cat -n Imakefile 1 XCOMM $XConsortium: Imakefile,v 1.8 91/08/15 12:19:56 rws Exp $ 2 #if defined(MacIIArchitecture) || defined(MotoR4Architecture) 3 IEEE_DEFS = -DIEEE 4 #endif 5 DEFINES = $(IEEE_DEFS) $(SIGNAL_DEFINES) 6 DEPLIBS = XawClientDepLibs 7 LOCAL_LIBRARIES = XawClientLibs 8 SYS_LIBRARIES = -lm 9 SRCS = actions.c math.c xcalc.c 10 OBJS = actions.o math.o xcalc.o 11 ComplexProgramTarget(xcalc) 12 InstallAppDefaults(XCalc)
Abbildung 1: ``Imakefile'' für ``xcalc''
Abbildung 2 zeigt einen Teil des entstehenden Makefiles.
$ imake $ cat Makefile ... 1 # $XConsortium: Imakefile,v 1.8 91/08/15 12:19:56 rws Exp $ 2 3 DEFINES = $(IEEE_DEFS) $(SIGNAL_DEFINES) 4 DEPLIBS = $(DEPXAWLIB) $(DEPXMULIB) $(DEPXTOOLLIB) $(DEPXLIB) 5 LOCAL_LIBRARIES = $(XAWLIB) $(XMULIB) $(XTOOLLIB) $(XLIB) 6 SYS_LIBRARIES = -lm 7 SRCS = actions.c math.c xcalc.c 8 OBJS = actions.o math.o xcalc.o 9 10 PROGRAM = xcalc 11 12 xcalc: $(OBJS) $(DEPLIBS) 13 $(RM) $@ 14 $(CC) -o $@ $(OBJS) $(LDOPTIONS) $(LOCAL_LIBRARIES)\ 15 $(LDLIBS) $(EXTRA_LOAD_FLAGS) 16 install:: xcalc 17 @if [ -d $(DESTDIR)$(BINDIR) ]; then set +x; \ 18 else (set -x; $(MKDIRHIER) $(DESTDIR)$(BINDIR)); fi 19 $(INSTALL) -c $(INSTPGMFLAGS) xcalc $(DESTDIR)$(BINDIR) 20 21 install.man:: xcalc.man 22 @if [ -d $(DESTDIR)$(MANDIR) ]; then set +x; \ 23 else (set -x; $(MKDIRHIER) $(DESTDIR)$(MANDIR)); fi 24 $(INSTALL) -c $(INSTMANFLAGS) xcalc.man \ 25 $(DESTDIR)$(MANDIR)/xcalc.$(MANSUFFIX) 26 install:: XCalc.ad 27 @if [ -d $(DESTDIR)$(XALPLOADDIR) ]; then set +x; \ 28 else (set -x; $(MKDIRHIER) $(DESTDIR)$(XALPLOADDIR)); fi 29 $(INSTALL) -c $(INSTALPFLAGS) XCalc.ad $(DESTDIR)$(XAPPLOADDIR)/XCalc ....
Abbildung 2: Teil des aus Abbildung 1 resultierenden ``Makefiles''
Betrachtet man die beiden Dateien etwas genauer, wird klar, wie die Makros ComplexProgramTarget (siehe auch Abbildung 6) und InstallAppDefaults aussehen müssen.
Durch den C-Makro ComplexProgramTarget (Abb. 1, Zeile 11) werden die Regeln generiert, mit denen man xcalc generieren (Abb. 2, Zeilen 12-15) und installieren (Abb. 2, Zeilen 16-19) und die Manualseiten (Abb. 2, Zeilen 21-25) installieren kann. In diesem C-Makro wird u.a. die Information der zuvor definierten Konstanten verwendet. Der C-Makro InstallAppDefaults generiert die Regeln, mit denen die zugehörige Ressourcedatei (Abb. 2, Zeilen 26-29) installiert werden kann.
imake
ist ein kleines, einfaches,
und dabei doch sehr nützliches Programm.
Unter Kontrolle von
imake
liest der C-Präprozessor die öffentlichen
Konfigurations- (*.cf),
Template- (*.tmpl) und Regeldateien (*.rules).
In diesen Dateien werden C- und make-Makros definiert.
Im
Imakefile
des Anwenders kann man diese
ersetzen bzw. ergänzen,
und man läßt sich die nötigen make-Regeln
durch Aufruf von C-Makros,
z.B.
ComplexProgramTarget
generieren.
Das Wissen über die konkreten Verhältnisse, die auf einer bestimmten Plattform herschen, ist in zentral verwalteten Dateien (Imake.tmpl, machine.cf , Lib.rules und site.def ) abgelegt. Je Projekt -- wie etwa X11 oder Motif -- wird in der Regel eine Template- und eine Regel-Datei gepflegt. Lokal entwickelte Dateien verwenden meistens existente Regel- oder Template-Dateien. Diese enthalten sehr viel undokumentierte Information. Das aus Sicht des Entwicklers entstehende Dilemma ist offensichtlich. In aller Regel läßt es sich nur durch das aufwendige Studium der entsprechende Konfigurationsdateien (ca. 3000 Zeilen) lösen.
Abbildung 3 zeigt den Aufbau von Imake.tmpl.
Abbildung 3: Aufbau von ``Imake.tmpl''
Imake.tmpl bildet den Rahmen, in den die weiteren Dateien via include eingebunden sind. In Imake.tmpl werden für fast alle benötigten Kommandos, Pfade und Flaggen symbolische Namen definiert. Von dieser generischen Plattform abweichende Werte müssen in site.def bzw. machine.cf (z.B. sun.cf) definiert werden. Die Auswahl der korrekten maschinenspezifischen Plattform geschieht durch cpp in Verbindung mit imake. In site.def werden alle aufgrund der lokalen Installation vom default-Wert abweichenden Variablen, z.B. ProjectRoot und UsrLibDir , definiert. In machine.cf werden die das Betriebssystem beschreibenden Variablen definiert. Die
#ifndef x # define x #endif
Technik garantiert, daß die Variablen nicht in den nachfolgenden Dateien überdefiniert werden. In aller Regel müssen site.def und machine.cf vom lokalen Systemadministrator ediert werden.
In Project.tmpl werden alle für das Projekt spezifischen Variablen festgelegt. Imake.rules enthält alle Definitionen der verwendbaren Makros. Imakefile beschreibt die Quellen und Ziele sowie deren Zusammenhang. Alles, was von cpp nicht bewertet werden kann, wird durchkopiert. Somit können an dieser Stelle auch ganz ``normale'' Makefile -Anweisungen stehen.
Die Funktionsweise von imake basiert im wesentlichen darauf, daß der C-Präprozessor ein maschinenabhängiges Symbol definiert hat, um Konditionalisierung der Art:
#ifdef sun
Sun-spezifische
Definition
#endif
zu ermöglichen. Beim Entwurf von imake konnte selbstverständlich nicht davon ausgegangen werden, daß jeder C-Präprozessor ein eindeutiges Symbol definiert. Aus diesem Grund wurde die Möglichkeit geschaffen, durch Änderung einer imake include -Datei (imakedep.h) den internen Aufruf des C-Präprozessors so zu gestalten, daß an ihn eine Option der Form -Duniq_symbol übergeben wird.
Im Vektor cpp_argv aus imakedep.h steht, welches Kommando (cpp oder cc) als Präprozessor verwendet und mit welchen Optionen das Kommando aufgerufen wird. Der Vektor wird mit der bekannten #ifdef ...-Technik statisch initialisiert. Sollten in diesem Bereich Probleme auftauchen, müssen die Quellen verändert werden. In diesem Fall sind die Punkte Step 1 und Step 5 in imakedep.h zu beachten.
An einem Beispiel wollen wir imake auf dem Weg durch das Labyrinth der Dateien auf einem Sun-Arbeitsplatzrechner verfolgen. Falls die Konfigurationsdateien nicht im default-Katalog liegen, wird imake der Pfad, der zu den Konfigurationsdateien führt, über die Environmentvariable IMAKEINCLUDE oder als Option auf der Kommandozeile angegeben. Abbildung 1 zeigt die Eingabe für imake. Im default-Fall sucht imake im aktuellen Katalog nach der Datei Imakefile und generiert eine Datei namens Makefile.
Imake.tmpl ist die erste Datei, die von imake gelesen wird. Als eine der ersten Zeilen findet sich dort: #define XCOMM # (siehe auch Abb. 1, Zeile 1). Im weiteren wird somit das Wort XCOMM als Kommentarbeginn betrachtet. Mit diesem Mechanismus wurde die Möglichkeit geschaffen, Kommentar besonders hervorzuheben. Falls lokale C-Präprozessoren an dieser Stelle scheitern sollten, hilft ein \ vor dem #. Mit der Phrase aus Abbildung 4 wird fixiert, in welcher Datei die plattformspezifische Information zu finden ist.
#ifdef sun # define MacroIncludeFile <sun.cf> # define MacroFile sun.cf # undef sun # define SunArchitecture #endif /* sun */
Abbildung 4: Bestimmung der maschinenspezifischen Beschreibung
Die nächste wesentliche Zeile in
Imake.tmpl
lautet:
#include MacroIncludeFile.
In sun.cf werden u.a. die Betriebssystemversion (OSMajorVersion und OSMinorVersion ) und verschiedene Flaggen gesetzt, welche die Generierung (XsunMonoServer) bzw. Installation (SetTtyGroup) beeinflussen. Die Regeln, nach denen Büchereien auf einer Sun-Architektur gebaut und installiert werden, werden in sunLib.rules definiert. Diese Datei wird via include in sun.cf eingebunden. Die nächsten wesentlichen Zeilen in Imake.tmpl definieren zwei C-Makros, mit denen zwei (Concat) bzw. drei (Concat3) Strings zusammengefügt werden können. Weil diese in den weiteren Dateien benutzt werden, findet die Definition an dieser, etwas unlogischen Stelle statt.
Nach dieser Definition
wird die für den lokalen Systemadministrator wichtigste Datei
mit
#include <site.def>
gelesen.
In dieser Datei wird all das definiert,
was die lokale Installation
von der
default -Installation
unterscheidet.
Bei uns finden sich dort die Definitionen aus Abbildung 5.
#ifdef sun # define MacroIncludeFile <sun.cf> # define MacroFile sun.cf # undef sun # define SunArchitecture #endif /* sun */
Abbildung 4: Bestimmung der maschinenspezifischen Beschreibung
#define UsrLibDir /usr/llib #define IncRoot /usr/linclude #define ManDirectoryRoot /usr/man/sun_manx #define BinDir /usr/lbin/X11 #define ManSuffix 1
Abbildung 5: Lokale Veränderungen in ``site.def''
Die nächsten 500 Zeilen in Imake.tmpl gießen die real vorhandene Plattform in eine imaginäre. Alle Kommandos wie
(#define CppCmd /lib/cpp),
Optionen, Pfadkomponenten, Pfade usw., die im weiteren benutzt werden, sind jetzt hinter symbolischen Namen verborgen.
Project.tmpl enthält alles X-Spezifische. Mit Konstanten wird gesteuert, ob ein Fontserver (BuildFontServer) gebaut wird, mit welcher Kennung die Manualseiten (LibManSuffix und ManSuffix) abgelegt werden und derlei Dinge mehr. sunLib.tmpl enthält alle projektspezifischen Informationen, die Büchereien betreffen. Die Versionsnummern der shared library (#define SharedXlibRev 4.10) werden dort ebenso fixiert wie die Pfade, die zu den X-Bibliotheken führen. Diese beiden Dateien werden in aller Regel nicht modifiziert. Soll ein neues Projekt mit imake verwaltet werden, muß meistens eine neue Projekt-Datei entwickelt werden. Die Kunst besteht dann darin, alle projektspezifischen Informationen in korrekter Abhängigkeit anzuordnen.
In Imake.rules werden ca. 120 C-Makros definiert, die zum Generieren und Installieren des Projekts, in diesem Fall X11 Release 5, verwendet werden. Abbildung 6 zeigt die Definition von ComplexProgramTarget.
#define ComplexProgramTarget(program) @@\
PROGRAM = program @@\
@@\
AllTarget(program) @@\
@@\
program: $(OBJS) $(DEPLIBS) @@\
RemoveTargetProgram($@) @@\
$(CC) -o $@ $(OBJS) $(LDOPTIONS) \
$(LOCAL_LIBRARIES) $(LDLIBS) $(EXTRA_LOAD_FLAGS) @@\
@@\
SaberProgramTarget(program,$(SRCS),$(OBJS), \
$(LOCAL_LIBRARIES),NullParameter) @@\
@@\
InstallProgram(program,$(BINDIR)) @@\
InstallManPage(program,$(MANDIR))
Abbildung 6: Definition des C-Makros ``ComplexProgramTarget''
Dieser Makro verwendet die zuvor definierten Makros und Konstanten.
Imake sammelt alle benötigten Dateien auf, die defines werden eingesetzt, die Makros bewertet und fertig ist das Makefile. So weit, so gut. Aber an sich sollten für imake zum Generieren und Installieren der Software dieselben Konfigurationsdateien wie beim Entwickeln mit der später installierten Software verwendet werden. Der Unterschied im Makefile , der zwischen Generierung der Software innerhalb des Quellbaums und der Benutzung der installierten Software liegt, ist in aller Regel nur in den Pfaden zu sehen, die zu den ausführbaren Programmen, den Bibliotheken und include -Dateien führen. Ein Beispiel: der Pfad, der zu einer Bibliothek führt, kann folgendes Aussehen haben: bei Generierung eines Objekts im Quellbaum ../../lib/libX11.a und bei der Generierung einer Applikation nach der Installation /usr/llib/X11/libX11.a. Trotzdem soll im Makefile in beiden Fällen jeweils derselbe symbolische Name benutzt werden können. Also müssen die entsprechenden Zuweisungen konditionalisiert werden, abhängig von der jeweiligen Verwendung. Wie der Pfad dann zusammengebaut werden muß, zeigt Abbildung 7.
/*
* _UseCat - combination of _Use and Concat.
* exists to avoid problems with some preprocessors
*/
#ifndef _UseCat
#if __STDC__ && !defined(UnixCpp)
#ifdef UseInstalled
#define _UseCat(a,b,c) a##c
#else
#define _UseCat(a,b,c) b##c
#endif
#else
#ifdef UseInstalled
#define _UseCat(a,b,c) a/**/c
#else
#define _UseCat(a,b,c) b/**/c
#endif
#endif
#endif
USRLIBDIR = /usr/llib/X11
TOOLKITSRC = ../../lib
XLIB = _UseCat($(USRLIBDIR),$(TOOLKITSRC),/libX11.a)
Abbildung 7: Konditionalisierte Zuweisung
Ein weiteres nützliches Werkzeug ist makedepend. Dieses, auch von Todd Brunhoff entwickelte Kommando, untersucht beliebige Dateien auf include-Anweisungen, generiert die daraus folgenden make-Regeln und fügt diese am Schluß zu dem Makefile hinzu. Es werden alle Konditionalisierungsmechanismen gemäß den Direktiven des C-Präprozessors behandelt. Abbildung 8 zeigt eine für ein Makefile typische Regel.
depend: $(SRC) makedepend -- $(CFLAGS) -- $(SRCS)
Abbildung 8: typische Regel eines Makefiles
Abbildung 9 die Ausgabe von % makedepend imake.c.
depend: $(SRC) makedepend -- $(CFLAGS) -- $(SRCS)
Abbildung 8: typische Regel eines Makefiles
# # ------------------------------------------------------------------------- # # dependencies generated by makedepend # DO NOT DELETE imake.o: /usr/include/stdio.h /usr/include/stddef.h /usr/include/stdarg.h imake.o: /usr/include/ctype.h .././X11/Xosdefs.h /usr/include/sys/types.h imake.o: /usr/include/fcntl.h /usr/include/signal.h /usr/include/sys/signal.h imake.o: /usr/include/sys/stat.h /usr/include/sys/wait.h imake.o: /usr/include/stdlib.h /usr/include/errno.h /usr/include/sys/errno.h imake.o: imakemdep.h # DO NOT DELETE THIS LINE -- make depend depends on it.
Abbildung 9: Resultat von ``makedepend imake.c''
Daß imake ein sehr gut funktionierendes Werkzeug ist, zeigt sich, wenn man X und Motif auf verschiedenen, nicht vorgesehenen Plattformen generiert und installiert. Man ist erstaunt, wie leicht es geht. Auch die Erweiterung der Konfigurationsdateien, um eigene Projekte in das System mit einbinden zu können, ist nicht schwierig. Ab und an fragt man sich allerdings, ob nicht mit Kanonen auf Spatzen geschossen wird, denn die für die Sun-Architektur benötigten Konfigurationsdateien umfassen zusammen ca. 3000 Zeilen. Um eine gezielte Änderung erreichen zu können, muß man gelegentlich schon etwas genauer hinsehen. Trotzdem, der Einsatz von imake und makedepend hat sich bei uns vielfach ausbezahlt. In diesem Fall gilt bestimmt nicht: ``Was nichts kostet, taugt nichts''.
J. Fulton, ``Configuration Management in the X Window System'', MIT, Dokumentation zu X11 Rev. 5.
S. Feldman, ``make -- A Program for Maintaining Computer Programs'', Software -- Practice and Experience, April 79.
|
|
Last modified 03/July/97