main.nim 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. #
  2. #
  3. # The Nim Compiler
  4. # (c) Copyright 2015 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. # implements the command dispatcher and several commands
  10. when not defined(nimcore):
  11. {.error: "nimcore MUST be defined for Nim's core tooling".}
  12. import
  13. std/[strutils, os, times, tables, sha1, with, json],
  14. llstream, ast, lexer, syntaxes, options, msgs,
  15. condsyms,
  16. sem, idents, passes, extccomp,
  17. cgen, nversion,
  18. platform, nimconf, passaux, depends, vm,
  19. modules,
  20. modulegraphs, lineinfos, pathutils, vmprofiler
  21. import ic / [cbackend, integrity, navigator]
  22. from ic / ic import rodViewer
  23. when not defined(leanCompiler):
  24. import jsgen, docgen, docgen2
  25. proc semanticPasses(g: ModuleGraph) =
  26. registerPass g, verbosePass
  27. registerPass g, semPass
  28. proc writeDepsFile(g: ModuleGraph) =
  29. let fname = g.config.nimcacheDir / RelativeFile(g.config.projectName & ".deps")
  30. let f = open(fname.string, fmWrite)
  31. for m in g.ifaces:
  32. if m.module != nil:
  33. f.writeLine(toFullPath(g.config, m.module.position.FileIndex))
  34. for k in g.inclToMod.keys:
  35. if g.getModule(k).isNil: # don't repeat includes which are also modules
  36. f.writeLine(toFullPath(g.config, k))
  37. f.close()
  38. proc commandGenDepend(graph: ModuleGraph) =
  39. semanticPasses(graph)
  40. registerPass(graph, gendependPass)
  41. compileProject(graph)
  42. let project = graph.config.projectFull
  43. writeDepsFile(graph)
  44. generateDot(graph, project)
  45. execExternalProgram(graph.config, "dot -Tpng -o" &
  46. changeFileExt(project, "png").string &
  47. ' ' & changeFileExt(project, "dot").string)
  48. proc commandCheck(graph: ModuleGraph) =
  49. let conf = graph.config
  50. conf.setErrorMaxHighMaybe
  51. defineSymbol(conf.symbols, "nimcheck")
  52. if optWasNimscript in conf.globalOptions:
  53. defineSymbol(conf.symbols, "nimscript")
  54. defineSymbol(conf.symbols, "nimconfig")
  55. semanticPasses(graph) # use an empty backend for semantic checking only
  56. compileProject(graph)
  57. if conf.symbolFiles != disabledSf:
  58. case conf.ideCmd
  59. of ideDef: navDefinition(graph)
  60. of ideUse: navUsages(graph)
  61. of ideDus: navDefusages(graph)
  62. else: discard
  63. writeRodFiles(graph)
  64. when not defined(leanCompiler):
  65. proc commandDoc2(graph: ModuleGraph; ext: string) =
  66. handleDocOutputOptions graph.config
  67. graph.config.setErrorMaxHighMaybe
  68. semanticPasses(graph)
  69. case ext:
  70. of TexExt: registerPass(graph, docgen2TexPass)
  71. of JsonExt: registerPass(graph, docgen2JsonPass)
  72. of HtmlExt: registerPass(graph, docgen2Pass)
  73. else: doAssert false, $ext
  74. compileProject(graph)
  75. finishDoc2Pass(graph.config.projectName)
  76. proc commandCompileToC(graph: ModuleGraph) =
  77. let conf = graph.config
  78. extccomp.initVars(conf)
  79. semanticPasses(graph)
  80. if conf.symbolFiles == disabledSf:
  81. registerPass(graph, cgenPass)
  82. if {optRun, optForceFullMake} * conf.globalOptions == {optRun} or isDefined(conf, "nimBetterRun"):
  83. if not changeDetectedViaJsonBuildInstructions(conf, conf.jsonBuildInstructionsFile):
  84. # nothing changed
  85. graph.config.notes = graph.config.mainPackageNotes
  86. return
  87. if not extccomp.ccHasSaneOverflow(conf):
  88. conf.symbols.defineSymbol("nimEmulateOverflowChecks")
  89. compileProject(graph)
  90. if graph.config.errorCounter > 0:
  91. return # issue #9933
  92. if conf.symbolFiles == disabledSf:
  93. cgenWriteModules(graph.backend, conf)
  94. else:
  95. if isDefined(conf, "nimIcIntegrityChecks"):
  96. checkIntegrity(graph)
  97. generateCode(graph)
  98. # graph.backend can be nil under IC when nothing changed at all:
  99. if graph.backend != nil:
  100. cgenWriteModules(graph.backend, conf)
  101. if conf.cmd != cmdTcc and graph.backend != nil:
  102. extccomp.callCCompiler(conf)
  103. # for now we do not support writing out a .json file with the build instructions when HCR is on
  104. if not conf.hcrOn:
  105. extccomp.writeJsonBuildInstructions(conf)
  106. if optGenScript in graph.config.globalOptions:
  107. writeDepsFile(graph)
  108. proc commandJsonScript(graph: ModuleGraph) =
  109. extccomp.runJsonBuildInstructions(graph.config, graph.config.jsonBuildInstructionsFile)
  110. proc commandCompileToJS(graph: ModuleGraph) =
  111. let conf = graph.config
  112. when defined(leanCompiler):
  113. globalError(conf, unknownLineInfo, "compiler wasn't built with JS code generator")
  114. else:
  115. conf.exc = excCpp
  116. setTarget(conf.target, osJS, cpuJS)
  117. defineSymbol(conf.symbols, "ecmascript") # For backward compatibility
  118. semanticPasses(graph)
  119. registerPass(graph, JSgenPass)
  120. compileProject(graph)
  121. if optGenScript in conf.globalOptions:
  122. writeDepsFile(graph)
  123. proc interactivePasses(graph: ModuleGraph) =
  124. initDefines(graph.config.symbols)
  125. defineSymbol(graph.config.symbols, "nimscript")
  126. # note: seems redundant with -d:nimHasLibFFI
  127. when hasFFI: defineSymbol(graph.config.symbols, "nimffi")
  128. registerPass(graph, verbosePass)
  129. registerPass(graph, semPass)
  130. registerPass(graph, evalPass)
  131. proc commandInteractive(graph: ModuleGraph) =
  132. graph.config.setErrorMaxHighMaybe
  133. interactivePasses(graph)
  134. compileSystemModule(graph)
  135. if graph.config.commandArgs.len > 0:
  136. discard graph.compileModule(fileInfoIdx(graph.config, graph.config.projectFull), {})
  137. else:
  138. var m = graph.makeStdinModule()
  139. incl(m.flags, sfMainModule)
  140. var idgen = IdGenerator(module: m.itemId.module, symId: m.itemId.item, typeId: 0)
  141. let s = llStreamOpenStdIn(onPrompt = proc() = flushDot(graph.config))
  142. processModule(graph, m, idgen, s)
  143. proc commandScan(cache: IdentCache, config: ConfigRef) =
  144. var f = addFileExt(AbsoluteFile mainCommandArg(config), NimExt)
  145. var stream = llStreamOpen(f, fmRead)
  146. if stream != nil:
  147. var
  148. L: Lexer
  149. tok: Token
  150. initToken(tok)
  151. openLexer(L, f, stream, cache, config)
  152. while true:
  153. rawGetTok(L, tok)
  154. printTok(config, tok)
  155. if tok.tokType == tkEof: break
  156. closeLexer(L)
  157. else:
  158. rawMessage(config, errGenerated, "cannot open file: " & f.string)
  159. proc commandView(graph: ModuleGraph) =
  160. let f = toAbsolute(mainCommandArg(graph.config), AbsoluteDir getCurrentDir()).addFileExt(RodExt)
  161. rodViewer(f, graph.config, graph.cache)
  162. const
  163. PrintRopeCacheStats = false
  164. proc hashMainCompilationParams*(conf: ConfigRef): string =
  165. ## doesn't have to be complete; worst case is a cache hit and recompilation.
  166. var state = newSha1State()
  167. with state:
  168. update os.getAppFilename() # nim compiler
  169. update conf.commandLine # excludes `arguments`, as it should
  170. update $conf.projectFull # so that running `nim r main` from 2 directories caches differently
  171. result = $SecureHash(state.finalize())
  172. proc setOutFile*(conf: ConfigRef) =
  173. proc libNameTmpl(conf: ConfigRef): string {.inline.} =
  174. result = if conf.target.targetOS == osWindows: "$1.lib" else: "lib$1.a"
  175. if conf.outFile.isEmpty:
  176. var base = conf.projectName
  177. if optUseNimcache in conf.globalOptions:
  178. base.add "_" & hashMainCompilationParams(conf)
  179. let targetName =
  180. if conf.backend == backendJs: base & ".js"
  181. elif optGenDynLib in conf.globalOptions:
  182. platform.OS[conf.target.targetOS].dllFrmt % base
  183. elif optGenStaticLib in conf.globalOptions: libNameTmpl(conf) % base
  184. else: base & platform.OS[conf.target.targetOS].exeExt
  185. conf.outFile = RelativeFile targetName
  186. proc mainCommand*(graph: ModuleGraph) =
  187. let conf = graph.config
  188. let cache = graph.cache
  189. # In "nim serve" scenario, each command must reset the registered passes
  190. clearPasses(graph)
  191. conf.lastCmdTime = epochTime()
  192. conf.searchPaths.add(conf.libpath)
  193. proc customizeForBackend(backend: TBackend) =
  194. ## Sets backend specific options but don't compile to backend yet in
  195. ## case command doesn't require it. This must be called by all commands.
  196. if conf.backend == backendInvalid:
  197. # only set if wasn't already set, to allow override via `nim c -b:cpp`
  198. conf.backend = backend
  199. defineSymbol(graph.config.symbols, $conf.backend)
  200. case conf.backend
  201. of backendC:
  202. if conf.exc == excNone: conf.exc = excSetjmp
  203. of backendCpp:
  204. if conf.exc == excNone: conf.exc = excCpp
  205. of backendObjc: discard
  206. of backendJs:
  207. if conf.hcrOn:
  208. # XXX: At the moment, system.nim cannot be compiled in JS mode
  209. # with "-d:useNimRtl". The HCR option has been processed earlier
  210. # and it has added this define implictly, so we must undo that here.
  211. # A better solution might be to fix system.nim
  212. undefSymbol(conf.symbols, "useNimRtl")
  213. of backendInvalid: doAssert false
  214. proc compileToBackend() =
  215. customizeForBackend(conf.backend)
  216. setOutFile(conf)
  217. case conf.backend
  218. of backendC: commandCompileToC(graph)
  219. of backendCpp: commandCompileToC(graph)
  220. of backendObjc: commandCompileToC(graph)
  221. of backendJs: commandCompileToJS(graph)
  222. of backendInvalid: doAssert false
  223. template docLikeCmd(body) =
  224. when defined(leanCompiler):
  225. conf.quitOrRaise "compiler wasn't built with documentation generator"
  226. else:
  227. wantMainModule(conf)
  228. let docConf = if conf.cmd == cmdDoc2tex: DocTexConfig else: DocConfig
  229. loadConfigs(docConf, cache, conf, graph.idgen)
  230. defineSymbol(conf.symbols, "nimdoc")
  231. body
  232. ## command prepass
  233. if conf.cmd == cmdCrun: conf.globalOptions.incl {optRun, optUseNimcache}
  234. if conf.cmd notin cmdBackends + {cmdTcc}: customizeForBackend(backendC)
  235. if conf.outDir.isEmpty:
  236. # doc like commands can generate a lot of files (especially with --project)
  237. # so by default should not end up in $PWD nor in $projectPath.
  238. var ret = if optUseNimcache in conf.globalOptions: getNimcacheDir(conf)
  239. else: conf.projectPath
  240. doAssert ret.string.isAbsolute # `AbsoluteDir` is not a real guarantee
  241. if conf.cmd in cmdDocLike + {cmdRst2html, cmdRst2tex}: ret = ret / htmldocsDir
  242. conf.outDir = ret
  243. ## process all commands
  244. case conf.cmd
  245. of cmdBackends: compileToBackend()
  246. of cmdTcc:
  247. when hasTinyCBackend:
  248. extccomp.setCC(conf, "tcc", unknownLineInfo)
  249. if conf.backend != backendC:
  250. rawMessage(conf, errGenerated, "'run' requires c backend, got: '$1'" % $conf.backend)
  251. compileToBackend()
  252. else:
  253. rawMessage(conf, errGenerated, "'run' command not available; rebuild with -d:tinyc")
  254. of cmdDoc0: docLikeCmd commandDoc(cache, conf)
  255. of cmdDoc:
  256. docLikeCmd():
  257. conf.setNoteDefaults(warnLockLevel, false) # issue #13218
  258. conf.setNoteDefaults(warnRstRedefinitionOfLabel, false) # issue #13218
  259. # because currently generates lots of false positives due to conflation
  260. # of labels links in doc comments, e.g. for random.rand:
  261. # ## * `rand proc<#rand,Rand,Natural>`_ that returns an integer
  262. # ## * `rand proc<#rand,Rand,range[]>`_ that returns a float
  263. commandDoc2(graph, HtmlExt)
  264. if optGenIndex in conf.globalOptions and optWholeProject in conf.globalOptions:
  265. commandBuildIndex(conf, $conf.outDir)
  266. of cmdRst2html:
  267. # XXX: why are warnings disabled by default for rst2html and rst2tex?
  268. for warn in rstWarnings:
  269. conf.setNoteDefaults(warn, true)
  270. conf.setNoteDefaults(warnRstRedefinitionOfLabel, false) # similar to issue #13218
  271. when defined(leanCompiler):
  272. conf.quitOrRaise "compiler wasn't built with documentation generator"
  273. else:
  274. loadConfigs(DocConfig, cache, conf, graph.idgen)
  275. commandRst2Html(cache, conf)
  276. of cmdRst2tex, cmdDoc2tex:
  277. for warn in rstWarnings:
  278. conf.setNoteDefaults(warn, true)
  279. when defined(leanCompiler):
  280. conf.quitOrRaise "compiler wasn't built with documentation generator"
  281. else:
  282. if conf.cmd == cmdRst2tex:
  283. loadConfigs(DocTexConfig, cache, conf, graph.idgen)
  284. commandRst2TeX(cache, conf)
  285. else:
  286. docLikeCmd commandDoc2(graph, TexExt)
  287. of cmdJsondoc0: docLikeCmd commandJson(cache, conf)
  288. of cmdJsondoc: docLikeCmd commandDoc2(graph, JsonExt)
  289. of cmdCtags: docLikeCmd commandTags(cache, conf)
  290. of cmdBuildindex: docLikeCmd commandBuildIndex(conf, $conf.projectFull, conf.outFile)
  291. of cmdGendepend: commandGenDepend(graph)
  292. of cmdDump:
  293. if getConfigVar(conf, "dump.format") == "json":
  294. wantMainModule(conf)
  295. var definedSymbols = newJArray()
  296. for s in definedSymbolNames(conf.symbols): definedSymbols.elems.add(%s)
  297. var libpaths = newJArray()
  298. var lazyPaths = newJArray()
  299. for dir in conf.searchPaths: libpaths.elems.add(%dir.string)
  300. for dir in conf.lazyPaths: lazyPaths.elems.add(%dir.string)
  301. var hints = newJObject() # consider factoring with `listHints`
  302. for a in hintMin..hintMax:
  303. hints[$a] = %(a in conf.notes)
  304. var warnings = newJObject()
  305. for a in warnMin..warnMax:
  306. warnings[$a] = %(a in conf.notes)
  307. var dumpdata = %[
  308. (key: "version", val: %VersionAsString),
  309. (key: "nimExe", val: %(getAppFilename())),
  310. (key: "prefixdir", val: %conf.getPrefixDir().string),
  311. (key: "libpath", val: %conf.libpath.string),
  312. (key: "project_path", val: %conf.projectFull.string),
  313. (key: "defined_symbols", val: definedSymbols),
  314. (key: "lib_paths", val: %libpaths),
  315. (key: "lazyPaths", val: %lazyPaths),
  316. (key: "outdir", val: %conf.outDir.string),
  317. (key: "out", val: %conf.outFile.string),
  318. (key: "nimcache", val: %getNimcacheDir(conf).string),
  319. (key: "hints", val: hints),
  320. (key: "warnings", val: warnings),
  321. ]
  322. msgWriteln(conf, $dumpdata, {msgStdout, msgSkipHook, msgNoUnitSep})
  323. # `msgNoUnitSep` to avoid generating invalid json, refs bug #17853
  324. else:
  325. msgWriteln(conf, "-- list of currently defined symbols --",
  326. {msgStdout, msgSkipHook, msgNoUnitSep})
  327. for s in definedSymbolNames(conf.symbols): msgWriteln(conf, s, {msgStdout, msgSkipHook, msgNoUnitSep})
  328. msgWriteln(conf, "-- end of list --", {msgStdout, msgSkipHook})
  329. for it in conf.searchPaths: msgWriteln(conf, it.string)
  330. of cmdCheck:
  331. commandCheck(graph)
  332. of cmdParse:
  333. wantMainModule(conf)
  334. discard parseFile(conf.projectMainIdx, cache, conf)
  335. of cmdRod:
  336. wantMainModule(conf)
  337. commandView(graph)
  338. #msgWriteln(conf, "Beware: Indentation tokens depend on the parser's state!")
  339. of cmdInteractive: commandInteractive(graph)
  340. of cmdNimscript:
  341. if conf.projectIsCmd or conf.projectIsStdin: discard
  342. elif not fileExists(conf.projectFull):
  343. rawMessage(conf, errGenerated, "NimScript file does not exist: " & conf.projectFull.string)
  344. # main NimScript logic handled in `loadConfigs`.
  345. of cmdNop: discard
  346. of cmdJsonscript:
  347. setOutFile(graph.config)
  348. commandJsonScript(graph)
  349. of cmdUnknown, cmdNone, cmdIdeTools, cmdNimfix:
  350. rawMessage(conf, errGenerated, "invalid command: " & conf.command)
  351. if conf.errorCounter == 0 and conf.cmd notin {cmdTcc, cmdDump, cmdNop}:
  352. if optProfileVM in conf.globalOptions:
  353. echo conf.dump(conf.vmProfileData)
  354. genSuccessX(conf)
  355. when PrintRopeCacheStats:
  356. echo "rope cache stats: "
  357. echo " tries : ", gCacheTries
  358. echo " misses: ", gCacheMisses
  359. echo " int tries: ", gCacheIntTries
  360. echo " efficiency: ", formatFloat(1-(gCacheMisses.float/gCacheTries.float),
  361. ffDecimal, 3)