next | next | up | down | Inhalt | Uebungen | Complete | Kommentar

all, section 8.11.

8.11.  ``imake'' contra ``I make''

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.

Das Problem

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:

Ein Ansatz

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.

Ein Beispiel

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.

Das Prinzip

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.

[picture]

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.

Generierung

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.

Ein Beispiel

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.

Ein Trick

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

makedepend

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

Fazit

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

Literatur

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.


back | next | up | down | Inhalt | Uebungen | Complete | Kommentar


Created by unroff & hp-tools. © by Hans-Peter Bischof. All Rights Reserved (1997).

Last modified 03/July/97