From 3d20f141939b0cb76079a9b0ae7784f7877f1698 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Wed, 20 May 2020 00:45:34 -0700 Subject: [PATCH] fix #10731 ; `runnableExamples "-b:cpp --run:off": code` works (#14384) * runnableExamples "-b:cpp -r:off": code --- changelog.md | 2 + compiler/docgen.nim | 86 +++++++++++++------- lib/system.nim | 11 ++- nimdoc/tester.nim | 5 +- nimdoc/testproject/expected/testproject.html | 2 +- tests/nimdoc/trunnableexamples.nim | 35 +++++++- 6 files changed, 104 insertions(+), 37 deletions(-) diff --git a/changelog.md b/changelog.md index 88619b1c2c..0e1af73c1b 100644 --- a/changelog.md +++ b/changelog.md @@ -185,6 +185,8 @@ proc mydiv(a, b): int {.raises: [].} = use `--doccmd:skip` to skip runnableExamples and rst test snippets. - new flag `--usenimcache` to output to nimcache (whatever it resolves to after all commands are processed) and avoids polluting both $pwd and $projectdir. It can be used with any command. +- `runnableExamples "-b:cpp -r:off": code` is now supported, allowing to override how an example is compiled and run, + for example to change backend or compile only. ## Tool changes diff --git a/compiler/docgen.nim b/compiler/docgen.nim index 6123d49cfa..8b5201029f 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -17,7 +17,7 @@ import packages/docutils/rst, packages/docutils/rstgen, json, xmltree, cgi, trees, types, typesrenderer, astalgo, lineinfos, intsets, - pathutils, trees + pathutils, trees, tables const exportSection = skField @@ -26,6 +26,12 @@ const type TSections = array[TSymKind, Rope] + ExampleGroup = ref object + ## a group of runnableExamples with same rdoccmd + rdoccmd: string ## from 1st arg in `runnableExamples(rdoccmd): body` + docCmd: string ## from user config, eg --doccmd:-d:foo + code: string ## contains imports; each import contains `body` + index: int ## group index TDocumentor = object of rstgen.RstGenerator modDesc: Rope # module description module: PSym @@ -44,7 +50,7 @@ type # already. See bug #3655 destFile*: AbsoluteFile thisDir*: AbsoluteDir - examples: string + exampleGroups: OrderedTable[string, ExampleGroup] wroteCss*: bool PDoc* = ref TDocumentor ## Alias to type less. @@ -443,54 +449,72 @@ proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRe dispA(d.conf, result, "$1", "\\spanOther{$1}", [escLit]) -proc testExample(d: PDoc; ex: PNode) = +proc exampleOutputDir(d: PDoc): AbsoluteDir = d.conf.getNimcacheDir / RelativeDir"runnableExamples" + +proc writeExample(d: PDoc; ex: PNode, rdoccmd: string) = if d.conf.errorCounter > 0: return - let outputDir = d.conf.getNimcacheDir / RelativeDir"runnableExamples" + let outputDir = d.exampleOutputDir createDir(outputDir) inc d.exampleCounter + # PRTEMP let outp = outputDir / RelativeFile(extractFilename(d.filename.changeFileExt"" & "_examples" & $d.exampleCounter & ".nim")) #let nimcache = outp.changeFileExt"" & "_nimcache" renderModule(ex, d.filename, outp.string, conf = d.conf) - d.examples.add "import r\"" & outp.string & "\"\n" + if rdoccmd notin d.exampleGroups: d.exampleGroups[rdoccmd] = ExampleGroup(rdoccmd: rdoccmd, docCmd: d.conf.docCmd, index: d.exampleGroups.len) + d.exampleGroups[rdoccmd].code.add "import r\"$1\"\n" % outp.string proc runAllExamples(d: PDoc) = - let docCmd = d.conf.docCmd let backend = d.conf.backend # This used to be: `let backend = if isDefined(d.conf, "js"): "js"` (etc), however # using `-d:js` (etc) cannot work properly, eg would fail with `importjs` # since semantics are affected by `config.backend`, not by isDefined(d.conf, "js") - if d.examples.len == 0 or docCmd == docCmdSkip: return - let outputDir = d.conf.getNimcacheDir / RelativeDir"runnableExamples" - let outp = outputDir / RelativeFile(extractFilename(d.filename.changeFileExt"" & - "_examples.nim")) - writeFile(outp, d.examples) - let cmd = "$nim $backend -r --warning:UnusedImport:off --path:$path --nimcache:$nimcache $docCmd $file" % [ - "nim", os.getAppFilename(), - "backend", $d.conf.backend, - "path", quoteShell(d.conf.projectPath), - "nimcache", quoteShell(outputDir), - "file", quoteShell(outp), - "docCmd", docCmd, - ] - if os.execShellCmd(cmd) != 0: - quit "[runnableExamples] failed: generated file: '$1' cmd: $2" % [outp.string, cmd] - else: - # keep generated source file `outp` to allow inspection. - rawMessage(d.conf, hintSuccess, ["runnableExamples: " & outp.string]) - removeFile(outp.changeFileExt(ExeExt)) + let outputDir = d.exampleOutputDir + for _, group in d.exampleGroups: + if group.docCmd == docCmdSkip: continue + let outp = outputDir / RelativeFile("$1_group$2_examples.nim" % [d.filename.splitFile.name, $group.index]) + group.code = "# autogenerated by docgen\n# source: $1\n# rdoccmd: $2\n$3" % [d.filename, group.rdoccmd, group.code] + writeFile(outp, group.code) + # most useful semantics is that `docCmd` comes after `rdoccmd`, so that we can (temporarily) override + # via command line + let cmd = "$nim $backend -r --warning:UnusedImport:off --path:$path --nimcache:$nimcache $rdoccmd $docCmd $file" % [ + "nim", os.getAppFilename(), + "backend", $d.conf.backend, + "path", quoteShell(d.conf.projectPath), + "nimcache", quoteShell(outputDir), + "file", quoteShell(outp), + "rdoccmd", group.rdoccmd, + "docCmd", group.docCmd, + ] + if os.execShellCmd(cmd) != 0: + quit "[runnableExamples] failed: generated file: '$1' group: '$2' cmd: $3" % [outp.string, $group[], cmd] + else: + # keep generated source file `outp` to allow inspection. + rawMessage(d.conf, hintSuccess, ["runnableExamples: " & outp.string]) + # removeFile(outp.changeFileExt(ExeExt)) # it's in nimcache, no need to remove +proc prepareExample(d: PDoc; n: PNode): string = + ## returns `rdoccmd` for this runnableExamples + var rdoccmd = "" + if n.len < 2 or n.len > 3: globalError(d.conf, n.info, "runnableExamples invalid") + if n.len == 3: + let n1 = n[1] + # xxx this should be evaluated during sempass + if n1.kind notin nkStrKinds: globalError(d.conf, n1.info, "string litteral expected") + rdoccmd = n1.strVal -proc prepareExamples(d: PDoc; n: PNode) = var docComment = newTree(nkCommentStmt) let loc = d.conf.toFileLineCol(n.info) - docComment.comment = "autogenerated by docgen from " & loc + + docComment.comment = "autogenerated by docgen\nloc: $1\nrdoccmd: $2" % [loc, rdoccmd] var runnableExamples = newTree(nkStmtList, docComment, newTree(nkImportStmt, newStrNode(nkStrLit, d.filename))) runnableExamples.info = n.info + for a in n.lastSon: runnableExamples.add a - testExample(d, runnableExamples) + writeExample(d, runnableExamples, rdoccmd) + result = rdoccmd when false: proc extractImports(n: PNode; result: PNode) = if n.kind in {nkImportStmt, nkImportExceptStmt, nkFromStmt}: @@ -510,9 +534,11 @@ proc getAllRunnableExamplesRec(d: PDoc; n, orig: PNode; dest: var Rope) = of nkCallKinds: if isRunnableExamples(n[0]) and n.len >= 2 and n.lastSon.kind == nkStmtList: - prepareExamples(d, n) + let rdoccmd = prepareExample(d, n) + var msg = "Example:" + if rdoccmd.len > 0: msg.add " cmd: " & rdoccmd dispA(d.conf, dest, "\n

