main.nim 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  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. llstream, strutils, os, ast, lexer, syntaxes, options, msgs,
  14. condsyms, times,
  15. sem, idents, passes, extccomp,
  16. cgen, json, nversion,
  17. platform, nimconf, passaux, depends, vm, idgen,
  18. modules,
  19. modulegraphs, tables, rod, lineinfos, pathutils, vmprofiler
  20. when not defined(leanCompiler):
  21. import jsgen, docgen, docgen2
  22. proc semanticPasses(g: ModuleGraph) =
  23. registerPass g, verbosePass
  24. registerPass g, semPass
  25. proc writeDepsFile(g: ModuleGraph) =
  26. let fname = g.config.nimcacheDir / RelativeFile(g.config.projectName & ".deps")
  27. let f = open(fname.string, fmWrite)
  28. for m in g.modules:
  29. if m != nil:
  30. f.writeLine(toFullPath(g.config, m.position.FileIndex))
  31. for k in g.inclToMod.keys:
  32. if g.getModule(k).isNil: # don't repeat includes which are also modules
  33. f.writeLine(toFullPath(g.config, k))
  34. f.close()
  35. proc commandGenDepend(graph: ModuleGraph) =
  36. semanticPasses(graph)
  37. registerPass(graph, gendependPass)
  38. compileProject(graph)
  39. let project = graph.config.projectFull
  40. writeDepsFile(graph)
  41. generateDot(graph, project)
  42. execExternalProgram(graph.config, "dot -Tpng -o" &
  43. changeFileExt(project, "png").string &
  44. ' ' & changeFileExt(project, "dot").string)
  45. proc commandCheck(graph: ModuleGraph) =
  46. graph.config.setErrorMaxHighMaybe
  47. defineSymbol(graph.config.symbols, "nimcheck")
  48. semanticPasses(graph) # use an empty backend for semantic checking only
  49. compileProject(graph)
  50. when not defined(leanCompiler):
  51. proc commandDoc2(graph: ModuleGraph; json: bool) =
  52. handleDocOutputOptions graph.config
  53. graph.config.setErrorMaxHighMaybe
  54. semanticPasses(graph)
  55. if json: registerPass(graph, docgen2JsonPass)
  56. else: registerPass(graph, docgen2Pass)
  57. compileProject(graph)
  58. finishDoc2Pass(graph.config.projectName)
  59. proc commandCompileToC(graph: ModuleGraph) =
  60. let conf = graph.config
  61. setOutFile(conf)
  62. extccomp.initVars(conf)
  63. semanticPasses(graph)
  64. registerPass(graph, cgenPass)
  65. if {optRun, optForceFullMake} * conf.globalOptions == {optRun} or isDefined(conf, "nimBetterRun"):
  66. let proj = changeFileExt(conf.projectFull, "")
  67. if not changeDetectedViaJsonBuildInstructions(conf, proj):
  68. # nothing changed
  69. graph.config.notes = graph.config.mainPackageNotes
  70. return
  71. if not extccomp.ccHasSaneOverflow(conf):
  72. conf.symbols.defineSymbol("nimEmulateOverflowChecks")
  73. compileProject(graph)
  74. if graph.config.errorCounter > 0:
  75. return # issue #9933
  76. cgenWriteModules(graph.backend, conf)
  77. if conf.cmd != cmdRun:
  78. extccomp.callCCompiler(conf)
  79. # for now we do not support writing out a .json file with the build instructions when HCR is on
  80. if not conf.hcrOn:
  81. extccomp.writeJsonBuildInstructions(conf)
  82. if optGenScript in graph.config.globalOptions:
  83. writeDepsFile(graph)
  84. proc commandJsonScript(graph: ModuleGraph) =
  85. let proj = changeFileExt(graph.config.projectFull, "")
  86. extccomp.runJsonBuildInstructions(graph.config, proj)
  87. proc commandCompileToJS(graph: ModuleGraph) =
  88. when defined(leanCompiler):
  89. globalError(graph.config, unknownLineInfo, "compiler wasn't built with JS code generator")
  90. else:
  91. let conf = graph.config
  92. conf.exc = excCpp
  93. if conf.outFile.isEmpty:
  94. conf.outFile = RelativeFile(conf.projectName & ".js")
  95. #incl(gGlobalOptions, optSafeCode)
  96. setTarget(graph.config.target, osJS, cpuJS)
  97. #initDefines()
  98. defineSymbol(graph.config.symbols, "ecmascript") # For backward compatibility
  99. semanticPasses(graph)
  100. registerPass(graph, JSgenPass)
  101. compileProject(graph)
  102. if optGenScript in graph.config.globalOptions:
  103. writeDepsFile(graph)
  104. proc interactivePasses(graph: ModuleGraph) =
  105. initDefines(graph.config.symbols)
  106. defineSymbol(graph.config.symbols, "nimscript")
  107. # note: seems redundant with -d:nimHasLibFFI
  108. when hasFFI: defineSymbol(graph.config.symbols, "nimffi")
  109. registerPass(graph, verbosePass)
  110. registerPass(graph, semPass)
  111. registerPass(graph, evalPass)
  112. proc commandInteractive(graph: ModuleGraph) =
  113. graph.config.setErrorMaxHighMaybe
  114. interactivePasses(graph)
  115. compileSystemModule(graph)
  116. if graph.config.commandArgs.len > 0:
  117. discard graph.compileModule(fileInfoIdx(graph.config, graph.config.projectFull), {})
  118. else:
  119. var m = graph.makeStdinModule()
  120. incl(m.flags, sfMainModule)
  121. processModule(graph, m, llStreamOpenStdIn())
  122. const evalPasses = [verbosePass, semPass, evalPass]
  123. proc evalNim(graph: ModuleGraph; nodes: PNode, module: PSym) =
  124. carryPasses(graph, nodes, module, evalPasses)
  125. proc commandScan(cache: IdentCache, config: ConfigRef) =
  126. var f = addFileExt(AbsoluteFile mainCommandArg(config), NimExt)
  127. var stream = llStreamOpen(f, fmRead)
  128. if stream != nil:
  129. var
  130. L: Lexer
  131. tok: Token
  132. initToken(tok)
  133. openLexer(L, f, stream, cache, config)
  134. while true:
  135. rawGetTok(L, tok)
  136. printTok(config, tok)
  137. if tok.tokType == tkEof: break
  138. closeLexer(L)
  139. else:
  140. rawMessage(config, errGenerated, "cannot open file: " & f.string)
  141. const
  142. PrintRopeCacheStats = false
  143. proc mainCommand*(graph: ModuleGraph) =
  144. let conf = graph.config
  145. let cache = graph.cache
  146. setupModuleCache(graph)
  147. # In "nim serve" scenario, each command must reset the registered passes
  148. clearPasses(graph)
  149. conf.lastCmdTime = epochTime()
  150. conf.searchPaths.add(conf.libpath)
  151. setId(100)
  152. proc customizeForBackend(backend: TBackend) =
  153. ## Sets backend specific options but don't compile to backend yet in
  154. ## case command doesn't require it. This must be called by all commands.
  155. if conf.backend == backendInvalid:
  156. # only set if wasn't already set, to allow override via `nim c -b:cpp`
  157. conf.backend = backend
  158. defineSymbol(graph.config.symbols, $conf.backend)
  159. case conf.backend
  160. of backendC:
  161. if conf.exc == excNone: conf.exc = excSetjmp
  162. of backendCpp:
  163. if conf.exc == excNone: conf.exc = excCpp
  164. of backendObjc: discard
  165. of backendJs:
  166. if conf.hcrOn:
  167. # XXX: At the moment, system.nim cannot be compiled in JS mode
  168. # with "-d:useNimRtl". The HCR option has been processed earlier
  169. # and it has added this define implictly, so we must undo that here.
  170. # A better solution might be to fix system.nim
  171. undefSymbol(conf.symbols, "useNimRtl")
  172. of backendInvalid: doAssert false
  173. if conf.selectedGC in {gcArc, gcOrc} and conf.backend != backendCpp:
  174. conf.exc = excGoto
  175. var commandAlreadyProcessed = false
  176. proc compileToBackend(backend: TBackend, cmd = cmdCompileToBackend) =
  177. commandAlreadyProcessed = true
  178. conf.cmd = cmd
  179. customizeForBackend(backend)
  180. case conf.backend
  181. of backendC: commandCompileToC(graph)
  182. of backendCpp: commandCompileToC(graph)
  183. of backendObjc: commandCompileToC(graph)
  184. of backendJs: commandCompileToJS(graph)
  185. of backendInvalid: doAssert false
  186. template docLikeCmd(body) =
  187. when defined(leanCompiler):
  188. quit "compiler wasn't built with documentation generator"
  189. else:
  190. wantMainModule(conf)
  191. conf.cmd = cmdDoc
  192. loadConfigs(DocConfig, cache, conf)
  193. defineSymbol(conf.symbols, "nimdoc")
  194. body
  195. block: ## command prepass
  196. var docLikeCmd2 = false # includes what calls `docLikeCmd` + some more
  197. case conf.command.normalize
  198. of "r": conf.globalOptions.incl {optRun, optUseNimcache}
  199. of "doc0", "doc2", "doc", "rst2html", "rst2tex", "jsondoc0", "jsondoc2",
  200. "jsondoc", "ctags", "buildindex": docLikeCmd2 = true
  201. else: discard
  202. if conf.outDir.isEmpty:
  203. # doc like commands can generate a lot of files (especially with --project)
  204. # so by default should not end up in $PWD nor in $projectPath.
  205. conf.outDir = block:
  206. var ret = if optUseNimcache in conf.globalOptions: getNimcacheDir(conf)
  207. else: conf.projectPath
  208. doAssert ret.string.isAbsolute # `AbsoluteDir` is not a real guarantee
  209. if docLikeCmd2: ret = ret / htmldocsDir
  210. ret
  211. ## process all backend commands
  212. case conf.command.normalize
  213. of "c", "cc", "compile", "compiletoc": compileToBackend(backendC) # compile means compileToC currently
  214. of "cpp", "compiletocpp": compileToBackend(backendCpp)
  215. of "objc", "compiletooc": compileToBackend(backendObjc)
  216. of "js", "compiletojs": compileToBackend(backendJs)
  217. of "r": compileToBackend(backendC) # different from `"run"`!
  218. of "run":
  219. when hasTinyCBackend:
  220. extccomp.setCC(conf, "tcc", unknownLineInfo)
  221. if conf.backend notin {backendC, backendInvalid}:
  222. rawMessage(conf, errGenerated, "'run' requires c backend, got: '$1'" % $conf.backend)
  223. compileToBackend(backendC, cmd = cmdRun)
  224. else:
  225. rawMessage(conf, errGenerated, "'run' command not available; rebuild with -d:tinyc")
  226. else: customizeForBackend(backendC) # fallback for other commands
  227. ## process all other commands
  228. case conf.command.normalize # synchronize with `cmdUsingHtmlDocs`
  229. of "doc0": docLikeCmd commandDoc(cache, conf)
  230. of "doc2", "doc":
  231. docLikeCmd():
  232. conf.setNoteDefaults(warnLockLevel, false) # issue #13218
  233. conf.setNoteDefaults(warnRedefinitionOfLabel, false) # issue #13218
  234. # because currently generates lots of false positives due to conflation
  235. # of labels links in doc comments, eg for random.rand:
  236. # ## * `rand proc<#rand,Rand,Natural>`_ that returns an integer
  237. # ## * `rand proc<#rand,Rand,range[]>`_ that returns a float
  238. commandDoc2(graph, false)
  239. if optGenIndex in conf.globalOptions and optWholeProject in conf.globalOptions:
  240. commandBuildIndex(conf, $conf.outDir)
  241. of "rst2html":
  242. conf.setNoteDefaults(warnRedefinitionOfLabel, false) # similar to issue #13218
  243. when defined(leanCompiler):
  244. quit "compiler wasn't built with documentation generator"
  245. else:
  246. conf.cmd = cmdRst2html
  247. loadConfigs(DocConfig, cache, conf)
  248. commandRst2Html(cache, conf)
  249. of "rst2tex":
  250. when defined(leanCompiler):
  251. quit "compiler wasn't built with documentation generator"
  252. else:
  253. conf.cmd = cmdRst2tex
  254. loadConfigs(DocTexConfig, cache, conf)
  255. commandRst2TeX(cache, conf)
  256. of "jsondoc0": docLikeCmd commandJson(cache, conf)
  257. of "jsondoc2", "jsondoc": docLikeCmd commandDoc2(graph, true)
  258. of "ctags": docLikeCmd commandTags(cache, conf)
  259. of "buildindex": docLikeCmd commandBuildIndex(conf, $conf.projectFull, conf.outFile)
  260. of "gendepend":
  261. conf.cmd = cmdGenDepend
  262. commandGenDepend(graph)
  263. of "dump":
  264. conf.cmd = cmdDump
  265. if getConfigVar(conf, "dump.format") == "json":
  266. wantMainModule(conf)
  267. var definedSymbols = newJArray()
  268. for s in definedSymbolNames(conf.symbols): definedSymbols.elems.add(%s)
  269. var libpaths = newJArray()
  270. var lazyPaths = newJArray()
  271. for dir in conf.searchPaths: libpaths.elems.add(%dir.string)
  272. for dir in conf.lazyPaths: lazyPaths.elems.add(%dir.string)
  273. var hints = newJObject() # consider factoring with `listHints`
  274. for a in hintMin..hintMax:
  275. hints[a.msgToStr] = %(a in conf.notes)
  276. var warnings = newJObject()
  277. for a in warnMin..warnMax:
  278. warnings[a.msgToStr] = %(a in conf.notes)
  279. var dumpdata = %[
  280. (key: "version", val: %VersionAsString),
  281. (key: "nimExe", val: %(getAppFilename())),
  282. (key: "prefixdir", val: %conf.getPrefixDir().string),
  283. (key: "libpath", val: %conf.libpath.string),
  284. (key: "project_path", val: %conf.projectFull.string),
  285. (key: "defined_symbols", val: definedSymbols),
  286. (key: "lib_paths", val: %libpaths),
  287. (key: "lazyPaths", val: %lazyPaths),
  288. (key: "outdir", val: %conf.outDir.string),
  289. (key: "out", val: %conf.outFile.string),
  290. (key: "nimcache", val: %getNimcacheDir(conf).string),
  291. (key: "hints", val: hints),
  292. (key: "warnings", val: warnings),
  293. ]
  294. msgWriteln(conf, $dumpdata, {msgStdout, msgSkipHook})
  295. else:
  296. msgWriteln(conf, "-- list of currently defined symbols --",
  297. {msgStdout, msgSkipHook})
  298. for s in definedSymbolNames(conf.symbols): msgWriteln(conf, s, {msgStdout, msgSkipHook})
  299. msgWriteln(conf, "-- end of list --", {msgStdout, msgSkipHook})
  300. for it in conf.searchPaths: msgWriteln(conf, it.string)
  301. of "check":
  302. conf.cmd = cmdCheck
  303. commandCheck(graph)
  304. of "parse":
  305. conf.cmd = cmdParse
  306. wantMainModule(conf)
  307. discard parseFile(conf.projectMainIdx, cache, conf)
  308. of "scan":
  309. conf.cmd = cmdScan
  310. wantMainModule(conf)
  311. commandScan(cache, conf)
  312. msgWriteln(conf, "Beware: Indentation tokens depend on the parser's state!")
  313. of "secret":
  314. conf.cmd = cmdInteractive
  315. commandInteractive(graph)
  316. of "e":
  317. if not fileExists(conf.projectFull):
  318. rawMessage(conf, errGenerated, "NimScript file does not exist: " & conf.projectFull.string)
  319. elif not conf.projectFull.string.endsWith(".nims"):
  320. rawMessage(conf, errGenerated, "not a NimScript file: " & conf.projectFull.string)
  321. # main NimScript logic handled in cmdlinehelper.nim.
  322. of "nop", "help":
  323. # prevent the "success" message:
  324. conf.cmd = cmdDump
  325. of "jsonscript":
  326. conf.cmd = cmdJsonScript
  327. setOutFile(graph.config)
  328. commandJsonScript(graph)
  329. elif commandAlreadyProcessed: discard # already handled
  330. else:
  331. rawMessage(conf, errGenerated, "invalid command: " & conf.command)
  332. if conf.errorCounter == 0 and
  333. conf.cmd notin {cmdInterpret, cmdRun, cmdDump}:
  334. let mem =
  335. when declared(system.getMaxMem): formatSize(getMaxMem()) & " peakmem"
  336. else: formatSize(getTotalMem()) & " totmem"
  337. let loc = $conf.linesCompiled
  338. let build = if isDefined(conf, "danger"): "Dangerous Release"
  339. elif isDefined(conf, "release"): "Release"
  340. else: "Debug"
  341. let sec = formatFloat(epochTime() - conf.lastCmdTime, ffDecimal, 3)
  342. let project = if optListFullPaths in conf.globalOptions: $conf.projectFull else: $conf.projectName
  343. var output: string
  344. if optCompileOnly in conf.globalOptions and conf.cmd != cmdJsonScript:
  345. output = $conf.jsonBuildFile
  346. elif conf.outFile.isEmpty and conf.cmd notin {cmdJsonScript, cmdCompileToBackend, cmdDoc}:
  347. # for some cmd we expect a valid absOutFile
  348. output = "unknownOutput"
  349. else:
  350. output = $conf.absOutFile
  351. if optListFullPaths notin conf.globalOptions: output = output.AbsoluteFile.extractFilename
  352. if optProfileVM in conf.globalOptions:
  353. echo conf.dump(conf.vmProfileData)
  354. rawMessage(conf, hintSuccessX, [
  355. "loc", loc,
  356. "sec", sec,
  357. "mem", mem,
  358. "build", build,
  359. "project", project,
  360. "output", output,
  361. ])
  362. when PrintRopeCacheStats:
  363. echo "rope cache stats: "
  364. echo " tries : ", gCacheTries
  365. echo " misses: ", gCacheMisses
  366. echo " int tries: ", gCacheIntTries
  367. echo " efficiency: ", formatFloat(1-(gCacheMisses.float/gCacheTries.float),
  368. ffDecimal, 3)