{ Copyright (c) 1998-2002 by Peter Vreman This unit handles the linker and binder calls for programs and libraries 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. **************************************************************************** } unit link; {$i fpcdefs.inc} interface uses cclasses, systems, fmodule, globtype; Type TLinkerInfo=record ExeCmd, DllCmd : array[1..3] of string; ResName : string[100]; ScriptName : string[100]; ExtraOptions : string; DynamicLinker : string[100]; end; TLinker = class(TAbstractLinker) public ObjectFiles, SharedLibFiles, StaticLibFiles : TStringList; Constructor Create;virtual; Destructor Destroy;override; procedure AddModuleFiles(hp:tmodule); Procedure AddObject(const S,unitpath : String;isunit:boolean); Procedure AddStaticLibrary(const S : String); Procedure AddSharedLibrary(S : String); Procedure AddStaticCLibrary(const S : String); Procedure AddSharedCLibrary(S : String); Function MakeExecutable:boolean;virtual; Function MakeSharedLibrary:boolean;virtual; Function MakeStaticLibrary:boolean;virtual; end; TExternalLinker = class(TLinker) public Info : TLinkerInfo; Constructor Create;override; Destructor Destroy;override; Function FindUtil(const s:string):String; Function DoExec(const command:string; para:TCmdStr;showinfo,useshell:boolean):boolean; procedure SetDefaultInfo;virtual; Function MakeStaticLibrary:boolean;override; end; TInternalLinker = class(TLinker) private procedure readobj(const fn:string); public Constructor Create;override; Destructor Destroy;override; Function MakeExecutable:boolean;override; end; var Linker : TLinker; function FindObjectFile(s : string;const unitpath:string;isunit:boolean) : string; function FindLibraryFile(s:string;const prefix,ext:string;var foundfile : string) : boolean; procedure InitLinker; procedure DoneLinker; Implementation uses {$IFDEF USE_SYSUTILS} SysUtils, {$ELSE USE_SYSUTILS} dos, {$ENDIF USE_SYSUTILS} cutils, script,globals,verbose,ppu, aasmbase,aasmtai,aasmcpu, ogbase,ogmap; type TLinkerClass = class of Tlinker; {***************************************************************************** Helpers *****************************************************************************} { searches an object file } function FindObjectFile(s:string;const unitpath:string;isunit:boolean) : string; var found : boolean; foundfile : string; begin findobjectfile:=''; if s='' then exit; {When linking on target, the units has not been assembled yet, so there is no object files to look for at the host. Look for the corresponding assembler file instead, because it will be assembled to object file on the target.} if isunit and (cs_link_on_target in aktglobalswitches) then s:= ForceExtension(s,target_info.asmext); { when it does not belong to the unit then check if the specified file exists without searching any paths } if not isunit then begin if FileExists(FixFileName(s)) then begin foundfile:=ScriptFixFileName(s); found:=true; end; end; if pos('.',s)=0 then s:=s+target_info.objext; { find object file 1. output unit path 2. output exe path 3. specified unit path (if specified) 4. cwd 5. unit search path 6. local object path 7. global object path 8. exepath (not when linking on target) } found:=false; if isunit and (OutputUnitDir<>'') then found:=FindFile(s,OutPutUnitDir,foundfile) else if OutputExeDir<>'' then found:=FindFile(s,OutPutExeDir,foundfile); if (not found) and (unitpath<>'') then found:=FindFile(s,unitpath,foundfile); if (not found) then found:=FindFile(s, CurDirRelPath(source_info), foundfile); if (not found) then found:=UnitSearchPath.FindFile(s,foundfile); if (not found) then found:=current_module.localobjectsearchpath.FindFile(s,foundfile); if (not found) then found:=objectsearchpath.FindFile(s,foundfile); if not(cs_link_on_target in aktglobalswitches) and (not found) then found:=FindFile(s,exepath,foundfile); if not(cs_link_extern in aktglobalswitches) and (not found) then Message1(exec_w_objfile_not_found,s); {Restore file extension} if isunit and (cs_link_on_target in aktglobalswitches) then foundfile:= ForceExtension(foundfile,target_info.objext); findobjectfile:=ScriptFixFileName(foundfile); end; { searches an library file } function FindLibraryFile(s:string;const prefix,ext:string;var foundfile : string) : boolean; var found : boolean; paths : string; begin findlibraryfile:=false; foundfile:=s; if s='' then exit; { split path from filename } paths:=SplitPath(s); s:=SplitFileName(s); { add prefix 'lib' } if (prefix<>'') and (Copy(s,1,length(prefix))<>prefix) then s:=prefix+s; { add extension } if (ext<>'') and (Copy(s,length(s)-length(ext)+1,length(ext))<>ext) then s:=s+ext; { readd the split path } s:=paths+s; if FileExists(s) then begin foundfile:=ScriptFixFileName(s); FindLibraryFile:=true; exit; end; { find libary 1. cwd 2. local libary dir 3. global libary dir 4. exe path of the compiler (not when linking on target) } found:=FindFile(s, CurDirRelPath(source_info), foundfile); if (not found) and (current_module.outputpath^<>'') then found:=FindFile(s,current_module.outputpath^,foundfile); if (not found) then found:=current_module.locallibrarysearchpath.FindFile(s,foundfile); if (not found) then found:=librarysearchpath.FindFile(s,foundfile); if not(cs_link_on_target in aktglobalswitches) and (not found) then found:=FindFile(s,exepath,foundfile); foundfile:=ScriptFixFileName(foundfile); findlibraryfile:=found; end; {***************************************************************************** TLINKER *****************************************************************************} Constructor TLinker.Create; begin Inherited Create; ObjectFiles:=TStringList.Create_no_double; SharedLibFiles:=TStringList.Create_no_double; StaticLibFiles:=TStringList.Create_no_double; end; Destructor TLinker.Destroy; begin ObjectFiles.Free; SharedLibFiles.Free; StaticLibFiles.Free; end; procedure TLinker.AddModuleFiles(hp:tmodule); var mask : longint; begin with hp do begin { link unit files } if (flags and uf_no_link)=0 then begin { create mask which unit files need linking } mask:=link_allways; { static linking ? } if (cs_link_static in aktglobalswitches) then begin if (flags and uf_static_linked)=0 then begin { if smart not avail then try static linking } if (flags and uf_smart_linked)<>0 then begin Message1(exec_t_unit_not_static_linkable_switch_to_smart,modulename^); mask:=mask or link_smart; end else Message1(exec_e_unit_not_smart_or_static_linkable,modulename^); end else mask:=mask or link_static; end; { smart linking ? } if (cs_link_smart in aktglobalswitches) then begin if (flags and uf_smart_linked)=0 then begin { if smart not avail then try static linking } if (flags and uf_static_linked)<>0 then begin Message1(exec_t_unit_not_smart_linkable_switch_to_static,modulename^); mask:=mask or link_static; end else Message1(exec_e_unit_not_smart_or_static_linkable,modulename^); end else mask:=mask or link_smart; end; { shared linking } if (cs_link_shared in aktglobalswitches) then begin if (flags and uf_shared_linked)=0 then begin { if shared not avail then try static linking } if (flags and uf_static_linked)<>0 then begin Message1(exec_t_unit_not_shared_linkable_switch_to_static,modulename^); mask:=mask or link_static; end else Message1(exec_e_unit_not_shared_or_static_linkable,modulename^); end else mask:=mask or link_shared; end; { unit files } while not linkunitofiles.empty do begin AddObject(linkunitofiles.getusemask(mask),path^,true); end; while not linkunitstaticlibs.empty do AddStaticLibrary(linkunitstaticlibs.getusemask(mask)); while not linkunitsharedlibs.empty do AddSharedLibrary(linkunitsharedlibs.getusemask(mask)); end; { Other needed .o and libs, specified using $L,$LINKLIB,external } mask:=link_allways; while not linkotherofiles.empty do AddObject(linkotherofiles.Getusemask(mask),path^,false); while not linkotherstaticlibs.empty do AddStaticCLibrary(linkotherstaticlibs.Getusemask(mask)); while not linkothersharedlibs.empty do AddSharedCLibrary(linkothersharedlibs.Getusemask(mask)); end; end; Procedure TLinker.AddObject(const S,unitpath : String;isunit:boolean); begin ObjectFiles.Concat(FindObjectFile(s,unitpath,isunit)); end; Procedure TLinker.AddSharedLibrary(S:String); begin if s='' then exit; { remove prefix 'lib' } if Copy(s,1,length(target_info.sharedlibprefix))=target_info.sharedlibprefix then Delete(s,1,length(target_info.sharedlibprefix)); { remove extension if any } if Copy(s,length(s)-length(target_info.sharedlibext)+1,length(target_info.sharedlibext))=target_info.sharedlibext then Delete(s,length(s)-length(target_info.sharedlibext)+1,length(target_info.sharedlibext)+1); { ready to be added } SharedLibFiles.Concat(S); end; Procedure TLinker.AddStaticLibrary(const S:String); var ns : string; found : boolean; begin if s='' then exit; found:=FindLibraryFile(s,target_info.staticlibprefix,target_info.staticlibext,ns); if not(cs_link_extern in aktglobalswitches) and (not found) then Message1(exec_w_libfile_not_found,s); StaticLibFiles.Concat(ns); end; Procedure TLinker.AddSharedCLibrary(S:String); begin if s='' then exit; { remove prefix 'lib' } if Copy(s,1,length(target_info.sharedclibprefix))=target_info.sharedclibprefix then Delete(s,1,length(target_info.sharedclibprefix)); { remove extension if any } if Copy(s,length(s)-length(target_info.sharedclibext)+1,length(target_info.sharedclibext))=target_info.sharedclibext then Delete(s,length(s)-length(target_info.sharedclibext)+1,length(target_info.sharedclibext)+1); { ready to be added } SharedLibFiles.Concat(S); end; Procedure TLinker.AddStaticCLibrary(const S:String); var ns : string; found : boolean; begin if s='' then exit; found:=FindLibraryFile(s,target_info.staticclibprefix,target_info.staticclibext,ns); if not(cs_link_extern in aktglobalswitches) and (not found) then Message1(exec_w_libfile_not_found,s); StaticLibFiles.Concat(ns); end; function TLinker.MakeExecutable:boolean; begin MakeExecutable:=false; Message(exec_e_exe_not_supported); end; Function TLinker.MakeSharedLibrary:boolean; begin MakeSharedLibrary:=false; Message(exec_e_dll_not_supported); end; Function TLinker.MakeStaticLibrary:boolean; begin MakeStaticLibrary:=false; Message(exec_e_dll_not_supported); end; {***************************************************************************** TEXTERNALLINKER *****************************************************************************} Constructor TExternalLinker.Create; begin inherited Create; { set generic defaults } FillChar(Info,sizeof(Info),0); if cs_link_on_target in aktglobalswitches then begin Info.ResName:=outputexedir+inputfile+'_link.res'; Info.ScriptName:=outputexedir+inputfile+'_script.res'; end else begin Info.ResName:='link.res'; Info.ScriptName:='script.res'; end; { set the linker specific defaults } SetDefaultInfo; { Allow Parameter overrides for linker info } with Info do begin if ParaLinkOptions<>'' then ExtraOptions:=ParaLinkOptions; if ParaDynamicLinker<>'' then DynamicLinker:=ParaDynamicLinker; end; end; Destructor TExternalLinker.Destroy; begin inherited destroy; end; Procedure TExternalLinker.SetDefaultInfo; begin end; Function TExternalLinker.FindUtil(const s:string):string; var Found : boolean; FoundBin : string; UtilExe : string; begin if cs_link_on_target in aktglobalswitches then begin { If linking on target, don't add any path PM } FindUtil:=AddExtension(s,target_info.exeext); exit; end; UtilExe:=AddExtension(s,source_info.exeext); FoundBin:=''; Found:=false; if utilsdirectory<>'' then Found:=FindFile(utilexe,utilsdirectory,Foundbin); if (not Found) then Found:=FindExe(utilexe,Foundbin); if (not Found) and not(cs_link_extern in aktglobalswitches) then begin Message1(exec_e_util_not_found,utilexe); aktglobalswitches:=aktglobalswitches+[cs_link_extern]; end; if (FoundBin<>'') then Message1(exec_t_using_util,FoundBin); FindUtil:=FoundBin; end; Function TExternalLinker.DoExec(const command:string; para:TCmdStr;showinfo,useshell:boolean):boolean; var exitcode: longint; begin DoExec:=true; if not(cs_link_extern in aktglobalswitches) then begin if useshell then exitcode := shell(maybequoted(command)+' '+para) else {$IFDEF USE_SYSUTILS} try if ExecuteProcess(command,para) <> 0 then begin Message(exec_e_error_while_linking); aktglobalswitches:=aktglobalswitches+[cs_link_extern]; DoExec:=false; end; except on E:EOSError do begin Message(exec_e_cant_call_linker); aktglobalswitches:=aktglobalswitches+[cs_link_extern]; DoExec:=false; end; end end; {$ELSE USE_SYSUTILS} begin swapvectors; exec(command,para); swapvectors; exitcode := dosexitcode; end; if (doserror<>0) then begin Message(exec_e_cant_call_linker); aktglobalswitches:=aktglobalswitches+[cs_link_extern]; DoExec:=false; end else if (exitcode<>0) then begin Message(exec_e_error_while_linking); aktglobalswitches:=aktglobalswitches+[cs_link_extern]; DoExec:=false; end; end; {$ENDIF USE_SYSUTILS} { Update asmres when externmode is set } if cs_link_extern in aktglobalswitches then begin if showinfo then begin if DLLsource then AsmRes.AddLinkCommand(Command,Para,current_module.sharedlibfilename^) else AsmRes.AddLinkCommand(Command,Para,current_module.exefilename^); end else AsmRes.AddLinkCommand(Command,Para,''); end; end; Function TExternalLinker.MakeStaticLibrary:boolean; var smartpath, cmdstr : TCmdStr; binstr : string; success : boolean; begin MakeStaticLibrary:=false; { remove the library, to be sure that it is rewritten } RemoveFile(current_module.staticlibfilename^); { Call AR } smartpath:=current_module.outputpath^+FixPath(lower(current_module.modulename^)+target_info.smartext,false); SplitBinCmd(target_ar.arcmd,binstr,cmdstr); Replace(cmdstr,'$LIB',maybequoted(current_module.staticlibfilename^)); Replace(cmdstr,'$FILES',FixFileName(smartpath+current_module.asmprefix^+'*'+target_info.objext)); success:=DoExec(FindUtil(binstr),cmdstr,false,true); { Clean up } if not(cs_asm_leave in aktglobalswitches) then if not(cs_link_extern in aktglobalswitches) then begin while not SmartLinkOFiles.Empty do RemoveFile(SmartLinkOFiles.GetFirst); RemoveDir(smartpath); end else begin AsmRes.AddDeleteCommand(FixFileName(smartpath+current_module.asmprefix^+'*'+target_info.objext)); AsmRes.Add('rmdir '+smartpath); end; MakeStaticLibrary:=success; end; {***************************************************************************** TINTERNALLINKER *****************************************************************************} Constructor TInternalLinker.Create; begin inherited Create; exemap:=nil; exeoutput:=nil; end; Destructor TInternalLinker.Destroy; begin exeoutput.free; exeoutput:=nil; inherited destroy; end; procedure TInternalLinker.readobj(const fn:string); var objdata : TAsmObjectData; objinput : tobjectinput; begin Comment(V_Info,'Reading object '+fn); objinput:=exeoutput.newobjectinput; objdata:=objinput.newobjectdata(fn); if objinput.readobjectfile(fn,objdata) then exeoutput.addobjdata(objdata); { release input object } objinput.free; end; function TInternalLinker.MakeExecutable:boolean; var s : string; begin MakeExecutable:=false; { no support yet for libraries } if (not StaticLibFiles.Empty) or (not SharedLibFiles.Empty) then internalerror(123456789); if (cs_link_map in aktglobalswitches) then exemap:=texemap.create(current_module.mapfilename^); { read objects } readobj(FindObjectFile('prt0','',false)); while not ObjectFiles.Empty do begin s:=ObjectFiles.GetFirst; if s<>'' then readobj(s); end; { generate executable } exeoutput.GenerateExecutable(current_module.exefilename^); { close map } if assigned(exemap) then begin exemap.free; exemap:=nil; end; MakeExecutable:=true; end; {***************************************************************************** Init/Done *****************************************************************************} procedure InitLinker; var lk : TlinkerClass; begin if (cs_link_internal in aktglobalswitches) and assigned(target_info.link) then begin lk:=TLinkerClass(target_info.link); linker:=lk.Create; end else if assigned(target_info.linkextern) then begin lk:=TlinkerClass(target_info.linkextern); linker:=lk.Create; end else begin linker:=Tlinker.Create; end; end; procedure DoneLinker; begin if assigned(linker) then Linker.Free; end; {***************************************************************************** Initialize *****************************************************************************} const ar_gnu_ar_info : tarinfo = ( id : ar_gnu_ar; arcmd : 'ar rs $LIB $FILES' ); initialization RegisterAr(ar_gnu_ar_info); end.