$1

\n", - "\n\\textbf{$1}\n", [rope"Examples:"]) + "\n\\textbf{$1}\n", [msg.rope]) inc d.listingCounter let id = $d.listingCounter dest.add(d.config.getOrDefault"doc.listing_start" % [id, "langNim"]) diff --git a/lib/system.nim b/lib/system.nim index e5b359c708..85be43cbac 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -117,7 +117,7 @@ else: Ordinal* = OrdinalImpl | uint | uint64 when defined(nimHasRunnableExamples): - proc runnableExamples*(body: untyped) {.magic: "RunnableExamples".} + proc runnableExamples*(rdoccmd = "", body: untyped) {.magic: "RunnableExamples".} ## A section you should use to mark `runnable example`:idx: code with. ## ## - In normal debug and release builds code within @@ -139,10 +139,15 @@ when defined(nimHasRunnableExamples): ## assert double(5) == 10 ## block: ## at block scope ## defer: echo "done" - ## ## result = 2 * x + ## runnableExamples "-d:foo -b:cpp": + ## import std/compilesettings + ## doAssert querySetting(backend) == "cpp" + ## runnableExamples "-r:off": ## this one is only compiled + ## import std/browsers + ## openDefaultBrowser "https://forum.nim-lang.org/" else: - template runnableExamples*(body: untyped) = + template runnableExamples*(doccmd = "", body: untyped) = discard proc declared*(x: untyped): bool {.magic: "Defined", noSideEffect, compileTime.} diff --git a/nimdoc/tester.nim b/nimdoc/tester.nim index a1500455ec..58272a9b67 100644 --- a/nimdoc/tester.nim +++ b/nimdoc/tester.nim @@ -24,12 +24,13 @@ proc testNimDoc(prjDir, docsDir: string; switches: NimSwitches; fixup = false) = nimBuildIndexSwitches = switches.buildIndex.join(" ") putEnv("SOURCE_DATE_EPOCH", "100000") + const nimExe = getCurrentCompilerExe() # so that `bin/nim_temp r nimdoc/tester.nim` works if nimDocSwitches != "": - exec("nim doc $1" % [nimDocSwitches]) + exec("$1 doc $2" % [nimExe, nimDocSwitches]) if nimBuildIndexSwitches != "": - exec("nim buildIndex $1" % [nimBuildIndexSwitches]) + exec("$1 buildIndex $2" % [nimExe, nimBuildIndexSwitches]) for expected in walkDirRec(prjDir / "expected/"): let produced = expected.replace('\\', '/').replace("/expected/", "/$1/" % [docsDir]) diff --git a/nimdoc/testproject/expected/testproject.html b/nimdoc/testproject/expected/testproject.html index 77f178265b..4eb8ed44ca 100644 --- a/nimdoc/testproject/expected/testproject.html +++ b/nimdoc/testproject/expected/testproject.html @@ -183,7 +183,7 @@ function main() {

