{
    Copyright (c) 2000-2002 by Florian Klaempfl

    This file is the "loader" for the Free Pascal compiler

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

 ****************************************************************************}
program fpc;

{$mode objfpc}{$H+}

  uses
     Sysutils;

  const
{$ifdef UNIX}
    exeext='';
{$else UNIX}
  {$ifdef HASAMIGA}
    exeext='';
  {$else}
    {$ifdef NETWARE}
      exeext='.nlm';
    {$else}
      {$ifdef ATARI}
        exeext='.ttp';
      {$else}
        exeext='.exe';
      {$endif ATARI}
    {$endif NETWARE}
  {$endif HASAMIGA}
{$endif UNIX}

Const
{$ifdef darwin}
  { the mach-o format supports "fat" binaries whereby }
  { a single executable contains machine code for     }
  { several architectures -> it is counter-intuitive  }
  { and non-standard to use different binary names    }
  { for cross-compilers vs. native compilers          }
  CrossSuffix = '';
{$else not darwin}
  CrossSuffix = 'ross';
{$endif not darwin}


  procedure error(const s : string);

  begin
     writeln('Error: ',s);
     halt(1);
  end;

  function processortosuffix(processorstr : string ) : String;

  begin
    case processorstr of
      'aarch64': Result := 'a64';
      'arm': Result := 'arm';
      'avr': Result := 'avr';
      'i386': Result := '386';
      'i8086': Result := '8086';
      'jvm': Result := 'jvm';  
      'loongarch64': Result:='loongarch64';
      'm68k': Result := '68k';
      'mips': Result := 'mips';
      'mipsel': Result := 'mipsel';
      'powerpc': Result := 'ppc';
      'powerpc64': Result := 'ppc64';
      'riscv32': Result := 'rv32';
      'riscv64': Result := 'rv64';
      'sparc': Result := 'sparc';
      'sparc64': Result := 'sparc64';
      'x86_64': Result := 'x64';
      'xtensa': Result := 'xtensa';
      'z80': Result := 'z80';
      'wasm32': Result := 'wasm32'
      else
        error('Illegal processor type "'+processorstr+'"');
    end;
  end;

  procedure InitPlatform(out ppcbin,processorname : string);

  begin
    {$ifdef i386}
         ppcbin:='ppc386';
         processorname:='i386';
    {$endif i386}
    {$ifdef m68k}
         ppcbin:='ppc68k';
         processorname:='m68k';
    {$endif m68k}
    {$ifdef powerpc}
         ppcbin:='ppcppc';
         processorname:='powerpc';
    {$endif powerpc}
    {$ifdef powerpc64}
         ppcbin:='ppcppc64';
         processorname:='powerpc64';
    {$endif powerpc64}
    {$ifdef arm}
         ppcbin:='ppcarm';
         processorname:='arm';
    {$endif arm}
    {$ifdef aarch64}
         ppcbin:='ppca64';
         processorname:='aarch64';
    {$endif aarch64}
    {$ifdef sparc}
         ppcbin:='ppcsparc';
         processorname:='sparc';
    {$endif sparc}
    {$ifdef sparc64}
         ppcbin:='ppcsparc64';
         processorname:='sparc64';
    {$endif sparc64}
    {$ifdef x86_64}
         ppcbin:='ppcx64';
         processorname:='x86_64';
    {$endif x86_64}
    {$ifdef mipsel}
         ppcbin:='ppcmipsel';
         processorname:='mipsel';
    {$else : not mipsel}
      {$ifdef mips}
         ppcbin:='ppcmips';
         processorname:='mips';
      {$endif mips}
    {$endif not mipsel}
    {$ifdef riscv32}
         ppcbin:='ppcrv32';
         processorname:='riscv32';
    {$endif riscv32}
    {$ifdef riscv64}
         ppcbin:='ppcrv64';
         processorname:='riscv64';
    {$endif riscv64}
    {$ifdef xtensa}
         ppcbin:='ppcxtensa';
         processorname:='xtensa';
    {$endif xtensa}
    {$ifdef wasm32}
         ppcbin:='ppcwasm32';
         processorname:='wasm32';
    {$endif wasm32}
    {$ifdef loongarch64}
         ppcbin:='ppcloongarch64';
         processorname:='loongarch64';
    {$endif loongarch64}
  end;

  function SplitPath(Const HStr:String):String;
    var
      i : longint;
    begin
      i:=Length(Hstr);
      while (i>0) and not(Hstr[i] in ['\','/']) do
       dec(i);
      SplitPath:=Copy(Hstr,1,i);
    end;


  function FileExists ( Const F : String) : Boolean;
    var
      Info : TSearchRec;
    begin
      FileExists:= findfirst(F,fareadonly+faarchive+fahidden,info)=0;
      findclose(Info);
    end;

  var
    warn : Boolean;
    ShowErrno : Boolean;
    extrapath : ansistring;

  function findexe(var ppcbin:string): boolean;
    var
      path : string;
    begin
      { add .exe extension }
      findexe:=false;
      ppcbin:=ppcbin+exeext;

      if (extrapath<>'') and (extrapath[length(extrapath)]<>DirectorySeparator) then
        extrapath:=extrapath+DirectorySeparator;
      { get path of fpc.exe }
      path:=splitpath(paramstr(0));
      { don't try with an empty extra patch, this might have strange results
        if the current directory contains a compiler
      }
      if (extrapath<>'') and FileExists(extrapath+ppcbin) then
       begin
         ppcbin:=extrapath+ppcbin;
         findexe:=true;
       end
      else if (path<>'') and FileExists(path+ppcbin) then
       begin
         ppcbin:=path+ppcbin;
         findexe:=true;
       end
      else
       begin
         path:=ExeSearch(ppcbin,getenvironmentvariable('PATH'));
         if path<>'' then
          begin
            ppcbin:=path;
            findexe:=true;
          end
       end;
    end;

    function findcompiler(basecompiler,cpusuffix,exesuffix : string) : string;

    begin
      Result:=basecompiler;
      if exesuffix<>'' then
        Result:=Result+'-'+exesuffix;
      if not findexe(Result) then
        begin
          if cpusuffix<>'' Then
            begin
              Result:='ppc'+cpusuffix;
              if exesuffix<>'' then
                result:=result+'-'+exesuffix;
              if not findexe(result) then
                result:='';
            end;
        end;
    end;

    procedure CheckSpecialProcessors(processorstr,processorname,ppcbin,cpusuffix,exesuffix : string);

    begin
      { -PB is a special code that will show the
        default compiler and exit immediately. It's
        main usage is for Makefile }
      if processorstr='B' then
        begin
          { report the full name of the ppcbin }
          writeln(findcompiler(ppcbin,cpusuffix,exesuffix));
          halt(0);
        end;
      { -PP is a special code that will show the
         processor and exit immediately. It's
         main usage is for Makefile }
      if processorstr='P' then
        begin
          { report the processor }
          writeln(processorname);
          halt(0);
        end;
      end;

Function FindConfigFile(const aFile : string; const aCompiler : String) : String;
// Adapted from check_configfile(fn:string; var foundfn:string):boolean;
{
  Order to read configuration file :
  Unix:
   1 - current dir
   2 - ~/.fpc.cfg
   3 - configpath
   4 - /etc
  Windows:
   1 - current dir
   2 - home dir of user or all users
   3 - config path
   4 - next to binary
  Other:
   1 - current dir
   3 - config path
   4 - next to binary
}

var
  {$ifdef unix}sl : rawbytestring;{$endif}
  {$ifdef unix}hs,{$endif} aSearchPath,exepath,configpath : string;

  Procedure AddToPath(aDir : String);

  begin
    if aDir='' then exit;
    if (aSearchPath<>'') then
      aSearchPath:=aSearchPath+PathSeparator;
    aSearchPath:=aSearchPath+IncludeTrailingPathDelimiter(SetDirSeparators(aDir));
  end;

begin
  if FileExists(aFile) then
    Exit(aFile);
  ExePath:=SetDirSeparators(ExtractFilePath(paramstr(0)));
  aSearchPath:='';
  { retrieve configpath }
  configpath:=SetDirSeparators(GetEnvironmentVariable('PPC_CONFIG_PATH'));
{$ifdef Unix}
  hs:=SetDirSeparators(GetEnvironmentVariable('HOME'));
  if (hs<>'') then
    begin
    Result:=IncludeTrailingPathDelimiter(hs)+'.'+aFile;
    if FileExists(Result) then
      exit;
    end;
  if configpath='' then
    begin
    {  
      We need to search relative to compiler binary, not relative to FPC binary.
      Beware of symlinks !
    }
    hs:=aCompiler;
    While FileGetSymLinkTarget(hs,sl) do
      begin
      if copy(sl,1,1)<>'/' then
        hs:=ExpandFileName(ExtractFilePath(hs)+sl)
      else
        hs:=sl;  
      end;
    ExePath:=ExtractFilePath(hs);
    configpath:=ExpandFileName(ExePath+'../etc/');
    end;
{$endif}
  AddToPath(ConfigPath);
{$ifdef WINDOWS}
  AddToPath(GetEnvironmentVariable('USERPROFILE'));
  AddToPath(GetEnvironmentVariable('ALLUSERSPROFILE'));
{$endif WINDOWS}
{$ifdef Unix}
  AddToPath('/etc/');
{$else}
  AddToPath(exepath);
{$endif}
  Result:=FileSearch(aFile,aSearchPath);
end;

Procedure CheckWarn(aOpt : String);

Var
  Len,I : integer;

begin
  Len:=Length(aOpt);
  For I:=1 to Len do
    begin
    if (aOpt[i]='w') then
      Warn:=(I=Len) or (aOpt[i+1]<>'-');
    if (aOpt[i]='q') then
      ShowErrNo:=(I=Len) or (aOpt[i+1]<>'-');
    end;
end;

procedure SetExeSuffix(var ExeSuffix : string; aValue : string);

begin
  if ExeSuffix='' then
    exesuffix :=aValue
  else if Warn then
    begin
    Write('Warning: ');
    if ShowErrNo then
      Write('(99999) ');
    Writeln('Compiler version already set to: ',ExeSuffix);
    end;
end;

Procedure ProcessConfigFile(aFileName : String; var ExeSuffix : String);

  Function Stripline(aLine : String) : string;

  Var
    P : integer;

  begin
    if (aLine<>'') and (aLine[1]=';') then exit;
    Pos('#',aLine); // no ifdef or include.
    if P=0 then
      P:=Length(aLine)+1;
    Result:=Copy(aLine,1,P-1);
  end;

Var
  aFile : Text;
  aLine : String;

begin
  Assign(aFile,aFileName);
  {$push}{$I-}
  filemode:=0;
  Reset(aFile);
  {$pop}
  if ioresult<>0 then
    Error('Cannot open config file: '+aFileName);
  While not EOF(aFile) do
    begin
    ReadLn(aFile,aLine);
    aLine:=StripLine(aLine);
    if aLine='' then
      continue;
    if Copy(aLine,1,2)='-V' then
      SetExeSuffix(ExeSuffix,Copy(aLine,3,Length(aLine)-2));
    end;
  {$i+}
  Close(aFile);
end;

  var
     s,CfgFile: ansistring;
     CPUSuffix, ExeSuffix, SourceCPU, ppcbin, TargetName, TargetCPU: string;
     PPCCommandLine: array of ansistring;
     PPCCommandLineLen: longint;
     i : longint;
     errorvalue     : Longint;

     Procedure AddToCommandLine(S : String);

     begin
       PPCCommandLine [PPCCommandLineLen] := S;
       Inc(PPCCommandLineLen);
     end;

begin
  ppccommandline := [];
  setlength(ppccommandline, paramcount);
  ppccommandlinelen := 0;
  cpusuffix := '';        // if not empty, signals attempt at cross
  // compiler.
  extrapath := '';
  initplatform(ppcbin, SourceCPU);
  exesuffix := '';                      { Default is just the name }
  if ParamCount = 0 then
  begin
    SetLength(PPCCommandLine, 1);
    AddToCommandLine('-?F'+ParamStr(0));
  end
  else
    for i := 1 to paramcount do
    begin
      s := ParamStr(i);
      if pos('-t', s) = 1 then
      begin
        targetname := copy(s, 3, length(s)-2);
        AddToCommandLine(S);
      end
      else if pos('-V', s) = 1 then
        SetExeSuffix(ExeSuffix,copy(s, 3, length(s)-2))
      else
      begin
        if pos('-P', s) = 1 then
           begin
             TargetCPU:=copy(s,3,length(s)-2);
             CheckSpecialProcessors(TargetCPU,SourceCPU,ppcbin,cpusuffix,exesuffix);
             if TargetCPU <> SourceCPU then
               begin
                 cpusuffix:=processortosuffix(TargetCPU);
                 ppcbin:='ppc'+crosssuffix+cpusuffix;
               end;
           end
        else if pos('-Xp',s)=1 then
          extrapath:=copy(s,4,length(s)-3)
        else
        begin
          if pos('-h', s) = 1 then
            AddToCommandLine('-hF'+ParamStr(0))
          else if pos('-?', s) = 1 then
            AddToCommandLine('-?F'+ParamStr(0))
          else
            begin
            AddToCommandLine(S);
            if pos('-v', s) = 1 then
              CheckWarn(Copy(S,3,length(S)-2));
            end;
        end;
      end;
    end;
     ppcbin := findcompiler(ppcbin, cpusuffix, exesuffix);
     if (TargetName<>'') then
       begin
       S:='fpc-'+lowercase(TargetName)+'.cfg';
       CfgFile:=FindConfigFile(s,ppcbin);
       if CfgFile='' then
         Error('Cannot find subtarget config file: '+s);
       ProcessConfigFile(CfgFile,ExeSuffix);
       end;
     SetLength(ppccommandline, ppccommandlinelen);

     { call ppcXXX }
     try
       errorvalue:=ExecuteProcess(ppcbin,ppccommandline);
     except
       on e : exception do
         error(ppcbin+' can''t be executed, error message: '+e.message);
     end;
     if (errorvalue<>0) and
        (paramcount<>0) then
       error(ppcbin+' returned an error exitcode');
     halt(errorvalue);
  end.