extccomp.nim 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923
  1. #
  2. #
  3. # The Nim Compiler
  4. # (c) Copyright 2013 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. # Module providing functions for calling the different external C compilers
  10. # Uses some hard-wired facts about each C/C++ compiler, plus options read
  11. # from a lineinfos file, to provide generalized procedures to compile
  12. # nim files.
  13. import
  14. ropes, os, strutils, osproc, platform, condsyms, options, msgs,
  15. lineinfos, std / sha1, streams, pathutils
  16. type
  17. TInfoCCProp* = enum # properties of the C compiler:
  18. hasSwitchRange, # CC allows ranges in switch statements (GNU C)
  19. hasComputedGoto, # CC has computed goto (GNU C extension)
  20. hasCpp, # CC is/contains a C++ compiler
  21. hasAssume, # CC has __assume (Visual C extension)
  22. hasGcGuard, # CC supports GC_GUARD to keep stack roots
  23. hasGnuAsm, # CC's asm uses the absurd GNU assembler syntax
  24. hasDeclspec, # CC has __declspec(X)
  25. hasAttribute, # CC has __attribute__((X))
  26. TInfoCCProps* = set[TInfoCCProp]
  27. TInfoCC* = tuple[
  28. name: string, # the short name of the compiler
  29. objExt: string, # the compiler's object file extension
  30. optSpeed: string, # the options for optimization for speed
  31. optSize: string, # the options for optimization for size
  32. compilerExe: string, # the compiler's executable
  33. cppCompiler: string, # name of the C++ compiler's executable (if supported)
  34. compileTmpl: string, # the compile command template
  35. buildGui: string, # command to build a GUI application
  36. buildDll: string, # command to build a shared library
  37. buildLib: string, # command to build a static library
  38. linkerExe: string, # the linker's executable (if not matching compiler's)
  39. linkTmpl: string, # command to link files to produce an exe
  40. includeCmd: string, # command to add an include dir
  41. linkDirCmd: string, # command to add a lib dir
  42. linkLibCmd: string, # command to link an external library
  43. debug: string, # flags for debug build
  44. pic: string, # command for position independent code
  45. # used on some platforms
  46. asmStmtFrmt: string, # format of ASM statement
  47. structStmtFmt: string, # Format for struct statement
  48. props: TInfoCCProps] # properties of the C compiler
  49. # Configuration settings for various compilers.
  50. # When adding new compilers, the cmake sources could be a good reference:
  51. # http://cmake.org/gitweb?p=cmake.git;a=tree;f=Modules/Platform;
  52. template compiler(name, settings: untyped): untyped =
  53. proc name: TInfoCC {.compileTime.} = settings
  54. # GNU C and C++ Compiler
  55. compiler gcc:
  56. result = (
  57. name: "gcc",
  58. objExt: "o",
  59. optSpeed: " -O3 -ffast-math ",
  60. optSize: " -Os -ffast-math ",
  61. compilerExe: "gcc",
  62. cppCompiler: "g++",
  63. compileTmpl: "-c $options $include -o $objfile $file",
  64. buildGui: " -mwindows",
  65. buildDll: " -shared",
  66. buildLib: "ar rcs $libfile $objfiles",
  67. linkerExe: "",
  68. linkTmpl: "$buildgui $builddll -o $exefile $objfiles $options",
  69. includeCmd: " -I",
  70. linkDirCmd: " -L",
  71. linkLibCmd: " -l$1",
  72. debug: "",
  73. pic: "-fPIC",
  74. asmStmtFrmt: "asm($1);$n",
  75. structStmtFmt: "$1 $3 $2 ", # struct|union [packed] $name
  76. props: {hasSwitchRange, hasComputedGoto, hasCpp, hasGcGuard, hasGnuAsm,
  77. hasAttribute})
  78. # GNU C and C++ Compiler
  79. compiler nintendoSwitchGCC:
  80. result = (
  81. name: "switch_gcc",
  82. objExt: "o",
  83. optSpeed: " -O3 -ffast-math ",
  84. optSize: " -Os -ffast-math ",
  85. compilerExe: "aarch64-none-elf-gcc",
  86. cppCompiler: "aarch64-none-elf-g++",
  87. compileTmpl: "-w -MMD -MP -MF $dfile -c $options $include -o $objfile $file",
  88. buildGui: " -mwindows",
  89. buildDll: " -shared",
  90. buildLib: "aarch64-none-elf-gcc-ar rcs $libfile $objfiles",
  91. linkerExe: "aarch64-none-elf-gcc",
  92. linkTmpl: "$buildgui $builddll -Wl,-Map,$mapfile -o $exefile $objfiles $options",
  93. includeCmd: " -I",
  94. linkDirCmd: " -L",
  95. linkLibCmd: " -l$1",
  96. debug: "",
  97. pic: "-fPIE",
  98. asmStmtFrmt: "asm($1);$n",
  99. structStmtFmt: "$1 $3 $2 ", # struct|union [packed] $name
  100. props: {hasSwitchRange, hasComputedGoto, hasCpp, hasGcGuard, hasGnuAsm,
  101. hasAttribute})
  102. # LLVM Frontend for GCC/G++
  103. compiler llvmGcc:
  104. result = gcc() # Uses settings from GCC
  105. result.name = "llvm_gcc"
  106. result.compilerExe = "llvm-gcc"
  107. result.cppCompiler = "llvm-g++"
  108. when defined(macosx):
  109. # OS X has no 'llvm-ar' tool:
  110. result.buildLib = "ar rcs $libfile $objfiles"
  111. else:
  112. result.buildLib = "llvm-ar rcs $libfile $objfiles"
  113. # Clang (LLVM) C/C++ Compiler
  114. compiler clang:
  115. result = llvmGcc() # Uses settings from llvmGcc
  116. result.name = "clang"
  117. result.compilerExe = "clang"
  118. result.cppCompiler = "clang++"
  119. # Microsoft Visual C/C++ Compiler
  120. compiler vcc:
  121. result = (
  122. name: "vcc",
  123. objExt: "obj",
  124. optSpeed: " /Ogityb2 /G7 /arch:SSE2 ",
  125. optSize: " /O1 /G7 ",
  126. compilerExe: "cl",
  127. cppCompiler: "cl",
  128. compileTmpl: "/c $options $include /Fo$objfile $file",
  129. buildGui: " /link /SUBSYSTEM:WINDOWS ",
  130. buildDll: " /LD",
  131. buildLib: "lib /OUT:$libfile $objfiles",
  132. linkerExe: "cl",
  133. linkTmpl: "$options $builddll /Fe$exefile $objfiles $buildgui",
  134. includeCmd: " /I",
  135. linkDirCmd: " /LIBPATH:",
  136. linkLibCmd: " $1.lib",
  137. debug: " /RTC1 /Z7 ",
  138. pic: "",
  139. asmStmtFrmt: "__asm{$n$1$n}$n",
  140. structStmtFmt: "$3$n$1 $2",
  141. props: {hasCpp, hasAssume, hasDeclspec})
  142. # Intel C/C++ Compiler
  143. compiler icl:
  144. result = vcc()
  145. result.name = "icl"
  146. result.compilerExe = "icl"
  147. result.linkerExe = "icl"
  148. # Intel compilers try to imitate the native ones (gcc and msvc)
  149. compiler icc:
  150. result = gcc()
  151. result.name = "icc"
  152. result.compilerExe = "icc"
  153. result.linkerExe = "icc"
  154. # Local C Compiler
  155. compiler lcc:
  156. result = (
  157. name: "lcc",
  158. objExt: "obj",
  159. optSpeed: " -O -p6 ",
  160. optSize: " -O -p6 ",
  161. compilerExe: "lcc",
  162. cppCompiler: "",
  163. compileTmpl: "$options $include -Fo$objfile $file",
  164. buildGui: " -subsystem windows",
  165. buildDll: " -dll",
  166. buildLib: "", # XXX: not supported yet
  167. linkerExe: "lcclnk",
  168. linkTmpl: "$options $buildgui $builddll -O $exefile $objfiles",
  169. includeCmd: " -I",
  170. linkDirCmd: "", # XXX: not supported yet
  171. linkLibCmd: "", # XXX: not supported yet
  172. debug: " -g5 ",
  173. pic: "",
  174. asmStmtFrmt: "_asm{$n$1$n}$n",
  175. structStmtFmt: "$1 $2",
  176. props: {})
  177. # Borland C Compiler
  178. compiler bcc:
  179. result = (
  180. name: "bcc",
  181. objExt: "obj",
  182. optSpeed: " -O3 -6 ",
  183. optSize: " -O1 -6 ",
  184. compilerExe: "bcc32c",
  185. cppCompiler: "cpp32c",
  186. compileTmpl: "-c $options $include -o$objfile $file",
  187. buildGui: " -tW",
  188. buildDll: " -tWD",
  189. buildLib: "", # XXX: not supported yet
  190. linkerExe: "bcc32",
  191. linkTmpl: "$options $buildgui $builddll -e$exefile $objfiles",
  192. includeCmd: " -I",
  193. linkDirCmd: "", # XXX: not supported yet
  194. linkLibCmd: "", # XXX: not supported yet
  195. debug: "",
  196. pic: "",
  197. asmStmtFrmt: "__asm{$n$1$n}$n",
  198. structStmtFmt: "$1 $2",
  199. props: {hasSwitchRange, hasComputedGoto, hasCpp, hasGcGuard,
  200. hasAttribute})
  201. # Digital Mars C Compiler
  202. compiler dmc:
  203. result = (
  204. name: "dmc",
  205. objExt: "obj",
  206. optSpeed: " -ff -o -6 ",
  207. optSize: " -ff -o -6 ",
  208. compilerExe: "dmc",
  209. cppCompiler: "",
  210. compileTmpl: "-c $options $include -o$objfile $file",
  211. buildGui: " -L/exet:nt/su:windows",
  212. buildDll: " -WD",
  213. buildLib: "", # XXX: not supported yet
  214. linkerExe: "dmc",
  215. linkTmpl: "$options $buildgui $builddll -o$exefile $objfiles",
  216. includeCmd: " -I",
  217. linkDirCmd: "", # XXX: not supported yet
  218. linkLibCmd: "", # XXX: not supported yet
  219. debug: " -g ",
  220. pic: "",
  221. asmStmtFrmt: "__asm{$n$1$n}$n",
  222. structStmtFmt: "$3$n$1 $2",
  223. props: {hasCpp})
  224. # Watcom C Compiler
  225. compiler wcc:
  226. result = (
  227. name: "wcc",
  228. objExt: "obj",
  229. optSpeed: " -ox -on -6 -d0 -fp6 -zW ",
  230. optSize: "",
  231. compilerExe: "wcl386",
  232. cppCompiler: "",
  233. compileTmpl: "-c $options $include -fo=$objfile $file",
  234. buildGui: " -bw",
  235. buildDll: " -bd",
  236. buildLib: "", # XXX: not supported yet
  237. linkerExe: "wcl386",
  238. linkTmpl: "$options $buildgui $builddll -fe=$exefile $objfiles ",
  239. includeCmd: " -i=",
  240. linkDirCmd: "", # XXX: not supported yet
  241. linkLibCmd: "", # XXX: not supported yet
  242. debug: " -d2 ",
  243. pic: "",
  244. asmStmtFrmt: "__asm{$n$1$n}$n",
  245. structStmtFmt: "$1 $2",
  246. props: {hasCpp})
  247. # Tiny C Compiler
  248. compiler tcc:
  249. result = (
  250. name: "tcc",
  251. objExt: "o",
  252. optSpeed: "",
  253. optSize: "",
  254. compilerExe: "tcc",
  255. cppCompiler: "",
  256. compileTmpl: "-c $options $include -o $objfile $file",
  257. buildGui: "-Wl,-subsystem=gui",
  258. buildDll: " -shared",
  259. buildLib: "", # XXX: not supported yet
  260. linkerExe: "tcc",
  261. linkTmpl: "-o $exefile $options $buildgui $builddll $objfiles",
  262. includeCmd: " -I",
  263. linkDirCmd: "", # XXX: not supported yet
  264. linkLibCmd: "", # XXX: not supported yet
  265. debug: " -g ",
  266. pic: "",
  267. asmStmtFrmt: "__asm{$n$1$n}$n",
  268. structStmtFmt: "$1 $2",
  269. props: {hasSwitchRange, hasComputedGoto})
  270. # Pelles C Compiler
  271. compiler pcc:
  272. # Pelles C
  273. result = (
  274. name: "pcc",
  275. objExt: "obj",
  276. optSpeed: " -Ox ",
  277. optSize: " -Os ",
  278. compilerExe: "cc",
  279. cppCompiler: "",
  280. compileTmpl: "-c $options $include -Fo$objfile $file",
  281. buildGui: " -SUBSYSTEM:WINDOWS",
  282. buildDll: " -DLL",
  283. buildLib: "", # XXX: not supported yet
  284. linkerExe: "cc",
  285. linkTmpl: "$options $buildgui $builddll -OUT:$exefile $objfiles",
  286. includeCmd: " -I",
  287. linkDirCmd: "", # XXX: not supported yet
  288. linkLibCmd: "", # XXX: not supported yet
  289. debug: " -Zi ",
  290. pic: "",
  291. asmStmtFrmt: "__asm{$n$1$n}$n",
  292. structStmtFmt: "$1 $2",
  293. props: {})
  294. # Your C Compiler
  295. compiler ucc:
  296. result = (
  297. name: "ucc",
  298. objExt: "o",
  299. optSpeed: " -O3 ",
  300. optSize: " -O1 ",
  301. compilerExe: "cc",
  302. cppCompiler: "",
  303. compileTmpl: "-c $options $include -o $objfile $file",
  304. buildGui: "",
  305. buildDll: " -shared ",
  306. buildLib: "", # XXX: not supported yet
  307. linkerExe: "cc",
  308. linkTmpl: "-o $exefile $buildgui $builddll $objfiles $options",
  309. includeCmd: " -I",
  310. linkDirCmd: "", # XXX: not supported yet
  311. linkLibCmd: "", # XXX: not supported yet
  312. debug: "",
  313. pic: "",
  314. asmStmtFrmt: "__asm{$n$1$n}$n",
  315. structStmtFmt: "$1 $2",
  316. props: {})
  317. const
  318. CC*: array[succ(low(TSystemCC))..high(TSystemCC), TInfoCC] = [
  319. gcc(),
  320. nintendoSwitchGCC(),
  321. llvmGcc(),
  322. clang(),
  323. lcc(),
  324. bcc(),
  325. dmc(),
  326. wcc(),
  327. vcc(),
  328. tcc(),
  329. pcc(),
  330. ucc(),
  331. icl(),
  332. icc()]
  333. hExt* = ".h"
  334. proc libNameTmpl(conf: ConfigRef): string {.inline.} =
  335. result = if conf.target.targetOS == osWindows: "$1.lib" else: "lib$1.a"
  336. proc nameToCC*(name: string): TSystemCC =
  337. ## Returns the kind of compiler referred to by `name`, or ccNone
  338. ## if the name doesn't refer to any known compiler.
  339. for i in countup(succ(ccNone), high(TSystemCC)):
  340. if cmpIgnoreStyle(name, CC[i].name) == 0:
  341. return i
  342. result = ccNone
  343. proc getConfigVar(conf: ConfigRef; c: TSystemCC, suffix: string): string =
  344. # use ``cpu.os.cc`` for cross compilation, unless ``--compileOnly`` is given
  345. # for niminst support
  346. let fullSuffix =
  347. if conf.cmd == cmdCompileToCpp:
  348. ".cpp" & suffix
  349. elif conf.cmd == cmdCompileToOC:
  350. ".objc" & suffix
  351. elif conf.cmd == cmdCompileToJS:
  352. ".js" & suffix
  353. else:
  354. suffix
  355. if (conf.target.hostOS != conf.target.targetOS or conf.target.hostCPU != conf.target.targetCPU) and
  356. optCompileOnly notin conf.globalOptions:
  357. let fullCCname = platform.CPU[conf.target.targetCPU].name & '.' &
  358. platform.OS[conf.target.targetOS].name & '.' &
  359. CC[c].name & fullSuffix
  360. result = getConfigVar(conf, fullCCname)
  361. if result.len == 0:
  362. # not overriden for this cross compilation setting?
  363. result = getConfigVar(conf, CC[c].name & fullSuffix)
  364. else:
  365. result = getConfigVar(conf, CC[c].name & fullSuffix)
  366. proc setCC*(conf: ConfigRef; ccname: string; info: TLineInfo) =
  367. conf.cCompiler = nameToCC(ccname)
  368. if conf.cCompiler == ccNone:
  369. localError(conf, info, "unknown C compiler: '$1'" % ccname)
  370. conf.compileOptions = getConfigVar(conf, conf.cCompiler, ".options.always")
  371. conf.linkOptions = ""
  372. conf.ccompilerpath = getConfigVar(conf, conf.cCompiler, ".path")
  373. for i in countup(low(CC), high(CC)): undefSymbol(conf.symbols, CC[i].name)
  374. defineSymbol(conf.symbols, CC[conf.cCompiler].name)
  375. proc addOpt(dest: var string, src: string) =
  376. if len(dest) == 0 or dest[len(dest)-1] != ' ': add(dest, " ")
  377. add(dest, src)
  378. proc addLinkOption*(conf: ConfigRef; option: string) =
  379. addOpt(conf.linkOptions, option)
  380. proc addCompileOption*(conf: ConfigRef; option: string) =
  381. if strutils.find(conf.compileOptions, option, 0) < 0:
  382. addOpt(conf.compileOptions, option)
  383. proc addLinkOptionCmd*(conf: ConfigRef; option: string) =
  384. addOpt(conf.linkOptionsCmd, option)
  385. proc addCompileOptionCmd*(conf: ConfigRef; option: string) =
  386. conf.compileOptionsCmd.add(option)
  387. proc initVars*(conf: ConfigRef) =
  388. # we need to define the symbol here, because ``CC`` may have never been set!
  389. for i in countup(low(CC), high(CC)): undefSymbol(conf.symbols, CC[i].name)
  390. defineSymbol(conf.symbols, CC[conf.cCompiler].name)
  391. addCompileOption(conf, getConfigVar(conf, conf.cCompiler, ".options.always"))
  392. #addLinkOption(getConfigVar(cCompiler, ".options.linker"))
  393. if len(conf.ccompilerpath) == 0:
  394. conf.ccompilerpath = getConfigVar(conf, conf.cCompiler, ".path")
  395. proc completeCFilePath*(conf: ConfigRef; cfile: AbsoluteFile,
  396. createSubDir: bool = true): AbsoluteFile =
  397. result = completeGeneratedFilePath(conf, cfile, createSubDir)
  398. proc toObjFile*(conf: ConfigRef; filename: AbsoluteFile): AbsoluteFile =
  399. # Object file for compilation
  400. result = AbsoluteFile(filename.string & "." & CC[conf.cCompiler].objExt)
  401. proc addFileToCompile*(conf: ConfigRef; cf: Cfile) =
  402. conf.toCompile.add(cf)
  403. proc resetCompilationLists*(conf: ConfigRef) =
  404. conf.toCompile.setLen 0
  405. ## XXX: we must associate these with their originating module
  406. # when the module is loaded/unloaded it adds/removes its items
  407. # That's because we still need to hash check the external files
  408. # Maybe we can do that in checkDep on the other hand?
  409. conf.externalToLink.setLen 0
  410. proc addExternalFileToLink*(conf: ConfigRef; filename: AbsoluteFile) =
  411. conf.externalToLink.insert(filename.string, 0)
  412. proc execWithEcho(conf: ConfigRef; cmd: string, msg = hintExecuting): int =
  413. rawMessage(conf, msg, cmd)
  414. result = execCmd(cmd)
  415. proc execExternalProgram*(conf: ConfigRef; cmd: string, msg = hintExecuting) =
  416. if execWithEcho(conf, cmd, msg) != 0:
  417. rawMessage(conf, errGenerated, "execution of an external program failed: '$1'" %
  418. cmd)
  419. proc generateScript(conf: ConfigRef; projectFile: AbsoluteFile, script: Rope) =
  420. let (_, name, _) = splitFile(projectFile)
  421. let filename = getNimcacheDir(conf) / RelativeFile(addFileExt("compile_" & name,
  422. platform.OS[conf.target.targetOS].scriptExt))
  423. if writeRope(script, filename):
  424. copyFile(conf.libpath / RelativeFile"nimbase.h",
  425. getNimcacheDir(conf) / RelativeFile"nimbase.h")
  426. else:
  427. rawMessage(conf, errGenerated, "could not write to file: " & filename.string)
  428. proc getOptSpeed(conf: ConfigRef; c: TSystemCC): string =
  429. result = getConfigVar(conf, c, ".options.speed")
  430. if result == "":
  431. result = CC[c].optSpeed # use default settings from this file
  432. proc getDebug(conf: ConfigRef; c: TSystemCC): string =
  433. result = getConfigVar(conf, c, ".options.debug")
  434. if result == "":
  435. result = CC[c].debug # use default settings from this file
  436. proc getOptSize(conf: ConfigRef; c: TSystemCC): string =
  437. result = getConfigVar(conf, c, ".options.size")
  438. if result == "":
  439. result = CC[c].optSize # use default settings from this file
  440. proc noAbsolutePaths(conf: ConfigRef): bool {.inline.} =
  441. # We used to check current OS != specified OS, but this makes no sense
  442. # really: Cross compilation from Linux to Linux for example is entirely
  443. # reasonable.
  444. # `optGenMapping` is included here for niminst.
  445. result = conf.globalOptions * {optGenScript, optGenMapping} != {}
  446. proc cFileSpecificOptions(conf: ConfigRef; cfilename: AbsoluteFile): string =
  447. result = conf.compileOptions
  448. for option in conf.compileOptionsCmd:
  449. if strutils.find(result, option, 0) < 0:
  450. addOpt(result, option)
  451. let trunk = splitFile(cfilename).name
  452. if optCDebug in conf.globalOptions:
  453. let key = trunk & ".debug"
  454. if existsConfigVar(conf, key): addOpt(result, getConfigVar(conf, key))
  455. else: addOpt(result, getDebug(conf, conf.cCompiler))
  456. if optOptimizeSpeed in conf.options:
  457. let key = trunk & ".speed"
  458. if existsConfigVar(conf, key): addOpt(result, getConfigVar(conf, key))
  459. else: addOpt(result, getOptSpeed(conf, conf.cCompiler))
  460. elif optOptimizeSize in conf.options:
  461. let key = trunk & ".size"
  462. if existsConfigVar(conf, key): addOpt(result, getConfigVar(conf, key))
  463. else: addOpt(result, getOptSize(conf, conf.cCompiler))
  464. let key = trunk & ".always"
  465. if existsConfigVar(conf, key): addOpt(result, getConfigVar(conf, key))
  466. proc getCompileOptions(conf: ConfigRef): string =
  467. result = cFileSpecificOptions(conf, AbsoluteFile"__dummy__")
  468. proc getLinkOptions(conf: ConfigRef): string =
  469. result = conf.linkOptions & " " & conf.linkOptionsCmd & " "
  470. for linkedLib in items(conf.cLinkedLibs):
  471. result.add(CC[conf.cCompiler].linkLibCmd % linkedLib.quoteShell)
  472. for libDir in items(conf.cLibs):
  473. result.add(join([CC[conf.cCompiler].linkDirCmd, libDir.quoteShell]))
  474. proc needsExeExt(conf: ConfigRef): bool {.inline.} =
  475. result = (optGenScript in conf.globalOptions and conf.target.targetOS == osWindows) or
  476. (conf.target.hostOS == osWindows)
  477. proc getCompilerExe(conf: ConfigRef; compiler: TSystemCC; cfile: AbsoluteFile): string =
  478. result = if conf.cmd == cmdCompileToCpp and not cfile.string.endsWith(".c"):
  479. CC[compiler].cppCompiler
  480. else:
  481. CC[compiler].compilerExe
  482. if result.len == 0:
  483. rawMessage(conf, errGenerated,
  484. "Compiler '$1' doesn't support the requested target" %
  485. CC[compiler].name)
  486. proc getLinkerExe(conf: ConfigRef; compiler: TSystemCC): string =
  487. result = if CC[compiler].linkerExe.len > 0: CC[compiler].linkerExe
  488. elif optMixedMode in conf.globalOptions and conf.cmd != cmdCompileToCpp: CC[compiler].cppCompiler
  489. else: getCompilerExe(conf, compiler, AbsoluteFile"")
  490. proc getCompileCFileCmd*(conf: ConfigRef; cfile: Cfile): string =
  491. var c = conf.cCompiler
  492. var options = cFileSpecificOptions(conf, cfile.cname)
  493. var exe = getConfigVar(conf, c, ".exe")
  494. if exe.len == 0: exe = getCompilerExe(conf, c, cfile.cname)
  495. if needsExeExt(conf): exe = addFileExt(exe, "exe")
  496. if optGenDynLib in conf.globalOptions and
  497. ospNeedsPIC in platform.OS[conf.target.targetOS].props:
  498. add(options, ' ' & CC[c].pic)
  499. var includeCmd, compilePattern: string
  500. if not noAbsolutePaths(conf):
  501. # compute include paths:
  502. includeCmd = CC[c].includeCmd & quoteShell(conf.libpath)
  503. for includeDir in items(conf.cIncludes):
  504. includeCmd.add(join([CC[c].includeCmd, includeDir.quoteShell]))
  505. compilePattern = joinPath(conf.ccompilerpath, exe)
  506. else:
  507. includeCmd = ""
  508. compilePattern = getCompilerExe(conf, c, cfile.cname)
  509. var cf = if noAbsolutePaths(conf): AbsoluteFile extractFilename(cfile.cname.string)
  510. else: cfile.cname
  511. var objfile =
  512. if cfile.obj.isEmpty:
  513. if not cfile.flags.contains(CfileFlag.External) or noAbsolutePaths(conf):
  514. toObjFile(conf, cf).string
  515. else:
  516. completeCFilePath(conf, toObjFile(conf, cf)).string
  517. elif noAbsolutePaths(conf):
  518. extractFilename(cfile.obj.string)
  519. else:
  520. cfile.obj.string
  521. # D files are required by nintendo switch libs for
  522. # compilation. They are basically a list of all includes.
  523. let dfile = objfile.changeFileExt(".d").quoteShell()
  524. objfile = quoteShell(objfile)
  525. let cfsh = quoteShell(cf)
  526. result = quoteShell(compilePattern % [
  527. "dfile", dfile,
  528. "file", cfsh, "objfile", objfile, "options", options,
  529. "include", includeCmd, "nim", getPrefixDir(conf).string,
  530. "lib", conf.libpath.string])
  531. add(result, ' ')
  532. addf(result, CC[c].compileTmpl, [
  533. "dfile", dfile,
  534. "file", cfsh, "objfile", objfile,
  535. "options", options, "include", includeCmd,
  536. "nim", quoteShell(getPrefixDir(conf)),
  537. "lib", quoteShell(conf.libpath)])
  538. proc footprint(conf: ConfigRef; cfile: Cfile): SecureHash =
  539. result = secureHash(
  540. $secureHashFile(cfile.cname.string) &
  541. platform.OS[conf.target.targetOS].name &
  542. platform.CPU[conf.target.targetCPU].name &
  543. extccomp.CC[conf.cCompiler].name &
  544. getCompileCFileCmd(conf, cfile))
  545. proc externalFileChanged(conf: ConfigRef; cfile: Cfile): bool =
  546. if conf.cmd notin {cmdCompileToC, cmdCompileToCpp, cmdCompileToOC, cmdCompileToLLVM}:
  547. return false
  548. var hashFile = toGeneratedFile(conf, conf.withPackageName(cfile.cname), "sha1")
  549. var currentHash = footprint(conf, cfile)
  550. var f: File
  551. if open(f, hashFile.string, fmRead):
  552. let oldHash = parseSecureHash(f.readLine())
  553. close(f)
  554. result = oldHash != currentHash
  555. else:
  556. result = true
  557. if result:
  558. if open(f, hashFile.string, fmWrite):
  559. f.writeLine($currentHash)
  560. close(f)
  561. proc addExternalFileToCompile*(conf: ConfigRef; c: var Cfile) =
  562. if optForceFullMake notin conf.globalOptions and fileExists(c.obj) and
  563. not externalFileChanged(conf, c):
  564. c.flags.incl CfileFlag.Cached
  565. conf.toCompile.add(c)
  566. proc addExternalFileToCompile*(conf: ConfigRef; filename: AbsoluteFile) =
  567. var c = Cfile(cname: filename,
  568. obj: toObjFile(conf, completeCFilePath(conf, filename, false)),
  569. flags: {CfileFlag.External})
  570. addExternalFileToCompile(conf, c)
  571. proc compileCFile(conf: ConfigRef; list: CFileList, script: var Rope, cmds: var TStringSeq,
  572. prettyCmds: var TStringSeq) =
  573. for it in list:
  574. # call the C compiler for the .c file:
  575. if it.flags.contains(CfileFlag.Cached): continue
  576. var compileCmd = getCompileCFileCmd(conf, it)
  577. if optCompileOnly notin conf.globalOptions:
  578. add(cmds, compileCmd)
  579. let (_, name, _) = splitFile(it.cname)
  580. add(prettyCmds, if hintCC in conf.notes: "CC: " & name else: "")
  581. if optGenScript in conf.globalOptions:
  582. add(script, compileCmd)
  583. add(script, "\n")
  584. proc getLinkCmd(conf: ConfigRef; projectfile: AbsoluteFile, objfiles: string): string =
  585. if optGenStaticLib in conf.globalOptions:
  586. var libname: string
  587. if not conf.outFile.isEmpty:
  588. libname = conf.outFile.string.expandTilde
  589. if not libname.isAbsolute():
  590. libname = getCurrentDir() / libname
  591. else:
  592. libname = (libNameTmpl(conf) % splitFile(conf.projectName).name)
  593. result = CC[conf.cCompiler].buildLib % ["libfile", quoteShell(libname),
  594. "objfiles", objfiles]
  595. else:
  596. var linkerExe = getConfigVar(conf, conf.cCompiler, ".linkerexe")
  597. if len(linkerExe) == 0: linkerExe = getLinkerExe(conf, conf.cCompiler)
  598. # bug #6452: We must not use ``quoteShell`` here for ``linkerExe``
  599. if needsExeExt(conf): linkerExe = addFileExt(linkerExe, "exe")
  600. if noAbsolutePaths(conf): result = linkerExe
  601. else: result = joinPath(conf.cCompilerpath, linkerExe)
  602. let buildgui = if optGenGuiApp in conf.globalOptions and conf.target.targetOS == osWindows:
  603. CC[conf.cCompiler].buildGui
  604. else:
  605. ""
  606. var exefile, builddll: string
  607. if optGenDynLib in conf.globalOptions:
  608. exefile = platform.OS[conf.target.targetOS].dllFrmt % splitFile(projectfile).name
  609. builddll = CC[conf.cCompiler].buildDll
  610. else:
  611. exefile = splitFile(projectfile).name & platform.OS[conf.target.targetOS].exeExt
  612. builddll = ""
  613. if not conf.outFile.isEmpty:
  614. exefile = conf.outFile.string.expandTilde
  615. if not exefile.isAbsolute():
  616. exefile = getCurrentDir() / exefile
  617. if not noAbsolutePaths(conf):
  618. if not exefile.isAbsolute():
  619. exefile = string(splitFile(projectfile).dir / RelativeFile(exefile))
  620. when false:
  621. if optCDebug in conf.globalOptions:
  622. writeDebugInfo(exefile.changeFileExt("ndb"))
  623. exefile = quoteShell(exefile)
  624. # Map files are required by Nintendo Switch compilation. They are a list
  625. # of all function calls in the library and where they come from.
  626. let mapfile = quoteShell(getNimcacheDir(conf) / RelativeFile(splitFile(projectFile).name & ".map"))
  627. let linkOptions = getLinkOptions(conf) & " " &
  628. getConfigVar(conf, conf.cCompiler, ".options.linker")
  629. var linkTmpl = getConfigVar(conf, conf.cCompiler, ".linkTmpl")
  630. if linkTmpl.len == 0:
  631. linkTmpl = CC[conf.cCompiler].linkTmpl
  632. result = quoteShell(result % ["builddll", builddll,
  633. "mapfile", mapfile,
  634. "buildgui", buildgui, "options", linkOptions, "objfiles", objfiles,
  635. "exefile", exefile, "nim", getPrefixDir(conf).string, "lib", conf.libpath.string])
  636. result.add ' '
  637. addf(result, linkTmpl, ["builddll", builddll,
  638. "mapfile", mapfile,
  639. "buildgui", buildgui, "options", linkOptions,
  640. "objfiles", objfiles, "exefile", exefile,
  641. "nim", quoteShell(getPrefixDir(conf)),
  642. "lib", quoteShell(conf.libpath)])
  643. template tryExceptOSErrorMessage(conf: ConfigRef; errorPrefix: string = "", body: untyped): typed =
  644. try:
  645. body
  646. except OSError:
  647. let ose = (ref OSError)(getCurrentException())
  648. if errorPrefix.len > 0:
  649. rawMessage(conf, errGenerated, errorPrefix & " " & ose.msg & " " & $ose.errorCode)
  650. else:
  651. rawMessage(conf, errGenerated, "execution of an external program failed: '$1'" %
  652. (ose.msg & " " & $ose.errorCode))
  653. raise
  654. proc execLinkCmd(conf: ConfigRef; linkCmd: string) =
  655. tryExceptOSErrorMessage(conf, "invocation of external linker program failed."):
  656. execExternalProgram(conf, linkCmd,
  657. if optListCmd in conf.globalOptions or conf.verbosity > 1: hintExecuting else: hintLinking)
  658. proc execCmdsInParallel(conf: ConfigRef; cmds: seq[string]; prettyCb: proc (idx: int)) =
  659. let runCb = proc (idx: int, p: Process) =
  660. let exitCode = p.peekExitCode
  661. if exitCode != 0:
  662. rawMessage(conf, errGenerated, "execution of an external compiler program '" &
  663. cmds[idx] & "' failed with exit code: " & $exitCode & "\n\n" &
  664. p.outputStream.readAll.strip)
  665. if conf.numberOfProcessors == 0: conf.numberOfProcessors = countProcessors()
  666. var res = 0
  667. if conf.numberOfProcessors <= 1:
  668. for i in countup(0, high(cmds)):
  669. tryExceptOSErrorMessage(conf, "invocation of external compiler program failed."):
  670. res = execWithEcho(conf, cmds[i])
  671. if res != 0:
  672. rawMessage(conf, errGenerated, "execution of an external program failed: '$1'" %
  673. cmds[i])
  674. else:
  675. tryExceptOSErrorMessage(conf, "invocation of external compiler program failed."):
  676. if optListCmd in conf.globalOptions or conf.verbosity > 1:
  677. res = execProcesses(cmds, {poEchoCmd, poStdErrToStdOut, poUsePath},
  678. conf.numberOfProcessors, afterRunEvent=runCb)
  679. elif conf.verbosity == 1:
  680. res = execProcesses(cmds, {poStdErrToStdOut, poUsePath},
  681. conf.numberOfProcessors, prettyCb, afterRunEvent=runCb)
  682. else:
  683. res = execProcesses(cmds, {poStdErrToStdOut, poUsePath},
  684. conf.numberOfProcessors, afterRunEvent=runCb)
  685. if res != 0:
  686. if conf.numberOfProcessors <= 1:
  687. rawMessage(conf, errGenerated, "execution of an external program failed: '$1'" %
  688. cmds.join())
  689. proc callCCompiler*(conf: ConfigRef; projectfile: AbsoluteFile) =
  690. var
  691. linkCmd: string
  692. if conf.globalOptions * {optCompileOnly, optGenScript} == {optCompileOnly}:
  693. return # speed up that call if only compiling and no script shall be
  694. # generated
  695. #var c = cCompiler
  696. var script: Rope = nil
  697. var cmds: TStringSeq = @[]
  698. var prettyCmds: TStringSeq = @[]
  699. let prettyCb = proc (idx: int) =
  700. when declared(echo):
  701. let cmd = prettyCmds[idx]
  702. if cmd != "": echo cmd
  703. compileCFile(conf, conf.toCompile, script, cmds, prettyCmds)
  704. if optCompileOnly notin conf.globalOptions:
  705. execCmdsInParallel(conf, cmds, prettyCb)
  706. if optNoLinking notin conf.globalOptions:
  707. # call the linker:
  708. var objfiles = ""
  709. for it in conf.externalToLink:
  710. let objFile = if noAbsolutePaths(conf): it.extractFilename else: it
  711. add(objfiles, ' ')
  712. add(objfiles, quoteShell(
  713. addFileExt(objFile, CC[conf.cCompiler].objExt)))
  714. for x in conf.toCompile:
  715. let objFile = if noAbsolutePaths(conf): x.obj.extractFilename else: x.obj.string
  716. add(objfiles, ' ')
  717. add(objfiles, quoteShell(objFile))
  718. linkCmd = getLinkCmd(conf, projectfile, objfiles)
  719. if optCompileOnly notin conf.globalOptions:
  720. execLinkCmd(conf, linkCmd)
  721. else:
  722. linkCmd = ""
  723. if optGenScript in conf.globalOptions:
  724. add(script, linkCmd)
  725. add(script, "\n")
  726. generateScript(conf, projectfile, script)
  727. #from json import escapeJson
  728. import json
  729. proc writeJsonBuildInstructions*(conf: ConfigRef; projectfile: AbsoluteFile) =
  730. template lit(x: untyped) = f.write x
  731. template str(x: untyped) =
  732. when compiles(escapeJson(x, buf)):
  733. buf.setLen 0
  734. escapeJson(x, buf)
  735. f.write buf
  736. else:
  737. f.write escapeJson(x)
  738. proc cfiles(conf: ConfigRef; f: File; buf: var string; clist: CfileList, isExternal: bool) =
  739. var pastStart = false
  740. for it in clist:
  741. if CfileFlag.Cached in it.flags: continue
  742. let compileCmd = getCompileCFileCmd(conf, it)
  743. if pastStart: lit "],\L"
  744. lit "["
  745. str it.cname.string
  746. lit ", "
  747. str compileCmd
  748. pastStart = true
  749. lit "]\L"
  750. proc linkfiles(conf: ConfigRef; f: File; buf, objfiles: var string; clist: CfileList;
  751. llist: seq[string]) =
  752. var pastStart = false
  753. for it in llist:
  754. let objfile = if noAbsolutePaths(conf): it.extractFilename
  755. else: it
  756. let objstr = addFileExt(objfile, CC[conf.cCompiler].objExt)
  757. add(objfiles, ' ')
  758. add(objfiles, objstr)
  759. if pastStart: lit ",\L"
  760. str objstr
  761. pastStart = true
  762. for it in clist:
  763. let objstr = quoteShell(it.obj)
  764. add(objfiles, ' ')
  765. add(objfiles, objstr)
  766. if pastStart: lit ",\L"
  767. str objstr
  768. pastStart = true
  769. lit "\L"
  770. var buf = newStringOfCap(50)
  771. let jsonFile = toGeneratedFile(conf, projectfile, "json")
  772. var f: File
  773. if open(f, jsonFile.string, fmWrite):
  774. lit "{\"compile\":[\L"
  775. cfiles(conf, f, buf, conf.toCompile, false)
  776. lit "],\L\"link\":[\L"
  777. var objfiles = ""
  778. # XXX add every file here that is to link
  779. linkfiles(conf, f, buf, objfiles, conf.toCompile, conf.externalToLink)
  780. lit "],\L\"linkcmd\": "
  781. str getLinkCmd(conf, projectfile, objfiles)
  782. lit "\L}\L"
  783. close(f)
  784. proc runJsonBuildInstructions*(conf: ConfigRef; projectfile: AbsoluteFile) =
  785. let jsonFile = toGeneratedFile(conf, projectfile, "json")
  786. try:
  787. let data = json.parseFile(jsonFile.string)
  788. let toCompile = data["compile"]
  789. doAssert toCompile.kind == JArray
  790. var cmds: TStringSeq = @[]
  791. var prettyCmds: TStringSeq = @[]
  792. for c in toCompile:
  793. doAssert c.kind == JArray
  794. doAssert c.len >= 2
  795. add(cmds, c[1].getStr)
  796. let (_, name, _) = splitFile(c[0].getStr)
  797. add(prettyCmds, "CC: " & name)
  798. let prettyCb = proc (idx: int) =
  799. when declared(echo):
  800. echo prettyCmds[idx]
  801. execCmdsInParallel(conf, cmds, prettyCb)
  802. let linkCmd = data["linkcmd"]
  803. doAssert linkCmd.kind == JString
  804. execLinkCmd(conf, linkCmd.getStr)
  805. except:
  806. when declared(echo):
  807. echo getCurrentException().getStackTrace()
  808. quit "error evaluating JSON file: " & jsonFile.string
  809. proc genMappingFiles(conf: ConfigRef; list: CFileList): Rope =
  810. for it in list:
  811. addf(result, "--file:r\"$1\"$N", [rope(it.cname.string)])
  812. proc writeMapping*(conf: ConfigRef; symbolMapping: Rope) =
  813. if optGenMapping notin conf.globalOptions: return
  814. var code = rope("[C_Files]\n")
  815. add(code, genMappingFiles(conf, conf.toCompile))
  816. add(code, "\n[C_Compiler]\nFlags=")
  817. add(code, strutils.escape(getCompileOptions(conf)))
  818. add(code, "\n[Linker]\nFlags=")
  819. add(code, strutils.escape(getLinkOptions(conf) & " " &
  820. getConfigVar(conf, conf.cCompiler, ".options.linker")))
  821. add(code, "\n[Environment]\nlibpath=")
  822. add(code, strutils.escape(conf.libpath.string))
  823. addf(code, "\n[Symbols]$n$1", [symbolMapping])
  824. let filename = conf.projectPath / RelativeFile"mapping.txt"
  825. if not writeRope(code, filename):
  826. rawMessage(conf, errGenerated, "could not write to file: " & filename.string)