Browse Source

+ Initial implementation of new fpmake system

git-svn-id: trunk@436 -
michael 20 years ago
parent
commit
7c1ce9f49e
6 changed files with 4129 additions and 0 deletions
  1. 5 0
      .gitattributes
  2. 162 0
      rtl/common/fpmake.lpi
  3. 24 0
      rtl/common/fpmake.pp
  4. 198 0
      rtl/common/fpmkpkg.lpi
  5. 961 0
      rtl/common/fpmkpkg.pp
  6. 2779 0
      rtl/common/fpmkunit.pp

+ 5 - 0
.gitattributes

@@ -3293,6 +3293,11 @@ rtl/bsd/system.pp svneol=native#text/plain
 rtl/bsd/unxsysch.inc svneol=native#text/plain
 rtl/bsd/x86_64/syscall.inc svneol=native#text/plain
 rtl/bsd/x86_64/syscallh.inc svneol=native#text/plain
+rtl/common/fpmake.lpi svneol=native#text/plain
+rtl/common/fpmake.pp svneol=native#text/plain
+rtl/common/fpmkpkg.lpi svneol=native#text/plain
+rtl/common/fpmkpkg.pp svneol=native#text/plain
+rtl/common/fpmkunit.pp svneol=native#text/plain
 rtl/darwin/Makefile -text
 rtl/darwin/Makefile.fpc svneol=native#text/plain
 rtl/darwin/console.pp svneol=native#text/plain

+ 162 - 0
rtl/common/fpmake.lpi

@@ -0,0 +1,162 @@
+<?xml version="1.0"?>
+<CONFIG>
+  <ProjectOptions>
+    <PathDelim Value="/"/>
+    <Version Value="5"/>
+    <General>
+      <Flags>
+        <MainUnitHasUsesSectionForAllUnits Value="False"/>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+      </Flags>
+      <MainUnit Value="0"/>
+      <ActiveEditorIndexAtStart Value="0"/>
+      <IconPath Value="./"/>
+      <TargetFileExt Value=""/>
+    </General>
+    <JumpHistory Count="16" HistoryIndex="15">
+      <Position1>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="200" Column="35" TopLine="161"/>
+      </Position1>
+      <Position2>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="1600" Column="16" TopLine="1600"/>
+      </Position2>
+      <Position3>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="21" Column="1" TopLine="21"/>
+      </Position3>
+      <Position4>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="316" Column="21" TopLine="285"/>
+      </Position4>
+      <Position5>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="1116" Column="28" TopLine="1086"/>
+      </Position5>
+      <Position6>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="316" Column="7" TopLine="316"/>
+      </Position6>
+      <Position7>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="317" Column="30" TopLine="297"/>
+      </Position7>
+      <Position8>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="1145" Column="33" TopLine="1121"/>
+      </Position8>
+      <Position9>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="1116" Column="26" TopLine="1096"/>
+      </Position9>
+      <Position10>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="316" Column="31" TopLine="316"/>
+      </Position10>
+      <Position11>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="1138" Column="22" TopLine="1114"/>
+      </Position11>
+      <Position12>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="1141" Column="21" TopLine="1121"/>
+      </Position12>
+      <Position13>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="1689" Column="10" TopLine="1671"/>
+      </Position13>
+      <Position14>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="634" Column="1" TopLine="603"/>
+      </Position14>
+      <Position15>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="631" Column="17" TopLine="616"/>
+      </Position15>
+      <Position16>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="1787" Column="1" TopLine="1752"/>
+      </Position16>
+    </JumpHistory>
+    <Units Count="6">
+      <Unit0>
+        <CursorPos X="9" Y="22"/>
+        <EditorIndex Value="1"/>
+        <Filename Value="fpmake.pp"/>
+        <IsPartOfProject Value="True"/>
+        <Loaded Value="True"/>
+        <TopLine Value="1"/>
+        <UnitName Value="fpmake"/>
+        <UsageCount Value="59"/>
+      </Unit0>
+      <Unit1>
+        <CursorPos X="1" Y="2588"/>
+        <EditorIndex Value="0"/>
+        <Filename Value="fpmkunit.pp"/>
+        <IsPartOfProject Value="True"/>
+        <Loaded Value="True"/>
+        <TopLine Value="2552"/>
+        <UnitName Value="fpmkunit"/>
+        <UsageCount Value="59"/>
+      </Unit1>
+      <Unit2>
+        <CursorPos X="23" Y="27"/>
+        <Filename Value="fpc/rtl/objpas/sysutils/osutilsh.inc"/>
+        <TopLine Value="8"/>
+        <UsageCount Value="9"/>
+      </Unit2>
+      <Unit3>
+        <CursorPos X="3" Y="931"/>
+        <Filename Value="fpc/rtl/unix/sysutils.pp"/>
+        <TopLine Value="908"/>
+        <UnitName Value="sysutils"/>
+        <UsageCount Value="9"/>
+      </Unit3>
+      <Unit4>
+        <CursorPos X="18" Y="610"/>
+        <Filename Value="fpc/rtl/objpas/classes/classesh.inc"/>
+        <TopLine Value="590"/>
+        <UsageCount Value="8"/>
+      </Unit4>
+      <Unit5>
+        <CursorPos X="7" Y="159"/>
+        <Filename Value="fpc/rtl/objpas/classes/streams.inc"/>
+        <TopLine Value="152"/>
+        <UsageCount Value="8"/>
+      </Unit5>
+    </Units>
+    <PublishOptions>
+      <Version Value="2"/>
+      <IgnoreBinaries Value="False"/>
+      <IncludeFileFilter Value="*.(pas|pp|inc|lfm|lpr|lrs|lpi|lpk|sh|xml)"/>
+      <ExcludeFileFilter Value="*.(bak|ppu|ppw|o|so);*~;backup"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+        <LaunchingApplication PathPlusParams="/usr/X11R6/bin/xterm -T 'Lazarus Run Output' -e $(LazarusDir)/tools/runwait.sh $(TargetCmdLine)"/>
+      </local>
+    </RunParams>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="5"/>
+    <CodeGeneration>
+      <Generate Value="Faster"/>
+    </CodeGeneration>
+    <Other>
+      <CompilerPath Value="$(CompPath)"/>
+    </Other>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="2">
+      <Item1>
+        <Name Value="ECodetoolError"/>
+      </Item1>
+      <Item2>
+        <Name Value="EFOpenError"/>
+      </Item2>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 24 - 0
rtl/common/fpmake.pp

@@ -0,0 +1,24 @@
+program fpmake;
+
+{$mode objfpc}{$H+}
+
+uses
+  fpmakeunit
+  { add your units here };
+
+begin
+  With Installer Do
+    begin
+    StartPackage('Mypackage');
+    Version:='1.0';
+    URL:='http://www.freepascal.org/';
+    Targets.AddUnit('myunit');
+    Targets['myunit'].OS:=[Win32,Linux];
+    Targets['myunit'].Resourcestrings:=True;
+    Targets.AddUnit('testbuild/myotherunit').OS:=[Linux];
+    Targets.AddProgram('myprog');
+    EndPackage;
+    Run;
+    end;
+end.
+

+ 198 - 0
rtl/common/fpmkpkg.lpi

@@ -0,0 +1,198 @@
+<?xml version="1.0"?>
+<CONFIG>
+  <ProjectOptions>
+    <PathDelim Value="/"/>
+    <Version Value="5"/>
+    <General>
+      <Flags>
+        <MainUnitHasUsesSectionForAllUnits Value="False"/>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+      </Flags>
+      <MainUnit Value="0"/>
+      <ActiveEditorIndexAtStart Value="1"/>
+      <IconPath Value="./"/>
+      <TargetFileExt Value=""/>
+    </General>
+    <JumpHistory Count="30" HistoryIndex="29">
+      <Position1>
+        <Filename Value="fpmkpkg.pp"/>
+        <Caret Line="566" Column="3" TopLine="546"/>
+      </Position1>
+      <Position2>
+        <Filename Value="fpmkpkg.pp"/>
+        <Caret Line="565" Column="9" TopLine="546"/>
+      </Position2>
+      <Position3>
+        <Filename Value="fpmkpkg.pp"/>
+        <Caret Line="581" Column="18" TopLine="561"/>
+      </Position3>
+      <Position4>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="677" Column="13" TopLine="667"/>
+      </Position4>
+      <Position5>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="157" Column="88" TopLine="121"/>
+      </Position5>
+      <Position6>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="2577" Column="53" TopLine="2565"/>
+      </Position6>
+      <Position7>
+        <Filename Value="fpmkpkg.pp"/>
+        <Caret Line="929" Column="20" TopLine="906"/>
+      </Position7>
+      <Position8>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="585" Column="25" TopLine="578"/>
+      </Position8>
+      <Position9>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="173" Column="51" TopLine="172"/>
+      </Position9>
+      <Position10>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="670" Column="11" TopLine="667"/>
+      </Position10>
+      <Position11>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="170" Column="25" TopLine="169"/>
+      </Position11>
+      <Position12>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="921" Column="22" TopLine="919"/>
+      </Position12>
+      <Position13>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="931" Column="5" TopLine="882"/>
+      </Position13>
+      <Position14>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="195" Column="80" TopLine="158"/>
+      </Position14>
+      <Position15>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="908" Column="1" TopLine="908"/>
+      </Position15>
+      <Position16>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="1098" Column="1" TopLine="1057"/>
+      </Position16>
+      <Position17>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="851" Column="5" TopLine="802"/>
+      </Position17>
+      <Position18>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="636" Column="15" TopLine="636"/>
+      </Position18>
+      <Position19>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="2700" Column="1" TopLine="2680"/>
+      </Position19>
+      <Position20>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="858" Column="27" TopLine="856"/>
+      </Position20>
+      <Position21>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="2702" Column="5" TopLine="2702"/>
+      </Position21>
+      <Position22>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="932" Column="47" TopLine="932"/>
+      </Position22>
+      <Position23>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="2715" Column="17" TopLine="2706"/>
+      </Position23>
+      <Position24>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="1" Column="1" TopLine="1"/>
+      </Position24>
+      <Position25>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="313" Column="25" TopLine="290"/>
+      </Position25>
+      <Position26>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="347" Column="48" TopLine="325"/>
+      </Position26>
+      <Position27>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="394" Column="25" TopLine="372"/>
+      </Position27>
+      <Position28>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="408" Column="26" TopLine="386"/>
+      </Position28>
+      <Position29>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="1049" Column="19" TopLine="1019"/>
+      </Position29>
+      <Position30>
+        <Filename Value="fpmkunit.pp"/>
+        <Caret Line="2162" Column="34" TopLine="2139"/>
+      </Position30>
+    </JumpHistory>
+    <Units Count="2">
+      <Unit0>
+        <CursorPos X="21" Y="738"/>
+        <EditorIndex Value="0"/>
+        <Filename Value="fpmkpkg.pp"/>
+        <IsPartOfProject Value="True"/>
+        <Loaded Value="True"/>
+        <TopLine Value="733"/>
+        <UnitName Value="fpmkpkg"/>
+        <UsageCount Value="87"/>
+      </Unit0>
+      <Unit1>
+        <CursorPos X="1" Y="2210"/>
+        <EditorIndex Value="1"/>
+        <Filename Value="fpmkunit.pp"/>
+        <Loaded Value="True"/>
+        <TopLine Value="2204"/>
+        <UnitName Value="fpmkunit"/>
+        <UsageCount Value="43"/>
+      </Unit1>
+    </Units>
+    <PublishOptions>
+      <Version Value="2"/>
+      <IgnoreBinaries Value="False"/>
+      <IncludeFileFilter Value="*.(pas|pp|inc|lfm|lpr|lrs|lpi|lpk|sh|xml)"/>
+      <ExcludeFileFilter Value="*.(bak|ppu|ppw|o|so);*~;backup"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+        <LaunchingApplication PathPlusParams="/usr/X11R6/bin/xterm -T 'Lazarus Run Output' -e $(LazarusDir)/tools/runwait.sh $(TargetCmdLine)"/>
+      </local>
+    </RunParams>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="5"/>
+    <CodeGeneration>
+      <Generate Value="Faster"/>
+    </CodeGeneration>
+    <Other>
+      <CompilerPath Value="$(CompPath)"/>
+    </Other>
+  </CompilerOptions>
+  <Debugging>
+    <BreakPoints Count="1">
+      <Item1>
+        <Source Value="fpmkpkg.pp"/>
+        <Line Value="662"/>
+      </Item1>
+    </BreakPoints>
+    <Exceptions Count="2">
+      <Item1>
+        <Name Value="ECodetoolError"/>
+      </Item1>
+      <Item2>
+        <Name Value="EFOpenError"/>
+      </Item2>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 961 - 0
rtl/common/fpmkpkg.pp

