Aliase nicht als Quellen verwenden
Wie ihr Name bereits zum Ausdruck bringt, sind Alias-Ziele dazu gedacht, dem Anwender von Yabu die Arbeit zu erleichtern. Ein Alias macht häufig benötigte Ziele oder Kombinationen von Zielen unter einem kurzen, einprägsamen Namen verfügbar. In solchen Fällen tritt der Alias im Buildfile immer als Ziel auf der linken Seite einer Regel auf. Zum Beispiel:
debug:: $(PROGRAMS:*=debug/*)
Ein Alias kann aber auch als Quelle vorkommen, wie das folgende Beispiel zeigt:
all:: $(PROGRAMS) tar:: all tar tf all.tar $(PROGRAMS)
Die Verwendung von Aliasen in dieser Weise ist aber nicht unbedenklich. Yabus Algorithmus basiert auf Änderungszeiten von Dateien, ein Alias ist aber per Definition keine Datei. Tatsächlich gilt ein Alias immer als veraltet, und damit auch alle davon abhängigen Ziele. Im obigen Beispiel würde mit jedem Aufruf von "yabu tar" die TAR-Datei neu erstellt, selbst wenn sich keine der in $(PROGRAMS) aufgeführten Dateien geändert hat.
Deshalb sollte man wenn möglich als Quellen keine Aliase verwenden. Das läßt sich oft durch Einführen einer weiteren Regel erreichen. Das obige Buildfile könnte man etwa so umschreiben:
all:: $(PROGRAMS) tar:: all.tar all.tar: $(PROGRAMS) tar tf all.tar $(PROGRAMS)
"Störende" Quellen aus $(*) entfernen
Manchmal benötigt man in einem Skript alle Quellen bis auf eine oder einige wenige. Ein Beispiel:
prog: defs.h src1.c src2.c src3.c cc -o $(0) src1.c src2.c src3.c
Da die Datei "defs.h" nicht mitkompiliert werden darf – offenbar wird sie von srcX.c per #include benutzt – kann man $(*) nicht verwenden. In diesem einfachen Beispiel läßt sich das leicht durch Verwendung einer Variablen umgehen:
SRCS=src1.c src2.c src3.c prog: defs.h $(SRCS) cc -o $(0) $(SRCS)
Es bleibt aber eine unschöne Wiederholung der Quellen im Skript, und in komplexeren Fällen wird die Regel dadurch unübersichtlicher.
Eine elegante Lösung besteht darin, die "störenden" Quellen in eine zweite Regel zu verlagern:
prog: src1.c src2.c src3.c cc -o $(0) $(*) prog: defs.h
Da die zweite Regel kein Skript hat, fügt sie dem Ziel "prog" lediglich eine weitere Quelle ("defs.h") hinzu – sie verändert aber nicht den Wert von $(*).
Mehrere Regeln für ein Ziel
Gelegentlich kommt es vor, daß Dateien mit der gleichen Namenserweiterung aus verschiedenen Quellen erzeugt werden. Zum Beispiel könnte ein Programm aus C- und FORTRAN-Quellen kompiliert werden. Im Buildfile wünscht man sich dann folgende Regeln
%.o: %.c cc -o -c $(0) $(1) %.o: %.f g77 -o -c $(0) $(1)
Das scheitert jedoch daran, daß Yabu für ein gegebenes Ziel nur eine Regel auswählt, und dabei nur das Ziel selbst (nicht die Quellen) betrachtet. Für "abc.o" würden beide Regel zutreffen, und ein Fehler wäre die Folge.
Das gleiche Problem tritt auf, wenn man Dateien aus verschiedenen Quellverzeichnissen kompilieren, die Ergebnisse aber in einem gemeinsamen Verzeichnis ablegen will. Auch hier führt der naheliegende Ansatz
build/%.o: unix/%.c cc -o -c $(0) $(1) build/%.o: general/%.c cc -o -c $(0) $(1)
zu einem Fehler, da Yabu beide Regeln als gleichwertig betrachtet.
Die Lösung besteht darin, die "Herkunft" der Dateien im Dateinamen zu kodieren. Die obigen Regeln für C- und FORTRAN-Quellen könnte man zum Beispiel so schreiben:
%_c.o: %.c cc -o -c $(0) $(1) %_f.o: %.f g77 -o -c $(0) $(1)
Spezialisierung von Prototypregeln
Für ein gegebenes Ziel wählt Yabu unter den Regeln mit Skripte höchstens eine aus; alle anderen bleiben unberücksichtigt. Das kann man dazu benutzen, allgemeine Prototypregeln für Spezialfälle mit einer anderen Regel zu "überschreiben". Hat die betreffende Regel kein Skript, dann kann man sich oft damit behelfen, ein "Dummy"-Skript hinzuzufügen.
Beispiel: die allgemeine Regel
all-%-%:: client-%1-%2 server-%1-%2 testsuite-%1-%2
soll für den Spezialfall "%1=win23" zu
all-win32-%:: client-win32-%2
abgewandelt werden. Das Hinzufügen der zweiten Regel hat allerdings nicht den gewünschten Effekt: beispielsweise würde Yabu für das Ziel "all-win32-gcc" immer noch beide Regeln berücksichtigen, so daß die zweite Regel gar nichts bewirkt.
Schreibt man aber
all-%-%:: client-%1-%2 server-%1-%2 testsuite-%1-%2 true all-win32-%:: client-win32-%2 true
dann greift der in beschriebene Auswahlmechanismus. Yabu würde nun für "all-win32-gcc" die zweite Regel auswählen und die erste Regel ignorieren.
Optionale Makroargumente: .foreach
als Ersatz für .if
Makroargumente, die eine Liste von Werten enthalten, führen manchmal
zu Fehlern wenn die Liste leer ist. Yabu kennt zwar keine .if
-Anweisung,
mit der man auf ein leeres Argument reagieren könnte,
aber in manchen Fällen kann man sich auch mit einer .foreach-Schleife behelfen.
Als Beispiel betrachten wir einen Codegenerator, der aus einer Modelldatei eine oder mehrere C-Quelldateien und Headerdateien erzeugt. Die entsprechenden Regeln sollen mit Hilfe eines Makros erzeugt werden:
.define generate model srcs hdrs $(.srcs): $(.model) gen_impl $(1) $(.hdrs): $(.model) gen_decl $(1) $(.srcs:*.c=*.o): $(.hdrs) .enddefine
Ein typischer Aufruf wäre
.generate aaa.mod srcs=aaa1.c aaa2.c aaa3.c hdrs=aaa1.h aaa2.h
Dies würde Regeln zur Erzeugung der C- und Header-Dateien liefern, und außerdem die Kompilate der C-Dateien von allen Header-Dateien abhängig machen.
Ein Problem tritt auf, wenn ein Modell gar keine Header erzeugt. Der Aufruf
.generate bbb.mod srcs=bbb1.c bbb2.c hdrs=
führt zu einem Syntaxfehler, denn die zweite, von .generate erzeugte Regel lautet nun:
: bbb.mod <----- SYNTAXFEHLER gen_decl $(1)
Die Lösung besteht darin, eine foreach-Schleife zu verwenden:
.define generate model srcs hdrs $(.srcs): $(.model) gen_impl $(1) .foreach h $(.hdrs) $(.h): $(.model) gen_decl $(1) .endforeach !serialize $(.hdrs) $(.srcs:*.c=*.o): $(.hdrs) .enddefine
Ein leerer Wert von $(.hdrs) ist nun kein Problem mehr: die .foreach
-Schleife
wird in diesem Fall gar nicht ausgeführt, und es entsteht keine unvollständige Regel.
Die !serialize
-Anweisung ist dann wichtig, wenn $(.hdrs) mehr als ein Element enthält;
sie verhindert, daß Yabu "gen_decl" parallel ausführt.
In der ursprünglichen Fassung ohne .foreach braucht man keine !serialize-Anweisung,
denn dort stehen alle Header-Dateien in einer Regel und bilden automatisch eine Gruppe.