extccomp.nim 43 KB

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