@@ -0,0 +1,961 @@
+program fpmkpkg;
+
+{$mode objfpc}{$H+}
+
+uses
+  Classes, SysUtils, TypInfo
+  { add your units here };
+  
+Resourcestring
+ //  SErrInValidArgument     = 'Invalid command-line argument at position %d : %s';
+  SErrNeedArgument           = 'Option at position %d (%s) needs an argument';
+  SErrMissingConfig          = 'Missing configuration Makefile.fpc or fpmake.pp';
+  SErrRunning                = 'The FPC make tool encountered the following error: %s';
+  SErrFailedToCompileFPCMake = 'Could not compile fpmake driver program';
+  SLogGeneratingFPMake       = 'Generating fpmake.pp';
+  SLogCompilingFPMake        = 'Compiling fpmake.pp: ';
+  SLogRunningFPMake          = 'Running fpmake';
+
+Type
+
+  TRunMode = (rmHelp,rmCompile,rmBuild,rmInstall,rmArchive,rmClean,rmDownload);
+
+  { TMakeTool }
+
+  TMakeTool = Class(TObject)
+  Private
+    FConvertOnly,
+    FLogging : Boolean;
+    FCompiler : String;
+    FRunMode : TRunMode;
+    FHaveMakefile : Boolean;
+    FHaveFpmake : Boolean;
+    FFPMakeSrc : String;
+    FFPMakeBin : String;
+    Procedure Log(Msg : String);
+    Procedure Error(Msg : String);
+    Procedure Error(Fmt : String; Args : Array of const);
+    Function GetCompiler : String;
+  Public
+    Procedure ProcessCommandLine;
+    procedure CreateFPMake;
+    procedure CompileFPMake(Extra : Boolean);
+    Function RunFPMake : Integer;
+    Procedure Run;
+  end;
+
+  EMakeToolError = Class(Exception);
+
+
+  { TMakeFileConverter }
+  TSectionType = (stNone,stPackage,stTarget,stclean,stinstall,stCompiler,
+                  stDefault,stRequire,stRules,stPrerules);
+  
+  TMakeFileConverter = Class(TObject)
+    FSection : TSectionType;
+    FPackageName,
+    FpackageDir,
+    FPackageOptions,
+    FPackageDeps,
+    FBuilDUnit,
+    FSubName,
+    FPackageVersion : String;
+    // Reading;
+    procedure DoPackageLine(Const S : String);
+    Procedure DoTargetLine(Line : String; Var T,R,D : TStrings);
+    Procedure DoInstallLine(Line : String; Var IFL : TStrings);
+    procedure DoCleanLine(Line : String; Var CFL : TStrings);
+    procedure DoRequireLine(Line : String);
+    procedure DoCompilerLine(Line : String;Var SD : TStrings);
+    // Writing;
+    procedure WriteOSCPUCheck(Src: TStrings;OS,CPU : String);
+    procedure StartPackage(Src : TStrings; Dir,OS : String);
+    procedure EndPackage(Src : TStrings; Dir,OS : String);
+    procedure DoTargets(Src,T,R,SD : TStrings; Dir,Prefix : String);
+    procedure DoCleans(Src,CFL : TStrings);
+    procedure DoInstalls(Src,IFL : TStrings);
+    Procedure StartInstaller(Src : TStrings);
+    Procedure EndInstaller(Src : TStrings);
+    Function GetLine (L : TStrings; Var I : Integer) : String;
+  Public
+    procedure ConvertFile(const AFileName: String; Src: TStrings; Dir,OS : String);
+    Procedure ConvertFile(Const Source,Dest: String);
+  end;
+
+{ Auxiliary functions }
+
+Function GetWord(var S : String; Sep : Char) : String;
+
+Var
+  L : Integer;
+
+begin
+  L:=Pos(Sep,S);
+  If (L=0) then
+    L:=Length(S)+1;
+  Result:=Copy(S,1,L-1);
+  Delete(S,1,L);
+  S:=Trim(S);
+end;
+
+Function GetWord(var S : String) : String;
+
+begin
+  Result:=GetWord(S,' ');
+end;
+
+
+Function IsCPU (S: String) : Boolean;
+
+begin
+  Result:=Pos(lowercase(S)+',','i386,powerpc,arm,alpha,sparc,')<>0
+end;
+
+Function GetOSCPU(L : String; var OS,CPU : String) : String;
+
+  Procedure Add(Var A : String; ad : String);
+  
+  begin
+    If (A<>'') then
+      A:=A+',';
+    A:=A+ad;
+  end;
+  
+
+Var
+  S : String;
+
+begin
+  OS:='';
+  CPU:='';
+  S:=GetWord(L,',');
+  While (S<>'') do
+    begin
+    If (S<>'all') then
+      If IsCPU(S) then
+        Add(CPU,S)
+      else
+        Add(OS,S);
+    S:=GetWord(L,',');
+    end;
+end;
+
+
+{ TMakeFileConverter }
+
+procedure TMakeFileConverter.StartInstaller(Src: TStrings);
+
+begin
+  With Src do
+    begin
+    Add('{$mode objfpc}{$H+}');
+    Add('program fpmake;');
+    
+    Add('');
+    Add(' { Generated automatically by '+ExtractFileName(Paramstr(0))+' on '+DateToStr(Sysutils.Date)+' }');
+    Add('');
+    Add('uses fpmkunit;');
+    Add('');
+    Add('Var');
+    Add('  T : TTarget;');
+    Add('');
+    Add('begin');
+    Add('  With Installer do ');
+    Add('    begin');
+    end;
+end;
+
+procedure TMakeFileConverter.EndInstaller(Src: TStrings);
+begin
+  With Src do
+    begin
+    Add('    Run;');
+    Add('    end;');
+    Add('end.');
+    Add('');
+    end;
+end;
+
+Function TMakeFileConverter.GetLine (L : TStrings; Var I : Integer) : String;
+
+Var
+  P : Integer;
+  OK : Boolean;
+
+begin
+  OK:=False;
+  Result:='';
+  Repeat
+    Result:=Result+L[i];
+    P:=Pos('#',Result);
+    If (P>0) then
+      Result:=Copy(Result,1,P-1);
+    Result:=Trim(Result);
+    P:=Length(Result);
+    If (P>0) and (Result[P]='\') then
+      Result:=Copy(Result,1,P-1)
+    else
+      OK:=(Result<>'');
+    if Not OK then
+      Inc(I);
+  Until OK or (I>L.Count-1);
+end;
+
+Function SplitNamevalue(Const S : String; Var AName,AValue : String) : boolean;
+
+var
+  L : Integer;
+
+begin
+  L:=Pos('=',S);
+  Result:=(L<>0);
+  If Result then
+    begin
+    AName:=LowerCase(trim(Copy(S,1,L-1)));
+    AValue:=S;
+    Delete(AValue,1,L);
+    AValue:=Trim(Avalue);
+    end
+  else
+    begin
+    AName:='';
+    AValue:='';
+    end;
+end;
+
+
+procedure TMakeFileConverter.StartPackage(Src : TStrings; Dir,OS : String);
+
+Var
+  S : String;
+
+begin
+  With Src do
+    begin
+    Add('    { ');
+    Add('      '+FPackageName);
+    Add('    } ');
+    Add('    StartPackage('''+FPackageName+''');');
+    If (Dir<>'') then
+      Add('    Directory:='''+ExcludeTrailingPathDelimiter(Dir)+''';');
+    If (OS<>'') and (OS<>'all') then
+      Add('    OS:=['+OS+'];');
+    If (FPackageVersion<>'') then
+      Add('    Version:='''+FPackageVersion+''';');
+    If (FPackageOptions<>'') then
+      Add('    Options:='''+FPackageOptions+''';');
+    If (FPackageDeps<>'') then
+      begin
+      S:=GetWord(FPackageDeps);
+      While S<>'' do
+        begin
+        Add('    Dependencies.Add('''+S+''');');
+        S:=GetWord(FPackageDeps);
+        end;
+      end;
+    end;
+end;
+
+procedure TMakeFileConverter.EndPackage(Src : TStrings; Dir,OS : String);
+
+begin
+  Src.add('    EndPackage;');
+  FPackageName:='';
+  FPackageVersion:='';
+  FPackageOptions:='';
+  FBuilDUnit:='';
+  FPackageDeps:='';
+end;
+
+
+procedure TMakeFileConverter.DoPackageLine(Const S : String);
+
+Var V,N : String;
+
+begin
+  SplitNameValue(S,N,V);
+  If (N='name') then
+    FPackageName:=V
+  else If (N='version') then
+    FPackageVersion:=V
+  else If (N='main') then
+    begin
+    FPackageName:='sub';
+    FSubName:=V;
+    end
+  else
+    Writeln(StdErr,'Unknown name/value pair in package section :',N);
+end;
+
+
+{
+  Convert various entries of type
+    XXYY_OSN=words
+  to entries of type
+    prefix_word=OS1,OS2,OS3
+  OS is never empty, 'all' is default.
+  }
+Procedure AddStrings(Var L : TStrings; Values,Prefix,OS : String) ;
+
+Var
+  S,O : String;
+  i : integer;
+  
+begin
+  If (L=Nil) then
+    L:=TstringList.Create;
+  If prefix<>'' then
+    prefix:=prefix+'_';
+  S:=GetWord(Values);
+  While (S<>'') do
+    begin
+    S:=Prefix+S;
+    I:=L.IndexOfName(S);
+    If (I<>-1) then
+      begin
+      O:=L.Values[S];
+      If (O='all') then
+        O:='';
+      If (O<>'') then
+        O:=O+',';
+      O:=O+OS;
+      L.Values[S]:=O;
+      end
+    else
+      L.Add(S+'='+OS);
+    S:=GetWord(Values);
+    end;
+end;
+
+
+procedure TMakeFileConverter.DoTargetLine(Line : String; Var T,R,D : TStrings);
+
+Var
+  V,N,OS : String;
+  P : Integer;
+
+begin
+  SplitNameValue(Line,N,V);
+  P:=Pos('_',N);
+  If (P=0) then
+    OS:='all'
+  else
+    begin
+    OS:=N;
+    Delete(OS,1,P);
+    N:=Copy(N,1,P-1);
+    end;
+  If (N='dirs') then
+    AddStrings(D,V,'',OS)
+  else If (N='units') then
+    AddStrings(T,V,'unit',OS)
+  else If (N='implicitunits') then
+    AddStrings(T,V,'unit',OS)
+  else If (N='programs') then
+    AddStrings(T,V,'program',OS)
+  else If (N='examples') then
+    AddStrings(T,V,'exampleunit',OS)
+  else If (N='rsts') then
+    AddStrings(R,V,'',OS)
+  else
+    Writeln(StdErr,'Unknown name/value pair in target section : ',Line);
+end;
+
+procedure TMakeFileConverter.DoInstallLine(Line : String; Var IFL : TStrings);
+
+Var
+  S,V,N,OS : String;
+  P : Integer;
+  
+begin
+  SplitNameValue(Line,N,V);
+  P:=Pos('_',N);
+  If (P=0) then
+    OS:='all'
+  else
+    begin
+    OS:=N;
+    Delete(OS,1,P);
+    N:=Copy(N,1,P-1);
+    end;
+  If (N='fpcpackage') then
+    P:=0 // temporary, needs fixing.
+  else If (N='buildunit') then
+    FBuildUnit:=V // temporary, needs fixing.
+  else If (N='units') then
+    begin
+    S:=GetWord(V);
+    While (S<>'') do
+      begin
+      AddStrings(IFL,S+'.o','',OS);
+      AddStrings(IFL,S+'.ppu','',OS);
+      S:=GetWord(V);
+      end;
+    end
+  else
+    Writeln(StdErr,'Unknown name/value pair in install section : ',N);
+end;
+
+procedure TMakeFileConverter.DoCleanLine(Line : String; Var CFL : TStrings);
+
+Var
+  V,N,S,OS : String;
+  P : Integer;
+
+begin
+  SplitNameValue(Line,N,V);
+  P:=Pos('_',N);
+  If (P=0) then
+    OS:='all'
+  else
+    begin
+    OS:=N;
+    Delete(OS,1,P);
+    N:=Copy(N,1,P-1);
+    end;
+  If (N='fpcpackage') then
+    P:=0 // temporary, needs fixing.
+  else If (N='units') then
+    begin
+    S:=GetWord(V);
+    While (S<>'') do
+      begin
+      AddStrings(CFL,S+'.o','',OS);
+      AddStrings(CFL,S+'.ppu','',OS);
+      S:=GetWord(V);
+      end;
+    end
+  else
+    Writeln(StdErr,'Unknown name/value pair in clean section : ',N);
+end;
+
+procedure TMakeFileConverter.DoRequireLine(Line : String);
+
+Var
+  V,N,OS : String;
+  P : Integer;
+  
+begin
+  SplitNameValue(Line,N,V);
+  P:=Pos('_',N);
+  If (P=0) then
+    OS:='all'
+  else
+    begin
+    OS:=N;
+    Delete(OS,1,P);
+    N:=Copy(N,1,P-1);
+    end;
+  if (N='packages') then
+    FPackageDeps:=V
+  else If (N='libc') and (Upcase(V)='Y') then
+     P:=0 // Set options ?
+  else
+    Writeln(StdErr,'Unknown name/value pair in require section : ',N);
+end;
+
+
+procedure TMakeFileConverter.DoCompilerLine(Line : String;Var SD : TStrings);
+
+Var
+  V,N,OS : String;
+  P : Integer;
+
+begin
+  SplitNameValue(Line,N,V);
+  P:=Pos('_',N);
+  If (P=0) then
+    OS:='all'
+  else
+    begin
+    OS:=N;
+    Delete(OS,1,P);
+    N:=Copy(N,1,P-1);
+    end;
+  If (N='includedir') then
+    FPackageOptions:=Trim(FPackageOptions+' -Fi'+V)
+  else If (N='options') then
+    FPackageOptions:=Trim(FPackageOptions+' '+V)
+  else If (N='targetdir') then
+    P:=0 // Ignore
+  else if (N='sourcedir') or (N='unitdir') then
+    begin
+    If (SD=Nil) then
+      SD:=TStringList.Create;
+    SD.Add(OS+'='+V);
+    end
+  else
+    Writeln(StdErr,'Unknown name/value pair in compiler section : ',N);
+end;
+
+Function SearchInDirs(Prefix,AName, Dirs : String) : string;
+
+Var
+  D,S : String;
+
+begin
+  S:=GetWord(Dirs);
+  Result:='';
+  While (Result='') and (S<>'') do
+    begin
+    D:=Prefix+S+PathDelim;
+    If FileExists(D+AName+'.pp') or FileExists(D+AName+'.pas') then
+      Result:=S;
+    S:=GetWord(Dirs);
+    end;
+end;
+
+procedure TMakeFileConverter.DoTargets(Src,T,R,SD : TStrings; Dir,Prefix : String);
+
+Var
+  I,J,P : Integer;
+  Pre,N,V,D,DOS,OS,CPU : String;
+  Res : Boolean;
+  
+begin
+  If (Dir<>'') then
+    Dir:=IncludeTrailingPathDelimiter(Dir);
+  If (Prefix<>'') then
+    Prefix:=IncludeTrailingPathDelimiter(Prefix);
+  Dir:=Prefix+Dir;
+  Res:=False;
+  If Assigned(T) then
+    For I:=0 to T.Count-1 do
+      begin
+      T.GetNamevalue(I,N,V);
+      P:=Pos('_',N);
+      If (P<>0) then
+        begin
+        Pre:=Copy(N,1,P-1);
+        Delete(N,1,P);
+        end;
+      If Assigned(R) then
+        Res:=R.IndexOfName(N)<>-1;
+      GetOSCPU(V,OS,CPU);
+      Pre[1]:=Upcase(Pre[1]);
+      Src.Add('    T:=Targets.Add'+Pre+'('''+Prefix+N+''');');
+      If (CPU<>'') then
+        Src.Add('    T.CPU:=['+CPU+'];');
+      If (OS<>'') then
+        Src.Add('    T.OS:=['+OS+'];');
+      If res then
+        Src.add('    T.ResourceStrings:=True;');
+      If (CompareText(FBuildUnit,N)=0) then
+        Src.add('    T.Install:=False;');
+      if Assigned(SD) then
+        for J:=0 to SD.Count-1 do
+          begin
+          SD.GetNameValue(J,DOS,D);
+          If (DOS<>'all') then
+            Src.Add('    if (Defaults.OS='+DOS+') then');
+          Src.add('      T.Directory:='''+SearchInDirs(Dir,N,D)+''';');
+          end;
+      end;
+end;
+
+procedure TMakeFileConverter.WriteOSCPUCheck(Src: TStrings;OS,CPU : String);
+
+Var
+  S : String;
+
+begin
+  If (CPU<>'') then
+    S:='(Defaults.CPU='+CPU+')';
+  If (OS<>'') then
+    begin
+    IF (S<>'') then
+      S:=S+' OR ';
+    S:=S+'(Defaults.OS='+CPU+')';
+    end;
+  If (S<>'') then
+    Src.Add('    If '+S+' then');
+end;
+
+procedure TMakeFileConverter.DoInstalls(Src,IFL : TStrings);
+
+Var
+  I,J,P : Integer;
+  Pre,N,V,D,DOS,OS,CPU : String;
+
+begin
+  If Assigned(IFL) then
+    For I:=0 to IFL.Count-1 do
+      begin
+      IFL.GetNamevalue(I,N,V);
+      GetOSCPU(V,OS,CPU);
+      WriteOSCPUCheck(Src,OS,CPU);
+      Src.add('      InstallFiles.Add('''+N+''');');
+      end;
+end;
+
+procedure TMakeFileConverter.DoCleans(Src,CFL : TStrings);
+
+Var
+  I,J,P : Integer;
+  N,V,DOS,OS,CPU : String;
+
+
+begin
+  If Assigned(CFL) then
+    For I:=0 to CFL.Count-1 do
+      begin
+      CFL.GetNamevalue(I,N,V);
+      GetOSCPU(V,OS,CPU);
+      WriteOSCPUCheck(Src,OS,CPU);
+      Src.add('      CleanFiles.Add('''+N+''');');
+      end;
+end;
+
+
+
+procedure TMakeFileConverter.ConvertFile(const AFileName: String; Src: TStrings; Dir,OS : String);
+
+  Function IsSection(var S : String) : Boolean;
+  
+  Var
+    L : Integer;
+  
+  begin
+    L:=Length(S);
+    Result:=(L>0) and (S[1]='[') and (S[L]=']');
+    If Result then
+      S:=trim(Copy(S,2,L-2));
+  end;
+
+Var
+  R,L,T,D,S,SD,IFL,CFL : TStrings;
+  I,J : Integer;
+  Prefix,Line,DN : String;
+  B : Boolean;
+  
+begin
+  Writeln('Converting '+AFileName);
+  T:=Nil;
+  D:=Nil;
+  S:=Nil;
+  SD:=Nil;
+  R:=Nil;
+  IFL:=Nil;
+  CFL:=Nil;
+  FPackageOptions:='';
+  FPackageDir:='';
+  L:=TStringList.Create;
+  try
+    L.LoadFromFile(AFileName);
+    I:=0;
+    While (I<L.Count) do
+      begin
+      Line:=GetLine(L,I);
+      If IsSection(Line) then
+        begin
+        J:=GetEnumValue(TypeInfo(TSectionType),'st'+Line);
+        If (J=-1) then
+          begin
+          FSection:=stNone;
+          Writeln(stdErr,'Unsupported section: ',Line);
+          end
+        else
+          FSection:=TSectiontype(J);
+        end
+      else
+        case FSection of
+          stPackage : DoPackageLine(Line);
+          stTarget  : DoTargetLine(Line,T,R,D);
+          stInstall : DoInstallLine(Line,IFL);
+          stClean   : DoCleanLine(Line,CFL);
+          stCompiler : DoCompilerLine(Line,SD);
+          strequire : DoRequireLine(Line);
+        end;
+      inc(I);
+      end;
+    // If there are only 'dir' entries, then there is no package name.
+    B:=False;
+    if (FPackageName<>'') then
+      begin
+      Prefix:='';
+      B:=FPackageName<>'sub';
+      If B then
+        StartPackage(Src,Dir,OS)
+      else
+        Prefix:=Dir;
+      DoTargets(Src,T,R,SD,Dir,Prefix);
+      DoInstalls(Src,IFL);
+      DoCleans(Src,CFL);
+      end;
+    If Assigned(D) then
+      begin
+      If (Dir<>'') then
+        Dir:=IncludeTrailingPathDelimiter(Dir);
+      For I:=0 to D.Count-1 do
+        begin
+        D.GetNameValue(I,DN,Line);
+        If (Line<>'all') and (Line<>'') then
+          OS:=Line;
+        DN:=Dir+DN+PathDelim;
+        If FileExists(DN+'Makefile.fpc') then
+          ConvertFile(DN+'Makefile.fpc',Src,DN,OS);
+        end;
+      end;
+    If B then
+      EndPackage(Src,Dir,OS);
+  Finally
+    S.Free;
+    IFL.Free;
+    CFL.Free;
+    D.Free;
+    SD.Free;
+    T.Free;
+    L.Free;
+  end;
+end;
+
+procedure TMakeFileConverter.ConvertFile(const Source, Dest: String);
+
+Var
+  L : TStrings;
+  
+begin
+  L:=TStringList.Create;
+  Try
+    StartInstaller(L);
+    ConvertFile(Source,L,'','');
+    EndInstaller(L);
+    L.SaveToFile(Dest);
+  Finally
+    L.Free;
+  end;
+end;
+
+{ TMakeTool }
+
+procedure TMakeTool.CompileFPMake(Extra: Boolean);
+
+Var
+  O,C : String;
+
+begin
+  C:=GetCompiler;
+  O:=FFPmakeSrc;
+  If Extra then
+    O:='-Fafpmakeex '+O;
+  Log(SLogCompilingFPMake+C+' '+O);
+  If ExecuteProcess(C,O)<>0 then
+    Error(SErrFailedToCompileFPCMake)
+end;
+
+procedure TMakeTool.CreateFPMake;
+begin
+  Log(SLogGeneratingFPMake);
+  With TMakeFileConverter.Create do
+    try
+      ConvertFile('Makefile.fpc','fpmake.pp');
+    finally
+      Free;
+    end;
+end;
+
+
+Function TMakeTool.RunFPMake : Integer;
+
+  Function MaybeQuote(Const S : String) : String;
+  
+  begin
+    If Pos(' ',S)=0 then
+      Result:=S
+    else
+      Result:='"'+S+'"';
+  end;
+  
+
+Var
+  I : integer;
+  D,O : String;
+
+begin
+  Log(SLogRunningFPMake);
+  D:=IncludeTrailingPathDelimiter(GetCurrentDir);
+  O:='';
+  For I:=1 to ParamCount do
+    begin
+    If (O<>'') then
+      O:=O+' ';
+    O:=O+MaybeQuote(ParamStr(I));
+    end;
+  Result:=ExecuteProcess(D+FFPMakeBin,O);
+end;
+
+procedure TMakeTool.Log(Msg: String);
+begin
+  If FLogging then
+    Writeln(stdErr,Msg);
+end;
+
+procedure TMakeTool.Error(Msg: String);
+begin
+  Raise EMakeToolError.Create(Msg);
+end;
+
+procedure TMakeTool.Error(Fmt: String; Args: array of const);
+begin
+  Raise EMakeToolError.CreateFmt(Fmt,Args);
+end;
+
+function TMakeTool.GetCompiler: String;
+begin
+  If (FCompiler='') then
+    begin
+    {$if defined(cpui386)}
+      FCompiler:='ppc386';
+    {$elseif defined(cpuAlpha)}
+      FCompiler:='ppcaxp';
+    {$elseif defined(cpusparc)}
+      FCompiler:='ppcsparc';
+    {$elseif defined(cpuarm)}
+      FCompiler:='ppcarm';
+    {$elseif defined(cpum68k)}
+      FCompiler:='ppcm68k';
+    {$elseif defined(cpupowerpc)}
+      FCompiler:='ppcppc';
+    {$else}
+      {$Fatal Unknown architecture}
+    {$endif}
+    end;
+  If (ExtractFilePath(FCompiler)<>'') then
+    Result:=FCompiler
+  else
+    begin
+    Result:=FileSearch(FCompiler,GetEnvironmentVariable('PATH'));
+    If (Result='') then
+      Result:=FCompiler;
+    end;
+end;
+
+
+procedure TMakeTool.ProcessCommandLine;
+
+  Function CheckOption(Index : Integer;Short,Long : String): Boolean;
+
+  var
+    O : String;
+
+  begin
+    O:=Paramstr(Index);
+    Result:=(O='-'+short) or (O='--'+long) or (copy(O,1,Length(Long)+3)=('--'+long+'='));
+  end;
+
+  Function OptionArg(Var Index : Integer) : String;
+
+  Var
+    P : Integer;
+
+  begin
+    if (Length(ParamStr(Index))>1) and (Paramstr(Index)[2]<>'-') then
+      begin
+      If Index<ParamCount then
+        begin
+        Inc(Index);
+        Result:=Paramstr(Index);
+        end
+      else
+        Error(SErrNeedArgument,[Index,ParamStr(Index)]);
+      end
+    else If length(ParamStr(Index))>2 then
+      begin
+      P:=Pos('=',Paramstr(Index));
+      If (P=0) then
+        Error(SErrNeedArgument,[Index,ParamStr(Index)])
+      else
+        begin
+        Result:=Paramstr(Index);
+        Delete(Result,1,P);
+        end;
+      end;
+  end;
+
+Var
+  I : Integer;
+
+begin
+  I:=0;
+  FLogging:=False;
+  FRunMode:=rmhelp;
+  FConvertOnly:=False;
+  While (I<ParamCount) do
+    begin
+    Inc(I);
+    if Checkoption(I,'n','convert') then
+      FConvertOnly:=True
+    else if Checkoption(I,'m','compile') then
+      FRunMode:=rmCompile
+    else if Checkoption(I,'b','build') then
+      FRunMode:=rmBuild
+    else if CheckOption(I,'i','install') then
+      FRunMode:=rmInstall
+    else if CheckOption(I,'c','clean') then
+      FRunMode:=rmClean
+    else if CheckOption(I,'a','archive') then
+      FRunMode:=rmarchive
+    else if CheckOption(I,'d','download') then
+      FRunMode:=rmDownload
+    else if CheckOption(I,'h','help') then
+      FRunMode:=rmhelp
+    // Check.
+    else if CheckOption(I,'r','compiler') then
+      FCompiler:=OptionArg(I)
+    else if CheckOption(I,'v','verbose') then
+      Flogging:=Pos('info',Lowercase(OptionArg(I)))<>0;
+    end;
+end;
+
+
+procedure TMakeTool.Run;
+
+
+begin
+  Try
+    ProcessCommandLine;
+    If FConvertOnly then
+      CreateFPMake
+    else
+      begin
+      FHaveMakefile:=FileExists('Makefile.fpc');
+      FFPMakeSrc:='fpmake.pp';
+      FHaveFpmake:=FileExists(FFPMakeSrc);
+      If Not FHaveFPMake then
+        begin
+        FHaveFPMake:=FileExists('fpmake.pas');
+        If FHaveFPMake then
+          FFPMakeSrc:='fpmake.pas';
+        end;
+      if Not (FHaveFPMake or FHaveMakeFile) then
+        Error(SErrMissingConfig);
+      If (Not FHaveFPMake) or (FileAge(FFPMakeSrc)<FileAge('Makefile.fpc')) then
+        CreateFPMake;
+    {$ifndef unix}
+      FFPMakeBin:='fpmake.exe';
+    {$else}
+      FFPMakeBin:='fpmake';
+    {$endif}
+      if FileAge(FFPMakeBin)<FileAge(FFPMakeSrc) then
+        CompileFPMake(FRunMode in [rmArchive,rmDownload]);
+      Halt(RunFPMake);
+      end;
+  except
+    On E : Exception do
+      begin
+      Writeln(StdErr,Format(SErrRunning,[E.Message]));
+      Halt(1);
+      end;
+  end;
+end;
+
+
+begin
+  With TMakeTool.Create do
+    try
+      run;
+    finally
+      Free;
+    end;
+end.
+

+ 2779 - 0
rtl/common/fpmkunit.pp

@@ -0,0 +1,2779 @@
+{$Mode objfpc}
+{$H+}
+{$define debug}
+unit fpmkunit;
+
+Interface
+
+uses SysUtils,Classes;
+
+Type
+
+  TFileType = (ftSource,ftUnit,ftObject,ftResource,ftExecutable,ftStaticLibrary,
+               ftSharedLibrary);
+  TFileTypes = set of TFileType;
+
+  TOS = (Amiga,Atari,Darwin,FreeBSD,Go32v2,Linux,MacOS,MorphOS,NetBSD,
+         Netware,NetwLibc,OpenBSD,OS2,PalmOS,Solaris,Win32,Emx);
+  TOSes = Set of TOS;
+  
+  TCPU = (Arm,I386,PPC,SPARC,X86_64);
+  TCPUS = Set of TCPU;
+  
+  TCompilerMode = (FPC,TP,ObjFPC,Delphi,MacPas);
+  TCompilerModes = Set of TCompilerMode;
+  
+  TTargetType = (ttUnit,ttProgram,ttExampleUnit,ttExampleProgram);
+  TTargetTypes = set of TTargetType;
+  
+  TTargetState = (tsNeutral,tsCompiling,tsCompiled,tsInstalled);
+  TTargetStates = Set of TTargetState;
+
+  TRunMode = (rmHelp,rmCompile,rmBuild,rmInstall,rmArchive,rmClean,rmDownload);
+
+  TVerboseLevel = (vlError,vlWarning,vlInfo,vlCompare,vlCommand,vldebug);
+  TVerboseLevels = Set of TVerboseLevel;
+  
+  TLogEvent = Procedure (Level : TVerboseLevel; Const Msg : String) of Object;
+
+
+  
+  { TNamedItem }
+
+  TNamedItem = Class(TCollectionItem)
+  private
+    FName: String;
+    procedure SetName(const AValue: String);virtual;
+  Public
+    property Name : String Read FName Write SetName;
+  end;  
+  
+  { TNamedCollection }
+
+  TNamedCollection = Class(TCollection)
+  private
+    FUniqueNames: Boolean;
+  Public
+    Function IndexOfName(AName : String) : Integer;
+    Function ItemByName(AName : String) : TNamedItem;
+    Property UniqueNames : Boolean Read FUniqueNames;
+  end;
+  
+  { TNamedItemList }
+  
+  TNamedItemList = Class(TList)
+  private
+    function GetNamedItem(Index : Integer): TNamedItem;
+    procedure SetNamedItem(Index : Integer; const AValue: TNamedItem);
+  public
+    Function IndexOfName(AName : String) : Integer;
+    Function ItemByName(ANAme : String) : TNamedItem;
+    Property NamedItems[Index : Integer] : TNamedItem Read GetNamedItem Write SetNamedItem; default;
+  end;
+  
+
+  { TFileItem }
+
+  TFileItem = Class(TNamedItem)
+  private
+    FFileType: TFileType;
+    function GetExtension: String;
+    function GetPath: String;
+  Public
+    Property Extension : String Read GetExtension;
+    Property FileType : TFileType Read FFileType Write FFileType;
+    Property Path : String Read GetPath;
+  end;
+  
+  { TFileItems }
+
+  TFileItems = Class(TNamedCollection)
+  end;
+
+  { TTarget }
+
+  TTarget = Class(TNamedItem)
+  private
+    FInstall : Boolean;
+    FAfterClean: TNotifyEvent;
+    FAfterCompile: TNotifyEvent;
+    FBeforeClean: TNotifyEvent;
+    FBeforeCompile: TNotifyEvent;
+    FCPUs: TCPUs;
+    FMode: TCompilerMode;
+    FResourceStrings: Boolean;
+    FObjectPath,
+    FUnitPath,
+    FIncludePath,
+    FDependencies: TStrings;
+    FDirectory: String;
+    FExtension: String;
+    FFileType: TFileType;
+    FOptions: String;
+    FOSes: TOSes;
+    FFPCTarget: String;
+    FTargetState: TTargetState;
+    FTargetType: TTargetType;
+    function GetHasStrings(AIndex: integer): Boolean;
+    function GetStrings(AIndex: integer): TStrings;
+    procedure SetStrings(AIndex: integer; const AValue: TStrings);
+  Protected
+    Function GetSourceFileName : String; virtual;
+    Function GetUnitFileName : String; virtual;
+    Function GetObjectFileName : String; virtual;
+    Function GetRSTFileName : String; Virtual;
+    Function GetProgramFileName(AnOS : TOS) : String; Virtual;
+  Public
+    Constructor Create(ACollection : TCollection); override;
+    Destructor Destroy; override;
+    Function GetOutputFileName (AOs : TOS) : String; Virtual;
+    Function NeedsCompile(ATargetDir : String;AOs : TOS) : Boolean; virtual;
+    procedure SetName(const AValue: String);override;
+    Procedure GetCleanFiles(List : TStrings; APrefix : String; AnOS : TOS); virtual;
+    Procedure GetInstallFiles(List : TStrings; APrefix : String; AnOS : TOS); virtual;
+    Procedure GetArchiveFiles(List : TStrings; APrefix : String; AnOS : TOS); virtual;
+    Property HasUnitPath : Boolean Index 0 Read GetHasStrings;
+    Property HasObjectPath : Boolean Index 1 Read GetHasStrings;
+    Property HasIncludePath : Boolean Index 2 Read GetHasStrings;
+    Property HasDependencies : Boolean Index 3 Read GetHasStrings;
+    Property UnitPath : TStrings Index 0 Read GetStrings Write SetStrings;
+    Property ObjectPath : TStrings Index 1  Read GetStrings Write SetStrings;
+    Property IncludePath : TStrings Index 2 Read GetStrings Write SetStrings;
+    Property Dependencies : TStrings Index 3 Read GetStrings Write SetStrings;
+    Property State : TTargetState Read FTargetState;
+    Property TargetType : TTargetType Read FTargetType Write FTargetType;
+    Property OS : TOSes Read FOSes Write FOSes;
+    Property Mode : TCompilerMode Read FMode Write FMode;
+    Property Options : String Read FOptions Write Foptions;
+    Property SourceFileName: String Read GetSourceFileName ;
+    Property UnitFileName : String Read GetUnitFileName;
+    Property ObjectFileName : String Read GetObjectFileName;
+    Property RSTFileName : String Read GetRSTFileName;
+    Property CPU : TCPUs Read FCPUs Write FCPUs;
+    Property FPCTarget : String Read FFPCTarget Write FFPCTarget;
+    Property Extension : String Read FExtension Write FExtension;
+    Property FileType : TFileType Read FFileType Write FFileType;
+    Property Directory : String Read FDirectory Write FDirectory;
+    Property ResourceStrings : Boolean Read FResourceStrings Write FResourceStrings;
+    Property Install : Boolean Read FInstall Write FInstall;
+    // Events.
+    Property BeforeCompile : TNotifyEvent Read FBeforeCompile Write FBeforeCompile;
+    Property AfterCompile : TNotifyEvent Read FAfterCompile Write FAfterCompile;
+    Property BeforeClean : TNotifyEvent Read FBeforeClean Write FBeforeClean;
+    Property AfterClean : TNotifyEvent Read FAfterClean Write FAfterClean;
+  end;
+
+  { TTargets }
+
+  TTargets = Class(TNamedCollection)
+  private
+    FDefaultCPU: TCPUs;
+    FDefaultDir : String;
+    FDefaultOS: TOSes;
+    function GetTargetItem(Index : Integer): TTarget;
+    function GetTarget(AName : String): TTarget;
+    procedure SetDefaultDir(const AValue: String);
+    procedure SetTargetItem(Index : Integer; const AValue: TTarget);
+    Procedure ApplyDefaults(ATarget : TTarget);
+  Public
+    Procedure ResetDefaults;
+    Function AddUnit(AUnitName : String) : TTarget;
+    Function AddProgram(AProgramName : String) : TTarget;
+    Function AddExampleUnit(AUnitName : String) : TTarget;
+    Function AddExampleProgram(AProgramName : String) : TTarget;
+    Property Targets[AName : String] : TTarget Read GetTarget; default;
+    Property TargetItems[Index : Integer] : TTarget Read GetTargetItem Write SetTargetItem;
+    Property DefaultDir : String Read FDefaultDir Write SetDefaultDir;
+    Property DefaultOS : TOSes Read FDefaultOS Write FDefaultOS;
+    Property DefaultCPU : TCPUs Read FDefaultCPU Write FDefaultCPU;
+  end;
+  
+  { TPackage }
+  
+  TPackage = Class(TNamedItem) // Maybe descend from/use TTarget ?
+  private
+    FAfterArchive: TNotifyEvent;
+    FAfterClean: TNotifyEvent;
+    FAfterCompile: TNotifyEvent;
+    FAfterDownload: TNotifyEvent;
+    FAfterInstall: TNotifyEvent;
+    FBeforeArchive: TNotifyEvent;
+    FBeforeClean: TNotifyEvent;
+    FBeforeCompile: TNotifyEvent;
+    FBeforeDownload: TNotifyEvent;
+    FBeforeInstall: TNotifyEvent;
+    FUnitPath,
+    FObjectPath,
+    FIncludePath,
+    FCleanFiles,
+    FArchiveFiles,
+    FInstallFiles,
+    FDependencies: TStrings;
+    FCPU: TCPUs;
+    FOS: TOses;
+    FTargetState: TTargetState;
+    FTargets: TTargets;
+    FDirectory: String;
+    FOptions: String;
+    FAuthor: String;
+    FLicense: String;
+    FURL: String;
+    FVersion: String;
+    function GetHasStrings(AIndex: integer): Boolean;
+    function GetStrings(AIndex: integer): TStrings;
+    procedure SetStrings(AIndex: integer; const AValue: TStrings);
+  Public
+    constructor Create(ACollection: TCollection); override;
+    destructor destroy; override;
+    Function AddTarget(AName : String) : TTarget;
+    Procedure AddDependency(AName : String);
+    Procedure AddInstallFile(AFileName : String);
+    Procedure GetCleanFiles(List : TStrings; Const APrefix : String; AOS : TOS); virtual;
+    procedure GetInstallFiles(List: TStrings;Types : TTargetTypes;Const APrefix : String; AOS : TOS);
+    Procedure GetArchiveFiles(List : TStrings; Const APrefix : String; AOS : TOS); virtual;
+    Function NeedsCompile(ATargetDir : String; AOS : TOS) : Boolean; virtual;
+    Property Version : String Read FVersion Write FVersion;
+    Property URL : String Read FURL Write FURL;
+    Property Author : String Read FAuthor Write FAuthor;
+    Property License : String Read FLicense Write FLicense;
+    Property Directory : String Read FDirectory Write FDirectory;
+    // Compiler options.
+    Property OS : TOses Read FOS Write FOS;
+    Property CPU : TCPUs Read FCPU Write FCPU;
+    Property Options: String Read FOptions Write FOptions;
+    Property HasUnitPath : Boolean Index 0 Read GetHasStrings;
+    Property HasObjectPath : Boolean Index 1 Read GetHasStrings;
+    Property HasIncludePath : Boolean Index 2 Read GetHasStrings;
+    Property HasDependencies : Boolean Index 3 Read GetHasStrings;
+    Property HasInstallFiles: Boolean Index 4 Read GetHasStrings;
+    Property HasCleanFiles : Boolean Index 5 Read GetHasStrings;
+    Property HasArchiveFiles : Boolean Index 6 Read GetHasStrings;
+    Property UnitPath : TStrings Index 0 Read GetStrings Write SetStrings;
+    Property ObjectPath : TStrings Index 1  Read GetStrings Write SetStrings;
+    Property IncludePath : TStrings Index 2 Read GetStrings Write SetStrings;
+    // Targets and dependencies
+    Property Dependencies : TStrings Index 3 Read GetStrings Write SetStrings;
+    Property InstallFiles : TStrings Index 4 Read GetStrings Write SetStrings;
+    Property CleanFiles : TStrings Index 5 Read GetStrings Write SetStrings;
+    Property ArchiveFiles : TStrings Index 6 Read GetStrings Write SetStrings;
+    Property State : TTargetState Read FTargetState;
+    Property Targets : TTargets Read FTargets;
+    // events
+    Property BeforeCompile : TNotifyEvent Read FBeforeCompile Write FBeforeCompile;
+    Property AfterCompile : TNotifyEvent Read FAfterCompile Write FAfterCompile;
+    Property BeforeInstall : TNotifyEvent Read FBeforeInstall Write FBeforeInstall;
+    Property AfterInstall : TNotifyEvent Read FAfterInstall Write FAfterInstall;
+    Property BeforeClean : TNotifyEvent Read FBeforeClean Write FBeforeClean;
+    Property AfterClean : TNotifyEvent Read FAfterClean Write FAfterClean;
+    Property BeforeArchive : TNotifyEvent Read FBeforeArchive Write FBeforeArchive;
+    Property AfterArchive : TNotifyEvent Read FAfterArchive Write FAfterArchive;
+    Property BeforeDownload : TNotifyEvent Read FBeforeDownload Write FBeforeDownload;
+    Property AfterDownload : TNotifyEvent Read FAfterDownload Write FAfterDownload;
+  end;
+
+  { TPackages }
+
+  TPackages = Class(TNamedCollection)
+  private
+    function GetPackage(AName : String): TPackage;
+    function GetPackageItem(AIndex : Integer): TPackage;
+    procedure SetPackageItem(AIndex : Integer; const AValue: TPackage);
+  Public
+    Function AddPackage(Const AName : String) : TPackage;
+    Property Packages[AName : String] : TPackage Read GetPackage ; Default;
+    Property PackageItems[AIndex : Integer] : TPackage Read GetPackageItem Write SetPackageItem;
+  end;
+
+  { TDefaults }
+  
+  TDefaults = Class(TPersistent)
+  Private
+    FArchive: String;
+    FCompiler: String;
+    FCopy: String;
+    FDownload: String;
+    FMkDir: String;
+    FMove: String;
+    FOptions: String;
+    FCPU: TCPU;
+    FOS: TOS;
+    FMode : TCompilerMode;
+    FPrefix: String;
+    FBaseInstallDir,
+    FUnitInstallDir,
+    FBinInstallDir,
+    FDocInstallDir,
+    FExamplesInstallDir : String;
+    FRemove: String;
+    FTarget: String;
+    FUnixPaths: Boolean;
+    function GetBaseInstallDir: String;
+    function GetBinInstallDir: String;
+    function GetCompiler: String;
+    function GetDocInstallDir: String;
+    function GetExamplesInstallDir: String;
+    function GetUnitInstallDir: String;
+    procedure SetBaseInstallDir(const AValue: String);
+    procedure SetCPU(const AValue: TCPU);
+    procedure SetOS(const AValue: TOS);
+    procedure SetPrefix(const AValue: String);
+    procedure SetTarget(const AValue: String);
+  Protected
+    procedure RecalcTarget;
+  Public
+    Constructor Create;
+    Procedure InitDefaults;
+    Procedure Assign(ASource : TPersistent);override;
+    Procedure LocalInit(Const AFileName : String);
+    Procedure LoadFromFile(Const AFileName : String);
+    Procedure SaveToFile(Const AFileName : String);
+    procedure SaveToStream(S : TStream);virtual;
+    procedure LoadFromStream(S : TStream);virtual;
+    // Compile Information
+    Property Target : String Read FTarget Write SetTarget;
+    Property OS : TOS Read FOS Write SetOS;
+    Property CPU : TCPU Read FCPU Write SetCPU;
+    Property Mode : TCompilerMode Read FMode Write FMode;
+    Property UnixPaths : Boolean Read FUnixPaths Write FUnixPaths;
+    Property Options : String Read FOptions Write FOptions;    // Default compiler options.
+    // paths etc.
+    Property Prefix : String Read FPrefix Write SetPrefix;
+    Property BaseInstallDir : String Read GetBaseInstallDir Write SetBaseInstallDir;
+    Property UnitInstallDir : String Read GetUnitInstallDir Write FUnitInstallDir;
+    Property BinInstallDir : String Read GetBinInstallDir Write FBinInstallDir;
+    Property DocInstallDir : String Read GetDocInstallDir Write FDocInstallDir;
+    Property ExamplesInstallDir : String Read GetExamplesInstallDir Write FExamplesInstallDir;
+    // Command tools. If not set, internal commands  will be used.
+    Property Compiler : String Read GetCompiler Write FCompiler; // Compiler. Defaults to fpc/ppc386
+    Property Copy : String Read FCopy Write FCopy;             // copy %FILES% to %DEST%
+    Property Move : String Read FMove Write FMove;             // Move %FILES% to %DEST%
+    Property Remove : String Read FRemove Write FRemove;       // Delete %FILES%
+    Property MkDir : String Read FMkDir write FMkDir;          // Make %DIRECTORY%
+    Property Archive : String Read FArchive Write FArchive;    // zip %ARCHIVE% %FILESORDIRS%
+    Property Download : String Read FDownload Write FDownload; // wget %URL% %DESTFILE%
+  end;
+
+  { TBuildEngine }
+
+  TBuildEngine = Class(TComponent)
+  private
+    // general variables
+    FCompiler : String;
+    FStartDir : String;
+    FTargetDir : String;
+    FDefaults : TDefaults;
+    FForceCompile : Boolean;
+    FListMode : Boolean;
+    // Variables used when compiling a package.
+    // Only valid during compilation of the package.
+    FCurrentPackage: TPackage;
+    // Events
+    FOnLog: TLogEvent;
+    FAfterArchive: TNotifyEvent;
+    FAfterClean: TNotifyEvent;
+    FAfterCompile: TNotifyEvent;
+    FAfterDownload: TNotifyEvent;
+    FAfterInstall: TNotifyEvent;
+    FBeforeArchive: TNotifyEvent;
+    FBeforeClean: TNotifyEvent;
+    FBeforeCompile: TNotifyEvent;
+    FBeforeDownload: TNotifyEvent;
+    FBeforeInstall: TNotifyEvent;
+    procedure SetDefaults(const AValue: TDefaults);
+    procedure SetTargetDir(const AValue: String);
+  Protected
+    Procedure Error(Msg : String);
+    Procedure Error(Fmt : String; Args : Array of const);
+    // Internal copy/delete/move/archive/mkdir files
+    Procedure SysCopyFile(Const Src,Dest : String); virtual;
+    Procedure SysMoveFile(Const Src,Dest : String); virtual;
+    Procedure SysDeleteFile(Const AFileName : String); virtual;
+    Procedure SysArchiveFiles(List : TStrings; Const AFileName : String); virtual;
+    Procedure Log(Level : TVerboseLevel; Const Msg : String);
+    Procedure Log(Level : TVerboseLevel; Const Fmt : String; Args : Array Of Const);
+    Procedure EnterDir(ADir : String);
+    Function GetCompiler : String;
+    Procedure InstallPackageFiles(APAckage : TPackage; tt : TTargetType; Const Src,Dest : String); virtual;
+
+  Public
+    Constructor Create(AOwner : TComponent); override;
+    // Public Copy/delete/Move/Archive/Mkdir Commands.
+    Procedure ExecuteCommand(Cmd : String; Args : String); virtual;
+    Procedure CmdCopyFiles(List : TStrings; Const DestDir : String);
+    Procedure CmdCreateDir(DestDir : String);
+    Procedure CmdMoveFiles(List : TStrings; Const DestDir : String);
+    Procedure CmdDeleteFiles(List : TStrings);
+    Procedure CmdArchiveFiles(List : TStrings; Const ArchiveFile : String);
+    // Target commands
+    Function  GetTargetDir(APackage : TPackage; ATarget : TTarget; AbsolutePath : Boolean = False) : String;
+    Function  GetCompilerCommand(APackage : TPackage; Target : TTarget) : String;
+    Function  TargetOK(Target : TTarget) : Boolean;
+    Procedure Compile(Target : TTarget);  virtual;
+    Procedure FixDependencies(Target: TTarget);
+    // Package commands
+    Function  GetPackageDir(APackage : TPackage; AbsolutePath : Boolean = False) : String;
+    Function  GetOutputDir(APackage : TPackage; AbsolutePath : Boolean = False) : String;
+    Function  PackageOK(APackage : TPackage) : Boolean;
+    Procedure Compile(APackage : TPackage); virtual;
+    Procedure Install(APackage : TPackage); virtual;
+    Procedure Archive(APackage : TPackage); virtual;
+    Procedure Clean(APackage : TPackage); virtual;
+    Procedure Download(APackage : TPackage); virtual;
+    Procedure FixDependencies(APackage : TPackage);virtual;
+    procedure CheckExternalPackage(Const APackageName : String); virtual;
+    procedure CreateOutputDir(APackage: TPackage);
+    // Packages commands
+    Procedure Compile(Packages : TPackages);
+    Procedure Install(Packages : TPackages);
+    Procedure Archive(Packages : TPackages);
+    Procedure Clean(Packages : TPackages);
+    Procedure Download(Packages : TPackages);
+    Property ListMode : Boolean Read FListMode Write FListMode;
+    Property ForceCompile : Boolean Read FForceCompile Write FForceCompile;
+    Property Defaults : TDefaults Read FDefaults Write SetDefaults;
+    Property TargetDir : String Read FTargetDir Write SetTargetDir;
+    // Events
+    Property BeforeCompile : TNotifyEvent Read FBeforeCompile Write FBeforeCompile;
+    Property AfterCompile : TNotifyEvent Read FAfterCompile Write FAfterCompile;
+    Property BeforeInstall : TNotifyEvent Read FBeforeInstall Write FBeforeInstall;
+    Property AfterInstall : TNotifyEvent Read FAfterInstall Write FAfterInstall;
+    Property BeforeClean : TNotifyEvent Read FBeforeClean Write FBeforeClean;
+    Property AfterClean : TNotifyEvent Read FAfterClean Write FAfterClean;
+    Property BeforeArchive : TNotifyEvent Read FBeforeArchive Write FBeforeArchive;
+    Property AfterArchive : TNotifyEvent Read FAfterArchive Write FAfterArchive;
+    Property BeforeDownload : TNotifyEvent Read FBeforeDownload Write FBeforeDownload;
+    Property AfterDownload : TNotifyEvent Read FAfterDownload Write FAfterDownload;
+    Property OnLog : TLogEvent Read FOnLog Write FOnlog;
+  end;
+
+  { TInstaller }
+
+  TInstaller = Class(TComponent)
+  private
+    FBuildEngine: TBuildEngine;
+    FDefaultPackage: TPackage;
+    FDefaults: TDefaults;
+    FPackages: TPackages;
+    FRunMode: TRunMode;
+    FListMode : Boolean;
+    FLogLevels : TVerboseLevels;
+    function GetAuthor: String;
+    function GetStrings(AIndex : Integer): TStrings;
+    function GetDirectory: String;
+    function GetLicense: String;
+    function GetOSes: TOSes;
+    function GetOptions: String;
+    function GetTargets: TTargets;
+    function GetURL: String;
+    function GetVersion: String;
+    procedure SetAuthor(const AValue: String);
+    procedure SetDefaultPackage(const AValue: TPackage);
+    procedure SetDefaults(const AValue: TDefaults);
+    procedure SetStrings(AIndex : Integer; const AValue: TStrings);
+    procedure SetDirectory(const AValue: String);
+    procedure SetOptions(const AValue: String);
+    procedure SetOses(const AValue: TOSes);
+    procedure SetURL(const AValue: String);
+    procedure SetVersion(const AValue: String);
+    procedure SetLicense(const AValue: String);
+  Protected
+    Procedure Log(Level : TVerboseLevel; Const Msg : String);
+    Procedure CreatePackages; virtual;
+    Procedure CheckPackages; virtual;
+    Procedure CreateBuildEngine; virtual;
+    Procedure CheckDefaultPackage;
+    Procedure Error(Msg : String);
+    Procedure Error(Fmt : String; Args : Array of const);
+    Procedure AnalyzeOptions;
+    Procedure Usage(FMT : String; Args : Array of const);
+    Procedure Compile(Force : Boolean); virtual;
+    Procedure Clean; virtual;
+    Procedure Install; virtual;
+    Procedure Archive; virtual;
+    Procedure Download; Virtual;
+    Property BuildEngine : TBuildEngine Read FBuildEngine;
+  Public
+    Constructor Create(AOWner : TComponent); override;
+    Destructor destroy; override;
+    Function StartPackage(Const AName : String) : TPackage;
+    Procedure EndPackage;
+    Function Run : Boolean;
+    Function AddTarget(AName : String) : TTarget;
+    Procedure AddDependency(AName : String);
+    Property DefaultPackage : TPackage read FDefaultPackage write SetDefaultPackage;
+    Property Packages : TPackages Read FPackages;
+    Property Dependencies : TStrings Index 0 Read GetStrings Write SetStrings;
+    Property InstallFiles : TStrings Index 1 Read GetStrings Write SetStrings;
+    Property CleanFiles : TStrings Index 2 Read GetStrings Write SetStrings;
+    Property ArchiveFiles : TStrings Index 3 Read GetStrings Write SetStrings;
+    Property Defaults : TDefaults Read FDefaults Write SetDefaults;
+    Property RunMode : TRunMode Read FRunMode;
+    Property ListMode : Boolean Read FListMode;
+    // Default Package redirects.
+    Property Targets : TTargets Read GetTargets;
+    Property Version : String Read GetVersion Write SetVersion;
+    Property URL : String Read GetURL Write SetURL;
+    Property Author : String Read GetAuthor Write SetAuthor;
+    Property License : String Read GetLicense Write SetLicense;
+    Property Directory : String Read GetDirectory Write SetDirectory;
+    Property Options : String Read GetOptions Write SetOptions;
+    Property OS: TOSes Read GetOSes Write SetOses;
+  end;
+
+  ECollectionError = Class(Exception);
+  EInstallerError = Class(Exception);
+  
+Const
+  // Aliases
+  AmD64   = X86_64;
+  PowerPC = PPC;
+  dos = go32v2;
+
+  // Useful
+  UnitExt = '.ppu';
+  PPUExt  = UnitExt;
+  PasExt  = '.pas';
+  PPExt   = '.pp';
+  IncExt  = '.inc';
+  ObjExt  = '.o';
+  RstExt  = '.rst';
+  LibExt  = '.a';
+  SharedLibExt = '.so';
+  DLLExt  = '.dll';
+  ExeExt  = '.exe';
+  
+
+  // Targets
+  i386_Linux = 'i386-linux';
+  i386_Win32 = 'i386-win32';
+  i386_Dos = 'i386-go32v2';
+  i386_OS2 = 'i386-os2';
+  i386_FreeBSD = 'i386-freebsd';
+  i386_NetBSD = 'i386-netsd';
+  i386_OpenBSD = 'i386-openbsd';
+  i386_netware = 'i386-netware';
+  i386_netwlibc = 'i386-netwlibc';
+  i386_go32v2 = 'i386-go32v2';
+  PPC_Linux = 'ppc-linux';
+  powerpc_linux = PPC_linux;
+  sparc_linux = 'sparc-linux';
+  arm_linux = 'arm-linux';
+  ppc_macos = 'ppc-macos';
+  ppc_darwin = 'ppc-darwin';
+
+  UnitTargets = [ttUnit,ttExampleUnit];
+  ProgramTargets = [ttProgram,ttExampleProgram];
+
+
+  AllMessages = [vlError,vlWarning,vlInfo,vlCompare,vlCommand];
+
+
+Var
+  Installer : TInstaller;
+  Defaults : TDefaults; // Set by installer.
+
+Function OSToString(OS: TOS) : String;
+Function OSesToString(OSes: TOSes) : String;
+Function CPUToString(CPU: TCPU) : String;
+Function CPUSToString(CPUS: TCPUS) : String;
+Function StringToOS(S : String) : TOS;
+Function OSesToString(S : String) : TOSes;
+Function StringToCPU(S : String) : TCPU;
+Function StringToCPUS(S : String) : TCPUS;
+Function ModeToString(Mode: TCompilerMode) : String;
+Function StringToMode(S : String) : TCompilerMode;
+Function MakeTargetString(CPU : TCPU;OS: TOS) : String;
+Procedure StringToCPUOS(S : String; Var CPU : TCPU; Var OS: TOS);
+Procedure ResolveDependencies(L : TStrings; P : TNamedCollection);
+Function AddStrings(Dest,Src : TStrings) : Integer ;
+function AddStrings(Dest, Src : TStrings; Const APrefix : String) : Integer ;
+Function FileListToString(List : TStrings; Prefix : String) : String;
+Function FixPath (APath : String) : String;
+Procedure ChangeDir(APath : String);
+
+Implementation
+
+uses TypInfo;
+
+ResourceString
+  SErrNameExists        = 'Name "%s" already exists in the collection.';
+  SErrNoSuchName        = 'Could not find item with name "%s" in the collection.';
+  SErrInvalidCPU        = 'Invalid CPU name : "%s"';
+  SErrInvalidOS         = 'Invalid OS name : "%s"';
+  SErrInvalidMode       = 'Invalid compiler mode : "%s"';
+  SErrNoPackage         = 'No package available. Add package with StartPackage Call';
+  SErrInValidArgument   = 'Invalid command-line argument at position %d : %s';
+  SErrNeedArgument      = 'Option at position %d (%s) needs an argument';
+  SErrInvalidTarget     = 'Invalid compiler target: %s';
+  SErrNoPackagesDefined = 'No action possible: No packages were defined.';
+  SErrInstaller         = 'The installer encountered the following error:';
+  SErrDepUnknownTarget  = 'Unknown target in dependencies for %s: %s';
+  SErrExternalCommandFailed = 'External command "%s" failed with exit code: %d';
+  SErrCreatingDirectory = 'Failed to create directory: %s';
+  SErrDeletingFile      = 'Failed to delete file: %s';
+  SErrMovingFile        = 'Failed to move file "%s" to "%s"';
+  SErrCopyingFile       = 'Failed to copy file "%s" to "%s"';
+  SErrChangeDirFailed   = 'Failed to enter directory: %s';
+  SWarnCircularDependency = 'Warning: Circular dependency detected when compiling target %s: %s';
+  SWarnFailedToSetTime  = 'Warning: Failed to set timestamp on file : %s';
+  SWarnFailedToGetTime  = 'Warning: Failed to get timestamp from file : %s';
+  SWarnFileDoesNotExist = 'Warning: File "%s" does not exist';
+  
+  // Log messages
+  SLogEnterDir           = 'Entering directory: %s';
+  SLogCompilingPackage   = 'Compiling package : %s';
+  SLogCompilingTarget    = 'Compiling target  : %s';
+  SLogExecutingCommand   = 'Executing command %s with options: %s';
+  SLogCreatingOutputDir  = 'Creating output dir : %s';
+  SLogInstallingPackage  = 'Installing package : %s';
+  SLogArchivingPackage   = 'Archiving package : %s';
+  SLogCleaningPackage    = 'Cleaning package : %s';
+  SLogDownloadingPackage = 'Downloading package : %s';
+  SLogCopyingFile        = 'Copying file "%s" to "%s"';
+  
+Const
+  // Keys for Defaults file. Do not localize.
+  KeyCompiler = 'Compiler';
+  KeyArchive  = 'Archive';
+  KeyCopy     = 'Copy';
+  KeyDownLoad = 'Download';
+  KeyMkDir    = 'MkDir';
+  KeyMove     = 'Move';
+  KeyRemove   = 'Remove';
+  KeyOptions  = 'Options';
+  KeyCPU      = 'CPU';
+  KeyOS       = 'OS';
+  KeyMode     = 'Mode';
+  KeyPrefix   = 'Prefix';
+  KeyTarget   = 'Target';
+  KeyBaseInstallDir     = 'BaseInstallDir';
+  KeyUnitInstallDir     = 'UnitInstallDir';
+  KeyBinInstallDir      = 'BinInstallDir';
+  KeyDocInstallDir      = 'DocInstallDir';
+  KeyExamplesInstallDir = 'ExamplesInstallDir';
+
+// Callback for Sysutils getapplicationname.
+
+Function GetFPMakeName : String;
+
+begin
+  Result:='fpmake';
+end;
+
+
+Function OSToString(OS: TOS) : String;
+
+begin
+  Result:=LowerCase(GetenumName(TypeInfo(TOS),Ord(OS)));
+end;
+
+Function OSesToString(OSes: TOSes) : String;
+
+begin
+  Result:=LowerCase(SetToString(TypeInfo(TOSes),Integer(OSes),False));
+end;
+
+Function CPUToString(CPU: TCPU) : String;
+
+begin
+  Result:=LowerCase(GetenumName(TypeInfo(TCPU),Ord(CPU)));
+end;
+
+Function CPUSToString(CPUS: TCPUS) : String;
+
+begin
+  Result:=LowerCase(SetToString(TypeInfo(TOSes),Integer(CPUS),False));
+end;
+
+Function StringToOS(S : String) : TOS;
+
+Var
+  I : Integer;
+
+begin
+  I:=GetEnumValue(TypeInfo(TOS),S);
+  if (I=-1) then
+    Raise EInstallerError.CreateFmt(SErrInvalidOS,[S]);
+  Result:=TOS(I);
+end;
+
+
+Function OSesToString(S : String) : TOSes;
+
+begin
+  Result:=TOSes(StringToSet(TypeInfo(TOSes),S));
+end;
+
+Function StringToCPU(S : String) : TCPU;
+
+Var
+  I : Integer;
+
+begin
+  I:=GetEnumValue(TypeInfo(TCPU),S);
+  if (I=-1) then
+    Raise EInstallerError.CreateFmt(SErrInvalidCPU,[S]);
+  Result:=TCPU(I);
+end;
+
+Function StringToCPUS(S : String) : TCPUS;
+
+begin
+  Result:=TCPUS(StringToSet(TypeInfo(TCPUS),S));
+end;
+
+Function ModeToString(Mode: TCompilerMode) : String;
+
+begin
+  Result:=LowerCase(GetenumName(TypeInfo(TCompilerMode),Ord(Mode)));
+end;
+
+Function StringToMode(S : String) : TCompilerMode;
+
+Var
+  I : Integer;
+
+begin
+  I:=GetEnumValue(TypeInfo(TCompilerMode),S);
+  if (I=-1) then
+    Raise EInstallerError.CreateFmt(SErrInvalidMode,[S]);
+  Result:=TCompilerMode(I);
+end;
+
+
+Function MakeTargetString(CPU : TCPU;OS: TOS) : String;
+
+begin
+  Result:=CPUToString(CPU)+'-'+OSToString(OS);
+end;
+
+Procedure StringToCPUOS(S : String; Var CPU : TCPU; Var OS: TOS);
+
+Var
+  P : integer;
+
+begin
+  P:=Pos('-',S);
+  If (P=0) then
+    Raise EInstallerError.CreateFmt(SErrInvalidTarget,[S]);
+  CPU:=StringToCPU(Copy(S,1,P-1));
+  OS:=StringToOs(Copy(S,P+1,Length(S)-P));
+end;
+
+Procedure ResolveDependencies(L : TStrings; P : TNamedCollection);
+
+Var
+  I,J : Integer;
+
+begin
+  If Assigned(L) then
+    For I:=0 to L.Count-1 do
+      begin
+      J:=P.IndexOfName(L[i]);
+      If J<>-1 then
+        L.Objects[I]:=P.Items[J];
+      end;
+end;
+
+Function AddStrings(Dest,Src : TStrings) : Integer ;
+
+begin
+  Result:=AddStrings(Dest,Src,'');
+end;
+
+Procedure AddStrings(Var S : String; L : TStrings; Prefix : String);
+
+Var
+  I : Integer;
+
+begin
+  For I:=0 to L.Count-1 do
+    begin
+    if (S<>'') then
+      S:=S+' ';
+    S:=S+Prefix+L[i];
+    end;
+end;
+
+function AddStrings(Dest, Src : TStrings; Const APrefix : String) : Integer ;
+begin
+  Result:=0;
+  While (Result<Src.Count) do
+    begin
+    If (APrefix<>'') then
+      Dest.Add(APrefix+Src[Result]) // Not sure whether '' is optimized away.
+    else
+      Dest.Add(Src[Result]);
+    Inc(Result);
+    end;
+end;
+
+function FileListToString(List : TStrings; Prefix : String) : String;
+
+Var
+  I : integer;
+  S : String;
+
+begin
+  Result:='';
+  For I:=0 to List.Count-1 do
+    begin
+    If (I>0) then
+      Result:=Result+' ';
+    S:=Prefix+List[i];
+    If (Pos(' ',S)<>0) then
+      S:='"'+S+'"';
+    Result:=Result+S;
+    end;
+end;
+
+function FixPath (APath : String) : String;
+
+Var
+  P : PChar;
+  
+begin
+  Result:=APath;
+  If (result<>'') then
+    begin
+    P:=PChar(Result);
+    While (P^<>#0) do
+      begin
+      If P^ in ['/','\',':'] then // do not use drive letters.
+        P^:=PathDelim;
+      Inc(P);
+      end;
+    end;
+end;
+
+procedure ChangeDir(APath : String);
+
+begin
+  if Not SetCurrentDir(APath) then
+    Raise EInstallerError.CreateFmt(SErrChangeDirFailed,[APath]);
+end;
+
+{ TNamedItem }
+
+procedure TNamedItem.SetName(const AValue: String);
+
+begin
+  if FName=AValue then exit;
+  With TNamedCollection(Collection) do
+    If UniqueNames then
+      If (IndexOfName(AVAlue)<>-1) then
+        Raise ECollectionError.CreateFmt(SErrNameExists,[AValue]);
+  FName:=AValue;
+end;
+
+{ TNamedCollection }
+
+function TNamedCollection.IndexOfName(AName: String): Integer;
+
+begin
+  Result:=Count-1;
+  While (Result>=0) and (CompareText(TNamedItem(Items[Result]).FName,AName)<>0) do
+    Dec(Result);
+end;
+
+function TNamedCollection.ItemByName(AName: String): TNamedItem;
+
+Var
+  I : Integer;
+
+begin
+  I:=IndexOfName(AName);
+  If (I=-1) Then
+    Raise ECollectionError.CreateFmt(SErrNoSuchName,[AName]);
+  Result:=TNamedItem(Items[i]);
+end;
+
+{ TFileItem }
+
+function TFileItem.GetExtension: String;
+begin
+  Result:=ExtractFileExt(Name);
+end;
+
+function TFileItem.GetPath: String;
+begin
+  Result:=ExtractFilePath(Name);
+end;
+
+{ TTargets }
+
+function TTargets.GetTargetItem(Index : Integer): TTarget;
+begin
+  Result:=TTarget(Items[Index]);
+end;
+
+function TTargets.GetTarget(AName : String): TTarget;
+begin
+  Result:=TTarget(ItemByName(AName));
+end;
+
+procedure TTargets.SetDefaultDir(const AValue: String);
+begin
+  If (AValue<>'') then
+    FDefaultDir:=IncludeTrailingPathDelimiter(AValue)
+  else
+    FDefaultDir:='';
+end;
+
+procedure TTargets.SetTargetItem(Index : Integer; const AValue: TTarget);
+
+begin
+  Items[Index]:=AValue;
+end;
+
+procedure TTargets.ApplyDefaults(ATarget: TTarget);
+begin
+  If (ATarget.Directory='') then
+    ATarget.Directory:=FDefaultDir;
+   ATarget.OS:=FDefaultOS;
+   ATarget.CPU:=FDefaultCPU;
+end;
+
+procedure TTargets.ResetDefaults;
+begin
+  FDefaultDir:='';
+  FDefaultOS:=[];
+  FDefaultCPU:=[];
+end;
+
+Function TTargets.AddUnit(AUnitName: String) : TTarget;
+
+
+begin
+  Result:=Add as TTarget;
+  Result.Name:=AUnitName;
+  Result.TargetType:=TTUnit;
+  ApplyDefaults(Result);
+end;
+
+Function TTargets.AddProgram(AProgramName: String) : TTarget;
+begin
+  Result:=Add as TTarget;
+  Result.Name:=AProgramName;
+  Result.TargetType:=ttProgram;
+  ApplyDefaults(Result);
+end;
+
+Function TTargets.AddExampleUnit(AUnitName: String): TTarget;
+begin
+  Result:=Add as TTarget;
+  Result.Name:=AUnitName;
+  Result.TargetType:=ttExampleUnit;
+  ApplyDefaults(Result);
+end;
+
+Function TTargets.AddExampleProgram(AProgramName: String): TTarget;
+begin
+  Result:=Add as TTarget;
+  Result.Name:=AProgramName;
+  Result.TargetType:=ttExampleProgram;
+  ApplyDefaults(Result);
+end;
+
+
+{ TNamedItemList }
+
+function TNamedItemList.GetNamedItem(Index : Integer): TNamedItem;
+begin
+  Result:=TNamedItem(Items[Index]);
+end;
+
+procedure TNamedItemList.SetNamedItem(Index : Integer; const AValue: TNamedItem);
+begin
+  Items[Index]:=AValue;
+end;
+
+function TNamedItemList.IndexOfName(AName: String): Integer;
+
+begin
+  Result:=Count-1;
+  While (Result>=0) and (CompareText(GetNamedItem(Result).Name,AName)<>0) do
+    Dec(Result);
+end;
+
+function TNamedItemList.ItemByName(ANAme: String): TNamedItem;
+
+Var
+  I : Integer;
+
+begin
+  I:=IndexOfName(AName);
+  If (I=-1) Then
+    Raise ECollectionError.CreateFmt(SErrNoSuchName,[AName]);
+  Result:=TNamedItem(Items[i]);
+end;
+
+{ TDefaults }
+
+procedure TDefaults.SetCPU(const AValue: TCPU);
+begin
+  FCPU:=AValue;
+  RecalcTarget;
+end;
+
+function TDefaults.GetBaseInstallDir: String;
+begin
+  If (FBaseInstallDir<>'') then
+    Result:=FBaseInstallDir
+  else
+    if UnixPaths then
+      Result:=Prefix+PathDelim+'lib'+PathDelim+'fpc'
+    else
+      Result:=Prefix;
+
+end;
+
+function TDefaults.GetBinInstallDir: String;
+begin
+  If (FBinInstallDir<>'') then
+    Result:=FBinInstallDir
+  else
+    If UnixPaths then
+      Result:=BaseInstallDir+PathDelim+'bin'
+    else
+      Result:=BaseInstallDir+PathDelim+'bin';
+end;
+
+function TDefaults.GetCompiler: String;
+begin
+  If (FCompiler<>'') then
+    Result:=FCompiler
+  else
+    Case CPU of
+      i386    : Result:='ppc386';
+      PowerPC : Result:='ppcppc';
+      sparc   : Result:='ppcsparc';
+      arm     : Result:='ppcarm';
+      x86_64  : Result:='ppcx64';
+    end;
+end;
+
+function TDefaults.GetDocInstallDir: String;
+begin
+  If (FBinInstallDir<>'') then
+    Result:=FBinInstallDir
+  else
+    If UnixPaths then
+      Result:=Prefix+PathDelim+'share'+PathDelim+'docs'
+    else
+      Result:=BaseInstallDir+PathDelim+'docs';
+end;
+
+function TDefaults.GetExamplesInstallDir: String;
+begin
+  If (FExamplesInstallDir<>'') then
+    Result:=FExamplesInstallDir
+  else
+    If UnixPaths then
+      Result:=Prefix+PathDelim+'share'+PathDelim+'docs'+PathDelim+'examples'
+    else
+      Result:=BaseInstallDir+PathDelim+'examples';
+end;
+
+function TDefaults.GetUnitInstallDir: String;
+begin
+  If (FUnitInstallDir<>'') then
+    Result:=FBinInstallDir
+  else
+    If UnixPaths then
+      Result:=BaseInstallDir+PathDelim+'units'+PathDelim+Target
+    else
+      Result:=BaseInstallDir+PathDelim+'units'+PathDelim+Target;
+end;
+
+procedure TDefaults.SetBaseInstallDir(const AValue: String);
+begin
+  FBaseInstallDir:=AValue;
+  UnitInstallDir:='';
+  BinInstallDir:='';
+  ExamplesInstallDir:='';
+end;
+
+procedure TDefaults.SetOS(const AValue: TOS);
+begin
+  FOS:=AValue;
+  Recalctarget;
+end;
+
+procedure TDefaults.SetPrefix(const AValue: String);
+begin
+  if FPrefix=AValue then exit;
+  FPrefix:=AValue;
+  BaseInstallDir:='';
+end;
+
+procedure TDefaults.SetTarget(const AValue: String);
+
+Var
+  P : Integer;
+
+begin
+  if FTarget<>AValue then
+    begin
+    P:=Pos('-',AValue);
+    If (P<>0) then
+      begin
+      FOS:=StringToOS(System.Copy(Avalue,P+1,Length(AValue)-P));
+      FCPU:=StringToCPU(System.Copy(Avalue,1,P-1));
+      end
+    else
+      FOS:=StringToOS(AValue);
+    FTarget:=AValue;
+    end;
+end;
+
+procedure TDefaults.RecalcTarget;
+begin
+  Ftarget:=CPUToString(FCPU)+'-'+OStoString(FOS);
+end;
+
+constructor TDefaults.Create;
+begin
+  InitDefaults;
+end;
+
+procedure TDefaults.InitDefaults;
+begin
+  {$ifdef unix}
+  UnixPaths:=True;
+  {$else}
+  UnixPaths:=False;
+  {$endif}
+  // Code to init defaults for compiled platform.
+  CPU:=StringToCPU({$I %FPCTARGETCPU%});
+  OS:=StringToOS({$I %FPCTARGETOS%});
+  Compiler:='ppc386';
+end;
+
+procedure TDefaults.Assign(ASource: TPersistent);
+
+Var
+  d : TDefaults;
+
+begin
+  If ASource is TDefaults then
+    begin
+    D:=ASource as TDefaults;
+    FArchive:=D.Farchive;
+    FCompiler:=D.Compiler;
+    FCopy:=D.FCopy;
+    FCPU:=D.FCPU;
+    FMode:=D.FMode;
+    FDownload:=D.FDownload;
+    FMkDir:=D.FDownload;
+    FMove:=D.FDownload;
+    FOptions:=D.FOptions;
+    FOS:=D.FOS;
+    FPrefix:=D.FPrefix;
+    FBaseInstallDir:=D.FBaseInstallDir;
+    FUnitInstallDir:=D.FUnitInstallDir;
+    FBinInstallDir:=D.FBinInstallDir;
+    FDocInstallDir:=D.FDocInstallDir;
+    FExamplesInstallDir:=D.FExamplesInstallDir;
+    FRemove:=D.FRemove;
+    FTarget:=D.FTarget;
+    FUnixPaths:=D.FUnixPaths;
+    end;
+end;
+
+procedure TDefaults.LocalInit(Const AFileName : String);
+
+Var
+  FN : String;
+
+begin
+  FN:=AFileName;
+  If (FN='') then
+    begin
+    // Environment variable.
+    FN:=GetEnvironmentVariable('FPMAKECFG');
+    If (FN<>'') then
+      If not FileExists(FN) then
+        FN:='';
+    // User config file fpmake.cfg
+    If (FN='') then
+      begin
+      FN:=GetAppConfigFile(False);
+      If Not FileExists(FN) then
+        FN:='';
+      end;
+    // Global config file fpmake.cfg
+    If (FN='') then
+      begin
+      FN:=GetAppConfigFile(True);
+      If Not FileExists(FN) then
+        FN:='';
+      end;
+    end;
+  If (FN<>'') and FileExists(FN) then
+    LoadFromFile(FN)
+  // Code to find local config file and load it using LoadFromFile.
+end;
+
+procedure TDefaults.LoadFromFile(Const AFileName: String);
+
+Var
+  F : TFileStream;
+
+begin
+  F:=TFileStream.Create(AFileName,fmOpenRead);
+  Try
+    LoadFromStream(F);
+  Finally
+    F.Free;
+  end;
+end;
+
+procedure TDefaults.SaveToFile(Const AFileName: String);
+
+Var
+  F : TFileStream;
+
+begin
+  F:=TFileStream.Create(AFileName,fmCreate);
+  Try
+    SaveToStream(F);
+  Finally
+    F.Free;
+  end;
+end;
+
+procedure TDefaults.SaveToStream(S : TStream);
+
+Var
+  L : TStringList;
+
+begin
+  L:=TStringList.Create;
+  try
+    With L do
+      begin
+      Values[KeyArchive]:=FArchive;
+      Values[KeyCompiler]:=FCompiler;
+      Values[KeyCopy]:=FCopy;
+      Values[KeyDownLoad]:=FDownload;
+      Values[KeyMkDir]:=FMkDir;
+      Values[KeyMove]:=FMove;
+      Values[KeyOptions]:=FOptions;
+      Values[KeyCPU]:=CPUToString(FCPU);
+      Values[KeyOS]:=OSToString(FOS);
+      Values[KeyMode]:=ModeToString(FMode);
+      Values[KeyPrefix]:=FPrefix;
+      Values[KeyBaseInstallDir]:=FBaseInstallDir;
+      Values[KeyUnitInstallDir]:=FUnitInstallDir;
+      Values[KeyBinInstallDir]:=FBinInstallDir;
+      Values[KeyDocInstallDir]:=FDocInstallDir;
+      Values[KeyExamplesInstallDir]:=FExamplesInstallDir;
+      Values[KeyRemove]:=FRemove;
+      Values[KeyTarget]:=FTarget;
+      end;
+    L.SaveToStream(S);
+  Finally
+    L.Free;
+  end;
+end;
+
+procedure TDefaults.LoadFromStream(S: TStream);
+
+Var
+  L : TStrings;
+  Line : String;
+  I,P,PC : Integer;
+  
+  
+begin
+  L:=TStringList.Create;
+  Try
+    L.LoadFromStream(S);
+    // Fix lines.
+    For I:=L.Count-1 downto 0 do
+      begin
+      Line:=L[I];
+      P:=Pos('=',Line);
+      PC:=Pos(';',Line);  // Comment line.
+      If (P=0) or ((PC<>0) and (PC<P)) then
+        L.Delete(I)
+      else
+        L[i]:=Trim(System.Copy(Line,1,P-1)+'='+Trim(System.Copy(Line,P+1,Length(Line)-P)));
+      end;
+    With L do
+      begin
+      FArchive:=Values[KeyArchive];
+      FCompiler:=Values[KeyCompiler];
+      FDownload:=Values[KeyDownLoad];
+      FCopy:=Values[KeyCopy];
+      FMkDir:=Values[KeyMkDir];
+      FMove:=Values[KeyMove];
+      FRemove:=Values[KeyRemove];
+      FOptions:=Values[KeyOptions];
+      Line:=Values[KeyCPU];
+      If (Line<>'') then
+        FCPU:=StringToCPU(Line);
+      Line:=Values[KeyOS];
+      If (Line<>'') then
+        FOS:=StringToOS(Line);
+      Line:=Values[KeyMode];
+      If (Line<>'') then
+        FMode:=StringToMode(Line);
+      FTarget:=Values[KeyTarget];
+      FPrefix:=Values[KeyPrefix];
+      FBaseInstallDir:=Values[KeyBaseInstallDir];
+      FUnitInstallDir:=Values[KeyUnitInstallDir];
+      FBinInstallDir:=Values[KeyBinInstallDir];
+      FDocInstallDir:=Values[KeyDocInstallDir];
+      FExamplesInstallDir:=Values[KeyExamplesInstallDir];
+      end;
+  Finally
+    L.Free;
+  end;
+end;
+
+{ TPackage }
+
+function TPackage.GetHasStrings(AIndex: integer): Boolean;
+begin
+  Result:=False;
+  Case AIndex Of
+    0 : Result:=FUnitPath<>Nil;
+    1 : Result:=FObjectPath<>Nil;
+    2 : Result:=FIncludePath<>Nil;
+    3 : Result:=FDependencies<>Nil;
+    4 : Result:=FInstallFiles<>Nil;
+    5 : Result:=FCleanFiles<>Nil;
+    6 : Result:=FArchiveFiles<>Nil;
+  end;
+end;
+
+function TPackage.GetStrings(AIndex: integer): TStrings;
+
+  Function EnsureStrings(Var S : TStrings) : TStrings;
+
+  begin
+    If (S=Nil) then
+      S:=TStringList.Create;
+    Result:=S;
+  end;
+
+begin
+  Result:=Nil;
+  Case AIndex Of
+    0 : Result:=EnsureStrings(FUnitPath);
+    1 : Result:=EnsureStrings(FObjectPath);
+    2 : Result:=EnsureStrings(FIncludePath);
+    3 : begin
+        Result:=EnsureStrings(FDependencies);
+        With TStringList(Result) do
+          if (Count=0) then
+            begin
+            Sorted:=True;
+            Duplicates:=dupError;
+            end;
+        end;
+    4 : Result:=EnsureStrings(FInstallFiles);
+    5 : Result:=EnsureStrings(FCleanFiles);
+    6 : Result:=EnsureStrings(FArchiveFiles);
+  end;
+
+
+end;
+
+procedure TPackage.SetStrings(AIndex: integer; const AValue: TStrings);
+begin
+  GetStrings(AIndex).Assign(AValue);
+end;
+
+constructor TPackage.Create(ACollection: TCollection);
+
+Var
+  L : TStringList;
+
+begin
+  inherited Create(ACollection);
+  FTargets:=TTargets.Create(TTarget);
+  L:=TStringList.Create;
+  FDependencies:=L;
+  FInstallFiles:=TStringList.Create;
+  FCleanFiles:=TStringList.Create;
+  FArchiveFiles:=TStringList.Create;
+end;
+
+destructor TPackage.destroy;
+begin
+  FreeAndNil(FDependencies);
+  FreeAndNil(FInstallFiles);
+  FreeAndNil(FCleanFiles);
+  FreeAndNil(FArchiveFiles);
+  FreeAndNil(FIncludePath);
+  FreeAndNil(FObjectPath);
+  FreeAndNil(FUnitPath);
+  FreeAndNil(FTargets);
+  inherited destroy;
+end;
+
+function TPackage.AddTarget(AName: String): TTarget;
+begin
+  Result:=Targets.Add as TTarget;
+  Result.Name:=AName;
+end;
+
+procedure TPackage.AddDependency(AName: String);
+begin
+  If FDependencies.IndexOf(AName)=-1 then
+    FDependencies.Add(AName);
+end;
+
+procedure TPackage.AddInstallFile(AFileName: String);
+begin
+  FInstallFiles.add(AFileName);
+end;
+
+procedure TPackage.GetCleanFiles(List: TStrings; Const APrefix : String; AOS : TOS);
+
+Var
+  I : Integer;
+
+begin
+  AddStrings(List,CleanFiles,APrefix);
+  For I:=0 to FTargets.Count-1 do
+    FTargets.TargetItems[I].GetCleanFiles(List,APrefix,AOS);
+end;
+
+procedure TPackage.GetInstallFiles(List: TStrings;Types : TTargetTypes;Const APrefix : String; AOS : TOS);
+
+Var
+  I : Integer;
+  T : TTarget;
+
+begin
+  AddStrings(List,InstallFiles,APrefix);
+  For I:=0 to FTargets.Count-1 do
+    begin
+    T:=FTargets.TargetItems[I];
+    if (T.TargetType in Types)  then
+      T.GetInstallFiles(List,APrefix,AOS);
+    end;
+end;
+
+
+procedure TPackage.GetArchiveFiles(List: TStrings;Const APrefix : String; AOS : TOS);
+
+Var
+  I : Integer;
+
+begin
+  If (OS=[]) or (AOS in OS) then
+    begin
+    AddStrings(List,ArchiveFiles,APrefix);
+    For I:=0 to FTargets.Count-1 do
+      FTargets.TargetItems[I].GetArchiveFiles(List,APrefix,AOS);
+    end;
+end;
+
+function TPackage.NeedsCompile(ATargetDir : String; AOS : TOS): Boolean;
+
+Var
+  I : Integer;
+  P : TPackage;
+
+begin
+  ResolveDependencies(Dependencies,(Collection as TPackages));
+  Result:=False;
+  I:=0;
+  While (Not Result) and (I<FDependencies.Count) do
+    begin
+    P:=TPackage(FDependencies.Objects[i]);
+    // I'm not sure whether the target dir is OK here ??
+    Result:=Assigned(P) and P.NeedsCompile(ATargetDir,AOS);
+    Inc(I);
+    end;
+  If Not Result then
+    begin
+    I:=0;
+    While (Not Result) and (I<FTargets.Count) do
+      begin
+      Result:=FTargets.TargetItems[i].NeedsCompile(ATargetDir,AOS);
+      Inc(I);
+      end;
+    end;
+end;
+
+
+{ TPackages }
+
+function TPackages.GetPackage(AName : String): TPackage;
+begin
+  Result:=TPackage(ItemByName(AName))
+end;
+
+function TPackages.GetPackageItem(AIndex : Integer): TPackage;
+begin
+  Result:=TPackage(Items[AIndex]);
+end;
+
+procedure TPackages.SetPackageItem(AIndex : Integer; const AValue: TPackage);
+begin
+  Items[AIndex]:=AValue;
+end;
+
+function TPackages.AddPackage(const AName: String): TPackage;
+begin
+  Result:=Add as TPackage;
+  Result.Name:=AName;
+end;
+
+
+{ TInstaller }
+
+function TInstaller.GetStrings(AIndex : Integer): TStrings;
+begin
+  CheckDefaultPackage;
+  Case AIndex of
+    0:  Result:=DefaultPackage.Dependencies;
+    1:  Result:=DefaultPackage.InstallFiles;
+    2:  Result:=DefaultPackage.CleanFiles;
+    3:  Result:=DefaultPackage.ArchiveFiles;
+  end;
+end;
+
+function TInstaller.GetDirectory: String;
+begin
+  CheckDefaultPackage;
+  Result:=DefaultPackage.Directory;
+end;
+
+function TInstaller.GetLicense: String;
+begin
+  CheckDefaultPackage;
+  Result:=DefaultPackage.License;
+end;
+
+function TInstaller.GetOSes: TOSes;
+begin
+  CheckDefaultPackage;
+  Result:=DefaultPackage.OS;
+end;
+
+function TInstaller.GetOptions: String;
+begin
+  CheckDefaultPackage;
+  Result:=DefaultPackage.Options;
+end;
+
+function TInstaller.GetAuthor: String;
+begin
+  CheckDefaultPackage;
+  Result:=DefaultPackage.Author;
+end;
+
+function TInstaller.GetTargets: TTargets;
+begin
+  CheckDefaultPackage;
+  Result:=DefaultPackage.Targets;
+end;
+
+function TInstaller.GetURL: String;
+begin
+  CheckDefaultPackage;
+  Result:=DefaultPackage.URL;
+end;
+
+function TInstaller.GetVersion: String;
+begin
+  CheckDefaultPackage;
+  Result:=DefaultPackage.Version;
+end;
+
+procedure TInstaller.SetAuthor(const AValue: String);
+begin
+  CheckDefaultPackage;
+  DefaultPackage.Author:=AValue;
+end;
+
+procedure TInstaller.SetDefaultPackage(const AValue: TPackage);
+begin
+  if FDefaultPackage=AValue then exit;
+  FDefaultPackage:=AValue;
+end;
+
+procedure TInstaller.SetDefaults(const AValue: TDefaults);
+begin
+  FDefaults.Assign(AValue);
+end;
+
+procedure TInstaller.SetStrings(AIndex : Integer; const AValue: TStrings);
+
+Var
+  Res : TStrings;
+
+begin
+  CheckDefaultPackage;
+  Case AIndex of
+    0:  Res:=DefaultPackage.Dependencies;
+    1:  Res:=DefaultPackage.InstallFiles;
+    2:  Res:=DefaultPackage.CleanFiles;
+    3:  Res:=DefaultPackage.ArchiveFiles;
+  end;
+  Res.Assign(Avalue);
+end;
+
+procedure TInstaller.SetDirectory(const AValue: String);
+begin
+  CheckDefaultPackage;
+  DefaultPackage.Directory:=AValue;
+end;
+
+procedure TInstaller.SetOptions(const AValue: String);
+begin
+  CheckDefaultPackage;
+  DefaultPackage.Options:=AValue;
+end;
+
+procedure TInstaller.SetOses(const AValue: TOSes);
+begin
+  CheckDefaultPackage;
+  DefaultPackage.OS:=AValue;
+end;
+
+procedure TInstaller.SetURL(const AValue: String);
+begin
+  CheckDefaultPackage;
+  DefaultPackage.URL:=AValue;
+end;
+
+procedure TInstaller.SetVersion(const AValue: String);
+begin
+  CheckDefaultPackage;
+  DefaultPackage.Version:=AValue;
+end;
+
+procedure TInstaller.SetLicense(const AValue: String);
+begin
+  CheckDefaultPackage;
+  DefaultPackage.License:=AValue;
+end;
+
+procedure TInstaller.Log(Level: TVerboseLevel; const Msg: String);
+begin
+  If Level in FLogLevels then
+    Writeln(StdErr,Msg);
+end;
+
+procedure TInstaller.CreatePackages;
+begin
+  FPAckages:=TPackages.Create(TPackage);
+end;
+
+procedure TInstaller.CreateBuildEngine;
+begin
+  FBuildEngine:=TBuildEngine.Create(Self);
+  FBuildEngine.Defaults:=Defaults;
+  FBuildEngine.ListMode:=FListMode;
+  FBuildEngine.OnLog:[email protected];
+end;
+
+procedure TInstaller.CheckDefaultPackage;
+begin
+  If (FDefaultPackage=Nil) then
+    Raise EInstallerError.Create(SErrNoPackage);
+end;
+
+procedure TInstaller.Error(Msg: String);
+begin
+  Raise EInstallerError.Create(Msg);
+end;
+
+procedure TInstaller.Error(Fmt: String; Args: array of const);
+begin
+  Raise EInstallerError.CreateFmt(Fmt,Args);
+end;
+
+Function TInstaller.StartPackage(const AName: String) : TPackage;
+begin
+  FDefaultPackage:=FPackages.AddPackage(AName);
+  Result:=FDefaultPackage;
+end;
+
+procedure TInstaller.EndPackage;
+begin
+  FDefaultPackage:=Nil;
+end;
+
+procedure TInstaller.AnalyzeOptions;
+
+  Function CheckOption(Index : Integer;Short,Long : String): Boolean;
+
+  var
+    O : String;
+
+  begin
+    O:=Paramstr(Index);
+    Result:=(O='-'+short) or (O='--'+long) or (copy(O,1,Length(Long)+3)=('--'+long+'='));
+  end;
+
+  Function OptionArg(Var Index : Integer) : String;
+
+  Var
+    P : Integer;
+
+  begin
+    if (Length(ParamStr(Index))>1) and (Paramstr(Index)[2]<>'-') then
+      begin
+      If Index<ParamCount then
+        begin
+        Inc(Index);
+        Result:=Paramstr(Index);
+        end
+      else
+        Error(SErrNeedArgument,[Index,ParamStr(Index)]);
+      end
+    else If length(ParamStr(Index))>2 then
+      begin
+      P:=Pos('=',Paramstr(Index));
+      If (P=0) then
+        Error(SErrNeedArgument,[Index,ParamStr(Index)])
+      else
+        begin
+        Result:=Paramstr(Index);
+        Delete(Result,1,P);
+        end;
+      end;
+  end;
+
+Var
+  I : Integer;
+  Nodefaults : Boolean;
+  DefaultsFileName : string;
+
+begin
+  I:=0;
+  NoDefaults:=False;
+  FListMode:=False;
+  While (I<ParamCount) do
+    begin
+    Inc(I);
+    if Checkoption(I,'m','compile') then
+      FRunMode:=rmCompile
+    else if Checkoption(I,'b','build') then
+      FRunMode:=rmBuild
+    else if CheckOption(I,'i','install') then
+      FRunMode:=rmInstall
+    else if CheckOption(I,'c','clean') then
+      FRunMode:=rmClean
+    else if CheckOption(I,'a','archive') then
+      FRunMode:=rmarchive
+    else if CheckOption(I,'d','download') then
+      FRunMode:=rmDownload
+    else if CheckOption(I,'h','help') then
+      FRunMode:=rmhelp
+    else if Checkoption(I,'C','CPU') then
+      Defaults.CPU:=StringToCPU(OptionArg(I))
+    else if Checkoption(I,'O','OS') then
+      Defaults.OS:=StringToOS(OptionArg(I))
+    else if Checkoption(I,'t','target') then
+      Defaults.Target:=OptionArg(I)
+    else if CheckOption(I,'l','list-commands') then
+      FListMode:=True
+    else if Checkoption(I,'P','prefix') then
+      Defaults.Prefix:=OptionArg(I)
+    else if Checkoption(I,'n','nodefaults') then
+      NoDefaults:=true
+    else if CheckOption(I,'b','baseinstalldir') then
+      Defaults.BaseInstallDir:=OptionArg(I)
+    else if CheckOption(I,'r','compiler') then
+      Defaults.Compiler:=OptionArg(I)
+    else if CheckOption(I,'f','config') then
+      DefaultsFileName:=OptionArg(I)
+    else if CheckOption(I,'v','verbose') then
+      begin
+      Try
+        FLogLevels:=TVerboseLevels(StringToSet(TypeInfo(TVerboseLevels),OptionArg(I)));
+      except
+        FLogLevels:=AllMessages;
+      end;
+      end
+    else
+      begin
+      Usage(SErrInValidArgument,[I,ParamStr(I)]);
+      end;
+    end;
+  If Not NoDefaults then
+    Defaults.LocalInit(DefaultsFileName);
+{$ifdef debug}
+  FLogLevels:=AllMessages;
+{$endif}
+end;
+
+procedure TInstaller.Usage(FMT: String; Args: array of const);
+begin
+  If (FMT<>'') then
+    Writeln(stderr,Format(Fmt,Args));
+  Writeln(stderr,ExtractFileName(Paramstr(0)),' usage: ');
+  Writeln(stderr,'');
+  If (FMT<>'') then
+    halt(1);
+end;
+
+procedure TInstaller.Compile(Force: Boolean);
+begin
+  FBuildEngine.ForceCompile:=Force;
+  FBuildEngine.Compile(FPackages);
+end;
+
+procedure TInstaller.Clean;
+begin
+  BuildEngine.Clean(FPackages);
+end;
+
+procedure TInstaller.Install;
+begin
+  BuildEngine.Install(FPackages);
+end;
+
+procedure TInstaller.Archive;
+begin
+  FBuildEngine.Archive(FPackages);
+end;
+
+procedure TInstaller.Download;
+begin
+  BuildEngine.Download(FPackages);
+end;
+
+constructor TInstaller.Create(AOWner: TComponent);
+begin
+  inherited Create(AOWner);
+  FDefaults:=TDefaults.Create;
+  AnalyzeOptions;
+  CreatePackages;
+end;
+
+destructor TInstaller.destroy;
+begin
+  inherited destroy;
+end;
+
+procedure TInstaller.CheckPackages;
+
+begin
+  If (FPackages.Count=0) then
+    Error(SErrNoPackagesDefined);
+  // Check for other obvious errors ?
+end;
+
+Function TInstaller.Run : Boolean;
+
+begin
+  Result:=True;
+  try
+    If RunMode<>rmHelp then
+      begin
+      CheckPackages;
+      CreateBuildEngine;
+      end;
+    Case RunMode of
+      rmHelp    : Usage('',[]);
+      rmCompile : Compile(False);
+      rmBuild   : Compile(True);
+      rmInstall : Install;
+      rmArchive : Archive;
+      rmClean    : Clean;
+      rmDownload : Download;
+    end;
+  except
+    On E : Exception do
+      begin
+      Writeln(StdErr,SErrInstaller);
+      Writeln(StdErr,E.Message);
+      Result:=False;
+      end;
+  end;
+end;
+
+function TInstaller.AddTarget(AName: String): TTarget;
+begin
+  CheckDefaultPackage;
+  Result:=DefaultPackage.AddTarget(AName);
+end;
+
+procedure TInstaller.AddDependency(AName: String);
+begin
+  CheckDefaultPackage;
+  DefaultPackage.AddDependency(AName);
+end;
+
+{ TBuildEngine }
+
+procedure TBuildEngine.SetDefaults(const AValue: TDefaults);
+begin
+  FDefaults.Assign(AValue);
+end;
+
+procedure TBuildEngine.SetTargetDir(const AValue: String);
+begin
+  if FTargetDir=AValue then exit;
+  FTargetDir:=AValue;
+end;
+
+procedure TBuildEngine.Error(Msg: String);
+begin
+  Raise EInstallerError.Create(Msg);
+end;
+
+procedure TBuildEngine.Error(Fmt: String; Args: array of const);
+begin
+  Raise EInstallerError.CreateFmt(Fmt,Args);
+end;
+
+constructor TBuildEngine.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FDefaults:=TDefaults.Create;
+  // Maybe this should be the current directory ?
+  // Or have it as a command-line option.
+  // Would allow to put all 'installers' in one dir and call them
+  // With --start-dir=/path/to/sources.
+  FStartDir:=includeTrailingPathDelimiter(GetCurrentDir);
+end;
+
+procedure TBuildEngine.ExecuteCommand(Cmd: String; Args : String);
+
+Var
+  E : Integer;
+
+begin
+  Log(vlCommand,SLogExecutingCommand,[Cmd,Args]);
+  // We should check cmd for spaces, and move all after first space to args.
+  E:=ExecuteProcess(cmd,args);
+  If (E<>0) then
+    Error(SErrExternalCommandFailed,[Cmd,E]);
+end;
+
+procedure TBuildEngine.SysCopyFile(Const Src,Dest : String);
+
+Var
+  D,S : String;
+  Fin,FOut : TFileStream;
+  Count : Int64;
+  A : Integer;
+  
+begin
+  Log(vlCommand,SLogCopyingFile,[Src,Dest]);
+  FIn:=TFileStream.Create(Src,fmopenRead);
+  Try
+    D:=IncludeTrailingPathDelimiter(Dest);
+    If DirectoryExists(D) then
+      S:=D+ExtractFileName(Src)
+    else
+      S:=Dest;
+    FOut:=TFileStream.Create(S,fmCreate);
+    Try
+      Count:=Fout.CopyFrom(FIn,0);
+      If (Count<>Fin.Size) then
+        Error(SErrCopyingFile,[Src,S]);
+    Finally
+      FreeAndNil(Fout);
+    end;
+    A:=FileGetDate(FIn.Handle);
+    If (A=-1) then
+      log(vlWarning,SWarnFailedToGetTime,[Src])
+    else
+      if FileSetDate(S,A)<>0 then
+        Log(vlWarning,SWarnFailedToSetTime,[S]);
+  finally
+    FreeAndNil(Fin);
+  end;
+end;
+
+procedure TBuildEngine.SysMoveFile(Const Src,Dest : String);
+
+Var
+  S : String;
+
+begin
+    If DirectoryExists(IncludeTrailingPathDelimiter(Dest)) then
+      S:=IncludeTrailingPathDelimiter(Dest)+ExtractFileName(Src)
+    else
+      S:=Dest;
+  If Not RenameFile(Src,S) then
+    begin
+    Try
+      SysCopyFile(Src,S);
+      SysDeleteFile(Src);
+    Except
+      On E : Exception Do
+        Error(SErrMovingFile,[Src,S]);
+    end;
+    end;
+end;
+
+procedure TBuildEngine.SysDeleteFile(Const AFileName : String);
+
+begin
+  if not FileExists(AFileName) then
+    Log(vlWarning,SWarnFileDoesNotExist,[AFileName])
+  else If Not DeleteFile(AFileName) then
+    Error(SErrDeletingFile,[AFileName]);
+end;
+
+procedure TBuildEngine.SysArchiveFiles(List: TStrings;Const AFileName: String);
+begin
+  // To be implemented
+end;
+
+procedure TBuildEngine.Log(Level: TVerboseLevel; const Msg: String);
+begin
+  If Assigned(FOnLog) then
+    FOnLog(Level,Msg);
+end;
+
+procedure TBuildEngine.Log(Level: TVerboseLevel; const Fmt: String;
+  Args: array of const);
+begin
+  Log(Level,Format(Fmt,Args));
+end;
+
+procedure TBuildEngine.EnterDir(ADir: String);
+
+Var
+  D : String;
+
+begin
+  D:=FStartDir;
+  D:=D+ADir;
+  Log(vlInfo,SLogEnterDir,[D]);
+  If Not SetCurrentDir(D) then
+    Error(SErrChangeDirFailed,[D]);
+end;
+
+
+procedure TBuildEngine.CmdCopyFiles(List: TStrings; Const DestDir: String);
+
+Var
+  Args : String;
+  I : Integer;
+
+begin
+  CmdCreateDir(DestDir);
+  If (Defaults.Copy<>'') then
+    begin
+    Args:=FileListToString(List,'');
+    Args:=Args+' '+DestDir;
+    ExecuteCommand(Defaults.Copy,Args);
+    end
+  else
+    For I:=0 to List.Count-1 do
+      SysCopyFile(List[i],DestDir);
+end;
+
+procedure TBuildEngine.CmdCreateDir(DestDir: String);
+
+begin
+  If (Defaults.MkDir<>'') then
+    ExecuteCommand(Defaults.MkDir,DestDir)
+  else
+    If not ForceDirectories(DestDir) then
+      Error(SErrCreatingDirectory,[DestDir]);
+end;
+
+procedure TBuildEngine.CmdMoveFiles(List: TStrings; Const DestDir: String);
+
+Var
+  Args : String;
+  I : Integer;
+
+begin
+  CmdCreateDir(DestDir);
+  If (Defaults.Move<>'') then
+    begin
+    Args:=FileListToString(List,'');
+    Args:=Args+' '+DestDir;
+    ExecuteCommand(Defaults.Move,Args);
+    end
+  else
+    For I:=0 to List.Count-1 do
+      SysMoveFile(List[i],DestDir);
+end;
+
+procedure TBuildEngine.CmdDeleteFiles(List: TStrings);
+
+Var
+  Args : String;
+  I : Integer;
+
+begin
+  If (Defaults.Remove<>'') then
+    begin
+    Args:=FileListToString(List,'');
+    ExecuteCommand(Defaults.Remove,Args);
+    end
+  else
+    For I:=0 to List.Count-1 do
+      SysDeleteFile(List[i]);
+end;
+
+procedure TBuildEngine.CmdArchiveFiles(List: TStrings; Const ArchiveFile: String);
+
+Var
+  Args : String;
+
+begin
+  If (Defaults.Archive<>'') then
+    begin
+    Args:=FileListToString(List,'');
+    Args:=ArchiveFile+' '+Args;
+    ExecuteCommand(Defaults.Archive,Args);
+    end
+  else
+    SysArchiveFiles(List,ArchiveFile);
+end;
+
+// Relative to startdir.
+Function TBuildEngine.GetTargetDir(APackage : TPackage; ATarget : TTarget; AbsolutePath : Boolean = False) : String;
+
+begin
+  If AbsolutePath then
+    Result:=IncludeTrailingPathDelimiter(FStartDir)
+  else
+    Result:='';
+  If (APackage.Directory<>'') then
+    Result:=Result+IncludeTrailingPathDelimiter(APackage.Directory);
+  If (ATarget.Directory<>'') then
+    Result:=IncludeTrailingPathDelimiter(Result+ATarget.Directory);
+
+end;
+
+Function TBuildEngine.TargetOK(Target : TTarget) : Boolean;
+
+begin
+  Result:=(Target.TargetType in [ttUnit,ttProgram])
+          and
+          ((Target.CPU=[]) or (Defaults.CPU in Target.CPU))
+          and
+          ((Target.OS=[]) or (Defaults.OS in Target.OS));
+end;
+
+
+
+Function TBuildEngine.GetCompilerCommand(APackage : TPackage; Target : TTarget) : String;
+
+
+Var
+  PD,TD,OD,RD : String;
+
+begin
+  PD:=IncludeTrailingPathDelimiter(GetPackageDir(APackage,True));
+  OD:=IncludeTrailingPathDelimiter(GetOutputDir(APackage,True));
+  RD:=ExtractRelativePath(PD,OD);
+  If Target.TargetType in ProgramTargets then
+    Result:='-FE.' // Make this relative to target directory.
+  else
+    Result:='-FU'+RD;
+  If Target.Mode<>fpc then
+    Result:=Result+' -M'+ModeToString(Target.Mode)
+  else If Defaults.Mode<>fpc then
+    Result:=Result+' -M'+ModeToString(Defaults.Mode);
+  If (Defaults.Options<>'') then
+    Result:=Result+' '+Defaults.Options;
+  If (APackage.Options<>'') then
+    Result:=Result+' '+APackage.Options;
+  If APackage.HasUnitPath then
+    AddStrings(Result,APackage.UnitPath,'-Fu');
+  If APackage.HasIncludePath then
+    AddStrings(Result,APackage.IncludePath,'-Fi');
+  If APackage.HasObjectPath then
+    AddStrings(Result,APackage.ObjectPath,'-Fo');
+  If Target.HasUnitPath then
+    AddStrings(Result,Target.UnitPath,'-Fu');
+  If Target.HasIncludePath then
+    AddStrings(Result,Target.IncludePath,'-Fi');
+  If Target.HasObjectPath then
+    AddStrings(Result,Target.ObjectPath,'-Fo');
+  If (Target.Options<>'') then
+    Result:=Result+' '+Target.Options;
+  TD:=Target.Directory;
+  if (TD<>'') then
+    TD:=IncludeTrailingPathDelimiter(TD);
+  Result:=Result+' '+TD+Target.SourceFileName;
+end;
+
+Function TBuildEngine.GetCompiler : String;
+
+Var
+  S : String;
+  
+begin
+  // Cache in FCompiler for speed.
+  If (FCompiler='') then
+    begin
+    FCompiler:=Defaults.Compiler;
+    If (ExtractFilePath(FCompiler)='') then
+      begin
+      S:=FileSearch(FCompiler,GetEnvironmentVariable('PATH'));
+      If (S<>'') then
+         FCompiler:=S;
+      end;
+    end;
+  Result:=FCompiler;
+end;
+
+procedure TBuildEngine.Compile(Target: TTarget);
+
+Var
+  S : String;
+  
+begin
+  Log(vlInfo,SLogCompilingTarget,[Target.Name]);
+  If Assigned(Target.BeforeCompile) then
+    Target.BeforeCompile(Target);
+  S:=GetCompilerCommand(FCurrentPackage,Target);
+  ExecuteCommand(GetCompiler,S);
+  If Assigned(Target.AfterCompile) then
+    Target.AfterCompile(Target);
+end;
+
+procedure TBuildEngine.FixDependencies(Target: TTarget);
+
+Var
+  I : Integer;
+  T : TTarget;
+
+begin
+  If Target.HasDependencies then
+    For I:=0 to Target.Dependencies.Count-1 do
+      begin
+      T:=TTarget(Target.Dependencies.Objects[i]);
+      If Assigned(T) then
+        begin
+        If (T.State=tsCompiling) then
+          Log(vlWarning,SWarnCircularDependency,[Target.Name,T.Name])
+        else
+          Compile(T) // If it already was compiled, then State<>tsNeutral, and it won't be compiled again.
+        end
+      else
+        Error(SErrDepUnknownTarget,[Target.Name,Target.Dependencies[i]]);
+      end;
+end;
+
+function TBuildEngine.GetPackageDir(APackage: TPackage; AbsolutePath: Boolean
+  ): String;
+begin
+  If AbsolutePath then
+    Result:= IncludeTrailingPathDelimiter(FStartDir)
+  else
+    Result:='';
+  Result:=Result+APackage.Directory;
+  If (Result<>'') then
+    Result:= ExcludeTrailingPathDelimiter(Result)
+end;
+
+
+Function TBuildEngine.GetOutputDir(APackage : TPackage; AbsolutePath : Boolean = False) : String;
+
+begin
+  If (TargetDir<>'') then
+    Result:=TargetDir
+  else
+    begin
+    If AbsolutePath then
+      Result:=IncludeTrailingPathDelimiter(FStartDir)
+    else
+      Result:='';
+    If (APackage.Directory<>'') then
+      Result:=IncludeTrailingPathDelimiter(Result+APackage.Directory);
+    Result:=Result+'units'+PathDelim+Defaults.Target;
+    end;
+end;
+
+procedure TBuildEngine.CreateOutputDir(APackage: TPackage);
+
+Var
+  D : String;
+
+begin
+  D:=GetOutputDir(APackage,True);
+  Log(vlInfo,SLogCreatingOutputDir,[D]);
+  CmdCreateDir(D);
+end;
+
+Function TBuildEngine.PackageOK(APackage : TPackage) : Boolean;
+
+begin
+  Result:=((APackage.CPU=[]) or (Defaults.CPU in APackage.CPU))
+          and
+          ((APAckage.OS=[]) or (Defaults.OS in APackage.OS));
+end;
+
+procedure TBuildEngine.Compile(APackage: TPackage);
+
+
+Var
+  T : TTarget;
+  I : Integer;
+  
+begin
+  Log(vlInfo,SLogCompilingPackage,[APackage.Name]);
+  FCurrentPackage:=APackage;
+  Try
+    If Assigned(APackage.BeforeCompile) then
+      APackage.BeforeCompile(APackage);
+    If (APackage.Directory<>'') then
+      EnterDir(APackage.Directory);
+    Try
+      CreateOutputDir(APackage);
+      For I:=0 to APackage.Targets.Count-1 do
+        begin
+        T:=APackage.Targets.TargetItems[i];
+        If TargetOK(T) then
+          If (T.State=tsNeutral) then
+            begin
+            If (FForceCompile or T.NeedsCompile(GetOutputDir(APackage,true),Defaults.OS)) then
+              begin
+              T.FTargetState:=tsCompiling;
+              FixDependencies(T);
+              Compile(T);
+              end;
+            T.FTargetState:=tsCompiled;
+            end;
+        end;
+      If Assigned(APackage.AfterCompile) then
+        APackage.AfterCompile(APackage);
+    Finally
+      If (APackage.Directory<>'') then
+        EnterDir('');
+    end;
+  Finally
+    FCurrentPackage:=Nil;
+  end;
+end;
+
+procedure TBuildEngine.CheckExternalPackage(Const APackageName : String);
+
+begin
+  // A check needs to be implemented here.
+  Log(vldebug,'Unresolved external dependency : %s',[APackageName]);
+end;
+
+procedure TBuildEngine.FixDependencies(APackage: TPackage);
+
+Var
+  I : Integer;
+  P : TPackage;
+
+begin
+  if APackage.HasDependencies then
+    For I:=0 to APAckage.Dependencies.Count-1 do
+      begin
+      P:=TPackage(Apackage.Dependencies.Objects[i]);
+      If Assigned(P) then
+        Compile(P) // If it already was compiled, then State<>tsNeutral, and it won't be compiled again.
+      else
+        CheckExternalPackage(Apackage.Dependencies[i]);
+      end;
+end;
+
+Procedure TBuildEngine.InstallPackageFiles(APAckage : TPackage; tt : TTargetType; Const Src,Dest : String);
+
+Var
+  I : Integer;
+  List : TStringList;
+
+begin
+  List:=TStringList.Create;
+  Try
+    APackage.GetInstallFiles(List,[tt],Src,Defaults.OS);
+    if (List.Count>0) then
+      CmdCopyFiles(List,Dest);
+  Finally
+    List.Free;
+  end;
+end;
+
+procedure TBuildEngine.Install(APackage: TPackage);
+
+
+Var
+  PD,D,O : String;
+
+begin
+  If (Apackage.State<>tsCompiled) then
+    Compile(APackage);
+  Log(vlInfo,SLogInstallingPackage,[APackage.Name]);
+  If Assigned(APackage.BeforeInstall) then
+    APackage.BeforeInstall(APackage);
+  O:=IncludeTrailingPathDelimiter(GetOutputDir(APAckage));
+  PD:=IncludeTrailingPathDelimiter(GetPackageDir(APackage));
+  // units
+  D:=IncludeTrailingPathDelimiter(Defaults.UnitInstallDir)+APackage.Name;
+  InstallPackageFiles(APAckage,ttUnit,O,D);
+  // Programs
+  D:=IncludeTrailingPathDelimiter(Defaults.BinInstallDir);
+  InstallPackageFiles(APAckage,ttProgram,PD,D);
+  // Done.
+  APackage.FTargetState:=tsInstalled;
+  If Assigned(APackage.AfterInstall) then
+    APackage.AfterInstall(APackage);
+end;
+
+procedure TBuildEngine.Archive(APackage: TPackage);
+begin
+  Log(vlInfo,SLogArchivingPackage,[APackage.Name]);
+  If Assigned(APackage.BeforeArchive) then
+    APackage.BeforeArchive(APackage);
+  Writeln('Archiving : ',APackage.Name);
+  If Assigned(APackage.AfterArchive) then
+    APackage.AfterArchive(APackage);
+end;
+
+procedure TBuildEngine.Clean(APackage: TPackage);
+
+Var
+  O : String;
+  List : TStringList;
+  
+begin
+  Log(vlInfo,SLogCleaningPackage,[APackage.Name]);
+  If Assigned(APackage.BeforeClean) then
+    APackage.BeforeClean(APackage);
+  O:=IncludeTrailingPathDelimiter(GetOutputDir(APAckage));
+  List:=TStringList.Create;
+  try
+    APackage.GetCleanFiles(List,O,Defaults.OS);
+    if (List.Count>0) then
+      CmdDeleteFiles(List);
+  Finally
+    List.Free;
+  end;
+  If Assigned(APackage.AfterClean) then
+    APackage.AfterClean(APackage);
+end;
+
+procedure TBuildEngine.Download(APackage: TPackage);
+begin
+  Log(vlInfo,SLogDownloadingPackage,[APackage.Name]);
+  If Assigned(APackage.BeforeDownload) then
+    APackage.BeforeDownload(APackage);
+  Writeln('Downloading : ',APackage.Name);
+  If Assigned(APackage.AfterDownload) then
+    APackage.AfterDownload(APackage);
+end;
+
+procedure TBuildEngine.Compile(Packages: TPackages);
+
+Var
+  I : Integer;
+  P : TPackage;
+
+begin
+  If Assigned(BeforeCompile) then
+    BeforeCompile(Self);
+  For I:=0 to Packages.Count-1 do
+    begin
+    P:=Packages.PackageItems[i];
+    If PackageOK(P) then
+      If (P.State=tsNeutral) then
+        begin
+        If (FForceCompile or P.NeedsCompile(TargetDir,Defaults.OS)) then
+          begin
+          P.FTargetState:=tsCompiling;
+          FixDependencies(P);
+          Compile(P);
+          end;
+        P.FTargetState:=tsCompiled;
+        end;
+    end;
+  If Assigned(AfterCompile) then
+    AfterCompile(Self);
+end;
+
+procedure TBuildEngine.Install(Packages: TPackages);
+
+Var
+  I : Integer;
+  P : TPackage;
+
+begin
+  If Assigned(BeforeInstall) then
+    BeforeInstall(Self);
+  For I:=0 to Packages.Count-1 do
+    begin
+    P:=Packages.PackageItems[i];
+    If PackageOK(P) then
+      Install(P);
+    end;
+  If Assigned(AfterInstall) then
+    AfterInstall(Self);
+end;
+
+procedure TBuildEngine.Archive(Packages: TPackages);
+
+Var
+  I : Integer;
+  P : TPackage;
+
+begin
+  If Assigned(BeforeArchive) then
+    BeforeArchive(Self);
+  Log(vlDebug,'Build engine archiving.');
+  For I:=0 to Packages.Count-1 do
+    begin
+    P:=Packages.PackageItems[i];
+    If PackageOK(P) then
+      Archive(P);
+    end;
+  If Assigned(AfterArchive) then
+    AfterArchive(Self);
+end;
+
+procedure TBuildEngine.Clean(Packages: TPackages);
+
+Var
+  I : Integer;
+  P : TPackage;
+
+begin
+  If Assigned(BeforeClean) then
+    BeforeClean(Self);
+  Log(vldebug,'Build engine cleaning.');
+  For I:=0 to Packages.Count-1 do
+    begin
+    P:=Packages.PackageItems[i];
+    If PackageOK(P) then
+      Clean(P);
+    end;
+  If Assigned(AfterClean) then
+    AfterClean(Self);
+end;
+
+procedure TBuildEngine.Download(Packages: TPackages);
+
+Var
+  I : Integer;
+  P : TPackage;
+
+begin
+  If Assigned(BeforeDownload) then
+    BeforeDownload(Self);
+  For I:=0 to Packages.Count-1 do
+    begin
+    P:=Packages.PackageItems[i];
+    If PackageOK(P) then
+      Download(P);
+    end;
+  If Assigned(AfterDownload) then
+    AfterDownload(Self);
+end;
+
+{ TTarget }
+
+function TTarget.GetHasStrings(AIndex: integer): Boolean;
+begin
+  Result:=False;
+  Case AIndex Of
+    0 : Result:=FUnitPath<>Nil;
+    1 : Result:=FObjectPath<>Nil;
+    2 : Result:=FIncludePath<>Nil;
+    3 : Result:=FDependencies<>Nil;
+  end;
+end;
+
+function TTarget.GetStrings(AIndex: integer): TStrings;
+
+  Function EnsureStrings(Var S : TStrings) : TStrings;
+
+  begin
+    If (S=Nil) then
+      S:=TStringList.Create;
+    Result:=S;
+  end;
+  
+begin
+  Result:=Nil;
+  Case AIndex Of
+    0 : Result:=EnsureStrings(FUnitPath);
+    1 : Result:=EnsureStrings(FObjectPath);
+    2 : Result:=EnsureStrings(FIncludePath);
+    3 : Result:=EnsureStrings(FDependencies);
+  end;
+end;
+
+procedure TTarget.SetStrings(AIndex: integer; const AValue: TStrings);
+begin
+  GetStrings(AIndex).Assign(AValue);
+end;
+
+
+function TTarget.GetSourceFileName: String;
+begin
+  Result:=Name+FExtension;
+end;
+
+function TTarget.GetUnitFileName: String;
+begin
+  Result:=Name+UnitExt;
+end;
+
+function TTarget.GetObjectFileName: String;
+begin
+  Result:=Name+ObjExt;
+end;
+
+function TTarget.GetRSTFileName: String;
+begin
+  Result:=Name+RSText;
+end;
+
+function TTarget.GetProgramFileName(AnOS : TOS): String;
+begin
+  if AnOS in [dos,win32,os2] then
+    Result:=Name+ExeExt
+  else
+    Result:=Name;
+end;
+
+constructor TTarget.Create(ACollection: TCollection);
+begin
+  inherited Create(ACollection);
+  FInstall:=True;
+end;
+
+destructor TTarget.Destroy;
+begin
+  FreeAndNil(FUnitPath);
+  FreeAndNil(FObjectPath);
+  FreeAndNil(FIncludePath);
+  FreeAndNil(FDependencies);
+  inherited Destroy;
+end;
+
+function TTarget.GetOutputFileName(AOs: TOS): String;
+begin
+  Result:=Name;
+  if TargetType in UnitTargets then
+    Result:=Result+UnitExt
+  else if AOs in [Win32,dos,OS2] then
+    Result:=Result+ExeExt
+end;
+
+function TTarget.NeedsCompile(ATargetDir : String;AOS : TOS): Boolean;
+
+Var
+  I : Integer;
+  T : TTarget;
+  OD,SD,SFN,OFN : String;
+
+begin
+  Result:=False;
+  OD:=ATargetDir;
+  If (OD<>'') then
+    OD:=IncludeTrailingPathDelimiter(ATargetDir);
+  OFN:=OD+GetOutPutFileName(AOS);
+  SD:=Directory;
+  If (SD<>'') then
+      SD:=IncludeTrailingPathDelimiter(SD);
+  Result:=Not FileExists(OFN);
+  // Check dependencies
+  If not Result then
+    If HasDependencies then
+      begin
+      ResolveDependencies(FDependencies,Collection as TTargets);
+      I:=0;
+      While (Not Result) and (I<FDependencies.Count) do
+        begin
+        T:=TTarget(FDependencies.Objects[i]);
+        If (T<>Nil) then
+          Result:=T.NeedsCompile(ATargetDir,AOS)
+        else // if it is a filename, check dates.
+          if FileExists(FDependencies[i]) then
+            Result:=(FileAge(OFN)<FileAge(FDependencies[i]))
+          else if FileExists(SD+FDependencies[i]) then
+            Result:=(FileAge(OFN)<FileAge(SD+FDependencies[i]));
+        Inc(I)
+        end;
+      end;
+  If not Result then
+    begin
+    SFN:=SD+SourceFileName;
+    If (ExtractFileExt(SFN)='') then
+      if FileExists(SFN+'.pp') then
+        SFN:=SFN+'.pp'
+      else
+        SFN:=SFN+'.pas';
+    // Writeln('Checking : ',OFN,' against ',SFN);
+    Result:=(FileAge(OFN)<FileAge(SFN));
+    // here we should check file timestamps.
+    end;
+end;
+
+procedure TTarget.SetName(const AValue: String);
+
+Var
+  D,N,E : String;
+
+begin
+  N:=FixPath(AValue);
+  D:=ExtractFilePath(N);
+  E:=ExtractFileExt(N);
+  N:=ExtractFileName(N);
+  If (E<>'') then
+    N:=Copy(N,1,Length(N)-Length(E));
+  inherited SetName(N);
+  FExtension:=E;
+  FDirectory:=D;
+end;
+
+procedure TTarget.GetCleanFiles(List: TStrings; APrefix : String; AnOS : TOS);
+begin
+  If (OS=[]) or (AnOS in OS) then
+    begin
+    List.Add(APrefix+ObjectFileName);
+    If (TargetType in [ttUnit,ttExampleUnit]) then
+      List.Add(APrefix+UnitFileName)
+    else If (TargetType in [ttProgram,ttExampleProgram]) then
+      List.Add(APrefix+GetProgramFileName(AnOS));
+    If ResourceStrings then
+      List.Add(APrefix+RSTFileName);
+    // Maybe add later ?  AddStrings(List,CleanFiles);
+    end;
+end;
+
+procedure TTarget.GetInstallFiles(List: TStrings; APrefix : String; AnOS : TOS);
+begin
+  If (OS=[]) or (AnOS in OS) then
+    begin
+    If Not (TargetType in [ttProgram,ttExampleProgram]) then
+      List.Add(APrefix+ObjectFileName);
+    If (TargetType in [ttUnit,ttExampleUnit]) then
+      List.Add(APrefix+UnitFileName)
+    else If (TargetType in [ttProgram,ttExampleProgram]) then
+      List.Add(APrefix+GetProgramFileName(AnOS));
+    If ResourceStrings then
+      List.Add(APrefix+RSTFileName);
+    // Maybe add later ?  AddStrings(List,InstallFiles);
+    end;
+end;
+
+procedure TTarget.GetArchiveFiles(List: TStrings; APrefix : String; AnOS : TOS);
+begin
+  If (OS=[]) or (AnOS in OS) then
+    begin
+    List.Add(APrefix+SourceFileName);
+    // Maybe add later ?  AddStrings(List,ArchiveFiles);
+    end;
+end;
+
+
+
+Initialization
+  OnGetApplicationName:=@GetFPMakeName;
+  Installer:=TInstaller.Create(Nil);
+  Defaults:=Installer.Defaults;
+
+Finalization
+  FreeAndNil(Installer);
+  FreeAndNil(Defaults);
+end.