extccomp.nim 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226
  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, sequtils, times, strtabs
  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. produceAsm: string, # Format how to produce assembler listings
  49. cppXsupport: string, # what to do to enable C++X support
  50. props: TInfoCCProps] # properties of the C compiler
  51. # Configuration settings for various compilers.
  52. # When adding new compilers, the cmake sources could be a good reference:
  53. # http://cmake.org/gitweb?p=cmake.git;a=tree;f=Modules/Platform;
  54. template compiler(name, settings: untyped): untyped =
  55. proc name: TInfoCC {.compileTime.} = settings
  56. const
  57. gnuAsmListing = "-Wa,-acdl=$asmfile -g -fverbose-asm -masm=intel"
  58. # GNU C and C++ Compiler
  59. compiler gcc:
  60. result = (
  61. name: "gcc",
  62. objExt: "o",
  63. optSpeed: " -O3 -fno-ident",
  64. optSize: " -Os -fno-ident",
  65. compilerExe: "gcc",
  66. cppCompiler: "g++",
  67. compileTmpl: "-c $options $include -o $objfile $file",
  68. buildGui: " -mwindows",
  69. buildDll: " -shared",
  70. buildLib: "ar rcs $libfile $objfiles",
  71. linkerExe: "",
  72. linkTmpl: "$buildgui $builddll -o $exefile $objfiles $options",
  73. includeCmd: " -I",
  74. linkDirCmd: " -L",
  75. linkLibCmd: " -l$1",
  76. debug: "",
  77. pic: "-fPIC",
  78. asmStmtFrmt: "asm($1);$n",
  79. structStmtFmt: "$1 $3 $2 ", # struct|union [packed] $name
  80. produceAsm: gnuAsmListing,
  81. cppXsupport: "-std=gnu++14 -funsigned-char",
  82. props: {hasSwitchRange, hasComputedGoto, hasCpp, hasGcGuard, hasGnuAsm,
  83. hasAttribute})
  84. # GNU C and C++ Compiler
  85. compiler nintendoSwitchGCC:
  86. result = (
  87. name: "switch_gcc",
  88. objExt: "o",
  89. optSpeed: " -O3 ",
  90. optSize: " -Os ",
  91. compilerExe: "aarch64-none-elf-gcc",
  92. cppCompiler: "aarch64-none-elf-g++",
  93. compileTmpl: "-w -MMD -MP -MF $dfile -c $options $include -o $objfile $file",
  94. buildGui: " -mwindows",
  95. buildDll: " -shared",
  96. buildLib: "aarch64-none-elf-gcc-ar rcs $libfile $objfiles",
  97. linkerExe: "aarch64-none-elf-gcc",
  98. linkTmpl: "$buildgui $builddll -Wl,-Map,$mapfile -o $exefile $objfiles $options",
  99. includeCmd: " -I",
  100. linkDirCmd: " -L",
  101. linkLibCmd: " -l$1",
  102. debug: "",
  103. pic: "-fPIE",
  104. asmStmtFrmt: "asm($1);$n",
  105. structStmtFmt: "$1 $3 $2 ", # struct|union [packed] $name
  106. produceAsm: gnuAsmListing,
  107. cppXsupport: "-std=gnu++14 -funsigned-char",
  108. props: {hasSwitchRange, hasComputedGoto, hasCpp, hasGcGuard, hasGnuAsm,
  109. hasAttribute})
  110. # LLVM Frontend for GCC/G++
  111. compiler llvmGcc:
  112. result = gcc() # Uses settings from GCC
  113. result.name = "llvm_gcc"
  114. result.compilerExe = "llvm-gcc"
  115. result.cppCompiler = "llvm-g++"
  116. when defined(macosx):
  117. # OS X has no 'llvm-ar' tool:
  118. result.buildLib = "ar rcs $libfile $objfiles"
  119. else:
  120. result.buildLib = "llvm-ar rcs $libfile $objfiles"
  121. # Clang (LLVM) C/C++ Compiler
  122. compiler clang:
  123. result = llvmGcc() # Uses settings from llvmGcc
  124. result.name = "clang"
  125. result.compilerExe = "clang"
  126. result.cppCompiler = "clang++"
  127. # Microsoft Visual C/C++ Compiler
  128. compiler vcc:
  129. result = (
  130. name: "vcc",
  131. objExt: "obj",
  132. optSpeed: " /Ogityb2 /G7 ",
  133. optSize: " /O1 /G7 ",
  134. compilerExe: "cl",
  135. cppCompiler: "cl",
  136. compileTmpl: "/c$vccplatform $options $include /Fo$objfile $file",
  137. buildGui: " /link /SUBSYSTEM:WINDOWS ",
  138. buildDll: " /LD",
  139. buildLib: "lib /OUT:$libfile $objfiles",
  140. linkerExe: "cl",
  141. linkTmpl: "$builddll$vccplatform /Fe$exefile $objfiles $buildgui $options",
  142. includeCmd: " /I",
  143. linkDirCmd: " /LIBPATH:",
  144. linkLibCmd: " $1.lib",
  145. debug: " /RTC1 /Z7 ",
  146. pic: "",
  147. asmStmtFrmt: "__asm{$n$1$n}$n",
  148. structStmtFmt: "$3$n$1 $2",
  149. produceAsm: "/Fa$asmfile",
  150. cppXsupport: "",
  151. props: {hasCpp, hasAssume, hasDeclspec})
  152. compiler clangcl:
  153. result = vcc()
  154. result.name = "clang_cl"
  155. result.compilerExe = "clang-cl"
  156. result.cppCompiler = "clang-cl"
  157. result.linkerExe = "clang-cl"
  158. # Intel C/C++ Compiler
  159. compiler icl:
  160. result = vcc()
  161. result.name = "icl"
  162. result.compilerExe = "icl"
  163. result.linkerExe = "icl"
  164. # Intel compilers try to imitate the native ones (gcc and msvc)
  165. compiler icc:
  166. result = gcc()
  167. result.name = "icc"
  168. result.compilerExe = "icc"
  169. result.linkerExe = "icc"
  170. # Local C Compiler
  171. compiler lcc:
  172. result = (
  173. name: "lcc",
  174. objExt: "obj",
  175. optSpeed: " -O -p6 ",
  176. optSize: " -O -p6 ",
  177. compilerExe: "lcc",
  178. cppCompiler: "",
  179. compileTmpl: "$options $include -Fo$objfile $file",
  180. buildGui: " -subsystem windows",
  181. buildDll: " -dll",
  182. buildLib: "", # XXX: not supported yet
  183. linkerExe: "lcclnk",
  184. linkTmpl: "$options $buildgui $builddll -O $exefile $objfiles",
  185. includeCmd: " -I",
  186. linkDirCmd: "", # XXX: not supported yet
  187. linkLibCmd: "", # XXX: not supported yet
  188. debug: " -g5 ",
  189. pic: "",
  190. asmStmtFrmt: "_asm{$n$1$n}$n",
  191. structStmtFmt: "$1 $2",
  192. produceAsm: "",
  193. cppXsupport: "",
  194. props: {})
  195. # Borland C Compiler
  196. compiler bcc:
  197. result = (
  198. name: "bcc",
  199. objExt: "obj",
  200. optSpeed: " -O3 -6 ",
  201. optSize: " -O1 -6 ",
  202. compilerExe: "bcc32c",
  203. cppCompiler: "cpp32c",
  204. compileTmpl: "-c $options $include -o$objfile $file",
  205. buildGui: " -tW",
  206. buildDll: " -tWD",
  207. buildLib: "", # XXX: not supported yet
  208. linkerExe: "bcc32",
  209. linkTmpl: "$options $buildgui $builddll -e$exefile $objfiles",
  210. includeCmd: " -I",
  211. linkDirCmd: "", # XXX: not supported yet
  212. linkLibCmd: "", # XXX: not supported yet
  213. debug: "",
  214. pic: "",
  215. asmStmtFrmt: "__asm{$n$1$n}$n",
  216. structStmtFmt: "$1 $2",
  217. produceAsm: "",
  218. cppXsupport: "",
  219. props: {hasSwitchRange, hasComputedGoto, hasCpp, hasGcGuard,
  220. hasAttribute})
  221. # Digital Mars C Compiler
  222. compiler dmc:
  223. result = (
  224. name: "dmc",
  225. objExt: "obj",
  226. optSpeed: " -ff -o -6 ",
  227. optSize: " -ff -o -6 ",
  228. compilerExe: "dmc",
  229. cppCompiler: "",
  230. compileTmpl: "-c $options $include -o$objfile $file",
  231. buildGui: " -L/exet:nt/su:windows",
  232. buildDll: " -WD",
  233. buildLib: "", # XXX: not supported yet
  234. linkerExe: "dmc",
  235. linkTmpl: "$options $buildgui $builddll -o$exefile $objfiles",
  236. includeCmd: " -I",
  237. linkDirCmd: "", # XXX: not supported yet
  238. linkLibCmd: "", # XXX: not supported yet
  239. debug: " -g ",
  240. pic: "",
  241. asmStmtFrmt: "__asm{$n$1$n}$n",
  242. structStmtFmt: "$3$n$1 $2",
  243. produceAsm: "",
  244. cppXsupport: "",
  245. props: {hasCpp})
  246. # Watcom C Compiler
  247. compiler wcc:
  248. result = (
  249. name: "wcc",
  250. objExt: "obj",
  251. optSpeed: " -ox -on -6 -d0 -fp6 -zW ",
  252. optSize: "",
  253. compilerExe: "wcl386",
  254. cppCompiler: "",
  255. compileTmpl: "-c $options $include -fo=$objfile $file",
  256. buildGui: " -bw",
  257. buildDll: " -bd",
  258. buildLib: "", # XXX: not supported yet
  259. linkerExe: "wcl386",
  260. linkTmpl: "$options $buildgui $builddll -fe=$exefile $objfiles ",
  261. includeCmd: " -i=",
  262. linkDirCmd: "", # XXX: not supported yet
  263. linkLibCmd: "", # XXX: not supported yet
  264. debug: " -d2 ",
  265. pic: "",
  266. asmStmtFrmt: "__asm{$n$1$n}$n",
  267. structStmtFmt: "$1 $2",
  268. produceAsm: "",
  269. cppXsupport: "",
  270. props: {hasCpp})
  271. # Tiny C Compiler
  272. compiler tcc:
  273. result = (
  274. name: "tcc",
  275. objExt: "o",
  276. optSpeed: "",
  277. optSize: "",
  278. compilerExe: "tcc",
  279. cppCompiler: "",
  280. compileTmpl: "-c $options $include -o $objfile $file",
  281. buildGui: "-Wl,-subsystem=gui",
  282. buildDll: " -shared",
  283. buildLib: "", # XXX: not supported yet
  284. linkerExe: "tcc",
  285. linkTmpl: "-o $exefile $options $buildgui $builddll $objfiles",
  286. includeCmd: " -I",
  287. linkDirCmd: "", # XXX: not supported yet
  288. linkLibCmd: "", # XXX: not supported yet
  289. debug: " -g ",
  290. pic: "",
  291. asmStmtFrmt: "asm($1);$n",
  292. structStmtFmt: "$1 $2",
  293. produceAsm: gnuAsmListing,
  294. cppXsupport: "",
  295. props: {hasSwitchRange, hasComputedGoto, hasGnuAsm})
  296. # Pelles C Compiler
  297. compiler pcc:
  298. # Pelles C
  299. result = (
  300. name: "pcc",
  301. objExt: "obj",
  302. optSpeed: " -Ox ",
  303. optSize: " -Os ",
  304. compilerExe: "cc",
  305. cppCompiler: "",
  306. compileTmpl: "-c $options $include -Fo$objfile $file",
  307. buildGui: " -SUBSYSTEM:WINDOWS",
  308. buildDll: " -DLL",
  309. buildLib: "", # XXX: not supported yet
  310. linkerExe: "cc",
  311. linkTmpl: "$options $buildgui $builddll -OUT:$exefile $objfiles",
  312. includeCmd: " -I",
  313. linkDirCmd: "", # XXX: not supported yet
  314. linkLibCmd: "", # XXX: not supported yet
  315. debug: " -Zi ",
  316. pic: "",
  317. asmStmtFrmt: "__asm{$n$1$n}$n",
  318. structStmtFmt: "$1 $2",
  319. produceAsm: "",
  320. cppXsupport: "",
  321. props: {})
  322. # Your C Compiler
  323. compiler ucc:
  324. result = (
  325. name: "ucc",
  326. objExt: "o",
  327. optSpeed: " -O3 ",
  328. optSize: " -O1 ",
  329. compilerExe: "cc",
  330. cppCompiler: "",
  331. compileTmpl: "-c $options $include -o $objfile $file",
  332. buildGui: "",
  333. buildDll: " -shared ",
  334. buildLib: "", # XXX: not supported yet
  335. linkerExe: "cc",
  336. linkTmpl: "-o $exefile $buildgui $builddll $objfiles $options",
  337. includeCmd: " -I",
  338. linkDirCmd: "", # XXX: not supported yet
  339. linkLibCmd: "", # XXX: not supported yet
  340. debug: "",
  341. pic: "",
  342. asmStmtFrmt: "__asm{$n$1$n}$n",
  343. structStmtFmt: "$1 $2",
  344. produceAsm: "",
  345. cppXsupport: "",
  346. props: {})
  347. const
  348. CC*: array[succ(low(TSystemCC))..high(TSystemCC), TInfoCC] = [
  349. gcc(),
  350. nintendoSwitchGCC(),
  351. llvmGcc(),
  352. clang(),
  353. lcc(),
  354. bcc(),
  355. dmc(),
  356. wcc(),
  357. vcc(),
  358. tcc(),
  359. pcc(),
  360. ucc(),
  361. icl(),
  362. icc(),
  363. clangcl()]
  364. hExt* = ".h"
  365. proc libNameTmpl(conf: ConfigRef): string {.inline.} =
  366. result = if conf.target.targetOS == osWindows: "$1.lib" else: "lib$1.a"
  367. proc nameToCC*(name: string): TSystemCC =
  368. ## Returns the kind of compiler referred to by `name`, or ccNone
  369. ## if the name doesn't refer to any known compiler.
  370. for i in succ(ccNone)..high(TSystemCC):
  371. if cmpIgnoreStyle(name, CC[i].name) == 0:
  372. return i
  373. result = ccNone
  374. proc listCCnames(): string =
  375. result = ""
  376. for i in succ(ccNone)..high(TSystemCC):
  377. if i > succ(ccNone): result.add ", "
  378. result.add CC[i].name
  379. proc isVSCompatible*(conf: ConfigRef): bool =
  380. return conf.cCompiler == ccVcc or
  381. conf.cCompiler == ccClangCl or
  382. (conf.cCompiler == ccIcl and conf.target.hostOS in osDos..osWindows)
  383. proc getConfigVar(conf: ConfigRef; c: TSystemCC, suffix: string): string =
  384. # use ``cpu.os.cc`` for cross compilation, unless ``--compileOnly`` is given
  385. # for niminst support
  386. let fullSuffix =
  387. if conf.cmd == cmdCompileToCpp:
  388. ".cpp" & suffix
  389. elif conf.cmd == cmdCompileToOC:
  390. ".objc" & suffix
  391. elif conf.cmd == cmdCompileToJS:
  392. ".js" & suffix
  393. else:
  394. suffix
  395. if (conf.target.hostOS != conf.target.targetOS or conf.target.hostCPU != conf.target.targetCPU) and
  396. optCompileOnly notin conf.globalOptions:
  397. let fullCCname = platform.CPU[conf.target.targetCPU].name & '.' &
  398. platform.OS[conf.target.targetOS].name & '.' &
  399. CC[c].name & fullSuffix
  400. result = getConfigVar(conf, fullCCname)
  401. if result.len == 0:
  402. # not overridden for this cross compilation setting?
  403. result = getConfigVar(conf, CC[c].name & fullSuffix)
  404. else:
  405. result = getConfigVar(conf, CC[c].name & fullSuffix)
  406. proc setCC*(conf: ConfigRef; ccname: string; info: TLineInfo) =
  407. conf.cCompiler = nameToCC(ccname)
  408. if conf.cCompiler == ccNone:
  409. localError(conf, info, "unknown C compiler: '$1'. Available options are: $2" % [ccname, listCCnames()])
  410. conf.compileOptions = getConfigVar(conf, conf.cCompiler, ".options.always")
  411. conf.linkOptions = ""
  412. conf.cCompilerPath = getConfigVar(conf, conf.cCompiler, ".path")
  413. for i in low(CC)..high(CC): undefSymbol(conf.symbols, CC[i].name)
  414. defineSymbol(conf.symbols, CC[conf.cCompiler].name)
  415. proc addOpt(dest: var string, src: string) =
  416. if dest.len == 0 or dest[^1] != ' ': dest.add(" ")
  417. dest.add(src)
  418. proc addLinkOption*(conf: ConfigRef; option: string) =
  419. addOpt(conf.linkOptions, option)
  420. proc addCompileOption*(conf: ConfigRef; option: string) =
  421. if strutils.find(conf.compileOptions, option, 0) < 0:
  422. addOpt(conf.compileOptions, option)
  423. proc addLinkOptionCmd*(conf: ConfigRef; option: string) =
  424. addOpt(conf.linkOptionsCmd, option)
  425. proc addCompileOptionCmd*(conf: ConfigRef; option: string) =
  426. conf.compileOptionsCmd.add(option)
  427. proc initVars*(conf: ConfigRef) =
  428. # we need to define the symbol here, because ``CC`` may have never been set!
  429. for i in low(CC)..high(CC): undefSymbol(conf.symbols, CC[i].name)
  430. defineSymbol(conf.symbols, CC[conf.cCompiler].name)
  431. addCompileOption(conf, getConfigVar(conf, conf.cCompiler, ".options.always"))
  432. #addLinkOption(getConfigVar(cCompiler, ".options.linker"))
  433. if conf.cCompilerPath.len == 0:
  434. conf.cCompilerPath = getConfigVar(conf, conf.cCompiler, ".path")
  435. proc completeCfilePath*(conf: ConfigRef; cfile: AbsoluteFile,
  436. createSubDir: bool = true): AbsoluteFile =
  437. result = completeGeneratedFilePath(conf, cfile, createSubDir)
  438. proc toObjFile*(conf: ConfigRef; filename: AbsoluteFile): AbsoluteFile =
  439. # Object file for compilation
  440. result = AbsoluteFile(filename.string & "." & CC[conf.cCompiler].objExt)
  441. proc addFileToCompile*(conf: ConfigRef; cf: Cfile) =
  442. conf.toCompile.add(cf)
  443. proc addLocalCompileOption*(conf: ConfigRef; option: string; nimfile: AbsoluteFile) =
  444. let key = completeCfilePath(conf, withPackageName(conf, nimfile)).string
  445. var value = conf.cfileSpecificOptions.getOrDefault(key)
  446. if strutils.find(value, option, 0) < 0:
  447. addOpt(value, option)
  448. conf.cfileSpecificOptions[key] = value
  449. proc resetCompilationLists*(conf: ConfigRef) =
  450. conf.toCompile.setLen 0
  451. ## XXX: we must associate these with their originating module
  452. # when the module is loaded/unloaded it adds/removes its items
  453. # That's because we still need to hash check the external files
  454. # Maybe we can do that in checkDep on the other hand?
  455. conf.externalToLink.setLen 0
  456. proc addExternalFileToLink*(conf: ConfigRef; filename: AbsoluteFile) =
  457. conf.externalToLink.insert(filename.string, 0)
  458. proc execWithEcho(conf: ConfigRef; cmd: string, msg = hintExecuting): int =
  459. rawMessage(conf, msg, if msg == hintLinking and not(optListCmd in conf.globalOptions or conf.verbosity > 1): "" else: cmd)
  460. result = execCmd(cmd)
  461. proc execExternalProgram*(conf: ConfigRef; cmd: string, msg = hintExecuting) =
  462. if execWithEcho(conf, cmd, msg) != 0:
  463. rawMessage(conf, errGenerated, "execution of an external program failed: '$1'" %
  464. cmd)
  465. proc generateScript(conf: ConfigRef; script: Rope) =
  466. let (_, name, _) = splitFile(conf.outFile.string)
  467. let filename = getNimcacheDir(conf) / RelativeFile(addFileExt("compile_" & name,
  468. platform.OS[conf.target.targetOS].scriptExt))
  469. if not writeRope(script, filename):
  470. rawMessage(conf, errGenerated, "could not write to file: " & filename.string)
  471. proc getOptSpeed(conf: ConfigRef; c: TSystemCC): string =
  472. result = getConfigVar(conf, c, ".options.speed")
  473. if result == "":
  474. result = CC[c].optSpeed # use default settings from this file
  475. proc getDebug(conf: ConfigRef; c: TSystemCC): string =
  476. result = getConfigVar(conf, c, ".options.debug")
  477. if result == "":
  478. result = CC[c].debug # use default settings from this file
  479. proc getOptSize(conf: ConfigRef; c: TSystemCC): string =
  480. result = getConfigVar(conf, c, ".options.size")
  481. if result == "":
  482. result = CC[c].optSize # use default settings from this file
  483. proc noAbsolutePaths(conf: ConfigRef): bool {.inline.} =
  484. # We used to check current OS != specified OS, but this makes no sense
  485. # really: Cross compilation from Linux to Linux for example is entirely
  486. # reasonable.
  487. # `optGenMapping` is included here for niminst.
  488. result = conf.globalOptions * {optGenScript, optGenMapping} != {}
  489. proc cFileSpecificOptions(conf: ConfigRef; nimname, fullNimFile: string): string =
  490. result = conf.compileOptions
  491. addOpt(result, conf.cfileSpecificOptions.getOrDefault(fullNimFile))
  492. for option in conf.compileOptionsCmd:
  493. if strutils.find(result, option, 0) < 0:
  494. addOpt(result, option)
  495. if optCDebug in conf.globalOptions:
  496. let key = nimname & ".debug"
  497. if existsConfigVar(conf, key): addOpt(result, getConfigVar(conf, key))
  498. else: addOpt(result, getDebug(conf, conf.cCompiler))
  499. if optOptimizeSpeed in conf.options:
  500. let key = nimname & ".speed"
  501. if existsConfigVar(conf, key): addOpt(result, getConfigVar(conf, key))
  502. else: addOpt(result, getOptSpeed(conf, conf.cCompiler))
  503. elif optOptimizeSize in conf.options:
  504. let key = nimname & ".size"
  505. if existsConfigVar(conf, key): addOpt(result, getConfigVar(conf, key))
  506. else: addOpt(result, getOptSize(conf, conf.cCompiler))
  507. let key = nimname & ".always"
  508. if existsConfigVar(conf, key): addOpt(result, getConfigVar(conf, key))
  509. proc getCompileOptions(conf: ConfigRef): string =
  510. result = cFileSpecificOptions(conf, "__dummy__", "__dummy__")
  511. proc vccplatform(conf: ConfigRef): string =
  512. # VCC specific but preferable over the config hacks people
  513. # had to do before, see #11306
  514. if conf.cCompiler == ccVcc:
  515. let exe = getConfigVar(conf, conf.cCompiler, ".exe")
  516. if "vccexe.exe" == extractFilename(exe):
  517. result = case conf.target.targetCPU
  518. of cpuI386: " --platform:x86"
  519. of cpuArm: " --platform:arm"
  520. of cpuAmd64: " --platform:amd64"
  521. else: ""
  522. proc getLinkOptions(conf: ConfigRef): string =
  523. result = conf.linkOptions & " " & conf.linkOptionsCmd & " "
  524. for linkedLib in items(conf.cLinkedLibs):
  525. result.add(CC[conf.cCompiler].linkLibCmd % linkedLib.quoteShell)
  526. for libDir in items(conf.cLibs):
  527. result.add(join([CC[conf.cCompiler].linkDirCmd, libDir.quoteShell]))
  528. proc needsExeExt(conf: ConfigRef): bool {.inline.} =
  529. result = (optGenScript in conf.globalOptions and conf.target.targetOS == osWindows) or
  530. (conf.target.hostOS == osWindows)
  531. proc useCpp(conf: ConfigRef; cfile: AbsoluteFile): bool =
  532. conf.cmd == cmdCompileToCpp and not cfile.string.endsWith(".c")
  533. proc getCompilerExe(conf: ConfigRef; compiler: TSystemCC; cfile: AbsoluteFile): string =
  534. result = if useCpp(conf, cfile):
  535. CC[compiler].cppCompiler
  536. else:
  537. CC[compiler].compilerExe
  538. if result.len == 0:
  539. rawMessage(conf, errGenerated,
  540. "Compiler '$1' doesn't support the requested target" %
  541. CC[compiler].name)
  542. proc ccHasSaneOverflow*(conf: ConfigRef): bool =
  543. if conf.cCompiler == ccGcc:
  544. result = false # assume an old or crappy GCC
  545. var exe = getConfigVar(conf, conf.cCompiler, ".exe")
  546. if exe.len == 0: exe = CC[conf.cCompiler].compilerExe
  547. let (s, exitCode) = try: execCmdEx(exe & " --version") except: ("", 1)
  548. if exitCode == 0:
  549. var i = 0
  550. var j = 0
  551. # the version is the last part of the first line:
  552. while i < s.len and s[i] != '\n':
  553. if s[i] in {' ', '\t'}: j = i+1
  554. inc i
  555. if j > 0:
  556. var major = 0
  557. while j < s.len and s[j] in {'0'..'9'}:
  558. major = major * 10 + (ord(s[j]) - ord('0'))
  559. inc j
  560. if i < s.len and s[j] == '.': inc j
  561. while j < s.len and s[j] in {'0'..'9'}:
  562. inc j
  563. if j+1 < s.len and s[j] == '.' and s[j+1] in {'0'..'9'}:
  564. # we found a third version number, chances are high
  565. # we really parsed the version:
  566. result = major >= 5
  567. else:
  568. result = conf.cCompiler == ccCLang
  569. proc getLinkerExe(conf: ConfigRef; compiler: TSystemCC): string =
  570. result = if CC[compiler].linkerExe.len > 0: CC[compiler].linkerExe
  571. elif optMixedMode in conf.globalOptions and conf.cmd != cmdCompileToCpp: CC[compiler].cppCompiler
  572. else: getCompilerExe(conf, compiler, AbsoluteFile"")
  573. proc getCompileCFileCmd*(conf: ConfigRef; cfile: Cfile,
  574. isMainFile = false; produceOutput = false): string =
  575. let c = conf.cCompiler
  576. # We produce files like module.nim.cpp, so the absolute Nim filename is not
  577. # cfile.name but `cfile.cname.changeFileExt("")`:
  578. var options = cFileSpecificOptions(conf, cfile.nimname, cfile.cname.changeFileExt("").string)
  579. var exe = getConfigVar(conf, c, ".exe")
  580. if exe.len == 0: exe = getCompilerExe(conf, c, cfile.cname)
  581. if needsExeExt(conf): exe = addFileExt(exe, "exe")
  582. if (optGenDynLib in conf.globalOptions or (conf.hcrOn and not isMainFile)) and
  583. ospNeedsPIC in platform.OS[conf.target.targetOS].props:
  584. options.add(' ' & CC[c].pic)
  585. if useCpp(conf, cfile.cname):
  586. options.add(' ' & CC[c].cppXsupport)
  587. if cfile.customArgs != "":
  588. options.add ' '
  589. options.add cfile.customArgs
  590. var compilePattern: string
  591. # compute include paths:
  592. var includeCmd = CC[c].includeCmd & quoteShell(conf.libpath)
  593. if not noAbsolutePaths(conf):
  594. for includeDir in items(conf.cIncludes):
  595. includeCmd.add(join([CC[c].includeCmd, includeDir.quoteShell]))
  596. compilePattern = joinPath(conf.cCompilerPath, exe)
  597. else:
  598. compilePattern = getCompilerExe(conf, c, cfile.cname)
  599. includeCmd.add(join([CC[c].includeCmd, quoteShell(conf.projectPath.string)]))
  600. var cf = if noAbsolutePaths(conf): AbsoluteFile extractFilename(cfile.cname.string)
  601. else: cfile.cname
  602. var objfile =
  603. if cfile.obj.isEmpty:
  604. if CfileFlag.External notin cfile.flags or noAbsolutePaths(conf):
  605. toObjFile(conf, cf).string
  606. else:
  607. completeCfilePath(conf, toObjFile(conf, cf)).string
  608. elif noAbsolutePaths(conf):
  609. extractFilename(cfile.obj.string)
  610. else:
  611. cfile.obj.string
  612. # D files are required by nintendo switch libs for
  613. # compilation. They are basically a list of all includes.
  614. let dfile = objfile.changeFileExt(".d").quoteShell
  615. let cfsh = quoteShell(cf)
  616. result = quoteShell(compilePattern % [
  617. "dfile", dfile,
  618. "file", cfsh, "objfile", quoteShell(objfile), "options", options,
  619. "include", includeCmd, "nim", getPrefixDir(conf).string,
  620. "lib", conf.libpath.string])
  621. if optProduceAsm in conf.globalOptions:
  622. if CC[conf.cCompiler].produceAsm.len > 0:
  623. let asmfile = objfile.changeFileExt(".asm").quoteShell
  624. addOpt(result, CC[conf.cCompiler].produceAsm % ["asmfile", asmfile])
  625. if produceOutput:
  626. rawMessage(conf, hintUserRaw, "Produced assembler here: " & asmfile)
  627. else:
  628. if produceOutput:
  629. rawMessage(conf, hintUserRaw, "Couldn't produce assembler listing " &
  630. "for the selected C compiler: " & CC[conf.cCompiler].name)
  631. result.add(' ')
  632. result.addf(CC[c].compileTmpl, [
  633. "dfile", dfile,
  634. "file", cfsh, "objfile", quoteShell(objfile),
  635. "options", options, "include", includeCmd,
  636. "nim", quoteShell(getPrefixDir(conf)),
  637. "lib", quoteShell(conf.libpath),
  638. "vccplatform", vccplatform(conf)])
  639. proc footprint(conf: ConfigRef; cfile: Cfile): SecureHash =
  640. result = secureHash(
  641. $secureHashFile(cfile.cname.string) &
  642. platform.OS[conf.target.targetOS].name &
  643. platform.CPU[conf.target.targetCPU].name &
  644. extccomp.CC[conf.cCompiler].name &
  645. getCompileCFileCmd(conf, cfile))
  646. proc externalFileChanged(conf: ConfigRef; cfile: Cfile): bool =
  647. if conf.cmd notin {cmdCompileToC, cmdCompileToCpp, cmdCompileToOC, cmdCompileToLLVM, cmdNone}:
  648. return false
  649. var hashFile = toGeneratedFile(conf, conf.withPackageName(cfile.cname), "sha1")
  650. var currentHash = footprint(conf, cfile)
  651. var f: File
  652. if open(f, hashFile.string, fmRead):
  653. let oldHash = parseSecureHash(f.readLine())
  654. close(f)
  655. result = oldHash != currentHash
  656. else:
  657. result = true
  658. if result:
  659. if open(f, hashFile.string, fmWrite):
  660. f.writeLine($currentHash)
  661. close(f)
  662. proc addExternalFileToCompile*(conf: ConfigRef; c: var Cfile) =
  663. if optForceFullMake notin conf.globalOptions and fileExists(c.obj) and
  664. not externalFileChanged(conf, c):
  665. c.flags.incl CfileFlag.Cached
  666. else:
  667. # make sure Nim keeps recompiling the external file on reruns
  668. # if compilation is not successful
  669. discard tryRemoveFile(c.obj.string)
  670. conf.toCompile.add(c)
  671. proc addExternalFileToCompile*(conf: ConfigRef; filename: AbsoluteFile) =
  672. var c = Cfile(nimname: splitFile(filename).name, cname: filename,
  673. obj: toObjFile(conf, completeCfilePath(conf, filename, false)),
  674. flags: {CfileFlag.External})
  675. addExternalFileToCompile(conf, c)
  676. proc getLinkCmd(conf: ConfigRef; output: AbsoluteFile,
  677. objfiles: string, isDllBuild: bool): string =
  678. if optGenStaticLib in conf.globalOptions:
  679. var libname: string
  680. if not conf.outFile.isEmpty:
  681. libname = conf.outFile.string.expandTilde
  682. if not libname.isAbsolute():
  683. libname = getCurrentDir() / libname
  684. else:
  685. libname = (libNameTmpl(conf) % splitFile(conf.projectName).name)
  686. result = CC[conf.cCompiler].buildLib % ["libfile", quoteShell(libname),
  687. "objfiles", objfiles]
  688. else:
  689. var linkerExe = getConfigVar(conf, conf.cCompiler, ".linkerexe")
  690. if linkerExe.len == 0: linkerExe = getLinkerExe(conf, conf.cCompiler)
  691. # bug #6452: We must not use ``quoteShell`` here for ``linkerExe``
  692. if needsExeExt(conf): linkerExe = addFileExt(linkerExe, "exe")
  693. if noAbsolutePaths(conf): result = linkerExe
  694. else: result = joinPath(conf.cCompilerPath, linkerExe)
  695. let buildgui = if optGenGuiApp in conf.globalOptions and conf.target.targetOS == osWindows:
  696. CC[conf.cCompiler].buildGui
  697. else:
  698. ""
  699. let builddll = if isDllBuild: CC[conf.cCompiler].buildDll else: ""
  700. let exefile = quoteShell(output)
  701. when false:
  702. if optCDebug in conf.globalOptions:
  703. writeDebugInfo(exefile.changeFileExt("ndb"))
  704. # Map files are required by Nintendo Switch compilation. They are a list
  705. # of all function calls in the library and where they come from.
  706. let mapfile = quoteShell(getNimcacheDir(conf) / RelativeFile(splitFile(output).name & ".map"))
  707. let linkOptions = getLinkOptions(conf) & " " &
  708. getConfigVar(conf, conf.cCompiler, ".options.linker")
  709. var linkTmpl = getConfigVar(conf, conf.cCompiler, ".linkTmpl")
  710. if linkTmpl.len == 0:
  711. linkTmpl = CC[conf.cCompiler].linkTmpl
  712. result = quoteShell(result % ["builddll", builddll,
  713. "mapfile", mapfile,
  714. "buildgui", buildgui, "options", linkOptions, "objfiles", objfiles,
  715. "exefile", exefile, "nim", getPrefixDir(conf).string, "lib", conf.libpath.string])
  716. result.add ' '
  717. result.addf(linkTmpl, ["builddll", builddll,
  718. "mapfile", mapfile,
  719. "buildgui", buildgui, "options", linkOptions,
  720. "objfiles", objfiles, "exefile", exefile,
  721. "nim", quoteShell(getPrefixDir(conf)),
  722. "lib", quoteShell(conf.libpath),
  723. "vccplatform", vccplatform(conf)])
  724. # On windows the debug information for binaries is emitted in a separate .pdb
  725. # file and the binaries (.dll and .exe) contain a full path to that .pdb file.
  726. # This is a problem for hot code reloading because even when we copy the .dll
  727. # and load the copy so the build process may overwrite the original .dll on
  728. # the disk (windows locks the files of running binaries) the copy still points
  729. # to the original .pdb (and a simple copy of the .pdb won't help). This is a
  730. # problem when a debugger is attached to the program we are hot-reloading.
  731. # This problem is nonexistent on Unix since there by default debug symbols
  732. # are embedded in the binaries so loading a copy of a .so will be fine. There
  733. # is the '/Z7' flag for the MSVC compiler to embed the debug info of source
  734. # files into their respective .obj files but the linker still produces a .pdb
  735. # when a final .dll or .exe is linked so the debug info isn't embedded.
  736. # There is also the issue that even when a .dll is unloaded the debugger
  737. # still keeps the .pdb for that .dll locked. This is a major problem and
  738. # because of this we cannot just alternate between 2 names for a .pdb file
  739. # when rebuilding a .dll - instead we need to accumulate differently named
  740. # .pdb files in the nimcache folder - this is the easiest and most reliable
  741. # way of being able to debug and rebuild the program at the same time. This
  742. # is accomplished using the /PDB:<filename> flag (there also exists the
  743. # /PDBALTPATH:<filename> flag). The only downside is that the .pdb files are
  744. # at least 300kb big (when linking statically to the runtime - or else 5mb+)
  745. # and will quickly accumulate. There is a hacky solution: we could try to
  746. # delete all .pdb files with a pattern and swallow exceptions.
  747. #
  748. # links about .pdb files and hot code reloading:
  749. # https://ourmachinery.com/post/dll-hot-reloading-in-theory-and-practice/
  750. # https://ourmachinery.com/post/little-machines-working-together-part-2/
  751. # https://github.com/fungos/cr
  752. # https://fungos.github.io/blog/2017/11/20/cr.h-a-simple-c-hot-reload-header-only-library/
  753. # on forcing the debugger to unlock a locked .pdb of an unloaded library:
  754. # https://blog.molecular-matters.com/2017/05/09/deleting-pdb-files-locked-by-visual-studio/
  755. # and a bit about the .pdb format in case that is ever needed:
  756. # https://github.com/crosire/blink
  757. # http://www.debuginfo.com/articles/debuginfomatch.html#pdbfiles
  758. if conf.hcrOn and isVSCompatible(conf):
  759. let t = now()
  760. let pdb = output.string & "." & format(t, "MMMM-yyyy-HH-mm-") & $t.nanosecond & ".pdb"
  761. result.add " /link /PDB:" & pdb
  762. if optCDebug in conf.globalOptions and conf.cCompiler == ccVcc:
  763. result.add " /Zi /FS /Od"
  764. template getLinkCmd(conf: ConfigRef; output: AbsoluteFile, objfiles: string): string =
  765. getLinkCmd(conf, output, objfiles, optGenDynLib in conf.globalOptions)
  766. template tryExceptOSErrorMessage(conf: ConfigRef; errorPrefix: string = "", body: untyped) =
  767. try:
  768. body
  769. except OSError:
  770. let ose = (ref OSError)(getCurrentException())
  771. if errorPrefix.len > 0:
  772. rawMessage(conf, errGenerated, errorPrefix & " " & ose.msg & " " & $ose.errorCode)
  773. else:
  774. rawMessage(conf, errGenerated, "execution of an external program failed: '$1'" %
  775. (ose.msg & " " & $ose.errorCode))
  776. raise
  777. proc execLinkCmd(conf: ConfigRef; linkCmd: string) =
  778. tryExceptOSErrorMessage(conf, "invocation of external linker program failed."):
  779. execExternalProgram(conf, linkCmd, hintLinking)
  780. proc maybeRunDsymutil(conf: ConfigRef; exe: AbsoluteFile) =
  781. when defined(osx):
  782. if optCDebug in conf.globalOptions and optGenStaticLib notin conf.globalOptions:
  783. # if needed, add an option to skip or override location
  784. let cmd = "dsymutil " & $(exe).quoteShell
  785. conf.extraCmds.add cmd
  786. tryExceptOSErrorMessage(conf, "invocation of dsymutil failed."):
  787. execExternalProgram(conf, cmd, hintExecuting)
  788. proc execCmdsInParallel(conf: ConfigRef; cmds: seq[string]; prettyCb: proc (idx: int)) =
  789. let runCb = proc (idx: int, p: Process) =
  790. let exitCode = p.peekExitCode
  791. if exitCode != 0:
  792. rawMessage(conf, errGenerated, "execution of an external compiler program '" &
  793. cmds[idx] & "' failed with exit code: " & $exitCode & "\n\n")
  794. if conf.numberOfProcessors == 0: conf.numberOfProcessors = countProcessors()
  795. var res = 0
  796. if conf.numberOfProcessors <= 1:
  797. for i in 0..high(cmds):
  798. tryExceptOSErrorMessage(conf, "invocation of external compiler program failed."):
  799. res = execWithEcho(conf, cmds[i])
  800. if res != 0:
  801. rawMessage(conf, errGenerated, "execution of an external program failed: '$1'" %
  802. cmds[i])
  803. else:
  804. tryExceptOSErrorMessage(conf, "invocation of external compiler program failed."):
  805. res = execProcesses(cmds, {poStdErrToStdOut, poUsePath, poParentStreams},
  806. conf.numberOfProcessors, prettyCb, afterRunEvent=runCb)
  807. if res != 0:
  808. if conf.numberOfProcessors <= 1:
  809. rawMessage(conf, errGenerated, "execution of an external program failed: '$1'" %
  810. cmds.join())
  811. proc linkViaResponseFile(conf: ConfigRef; cmd: string) =
  812. # Extracting the linker.exe here is a bit hacky but the best solution
  813. # given ``buildLib``'s design.
  814. var i = 0
  815. var last = 0
  816. if cmd.len > 0 and cmd[0] == '"':
  817. inc i
  818. while i < cmd.len and cmd[i] != '"': inc i
  819. last = i
  820. inc i
  821. else:
  822. while i < cmd.len and cmd[i] != ' ': inc i
  823. last = i
  824. while i < cmd.len and cmd[i] == ' ': inc i
  825. let linkerArgs = conf.projectName & "_" & "linkerArgs.txt"
  826. let args = cmd.substr(i)
  827. # GCC's response files don't support backslashes. Junk.
  828. if conf.cCompiler == ccGcc or conf.cCompiler == ccCLang:
  829. writeFile(linkerArgs, args.replace('\\', '/'))
  830. else:
  831. writeFile(linkerArgs, args)
  832. try:
  833. execLinkCmd(conf, cmd.substr(0, last) & " @" & linkerArgs)
  834. finally:
  835. removeFile(linkerArgs)
  836. proc getObjFilePath(conf: ConfigRef, f: Cfile): string =
  837. if noAbsolutePaths(conf): f.obj.extractFilename
  838. else: f.obj.string
  839. proc hcrLinkTargetName(conf: ConfigRef, objFile: string, isMain = false): AbsoluteFile =
  840. let basename = splitFile(objFile).name
  841. let targetName = if isMain: basename & ".exe"
  842. else: platform.OS[conf.target.targetOS].dllFrmt % basename
  843. result = conf.getNimcacheDir / RelativeFile(targetName)
  844. proc displayProgressCC(conf: ConfigRef, path, compileCmd: string): string =
  845. if conf.hasHint(hintCC):
  846. if optListCmd in conf.globalOptions or conf.verbosity > 1:
  847. result = MsgKindToStr[hintCC] % (demanglePackageName(path.splitFile.name) & ": " & compileCmd)
  848. else:
  849. result = MsgKindToStr[hintCC] % demanglePackageName(path.splitFile.name)
  850. proc callCCompiler*(conf: ConfigRef) =
  851. var
  852. linkCmd: string
  853. if conf.globalOptions * {optCompileOnly, optGenScript} == {optCompileOnly}:
  854. return # speed up that call if only compiling and no script shall be
  855. # generated
  856. #var c = cCompiler
  857. var script: Rope = nil
  858. var cmds: TStringSeq
  859. var prettyCmds: TStringSeq
  860. let prettyCb = proc (idx: int) =
  861. if prettyCmds[idx].len > 0: echo prettyCmds[idx]
  862. for idx, it in conf.toCompile:
  863. # call the C compiler for the .c file:
  864. if CfileFlag.Cached in it.flags: continue
  865. let compileCmd = getCompileCFileCmd(conf, it, idx == conf.toCompile.len - 1, produceOutput=true)
  866. if optCompileOnly notin conf.globalOptions:
  867. cmds.add(compileCmd)
  868. prettyCmds.add displayProgressCC(conf, $it.cname, compileCmd)
  869. if optGenScript in conf.globalOptions:
  870. script.add(compileCmd)
  871. script.add("\n")
  872. if optCompileOnly notin conf.globalOptions:
  873. execCmdsInParallel(conf, cmds, prettyCb)
  874. if optNoLinking notin conf.globalOptions:
  875. # call the linker:
  876. var objfiles = ""
  877. for it in conf.externalToLink:
  878. let objFile = if noAbsolutePaths(conf): it.extractFilename else: it
  879. objfiles.add(' ')
  880. objfiles.add(quoteShell(
  881. addFileExt(objFile, CC[conf.cCompiler].objExt)))
  882. if conf.hcrOn: # lets assume that optCompileOnly isn't on
  883. cmds = @[]
  884. let mainFileIdx = conf.toCompile.len - 1
  885. for idx, x in conf.toCompile:
  886. # don't relink each of the many binaries (one for each source file) if the nim code is
  887. # cached because that would take too much time for small changes - the only downside to
  888. # this is that if an external-to-link file changes the final target wouldn't be relinked
  889. if CfileFlag.Cached in x.flags: continue
  890. # we pass each object file as if it is the project file - a .dll will be created for each such
  891. # object file in the nimcache directory, and only in the case of the main project file will
  892. # there be probably an executable (if the project is such) which will be copied out of the nimcache
  893. let objFile = conf.getObjFilePath(x)
  894. let buildDll = idx != mainFileIdx
  895. let linkTarget = conf.hcrLinkTargetName(objFile, not buildDll)
  896. cmds.add(getLinkCmd(conf, linkTarget, objfiles & " " & quoteShell(objFile), buildDll))
  897. # try to remove all .pdb files for the current binary so they don't accumulate endlessly in the nimcache
  898. # for more info check the comment inside of getLinkCmd() where the /PDB:<filename> MSVC flag is used
  899. if isVSCompatible(conf):
  900. for pdb in walkFiles(objFile & ".*.pdb"):
  901. discard tryRemoveFile(pdb)
  902. # execute link commands in parallel - output will be a bit different
  903. # if it fails than that from execLinkCmd() but that doesn't matter
  904. prettyCmds = map(prettyCmds, proc (curr: string): string = return curr.replace("CC", "Link"))
  905. execCmdsInParallel(conf, cmds, prettyCb)
  906. # only if not cached - copy the resulting main file from the nimcache folder to its originally intended destination
  907. if CfileFlag.Cached notin conf.toCompile[mainFileIdx].flags:
  908. let mainObjFile = getObjFilePath(conf, conf.toCompile[mainFileIdx])
  909. var src = conf.hcrLinkTargetName(mainObjFile, true)
  910. var dst = conf.prepareToWriteOutput
  911. copyFileWithPermissions(src.string, dst.string)
  912. else:
  913. for x in conf.toCompile:
  914. let objFile = if noAbsolutePaths(conf): x.obj.extractFilename else: x.obj.string
  915. objfiles.add(' ')
  916. objfiles.add(quoteShell(objFile))
  917. let mainOutput = if optGenScript notin conf.globalOptions: conf.prepareToWriteOutput
  918. else: AbsoluteFile(conf.projectName)
  919. linkCmd = getLinkCmd(conf, mainOutput, objfiles)
  920. if optCompileOnly notin conf.globalOptions:
  921. const MaxCmdLen = when defined(windows): 8_000 else: 32_000
  922. if linkCmd.len > MaxCmdLen:
  923. # Windows's command line limit is about 8K (don't laugh...) so C compilers on
  924. # Windows support a feature where the command line can be passed via ``@linkcmd``
  925. # to them.
  926. linkViaResponseFile(conf, linkCmd)
  927. else:
  928. execLinkCmd(conf, linkCmd)
  929. maybeRunDsymutil(conf, mainOutput)
  930. else:
  931. linkCmd = ""
  932. if optGenScript in conf.globalOptions:
  933. script.add(linkCmd)
  934. script.add("\n")
  935. generateScript(conf, script)
  936. #from json import escapeJson
  937. import json, std / sha1
  938. template hashNimExe(): string = $secureHashFile(os.getAppFilename())
  939. proc writeJsonBuildInstructions*(conf: ConfigRef) =
  940. template lit(x: untyped) = f.write x
  941. template str(x: untyped) =
  942. when compiles(escapeJson(x, buf)):
  943. buf.setLen 0
  944. escapeJson(x, buf)
  945. f.write buf
  946. else:
  947. f.write escapeJson(x)
  948. proc cfiles(conf: ConfigRef; f: File; buf: var string; clist: CfileList, isExternal: bool) =
  949. var comma = false
  950. for i, it in clist:
  951. if CfileFlag.Cached in it.flags: continue
  952. let compileCmd = getCompileCFileCmd(conf, it)
  953. if comma: lit ",\L" else: comma = true
  954. lit "["
  955. str it.cname.string
  956. lit ", "
  957. str compileCmd
  958. lit "]"
  959. proc linkfiles(conf: ConfigRef; f: File; buf, objfiles: var string; clist: CfileList;
  960. llist: seq[string]) =
  961. var pastStart = false
  962. for it in llist:
  963. let objfile = if noAbsolutePaths(conf): it.extractFilename
  964. else: it
  965. let objstr = addFileExt(objfile, CC[conf.cCompiler].objExt)
  966. objfiles.add(' ')
  967. objfiles.add(objstr)
  968. if pastStart: lit ",\L"
  969. str objstr
  970. pastStart = true
  971. for it in clist:
  972. let objstr = quoteShell(it.obj)
  973. objfiles.add(' ')
  974. objfiles.add(objstr)
  975. if pastStart: lit ",\L"
  976. str objstr
  977. pastStart = true
  978. lit "\L"
  979. proc depfiles(conf: ConfigRef; f: File) =
  980. var i = 0
  981. for it in conf.m.fileInfos:
  982. let path = it.fullPath.string
  983. if isAbsolute(path): # TODO: else?
  984. if i > 0: lit "],\L"
  985. lit "["
  986. str path
  987. lit ", "
  988. str $secureHashFile(path)
  989. inc i
  990. lit "]\L"
  991. var buf = newStringOfCap(50)
  992. let jsonFile = conf.getNimcacheDir / RelativeFile(conf.projectName & ".json")
  993. var f: File
  994. if open(f, jsonFile.string, fmWrite):
  995. lit "{\"compile\":[\L"
  996. cfiles(conf, f, buf, conf.toCompile, false)
  997. lit "],\L\"link\":[\L"
  998. var objfiles = ""
  999. # XXX add every file here that is to link
  1000. linkfiles(conf, f, buf, objfiles, conf.toCompile, conf.externalToLink)
  1001. lit "],\L\"linkcmd\": "
  1002. str getLinkCmd(conf, conf.absOutFile, objfiles)
  1003. lit ",\L\"extraCmds\": "
  1004. lit $(%* conf.extraCmds)
  1005. lit ",\L\"stdinInput\": "
  1006. lit $(%* conf.projectIsStdin)
  1007. if optRun in conf.globalOptions or isDefined(conf, "nimBetterRun"):
  1008. lit ",\L\"cmdline\": "
  1009. str conf.commandLine
  1010. lit ",\L\"depfiles\":[\L"
  1011. depfiles(conf, f)
  1012. lit "],\L\"nimexe\": \L"
  1013. str hashNimExe()
  1014. lit "\L"
  1015. lit "\L}\L"
  1016. close(f)
  1017. proc changeDetectedViaJsonBuildInstructions*(conf: ConfigRef; projectfile: AbsoluteFile): bool =
  1018. let jsonFile = toGeneratedFile(conf, projectfile, "json")
  1019. if not fileExists(jsonFile): return true
  1020. if not fileExists(conf.absOutFile): return true
  1021. result = false
  1022. try:
  1023. let data = json.parseFile(jsonFile.string)
  1024. if not data.hasKey("depfiles") or not data.hasKey("cmdline"):
  1025. return true
  1026. let oldCmdLine = data["cmdline"].getStr
  1027. if conf.commandLine != oldCmdLine:
  1028. return true
  1029. if hashNimExe() != data["nimexe"].getStr:
  1030. return true
  1031. if not data.hasKey("stdinInput"): return true
  1032. let stdinInput = data["stdinInput"].getBool
  1033. if conf.projectIsStdin or stdinInput:
  1034. # could optimize by returning false if stdin input was the same,
  1035. # but I'm not sure how to get full stding input
  1036. return true
  1037. let depfilesPairs = data["depfiles"]
  1038. doAssert depfilesPairs.kind == JArray
  1039. for p in depfilesPairs:
  1040. doAssert p.kind == JArray
  1041. # >= 2 for forwards compatibility with potential later .json files:
  1042. doAssert p.len >= 2
  1043. let depFilename = p[0].getStr
  1044. let oldHashValue = p[1].getStr
  1045. let newHashValue = $secureHashFile(depFilename)
  1046. if oldHashValue != newHashValue:
  1047. return true
  1048. except IOError, OSError, ValueError:
  1049. echo "Warning: JSON processing failed: ", getCurrentExceptionMsg()
  1050. result = true
  1051. proc runJsonBuildInstructions*(conf: ConfigRef; projectfile: AbsoluteFile) =
  1052. let jsonFile = toGeneratedFile(conf, projectfile, "json")
  1053. try:
  1054. let data = json.parseFile(jsonFile.string)
  1055. let toCompile = data["compile"]
  1056. doAssert toCompile.kind == JArray
  1057. var cmds: TStringSeq
  1058. var prettyCmds: TStringSeq
  1059. let prettyCb = proc (idx: int) =
  1060. if prettyCmds[idx].len > 0: echo prettyCmds[idx]
  1061. for c in toCompile:
  1062. doAssert c.kind == JArray
  1063. doAssert c.len >= 2
  1064. cmds.add(c[1].getStr)
  1065. prettyCmds.add displayProgressCC(conf, c[0].getStr, c[1].getStr)
  1066. execCmdsInParallel(conf, cmds, prettyCb)
  1067. let linkCmd = data["linkcmd"]
  1068. doAssert linkCmd.kind == JString
  1069. execLinkCmd(conf, linkCmd.getStr)
  1070. if data.hasKey("extraCmds"):
  1071. let extraCmds = data["extraCmds"]
  1072. doAssert extraCmds.kind == JArray
  1073. for cmd in extraCmds:
  1074. doAssert cmd.kind == JString, $cmd.kind
  1075. let cmd2 = cmd.getStr
  1076. execExternalProgram(conf, cmd2, hintExecuting)
  1077. except:
  1078. let e = getCurrentException()
  1079. quit "\ncaught exception:\n" & e.msg & "\nstacktrace:\n" & e.getStackTrace() &
  1080. "error evaluating JSON file: " & jsonFile.string
  1081. proc genMappingFiles(conf: ConfigRef; list: CfileList): Rope =
  1082. for it in list:
  1083. result.addf("--file:r\"$1\"$N", [rope(it.cname.string)])
  1084. proc writeMapping*(conf: ConfigRef; symbolMapping: Rope) =
  1085. if optGenMapping notin conf.globalOptions: return
  1086. var code = rope("[C_Files]\n")
  1087. code.add(genMappingFiles(conf, conf.toCompile))
  1088. code.add("\n[C_Compiler]\nFlags=")
  1089. code.add(strutils.escape(getCompileOptions(conf)))
  1090. code.add("\n[Linker]\nFlags=")
  1091. code.add(strutils.escape(getLinkOptions(conf) & " " &
  1092. getConfigVar(conf, conf.cCompiler, ".options.linker")))
  1093. code.add("\n[Environment]\nlibpath=")
  1094. code.add(strutils.escape(conf.libpath.string))
  1095. code.addf("\n[Symbols]$n$1", [symbolMapping])
  1096. let filename = conf.projectPath / RelativeFile"mapping.txt"
  1097. if not writeRope(code, filename):
  1098. rawMessage(conf, errGenerated, "could not write to file: " & filename.string)