koch.nim 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. #
  2. #
  3. # Maintenance program for Nim
  4. # (c) Copyright 2017 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. # See doc/koch.txt for documentation.
  10. #
  11. when defined(gcc) and defined(windows):
  12. when defined(x86):
  13. {.link: "icons/koch.res".}
  14. else:
  15. {.link: "icons/koch_icon.o".}
  16. when defined(amd64) and defined(windows) and defined(vcc):
  17. {.link: "icons/koch-amd64-windows-vcc.res".}
  18. when defined(i386) and defined(windows) and defined(vcc):
  19. {.link: "icons/koch-i386-windows-vcc.res".}
  20. import
  21. os, strutils, parseopt, osproc, streams
  22. import tools / kochdocs
  23. const VersionAsString = system.NimVersion
  24. const
  25. HelpText = """
  26. +-----------------------------------------------------------------+
  27. | Maintenance program for Nim |
  28. | Version $1|
  29. | (c) 2017 Andreas Rumpf |
  30. +-----------------------------------------------------------------+
  31. Build time: $2, $3
  32. Usage:
  33. koch [options] command [options for command]
  34. Options:
  35. --help, -h shows this help and quits
  36. --latest bundle the installers with a bleeding edge Nimble
  37. --stable bundle the installers with a stable Nimble
  38. Possible Commands:
  39. boot [options] bootstraps with given command line options
  40. distrohelper [bindir] helper for distro packagers
  41. tools builds Nim related tools
  42. nimble builds the Nimble tool
  43. Boot options:
  44. -d:release produce a release version of the compiler
  45. -d:useLinenoise use the linenoise library for interactive mode
  46. (not needed on Windows)
  47. Commands for core developers:
  48. docs [options] generates the full documentation
  49. csource -d:release builds the C sources for installation
  50. pdf builds the PDF documentation
  51. zip builds the installation zip package
  52. xz builds the installation tar.xz package
  53. testinstall test tar.xz package; Unix only!
  54. tests [options] run the testsuite (run a subset of tests by
  55. specifying a category, e.g. `tests cat async`)
  56. temp options creates a temporary compiler for testing
  57. pushcsource push generated C sources to its repo
  58. Web options:
  59. --googleAnalytics:UA-... add the given google analytics code to the docs. To
  60. build the official docs, use UA-48159761-1
  61. """
  62. template withDir(dir, body) =
  63. let old = getCurrentDir()
  64. try:
  65. setCurrentDir(dir)
  66. body
  67. finally:
  68. setCurrentdir(old)
  69. proc tryExec(cmd: string): bool =
  70. echo(cmd)
  71. result = execShellCmd(cmd) == 0
  72. proc safeRemove(filename: string) =
  73. if existsFile(filename): removeFile(filename)
  74. proc overwriteFile(source, dest: string) =
  75. safeRemove(dest)
  76. moveFile(source, dest)
  77. proc copyExe(source, dest: string) =
  78. safeRemove(dest)
  79. copyFile(dest=dest, source=source)
  80. inclFilePermissions(dest, {fpUserExec})
  81. const
  82. compileNimInst = "tools/niminst/niminst"
  83. proc csource(args: string) =
  84. nimexec(("cc $1 -r $3 --var:version=$2 --var:mingw=none csource " &
  85. "--main:compiler/nim.nim compiler/installer.ini $1") %
  86. [args, VersionAsString, compileNimInst])
  87. proc bundleNimbleSrc(latest: bool) =
  88. ## bunldeNimbleSrc() bundles a specific Nimble commit with the tarball. We
  89. ## always bundle the latest official release.
  90. if not dirExists("dist/nimble/.git"):
  91. exec("git clone https://github.com/nim-lang/nimble.git dist/nimble")
  92. if not latest:
  93. withDir("dist/nimble"):
  94. exec("git checkout -f stable")
  95. exec("git pull")
  96. proc bundleNimbleExe(latest: bool) =
  97. bundleNimbleSrc(latest)
  98. # now compile Nimble and copy it to $nim/bin for the installer.ini
  99. # to pick it up:
  100. nimexec("c -d:release --nilseqs:on dist/nimble/src/nimble.nim")
  101. copyExe("dist/nimble/src/nimble".exe, "bin/nimble".exe)
  102. proc buildNimble(latest: bool) =
  103. # old installations created nim/nimblepkg/*.nim files. We remove these
  104. # here so that it cannot cause problems (nimble bug #306):
  105. if dirExists("bin/nimblepkg"):
  106. removeDir("bin/nimblepkg")
  107. # if koch is used for a tar.xz, build the dist/nimble we shipped
  108. # with the tarball:
  109. var installDir = "dist/nimble"
  110. if not latest and dirExists(installDir) and not dirExists("dist/nimble/.git"):
  111. discard "don't do the git dance"
  112. else:
  113. if not dirExists("dist/nimble/.git"):
  114. if dirExists(installDir):
  115. var id = 0
  116. while dirExists("dist/nimble" & $id):
  117. inc id
  118. installDir = "dist/nimble" & $id
  119. exec("git clone https://github.com/nim-lang/nimble.git " & installDir)
  120. withDir(installDir):
  121. if latest:
  122. exec("git checkout -f master")
  123. else:
  124. exec("git checkout -f stable")
  125. exec("git pull")
  126. nimexec("c --noNimblePath -p:compiler --nilseqs:on -d:release " & installDir / "src/nimble.nim")
  127. copyExe(installDir / "src/nimble".exe, "bin/nimble".exe)
  128. proc bundleNimsuggest(buildExe: bool) =
  129. if buildExe:
  130. nimexec("c --noNimblePath -d:release -p:compiler nimsuggest/nimsuggest.nim")
  131. copyExe("nimsuggest/nimsuggest".exe, "bin/nimsuggest".exe)
  132. removeFile("nimsuggest/nimsuggest".exe)
  133. proc buildVccTool() =
  134. nimexec("c -o:bin/vccexe.exe tools/vccenv/vccexe")
  135. proc bundleWinTools() =
  136. nimexec("c tools/finish.nim")
  137. copyExe("tools/finish".exe, "finish".exe)
  138. removeFile("tools/finish".exe)
  139. buildVccTool()
  140. nimexec("c -o:bin/nimgrab.exe -d:ssl tools/nimgrab.nim")
  141. nimexec("c -o:bin/nimgrep.exe tools/nimgrep.nim")
  142. when false:
  143. # not yet a tool worth including
  144. nimexec(r"c --cc:vcc --app:gui -o:bin\downloader.exe -d:ssl --noNimblePath " &
  145. r"--path:..\ui tools\downloader.nim")
  146. proc zip(latest: bool; args: string) =
  147. bundleNimbleExe(latest)
  148. bundleNimsuggest(true)
  149. bundleWinTools()
  150. nimexec("cc -r $2 --var:version=$1 --var:mingw=none --main:compiler/nim.nim scripts compiler/installer.ini" %
  151. [VersionAsString, compileNimInst])
  152. exec("$# --var:version=$# --var:mingw=none --main:compiler/nim.nim zip compiler/installer.ini" %
  153. ["tools/niminst/niminst".exe, VersionAsString])
  154. proc ensureCleanGit() =
  155. let (outp, status) = osproc.execCmdEx("git diff")
  156. if outp.len != 0:
  157. quit "Not a clean git repository; 'git diff' not empty!"
  158. if status != 0:
  159. quit "Not a clean git repository; 'git diff' returned non-zero!"
  160. proc xz(latest: bool; args: string) =
  161. ensureCleanGit()
  162. bundleNimbleSrc(latest)
  163. bundleNimsuggest(false)
  164. nimexec("cc -r $2 --var:version=$1 --var:mingw=none --main:compiler/nim.nim scripts compiler/installer.ini" %
  165. [VersionAsString, compileNimInst])
  166. exec("$# --var:version=$# --var:mingw=none --main:compiler/nim.nim xz compiler/installer.ini" %
  167. ["tools" / "niminst" / "niminst".exe, VersionAsString])
  168. proc buildTool(toolname, args: string) =
  169. nimexec("cc $# $#" % [args, toolname])
  170. copyFile(dest="bin" / splitFile(toolname).name.exe, source=toolname.exe)
  171. proc buildTools(latest: bool) =
  172. nimexec "c --noNimblePath -p:compiler -d:release -o:" & ("bin/nimsuggest".exe) &
  173. " nimsuggest/nimsuggest.nim"
  174. nimexec "c -d:release -o:" & ("bin/nimgrep".exe) & " tools/nimgrep.nim"
  175. when defined(windows): buildVccTool()
  176. nimexec "c -o:" & ("bin/nimpretty".exe) & " nimpretty/nimpretty.nim"
  177. buildNimble(latest)
  178. proc nsis(latest: bool; args: string) =
  179. bundleNimbleExe(latest)
  180. bundleNimsuggest(true)
  181. bundleWinTools()
  182. # make sure we have generated the niminst executables:
  183. buildTool("tools/niminst/niminst", args)
  184. #buildTool("tools/nimgrep", args)
  185. # produce 'nim_debug.exe':
  186. #exec "nim c compiler" / "nim.nim"
  187. #copyExe("compiler/nim".exe, "bin/nim_debug".exe)
  188. exec(("tools" / "niminst" / "niminst --var:version=$# --var:mingw=mingw$#" &
  189. " nsis compiler/installer.ini") % [VersionAsString, $(sizeof(pointer)*8)])
  190. proc geninstall(args="") =
  191. nimexec("cc -r $# --var:version=$# --var:mingw=none --main:compiler/nim.nim scripts compiler/installer.ini $#" %
  192. [compileNimInst, VersionAsString, args])
  193. proc install(args: string) =
  194. geninstall()
  195. exec("sh ./install.sh $#" % args)
  196. when false:
  197. proc web(args: string) =
  198. nimexec("js tools/dochack/dochack.nim")
  199. nimexec("cc -r tools/nimweb.nim $# web/website.ini --putenv:nimversion=$#" %
  200. [args, VersionAsString])
  201. proc website(args: string) =
  202. nimexec("cc -r tools/nimweb.nim $# --website web/website.ini --putenv:nimversion=$#" %
  203. [args, VersionAsString])
  204. proc pdf(args="") =
  205. exec("$# cc -r tools/nimweb.nim $# --pdf web/website.ini --putenv:nimversion=$#" %
  206. [findNim(), args, VersionAsString], additionalPATH=findNim().splitFile.dir)
  207. # -------------- boot ---------------------------------------------------------
  208. proc findStartNim: string =
  209. # we try several things before giving up:
  210. # * bin/nim
  211. # * $PATH/nim
  212. # If these fail, we try to build nim with the "build.(sh|bat)" script.
  213. var nim = "nim".exe
  214. result = "bin" / nim
  215. if existsFile(result): return
  216. for dir in split(getEnv("PATH"), PathSep):
  217. if existsFile(dir / nim): return dir / nim
  218. when defined(Posix):
  219. const buildScript = "build.sh"
  220. if existsFile(buildScript):
  221. if tryExec("./" & buildScript): return "bin" / nim
  222. else:
  223. const buildScript = "build.bat"
  224. if existsFile(buildScript):
  225. if tryExec(buildScript): return "bin" / nim
  226. echo("Found no nim compiler and every attempt to build one failed!")
  227. quit("FAILURE")
  228. proc thVersion(i: int): string =
  229. result = ("compiler" / "nim" & $i).exe
  230. proc boot(args: string) =
  231. var output = "compiler" / "nim".exe
  232. var finalDest = "bin" / "nim".exe
  233. # default to use the 'c' command:
  234. let defaultCommand = if getEnv("NIM_COMPILE_TO_CPP", "false") == "true": "cpp" else: "c"
  235. let bootOptions = if args.len == 0 or args.startsWith("-"): defaultCommand else: ""
  236. let smartNimcache = (if "release" in args: "nimcache/r_" else: "nimcache/d_") &
  237. hostOs & "_" & hostCpu
  238. copyExe(findStartNim(), 0.thVersion)
  239. for i in 0..2:
  240. echo "iteration: ", i+1
  241. exec i.thVersion & " $# $# --nimcache:$# compiler" / "nim.nim" % [bootOptions, args,
  242. smartNimcache]
  243. if sameFileContent(output, i.thVersion):
  244. copyExe(output, finalDest)
  245. echo "executables are equal: SUCCESS!"
  246. return
  247. copyExe(output, (i+1).thVersion)
  248. copyExe(output, finalDest)
  249. when not defined(windows): echo "[Warning] executables are still not equal"
  250. # -------------- clean --------------------------------------------------------
  251. const
  252. cleanExt = [
  253. ".ppu", ".o", ".obj", ".dcu", ".~pas", ".~inc", ".~dsk", ".~dpr",
  254. ".map", ".tds", ".err", ".bak", ".pyc", ".exe", ".rod", ".pdb", ".idb",
  255. ".idx", ".ilk"
  256. ]
  257. ignore = [
  258. ".bzrignore", "nim", "nim.exe", "koch", "koch.exe", ".gitignore"
  259. ]
  260. proc cleanAux(dir: string) =
  261. for kind, path in walkDir(dir):
  262. case kind
  263. of pcFile:
  264. var (_, name, ext) = splitFile(path)
  265. if ext == "" or cleanExt.contains(ext):
  266. if not ignore.contains(name):
  267. echo "removing: ", path
  268. removeFile(path)
  269. of pcDir:
  270. case splitPath(path).tail
  271. of "nimcache":
  272. echo "removing dir: ", path
  273. removeDir(path)
  274. of "dist", ".git", "icons": discard
  275. else: cleanAux(path)
  276. else: discard
  277. proc removePattern(pattern: string) =
  278. for f in walkFiles(pattern):
  279. echo "removing: ", f
  280. removeFile(f)
  281. proc clean(args: string) =
  282. removePattern("web/*.html")
  283. removePattern("doc/*.html")
  284. cleanAux(getCurrentDir())
  285. for kind, path in walkDir(getCurrentDir() / "build"):
  286. if kind == pcDir:
  287. echo "removing dir: ", path
  288. removeDir(path)
  289. # -------------- builds a release ---------------------------------------------
  290. proc winReleaseArch(arch: string) =
  291. doAssert arch in ["32", "64"]
  292. let cpu = if arch == "32": "i386" else: "amd64"
  293. template withMingw(path, body) =
  294. let prevPath = getEnv("PATH")
  295. putEnv("PATH", (if path.len > 0: path & PathSep else: "") & prevPath)
  296. try:
  297. body
  298. finally:
  299. putEnv("PATH", prevPath)
  300. withMingw r"..\mingw" & arch & r"\bin":
  301. # Rebuilding koch is necessary because it uses its pointer size to
  302. # determine which mingw link to put in the NSIS installer.
  303. nimexec "c --cpu:$# koch" % cpu
  304. exec "koch boot -d:release --cpu:$#" % cpu
  305. exec "koch --latest zip -d:release"
  306. overwriteFile r"build\nim-$#.zip" % VersionAsString,
  307. r"web\upload\download\nim-$#_x$#.zip" % [VersionAsString, arch]
  308. proc winRelease*() =
  309. # Now used from "tools/winrelease" and not directly supported by koch
  310. # anymore!
  311. # Build -docs file:
  312. when true:
  313. buildDocs(gaCode)
  314. withDir "web/upload/" & VersionAsString:
  315. exec "7z a -tzip docs-$#.zip *.html" % VersionAsString
  316. overwriteFile "web/upload/$1/docs-$1.zip" % VersionAsString,
  317. "web/upload/download/docs-$1.zip" % VersionAsString
  318. when true:
  319. csource("-d:release")
  320. when sizeof(pointer) == 4:
  321. winReleaseArch "32"
  322. when sizeof(pointer) == 8:
  323. winReleaseArch "64"
  324. # -------------- tests --------------------------------------------------------
  325. template `|`(a, b): string = (if a.len > 0: a else: b)
  326. proc tests(args: string) =
  327. # we compile the tester with taintMode:on to have a basic
  328. # taint mode test :-)
  329. nimexec "cc --taintMode:on --opt:speed testament/tester"
  330. # Since tests take a long time (on my machine), and we want to defy Murhpys
  331. # law - lets make sure the compiler really is freshly compiled!
  332. nimexec "c --lib:lib -d:release --opt:speed compiler/nim.nim"
  333. let tester = quoteShell(getCurrentDir() / "testament/tester".exe)
  334. let success = tryExec tester & " " & (args|"all")
  335. if not existsEnv("TRAVIS") and not existsEnv("APPVEYOR"):
  336. exec tester & " html"
  337. if not success:
  338. quit("tests failed", QuitFailure)
  339. proc temp(args: string) =
  340. proc splitArgs(a: string): (string, string) =
  341. # every --options before the command (indicated by starting
  342. # with not a dash) is part of the bootArgs, the rest is part
  343. # of the programArgs:
  344. let args = os.parseCmdLine a
  345. result = ("", "")
  346. var i = 0
  347. while i < args.len and args[i][0] == '-':
  348. result[0].add " " & quoteShell(args[i])
  349. inc i
  350. while i < args.len:
  351. result[1].add " " & quoteShell(args[i])
  352. inc i
  353. let d = getAppDir()
  354. var output = d / "compiler" / "nim".exe
  355. var finalDest = d / "bin" / "nim_temp".exe
  356. # 125 is the magic number to tell git bisect to skip the current
  357. # commit.
  358. let (bootArgs, programArgs) = splitArgs(args)
  359. let nimexec = findNim()
  360. exec(nimexec & " c -d:debug --debugger:native " & bootArgs & " " & (d / "compiler" / "nim"), 125)
  361. copyExe(output, finalDest)
  362. if programArgs.len > 0: exec(finalDest & " " & programArgs)
  363. proc xtemp(cmd: string) =
  364. let d = getAppDir()
  365. copyExe(d / "bin" / "nim".exe, d / "bin" / "nim_backup".exe)
  366. try:
  367. withDir(d):
  368. temp""
  369. copyExe(d / "bin" / "nim_temp".exe, d / "bin" / "nim".exe)
  370. exec(cmd)
  371. finally:
  372. copyExe(d / "bin" / "nim_backup".exe, d / "bin" / "nim".exe)
  373. proc pushCsources() =
  374. if not dirExists("../csources/.git"):
  375. quit "[Error] no csources git repository found"
  376. csource("-d:release")
  377. let cwd = getCurrentDir()
  378. try:
  379. copyDir("build/c_code", "../csources/c_code")
  380. copyFile("build/build.sh", "../csources/build.sh")
  381. copyFile("build/build.bat", "../csources/build.bat")
  382. copyFile("build/build64.bat", "../csources/build64.bat")
  383. copyFile("build/makefile", "../csources/makefile")
  384. setCurrentDir("../csources")
  385. for kind, path in walkDir("c_code"):
  386. if kind == pcDir:
  387. exec("git add " & path / "*.c")
  388. exec("git commit -am \"updated csources to version " & NimVersion & "\"")
  389. exec("git push origin master")
  390. exec("git tag -am \"Version $1\" v$1" % NimVersion)
  391. exec("git push origin v$1" % NimVersion)
  392. finally:
  393. setCurrentDir(cwd)
  394. proc testUnixInstall(cmdLineRest: string) =
  395. csource("-d:release " & cmdLineRest)
  396. xz(false, cmdLineRest)
  397. let oldCurrentDir = getCurrentDir()
  398. try:
  399. let destDir = getTempDir()
  400. copyFile("build/nim-$1.tar.xz" % VersionAsString,
  401. destDir / "nim-$1.tar.xz" % VersionAsString)
  402. setCurrentDir(destDir)
  403. execCleanPath("tar -xJf nim-$1.tar.xz" % VersionAsString)
  404. setCurrentDir("nim-$1" % VersionAsString)
  405. execCleanPath("sh build.sh")
  406. # first test: try if './bin/nim --version' outputs something sane:
  407. let output = execProcess("./bin/nim --version").splitLines
  408. if output.len > 0 and output[0].contains(VersionAsString):
  409. echo "Version check: success"
  410. execCleanPath("./bin/nim c koch.nim")
  411. execCleanPath("./koch boot -d:release", destDir / "bin")
  412. # check the docs build:
  413. execCleanPath("./koch docs", destDir / "bin")
  414. # check nimble builds:
  415. execCleanPath("./koch --latest tools")
  416. # check the tests work:
  417. putEnv("NIM_EXE_NOT_IN_PATH", "NOT_IN_PATH")
  418. execCleanPath("./koch tests", destDir / "bin")
  419. #execCleanPath("./koch tests cat newconfig", destDir / "bin")
  420. else:
  421. echo "Version check: failure"
  422. finally:
  423. setCurrentDir oldCurrentDir
  424. proc valgrind(cmd: string) =
  425. # somewhat hacky: '=' sign means "pass to valgrind" else "pass to Nim"
  426. let args = parseCmdLine(cmd)
  427. var nimcmd = ""
  428. var valcmd = ""
  429. for i, a in args:
  430. if i == args.len-1:
  431. # last element is the filename:
  432. valcmd.add ' '
  433. valcmd.add changeFileExt(a, ExeExt)
  434. nimcmd.add ' '
  435. nimcmd.add a
  436. elif '=' in a:
  437. valcmd.add ' '
  438. valcmd.add a
  439. else:
  440. nimcmd.add ' '
  441. nimcmd.add a
  442. exec("nim c" & nimcmd)
  443. let supp = getAppDir() / "tools" / "nimgrind.supp"
  444. exec("valgrind --suppressions=" & supp & valcmd)
  445. proc showHelp() =
  446. quit(HelpText % [VersionAsString & spaces(44-len(VersionAsString)),
  447. CompileDate, CompileTime], QuitSuccess)
  448. when isMainModule:
  449. var op = initOptParser()
  450. var latest = false
  451. var stable = false
  452. while true:
  453. op.next()
  454. case op.kind
  455. of cmdLongOption, cmdShortOption:
  456. case normalize(op.key)
  457. of "latest": latest = true
  458. of "stable": stable = true
  459. else: showHelp()
  460. of cmdArgument:
  461. case normalize(op.key)
  462. of "boot": boot(op.cmdLineRest)
  463. of "clean": clean(op.cmdLineRest)
  464. of "doc", "docs": buildDocs(op.cmdLineRest)
  465. of "doc0", "docs0":
  466. # undocumented command for Araq-the-merciful:
  467. buildDocs(op.cmdLineRest & gaCode)
  468. of "pdf": buildPdfDoc(op.cmdLineRest, "doc/pdf")
  469. of "csource", "csources": csource(op.cmdLineRest)
  470. of "zip": zip(latest, op.cmdLineRest)
  471. of "xz": xz(latest, op.cmdLineRest)
  472. of "nsis": nsis(latest, op.cmdLineRest)
  473. of "geninstall": geninstall(op.cmdLineRest)
  474. of "distrohelper": geninstall()
  475. of "install": install(op.cmdLineRest)
  476. of "testinstall": testUnixInstall(op.cmdLineRest)
  477. of "test", "tests": tests(op.cmdLineRest)
  478. of "temp": temp(op.cmdLineRest)
  479. of "xtemp": xtemp(op.cmdLineRest)
  480. of "wintools": bundleWinTools()
  481. of "nimble":
  482. if stable: buildNimble(false)
  483. else: buildNimble(existsDir(".git") or latest)
  484. of "nimsuggest": bundleNimsuggest(buildExe=true)
  485. of "tools":
  486. if stable: buildTools(false)
  487. else: buildTools(existsDir(".git") or latest)
  488. of "pushcsource", "pushcsources": pushCsources()
  489. of "valgrind": valgrind(op.cmdLineRest)
  490. else: showHelp()
  491. break
  492. of cmdEnd: break