This is the top level module. -

Examples:

+

Example:

import
   subdir / subdir_b / utils
 
diff --git a/tests/nimdoc/trunnableexamples.nim b/tests/nimdoc/trunnableexamples.nim
index e3ae6b9cb1..73cfacd438 100644
--- a/tests/nimdoc/trunnableexamples.nim
+++ b/tests/nimdoc/trunnableexamples.nim
@@ -1,5 +1,5 @@
 discard """
-cmd: "nim doc $file"
+cmd: "nim doc --doccmd:-d:testFooExternal --hints:off $file"
 action: "compile"
 nimout: '''
 foo1
@@ -66,6 +66,39 @@ when true: # issue #12746
         # specifying Error is culprit
         discard
 
+when true: # runnableExamples with rdoccmd
+  runnableExamples "-d:testFoo -d:testBar":
+    doAssert defined(testFoo) and defined(testBar)
+    doAssert defined(testFooExternal)
+  runnableExamples "-d:testFoo2":
+    doAssert defined(testFoo2)
+    doAssert not defined(testFoo) # doesn't get confused by other examples
+
+  ## all these syntaxes work too
+  runnableExamples("-d:testFoo2"): discard
+  runnableExamples(): discard
+  runnableExamples: discard
+  runnableExamples "-r:off": # issue #10731
+    doAssert false ## we compile only (-r:off), so this won't be run
+  runnableExamples "-b:js":
+    import std/compilesettings
+    proc startsWith*(s, prefix: cstring): bool {.noSideEffect, importjs: "#.startsWith(#)".}
+    doAssert querySetting(backend) == "js"
+  runnableExamples "-b:cpp":
+    static: doAssert defined(cpp)
+    type std_exception {.importcpp: "std::exception", header: "".} = object
+
+  proc fun2*() =
+    runnableExamples "-d:foo": discard # checks that it also works inside procs
+
+  when false: # future work
+    # passing non-string-litterals (for reuse)
+    const a = "-b:cpp"
+    runnableExamples(a): discard
+
+    # passing seq (to run with multiple compilation options)
+    runnableExamples(@["-b:cpp", "-b:js"]): discard
+
 # also check for runnableExamples at module scope
 runnableExamples:
   block: