Fehlersuche

4 Fehlersuche

» » » »

Wenn ein Buildfile einmal nicht so arbeitet wie erwartet, beginnt die Suche nach der Fehlerursache – wer regelmäßig Makefiles bearbeitet, weiß wie mühsam das sein kann. Yabu ist in dieser Hinsicht nicht einfacher als Make. Im Folgenden sind einige Tips und Erklärungen zusammengestellt, die bei der Behebung von Fehlern helfen können.

.1 Fehlermeldungen

Alle Fehlermeldungen von Yabu haben einen 3-stelligen Kurznamen, zum Beispiel S01 für "Syntaxfehler". Im Abschnitt findet man für einige Fehlermeldungen Hinweise zu möglichen Ursachen und Behebung.

Wenn eine Fehlermeldung nicht unmittelbar einleuchtend ist, sollte man Yabu ein zweites Mal mit der Option -v aufrufen. Im Fehlerfalle gibt Yabu dann den vollständigen Programmkontext aus, was zum Verständnis des Fehlers nützlich sein kann. Noch mehr Ausgaben bekommt man mit -vv, -vvv und -vvvv; jedes "v" erhöht den Umfang der Ausgaben.

Normalerweise versucht Yabu, nach einem Fehler fortzufahren und die nicht betroffenen Ziele trotzdem zu erreichen. Das führt manchmal dazu, daß der gleiche Fehler immer wieder auftritt und die Ausgabe unübersichtlich wird. In diesem Fall sollte man Yabu mit der Option -k aufrufen oder in .yaburc bzw. im Buildfile "max_warnings=0" setzen.

Viele Terminals beherrschen Zeichenattribute wie "fett", "invers", "unterstrichen" oder verschiedene Schriftfarben. Yabu kann diese Fähigkeiten nutzen, um Fehlermeldungen optisch hervorzuheben. Das erleichtert die Suche nach Fehlermeldungen, vor allem bei längeren Programmläufen mit vielen Ausgaben. Die folgenden Einstellungen in .yaburc funktionieren zum Beispiel mit dem weit verbreiteten "xterm" (zumindest mit neueren Versionen):

