main.nim 17 KB

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