extccomp.nim 44 KB

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