Files
Nim/nim/extccomp.pas
2010-02-14 00:29:35 +01:00

677 lines
20 KiB
ObjectPascal

//
//
// The Nimrod Compiler
// (c) Copyright 2009 Andreas Rumpf
//
// See the file "copying.txt", included in this
// distribution, for details about the copyright.
//
unit extccomp;
// module for calling the different external C compilers
interface
{$include 'config.inc'}
uses
nsystem, charsets, lists, ropes, nos, strutils, osproc, platform, condsyms,
options, msgs;
// some things are read in from the configuration file
type
TSystemCC = (ccNone, ccGcc, ccLLVM_Gcc, ccLcc, ccBcc, ccDmc, ccWcc, ccVcc,
ccTcc, ccPcc, ccUcc, ccIcc, ccGpp);
TInfoCCProp = ( // properties of the C compiler:
hasSwitchRange, // CC allows ranges in switch statements (GNU C extension)
hasComputedGoto, // CC has computed goto (GNU C extension)
hasCpp, // CC is/contains a C++ compiler
hasAssume // CC has __assume (Visual C extension)
);
TInfoCCProps = set of TInfoCCProp;
TInfoCC = record{@tuple}
name: string; // the short name of the compiler
objExt: string; // the compiler's object file extenstion
optSpeed: string; // the options for optimization for speed
optSize: string; // the options for optimization for size
compilerExe: string; // the compiler's executable
compileTmpl: string; // the compile command template
buildGui: string; // command to build a GUI application
buildDll: string; // command to build a shared library
linkerExe: string; // the linker's executable
linkTmpl: string; // command to link files to produce an executable
includeCmd: string; // command to add an include directory path
debug: string; // flags for debug build
pic: string; // command for position independent code
// used on some platforms
asmStmtFrmt: string; // format of ASM statement
props: TInfoCCProps; // properties of the C compiler
end;
const
CC: array [succ(low(TSystemCC))..high(TSystemCC)] of TInfoCC = (
(
name: 'gcc';
objExt: 'o'+'';
optSpeed: ' -O3 -ffast-math ';
optSize: ' -Os -ffast-math ';
compilerExe: 'gcc';
compileTmpl: '-c $options $include -o $objfile $file';
buildGui: ' -mwindows';
buildDll: ' -mdll';
linkerExe: 'gcc';
linkTmpl: '$options $buildgui $builddll -o $exefile $objfiles';
includeCmd: ' -I';
debug: '';
pic: '-fPIC';
asmStmtFrmt: 'asm($1);$n';
props: {@set}[hasSwitchRange, hasComputedGoto, hasCpp];
),
(
name: 'llvm_gcc';
objExt: 'o'+'';
optSpeed: ' -O3 -ffast-math ';
optSize: ' -Os -ffast-math ';
compilerExe: 'llvm-gcc';
compileTmpl: '-c $options $include -o $objfile $file';
buildGui: ' -mwindows';
buildDll: ' -mdll';
linkerExe: 'llvm-gcc';
linkTmpl: '$options $buildgui $builddll -o $exefile $objfiles';
includeCmd: ' -I';
debug: '';
pic: '-fPIC';
asmStmtFrmt: 'asm($1);$n';
props: {@set}[hasSwitchRange, hasComputedGoto, hasCpp];
),
(
name: 'lcc';
objExt: 'obj';
optSpeed: ' -O -p6 ';
optSize: ' -O -p6 ';
compilerExe: 'lcc';
compileTmpl: '$options $include -Fo$objfile $file';
buildGui: ' -subsystem windows';
buildDll: ' -dll';
linkerExe: 'lcclnk';
linkTmpl: '$options $buildgui $builddll -O $exefile $objfiles';
includeCmd: ' -I';
debug: ' -g5 ';
pic: '';
asmStmtFrmt: '_asm{$n$1$n}$n';
props: {@set}[];
),
(
name: 'bcc';
objExt: 'obj';
optSpeed: ' -O2 -6 ';
optSize: ' -O1 -6 ';
compilerExe: 'bcc32';
compileTmpl: '-c $options $include -o$objfile $file';
buildGui: ' -tW';
buildDll: ' -tWD';
linkerExe: 'bcc32';
linkTmpl: '$options $buildgui $builddll -e$exefile $objfiles';
includeCmd: ' -I';
debug: '';
pic: '';
asmStmtFrmt: '__asm{$n$1$n}$n';
props: {@set}[hasCpp];
),
(
name: 'dmc';
objExt: 'obj';
optSpeed: ' -ff -o -6 ';
optSize: ' -ff -o -6 ';
compilerExe: 'dmc';
compileTmpl: '-c $options $include -o$objfile $file';
buildGui: ' -L/exet:nt/su:windows';
buildDll: ' -WD';
linkerExe: 'dmc';
linkTmpl: '$options $buildgui $builddll -o$exefile $objfiles';
includeCmd: ' -I';
debug: ' -g ';
pic: '';
asmStmtFrmt: '__asm{$n$1$n}$n';
props: {@set}[hasCpp];
),
(
name: 'wcc';
objExt: 'obj';
optSpeed: ' -ox -on -6 -d0 -fp6 -zW ';
optSize: '';
compilerExe: 'wcl386';
compileTmpl: '-c $options $include -fo=$objfile $file';
buildGui: ' -bw';
buildDll: ' -bd';
linkerExe: 'wcl386';
linkTmpl: '$options $buildgui $builddll -fe=$exefile $objfiles ';
includeCmd: ' -i=';
debug: ' -d2 ';
pic: '';
asmStmtFrmt: '__asm{$n$1$n}$n';
props: {@set}[hasCpp];
),
(
name: 'vcc';
objExt: 'obj';
optSpeed: ' /Ogityb2 /G7 /arch:SSE2 ';
optSize: ' /O1 /G7 ';
compilerExe: 'cl';
compileTmpl: '/c $options $include /Fo$objfile $file';
buildGui: ' /link /SUBSYSTEM:WINDOWS ';
buildDll: ' /LD';
linkerExe: 'cl';
linkTmpl: '$options $builddll /Fe$exefile $objfiles $buildgui';
includeCmd: ' /I';
debug: ' /GZ /Zi ';
pic: '';
asmStmtFrmt: '__asm{$n$1$n}$n';
props: {@set}[hasCpp, hasAssume];
),
(
name: 'tcc';
objExt: 'o'+'';
optSpeed: '';
optSize: '';
compilerExe: 'tcc';
compileTmpl: '-c $options $include -o $objfile $file';
buildGui: 'UNAVAILABLE!';
buildDll: ' -shared';
linkerExe: 'tcc';
linkTmpl: '-o $exefile $options $buildgui $builddll $objfiles';
includeCmd: ' -I';
debug: ' -g ';
pic: '';
asmStmtFrmt: '__asm{$n$1$n}$n';
props: {@set}[hasSwitchRange, hasComputedGoto];
),
(
name: 'pcc'; // Pelles C
objExt: 'obj';
optSpeed: ' -Ox ';
optSize: ' -Os ';
compilerExe: 'cc';
compileTmpl: '-c $options $include -Fo$objfile $file';
buildGui: ' -SUBSYSTEM:WINDOWS';
buildDll: ' -DLL';
linkerExe: 'cc';
linkTmpl: '$options $buildgui $builddll -OUT:$exefile $objfiles';
includeCmd: ' -I';
debug: ' -Zi ';
pic: '';
asmStmtFrmt: '__asm{$n$1$n}$n';
props: {@set}[];
),
(
name: 'ucc';
objExt: 'o'+'';
optSpeed: ' -O3 ';
optSize: ' -O1 ';
compilerExe: 'cc';
compileTmpl: '-c $options $include -o $objfile $file';
buildGui: '';
buildDll: ' -shared ';
linkerExe: 'cc';
linkTmpl: '-o $exefile $options $buildgui $builddll $objfiles';
includeCmd: ' -I';
debug: '';
pic: '';
asmStmtFrmt: '__asm{$n$1$n}$n';
props: {@set}[];
), (
name: 'icc';
objExt: 'o'+'';
optSpeed: ' -O3 ';
optSize: ' -Os ';
compilerExe: 'icc';
compileTmpl: '-c $options $include -o $objfile $file';
buildGui: ' -mwindows';
buildDll: ' -mdll';
linkerExe: 'icc';
linkTmpl: '$options $buildgui $builddll -o $exefile $objfiles';
includeCmd: ' -I';
debug: '';
pic: '-fPIC';
asmStmtFrmt: 'asm($1);$n';
props: {@set}[hasSwitchRange, hasComputedGoto, hasCpp];
), (
name: 'gpp';
objExt: 'o'+'';
optSpeed: ' -O3 -ffast-math ';
optSize: ' -Os -ffast-math ';
compilerExe: 'g++';
compileTmpl: '-c $options $include -o $objfile $file';
buildGui: ' -mwindows';
buildDll: ' -mdll';
linkerExe: 'g++';
linkTmpl: '$options $buildgui $builddll -o $exefile $objfiles';
includeCmd: ' -I';
debug: ' -g ';
pic: '-fPIC';
asmStmtFrmt: 'asm($1);$n';
props: {@set}[hasSwitchRange, hasComputedGoto, hasCpp];
)
);
var
ccompiler: TSystemCC = ccGcc; // the used compiler
const
hExt = 'h'+'';
var
cExt: string = 'c'+''; // extension of generated C/C++ files
// (can be changed to .cpp later)
function completeCFilePath(const cfile: string;
createSubDir: Boolean = true): string;
function getCompileCFileCmd(const cfilename: string;
isExternal: bool = false): string;
procedure addFileToCompile(const filename: string);
procedure addExternalFileToCompile(const filename: string);
procedure addFileToLink(const filename: string);
procedure addCompileOption(const option: string);
procedure addLinkOption(const option: string);
function toObjFile(const filenameWithoutExt: string): string;
procedure CallCCompiler(const projectFile: string);
procedure execExternalProgram(const cmd: string);
function NameToCC(const name: string): TSystemCC;
procedure initVars;
procedure setCC(const ccname: string);
procedure writeMapping(gSymbolMapping: PRope);
implementation
var
toLink, toCompile, externalToCompile: TLinkedList;
linkOptions: string = '';
compileOptions: string = '';
ccompilerpath: string = '';
procedure setCC(const ccname: string);
var
i: TSystemCC;
begin
ccompiler := nameToCC(ccname);
if ccompiler = ccNone then rawMessage(errUnknownCcompiler, ccname);
compileOptions := getConfigVar(CC[ccompiler].name + '.options.always');
linkOptions := getConfigVar(CC[ccompiler].name + '.options.linker');
ccompilerpath := getConfigVar(CC[ccompiler].name + '.path');
for i := low(CC) to high(CC) do undefSymbol(CC[i].name);
defineSymbol(CC[ccompiler].name);
end;
procedure initVars;
var
i: TSystemCC;
begin
// we need to define the symbol here, because ``CC`` may have never been set!
for i := low(CC) to high(CC) do undefSymbol(CC[i].name);
defineSymbol(CC[ccompiler].name);
if gCmd = cmdCompileToCpp then
cExt := '.cpp';
addCompileOption(getConfigVar(CC[ccompiler].name + '.options.always'));
addLinkOption(getConfigVar(CC[ccompiler].name + '.options.linker'));
if length(ccompilerPath) = 0 then
ccompilerpath := getConfigVar(CC[ccompiler].name + '.path');
end;
function completeCFilePath(const cfile: string;
createSubDir: Boolean = true): string;
begin
result := completeGeneratedFilePath(cfile, createSubDir);
end;
function NameToCC(const name: string): TSystemCC;
var
i: TSystemCC;
begin
for i := succ(ccNone) to high(TSystemCC) do
if cmpIgnoreStyle(name, CC[i].name) = 0 then begin
result := i; exit
end;
result := ccNone
end;
procedure addOpt(var dest: string; const src: string);
begin
if (length(dest) = 0) or (dest[length(dest)-1+strStart] <> ' ') then
add(dest, ' '+'');
add(dest, src);
end;
procedure addCompileOption(const option: string);
begin
if strutils.find(compileOptions, option, strStart) < strStart then
addOpt(compileOptions, option)
end;
procedure addLinkOption(const option: string);
begin
if find(linkOptions, option, strStart) < strStart then
addOpt(linkOptions, option)
end;
function toObjFile(const filenameWithoutExt: string): string;
begin
result := changeFileExt(filenameWithoutExt, cc[ccompiler].objExt)
end;
procedure addFileToCompile(const filename: string);
begin
appendStr(toCompile, filename);
end;
procedure addExternalFileToCompile(const filename: string);
begin
appendStr(externalToCompile, filename);
end;
procedure addFileToLink(const filename: string);
begin
prependStr(toLink, filename); // BUGFIX
//appendStr(toLink, filename);
end;
procedure execExternalProgram(const cmd: string);
begin
if (optListCmd in gGlobalOptions) or (gVerbosity > 0) then
MessageOut(cmd);
if execCmd(cmd) <> 0 then
rawMessage(errExecutionOfProgramFailed);
end;
procedure generateScript(const projectFile: string; script: PRope);
var
path, scriptname, name, ext: string;
begin
splitPath(projectFile, path, scriptname);
SplitFilename(scriptname, name, ext);
name := addFileExt('compile_' + name, platform.os[targetOS].scriptExt);
WriteRope(script, joinPath(path, name));
end;
function getOptSpeed(c: TSystemCC): string;
begin
result := getConfigVar(cc[c].name + '.options.speed');
if result = '' then
result := cc[c].optSpeed // use default settings from this file
end;
function getDebug(c: TSystemCC): string;
begin
result := getConfigVar(cc[c].name + '.options.debug');
if result = '' then
result := cc[c].debug // use default settings from this file
end;
function getOptSize(c: TSystemCC): string;
begin
result := getConfigVar(cc[c].name + '.options.size');
if result = '' then
result := cc[c].optSize // use default settings from this file
end;
const
specialFileA = 42;
specialFileB = 42;
var
fileCounter: int;
function getCompileCFileCmd(const cfilename: string;
isExternal: bool = false): string;
var
cfile, objfile, options, includeCmd, compilePattern, key, trunk, exe: string;
c: TSystemCC; // an alias to ccompiler
begin
c := ccompiler;
options := compileOptions;
trunk := splitFile(cfilename).name;
if optCDebug in gGlobalOptions then begin
key := trunk + '.debug';
if existsConfigVar(key) then
addOpt(options, getConfigVar(key))
else
addOpt(options, getDebug(c))
end;
if (optOptimizeSpeed in gOptions) then begin
//if ((fileCounter >= specialFileA) and (fileCounter <= specialFileB)) then
key := trunk + '.speed';
if existsConfigVar(key) then
addOpt(options, getConfigVar(key))
else
addOpt(options, getOptSpeed(c))
end
else if optOptimizeSize in gOptions then begin
key := trunk + '.size';
if existsConfigVar(key) then
addOpt(options, getConfigVar(key))
else
addOpt(options, getOptSize(c))
end;
key := trunk + '.always';
if existsConfigVar(key) then
addOpt(options, getConfigVar(key));
exe := cc[c].compilerExe;
key := cc[c].name + '.exe';
if existsConfigVar(key) then
exe := getConfigVar(key);
if targetOS = osWindows then exe := addFileExt(exe, 'exe');
if (optGenDynLib in gGlobalOptions)
and (ospNeedsPIC in platform.OS[targetOS].props) then
add(options, ' ' + cc[c].pic);
if targetOS = platform.hostOS then begin
// compute include paths:
includeCmd := cc[c].includeCmd; // this is more complex than needed, but
// a workaround of a FPC bug...
add(includeCmd, quoteIfContainsWhite(libpath));
compilePattern := JoinPath(ccompilerpath, exe);
end
else begin
includeCmd := '';
compilePattern := cc[c].compilerExe
end;
if targetOS = platform.hostOS then
cfile := cfilename
else
cfile := extractFileName(cfilename);
if not isExternal or (targetOS <> platform.hostOS) then
objfile := toObjFile(cfile)
else
objfile := completeCFilePath(toObjFile(cfile));
cfile := quoteIfContainsWhite(AddFileExt(cfile, cExt));
objfile := quoteIfContainsWhite(objfile);
result := quoteIfContainsWhite(format(compilePattern,
['file', cfile,
'objfile', objfile,
'options', options,
'include', includeCmd,
'nimrod', getPrefixDir(),
'lib', libpath
]));
add(result, ' ');
add(result, format(cc[c].compileTmpl,
['file', cfile,
'objfile', objfile,
'options', options,
'include', includeCmd,
'nimrod', quoteIfContainsWhite(getPrefixDir()),
'lib', quoteIfContainsWhite(libpath)
]));
end;
procedure CompileCFile(const list: TLinkedList;
var script: PRope;
var cmds: TStringSeq;
isExternal: Boolean);
var
it: PStrEntry;
compileCmd: string;
begin
it := PStrEntry(list.head);
while it <> nil do begin
inc(fileCounter);
// call the C compiler for the .c file:
compileCmd := getCompileCFileCmd(it.data, isExternal);
if not (optCompileOnly in gGlobalOptions) then
add(cmds, compileCmd); //execExternalProgram(compileCmd);
if (optGenScript in gGlobalOptions) then begin
app(script, compileCmd);
app(script, tnl);
end;
it := PStrEntry(it.next);
end;
end;
procedure CallCCompiler(const projectfile: string);
var
it: PStrEntry;
linkCmd, objfiles, exefile, buildgui, builddll, linkerExe: string;
c: TSystemCC; // an alias to ccompiler
script: PRope;
cmds: TStringSeq;
res, i: int;
begin
if (gGlobalOptions * [optCompileOnly, optGenScript] = [optCompileOnly]) then
exit; // speed up that call if only compiling and no script shall be
// generated
if (toCompile.head = nil) and (externalToCompile.head = nil) then exit;
fileCounter := 0;
c := ccompiler;
script := nil;
cmds := {@ignore} nil {@emit @[]};
CompileCFile(toCompile, script, cmds, false);
CompileCFile(externalToCompile, script, cmds, true);
if not (optCompileOnly in gGlobalOptions) then begin
if gNumberOfProcessors = 0 then
gNumberOfProcessors := countProcessors();
if gNumberOfProcessors <= 1 then begin
res := 0;
for i := 0 to high(cmds) do res := max(execCmd(cmds[i]), res);
end
else if (optListCmd in gGlobalOptions) or (gVerbosity > 0) then
res := execProcesses(cmds, {@set}[poEchoCmd, poUseShell, poParentStreams],
gNumberOfProcessors)
else
res := execProcesses(cmds, {@set}[poUseShell, poParentStreams],
gNumberOfProcessors);
if res <> 0 then
rawMessage(errExecutionOfProgramFailed);
end;
if not (optNoLinking in gGlobalOptions) then begin
// call the linker:
linkerExe := getConfigVar(cc[c].name + '.linkerexe');
if length(linkerExe) = 0 then linkerExe := cc[c].linkerExe;
if targetOS = osWindows then linkerExe := addFileExt(linkerExe, 'exe');
if (platform.hostOS <> targetOS) then
linkCmd := quoteIfContainsWhite(linkerExe)
else
linkCmd := quoteIfContainsWhite(JoinPath(ccompilerpath, linkerExe));
if optGenGuiApp in gGlobalOptions then
buildGui := cc[c].buildGui
else
buildGui := '';
if optGenDynLib in gGlobalOptions then begin
exefile := format(platform.os[targetOS].dllFrmt,
[splitFile(projectFile).name]);
buildDll := cc[c].buildDll;
end
else begin
exefile := splitFile(projectFile).name +{&} platform.os[targetOS].exeExt;
buildDll := '';
end;
if targetOS = platform.hostOS then
exefile := joinPath(splitFile(projectFile).dir, exefile);
exefile := quoteIfContainsWhite(exefile);
it := PStrEntry(toLink.head);
objfiles := '';
while it <> nil do begin
add(objfiles, ' '+'');
if targetOS = platform.hostOS then
add(objfiles, quoteIfContainsWhite(toObjfile(it.data)))
else
add(objfiles, quoteIfContainsWhite(
toObjfile(extractFileName(it.data))));
it := PStrEntry(it.next);
end;
linkCmd := quoteIfContainsWhite(format(linkCmd, [
'builddll', builddll,
'buildgui', buildgui,
'options', linkOptions,
'objfiles', objfiles,
'exefile', exefile,
'nimrod', getPrefixDir(),
'lib', libpath
]));
add(linkCmd, ' ');
add(linkCmd, format(cc[c].linkTmpl, [
'builddll', builddll,
'buildgui', buildgui,
'options', linkOptions,
'objfiles', objfiles,
'exefile', exefile,
'nimrod', quoteIfContainsWhite(getPrefixDir()),
'lib', quoteIfContainsWhite(libpath)
]));
if not (optCompileOnly in gGlobalOptions) then
execExternalProgram(linkCmd);
end // end if not noLinking
else
linkCmd := '';
if (optGenScript in gGlobalOptions) then begin
app(script, linkCmd);
app(script, tnl);
generateScript(projectFile, script)
end
end;
function genMappingFiles(const list: TLinkedList): PRope;
var
it: PStrEntry;
begin
result := nil;
it := PStrEntry(list.head);
while it <> nil do begin
appf(result, '--file:r"$1"$n', [toRope(AddFileExt(it.data, cExt))]);
it := PStrEntry(it.next);
end;
end;
procedure writeMapping(gSymbolMapping: PRope);
var
code: PRope;
begin
if not (optGenMapping in gGlobalOptions) then exit;
code := toRope('[C_Files]'+nl);
app(code, genMappingFiles(toCompile));
app(code, genMappingFiles(externalToCompile));
appf(code, '[Symbols]$n$1', [gSymbolMapping]);
WriteRope(code, joinPath(projectPath, 'mapping.txt'));
end;
end.