main.nim 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  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,
  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. var idgen = IdGenerator(module: m.itemId.module, item: m.itemId.item)
  122. processModule(graph, m, idgen, llStreamOpenStdIn())
  123. proc commandScan(cache: IdentCache, config: ConfigRef) =
  124. var f = addFileExt(AbsoluteFile mainCommandArg(config), NimExt)
  125. var stream = llStreamOpen(f, fmRead)
  126. if stream != nil:
  127. var
  128. L: Lexer
  129. tok: Token
  130. initToken(tok)
  131. openLexer(L, f, stream, cache, config)
  132. while true:
  133. rawGetTok(L, tok)
  134. printTok(config, tok)
  135. if tok.tokType == tkEof: break
  136. closeLexer(L)
  137. else:
  138. rawMessage(config, errGenerated, "cannot open file: " & f.string)
  139. const
  140. PrintRopeCacheStats = false
  141. proc mainCommand*(graph: ModuleGraph) =
  142. let conf = graph.config
  143. let cache = graph.cache
  144. setupModuleCache(graph)
  145. # In "nim serve" scenario, each command must reset the registered passes
  146. clearPasses(graph)
  147. conf.lastCmdTime = epochTime()
  148. conf.searchPaths.add(conf.libpath)
  149. proc customizeForBackend(backend: TBackend) =
  150. ## Sets backend specific options but don't compile to backend yet in
  151. ## case command doesn't require it. This must be called by all commands.
  152. if conf.backend == backendInvalid:
  153. # only set if wasn't already set, to allow override via `nim c -b:cpp`
  154. conf.backend = backend
  155. defineSymbol(graph.config.symbols, $conf.backend)
  156. case conf.backend
  157. of backendC:
  158. if conf.exc == excNone: conf.exc = excSetjmp
  159. of backendCpp:
  160. if conf.exc == excNone: conf.exc = excCpp
  161. of backendObjc: discard
  162. of backendJs:
  163. if conf.hcrOn:
  164. # XXX: At the moment, system.nim cannot be compiled in JS mode
  165. # with "-d:useNimRtl". The HCR option has been processed earlier
  166. # and it has added this define implictly, so we must undo that here.
  167. # A better solution might be to fix system.nim
  168. undefSymbol(conf.symbols, "useNimRtl")
  169. of backendInvalid: doAssert false
  170. if conf.selectedGC in {gcArc, gcOrc} and conf.backend != backendCpp:
  171. conf.exc = excGoto
  172. var commandAlreadyProcessed = false
  173. proc compileToBackend(backend: TBackend, cmd = cmdCompileToBackend) =
  174. commandAlreadyProcessed = true
  175. conf.cmd = cmd
  176. customizeForBackend(backend)
  177. case conf.backend
  178. of backendC: commandCompileToC(graph)
  179. of backendCpp: commandCompileToC(graph)
  180. of backendObjc: commandCompileToC(graph)
  181. of backendJs: commandCompileToJS(graph)
  182. of backendInvalid: doAssert false
  183. template docLikeCmd(body) =
  184. when defined(leanCompiler):
  185. quit "compiler wasn't built with documentation generator"
  186. else:
  187. wantMainModule(conf)
  188. conf.cmd = cmdDoc
  189. loadConfigs(DocConfig, cache, conf, graph.idgen)
  190. defineSymbol(conf.symbols, "nimdoc")
  191. body
  192. block: ## command prepass
  193. var docLikeCmd2 = false # includes what calls `docLikeCmd` + some more
  194. case conf.command.normalize
  195. of "r": conf.globalOptions.incl {optRun, optUseNimcache}
  196. of "doc0", "doc2", "doc", "rst2html", "rst2tex", "jsondoc0", "jsondoc2",
  197. "jsondoc", "ctags", "buildindex": docLikeCmd2 = true
  198. else: discard
  199. if conf.outDir.isEmpty:
  200. # doc like commands can generate a lot of files (especially with --project)
  201. # so by default should not end up in $PWD nor in $projectPath.
  202. conf.outDir = block:
  203. var ret = if optUseNimcache in conf.globalOptions: getNimcacheDir(conf)
  204. else: conf.projectPath
  205. doAssert ret.string.isAbsolute # `AbsoluteDir` is not a real guarantee
  206. if docLikeCmd2: ret = ret / htmldocsDir
  207. ret
  208. ## process all backend commands
  209. case conf.command.normalize
  210. of "c", "cc", "compile", "compiletoc": compileToBackend(backendC) # compile means compileToC currently
  211. of "cpp", "compiletocpp": compileToBackend(backendCpp)
  212. of "objc", "compiletooc": compileToBackend(backendObjc)
  213. of "js", "compiletojs": compileToBackend(backendJs)
  214. of "r": compileToBackend(backendC) # different from `"run"`!
  215. of "run":
  216. when hasTinyCBackend:
  217. extccomp.setCC(conf, "tcc", unknownLineInfo)
  218. if conf.backend notin {backendC, backendInvalid}:
  219. rawMessage(conf, errGenerated, "'run' requires c backend, got: '$1'" % $conf.backend)
  220. compileToBackend(backendC, cmd = cmdRun)
  221. else:
  222. rawMessage(conf, errGenerated, "'run' command not available; rebuild with -d:tinyc")
  223. else: customizeForBackend(backendC) # fallback for other commands
  224. ## process all other commands
  225. case conf.command.normalize # synchronize with `cmdUsingHtmlDocs`
  226. of "doc0": docLikeCmd commandDoc(cache, conf)
  227. of "doc2", "doc":
  228. docLikeCmd():
  229. conf.setNoteDefaults(warnLockLevel, false) # issue #13218
  230. conf.setNoteDefaults(warnRedefinitionOfLabel, false) # issue #13218
  231. # because currently generates lots of false positives due to conflation
  232. # of labels links in doc comments, e.g. for random.rand:
  233. # ## * `rand proc<#rand,Rand,Natural>`_ that returns an integer
  234. # ## * `rand proc<#rand,Rand,range[]>`_ that returns a float
  235. commandDoc2(graph, false)
  236. if optGenIndex in conf.globalOptions and optWholeProject in conf.globalOptions:
  237. commandBuildIndex(conf, $conf.outDir)
  238. of "rst2html":
  239. conf.setNoteDefaults(warnRedefinitionOfLabel, false) # similar to issue #13218
  240. when defined(leanCompiler):
  241. quit "compiler wasn't built with documentation generator"
  242. else:
  243. conf.cmd = cmdRst2html
  244. loadConfigs(DocConfig, cache, conf, graph.idgen)
  245. commandRst2Html(cache, conf)
  246. of "rst2tex":
  247. when defined(leanCompiler):
  248. quit "compiler wasn't built with documentation generator"
  249. else:
  250. conf.cmd = cmdRst2tex
  251. loadConfigs(DocTexConfig, cache, conf, graph.idgen)
  252. commandRst2TeX(cache, conf)
  253. of "jsondoc0": docLikeCmd commandJson(cache, conf)
  254. of "jsondoc2", "jsondoc": docLikeCmd commandDoc2(graph, true)
  255. of "ctags": docLikeCmd commandTags(cache, conf)
  256. of "buildindex": docLikeCmd commandBuildIndex(conf, $conf.projectFull, conf.outFile)
  257. of "gendepend":
  258. conf.cmd = cmdGenDepend
  259. commandGenDepend(graph)
  260. of "dump":
  261. conf.cmd = cmdDump
  262. if getConfigVar(conf, "dump.format") == "json":
  263. wantMainModule(conf)
  264. var definedSymbols = newJArray()
  265. for s in definedSymbolNames(conf.symbols): definedSymbols.elems.add(%s)
  266. var libpaths = newJArray()
  267. var lazyPaths = newJArray()
  268. for dir in conf.searchPaths: libpaths.elems.add(%dir.string)
  269. for dir in conf.lazyPaths: lazyPaths.elems.add(%dir.string)
  270. var hints = newJObject() # consider factoring with `listHints`
  271. for a in hintMin..hintMax:
  272. hints[$a] = %(a in conf.notes)
  273. var warnings = newJObject()
  274. for a in warnMin..warnMax:
  275. warnings[$a] = %(a in conf.notes)
  276. var dumpdata = %[
  277. (key: "version", val: %VersionAsString),
  278. (key: "nimExe", val: %(getAppFilename())),
  279. (key: "prefixdir", val: %conf.getPrefixDir().string),
  280. (key: "libpath", val: %conf.libpath.string),
  281. (key: "project_path", val: %conf.projectFull.string),
  282. (key: "defined_symbols", val: definedSymbols),
  283. (key: "lib_paths", val: %libpaths),
  284. (key: "lazyPaths", val: %lazyPaths),
  285. (key: "outdir", val: %conf.outDir.string),
  286. (key: "out", val: %conf.outFile.string),
  287. (key: "nimcache", val: %getNimcacheDir(conf).string),
  288. (key: "hints", val: hints),
  289. (key: "warnings", val: warnings),
  290. ]
  291. msgWriteln(conf, $dumpdata, {msgStdout, msgSkipHook})
  292. else:
  293. msgWriteln(conf, "-- list of currently defined symbols --",
  294. {msgStdout, msgSkipHook})
  295. for s in definedSymbolNames(conf.symbols): msgWriteln(conf, s, {msgStdout, msgSkipHook})
  296. msgWriteln(conf, "-- end of list --", {msgStdout, msgSkipHook})
  297. for it in conf.searchPaths: msgWriteln(conf, it.string)
  298. of "check":
  299. conf.cmd = cmdCheck
  300. commandCheck(graph)
  301. of "parse":
  302. conf.cmd = cmdParse
  303. wantMainModule(conf)
  304. discard parseFile(conf.projectMainIdx, cache, conf)
  305. of "scan":
  306. conf.cmd = cmdScan
  307. wantMainModule(conf)
  308. commandScan(cache, conf)
  309. msgWriteln(conf, "Beware: Indentation tokens depend on the parser's state!")
  310. of "secret":
  311. conf.cmd = cmdInteractive
  312. commandInteractive(graph)
  313. of "e":
  314. if conf.projectIsCmd or conf.projectIsStdin: discard
  315. elif not fileExists(conf.projectFull):
  316. rawMessage(conf, errGenerated, "NimScript file does not exist: " & conf.projectFull.string)
  317. elif not conf.projectFull.string.endsWith(".nims"):
  318. rawMessage(conf, errGenerated, "not a NimScript file: " & conf.projectFull.string)
  319. # main NimScript logic handled in `loadConfigs`.
  320. of "nop", "help":
  321. # prevent the "success" message:
  322. conf.cmd = cmdDump
  323. of "jsonscript":
  324. conf.cmd = cmdJsonScript
  325. setOutFile(graph.config)
  326. commandJsonScript(graph)
  327. elif commandAlreadyProcessed: discard # already handled
  328. else:
  329. rawMessage(conf, errGenerated, "invalid command: " & conf.command)
  330. if conf.errorCounter == 0 and
  331. conf.cmd notin {cmdInterpret, cmdRun, cmdDump}:
  332. let mem =
  333. when declared(system.getMaxMem): formatSize(getMaxMem()) & " peakmem"
  334. else: formatSize(getTotalMem()) & " totmem"
  335. let loc = $conf.linesCompiled
  336. let build = if isDefined(conf, "danger"): "Dangerous Release"
  337. elif isDefined(conf, "release"): "Release"
  338. else: "Debug"
  339. let sec = formatFloat(epochTime() - conf.lastCmdTime, ffDecimal, 3)
  340. let project = if optListFullPaths in conf.globalOptions: $conf.projectFull else: $conf.projectName
  341. var output: string
  342. if optCompileOnly in conf.globalOptions and conf.cmd != cmdJsonScript:
  343. output = $conf.jsonBuildFile
  344. elif conf.outFile.isEmpty and conf.cmd notin {cmdJsonScript, cmdCompileToBackend, cmdDoc}:
  345. # for some cmd we expect a valid absOutFile
  346. output = "unknownOutput"
  347. else:
  348. output = $conf.absOutFile
  349. if optListFullPaths notin conf.globalOptions: output = output.AbsoluteFile.extractFilename
  350. if optProfileVM in conf.globalOptions:
  351. echo conf.dump(conf.vmProfileData)
  352. rawMessage(conf, hintSuccessX, [
  353. "loc", loc,
  354. "sec", sec,
  355. "mem", mem,
  356. "build", build,
  357. "project", project,
  358. "output", output,
  359. ])
  360. when PrintRopeCacheStats:
  361. echo "rope cache stats: "
  362. echo " tries : ", gCacheTries
  363. echo " misses: ", gCacheMisses
  364. echo " int tries: ", gCacheIntTries
  365. echo " efficiency: ", formatFloat(1-(gCacheMisses.float/gCacheTries.float),
  366. ffDecimal, 3)