highlight_e=\e[01;31m|\e[0m

Sie bewirken, daß Fehlermeldungen in roter Schrift erscheinen.

.2 Programmoptionen zur Fehlersuche

Ausgabe von Daten mit -D

Mit dem Kommandozeilenschalter "-D Auswahl" kann man bei Bedarf die Ausgabe zusätzlicher Daten veranlassen. Das Argument Auswahl ist eine Kombination der folgenden Buchstaben:

r

Ausgabe aller Regeln

T

Ausgabe aller Ziele nach Abschluß von Phase 1. Hier werden nur die Ziele ausgegeben, die in der Statusdatei (Buildfile.state) gespeichert sind.

t

Ausgabe aller Ziele nach Abschluß von Phase 2.

p

Ausgabe der Programmeinstellungen. Die in eckigen Klammern angegebene Zahl gibt an, woher der jeweilige Wert stammt:

[0]

Standardwert

[5]

Globale Konfigurationsdatei (yabu.cfg)

[4]

Globale Konfigurationsdatei (yabu.cfg)

[5]

Konfiguratiosdatei des Benutzers ($HOME/.yaburc)

[10]

!settings-Abschnitt im Buildfile

[20]

Kommandozeile

c

Ausgabe aller Optionen

v

Ausgabe aller Variablen

A

Alle Ausgaben

Die Buchstaben lassen sich beliebig kombinieren, zum Beispiel:

yabu -D rvc

Ausgabe von Meldungen mit -v

Normalerweise gibt Yabu eine Zeile aus, wenn ein Ziel veraltet war und das zugehörige Skript ausgeführt wird. Durch Aufruf mit -v, -vv, -vvv usw. kann man den Umfang der Ausgaben schrittweise steigern. Um einem Fehler auf die Spur zu kommen, oder um zu verstehen warum Yabu ein bestimmtes Skript ausführt bzw. nicht ausführt, beginnt man sinnvollerweise mit -v oder -vv.

Mit -v nennt Yabu die Namen der verarbeiteten Dateien, also Buildfile, Buildfile.state und .include-Dateien. Ist ein Ziel veraltet, dann gibt Yabu den Namen der (ersten) Quelle aus, die jünger als das Ziel ist. Im Falle eines Fehlers gibt Yabu zusätzlich zu der Fehlermeldung den aktuellen Programmkontext aus.

Durch Angabe von -vv läßt sich die Arbeit von Yabu in Phase 2 im Detail mitverfolgen. Zusätzlich zu den unter -v beschriebenen Ausgaben gibt Yabu alle auftretenden Ziele aus, und beschreibt ihre Abhängigkeiten. Ist ein Ziel noch aktuell, dann gibt Yabu auch diese Tatsache in einer eigenen Meldung aus.

Die höheren Ausgabestufen ab -vvv liefern weitere Details, unter anderem zur Auswahl der Regeln. Hat man ein Problem bereits eingekreist, dann kann es sinnvoll sein, sich diese Ausgaben genauer anzusehen. Um den Umfang der Meldungen zu reduzieren, sollte man -vvv (und höher) nur im Zusammenhang mit einem einzelnen Ziel verwenden. Beispiel:

yabu -vvvv yabu3.o

Fehlersuche in Skripten: -e, -n, -k und -j

Normalerweise zeigt Yabu die ausgeführten Kommandos nicht an. Nur wenn ein Skript mit einem Exit-Code ungleich 0 endet, gibt Yabu zusammen mit der Fehlermeldung das vollständige Skript aus. Dieses Verhalten läßt sich über zwei Programmeinstellungen steuern: echo_after_error (Default: true) bestimmt, ob Yabu nach einem Fehler das Skript ausgibt, und mit echo oder -e (Default: false) legt man fest, daß Yabu alle Skripte ausgeben soll.

Die Einstellung echo=true ist nur in Ausnahmefällen sinnvoll. Zur Fehlersuche ist sie aber sehr nützlich, insbesondere wenn man sie mit -nn kombiniert. Zum Beispiel zeigt

yabu -nne prog1.o

an, welche Kommandos Yabu ausführen würde, um prog1.o zu erzeugen.

Yabu versucht nach einem Fehler normalwerweise, weitere Ziele zu erreichen. Das kann manchmal dazu führen, daß die gleiche Fehlermeldung sehr oft wiederholt wird. In diesem Fall ist es sinnvoll, mit max_warnings=0 oder -k die Ausführung nach dem ersten Fehler abzubrechen.

Parallelisierung verhindern: -j und -p

Die Fehlersuche wird manchmal dadurch erschwert, daß Yabu mehrere Skript parallel ausführt. Das betrifft die "lokale" Ausführung durch Yabu selbst und auch die Verteilung von Skripten auf Server. Manche Fehler treten auch nur dann auf, wenn Skripte parellel laufen. Deshalb gibt es zwei Einstellungen, mit denen sich die Parallelisierung unterbinden läßt: use_server=false (oder -j) verhindert, daß Yabu Skripte auf einem Server ausführt, und parallel_build=false (oder -p) erzwingt die sequentielle Ausführung von Skripten durch Yabu selbst.

.3 Variablen

Manchmal ergibt sich bei der Fehlersuche die Frage, welchen Wert eine bestimmte Variable hat. Der Wert entsteht möglicherweise durch Zusammenwirken vieler, über das ganze Buildfile verstreuter Zuweisungen. Dazu kommen unter Umständen noch verschiedene Konfigurationen, welche ebenfalls den Wert einer Variablen beeinflussen. Ein Beispiel:

LOCAL_john=/home/john/local
CC=$(LOCAL)/bin/gcc
LOCAL=$(LOCAL_$(USER))
USER=john
LOCAL[linux]=/usr

Deshalb ist es in der Praxis oft sehr mühsam, den Wert einer Variable "zu Fuß" durch Betrachten des Buildfiles zu bestimmen. Viel einfacher ist es, Yabu diese Arbeit zu überlassen. Die folgenden, durch [JGC07] inspirierten Regeln helfen dabei:

print-%::
    echo "%=$(%)"
print-%/%:: [%2]
    echo "%1[%2]=$(%1)"

Mit diesen beiden Regeln im Buildfile kann man auf einfache Weise beliebige Variablenwerte abfragen:

# yabu -q print-CC
CC=/home/john/local/bin/gcc

Oder, wenn der Wert in einer bestimmten Konfiguration gefragt ist:

# yabu -q print-CC/+linux+gcc
CC[+linux+gcc]=/usr/bin/gcc

Manchmal möchte man aber nicht nur den Wert einer Variablen wissen, sondern auch, wie der Wert enstanden ist. Eine erste Hilfe dabei ist, sich mit der Option -D v die Definitionen aller Variablen ausgeben zu lassen:

# yabu -D v
...
----- Variablen -----
...
LOCAL_john=/home/john/local
CC=$(LOCAL)/bin/gcc
LOCAL=$(LOCAL_$(USER))
  [+gaga]=/usr/gaga
USER=john
...

Mit -vvvv gibt Yabu unter anderem alle Variablendefinitionen aus. Das beantwortet zwar noch nicht die Frage "Wie kam es zu dem Wert CC=/usr/bin/gcc?", doch immerhin findet man hier alle beteiligten Zuweisungen übersichlich zusammengefaßt.

.4 Erläuterungen zu den Fehlermeldungen

» » » »

..1 Allgemeine Fehler (Gxy)

G01: Speicheranforderung gescheitert

Das Betriebssystem konnte eine Speicheranforderung nicht erfüllen.

G10: Datei kann nicht geöffnet werden

Yabu kann eine Datei nicht öffnen. Warum, sollte aus der Fehlermeldung hervorgehen.

G20: Fehler bei einer Systemfunktion

Eine Systemfunktion, typischerweise im Zusammenhang mit Dateien oder der Ausführung eines Skriptes, ist fehlgeschlagen. Mögliche Ursachen sind mangelnde Systemresourcen oder fehlende Zugriffsrechte.

G21: Verzeichnis konnte nicht angelegt werden

Yabu hat erfolglos versucht, ein fehlendes Verzeichnis anzulegen. Dieser Fehler kann nur auftreten, wenn auto_mkdir=true eingestellt ist.

G30: Fehler im Skript

Bei der Ausführung eines Skriptes ist ein Fehler aufgetreten. Das betreffende Ziel (und alle davon abhängigen Ziele) werden als fehlerhaft gewertet. Yabu versucht jedoch, alle übrigen, davon unabhängigen Ziele zu erreichen. Dies kann man mit der Option -k verhindern.

Der Fehler G30 kann auch dann auftreten, wenn Yabu ein Skript im Zuge einer .include- oder !configure-Anweisung ausführt. Die Fehlerursache ist möglicherweise aus den Fehlerausgaben des Skriptes ersichtlich. Man beachte, daß die Standardausgabe des Skriptes nicht sichtbar ist, da sie von Yabu verarbeitet wird.

G41: Unbekannte Option

Die angegebene Kommandozeilenoption ist unbekannt. Eine mögliche Ursache ist, daß das Leerzeichen zwischen einer Option und ihrem Argument fehlt. Zum Beispiel:

yabu -fmybuildfile       <----- FEHLER

Eine Hilfe zur Kommandozeilensyntax läßt sich mit

yabu -?

ausgeben.

G42: Fehlendes Argument

Auf der Kommandozeile wurde eine Option benutzt, die ein Argument benötigt, das Argument fehlt jedoch. Zum Beispiel:

yabu -f     <----- FEHLER

G43: Falsches Argument hinter -y

Das auf -y folgende Argument ist keiner der erlaubten Namen "mt", "mtid" oder "cksum".

..2 Syntaxfehler (Sxy)

S01: Allgemeiner Syntaxfehler

Diser Fehler wird ausgegeben, wenn die Syntax des Buildfiles nicht korrekt ist, und keine spezielle Fehlermeldung hierfür existiert.

S02: Element nicht abgeschlossen

Ein syntaktisches Element wurde nicht ordnungsgemäß abgeschlossen. Beispiele sind ein fehlendes .endforeach, oder eine fehlende schließende Klammer (")", "]" oder "}").

..3 Fehler in Phase 2 (Bxy)

B21: Ziel wurde nicht erzeugt

Das Skript zu einer Regel wurde zwar fehlerfrei ausgeführt, aber die erwartete Zieldatei existiert nicht. Eine mögliche Ursache ist, daß Regel und Skript nicht zusammenpassen, wie im folgenden Beispiel:

file.o: file.c
  cc -o file.obj file.c

Yabu erwartet, daß nach Ausführung der Regel die Datei "file.o" existiert, tatsächlich erzeugt das Skript aber "file.obj".

Eine weitere typische Fehlerursache ist, daß die Regel gar keine Datei erzeugt, aber trotzdem mit der Syntax für dateierzeugende Regeln geschrieben wurde:

run-tests : test1 test2
  ./test1
  ./test2

Hier dient "run-tests" lediglich als Abkürzung zur Ausführung der beiden Testprogramme. In solchen Fällen muß man eine Alias-Regel verwenden:

run-tests :: test1 test2
  ./test1
  ./test2

Wie beim Fehler G30 gilt: das betroffene Ziel wird als fehlerhaft markiert, Yabu bricht jedoch nicht ab, sondern versucht alle übrigen, davon unabhängigen Ziele zu aktualisieren. Dies läßt sich mit der Option -k verhindern.

Wenn man Yabu mit der Option -n aufruft, wird nicht nur die Ausführung von Skripten unterdrückt, sondern auch der Fehler B21.

..4 Sonstige Fehler

L01: Element ist nicht definiert

Dieser Fehler tritt auf, wenn man eine Variable, Option o.ä. verwendet, ohne sie vorher zu definieren. Folgende Situationen führen zu dem Fehler:

L03: Element ist bereits definiert

Dieser Fehler bedeutet, daß eine Variable, eine Option oder ein Makro bereits definiert ist. Er tritt auch auf, wenn man in einem Makroaufruf ein Argument mehrfach angibt:

.mymacro
   arg=value
   arg=value      <----- FEHLER

Optionen und Variablen mit gleichen Namen sind ebenfalls nicht erlaubt und führen zu dem Fehler L03:

!options
    compiler(gcc ms intel)
compiler=GCC      <----- FEHLER ('compiler' bereits definiert)
gcc=not unix      <----- FEHLER ('gcc' bereits definiert)

L04: Zirkuläre Abhängigkeit

Dieser Fehler tritt auf wenn ein Ziel, eine Variable oder ein Makro von sich selbst abhängt. Das würde bei der Auswertung bzw. Ausführung zu einer endlosen Schleife führen und ist deshalb verboten. Zirkuläre Abhängigkeiten entstehen oft indirekt über mehrere "Zwischenstationen". Im folgenden Beispiel hängt die Variable A über B und C von sich selbst ab:

A=aaa-$(B)
B=$(C)-bbb
C=ccc-$(A)

Da die Anweisungen über das Buildfile verstreut sein können, sind zirkuläre Abhängigkeiten nicht leicht zu erkennen. Auch Yabu bemerkt die Schleife erst, wenn das Ziel, die Variable bzw. das Makro tatsächlich verwendet werden. Die obigen drei Zeilen allein würde Yabu nicht beanstanden; erst wenn der Wert einer der drei Variablen ermittelt wird – zum Beispiel bei Ausführung eines Skriptes, welches $(A) enthält – kommt es zu einem Fehler.

Bei Zielen kann es zu einer ähnlichen Schleifenbildung kommen, im einfachsten Fall durch zwei Regeln der folgenden Art:

a: b
b: a

In der Praxis treten meist größere Schleifen aus 3 oder mehr Zielen auf. Auch hier erkennt Yabu den Fehler erst, wenn eines der betroffenen Ziele tatsächlich erreicht werden soll.

Ein anderer Typ von Schleifen kann bei der Verwendung von Prototyp-Regeln entstehen. Dazu ein Beispiel:

a%: ba%
b%: ab%

Angewendet auf das Ziel "a" würden diese beiden Regeln eine unendliche Liste von Quellen produzieren, nämlich "a", "ba", "aba", "baba" usw. Da hier kein Ziel zweimal auftritt, versagt die Schleifenerkennung. Yabu beschränkt jedoch die Rekursionstiefe bei der Ermittlung der Quellen auf 50. Beim Überschreiten dieser Grenze bricht Yabu ebenfalls mit dem Fehler L04 ab.

L05: Ziel ist keine reguläre Datei

Diese Fehlermeldung entsteht, wenn folgende Bedingungen zusammenkommen:

Die Idee dabei ist, daß Ziele ohne Regel normalerweise nur die eigentlichen Quelldateien des Projektes sind, aber niemals Verzeichnisse oder andere spezielle Dateien. Der Fehler tritt beispielsweise auf, wenn das Verzeichnis "sources" existiert, im Buildfile nicht vorkommt, und der Benutzer

yabu sources

ausführt.

L11: Zu viele Platzhalter

Ein Ziel in einer Regel enthält mehr als 9 "%"-Platzhalter, oder die linke Seite einer Transformationsregel enthält mehr als 9 "*"-Platzhalter.

L12: Definieren von Optionen nicht möglich

Dieser Fehler tritt auf, wenn eine !options-Anweisung nach einer bedingten Zuweisung steht. Zum Beispiel:

!options
    a
VAR=
VAR[a]=b
!options
    b      <----- FEHLER

Es ist zulässig, Optionen in mehreren !options-Anweisungen zu definieren. Dabei müssen aber alle !options-Anweisungen vor der ersten bedingten Zuweisung stehen.

L13: Zuweisung an Systemvariable

Eine Variable, deren Name mit "_" beginnt, kann nicht verändert werden.

L14: Wert paßt nicht zur Transformationsregel

Auf eine Variable wurde eine Transformationsregel angewendet, aber eines der zu transformierenden Wörter paßt nicht zur linken Seite der Regel. Zum Beispiel paßt in

OBJS=aaa.o bbb.o ccc.c ddd.o
SRCS=$(OBJS:*.o=*.c)           <----- FEHLER

der dritte Wert (ccc.c) nicht zur linken Seite (*.o) der Transformationsregel.

Mit einer erweiterten Form der Transformationsregel kann man Yabu dazu bringen, die nicht passenden Wörter zu ignorieren. Das obige Beispiel würde dann so aussehen:

OBJS=aaa.o bbb.o ccc.c ddd.o
SRCS=$(OBJS?:*.o=*.c)          <----- aaa.c bbb.c ddd.c

L24: Widersprüchliche Optionswerte

Eine Konfiguration enthält widersprüchliche Werte für eine Option, also sowohl "+" als auch "-". Das kann zum Beispiel in einer bedingten Zuweisung vorkommen:

CC[debug+gcc-debug]=/usr/bin/gcc    <----- FEHLER

oder, weniger offensichtlich:

!configuration [gcc]
   CFLAGS[debug-gcc]=-g             <----- FEHLER

Der Fehler tritt auch dann auf, wenn man versucht, mehr als eine Option aus einer Optionsgruppe einzuschalten. Das ist ebenfalls verboten, denn mit dem Einschalten einer Option werden alle anderen Optionen in der Gruppe implizit ausgeschaltet. Auch hierzu ein Beispiel:

!options
   type(debug release)
CFLAGS[debug+release]=-g     <----- FEHLER

L31: Mehrdeutige Regeln

Für das angegebene Ziel ist keine eindeutige Regel definiert. Dieser Fehler tritt auf, wenn es für das betreffende Ziel zwei oder mehr Regeln mit Skript gibt (Regeln ohne Skript sind dagegen in beliebiger Anzahl erlaubt). Ein Beispiel:

all:: test-abc.exe

%.exe: %.c
       $(CC) -o $(0) $(1)

test-%.exe: test/%.c
       $(CC) -o $(0) $(1)

Für Yabu haben beide Regeln die gleiche Priorität, selbst wenn die zweite "spezifischer" aussieht als die erste. Es hilft auch nicht, daß in diesem Falle beide Skripte identisch sind.

Details zur Auswahl der Regeln siehe in .

L32: Widersprüchliche Regeltypen

Für ein Ziel existieren sowohl normale (":") als auch Alias-Regeln ('::'). Das ist nicht erlaubt; alle Regeln für ein Ziel müssen vom gleichen Typ sein.

L35: Widersprüchliche bedingte Zuweisungen

Für die genannte Variable gibt es in der aktuellen Konfiguration widersprüchliche Wertzuweisungen. Ein Beispiel:

!options
   eins zwei
CFLAGS=""
CFLAGS[eins] = -Ieins/include
CFLAGS[zwei] = -Izwei/include

all:: [eins+zwei]
    echo "$(CFLAGS)"       <----- FEHLER

Diesen Fehler kann man in der Regel vermeiden, indem man die bedingten Zuweisungen mit "+=" an Stelle von "=" schreibt. In jeder Konfiguration darf es nämlich nur eine "="-Zuweisung, aber beliebig viele "+="-Zuweisungen geben.

L33: Keine Regel gefunden

Für das angegebene Ziel existiert im Buildfile keine passende Regel. Das bedeutet entweder, daß es gar keine Regel für das Ziel gibt, oder daß die vorhandenen Regeln nicht zur aktuellen Konfiguration passen. Ein einfaches Beispiel für den zweiten Fall:

all:: [xzy] target1
target1: [-xyz]
  ...

Hier würde Yabu versuchen, "target1" in der Konfiguration [xyz] zu erreichen. Die dazu passende Regel verlangt aber die Konfiguration [-xyz] und wird deshalb – ohne Fehlermeldung – verworfen. Sofern das Buildfile keine weitere passende Regel enthält, tritt der Fehler L33 auf.

Einen Sonderfall bilden Ziele, die beim Aufruf von Yabu als Argument angegeben werden. Für ein solches Ziel verlangt Yabu immer die Existenz einer passenden Regel. Dahinter steht die Überlegung, daß der Benutzer

yabu source.c

nur eingibt, wenn Yabu damit etwas sinnvolles anfangen kann, mit anderen Worten, wenn es eine Regel für "source.c" gibt. Gibt es keine Regel, dann meldet Yabu den Fehler L33, selbst wenn die Datei "source.c" bereits existiert.

L40: Zu viele verschachtelte Include-Dateien

.include-Anweisungen können bis zu einer Tiefe von 10 verschachtelt werden. Wird diese Grenze überschritten, dann bricht Yabu mit diesem Fehler ab. Eine mögliche Ursache ist, daß .include-Anweisungen in verschiedenen Dateien unbeabsichtigt eine Schleife bilden, zum Beispiel:

#Buildfile.1
include Buildfile.2

und

#Buildfile.2
include Buildfile.1