|
|||
Der EasyFileGenerator ist ein Datei Generator geschrieben in Perl, der nicht nur Makefiles in paar Augenblicken generieren kann.
Mit diesem Tool lassen sich alle Make-Träume erfüllen und Alpträume in Zukunft vermeiden! ;-)
Ralf Peine <ralf.peine@jupiter-programs.de
>
Aufgewachsen in Bochum (geb. 1965) siedelte ich nach dem
Mathematik-Studium in Dortmund (Diplom 1991) nach Erlangen über und
entwickelte bei der Siemens AG (KWU OI41) SW-Tools für die
Kraftwerksleittechnik. 1994 schrieb ich meine ersten Perl-Scripts
unter HP UX (Unix) u.a. für SQL-Datenbanken und das Configuration
Management. Seit 1998 arbeite ich für die SW-Entwicklung der Cordless
Products (ICM CP RD SD GES SDS) in Bocholt und entwickelte dort
u.a. größere Tools mit Perl unter Microsoft Windows. 2001 nahm ich zum
ersten Mal am "
Deutschen Perl Workshop"
teil und hielt 2003
meinen ersten Vortrag im Perl-Workshop über "
Objektorientiertes
Perl, XML und große Datenmengen"
.
Wer sich schon mal mit dem Make- / Build-Management-Tool Imake beschäftigt (oder gequält) hat, kennt vermutlich auch die folgende, besonders hilfreiche Fehlermeldung vom cpp:
Error in line 12347: ")" missing...
Da der cpp keine Variablen, sondern nur Macros (Funktionen) kennt, muss man dieses Manko bei Imake durch Verwendung von genügend Klammern ausgleichen:
SRC (Comfort, (HP800, WIN32), CONCAT(DIR(util/calc), calc.c) ...
Dass oben eine Klammer fehlt, sieht man doch auf den ersten Blick! (Oder etwa doch nicht ?)
Der Klammerdschungel in den Regeln trieb mich fast zur Verzweiflung, als ich '97 unser CM (unter HP-UX und SCO-Unix) von RCS auf ClearCase umstellen und dazu alle Regeln anpassen musste.
Inzwischen arbeite ich seit 6 Jahren unter Microsoft Windows. Windows hat u.a. einen entscheidenden Unterschied zu Unix: Es gibt dort kein Imake! Das stellte mich vor vier Jahren vor die Wahl:
"
Schweren Herzens"
entschied ich mich für (2) und entwickelte
einen eigenen Generator. Jetzt, mit dem 3. Design, ist mir mit dem
"
Easy File Generator"
ein OO-Entwurf gelungen, den ich Euch in
einem Vortrag vorstellen möchte, weil er einige Perl-Features
(u.a. require und compile zur Laufzeit) optimal ausnutzt und so leicht
nur in Perl realisierbar ist.
Der Easy File Generator erzeugt durch den Aufruf von Regeln in
*.efg-Dateien beliebig viele weitere Dateien. Für die Anwender sind
keine Programmierkenntnisse in Perl oder anderen Programmiersprachen
erforderlich. Anführungszeichen, Escape-Sequenzen, Kommata habe ich in
der Anwendung der Regeln nahezu komplett gestrichen, Klammern werden
nur für Variablen z.B. "
$(platform)"
, "
$(suffix)"
benötigt:
&SRC Comfort HP800 ui/X11/display display.c &OUT Comfort Win32|HP800 ui\$(platform) viewer$(suffix)
Die Regeln werden direkt in Perl geschrieben, man kann alle (wirklich
alle!) Perl-Statements verwenden und sogar - wenn man das gut findet
und nicht anders kann - alles in einer *.efg-Datei beliebig
miteinander kombinieren. Die Möglichkeiten von Perl zum Tracen und
Debuggen erleichtern das Erstellen der Regeln enorm. Syntaxfehler in
den *.efg Dateien werden mit Angabe der fehlerhaften Zeile/Zeichen
gemeldet. Das macht das Generieren von Files wirklich "
easy"
!
(Hoffentlich sehen die Anwender das genauso...)
Im Prinzip läuft der EasyFileGenerator auf jeder Plattform, auf der es Perl gibt, da er ausschliesslich plattformunabhängige Perl-Module verwendet. Testen konnte ich bisher nur Linux und Win32, aber ich erwarte für andere Plattformen keinen nennenswerten Portierungsaufwand.
Die Anwendungsmöglichkeiten des EasyFileGenerator sind vielfältig, er lässt sich z.B. auch zum Erstellen von Web-Seiten oder zum Katalogisieren von DVDs verwenden.
Ende 1999 erhielt ich den Auftrag, für die neu gegründete Bluetooth Abteilung bei Siemens in Bocholt eine Software Entwicklungsumgebung aufzubauen. Windows-NT als Betriebssystem und das Microsoft-Developer Studio als Developer Workbench waren als Eckpunkte vorgegeben.
Da uns das bisher verwendete SIEMAKE (eine Eigenentwicklung
von Siemens Bocholt) wegen seiner fest einprogrammierten Regeln nicht
flexibel genug war, suchte ich nach einem Makefilegenerator für
Windows. Als Configuration Manager bei Siemens in Erlangen hatte ich
bereits fünf Jahre Makefiles mit "
Imake"
generiert und wollte
eine Implementierung für MS-Windows einsetzen - fand aber nur eine
Java Variante, die nicht sehr vielversprechend erschien.
Das stellte mich vor die Wahl, entweder Imake zu portieren oder einen eigenen Makefilegenerator - natürlich in Perl - zu schreiben. Ich stellte also folgenden Anforderungskatalog auf:
1 Syntax 1.1 Einfach
2 Varianten 2.1 Konfigurierbar 2.2 Alle auf einmal 2.3 Einzeln
3 Zielsysteme 3.1 Konfigurierbar 3.2 Alle auf einmal 3.3 Einzeln 3.4 (Compiler)
4 Regelsprache 4.1 Mächtig 4.2 Standard
5 Fehlersuche 5.1 Testausgaben 5.2 Debugging
6 Fehlermeldungen 6.1 File, Zeile und Position
7 Plattformen (Generator) 7.1 Möglichst viele 7.2 Möglichst plattformunabhängig, d.h. kein Portierungsaufwand bei Plattformwechsel des Makefile-Generators
8 Überlagerung von allg. Rules durch projektspezifische
und implementierte dann - laut Lehrbuch - gemäß Wasserfallmodell
Analyse -> Design -> Implementierung -> Test
den EasyFileGenerator, fertig!
Da das "
real life"
sich wie ein bockiger Esel
nicht um Lehrbücher und Prozesse aus denselben schert, verlief die
Entwicklung des EasyFileGenerators natürlich ganz anders. Wie genau,
möchte ich im folgenden darstellen und dadurch auch die Gründe für
meinen Ansatz zur Generierung von Makefiles erläutern. Von dem
Anforderungskatalog waren mir damals (1999) z.B. erst ein Drittel der
Punkte bekannt.
Bevor der Easy File Generator vorgestellt wird, gebe ich hier noch
eine kurze Einführung in Make, Make-Rules und
SW-Build-Verfahren. Wer sich hier gut auskennt
kann auch gleich im nächsten Kapitel "
Einfache Syntax"
weiterlesen.
Makefiles werden vom Configuration Management dazu verwendet, um ein Programm (-System) aufzubauen (build), vergleichbar mit einer vollautomatischen Produktionsstraße z.B. für Autos: Verschiedene Teile müssen auf unterschiedliche Weise hergestellt (geformt) und in der richtigen Reihenfolge montiert werden. Komplizierte Abfolgen, Vorratshaltungen usw. sind möglichst zu vermeiden weil schwer zu automatisieren und fehleranfällig.
Das allseits bekannte "
Hello World"
Programm (hello.c) läßt sich
noch vom Compiler "
zusammenschrauben"
:
// Hello world include <stdio.h> void main() { print "Hello World!\n"; }
>cc hello.c
fertig ist das Programm. Perl benötigt ebenfalls kein Make, weil
Perlscripts vor dem Start von Perl compiliert werden und in den
Scripts selbst die Reihenfolge der Compilierung festgelegt ist. (Der
Perl-Kernel die C-Libraries sind beim Aufruf von perl ja bereits
"
gemaked"
.)
Müssen in C mehrere Files compiliert werden, kann man Batches oder Shell Scripts einsetzen. Dann wird jedes File bei jedem neuen Build-Lauf erneut compiliert. Das führt zu großen Laufzeitverlusten, wenn man mehrere tausend Files compilieren muss, obwohl sich nur ein einziges geändert hat. (Microsoft hat das aber nicht davon abgehalten, für die WindowsME Entwicklungsumgebung statt Make Batches einzusetzen...)
Um die Laufzeit zu verkürzen, kann man das Tool "
Make"
und
Makefiles verwenden. Make sorgt dafür, dass nur die Objects
neu erzeugt (compiliert) werden, deren Sourcen seit dem letzten Build
verändert wurden. Dazu werden von Make die Zeitstempel
verglichen: Ist die Source Datei neuer als das dazugehörige Object,
wird es neu compiliert. Damit das funktioniert, muss Make wissen,
welches Object durch die Compilierung welcher Source File (s) erzeugt
wird. Dazu werden im einfachsten Fall die folgenden Build Rules
aufgestellt:
OBJECTS = a.o b.o c.o d.o # (usw.)
.c.o : $(CC) $(CFLAGS) .c $<
file.exe: $(OBJECTS) $(LD) -o file.exe $(LFLAGS) $(OBJECTS)
ALL:: file.exe
mit
make ALL
wird das Programm schliesslich vollautomatisch von Make generiert (build).
Wenn ein C-File a.c ein Header-File a.h includiert, muss a.c auch neu übersetzt werden, wenn a.h geändert wurde. Damit make das auch weiß, müssen diese Abhängigkeiten in das Makefile eingetragen werden. Keine gute Idee ist das manuelle Pflegen der Abhängigkeitsliste, was fast zwangsläufig zu sehr schwer zu findenden SW-Fehlern führt. Besser ist es, die Abhängigkeiten von einem Tool erfassen zu lassen und in einer extra Datei zu speichern, die an das Makefile angehängt werden kann. Aber auch hier können Abhängigkeiten von den Tools übersehen werden.
Die einzige mir bekannte Lösung, in jedem Fall alle Abhängigkeiten zu ermitteln, bietet der Einsatz von ClearCase und omake (Windows) bzw. clearmake (UNIX). omake/clearmake arbeiten auf einem speziellen Filesystem von ClearCase, welches jedes gelesene File während des Compiles mitprotokolliert und sich dies für jedes durch make erzeugte Object im sogenannten Configuration Record speichert. Beim nächsten Makelauf können dann zusätzlich zu allen in den Regeln angegebenen Abhängigkeiten auch noch die im vorigen Lauf gespeicherten berücksichtigt werden, wodurch kein notwendiges Compile mehr ausgelassen wird. omake/clearmake erkennen sogar geänderte Make-Rules!
In größeren Projekten gibt es meist ein Files (hoffentlich nur wenige), die eine besondere Behandlung erfordern, spezielle Compilerschalter, Precompiles oder ähnliches. Mit Standard Make ist das nicht leicht zu handhaben, weil man dann für jede abweichende Datei eine eigene Makerule schreiben muss:
a.o: a.c CC -o a.o $(CC_ARGS_a) a.c
b.o: b.c CC -o b.o $(CC_ARGS_b) b.c
c.o: c.c CC -o c.o $(CC_ARGS_c) c.c
...
Änderungen an den Makerules werden jetzt zu einer aufwendigen und fehleranfälligen Aufgabe, denn eine (generierte) Makezeile aus einem realen Projekt sieht z.B. so aus:
..\p_appl\cfgsrv\pc_prc.o66: ..\p_appl\cfgsrv\pc_prc.c @echo --- Target Compile ------ ..\p_appl\cfgsrv\pc_prc.c -------------- @CC166 -I..\..\..\tools\complink\task_166\C166\include \ -Id:\prj\jupiter\jupiter\src\imake\..\global \ -Id:\prj\jupiter\jupiter\src\imake\.. \ -Id:\prj\jupiter\jupiter\src\imake\..\config \ -Id:\prj\jupiter\jupiter\src\imake\..\coreadap \ -Id:\prj\jupiter\jupiter\src\imake\..\coreadap\bt_pt_sw \ -Id:\prj\jupiter\jupiter\src\imake\..\coreadap\btos \ -Id:\prj\jupiter\jupiter\src\imake\..\coreadap\btswte \ -c -tmp -v -WaNOMOD166 -WaSN(REG172BT.DEF) -Ml -x -s -g -t \ -T0 LISTALL EP DEBUG SG SN(REG172BT.DEF) NOMOD166 \ -c ..\p_appl\cfgsrv\pc_prc.c -o ..\p_appl\cfgsrv\pc_prc.o66 @del /F ..\p_appl\cfgsrv\pc_prc.erl @del /F ..\p_appl\cfgsrv\pc_prc.src @move pc_prc.erl ..\p_appl\cfgsrv @move pc_prc.src ..\p_appl\cfgsrv
Eine Erleichterung schaffen Make-Erweiterungen wie nmake, omake, clearmake usw., die eine weitergehende Konfigurierung der Makefiles ermöglichen.
Wir setzten ab 1996 unserer eigenes Maketool ein: SIEMAKE
(Siemens Make), ein exakt auf die Bedürfnisse in der SW-Entwicklung
für Gigasets (Schnurlose DECT Telefone)
zugeschnittenes Make, das auch die Behandlung von Varianten
unterstützt. Der Haken dieses Tools sind die in "
C gegossenen"
Rules, welche nicht so einfach erweitert und projektspezifisch
angepasst werden können.
Einfacher gestaltet sich die Pflege der Makerules, wenn man Makefile
Generatoren einsetzt, um im 1. Schritt Makefiles zu generieren, welche
dann im 2. Schritt über den Aufruf von "
make"
das Programm
erzeugen. Man gibt dann für jedes File eine Regel vor, die angewendet
werden soll. Mit Generator Regeln definiert man
dann für den Makefile Generator, wie die angegebenen
Parameter in Makeanweisungen umgeschrieben werden
sollen. Die Regeln für den Generator können zentral und getrennt von
den Projektdaten erstellt und gepflegt werden. Ein weiterer Vorteil
des Regelbasierten Verfahrens besteht
darin, dass nur eine Regel geändert werden muss, um für 1000 oder mehr
C-Files die Build-Rule im Makefile anzupassen. Unterschiedliche
Build-Rules sind leicht an verschiedenen Generator Regeln zu erkennen.
Zusätzlich hat man die Möglichkeit, in den generierten Makefiles nach Fehlern zu suchen, mit neuen Rules zu experimentieren und die Compiler Flags zu überprüfen/anzupassen (vorausgesetzt, der Generator lässt keine Make Variablen stehen...).
Es existieren eine Vielzahl von Makefilegeneratoren, die alle ihre Stärken und Schächen haben und oft vom Configuration Management in Eigenregie aus der Not heraus entwickelt wurden (wie auch der EasyFileGenerator).
Perls "
MakeMaker"
wurde nicht als allgemeiner
Makefilegenerator entworfen, siehe .../Perl/lib/ExtUtils/MakeMaker.pm:
This utility is designed to write a Makefile for an extension module from a Makefile.PL.
Imake z.B. verwendet den C-Preprozessor cpp und kann somit in den Rules nur Strings verketten. Zählungen der Files und der angewendeten Rules sind daher nicht möglich:
SRC(File(dir_a\dir_af, file.c), Variants(HP800, SCO))
Wie bereits in der Einleitung beschrieben, führt eine vergessene schließende Klammer lediglich zu der Fehlermeldung:
Error in line 12347: ")" missing...
Das Einzige, was hier weiterhilft, ist die "
binäre"
Suche nach
der öffnenden Klammer: Etwas Code entfernen und prüfen, ob der Fehler
noch auftritt. Wenn ja, im nächsten Schritt etwas mehr Code entfernen,
wenn nein, etwas weniger Code entfernen. Mit Markierungen zwischen den
Regeln lässt sich das Ganze auch mit Perl automatisieren, aber gerade
das will der EasyFileGenerator ja vermeiden...
Einige wenige weitere Generatoren finden sich z.B. im Google Directory:
Computers > Software > Build Management > Makefile Generators.
Diese Seite findet sich auch in den Goodies.
Die Behandlung von unterschiedlichen Plattformen und Varianten für die zu generierenden Objects wird unübersichtlich bis problematisch, wenn bestimmte Files nur für bestimmte Varianten benötigt werden. Noch schwieriger ist es, projektspezifische Varianten von den Rules selbst zu erstellen.
Die meisten Makefile Generatoren verwenden eine eigene Syntax, sodass der Umstieg auf einen anderen Generator nur dann durchgeführt wird, wenn es unumgänglich ist. Denn zuerst muss der Configuration Manager die Syntax der neuen Regelsprache beherrschen, bevor er sich an die mühsame, zeitaufwendige und fehlerträchtige Aufgabe wagen kann, die Regeln umzustellen. Und zum Schluss müssen die Entwickler lernen, wie die neuen, andersartigen Regeln anzuwenden sind.
Da gab es soviele Möglichkeiten, etwas zu verbessern, dass ich gar
nicht wusste, womit ich beginnen sollte. Um eine hohe Akzeptanz des
neuen Tools zu erreichen, kümmerte ich mich zuerst um die
"
Anwenderschnittstelle"
, die Syntax der Rule-Calls.
Gemäß der Entwicklungsphilosophie "
Fortschritt ist der Weg vom
Primitiven über das Komplizierte zum Einfachen"
definierte ich
zunächst eine sehr einfache Syntax für den Makefilegenerator, die sehr
ähnlich zu der war, die bereits SIEMAKE verwendete:
SRC dir_a\dir_af file.c HP800|SCO
Lediglich ein & wurde von mir vorangestellt, damit man den Aufruf der Regeln leicht erkennen konnte:
&SRC dir_a\dir_af file.c HP800|SCO
Eine Regel fängt also immer mit & an und ist am Zeilenende auch zuende. Als Trenner zwischen den Argumenten werden (beliebig viele) Whitespaces, also Leerzeichen und Tabs, verwendet. Das ist deutlich übersichtlicher und einfacher als die Klammerung in Imake:
SRC(File(dir_a\dir_af, file.c), Variants(HP800, SCO))
Wenn man den Call direkt in Perl absetzt, wird es auch nicht einfacher:
&SRC( 'dir_a\dir_af', 'file.c', 'HP800|SCO' );
weil man berücksichtigen muss, dass viele der Anwender ( SW-Entwickler) mit Perl nicht vertraut sind und so besonders bei der Verwendung der Zeichen
\ $ @ & % ( ) ' "
Probleme entstehen können.
Ausgehend von dieser Grundidee entwickelte ich eine eigene Syntax und einen Interpreter, der Variablen setzen, if-then-else-endif und ein paar Befehle mehr ausführen konnte. Allein das mehrstufige if-then-else-endif hat mich 4 Wochen Entwicklungsarbeit gekostet, weil das Tool, um Zeit zu sparen, immer nur den durch if selektierten Teil weiter geparst und den anderen Zweig überlesen hat (natürlich mussten if-then-else-endif weiterhin beachtet werden, damit auch das passende endif gefunden wurde).
Bald zeigte sich, dass die Syntax erweitert werden musste, um die Makefile Generierung durchführen zu können. Ausserdem wurde die Laufzeit mit der benötigten Stack-Tiefe (Calls in Calls in Calls...) deutlich größer. Und die Entwickler hatten keine Zeit, eine Spezialsprache zum Erstellen der Rules zu lernen, sodass ich selbst die meisten Make-Rules verfasste.
Doch schließlich lief der Generator, und ich konnte 2001 zum ersten
Mal am "
Deutschen Perl-Workshop"
teilnehmen.
Am zweiten Tag des Workshops fiel mir auf, dass ich einfach nur die fehlenden Zeichen
('', "");
ergänzen musste, um ein Perl-Command zu erhalten:
&SRC dir_a\dir_af file.c HP800|SCO &SRC( 'dir_a\dir_af', 'file.c', 'HP800|SCO' ); # Perl !
Geleistet wird das ganze dann (für eine Zeile $l) durch den folgenden Code:
if ($l =~ /\s*^(\#)/o) { # --- write # comment --- ... } elsif ($l =~ /^\s*&(\w+)\s*(.*)/o) { # &rule_call 1 2 3 4 $rule = $1; $argstr = "$2\n"; if ($argstr =~ /^\(/io) { # &rule_call (1, 2, 3, 4); # --- rule startet with "(" --> standard perl --> just print out print OUTP $l; next; }
while ($argstr) { if ($argstr =~ /^\'((?:[^\']|\'\')+)\'/o) { # --- 'String' recognizing, substituting '' to \' --- $pval = $1; $argstr = $'; $pval =~ s/\'\'/\\\'/go; # ' # for emacs push (@cmdArr, "'$pval'"); } elsif ($argstr =~ /^\"(([^\"]|\"\")+)(\")/o) { # --- "String" recognizing, substituting "" to \" --- $pval = $1; $argstr = $'; # ' # for emacs $pval =~ s/\"\"/\\\"/go; push (@cmdArr, "\"$pval\""); } elsif ($argstr =~ /^([^\s\'\"]+)/o) { # --- handle string up to next white space, ' or " --- $pval = $1; $argstr = $'; # ' # for emacs push (@cmdArr, "'$pval'"); } else { last; } $argstr =~ s/^\s+//o; } }
$fh->write ("$sub (".join(', ', @cmdArr).");";
Das war der Start für die 2. Generation meines Makefile Generators. Die Regeln wurden ab jetzt in Perl geschrieben. Die Ausführung wurde dadurch deutlich beschleunigt: Der soeben erzeugte Perl-Code wurde einfach abgespeichert, per require eingezogen und ausgeführt. Ausserdem konnte ab jetzt jedes Perl-Kommando in den Makefiles verwendet werden, denn alle Zeilen, die nicht mit
# // &
anfangen, wurden automatisch als Perl interpretiert und unverändert
übernommen. Perl-Subs wurden von den Rules dadurch unterschieden,
dass nach dem einleitenden &src noch eine Klammer auf "("
folgte:
&src ( 123 ); # Perl &src 123 # kein Perl mit 7! Argumenten # 1 2 3 4 5 6 7
Zusätzlich gab es noch ein paar Spezialfunktionen, z.B.
&include file.imk # wie require, doch vorher parsen &setVar var dir1\dir2
und Variablen, die bereits vom Parser ausgewertet wurden
&include $(var)\file.imk
Aber einige wichtige Punkte aus dem Anforderungskatalog fehlten noch, nämlich
2 Varianten 2.1 Konfigurierbar 2.2 Alle auf einmal 2.3 Einzeln
3 Zielsysteme 3.1 Konfigurierbar 3.2 Alle auf einmal 3.3 Einzeln 3.4 (Compiler)
8 Überlagerung von allg. Rules durch projektspezifische
Auch waren der Parser und der Generator noch nicht getrennt.
Der EasyFileGenerator implementiert schliesslich alle noch offenen Punkte des Anforderungskatalogs.
Die Syntax der Inputfiles bleibt fast unverändert:
$g1->src ( 123 ); # Perl &src -all 123 # kein Perl mit 8! Argumenten
&include file.imk # wie require, doch vorher parsen &setVar -all var dir1\dir2 &include $(var)\file.imk
Beim Rule-Call und bei setVar wird ein weiteres Argument benötigt: Der Generator, der die Rule durchführen oder für den die Variable gesetzt werden soll.
Der Parser und die Generatoren sind als getrennte Objekte
implementiert. Für jede Compilervariante kann eine eigene
Generatorklasse von Efg::Generator abgeleitet werden, von der dann für
jede Produktvariante eine Instanz mit eigenen Parametern versorgt
werden kann. Vom Parser genügt eine Instanz, aber Ableitungen von
Efg::Parser sind natürlich ebenfalls möglich. Der EasyFileGenerator
zieht dann per require als erstes eine Datei (z.B. prepare.pl) ein, in
der Parser und Generatoren erzeugt und mit Parametern versorgt werden.
Dazu wird in dieser Datei die Funktion
"
&buildUpEfg"
definiert, die von main zur Initialisierung
aufgerufen wird:
# prepare.pl
use strict;
use Efg; use Efg::Generator::HP800Compiler; use Efg::Generator::SCOCompiler; use Efg::Generator::WinCC; use Efg::Generator::MSDevStudio; use Efg::Generator::Xml; use Efg::Parser::MyParser;
# --- will be called by main to setup the Efg and the generators --- sub buildUpEfg {
my $efg = new Efg;
$efg->setParser(new Efg::Parser::MyParser());
my $g1 = new Efg::Generator::HP800Compiler(); my $g2 = new Efg::Generator::SCOCompiler(); my $g3 = new Efg::Generator::WinCC(); my $g4 = new Efg::Generator::MSDevStudio(); my $g5 = new Efg::Generator::Xml(); # *.c-Files only my $g6 = new Efg::Generator::Xml(); # all files
$g1->Config(OUTPUT_DIR => "$CFG{OUTPUT_DIR}\\g1", BLUBB => 'blubber'); $g5->Config(FILE_TYPE => '*.c'); # *.c-Files only
$g1->outputFile('HP800.mk'); $g2->outputFile('SCO.mk'); $g3->outputFile('WinCC.mk'); $g4->outputFile('DevStudio.prj'); $g5->outputFile('files_c.xml'); $g6->outputFile('files_all.xml');
$efg->addGenerator('HP800', $g1); $efg->addGenerator('SCO', $g2); $efg->addGenerator('WinCC', $g3); $efg->addGenerator('MSDev', $g4); $efg->addGenerator('xml.c', $g5); $efg->addGenerator('xml.all', $g6);
return $efg; }
1;
Es werden sechs Dateien generiert, drei Makefiles für HP800, SCO (beide Unix) und WinCC, eine Projektdatei für das Microsoft Developer Studio und zwei XML Dateien.
Aus jedem Rulecall werden jetzt bis zu sechs Generator Calls:
&SRC -all a2 a3 a4
wird zu
$g1->SRC('-all', 'a2', 'a3', 'a4'); $g2->SRC('-all', 'a2', 'a3', 'a4'); $g3->SRC('-all', 'a2', 'a3', 'a4'); $g4->SRC('-all', 'a2', 'a3', 'a4'); $g5->SRC('-all', 'a2', 'a3', 'a4'); $g6->SRC('-all', 'a2', 'a3', 'a4');
und
&SRC HP800|SCO b2 b3 b4
wird zu
$g1->SRC('HP800|SCO', 'b2', 'b3', 'b4'); $g2->SRC('HP800|SCO', 'b2', 'b3', 'b4');
Aufgerufen wird der EasyFileGenerator schliesslich mit:
perl -w efg.pl --config ../src/config.pl
Die Datei config.pl enthält die Voreinstellungen des EasyFileGenerators:
# cfg.pm
use strict; use CFG;
$CFG{"INPUT_DIR"} = '../src';
$CFG{"OUTPUT_DIR"} = '../gen';
$CFG{prepare_pl} = 'prepare.pl'; $CFG{genRules_pm} = 'genRules.pm';
$CFG{start_efg} = 'start.efg';
1;
diese können durch Shell Variablen
SET EFG_VARIANT=A
und diese wiederum durch Aufrufparameter --data VAR=VALUE überschrieben werden:
perl -w efg.pl --data OUTPUT_DIR=../gen --config ../src/config.pl
Man beachte das den Shell Variablen vorangestellte "
EFG_"
,
welches die Eindeutigkeit der Namen garantiert und vor der Speicherung
der Variablen im Hash "
%CFG"
entfernt wird. Die Shell Variable
"
EFG_VARIANT"
wird demnach unter dem Key "
VARIANT"
im Hash
abgelegt.
$CFG{prepare_pl}
enthält den Dateinamen mit der oben angegebene Startup Funktion, und
$CFG{genRules_pm}
definiert die Rules für die einzelnen Generatoren. Ein
use Efg::Generator::GenHP800; use Efg::Generator::GenSCO; use Efg::Generator::GenWinCC;
ist ausreichend (vorausgesetzt, man hat die Klassen irgendwo definiert, wo perl sie auch finden kann...). Noch einfacher kann man auch die Regeln direkt für Efg::Generator definieren:
# genRules.pm # # Rules for the standard generator
use strict;
package Efg::Generator;
sub SRC { my $self = shift; my @parArr = $self->parseForAndInsertVariables(@_);
# print join ("\t", $self->{_fh}, @parArr)."\n";
$self->write(join ("\t", @parArr)."\n"); }
1;
"
Enthalten die Parameter der Regel "
White-Space"
Zeichen, $( , ' oder
"
, werden folgende Escape Sequenzen verwendet:
1: &SRC 'dir_a\dir_af' "file"".c" HP800|SCO 2: &SRC 'Program Files' 'file''A.c' HP800|SCO 3: &SRC $$(dir) fileA.c HP800|SCO 4: &SRC '$(dir)' fileA.c HP800|SCO
Innerhalb von '...' wird ' durch Verdopplung escaped, also ' ' statt '
verwenden (2). "
wird innerhalb "
..."
ebenso escaped (1). Ein $ wird
ebenfalls mit sich selbst escaped (3), d.h. $$ -> $. White Space Zeichen
müssen von '...' oder "
..."
umschlossen sein (2), um nicht als
Parametertrenner angesehen zu werden. "
..."
und '...' (!)
Strings werden dann vom Generator nach Variablen $(Abc1) durchsucht (4)
und die Variable durch den Wert von
$self->{"_var_Abc1"}
ersetzt. Dabei muss man berücksichtigen, dass Perl den String
"$val \t\n"
ebenfalls wie gewöhnlich interpretiert (also einen Tab und ein Newline einfügt sowie den Wert der Perl-Variablen $val) und
'$val \t\n'
unverändert an die Rule weitergegeben wird.
Variablen werden durch
&setVar -all var1 dir1\dir2
gesetzt und der Wert kann mit
$(var1)
wieder abgerufen werden. Der erste Parameter von setVar gibt an, für welchen Generator die Variable gesetzt werden soll.
-all: # Variable für alle Generatoren setzen.
HP800|SCO # Variable für die Generatoren setzen, die unter den Namen # HP800 bzw. SCO bekannt sind.
Der Parser wandelt dann die Regel setVar genauso wie alle anderen auch in Perl-Calls um:
$g1->setVar( 'var1', 'dir1\dir2'); $g2->setVar( 'var1', 'dir1\dir2'); $g3->setVar( 'var1', 'dir1\dir2'); $g4->setVar( 'var1', 'dir1\dir2'); $g5->setVar( 'var1', 'dir1\dir2'); $g6->setVar( 'var1', 'dir1\dir2');
Diesen Mechanismus zum Setzen von Variablen könnte man auch zur Steuerung des Parsers verwenden, aber bisher habe ich das noch nicht benötigt.
Die Regel
&include inc.efg
in der Datei start.efg weist den Parser an, die Datei "
inc.efg"
zu parsen. Das Ergebnis "
inc.efg.pl"
wird als require in die
Perl Ergebnisdatei start.efg.pl geschrieben:
require '../gen/inc.efg.pl';
Das '../gen/' wird durch die Generator Variable "
OUTPUT_DIR"
erzeugt.
Sollen mehrere Zeilen unverändert in die Ausgabedatei (Makefile) übernommen werden, kann man das einfach mit
&fix_text HP800|SCO << EnDe Das ist ein kurzer Test Text. This is a short test text. Das ist ein weiterer kurzer Test Text. This is another short test text. Das ist der letzte kurze Test Text. This is the last short test text. EnDe
erledigen. Die Generatoren für "
HP800"
und "
SCO"
schreiben
dann den ganzen Text unverändert ab der nächsten Zeile bis zum Ende
Tag,
EnDe
das nach dem << angegeben wurde und am Zeilenanfang stehen muss. Das Leerzeichen als Trenner zwischen << und Ende Tag ist zwingend vorgeschrieben. (Im Gegensatz zu den UNIX Shells, die dort kein Leerzeichen erlauben.)
Projektspezifische Überlagerung von Regeln
wird einfach durch Ableitung des Generators
durchgeführt. Die abweichenden Regeln können dann in der
Unterklasse neu definiert werden. In der Funktion
"
&buildUpEfg"
muss dann nur eine
Instanz der Subklasse erzeugt und mit dem richtigen Namen versehen
werden.
Dank Perl ist es mir gelungen, den EasyFileGenerator mit 7 Dateien und insgesamt weniger als 20 KB Speicherplatz zu implementieren, die Rules nicht mitgerechnet. Die Sourcen finden sich, zusammen mit einer Testumgebung, unter den Goodies auf der Workshop CD und demnächst auf meiner Homepage http://www.jupiter-programs.de. Die Veröffentlichung der SW auf dem CPAN kann erst dann erfolgen, wenn alle Bedingungen für das CPAN erfüllt sind, was wohl noch ein wenig dauern wird.
Nicht existierende Zielverzeichnisse werden noch nicht durch die Generatoren angelegt.
Im folgenden möchte ich noch ein paar neue Anforderungen beschreiben, auf die ich während der Implementierung gestoßen bin.
Die verschiedenen Zeichenfolgen für Newline in MSWindows (#0D0A) und LINUX (#0A) müssen im Generator und im Parser konfigurierbar werden. Bis dahin kann der Generator (für ein Projekt) entweder auf UNIX oder auf MSWindows gestartet werden, weil es Probleme mit dem Matching von <newline> geben kann.
Als Verzeichnistrenner fungieren zur Zeit
\ und /, alle \ werden für Perl in / umgewandelt. Das kann für
Calls unter MS-Windows problematisch werden, da hier die Command
Options oft mit vorangestelltem / und nicht mit - eingeleitet
werden. Andererseits ist die Verwendung von \ in Perl umständlich und
fehleranfällig, besonders in Strings der Art "
Text"
. Falls
jemand dafür eine "
easy"
Lösung findet, nur her damit!
Die Auswahl der Generatoren, die die Rule ausführen sollen, wird zur
Zeit fest an den ersten Parameter gebunden. In
einer der nächsten Versionen können die Parameter (1 bis n), die vom
Parser zur Auswahl der Generatoren verwendet werden sollen, noch
global und für jede Rule einzeln eingestellt werden. Eine "
easy"
Lösung ist mir dafür leider noch nicht eingefallen.
Es könnte erforderlich sein, von einem Generator mehrere
Ausgabedateien erstellen zu lassen. Dazu möchte ich einen "
File Stack"
realisieren, der das aktuelle File
schliesst, das nächste öffnet und nach Fertigstellung wieder auf das
alte File zurückschaltet.
# schreibe in file Makefile.HP800
# rules, rules, rules ...
# schreibe ab jetzt in "dasIstEinNeuesMakefile.mk"
&startFile HP800 dasIstEinNeuesMakefile.mk
# rules, rules, rules ...
&endFile HP800
# schreibe wieder in file Makefile.HP800
In größeren Systemen, die aus vielen zu generierenden
Komponenten (Programmen u.ä.) bestehen, sollte man den
EasyFileGenerator für das Gesamtsystem, ein beliebiges
Subsystem oder nur für eine einzelne Komponente starten
können. Auch dafür suche ich noch nach einer "
easy"
Lösung.
Ich hoffe, dass der eine oder andere sich an den EasyFileGenerator erinnert, wenn es daran geht, Makefiles zu generieren. Viele andere Wahlmöglichkeiten (noch dazu kostenfreie) bieten sich nicht an, Google hat lediglich fünf Makefilegeneratoren im Angebot (MakeMaker befindet sich merkwürdigerweise nicht darunter). In den vielen anderen von mir durchsuchten Web-Verzeichnissen habe ich bisher keine weiteren Generatoren finden können.
Anregungen, Fehlermeldungen und Erweiterungswünsche zum EasyFileGenerator sind immer willkommen und natürlich leiste ich auch Hilfestellung bei der Anwendung / Einführung.
Weitergehende Dokumentation und Tutorials werde ich (hoffentlich bald) auf meiner Homepage veröffentlichen.
Ralf Peine, im Mai 2004
"
Perl: every time my favorite camel"
siehe Goodies u. http://directory.google.com/Top/Computers/Software/Build_Management