extccomp.nim 45 KB

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