extccomp.nim 44 KB

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