koch.nim 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  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. -d:leanCompiler produce a compiler without JS codegen or
  48. documentation generator in order to use less RAM
  49. for bootstrapping
  50. Commands for core developers:
  51. docs [options] generates the full documentation
  52. csource -d:release builds the C sources for installation
  53. pdf builds the PDF documentation
  54. zip builds the installation zip package
  55. xz builds the installation tar.xz package
  56. testinstall test tar.xz package; Unix only!
  57. tests [options] run the testsuite (run a subset of tests by
  58. specifying a category, e.g. `tests cat async`)
  59. temp options creates a temporary compiler for testing
  60. pushcsource push generated C sources to its repo
  61. Web options:
  62. --googleAnalytics:UA-... add the given google analytics code to the docs. To
  63. build the official docs, use UA-48159761-1
  64. """
  65. template withDir(dir, body) =
  66. let old = getCurrentDir()
  67. try:
  68. setCurrentDir(dir)
  69. body
  70. finally:
  71. setCurrentdir(old)
  72. let origDir = getCurrentDir()
  73. setCurrentDir(getAppDir())
  74. proc tryExec(cmd: string): bool =
  75. echo(cmd)
  76. result = execShellCmd(cmd) == 0
  77. proc safeRemove(filename: string) =
  78. if existsFile(filename): removeFile(filename)
  79. proc overwriteFile(source, dest: string) =
  80. safeRemove(dest)
  81. moveFile(source, dest)
  82. proc copyExe(source, dest: string) =
  83. safeRemove(dest)
  84. copyFile(dest=dest, source=source)
  85. inclFilePermissions(dest, {fpUserExec})
  86. const
  87. compileNimInst = "tools/niminst/niminst"
  88. proc csource(args: string) =
  89. nimexec(("cc $1 -r $3 --var:version=$2 --var:mingw=none csource " &
  90. "--main:compiler/nim.nim compiler/installer.ini $1") %
  91. [args, VersionAsString, compileNimInst])
  92. proc bundleNimbleSrc(latest: bool) =
  93. ## bunldeNimbleSrc() bundles a specific Nimble commit with the tarball. We
  94. ## always bundle the latest official release.
  95. if not dirExists("dist/nimble/.git"):
  96. exec("git clone https://github.com/nim-lang/nimble.git dist/nimble")
  97. if not latest:
  98. withDir("dist/nimble"):
  99. exec("git checkout -f stable")
  100. exec("git pull")
  101. proc bundleNimbleExe(latest: bool) =
  102. bundleNimbleSrc(latest)
  103. # installer.ini expects it under $nim/bin
  104. nimCompile("dist/nimble/src/nimble.nim", options = "-d:release --nilseqs:on")
  105. proc buildNimfind() =
  106. nimCompile("tools/nimfind.nim", options = "-d:release")
  107. proc buildNimble(latest: bool) =
  108. # old installations created nim/nimblepkg/*.nim files. We remove these
  109. # here so that it cannot cause problems (nimble bug #306):
  110. if dirExists("bin/nimblepkg"):
  111. removeDir("bin/nimblepkg")
  112. # if koch is used for a tar.xz, build the dist/nimble we shipped
  113. # with the tarball:
  114. var installDir = "dist/nimble"
  115. if not latest and dirExists(installDir) and not dirExists("dist/nimble/.git"):
  116. discard "don't do the git dance"
  117. else:
  118. if not dirExists("dist/nimble/.git"):
  119. if dirExists(installDir):
  120. var id = 0
  121. while dirExists("dist/nimble" & $id):
  122. inc id
  123. installDir = "dist/nimble" & $id
  124. exec("git clone https://github.com/nim-lang/nimble.git " & installDir)
  125. withDir(installDir):
  126. if latest:
  127. exec("git checkout -f master")
  128. else:
  129. exec("git checkout -f stable")
  130. exec("git pull")
  131. nimCompile(installDir / "src/nimble.nim", options = "--noNimblePath --nilseqs:on -d:release")
  132. proc bundleNimsuggest() =
  133. nimCompile("nimsuggest/nimsuggest.nim", options = "-d:release")
  134. proc buildVccTool() =
  135. nimCompile("tools/vccexe/vccexe.nim")
  136. proc bundleWinTools() =
  137. # TODO: consider building under `bin` instead of `.`
  138. nimCompile("tools/finish.nim", outputDir = "")
  139. buildVccTool()
  140. nimCompile("tools/nimgrab.nim", options = "-d:ssl")
  141. nimCompile("tools/nimgrep.nim")
  142. when false:
  143. # not yet a tool worth including
  144. nimCompile(r"tools\downloader.nim", options = r"--cc:vcc --app:gui -d:ssl --noNimblePath --path:..\ui")
  145. proc zip(latest: bool; args: string) =
  146. bundleNimbleExe(latest)
  147. bundleNimsuggest()
  148. bundleWinTools()
  149. nimexec("cc -r $2 --var:version=$1 --var:mingw=none --main:compiler/nim.nim scripts compiler/installer.ini" %
  150. [VersionAsString, compileNimInst])
  151. exec("$# --var:version=$# --var:mingw=none --main:compiler/nim.nim zip compiler/installer.ini" %
  152. ["tools/niminst/niminst".exe, VersionAsString])
  153. proc ensureCleanGit() =
  154. let (outp, status) = osproc.execCmdEx("git diff")
  155. if outp.len != 0:
  156. quit "Not a clean git repository; 'git diff' not empty!"
  157. if status != 0:
  158. quit "Not a clean git repository; 'git diff' returned non-zero!"
  159. proc xz(latest: bool; args: string) =
  160. ensureCleanGit()
  161. bundleNimbleSrc(latest)
  162. when false:
  163. bundleNimsuggest()
  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. bundleNimsuggest()
  173. nimCompile("tools/nimgrep.nim", options = "-d:release")
  174. when defined(windows): buildVccTool()
  175. nimCompile("nimpretty/nimpretty.nim", options = "-d:release")
  176. buildNimble(latest)
  177. buildNimfind()
  178. proc nsis(latest: bool; args: string) =
  179. bundleNimbleExe(latest)
  180. bundleNimsuggest()
  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. nimexec "cc --opt:speed testament/tester"
  328. # Since tests take a long time (on my machine), and we want to defy Murhpys
  329. # law - lets make sure the compiler really is freshly compiled!
  330. nimexec "c --lib:lib -d:release --opt:speed compiler/nim.nim"
  331. let tester = quoteShell(getCurrentDir() / "testament/tester".exe)
  332. let success = tryExec tester & " " & (args|"all")
  333. if not existsEnv("TRAVIS") and not existsEnv("APPVEYOR"):
  334. exec tester & " html"
  335. if not success:
  336. quit("tests failed", QuitFailure)
  337. proc temp(args: string) =
  338. proc splitArgs(a: string): (string, string) =
  339. # every --options before the command (indicated by starting
  340. # with not a dash) is part of the bootArgs, the rest is part
  341. # of the programArgs:
  342. let args = os.parseCmdLine a
  343. result = ("", "")
  344. var i = 0
  345. while i < args.len and args[i][0] == '-':
  346. result[0].add " " & quoteShell(args[i])
  347. inc i
  348. while i < args.len:
  349. result[1].add " " & quoteShell(args[i])
  350. inc i
  351. let d = getAppDir()
  352. var output = d / "compiler" / "nim".exe
  353. var finalDest = d / "bin" / "nim_temp".exe
  354. # 125 is the magic number to tell git bisect to skip the current
  355. # commit.
  356. let (bootArgs, programArgs) = splitArgs(args)
  357. let nimexec = findNim()
  358. exec(nimexec & " c -d:debug --debugger:native " & bootArgs & " " & (d / "compiler" / "nim"), 125)
  359. copyExe(output, finalDest)
  360. setCurrentDir(origDir)
  361. if programArgs.len > 0: exec(finalDest & " " & programArgs)
  362. proc xtemp(cmd: string) =
  363. let d = getAppDir()
  364. copyExe(d / "bin" / "nim".exe, d / "bin" / "nim_backup".exe)
  365. try:
  366. withDir(d):
  367. temp""
  368. copyExe(d / "bin" / "nim_temp".exe, d / "bin" / "nim".exe)
  369. exec(cmd)
  370. finally:
  371. copyExe(d / "bin" / "nim_backup".exe, d / "bin" / "nim".exe)
  372. proc pushCsources() =
  373. if not dirExists("../csources/.git"):
  374. quit "[Error] no csources git repository found"
  375. csource("-d:release")
  376. let cwd = getCurrentDir()
  377. try:
  378. copyDir("build/c_code", "../csources/c_code")
  379. copyFile("build/build.sh", "../csources/build.sh")
  380. copyFile("build/build.bat", "../csources/build.bat")
  381. copyFile("build/build64.bat", "../csources/build64.bat")
  382. copyFile("build/makefile", "../csources/makefile")
  383. setCurrentDir("../csources")
  384. for kind, path in walkDir("c_code"):
  385. if kind == pcDir:
  386. exec("git add " & path / "*.c")
  387. exec("git commit -am \"updated csources to version " & NimVersion & "\"")
  388. exec("git push origin master")
  389. exec("git tag -am \"Version $1\" v$1" % NimVersion)
  390. exec("git push origin v$1" % NimVersion)
  391. finally:
  392. setCurrentDir(cwd)
  393. proc testUnixInstall(cmdLineRest: string) =
  394. csource("-d:release " & cmdLineRest)
  395. xz(false, cmdLineRest)
  396. let oldCurrentDir = getCurrentDir()
  397. try:
  398. let destDir = getTempDir()
  399. copyFile("build/nim-$1.tar.xz" % VersionAsString,
  400. destDir / "nim-$1.tar.xz" % VersionAsString)
  401. setCurrentDir(destDir)
  402. execCleanPath("tar -xJf nim-$1.tar.xz" % VersionAsString)
  403. setCurrentDir("nim-$1" % VersionAsString)
  404. execCleanPath("sh build.sh")
  405. # first test: try if './bin/nim --version' outputs something sane:
  406. let output = execProcess("./bin/nim --version").splitLines
  407. if output.len > 0 and output[0].contains(VersionAsString):
  408. echo "Version check: success"
  409. execCleanPath("./bin/nim c koch.nim")
  410. execCleanPath("./koch boot -d:release", destDir / "bin")
  411. # check the docs build:
  412. execCleanPath("./koch docs", destDir / "bin")
  413. # check nimble builds:
  414. execCleanPath("./koch --latest tools")
  415. # check the tests work:
  416. putEnv("NIM_EXE_NOT_IN_PATH", "NOT_IN_PATH")
  417. execCleanPath("./koch tests cat megatest", destDir / "bin")
  418. else:
  419. echo "Version check: failure"
  420. finally:
  421. setCurrentDir oldCurrentDir
  422. proc valgrind(cmd: string) =
  423. # somewhat hacky: '=' sign means "pass to valgrind" else "pass to Nim"
  424. let args = parseCmdLine(cmd)
  425. var nimcmd = ""
  426. var valcmd = ""
  427. for i, a in args:
  428. if i == args.len-1:
  429. # last element is the filename:
  430. valcmd.add ' '
  431. valcmd.add changeFileExt(a, ExeExt)
  432. nimcmd.add ' '
  433. nimcmd.add a
  434. elif '=' in a:
  435. valcmd.add ' '
  436. valcmd.add a
  437. else:
  438. nimcmd.add ' '
  439. nimcmd.add a
  440. exec("nim c" & nimcmd)
  441. let supp = getAppDir() / "tools" / "nimgrind.supp"
  442. exec("valgrind --suppressions=" & supp & valcmd)
  443. proc showHelp() =
  444. quit(HelpText % [VersionAsString & spaces(44-len(VersionAsString)),
  445. CompileDate, CompileTime], QuitSuccess)
  446. when isMainModule:
  447. var op = initOptParser()
  448. var latest = false
  449. var stable = false
  450. while true:
  451. op.next()
  452. case op.kind
  453. of cmdLongOption, cmdShortOption:
  454. case normalize(op.key)
  455. of "latest": latest = true
  456. of "stable": stable = true
  457. else: showHelp()
  458. of cmdArgument:
  459. case normalize(op.key)
  460. of "boot": boot(op.cmdLineRest)
  461. of "clean": clean(op.cmdLineRest)
  462. of "doc", "docs": buildDocs(op.cmdLineRest)
  463. of "doc0", "docs0":
  464. # undocumented command for Araq-the-merciful:
  465. buildDocs(op.cmdLineRest & gaCode)
  466. of "pdf": buildPdfDoc(op.cmdLineRest, "doc/pdf")
  467. of "csource", "csources": csource(op.cmdLineRest)
  468. of "zip": zip(latest, op.cmdLineRest)
  469. of "xz": xz(latest, op.cmdLineRest)
  470. of "nsis": nsis(latest, op.cmdLineRest)
  471. of "geninstall": geninstall(op.cmdLineRest)
  472. of "distrohelper": geninstall()
  473. of "install": install(op.cmdLineRest)
  474. of "testinstall": testUnixInstall(op.cmdLineRest)
  475. of "test", "tests": tests(op.cmdLineRest)
  476. of "temp": temp(op.cmdLineRest)
  477. of "xtemp": xtemp(op.cmdLineRest)
  478. of "wintools": bundleWinTools()
  479. of "nimble":
  480. if stable: buildNimble(false)
  481. else: buildNimble(existsDir(".git") or latest)
  482. of "nimsuggest": bundleNimsuggest()
  483. of "tools":
  484. if stable: buildTools(false)
  485. else: buildTools(existsDir(".git") or latest)
  486. of "pushcsource", "pushcsources": pushCsources()
  487. of "valgrind": valgrind(op.cmdLineRest)
  488. else: showHelp()
  489. break
  490. of cmdEnd: break