From 6e8f691b426d85da235e5e6a6968b6a7d07cd7a1 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 17 Mar 2019 15:27:50 +0000 Subject: [PATCH 01/95] Add test for #620 with attempted workaround. --- src/nimblepkg/nimscriptsupport.nim | 13 +++++++++++++ tests/tester.nim | 9 +++++++++ 2 files changed, 22 insertions(+) diff --git a/src/nimblepkg/nimscriptsupport.nim b/src/nimblepkg/nimscriptsupport.nim index 2806251..997f198 100644 --- a/src/nimblepkg/nimscriptsupport.nim +++ b/src/nimblepkg/nimscriptsupport.nim @@ -310,6 +310,7 @@ proc execScript(scriptName: string, flags: Flags, options: Options): PSym = graph = newModuleGraph(graph.config) let conf = graph.config + conf.searchPaths = @[] when declared(NimCompilerApiVersion): if "nimblepkg/nimscriptapi" notin conf.implicitImports: conf.implicitImports.add("nimblepkg/nimscriptapi") @@ -381,11 +382,20 @@ proc execScript(scriptName: string, flags: Flags, options: Options): PSym = let pkgName = scriptName.splitFile.name # Ensure that "nimblepkg/nimscriptapi" is in the PATH. + var issue620 = false block: + echo(scriptName.splitFile.dir / "nimblepkg" / "nimscriptapi.nim") + if existsFile(scriptName.splitFile.dir / "nimblepkg" / "nimscriptapi.nim"): + # TODO: hacky workaround for #620. + issue620 = true + conf.disableNimblePath() + break + let t = getTempDir() / "nimblecache" let tmpNimscriptApiPath = t / "nimblepkg" / "nimscriptapi.nim" createDir(tmpNimscriptApiPath.splitFile.dir) writeFile(tmpNimscriptApiPath, nimscriptApi) + echo("before ", conf.searchPaths) when declared(NimCompilerApiVersion): when NimCompilerApiVersion >= 3: conf.searchPaths.add(AbsoluteDir t) @@ -393,6 +403,7 @@ proc execScript(scriptName: string, flags: Flags, options: Options): PSym = conf.searchPaths.add(t) else: searchPaths.add(t) + echo("after ", conf.searchPaths) when declared(NimCompilerApiVersion): initDefines(conf.symbols) @@ -428,6 +439,8 @@ proc execScript(scriptName: string, flags: Flags, options: Options): PSym = searchPaths.add(compiler_options.libpath) + echo("after2 ", conf.searchPaths) + when declared(resetAllModulesHard): result = makeModule(scriptName) else: diff --git a/tests/tester.nim b/tests/tester.nim index 372900e..ff44add 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -67,6 +67,15 @@ proc inLines(lines: seq[string], line: string): bool = for i in lines: if line.normalize in i.normalize: return true +suite "Issue 620": + test "install nimble": + cd "..": + check execNimble(["install", "-y"]).exitCode == QuitSuccess + + test "nimble check the installed nimble file": + cd execNimble(["path", "nimble"]).output: + check execNimble(["check"]).exitCode == QuitSuccess + test "picks #head when looking for packages": cd "versionClashes" / "aporiaScenario": let (output, exitCode) = execNimble("install", "-y", "--verbose") From c8d79fc0228682677330a9f57d14389aaa641153 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 26 Mar 2019 10:06:06 +0100 Subject: [PATCH 02/95] update the installation instructions --- readme.markdown | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/readme.markdown b/readme.markdown index 31ab0fc..f246c7e 100644 --- a/readme.markdown +++ b/readme.markdown @@ -86,23 +86,6 @@ Simply execute the following command to compile and install Nimble. This will clone the Nimble repository, compile Nimble and copy it into Nim's bin directory. -The second approach is to install Nimble as a Nimble package. You can do this -by compiling Nimble, then running ``nimble install`` in Nimble's directory. - -``` -git clone https://github.com/nim-lang/nimble.git -cd nimble -nim c src/nimble -src/nimble install -``` - -**Note for Windows users**: You will need to rename ``nimble.exe`` after -compilation to something else like ``nimble1.exe``, then run -``src\nimble1.exe install``. - -This will install Nimble to the default Nimble packages location: -``~/.nimble/pkgs``. The binary will be installed to ``~/.nimble/bin``, so you -will need to add this directory to your PATH. ## Nimble usage From 6542c1ef161b4594387e6da8babe2087aec4adc8 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Mon, 29 Apr 2019 23:03:57 +0100 Subject: [PATCH 03/95] Squashed merge of #635 by @genotrance. Squashed commit of the following: commit e86a376f2faf9d26109405a3a9f73f986185f62d Author: Ganesh Viswanathan Date: Sun Apr 28 15:37:22 2019 -0500 Fix caching issue commit 640ce3f2e464e52668b5350fdc5a8fe506e79d38 Author: Ganesh Viswanathan Date: Thu Apr 25 18:38:48 2019 -0500 Clean up per feedback commit ae3ef9f7a0cbad574b725d1bc7a83bd6115e19cc Author: Ganesh Viswanathan Date: Thu Apr 25 16:39:26 2019 -0500 Fix for 0.19.4 commit 915d6b2be43e33bc51327585193b1899386ee250 Author: Ganesh Viswanathan Date: Thu Apr 25 16:13:42 2019 -0500 Keep nimscript separate, pin devel commit c278bd6ba09771dc079029a87e3a375998f0b447 Author: Ganesh Viswanathan Date: Mon Apr 22 14:57:44 2019 -0500 Hardcode version, json{}, code width 80, isScriptResultCached, no blank paramStr check commit 64e5489e256d5fc5abbfe3345789f65edf5980b7 Author: Ganesh Viswanathan Date: Wed Apr 17 21:07:03 2019 -0500 Remove compiler dependency commit a031fffd70c118c16eb3e16d3b1ed10472baf5d7 Author: Ganesh Viswanathan Date: Wed Apr 17 16:49:09 2019 -0500 Add devel to travis commit d49916e2a05b6bd7716f45bd8f74253fc8037827 Author: Ganesh Viswanathan Date: Wed Apr 17 16:43:14 2019 -0500 Interactive live, json to file commit 24131deea4693199922f9a5697aa3d072cceaee1 Author: Ganesh Viswanathan Date: Wed Apr 17 12:40:27 2019 -0500 Fix empty param, json echo commit b22fe37d47fd03367d49129ea4d2d56a779a6f26 Merge: 5cf0240 2942f11 Author: Ganesh Viswanathan Date: Tue Apr 16 22:23:17 2019 -0500 Merge branch 'nocompiler' of https://github.com/genotrance/nimble into nocompiler commit 5cf0240b728ab6ff4a39ddf629ba5833eb8985f5 Author: Ganesh Viswanathan Date: Tue Apr 16 22:23:06 2019 -0500 No hints, live output commit 2942f116c7774e0fa91f770cebde32bc431923a5 Author: Ganesh Viswanathan Date: Tue Apr 16 21:02:28 2019 -0500 Remove osx, test with stable commit 85f3865ef195c7b813f0b9e30b5cc8c9b2756518 Author: Ganesh Viswanathan Date: Tue Apr 16 18:19:42 2019 -0500 Remove ospaths, fix tests for Windows commit 74201bcfe4de00bdece5b31715618975f9ce8e6e Author: Ganesh Viswanathan Date: Tue Apr 16 14:00:14 2019 -0500 No success for missing task commit 8c2e65e223d32366b03004d9711364504c5d7916 Author: Ganesh Viswanathan Date: Tue Apr 16 13:44:32 2019 -0500 Fix packageName to name commit b05d9480281ebae7a0f5fd0331c8627bbf2a77d5 Author: Ganesh Viswanathan Date: Tue Apr 16 13:29:37 2019 -0500 Add switch support commit deecd903102a9baa5d4674cb9871cd9dbb658a04 Author: Ganesh Viswanathan Date: Tue Apr 16 12:24:01 2019 -0500 API cleanup, json setCommand fix commit 1e95fd4104ec3ffb69fe67b9c2fac23f991e163a Author: Ganesh Viswanathan Date: Tue Apr 16 10:45:12 2019 -0500 getParams once, hash nimscriptapi, fix loop in setcommand commit 51d03b3845cd562796bb32d41d5ad17cd09a91e7 Author: Ganesh Viswanathan Date: Tue Apr 16 07:21:32 2019 -0500 getPkgDir impl commit 7d0a40aa286d114d7557b229852f3c314795dc5d Author: Ganesh Viswanathan Date: Mon Apr 15 14:24:02 2019 -0500 Before/after hook info commit cbb3af3e970b20322030331d4849436b821f25ca Author: Ganesh Viswanathan Date: Mon Apr 15 13:44:56 2019 -0500 Remove nims from package dir after exec commit 0ed53d60bcdc8bb11beddb965590ed3ee63349d4 Author: Ganesh Viswanathan Date: Sat Apr 13 00:44:26 2019 -0500 Return bool from hooks commit ab38b81b81e68cfccf3ca84fd854422cd3733c84 Author: Ganesh Viswanathan Date: Fri Apr 12 23:20:13 2019 -0500 Initial version commit b9ef88b9f79b48435e7b4beeff959b4223f4b8ba Merge: 220ebae c8d79fc Author: Ganesh Viswanathan Date: Tue Mar 26 20:16:21 2019 -0500 Merge remote-tracking branch 'upstream/master' into nocompiler commit 220ebae355c945963591b002a43b262a70640aa5 Merge: 3d7227c 119be48 Author: Ganesh Viswanathan Date: Wed Dec 12 18:02:10 2018 -0600 Merge remote-tracking branch 'upstream/master' commit 3d7227c8900c205aada488d60565c90e17759639 Merge: cf7263d 66d79bf Author: Ganesh Viswanathan Date: Wed Oct 17 13:39:51 2018 -0500 Merge remote-tracking branch 'upstream/master' commit cf7263d6caf27ca4930ed54b05d4aa4f36e1dff1 Merge: 2fc3106 ee4c0ae Author: Ganesh Viswanathan Date: Thu Sep 13 23:03:41 2018 -0500 Merge remote-tracking branch 'upstream/master' commit 2fc310623b9f49ea012fc04fa09713fda140a7a3 Merge: e9a8850 c249f9b Author: Ganesh Viswanathan Date: Thu Apr 26 16:27:31 2018 -0500 Merge remote-tracking branch 'upstream/master' commit e9a885099b0b97bf3e0cddcde27e8c6b0bd51b10 Merge: 7adfd7b 75b7a21 Author: Ganesh Viswanathan Date: Thu Mar 8 14:26:46 2018 -0600 Merge remote-tracking branch 'upstream/master' commit 7adfd7be2b38a52886640579845de378139ca0cc Author: Ganesh Viswanathan Date: Mon Jan 15 00:35:55 2018 -0600 Updated fix for #398 commit de18319159b76a9da6765f35ea4d2e2c963d688a Merge: 93ba4a0 3dae264 Author: Ganesh Viswanathan Date: Sun Jan 14 22:01:20 2018 -0600 Merge remote-tracking branch 'upstream/master' commit 93ba4a00820ccb9a5362f0398cf3b5b4782bbefe Author: Ganesh Viswanathan Date: Sat Jan 13 19:52:34 2018 -0600 Fix for #398 --- .travis.yml | 14 +-- nimble.nimble | 14 +-- src/nimble.nim | 6 +- src/nimblepkg/nimscriptapi.nim | 155 +++++++++++++++++++++--- src/nimblepkg/nimscriptexecutor.nim | 4 +- src/nimblepkg/nimscriptwrapper.nim | 176 ++++++++++++++++++++++++++++ src/nimblepkg/packageinfo.nim | 2 +- src/nimblepkg/packageparser.nim | 20 +++- tests/caching/caching.nimble | 10 ++ tests/tester.nim | 44 +++++-- 10 files changed, 389 insertions(+), 56 deletions(-) mode change 100644 => 100755 src/nimblepkg/nimscriptapi.nim create mode 100755 src/nimblepkg/nimscriptwrapper.nim mode change 100644 => 100755 src/nimblepkg/packageparser.nim create mode 100644 tests/caching/caching.nimble diff --git a/.travis.yml b/.travis.yml index 4f39d6d..0e5d3a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,24 +1,24 @@ os: - linux -dist: trusty + - osx language: c +env: + - BRANCH=0.19.4 + - BRANCH=#4f9366975441be889a8cd4fbfb4e41f6830dc542 + cache: directories: - - "$HOME/.nimble" - - "$HOME/.choosenim" + - "$HOME/.choosenim/toolchains/nim-0.19.4" install: - - export CHOOSENIM_CHOOSE_VERSION="#7bb93c730ea87f" - - export NIM_LIB_PREFIX="$HOME/.choosenim/toolchains/nim-"$CHOOSENIM_CHOOSE_VERSION + - export CHOOSENIM_CHOOSE_VERSION=$BRANCH - | curl https://nim-lang.org/choosenim/init.sh -sSf > init.sh sh init.sh -y before_script: - - set -e - - set -x - export CHOOSENIM_NO_ANALYTICS=1 - export PATH=$HOME/.nimble/bin:$PATH diff --git a/nimble.nimble b/nimble.nimble index 7a8e416..66c1b63 100644 --- a/nimble.nimble +++ b/nimble.nimble @@ -1,16 +1,6 @@ -import ospaths -template thisModuleFile: string = instantiationInfo(fullPaths = true).filename - -when fileExists(thisModuleFile.parentDir / "src/nimblepkg/common.nim"): - # In the git repository the Nimble sources are in a ``src`` directory. - import src/nimblepkg/common -else: - # When the package is installed, the ``src`` directory disappears. - import nimblepkg/common - # Package -version = nimbleVersion +version = "0.9.0" author = "Dominik Picheta" description = "Nim package manager." license = "BSD" @@ -21,7 +11,7 @@ installExt = @["nim"] # Dependencies -requires "nim >= 0.13.0", "compiler#head" +requires "nim >= 0.13.0" when defined(nimdistros): import distros diff --git a/src/nimble.nim b/src/nimble.nim index e0b10f3..26f4a2a 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -16,7 +16,7 @@ import nimblepkg/packageinfo, nimblepkg/version, nimblepkg/tools, nimblepkg/cli, nimblepkg/packageinstaller, nimblepkg/reversedeps, nimblepkg/nimscriptexecutor, nimblepkg/init -import nimblepkg/nimscriptsupport +import nimblepkg/nimscriptwrapper proc refresh(options: Options) = ## Downloads the package list from the specified URL. @@ -896,7 +896,7 @@ proc uninstall(options: Options) = proc listTasks(options: Options) = let nimbleFile = findNimbleFile(getCurrentDir(), true) - nimscriptsupport.listTasks(nimbleFile, options) + nimscriptwrapper.listTasks(nimbleFile, options) proc developFromDir(dir: string, options: Options) = if options.depsOnly: @@ -1103,7 +1103,7 @@ proc doAction(options: Options) = return let isPreDefined = options.action.command.normalize == "test" - var execResult: ExecutionResult[void] + var execResult: ExecutionResult[bool] if execCustom(options, execResult, failFast=not isPreDefined): if execResult.hasTaskRequestedCommand(): doAction(execResult.getOptionsForCommand(options)) diff --git a/src/nimblepkg/nimscriptapi.nim b/src/nimblepkg/nimscriptapi.nim old mode 100644 new mode 100755 index 0e0ce45..813c5d3 --- a/src/nimblepkg/nimscriptapi.nim +++ b/src/nimblepkg/nimscriptapi.nim @@ -3,6 +3,9 @@ ## This module is implicitly imported in NimScript .nimble files. +import system except getCommand, setCommand, switch, `--` +import strformat, strutils, tables + var packageName* = "" ## Set this to the package name. It ## is usually not required to do that, nims' filename is @@ -22,30 +25,134 @@ var foreignDeps*: seq[string] = @[] ## The foreign dependencies. Only ## exported for 'distros.nim'. + beforeHooks: seq[string] = @[] + afterHooks: seq[string] = @[] + commandLineParams: seq[string] = @[] + flags: TableRef[string, seq[string]] + + command = "e" + project = "" + success = false + retVal = true + projectFile = "" + proc requires*(deps: varargs[string]) = ## Call this to set the list of requirements of your Nimble ## package. for d in deps: requiresData.add(d) +proc getParams() = + for i in 2 .. paramCount(): + let + param = paramStr(i) + if param.fileExists(): + projectFile = param + elif param[0] != '-': + commandLineParams.add paramStr(i).normalize + +proc getCommand*(): string = + return command + +proc setCommand*(cmd: string, prj = "") = + command = cmd + if prj.len != 0: + project = prj + +proc switch*(key: string, value="") = + if flags.isNil: + flags = newTable[string, seq[string]]() + + if flags.hasKey(key): + flags[key].add(value) + else: + flags[key] = @[value] + +template `--`*(key, val: untyped) = + switch(astToStr(key), strip astToStr(val)) + +template `--`*(key: untyped) = + switch(astToStr(key), "") + +template printIfLen(varName) = + if varName.len != 0: + iniOut &= astToStr(varName) & ": \"" & varName & "\"\n" + +template printSeqIfLen(varName) = + if varName.len != 0: + iniOut &= astToStr(varName) & ": \"" & varName.join(", ") & "\"\n" + +proc printPkgInfo() = + if backend.len == 0: + backend = "c" + + var + iniOut = "[Package]\n" + if packageName.len != 0: + iniOut &= "name: \"" & packageName & "\"\n" + printIfLen version + printIfLen author + printIfLen description + printIfLen license + printIfLen srcdir + printIfLen binDir + printIfLen backend + + printSeqIfLen skipDirs + printSeqIfLen skipFiles + printSeqIfLen skipExt + printSeqIfLen installDirs + printSeqIfLen installFiles + printSeqIfLen installExt + printSeqIfLen bin + printSeqIfLen beforeHooks + printSeqIfLen afterHooks + + if requiresData.len != 0: + iniOut &= "\n[Deps]\n" + iniOut &= &"requires: \"{requiresData.join(\", \")}\"\n" + + echo iniOut + +proc onExit*() = + if "printPkgInfo".normalize in commandLineParams: + printPkgInfo() + else: + var + output = "" + output &= "\"success\": " & $success & ", " + output &= "\"command\": \"" & command & "\", " + if project.len != 0: + output &= "\"project\": \"" & project & "\", " + if not flags.isNil and flags.len != 0: + output &= "\"flags\": {" + for key, val in flags.pairs: + output &= "\"" & key & "\": [" + for v in val: + output &= "\"" & v & "\", " + output = output[0 .. ^3] & "], " + output = output[0 .. ^3] & "}, " + + output &= "\"retVal\": " & $retVal + + writeFile(projectFile & ".out", "{" & output & "}") + # TODO: New release of Nim will move this `task` template under a # `when not defined(nimble)`. This will allow us to override it in the future. -when not declared(task): - template task*(name: untyped; description: string; body: untyped): untyped = - ## Defines a task. Hidden tasks are supported via an empty description. - ## Example: - ## - ## .. code-block:: nim - ## task build, "default build is via the C backend": - ## setCommand "c" - proc `name Task`*() = body +template task*(name: untyped; description: string; body: untyped): untyped = + ## Defines a task. Hidden tasks are supported via an empty description. + ## Example: + ## + ## .. code-block:: nim + ## task build, "default build is via the C backend": + ## setCommand "c" + proc `name Task`*() = body - let cmd = getCommand() - if cmd.len == 0 or cmd == "help": - setCommand "help" - echo(astToStr(name), " ", description) - elif cmd == astToStr(name): - setCommand "nop" - `name Task`() + if commandLineParams.len == 0 or "help" in commandLineParams: + success = true + echo(astToStr(name), " ", description) + elif astToStr(name).normalize in commandLineParams: + success = true + `name Task`() template before*(action: untyped, body: untyped): untyped = ## Defines a block of code which is evaluated before ``action`` is executed. @@ -53,15 +160,27 @@ template before*(action: untyped, body: untyped): untyped = result = true body + beforeHooks.add astToStr(action) + + if (astToStr(action) & "Before").normalize in commandLineParams: + success = true + retVal = `action Before`() + template after*(action: untyped, body: untyped): untyped = ## Defines a block of code which is evaluated after ``action`` is executed. proc `action After`*(): bool = result = true body -template builtin = discard + afterHooks.add astToStr(action) + + if (astToStr(action) & "After").normalize in commandLineParams: + success = true + retVal = `action After`() proc getPkgDir*(): string = ## Returns the package directory containing the .nimble file currently ## being evaluated. - builtin + result = projectFile.rsplit(seps={'/', '\\', ':'}, maxsplit=1)[0] + +getParams() diff --git a/src/nimblepkg/nimscriptexecutor.nim b/src/nimblepkg/nimscriptexecutor.nim index 70fc165..fad0d1e 100644 --- a/src/nimblepkg/nimscriptexecutor.nim +++ b/src/nimblepkg/nimscriptexecutor.nim @@ -3,7 +3,7 @@ import os, tables, strutils, sets -import packageparser, common, packageinfo, options, nimscriptsupport, cli +import packageparser, common, packageinfo, options, nimscriptwrapper, cli proc execHook*(options: Options, before: bool): bool = ## Returns whether to continue. @@ -31,7 +31,7 @@ proc execHook*(options: Options, before: bool): bool = result = res.retVal proc execCustom*(options: Options, - execResult: var ExecutionResult[void], + execResult: var ExecutionResult[bool], failFast = true): bool = ## Executes the custom command using the nimscript backend. ## diff --git a/src/nimblepkg/nimscriptwrapper.nim b/src/nimblepkg/nimscriptwrapper.nim new file mode 100755 index 0000000..44d6b81 --- /dev/null +++ b/src/nimblepkg/nimscriptwrapper.nim @@ -0,0 +1,176 @@ +# Copyright (C) Andreas Rumpf. All rights reserved. +# BSD License. Look at license.txt for more info. + +## Implements the new configuration system for Nimble. Uses Nim as a +## scripting language. + +import common, version, options, packageinfo, cli +import hashes, json, os, streams, strutils, strtabs, + tables, times, osproc, sets, pegs + +type + Flags = TableRef[string, seq[string]] + ExecutionResult*[T] = object + success*: bool + command*: string + arguments*: seq[string] + flags*: Flags + retVal*: T + +const + internalCmd = "e" + nimscriptApi = staticRead("nimscriptapi.nim") + +proc execNimscript(nimsFile, projectDir, actionName: string, options: Options, + live = true): tuple[output: string, exitCode: int] = + let + shash = $projectDir.hash().abs() + nimsFileCopied = projectDir / nimsFile.splitFile().name & "_" & shash & ".nims" + + let + isScriptResultCopied = + nimsFileCopied.fileExists() and + nimsFileCopied.getLastModificationTime() >= nimsFile.getLastModificationTime() + + if not isScriptResultCopied: + nimsFile.copyFile(nimsFileCopied) + + defer: + nimsFileCopied.removeFile() + + let + cmd = ("nim e --hints:off --verbosity:0 -p:" & (getTempDir() / "nimblecache").quoteShell & + " " & nimsFileCopied.quoteShell & " " & actionName).strip() + + if live: + result.exitCode = execCmd(cmd) + let + outFile = nimsFileCopied & ".out" + if outFile.fileExists(): + result.output = outFile.readFile() + discard outFile.tryRemoveFile() + else: + result = execCmdEx(cmd, options = {poUsePath}) + +proc getNimsFile(scriptName: string, options: Options): string = + let + cacheDir = getTempDir() / "nimblecache" + shash = $scriptName.parentDir().hash().abs() + prjCacheDir = cacheDir / scriptName.splitFile().name & "_" & shash + + result = prjCacheDir / scriptName.extractFilename().changeFileExt ".nims" + +proc setupNimscript(scriptName: string, options: Options) = + let + cacheDir = getTempDir() / "nimblecache" + nimscriptApiFile = cacheDir / "nimscriptapi.nim" + nimsFile = getNimsFile(scriptName, options) + + let + isNimscriptApiCached = + nimscriptApiFile.fileExists() and nimscriptApiFile.getLastModificationTime() > + getAppFilename().getLastModificationTime() + + isScriptResultCached = + nimsFile.fileExists() and nimsFile.getLastModificationTime() > + scriptName.getLastModificationTime() + + if not isNimscriptApiCached: + createDir(cacheDir) + writeFile(nimscriptApiFile, nimscriptApi) + + if not isScriptResultCached: + createDir(nimsFile.parentDir()) + writeFile(nimsFile, """ +import system except getCommand, setCommand, switch, `--`, + packageName, version, author, description, license, srcDir, binDir, backend, + skipDirs, skipFiles, skipExt, installDirs, installFiles, installExt, bin, foreignDeps, + requires, task, packageName +""" & + "import nimscriptapi, strutils\n" & scriptName.readFile() & "\nonExit()\n") + +proc getIniFile*(scriptName: string, options: Options): string = + let + nimsFile = getNimsFile(scriptName, options) + + result = nimsFile.changeFileExt(".ini") + + let + isIniResultCached = + result.fileExists() and result.getLastModificationTime() > + scriptName.getLastModificationTime() + + if not isIniResultCached: + setupNimscript(scriptName, options) + let + (output, exitCode) = + execNimscript(nimsFile, scriptName.parentDir(), "printPkgInfo", options, live=false) + + if exitCode == 0 and output.len != 0: + result.writeFile(output) + else: + raise newException(NimbleError, output & "\nprintPkgInfo() failed") + +proc execScript(scriptName, actionName: string, options: Options): + ExecutionResult[bool] = + let + nimsFile = getNimsFile(scriptName, options) + + if not nimsFile.fileExists(): + setupNimScript(scriptName, options) + + let + (output, exitCode) = execNimscript(nimsFile, scriptName.parentDir(), actionName, options) + + if exitCode != 0: + raise newException(NimbleError, output) + + let + j = + if output.len != 0: + parseJson(output) + else: + parseJson("{}") + + result.flags = newTable[string, seq[string]]() + result.success = j{"success"}.getBool() + result.command = j{"command"}.getStr() + if "project" in j: + result.arguments.add j["project"].getStr() + if "flags" in j: + for flag, vals in j["flags"].pairs: + result.flags[flag] = @[] + for val in vals.items(): + result.flags[flag].add val.getStr() + result.retVal = j{"retVal"}.getBool() + +proc execTask*(scriptName, taskName: string, + options: Options): ExecutionResult[bool] = + ## Executes the specified task in the specified script. + ## + ## `scriptName` should be a filename pointing to the nimscript file. + display("Executing", "task $# in $#" % [taskName, scriptName], + priority = HighPriority) + + result = execScript(scriptName, taskName, options) + +proc execHook*(scriptName, actionName: string, before: bool, + options: Options): ExecutionResult[bool] = + ## Executes the specified action's hook. Depending on ``before``, either + ## the "before" or the "after" hook. + ## + ## `scriptName` should be a filename pointing to the nimscript file. + let hookName = + if before: actionName.toLowerAscii & "Before" + else: actionName.toLowerAscii & "After" + display("Attempting", "to execute hook $# in $#" % [hookName, scriptName], + priority = MediumPriority) + + result = execScript(scriptName, hookName, options) + +proc hasTaskRequestedCommand*(execResult: ExecutionResult): bool = + ## Determines whether the last executed task used ``setCommand`` + return execResult.command != internalCmd + +proc listTasks*(scriptName: string, options: Options) = + discard execScript(scriptName, "", options) diff --git a/src/nimblepkg/packageinfo.nim b/src/nimblepkg/packageinfo.nim index 1261494..c54b97d 100644 --- a/src/nimblepkg/packageinfo.nim +++ b/src/nimblepkg/packageinfo.nim @@ -324,7 +324,7 @@ proc getInstalledPkgsMin*(libsDir: string, options: Options): seq[tuple[pkginfo: PackageInfo, meta: MetaData]] = ## Gets a list of installed packages. The resulting package info is ## minimal. This has the advantage that it does not depend on the - ## ``packageparser`` module, and so can be used by ``nimscriptsupport``. + ## ``packageparser`` module, and so can be used by ``nimscriptwrapper``. ## ## ``libsDir`` is in most cases: ~/.nimble/pkgs/ (options.getPkgsDir) result = @[] diff --git a/src/nimblepkg/packageparser.nim b/src/nimblepkg/packageparser.nim old mode 100644 new mode 100755 index cf77dcc..5c034eb --- a/src/nimblepkg/packageparser.nim +++ b/src/nimblepkg/packageparser.nim @@ -1,12 +1,12 @@ # Copyright (C) Dominik Picheta. All rights reserved. # BSD License. Look at license.txt for more info. -import parsecfg, json, streams, strutils, parseutils, os, tables, sugar +import parsecfg, json, sets, streams, strutils, parseutils, os, tables, sugar from sequtils import apply, map -import version, tools, common, nimscriptsupport, options, packageinfo, cli +import version, tools, common, nimscriptwrapper, options, packageinfo, cli ## Contains procedures for parsing .nimble files. Moved here from ``packageinfo`` -## because it depends on ``nimscriptsupport`` (``nimscriptsupport`` also +## because it depends on ``nimscriptwrapper`` (``nimscriptwrapper`` also ## depends on other procedures in ``packageinfo``. type @@ -259,6 +259,12 @@ proc readPackageInfoFromNimble(path: string; result: var PackageInfo) = case result.backend.normalize of "javascript": result.backend = "js" else: discard + of "beforehooks": + for i in ev.value.multiSplit: + result.preHooks.incl(i.normalize) + of "afterhooks": + for i in ev.value.multiSplit: + result.postHooks.incl(i.normalize) else: raise newException(NimbleError, "Invalid field: " & ev.key) of "deps", "dependencies": @@ -277,6 +283,14 @@ proc readPackageInfoFromNimble(path: string; result: var PackageInfo) = else: raise newException(ValueError, "Cannot open package info: " & path) +proc readPackageInfoFromNims(scriptName: string, options: Options, + result: var PackageInfo) = + let + iniFile = getIniFile(scriptName, options) + + if iniFile.fileExists(): + readPackageInfoFromNimble(iniFile, result) + proc inferInstallRules(pkgInfo: var PackageInfo, options: Options) = # Binary packages shouldn't install .nim files by default. # (As long as the package info doesn't explicitly specify what should be diff --git a/tests/caching/caching.nimble b/tests/caching/caching.nimble new file mode 100644 index 0000000..d3035a9 --- /dev/null +++ b/tests/caching/caching.nimble @@ -0,0 +1,10 @@ +# Package + +version = "0.1.0" +author = "Dominik Picheta" +description = "Test package" +license = "BSD" + +# Dependencies + +requires "nim >= 0.12.1" diff --git a/tests/tester.nim b/tests/tester.nim index 372900e..b57ab18 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -36,9 +36,13 @@ proc execNimble(args: varargs[string]): tuple[output: string, exitCode: int] = quotedArgs.add("--nimbleDir:" & installDir) quotedArgs = quotedArgs.map((x: string) => ("\"" & x & "\"")) - let path = getCurrentDir().parentDir() / "src" + let path {.used.} = getCurrentDir().parentDir() / "src" - var cmd = "PATH=" & path & ":$PATH " & quotedArgs.join(" ") + var cmd = + when not defined(windows): + "PATH=" & path & ":$PATH " & quotedArgs.join(" ") + else: + quotedArgs.join(" ") when defined(macosx): # TODO: Yeah, this is really specific to my machine but for my own sanity... cmd = "DYLD_LIBRARY_PATH=/usr/local/opt/openssl@1.1/lib " & cmd @@ -67,6 +71,17 @@ proc inLines(lines: seq[string], line: string): bool = for i in lines: if line.normalize in i.normalize: return true +test "caching works": + cd "caching": + var (output, exitCode) = execNimble("dump") + check output.contains("0.1.0") + let + nfile = "caching.nimble" + writeFile(nfile, readFile(nfile).replace("0.1.0", "0.2.0")) + (output, exitCode) = execNimble("dump") + check output.contains("0.2.0") + writeFile(nfile, readFile(nfile).replace("0.2.0", "0.1.0")) + test "picks #head when looking for packages": cd "versionClashes" / "aporiaScenario": let (output, exitCode) = execNimble("install", "-y", "--verbose") @@ -213,7 +228,7 @@ test "can refresh with local package list": [PackageList] name = "local" path = "$1" - """.unindent % (getCurrentDir() / "issue368" / "packages.json")) + """.unindent % (getCurrentDir() / "issue368" / "packages.json").replace("\\", "\\\\")) let (output, exitCode) = execNimble(["refresh", "--verbose"]) let lines = output.strip.processOutput() check inLines(lines, "config file at") @@ -258,9 +273,9 @@ suite "nimscript": check exitCode == QuitSuccess let lines = output.strip.processOutput() check lines[0].startsWith("Before PkgDir:") - check lines[0].endsWith("tests/nimscript") + check lines[0].endsWith("tests" / "nimscript") check lines[^1].startsWith("After PkgDir:") - check lines[^1].endsWith("tests/nimbleDir/pkgs/nimscript-0.1.0") + check lines[^1].endsWith("tests" / "nimbleDir" / "pkgs" / "nimscript-0.1.0") test "can execute nimscript tasks": cd "nimscript": @@ -412,6 +427,9 @@ test "issue #349": ] proc checkName(name: string) = + when defined(windows): + if name.toLowerAscii() in @["con", "nul"]: + return let (outp, code) = execNimble("init", "-y", name) let msg = outp.strip.processOutput() check code == QuitFailure @@ -526,9 +544,15 @@ suite "can handle two binary versions": cd "binaryPackage/v2": check execNimble("install", "-y").exitCode == QuitSuccess + var + cmd = installDir / "bin" / "binaryPackage" + + when defined(windows): + cmd = "cmd /c " & cmd & ".cmd" + test "can execute v2": let (output, exitCode) = - execCmdEx(installDir / "bin" / "binaryPackage".addFileExt(ExeExt)) + execCmdEx(cmd) check exitCode == QuitSuccess check output.strip() == "v2" @@ -536,7 +560,7 @@ suite "can handle two binary versions": check execNimble("remove", "binaryPackage@2.0", "-y").exitCode==QuitSuccess let (output, exitCode) = - execCmdEx(installDir / "bin" / "binaryPackage".addFileExt(ExeExt)) + execCmdEx(cmd) check exitCode == QuitSuccess check output.strip() == "v1" @@ -544,7 +568,7 @@ suite "can handle two binary versions": check execNimble("remove", "binaryPackage@1.0", "-y").exitCode==QuitSuccess let (output, exitCode) = - execCmdEx(installDir / "bin" / "binaryPackage".addFileExt(ExeExt)) + execCmdEx(cmd) check exitCode == QuitSuccess check output.strip() == "v2" @@ -678,9 +702,9 @@ suite "path command": test "can get correct path for srcDir (#531)": check execNimble("uninstall", "srcdirtest", "-y").exitCode == QuitSuccess cd "develop/srcdirtest": - let (output, exitCode) = execNimble("install", "-y") + let (_, exitCode) = execNimble("install", "-y") check exitCode == QuitSuccess - let (output, exitCode) = execNimble("path", "srcdirtest") + let (output, _) = execNimble("path", "srcdirtest") check output.strip() == installDir / "pkgs" / "srcdirtest-1.0" suite "test command": From ca779afb208bfdb4d81567130ae497b4051440c5 Mon Sep 17 00:00:00 2001 From: genotrance Date: Tue, 30 Apr 2019 14:59:12 -0500 Subject: [PATCH 04/95] Fix quoted switch, multi-line description, more caching (#642) * Fix quoted switch, multi-line description, more caching * Incorporate feedback --- src/nimblepkg/nimscriptapi.nim | 6 ++++-- src/nimblepkg/nimscriptwrapper.nim | 18 ++++++------------ tests/nimscript/nimscript.nimble | 6 +++++- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/nimblepkg/nimscriptapi.nim b/src/nimblepkg/nimscriptapi.nim index 813c5d3..eba18cd 100755 --- a/src/nimblepkg/nimscriptapi.nim +++ b/src/nimblepkg/nimscriptapi.nim @@ -75,7 +75,7 @@ template `--`*(key: untyped) = template printIfLen(varName) = if varName.len != 0: - iniOut &= astToStr(varName) & ": \"" & varName & "\"\n" + iniOut &= astToStr(varName) & ": \"\"\"" & varName & "\"\"\"\n" template printSeqIfLen(varName) = if varName.len != 0: @@ -128,7 +128,9 @@ proc onExit*() = for key, val in flags.pairs: output &= "\"" & key & "\": [" for v in val: - output &= "\"" & v & "\", " + let v = if v.len > 0 and v[0] == '"': strutils.unescape(v) + else: v + output &= v.escape & ", " output = output[0 .. ^3] & "], " output = output[0 .. ^3] & "}, " diff --git a/src/nimblepkg/nimscriptwrapper.nim b/src/nimblepkg/nimscriptwrapper.nim index 44d6b81..4002361 100755 --- a/src/nimblepkg/nimscriptwrapper.nim +++ b/src/nimblepkg/nimscriptwrapper.nim @@ -57,22 +57,19 @@ proc getNimsFile(scriptName: string, options: Options): string = cacheDir = getTempDir() / "nimblecache" shash = $scriptName.parentDir().hash().abs() prjCacheDir = cacheDir / scriptName.splitFile().name & "_" & shash + nimscriptApiFile = cacheDir / "nimscriptapi.nim" result = prjCacheDir / scriptName.extractFilename().changeFileExt ".nims" -proc setupNimscript(scriptName: string, options: Options) = let - cacheDir = getTempDir() / "nimblecache" - nimscriptApiFile = cacheDir / "nimscriptapi.nim" - nimsFile = getNimsFile(scriptName, options) + iniFile = result.changeFileExt(".ini") - let isNimscriptApiCached = nimscriptApiFile.fileExists() and nimscriptApiFile.getLastModificationTime() > getAppFilename().getLastModificationTime() isScriptResultCached = - nimsFile.fileExists() and nimsFile.getLastModificationTime() > + isNimscriptApiCached and result.fileExists() and result.getLastModificationTime() > scriptName.getLastModificationTime() if not isNimscriptApiCached: @@ -80,14 +77,15 @@ proc setupNimscript(scriptName: string, options: Options) = writeFile(nimscriptApiFile, nimscriptApi) if not isScriptResultCached: - createDir(nimsFile.parentDir()) - writeFile(nimsFile, """ + createDir(result.parentDir()) + writeFile(result, """ import system except getCommand, setCommand, switch, `--`, packageName, version, author, description, license, srcDir, binDir, backend, skipDirs, skipFiles, skipExt, installDirs, installFiles, installExt, bin, foreignDeps, requires, task, packageName """ & "import nimscriptapi, strutils\n" & scriptName.readFile() & "\nonExit()\n") + discard tryRemoveFile(iniFile) proc getIniFile*(scriptName: string, options: Options): string = let @@ -101,7 +99,6 @@ proc getIniFile*(scriptName: string, options: Options): string = scriptName.getLastModificationTime() if not isIniResultCached: - setupNimscript(scriptName, options) let (output, exitCode) = execNimscript(nimsFile, scriptName.parentDir(), "printPkgInfo", options, live=false) @@ -116,9 +113,6 @@ proc execScript(scriptName, actionName: string, options: Options): let nimsFile = getNimsFile(scriptName, options) - if not nimsFile.fileExists(): - setupNimScript(scriptName, options) - let (output, exitCode) = execNimscript(nimsFile, scriptName.parentDir(), actionName, options) diff --git a/tests/nimscript/nimscript.nimble b/tests/nimscript/nimscript.nimble index 4625abb..f27631a 100644 --- a/tests/nimscript/nimscript.nimble +++ b/tests/nimscript/nimscript.nimble @@ -2,7 +2,9 @@ version = "0.1.0" author = "Dominik Picheta" -description = "Test package" +description = """Test package +with multi-line description +""" license = "BSD" bin = @["nimscript"] @@ -24,6 +26,8 @@ task cr, "Testing `nimble c -r nimscript.nim` via setCommand": task repeated, "Testing `nimble c nimscript.nim` with repeated flags": --define: foo --define: bar + --define: "quoted" + --define: "quoted\\\"with\\\"quotes" setCommand "c", "nimscript.nim" task api, "Testing nimscriptapi module functionality": From deb20ee57a71476610a3cc6e149c04d1b8c0a74f Mon Sep 17 00:00:00 2001 From: Ganesh Viswanathan Date: Tue, 30 Apr 2019 21:29:04 -0500 Subject: [PATCH 05/95] Print nimscript errors --- src/nimblepkg/nimscriptwrapper.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nimblepkg/nimscriptwrapper.nim b/src/nimblepkg/nimscriptwrapper.nim index 4002361..f0b2f86 100755 --- a/src/nimblepkg/nimscriptwrapper.nim +++ b/src/nimblepkg/nimscriptwrapper.nim @@ -50,7 +50,7 @@ proc execNimscript(nimsFile, projectDir, actionName: string, options: Options, result.output = outFile.readFile() discard outFile.tryRemoveFile() else: - result = execCmdEx(cmd, options = {poUsePath}) + result = execCmdEx(cmd, options = {poUsePath, poStdErrToStdOut}) proc getNimsFile(scriptName: string, options: Options): string = let From 83a1cceb4eaa473560a356b911d8ac1866587512 Mon Sep 17 00:00:00 2001 From: Ganesh Viswanathan Date: Thu, 2 May 2019 13:42:59 -0500 Subject: [PATCH 06/95] Fix for recursive nimble calls --- src/nimblepkg/nimscriptwrapper.nim | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/nimblepkg/nimscriptwrapper.nim b/src/nimblepkg/nimscriptwrapper.nim index f0b2f86..0956b79 100755 --- a/src/nimblepkg/nimscriptwrapper.nim +++ b/src/nimblepkg/nimscriptwrapper.nim @@ -36,7 +36,9 @@ proc execNimscript(nimsFile, projectDir, actionName: string, options: Options, nimsFile.copyFile(nimsFileCopied) defer: - nimsFileCopied.removeFile() + # Only if copied in this invocation, allows recursive calls of nimble + if not isScriptResultCopied: + nimsFileCopied.removeFile() let cmd = ("nim e --hints:off --verbosity:0 -p:" & (getTempDir() / "nimblecache").quoteShell & From dcf99adf91c57cd218eec372eeee49a5242ce4b1 Mon Sep 17 00:00:00 2001 From: Ganesh Viswanathan Date: Fri, 3 May 2019 13:06:42 -0500 Subject: [PATCH 07/95] Test case for #645 - recursive --- tests/recursive/recursive.nimble | 21 +++++++++++++++++++++ tests/tester.nim | 4 ++++ 2 files changed, 25 insertions(+) create mode 100644 tests/recursive/recursive.nimble diff --git a/tests/recursive/recursive.nimble b/tests/recursive/recursive.nimble new file mode 100644 index 0000000..ec09ab9 --- /dev/null +++ b/tests/recursive/recursive.nimble @@ -0,0 +1,21 @@ +# Package + +version = "0.1.0" +author = "Dominik Picheta" +description = "Test package" +license = "BSD" + +# Dependencies + +requires "nim >= 0.12.1" + +task recurse, "Level 1": + echo 1 + exec "nimble recurse2" + +task recurse2, "Level 2": + echo 2 + exec "nimble recurse3" + +task recurse3, "Level 3": + echo 3 diff --git a/tests/tester.nim b/tests/tester.nim index b57ab18..1106998 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -82,6 +82,10 @@ test "caching works": check output.contains("0.2.0") writeFile(nfile, readFile(nfile).replace("0.2.0", "0.1.0")) +test "recursion works": + cd "recursive": + check execNimble("recurse").exitCode == QuitSuccess + test "picks #head when looking for packages": cd "versionClashes" / "aporiaScenario": let (output, exitCode) = execNimble("install", "-y", "--verbose") From 5b94a1b70cf0666cc6b21a3c504b98e6146ea818 Mon Sep 17 00:00:00 2001 From: Julian Fondren Date: Sat, 4 May 2019 18:58:12 -0500 Subject: [PATCH 08/95] test -c,--continue option to continue tests on error --- src/nimble.nim | 19 ++++++++++++++++--- src/nimblepkg/options.nim | 5 +++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/nimble.nim b/src/nimble.nim index e0b10f3..f1034df 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -992,7 +992,9 @@ proc develop(options: Options) = proc test(options: Options) = ## Executes all tests starting with 't' in the ``tests`` directory. ## Subdirectories are not walked. - var files = toSeq(walkDir(getCurrentDir() / "tests")) + var + files = toSeq(walkDir(getCurrentDir() / "tests")) + tests, failures: int if files.len < 1: display("Warning:", "No tests found!", Warning, HighPriority) @@ -1014,7 +1016,14 @@ proc test(options: Options) = binFileName = file.path.changeFileExt(ExeExt) existsBefore = existsFile(binFileName) - execBackend(optsCopy) + if options.continueTestsOnFailure: + inc tests + try: + execBackend(optsCopy) + except NimbleError: + inc failures + else: + execBackend(optsCopy) let existsAfter = existsFile(binFileName) @@ -1022,7 +1031,11 @@ proc test(options: Options) = if canRemove: removeFile(binFileName) - display("Success:", "All tests passed", Success, HighPriority) + if failures == 0: + display("Success:", "All tests passed", Success, HighPriority) + else: + let error = "Only " & $(tests - failures) & "/" & $tests & " tests passed" + display("Error:", error, Error, HighPriority) proc check(options: Options) = ## Validates a package a in the current working directory. diff --git a/src/nimblepkg/options.nim b/src/nimblepkg/options.nim index 749a4ed..70924bd 100644 --- a/src/nimblepkg/options.nim +++ b/src/nimblepkg/options.nim @@ -23,6 +23,7 @@ type showVersion*: bool noColor*: bool disableValidation*: bool + continueTestsOnFailure*: bool ## Whether packages' repos should always be downloaded with their history. forceFullClone*: bool @@ -77,6 +78,7 @@ Commands: c, cc, js [opts, ...] f.nim Builds a file inside a package. Passes options to the Nim compiler. test Compiles and executes tests + [-c, --continue] Don't stop execution on a failed test. doc, doc2 [opts, ...] f.nim Builds documentation for a file inside a package. Passes options to the Nim compiler. refresh [url] Refreshes the package list. A package list URL @@ -316,6 +318,9 @@ proc parseFlag*(flag, val: string, result: var Options, kind = cmdLongOption) = else: result.action.compileOptions.add(prefix & flag & ":" & val) of actionCustom: + if result.action.command.normalize == "test": + if f == "continue" or f == "c": + result.continueTestsOnFailure = true result.action.flags[flag] = val else: wasFlagHandled = false From b2e31fb012458ef7e2a0dab86dfc869329504b29 Mon Sep 17 00:00:00 2001 From: Julian Fondren Date: Sat, 4 May 2019 19:30:55 -0500 Subject: [PATCH 09/95] use displayLine rather than display with displayCategory prompt --- src/nimblepkg/cli.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nimblepkg/cli.nim b/src/nimblepkg/cli.nim index d8d52d5..8d96015 100644 --- a/src/nimblepkg/cli.nim +++ b/src/nimblepkg/cli.nim @@ -140,7 +140,7 @@ proc prompt*(forcePrompts: ForcePrompt, question: string): bool = display("Prompt:", question & " -> [forced no]", Warning, HighPriority) return false of dontForcePrompt: - display("Prompt:", question & " [y/N]", Warning, HighPriority) + displayLine("Prompt:", question & " [y/N]", Warning, HighPriority) displayCategory("Answer:", Warning, HighPriority) let yn = stdin.readLine() case yn.normalize From cf7c1471217323893059f750da5ac82b096addda Mon Sep 17 00:00:00 2001 From: Christopher Dunn Date: Sat, 25 May 2019 16:59:53 -0500 Subject: [PATCH 10/95] Choose USER-specific tmpdir re: #80 --- src/nimblepkg/nimscriptsupport.nim | 4 ++-- src/nimblepkg/publish.nim | 2 +- src/nimblepkg/tools.nim | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/nimblepkg/nimscriptsupport.nim b/src/nimblepkg/nimscriptsupport.nim index 2806251..4c8747b 100644 --- a/src/nimblepkg/nimscriptsupport.nim +++ b/src/nimblepkg/nimscriptsupport.nim @@ -15,7 +15,7 @@ from compiler/scriptconfig import setupVM from compiler/astalgo import strTableGet import compiler/options as compiler_options -import common, version, options, packageinfo, cli +import common, version, options, packageinfo, cli, tools import os, strutils, strtabs, tables, times, osproc, sets, pegs when not declared(resetAllModulesHard): @@ -382,7 +382,7 @@ proc execScript(scriptName: string, flags: Flags, options: Options): PSym = # Ensure that "nimblepkg/nimscriptapi" is in the PATH. block: - let t = getTempDir() / "nimblecache" + let t = getNimbleUserTempDir() / "nimblecache" let tmpNimscriptApiPath = t / "nimblepkg" / "nimscriptapi.nim" createDir(tmpNimscriptApiPath.splitFile.dir) writeFile(tmpNimscriptApiPath, nimscriptApi) diff --git a/src/nimblepkg/publish.nim b/src/nimblepkg/publish.nim index 2d73478..333bbbd 100644 --- a/src/nimblepkg/publish.nim +++ b/src/nimblepkg/publish.nim @@ -155,7 +155,7 @@ proc editJson(p: PackageInfo; url, tags, downloadMethod: string) = proc publish*(p: PackageInfo, o: Options) = ## Publishes the package p. let auth = getGithubAuth(o) - var pkgsDir = getTempDir() / "nimble-packages-fork" + var pkgsDir = getNimbleUserTempDir() / "nimble-packages-fork" if not forkExists(auth): createFork(auth) display("Info:", "Waiting 10s to let Github create a fork", diff --git a/src/nimblepkg/tools.nim b/src/nimblepkg/tools.nim index 8dc71e2..2073154 100644 --- a/src/nimblepkg/tools.nim +++ b/src/nimblepkg/tools.nim @@ -162,3 +162,17 @@ proc getNimbleTempDir*(): string = result.add($GetCurrentProcessId()) else: result.add($getpid()) + +proc getNimbleUserTempDir*(): string = + ## Returns a path to a temporary directory. + ## + ## The returned path will be the same for the duration of the process but + ## different for different runs of it. You have to make sure to create it + ## first. In release builds the directory will be removed when nimble finishes + ## its work. + var tmpdir: string + if existsEnv("TMPDIR") and existsEnv("USER"): + tmpdir = joinPath(getEnv("TMPDIR"), getEnv("USER")) + else: + tmpdir = getTempDir() + return tmpdir From 06b9b494496a622cd444e992142af4cdbcfca416 Mon Sep 17 00:00:00 2001 From: genotrance Date: Mon, 27 May 2019 13:29:19 -0500 Subject: [PATCH 11/95] Update to 0.10.0 (#657) * Update changelog for 0.10.0 * Multi-user systems * Minor fixes * Update changelog.markdown --- changelog.markdown | 33 +++++++++++++++++++++++++++++++++ nimble.nimble | 2 +- src/nimblepkg/common.nim | 2 +- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/changelog.markdown b/changelog.markdown index 2b1e385..1f417e6 100644 --- a/changelog.markdown +++ b/changelog.markdown @@ -3,6 +3,39 @@ # Nimble changelog +## 0.10.0 - 27/05/2019 + +Nimble now uses the Nim compiler directly via `nim e` to execute nimble +scripts rather than embedding the Nim VM. This has multiple benefits: +- Evolve independently from Nim enabling new versions of Nimble to work + with multiple versions of Nim. +- Inherit all nimscript enhancements and bug fixes rather than having to + duplicate functionality. +- Fast build time and smaller binary. +- No dependency on the compiler package which could cause dependency issues + when nimble is used as a package. + +Several other features and fixes have been implemented to improve general +development and test workflows. +- `nimble test` now sports a `-continue` or `-c` flag that allows tests + to continue on failure, removes all created test binaries on completion + and warns if no tests found. +- The `--inclDeps` or `-i` flag enables `nimble uninstall` to remove all + dependent packages during uninstall. +- Added documentation on the usage of a custom `nimbleDir`. +- Package type interactive prompt is more readable. +- Save temporary files in a per-user temp dir to enable Nimble on multi-user + systems. +- CTRL-C is now handled correctly in interactive prompts. +- Fixed issue where empty package list led to error. +- Fixed issue where file:// was prepended incorrectly. +- Fixed miscellaneous issues in version parsing, Github auth and briefClone. +- Miscellaneous cleanup of deprecated procs. + +---- + +Full changelog: https://github.com/nim-lang/nimble/compare/v0.9.0...v0.10.0 + ## 0.9.0 - 19/09/2018 This is a major new release which contains at least one breaking change. diff --git a/nimble.nimble b/nimble.nimble index 66c1b63..8de754f 100644 --- a/nimble.nimble +++ b/nimble.nimble @@ -1,6 +1,6 @@ # Package -version = "0.9.0" +version = "0.10.0" author = "Dominik Picheta" description = "Nim package manager." license = "BSD" diff --git a/src/nimblepkg/common.nim b/src/nimblepkg/common.nim index 19faed5..676a2ff 100644 --- a/src/nimblepkg/common.nim +++ b/src/nimblepkg/common.nim @@ -63,4 +63,4 @@ when not defined(nimscript): return (error, hint) const - nimbleVersion* = "0.9.0" + nimbleVersion* = "0.10.0" From 9beb6e1529546f82d50c1b1bfdc20ca79cc41174 Mon Sep 17 00:00:00 2001 From: Taylor Rose <3nki.nam.shub@gmail.com> Date: Wed, 29 May 2019 19:38:53 -0400 Subject: [PATCH 12/95] Add backend selection to nimble init --- src/nimble.nim | 9 +++++++++ src/nimblepkg/init.nim | 10 ++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/nimble.nim b/src/nimble.nim index 94e677a..2eda5c5 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -802,6 +802,14 @@ This should ideally be a valid SPDX identifier. See https://spdx.org/licenses/. Please specify a valid SPDX identifier.""", "MIT" ) + var pkgBackend = options.promptList( + """Package Backend? +c - Compile using C backend. +cpp - Compile using C++ backend. +objc - Compile using Objective-C backend. +js - Compile using JavaScript backend.""", + ["c", "cpp", "objc", "js"] + ) # Ask for Nim dependency let nimDepDef = getNimrodVersion() @@ -816,6 +824,7 @@ Please specify a valid SPDX identifier.""", pkgAuthor, pkgDesc, pkgLicense, + pkgBackend, pkgSrcDir, pkgNimDep, pkgType diff --git a/src/nimblepkg/init.nim b/src/nimblepkg/init.nim index 42c7f4e..0617d97 100644 --- a/src/nimblepkg/init.nim +++ b/src/nimblepkg/init.nim @@ -9,6 +9,7 @@ type pkgAuthor: string pkgDesc: string pkgLicense: string + pkgBackend: string pkgSrcDir: string pkgNimDep: string pkgType: string @@ -149,6 +150,10 @@ test "correct welcome": # Write the nimble file let nimbleFile = pkgRoot / info.pkgName.changeFileExt("nimble") + # Only write backend if it isn't "c" + var pkgBackend = "" + if (info.pkgBackend != "c"): + pkgBackend = "backend = " & info.pkgbackend.escape() writeFile(nimbleFile, """# Package version = $# @@ -157,6 +162,7 @@ description = $# license = $# srcDir = $# $# +$# # Dependencies @@ -164,8 +170,8 @@ requires "nim >= $#" """ % [ info.pkgVersion.escape(), info.pkgAuthor.escape(), info.pkgDesc.escape(), info.pkgLicense.escape(), info.pkgSrcDir.escape(), nimbleFileOptions, - info.pkgNimDep + pkgBackend, info.pkgNimDep ] ) - display("Info:", "Nimble file created successfully", priority=MediumPriority) \ No newline at end of file + display("Info:", "Nimble file created successfully", priority=MediumPriority) From d800fb452564912fa3245caedf1ef1df3331d2b8 Mon Sep 17 00:00:00 2001 From: Ganesh Viswanathan Date: Fri, 31 May 2019 16:58:54 -0500 Subject: [PATCH 13/95] Fix object variants - nim issue 1286 --- src/nimble.nim | 2 +- src/nimblepkg/options.nim | 4 ++-- src/nimblepkg/version.nim | 31 +++++++++++++------------------ 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/nimble.nim b/src/nimble.nim index 2eda5c5..1b01502 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -1015,7 +1015,7 @@ proc test(options: Options) = let (_, name, ext) = file.path.splitFile() if ext == ".nim" and name[0] == 't' and file.kind in {pcFile, pcLinkToFile}: var optsCopy = options.briefClone() - optsCopy.action.typ = actionCompile + optsCopy.action = Action(typ: actionCompile) optsCopy.action.file = file.path optsCopy.action.backend = "c" optsCopy.action.compileOptions = @[] diff --git a/src/nimblepkg/options.nim b/src/nimblepkg/options.nim index 70924bd..f49ed04 100644 --- a/src/nimblepkg/options.nim +++ b/src/nimblepkg/options.nim @@ -238,7 +238,7 @@ proc getBinDir*(options: Options): string = options.getNimbleDir() / "bin" proc parseCommand*(key: string, result: var Options) = - result.action.typ = parseActionType(key) + result.action = Action(typ: parseActionType(key)) initAction(result, key) proc parseArgument*(key: string, result: var Options) = @@ -329,7 +329,7 @@ proc parseFlag*(flag, val: string, result: var Options, kind = cmdLongOption) = raise newException(NimbleError, "Unknown option: --" & flag) proc initOptions*(): Options = - result.action.typ = actionNil + result.action = Action(typ: actionNil) result.pkgInfoCache = newTable[string, PackageInfo]() result.nimbleDir = "" result.verbosity = HighPriority diff --git a/src/nimblepkg/version.nim b/src/nimblepkg/version.nim index ae85e99..a3e7c4a 100644 --- a/src/nimblepkg/version.nim +++ b/src/nimblepkg/version.nim @@ -130,34 +130,32 @@ proc contains*(ran: VersionRange, ver: Version): bool = return withinRange(ver, ran) proc makeRange*(version: string, op: string): VersionRange = - new(result) if version == "": raise newException(ParseVersionError, "A version needs to accompany the operator.") case op of ">": - result.kind = verLater + result = VersionRange(kind: verLater) of "<": - result.kind = verEarlier + result = VersionRange(kind: verEarlier) of ">=": - result.kind = verEqLater + result = VersionRange(kind: verEqLater) of "<=": - result.kind = verEqEarlier + result = VersionRange(kind: verEqEarlier) of "": - result.kind = verEq + result = VersionRange(kind: verEq) else: raise newException(ParseVersionError, "Invalid operator: " & op) result.ver = Version(version) proc parseVersionRange*(s: string): VersionRange = # >= 1.5 & <= 1.8 - new(result) if s.len == 0: - result.kind = verAny + result = VersionRange(kind: verAny) return if s[0] == '#': - result.kind = verSpecial + result = VersionRange(kind: verSpecial) result.spe = s.Version return @@ -169,7 +167,7 @@ proc parseVersionRange*(s: string): VersionRange = of '>', '<', '=': op.add(s[i]) of '&': - result.kind = verIntersect + result = VersionRange(kind: verIntersect) result.verILeft = makeRange(version, op) # Parse everything after & @@ -204,10 +202,10 @@ proc toVersionRange*(ver: Version): VersionRange = ## Converts a version to either a verEq or verSpecial VersionRange. new(result) if ver.isSpecial: - result.kind = verSpecial + result = VersionRange(kind: verSpecial) result.spe = ver else: - result.kind = verEq + result = VersionRange(kind: verEq) result.ver = ver proc parseRequires*(req: string): PkgTuple = @@ -263,17 +261,14 @@ proc getSimpleString*(verRange: VersionRange): string = result = "" proc newVRAny*(): VersionRange = - new(result) - result.kind = verAny + result = VersionRange(kind: verAny) proc newVREarlier*(ver: string): VersionRange = - new(result) - result.kind = verEarlier + result = VersionRange(kind: verEarlier) result.ver = newVersion(ver) proc newVREq*(ver: string): VersionRange = - new(result) - result.kind = verEq + result = VersionRange(kind: verEq) result.ver = newVersion(ver) proc findLatest*(verRange: VersionRange, From cbd63e61dec2360a17a640987298ca702d69d4c3 Mon Sep 17 00:00:00 2001 From: Ganesh Viswanathan Date: Fri, 31 May 2019 12:49:15 -0500 Subject: [PATCH 14/95] Fix outfile issue when binary exists --- src/nimblepkg/nimscriptapi.nim | 17 ++++++++++++----- src/nimblepkg/nimscriptwrapper.nim | 9 +++++---- 2 files changed, 17 insertions(+), 9 deletions(-) mode change 100755 => 100644 src/nimblepkg/nimscriptapi.nim mode change 100755 => 100644 src/nimblepkg/nimscriptwrapper.nim diff --git a/src/nimblepkg/nimscriptapi.nim b/src/nimblepkg/nimscriptapi.nim old mode 100755 new mode 100644 index eba18cd..0ace797 --- a/src/nimblepkg/nimscriptapi.nim +++ b/src/nimblepkg/nimscriptapi.nim @@ -35,6 +35,7 @@ var success = false retVal = true projectFile = "" + outFile = "" proc requires*(deps: varargs[string]) = ## Call this to set the list of requirements of your Nimble @@ -42,13 +43,18 @@ proc requires*(deps: varargs[string]) = for d in deps: requiresData.add(d) proc getParams() = + # Called by nimscriptwrapper.nim:execNimscript() + # nim e --flags /full/path/to/file.nims /full/path/to/file.out action for i in 2 .. paramCount(): let param = paramStr(i) - if param.fileExists(): - projectFile = param - elif param[0] != '-': - commandLineParams.add paramStr(i).normalize + if param[0] != '-': + if projectFile.len == 0: + projectFile = param + elif outFile.len == 0: + outFile = param + else: + commandLineParams.add param.normalize proc getCommand*(): string = return command @@ -136,7 +142,8 @@ proc onExit*() = output &= "\"retVal\": " & $retVal - writeFile(projectFile & ".out", "{" & output & "}") + if outFile.len != 0: + writeFile(outFile, "{" & output & "}") # TODO: New release of Nim will move this `task` template under a # `when not defined(nimble)`. This will allow us to override it in the future. diff --git a/src/nimblepkg/nimscriptwrapper.nim b/src/nimblepkg/nimscriptwrapper.nim old mode 100755 new mode 100644 index 0956b79..ff28e8e --- a/src/nimblepkg/nimscriptwrapper.nim +++ b/src/nimblepkg/nimscriptwrapper.nim @@ -4,7 +4,7 @@ ## Implements the new configuration system for Nimble. Uses Nim as a ## scripting language. -import common, version, options, packageinfo, cli +import common, version, options, packageinfo, cli, tools import hashes, json, os, streams, strutils, strtabs, tables, times, osproc, sets, pegs @@ -26,6 +26,7 @@ proc execNimscript(nimsFile, projectDir, actionName: string, options: Options, let shash = $projectDir.hash().abs() nimsFileCopied = projectDir / nimsFile.splitFile().name & "_" & shash & ".nims" + outFile = getNimbleTempDir() & ".out" let isScriptResultCopied = @@ -42,12 +43,12 @@ proc execNimscript(nimsFile, projectDir, actionName: string, options: Options, let cmd = ("nim e --hints:off --verbosity:0 -p:" & (getTempDir() / "nimblecache").quoteShell & - " " & nimsFileCopied.quoteShell & " " & actionName).strip() + " " & nimsFileCopied.quoteShell & " " & outFile.quoteShell & " " & actionName).strip() + + displayDebug("Executing " & cmd) if live: result.exitCode = execCmd(cmd) - let - outFile = nimsFileCopied & ".out" if outFile.fileExists(): result.output = outFile.readFile() discard outFile.tryRemoveFile() From 8dc036c8961975a117e42f4ebde97bb08845e0f6 Mon Sep 17 00:00:00 2001 From: Ganesh Viswanathan Date: Fri, 31 May 2019 14:33:31 -0500 Subject: [PATCH 15/95] Update Nim versions, fix recursive test --- .travis.yml | 6 +++--- tests/recursive/recursive.nimble | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0e5d3a5..95b29c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,12 +5,12 @@ os: language: c env: - - BRANCH=0.19.4 - - BRANCH=#4f9366975441be889a8cd4fbfb4e41f6830dc542 + - BRANCH=0.19.6 + - BRANCH=#44cc5f6360c7ccc96c296948b2524bd2cdebf1f0 cache: directories: - - "$HOME/.choosenim/toolchains/nim-0.19.4" + - "$HOME/.choosenim/toolchains/nim-0.19.6" install: - export CHOOSENIM_CHOOSE_VERSION=$BRANCH diff --git a/tests/recursive/recursive.nimble b/tests/recursive/recursive.nimble index ec09ab9..a188170 100644 --- a/tests/recursive/recursive.nimble +++ b/tests/recursive/recursive.nimble @@ -11,11 +11,11 @@ requires "nim >= 0.12.1" task recurse, "Level 1": echo 1 - exec "nimble recurse2" + exec "../../src/nimble recurse2" task recurse2, "Level 2": echo 2 - exec "nimble recurse3" + exec "../../src/nimble recurse3" task recurse3, "Level 3": echo 3 From 374f62614a67f6f6701213366c0b112857d66e18 Mon Sep 17 00:00:00 2001 From: Ganesh Viswanathan Date: Fri, 31 May 2019 14:49:58 -0500 Subject: [PATCH 16/95] Improved test naming --- tests/tester.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/tester.nim b/tests/tester.nim index 1106998..1448869 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -71,7 +71,7 @@ proc inLines(lines: seq[string], line: string): bool = for i in lines: if line.normalize in i.normalize: return true -test "caching works": +test "caching of nims and ini detects changes": cd "caching": var (output, exitCode) = execNimble("dump") check output.contains("0.1.0") @@ -82,7 +82,7 @@ test "caching works": check output.contains("0.2.0") writeFile(nfile, readFile(nfile).replace("0.2.0", "0.1.0")) -test "recursion works": +test "tasks can be called recursively": cd "recursive": check execNimble("recurse").exitCode == QuitSuccess From be83dcdca9385a2818fec096096c75db230c8d15 Mon Sep 17 00:00:00 2001 From: Ganesh Viswanathan Date: Fri, 31 May 2019 22:36:53 -0500 Subject: [PATCH 17/95] Unique nims filename to enable parallelism --- src/nimblepkg/nimscriptwrapper.nim | 3 +-- src/nimblepkg/tools.nim | 17 ++++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/nimblepkg/nimscriptwrapper.nim b/src/nimblepkg/nimscriptwrapper.nim index ff28e8e..473fdad 100644 --- a/src/nimblepkg/nimscriptwrapper.nim +++ b/src/nimblepkg/nimscriptwrapper.nim @@ -24,8 +24,7 @@ const proc execNimscript(nimsFile, projectDir, actionName: string, options: Options, live = true): tuple[output: string, exitCode: int] = let - shash = $projectDir.hash().abs() - nimsFileCopied = projectDir / nimsFile.splitFile().name & "_" & shash & ".nims" + nimsFileCopied = projectDir / nimsFile.splitFile().name & "_" & getProcessId() & ".nims" outFile = getNimbleTempDir() & ".out" let diff --git a/src/nimblepkg/tools.nim b/src/nimblepkg/tools.nim index 2073154..acfa9a9 100644 --- a/src/nimblepkg/tools.nim +++ b/src/nimblepkg/tools.nim @@ -148,6 +148,15 @@ proc contains*(j: JsonNode, elem: tuple[key: string, val: JsonNode]): bool = when not defined(windows): from posix import getpid + +proc getProcessId*(): string = + when defined(windows): + proc GetCurrentProcessId(): int32 {.stdcall, dynlib: "kernel32", + importc: "GetCurrentProcessId".} + result = $GetCurrentProcessId() + else: + result = $getpid() + proc getNimbleTempDir*(): string = ## Returns a path to a temporary directory. ## @@ -155,13 +164,7 @@ proc getNimbleTempDir*(): string = ## different for different runs of it. You have to make sure to create it ## first. In release builds the directory will be removed when nimble finishes ## its work. - result = getTempDir() / "nimble_" - when defined(windows): - proc GetCurrentProcessId(): int32 {.stdcall, dynlib: "kernel32", - importc: "GetCurrentProcessId".} - result.add($GetCurrentProcessId()) - else: - result.add($getpid()) + result = getTempDir() / "nimble_" & getProcessId() proc getNimbleUserTempDir*(): string = ## Returns a path to a temporary directory. From 016f42c34a2bd2c8921045b453038d68b55928fc Mon Sep 17 00:00:00 2001 From: Ganesh Viswanathan Date: Sun, 2 Jun 2019 00:42:41 -0500 Subject: [PATCH 18/95] Fix issue #597 - error if bin is a source file --- src/nimblepkg/packageparser.nim | 2 ++ tests/issue597/dummy.nimble | 12 ++++++++++++ tests/issue597/test.nim | 0 tests/tester.nim | 6 ++++++ 4 files changed, 20 insertions(+) mode change 100755 => 100644 src/nimblepkg/packageparser.nim create mode 100644 tests/issue597/dummy.nimble create mode 100644 tests/issue597/test.nim diff --git a/src/nimblepkg/packageparser.nim b/src/nimblepkg/packageparser.nim old mode 100755 new mode 100644 index 5c034eb..4a6d5db --- a/src/nimblepkg/packageparser.nim +++ b/src/nimblepkg/packageparser.nim @@ -253,6 +253,8 @@ proc readPackageInfoFromNimble(path: string; result: var PackageInfo) = result.installExt.add(ev.value.multiSplit) of "bin": for i in ev.value.multiSplit: + if i.splitFile().ext == ".nim": + raise newException(NimbleError, "`bin` entry should not be a source file: " & i) result.bin.add(i.addFileExt(ExeExt)) of "backend": result.backend = ev.value.toLowerAscii() diff --git a/tests/issue597/dummy.nimble b/tests/issue597/dummy.nimble new file mode 100644 index 0000000..13bf4be --- /dev/null +++ b/tests/issue597/dummy.nimble @@ -0,0 +1,12 @@ +# Package + +version = "0.1.0" +author = "Author" +description = "dummy" +license = "MIT" + +# Dependencies + +requires "nim >= 0.17.0" + +bin = @["test.nim"] diff --git a/tests/issue597/test.nim b/tests/issue597/test.nim new file mode 100644 index 0000000..e69de29 diff --git a/tests/tester.nim b/tests/tester.nim index 1448869..bd7a306 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -585,6 +585,12 @@ test "can pass args with spaces to Nim (#351)": checkpoint output check exitCode == QuitSuccess +test "error if `bin` is a source file (#597)": + cd "issue597": + var (output, exitCode) = execNimble("build") + check exitCode != QuitSuccess + check output.contains("entry should not be a source file: test.nim") + suite "reverse dependencies": test "basic test": cd "revdep/mydep": From 21616e35a78594a7335005d81efe2c91eec6824f Mon Sep 17 00:00:00 2001 From: genotrance Date: Sun, 2 Jun 2019 06:12:20 -0500 Subject: [PATCH 19/95] Fix issue #655 (#661) * Fix issue #655 * Update nimscriptwrapper.nim --- src/nimblepkg/nimscriptwrapper.nim | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/nimblepkg/nimscriptwrapper.nim b/src/nimblepkg/nimscriptwrapper.nim index 473fdad..39df926 100644 --- a/src/nimblepkg/nimscriptwrapper.nim +++ b/src/nimblepkg/nimscriptwrapper.nim @@ -119,7 +119,12 @@ proc execScript(scriptName, actionName: string, options: Options): (output, exitCode) = execNimscript(nimsFile, scriptName.parentDir(), actionName, options) if exitCode != 0: - raise newException(NimbleError, output) + let errMsg = + if output.len != 0: + output + else: + "Exception raised during nimble script execution" + raise newException(NimbleError, errMsg) let j = From d15c8530cb7480ce39ffa85a2dd9819d2d4fc645 Mon Sep 17 00:00:00 2001 From: Ganesh Viswanathan Date: Mon, 3 Jun 2019 13:56:49 -0500 Subject: [PATCH 20/95] Version 0.10.2 --- changelog.markdown | 18 ++++++++++++++++++ nimble.nimble | 2 +- src/nimblepkg/common.nim | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/changelog.markdown b/changelog.markdown index 1f417e6..c8ae9cf 100644 --- a/changelog.markdown +++ b/changelog.markdown @@ -3,6 +3,24 @@ # Nimble changelog +## 0.10.2 - 03/06/2019 + +This is a small release which avoids object variant changes that are now +treated as runtime errors (Nim #1286). It also adds support for `backend` +selection during `nimble init`. + +Multiple bug fixes are also included: +- Fixed an issue where failing tasks were not returning a non-zero return + value (#655). +- Error out if `bin` is a Nim source file (#597). +- Fixed an issue where nimble task would not run if file of same name exists. +- Fixed an issue that prevented multiple instances of nimble from running on + the same package. + +---- + +Full changelog: https://github.com/nim-lang/nimble/compare/v0.10.0...v0.10.2 + ## 0.10.0 - 27/05/2019 Nimble now uses the Nim compiler directly via `nim e` to execute nimble diff --git a/nimble.nimble b/nimble.nimble index 8de754f..5344094 100644 --- a/nimble.nimble +++ b/nimble.nimble @@ -1,6 +1,6 @@ # Package -version = "0.10.0" +version = "0.10.2" author = "Dominik Picheta" description = "Nim package manager." license = "BSD" diff --git a/src/nimblepkg/common.nim b/src/nimblepkg/common.nim index 676a2ff..abb8094 100644 --- a/src/nimblepkg/common.nim +++ b/src/nimblepkg/common.nim @@ -63,4 +63,4 @@ when not defined(nimscript): return (error, hint) const - nimbleVersion* = "0.10.0" + nimbleVersion* = "0.10.2" From 2c87a7fe5ec2c07db8596195b1db297c48a4f232 Mon Sep 17 00:00:00 2001 From: Ganesh Viswanathan Date: Tue, 11 Jun 2019 11:39:32 -0500 Subject: [PATCH 21/95] Add 0.20.0 to test matrix, latest nightlies --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 95b29c0..2a17224 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,11 +6,13 @@ language: c env: - BRANCH=0.19.6 - - BRANCH=#44cc5f6360c7ccc96c296948b2524bd2cdebf1f0 + - BRANCH=0.20.0 + - BRANCH=#ced0527ae334439a10e1719d1eccb727c19dc781 cache: directories: - "$HOME/.choosenim/toolchains/nim-0.19.6" + - "$HOME/.choosenim/toolchains/nim-0.20.0" install: - export CHOOSENIM_CHOOSE_VERSION=$BRANCH From 513780a38249d27adab2a120eacd63762dba5960 Mon Sep 17 00:00:00 2001 From: Ganesh Viswanathan Date: Tue, 11 Jun 2019 10:54:00 -0500 Subject: [PATCH 22/95] Fix #665 - avoid stdout in printPkgInfo --- src/nimblepkg/nimscriptapi.nim | 20 +++++++++----------- src/nimblepkg/nimscriptwrapper.nim | 17 +++++++---------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/nimblepkg/nimscriptapi.nim b/src/nimblepkg/nimscriptapi.nim index 0ace797..fffad4d 100644 --- a/src/nimblepkg/nimscriptapi.nim +++ b/src/nimblepkg/nimscriptapi.nim @@ -81,20 +81,19 @@ template `--`*(key: untyped) = template printIfLen(varName) = if varName.len != 0: - iniOut &= astToStr(varName) & ": \"\"\"" & varName & "\"\"\"\n" + result &= astToStr(varName) & ": \"\"\"" & varName & "\"\"\"\n" template printSeqIfLen(varName) = if varName.len != 0: - iniOut &= astToStr(varName) & ": \"" & varName.join(", ") & "\"\n" + result &= astToStr(varName) & ": \"" & varName.join(", ") & "\"\n" -proc printPkgInfo() = +proc printPkgInfo(): string = if backend.len == 0: backend = "c" - var - iniOut = "[Package]\n" + result = "[Package]\n" if packageName.len != 0: - iniOut &= "name: \"" & packageName & "\"\n" + result &= "name: \"" & packageName & "\"\n" printIfLen version printIfLen author printIfLen description @@ -114,14 +113,13 @@ proc printPkgInfo() = printSeqIfLen afterHooks if requiresData.len != 0: - iniOut &= "\n[Deps]\n" - iniOut &= &"requires: \"{requiresData.join(\", \")}\"\n" - - echo iniOut + result &= "\n[Deps]\n" + result &= &"requires: \"{requiresData.join(\", \")}\"\n" proc onExit*() = if "printPkgInfo".normalize in commandLineParams: - printPkgInfo() + if outFile.len != 0: + writeFile(outFile, printPkgInfo()) else: var output = "" diff --git a/src/nimblepkg/nimscriptwrapper.nim b/src/nimblepkg/nimscriptwrapper.nim index 39df926..10cfae8 100644 --- a/src/nimblepkg/nimscriptwrapper.nim +++ b/src/nimblepkg/nimscriptwrapper.nim @@ -21,8 +21,8 @@ const internalCmd = "e" nimscriptApi = staticRead("nimscriptapi.nim") -proc execNimscript(nimsFile, projectDir, actionName: string, options: Options, - live = true): tuple[output: string, exitCode: int] = +proc execNimscript(nimsFile, projectDir, actionName: string, options: Options): + tuple[output: string, exitCode: int] = let nimsFileCopied = projectDir / nimsFile.splitFile().name & "_" & getProcessId() & ".nims" outFile = getNimbleTempDir() & ".out" @@ -46,13 +46,10 @@ proc execNimscript(nimsFile, projectDir, actionName: string, options: Options, displayDebug("Executing " & cmd) - if live: - result.exitCode = execCmd(cmd) - if outFile.fileExists(): - result.output = outFile.readFile() - discard outFile.tryRemoveFile() - else: - result = execCmdEx(cmd, options = {poUsePath, poStdErrToStdOut}) + result.exitCode = execCmd(cmd) + if outFile.fileExists(): + result.output = outFile.readFile() + discard outFile.tryRemoveFile() proc getNimsFile(scriptName: string, options: Options): string = let @@ -103,7 +100,7 @@ proc getIniFile*(scriptName: string, options: Options): string = if not isIniResultCached: let (output, exitCode) = - execNimscript(nimsFile, scriptName.parentDir(), "printPkgInfo", options, live=false) + execNimscript(nimsFile, scriptName.parentDir(), "printPkgInfo", options) if exitCode == 0 and output.len != 0: result.writeFile(output) From 98e566adab85e5e1b6226a5e69418d925894fb89 Mon Sep 17 00:00:00 2001 From: Hitesh Jasani Date: Wed, 12 Jun 2019 16:16:52 -0400 Subject: [PATCH 23/95] Add example dependency with git commit [docs] (#669) * Add example dependency with git commit [docs] * Update readme.markdown --- readme.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.markdown b/readme.markdown index f246c7e..5f3ef52 100644 --- a/readme.markdown +++ b/readme.markdown @@ -401,6 +401,7 @@ You can also specify multiple dependencies like so: requires "nim >= 0.10.0", "foobar >= 0.1.0" requires "fizzbuzz >= 1.0" +requires "https://github.com/user/pkg#5a54b5e" ``` Nimble currently supports installation of packages from a local directory, a From df8317585f5ee99de68cdda38df2a5b1a2d6027f Mon Sep 17 00:00:00 2001 From: liquid600pgm Date: Sat, 22 Jun 2019 00:10:41 +0200 Subject: [PATCH 24/95] fixed #581 nimble init does not overwrite existing files with its templates anymore --- src/nimblepkg/init.nim | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/nimblepkg/init.nim b/src/nimblepkg/init.nim index 0617d97..e8c1000 100644 --- a/src/nimblepkg/init.nim +++ b/src/nimblepkg/init.nim @@ -14,6 +14,10 @@ type pkgNimDep: string pkgType: string +proc writeFileIfNonExistent(file: string, content: string) = + if not existsFile(file): + writeFile(file, content) + proc createPkgStructure*(info: PkgInitInfo, pkgRoot: string) = # Create source directory createDirD(pkgRoot / info.pkgSrcDir) @@ -23,7 +27,7 @@ proc createPkgStructure*(info: PkgInitInfo, pkgRoot: string) = case info.pkgType of "binary": let mainFile = pkgRoot / info.pkgSrcDir / info.pkgName.changeFileExt("nim") - writeFile(mainFile, + writeFileIfNonExistent(mainFile, """ # This is just an example to get you started. A typical binary package # uses this file as the main entry point of the application. @@ -35,7 +39,7 @@ when isMainModule: nimbleFileOptions.add("bin = @[\"$1\"]\n" % info.pkgName) of "library": let mainFile = pkgRoot / info.pkgSrcDir / info.pkgName.changeFileExt("nim") - writeFile(mainFile, + writeFileIfNonExistent(mainFile, """ # This is just an example to get you started. A typical library package # exports the main API in this file. Note that you cannot rename this file @@ -50,7 +54,7 @@ proc add*(x, y: int): int = createDirD(pkgRoot / info.pkgSrcDir / info.pkgName) let submodule = pkgRoot / info.pkgSrcDir / info.pkgName / "submodule".addFileExt("nim") - writeFile(submodule, + writeFileIfNonExistent(submodule, """ # This is just an example to get you started. Users of your library will # import this file by writing ``import $1/submodule``. Feel free to rename or @@ -68,7 +72,7 @@ proc initSubmodule*(): Submodule = ) of "hybrid": let mainFile = pkgRoot / info.pkgSrcDir / info.pkgName.changeFileExt("nim") - writeFile(mainFile, + writeFileIfNonExistent(mainFile, """ # This is just an example to get you started. A typical hybrid package # uses this file as the main entry point of the application. @@ -83,7 +87,7 @@ when isMainModule: let pkgSubDir = pkgRoot / info.pkgSrcDir / info.pkgName & "pkg" createDirD(pkgSubDir) let submodule = pkgSubDir / "submodule".addFileExt("nim") - writeFile(submodule, + writeFileIfNonExistent(submodule, """ # This is just an example to get you started. Users of your hybrid library will # import this file by writing ``import $1pkg/submodule``. Feel free to rename or @@ -112,7 +116,7 @@ proc getWelcomeMessage*(): string = "Hello, World!" ) if info.pkgType == "library": - writeFile(pkgTestPath / "test1".addFileExt("nim"), + writeFileIfNonExistent(pkgTestPath / "test1".addFileExt("nim"), """ # This is just an example to get you started. You may wish to put all of your # tests into a single file, or separate them into multiple `test1`, `test2` @@ -129,7 +133,7 @@ test "can add": """ % info.pkgName ) else: - writeFile(pkgTestPath / "test1".addFileExt("nim"), + writeFileIfNonExistent(pkgTestPath / "test1".addFileExt("nim"), """ # This is just an example to get you started. You may wish to put all of your # tests into a single file, or separate them into multiple `test1`, `test2` From 192ea12ab9cd66a1f38d643b0258f508fc3a94a0 Mon Sep 17 00:00:00 2001 From: liquid600pgm Date: Sat, 22 Jun 2019 00:32:48 +0200 Subject: [PATCH 25/95] Output message when file already exists --- src/nimblepkg/init.nim | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/nimblepkg/init.nim b/src/nimblepkg/init.nim index e8c1000..97c5589 100644 --- a/src/nimblepkg/init.nim +++ b/src/nimblepkg/init.nim @@ -14,9 +14,12 @@ type pkgNimDep: string pkgType: string -proc writeFileIfNonExistent(file: string, content: string) = +proc writeExampleIfNonExistent(file: string, content: string) = if not existsFile(file): writeFile(file, content) + else: + display("Info:", "File " & file & " already exists, did not write " & + "example code", Message, LowPriority) proc createPkgStructure*(info: PkgInitInfo, pkgRoot: string) = # Create source directory @@ -27,7 +30,7 @@ proc createPkgStructure*(info: PkgInitInfo, pkgRoot: string) = case info.pkgType of "binary": let mainFile = pkgRoot / info.pkgSrcDir / info.pkgName.changeFileExt("nim") - writeFileIfNonExistent(mainFile, + writeExampleIfNonExistent(mainFile, """ # This is just an example to get you started. A typical binary package # uses this file as the main entry point of the application. @@ -39,7 +42,7 @@ when isMainModule: nimbleFileOptions.add("bin = @[\"$1\"]\n" % info.pkgName) of "library": let mainFile = pkgRoot / info.pkgSrcDir / info.pkgName.changeFileExt("nim") - writeFileIfNonExistent(mainFile, + writeExampleIfNonExistent(mainFile, """ # This is just an example to get you started. A typical library package # exports the main API in this file. Note that you cannot rename this file @@ -54,7 +57,7 @@ proc add*(x, y: int): int = createDirD(pkgRoot / info.pkgSrcDir / info.pkgName) let submodule = pkgRoot / info.pkgSrcDir / info.pkgName / "submodule".addFileExt("nim") - writeFileIfNonExistent(submodule, + writeExampleIfNonExistent(submodule, """ # This is just an example to get you started. Users of your library will # import this file by writing ``import $1/submodule``. Feel free to rename or @@ -72,7 +75,7 @@ proc initSubmodule*(): Submodule = ) of "hybrid": let mainFile = pkgRoot / info.pkgSrcDir / info.pkgName.changeFileExt("nim") - writeFileIfNonExistent(mainFile, + writeExampleIfNonExistent(mainFile, """ # This is just an example to get you started. A typical hybrid package # uses this file as the main entry point of the application. @@ -87,7 +90,7 @@ when isMainModule: let pkgSubDir = pkgRoot / info.pkgSrcDir / info.pkgName & "pkg" createDirD(pkgSubDir) let submodule = pkgSubDir / "submodule".addFileExt("nim") - writeFileIfNonExistent(submodule, + writeExampleIfNonExistent(submodule, """ # This is just an example to get you started. Users of your hybrid library will # import this file by writing ``import $1pkg/submodule``. Feel free to rename or @@ -116,7 +119,7 @@ proc getWelcomeMessage*(): string = "Hello, World!" ) if info.pkgType == "library": - writeFileIfNonExistent(pkgTestPath / "test1".addFileExt("nim"), + writeExampleIfNonExistent(pkgTestPath / "test1".addFileExt("nim"), """ # This is just an example to get you started. You may wish to put all of your # tests into a single file, or separate them into multiple `test1`, `test2` @@ -133,7 +136,7 @@ test "can add": """ % info.pkgName ) else: - writeFileIfNonExistent(pkgTestPath / "test1".addFileExt("nim"), + writeExampleIfNonExistent(pkgTestPath / "test1".addFileExt("nim"), """ # This is just an example to get you started. You may wish to put all of your # tests into a single file, or separate them into multiple `test1`, `test2` From 153866252d8606d6f77d6a1747e6ad6674ba3741 Mon Sep 17 00:00:00 2001 From: liquid600pgm Date: Sat, 22 Jun 2019 01:17:03 +0200 Subject: [PATCH 26/95] File not overwritten by example code message --- src/nimblepkg/init.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nimblepkg/init.nim b/src/nimblepkg/init.nim index 97c5589..085c616 100644 --- a/src/nimblepkg/init.nim +++ b/src/nimblepkg/init.nim @@ -19,7 +19,7 @@ proc writeExampleIfNonExistent(file: string, content: string) = writeFile(file, content) else: display("Info:", "File " & file & " already exists, did not write " & - "example code", Message, LowPriority) + "example code", priority = HighPriority) proc createPkgStructure*(info: PkgInitInfo, pkgRoot: string) = # Create source directory From 5e72840336c03ef66557f96623fedddece53083d Mon Sep 17 00:00:00 2001 From: liquid600pgm Date: Sat, 22 Jun 2019 01:17:41 +0200 Subject: [PATCH 27/95] Test for the #581 fix --- tests/tester.nim | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/tester.nim b/tests/tester.nim index bd7a306..fcd4752 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -818,3 +818,12 @@ suite "Module tests": test "cli": cd "..": check execCmdEx("nim c -r src/nimblepkg/cli").exitCode == QuitSuccess + +test "init does not overwrite existing files (#581)": + createDir("issue581/src") + cd "issue581": + const Src = "echo \"OK\"" + writeFile("src/issue581.nim", Src) + check execNimbleYes("init").exitCode == QuitSuccess + check readFile("src/issue581.nim") == Src + removeDir("issue581") From 7c2b9f612e6a42609f28f8f51de4ab6949ced4a0 Mon Sep 17 00:00:00 2001 From: Leonardo Mariscal Date: Tue, 16 Jul 2019 10:32:17 -0500 Subject: [PATCH 28/95] Update submodules at checkout time to fix #675 --- src/nimblepkg/download.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nimblepkg/download.nim b/src/nimblepkg/download.nim index f8954e9..b504b61 100644 --- a/src/nimblepkg/download.nim +++ b/src/nimblepkg/download.nim @@ -24,6 +24,7 @@ proc doCheckout(meth: DownloadMethod, downloadDir, branch: string) = # clone has happened. Like in the case of git on Windows where it # messes up the damn line endings. doCmd("git checkout --force " & branch) + doCmd("git submodule update --recursive") of DownloadMethod.hg: cd downloadDir: doCmd("hg checkout " & branch) From added89acc9437946b0fba7dfdc07021da564196 Mon Sep 17 00:00:00 2001 From: genotrance Date: Sat, 27 Jul 2019 08:37:56 -0500 Subject: [PATCH 29/95] Fixes #504 (#683) * Improve messaging for uninstall * Uninstall if in right order * Fix CI - cannot looks better * Raise exception when nothing to remove * Test case, minor test fix * 0.19.6 fixes * Fix Nim devel hash * Feedback --- .travis.yml | 6 +++--- src/nimble.nim | 37 ++++++++++++++++------------------- src/nimblepkg/packageinfo.nim | 7 ++++++- src/nimblepkg/reversedeps.nim | 17 +++++++++++----- tests/tester.nim | 17 ++++++++++++++-- 5 files changed, 53 insertions(+), 31 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2a17224..13b8678 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,13 +6,13 @@ language: c env: - BRANCH=0.19.6 - - BRANCH=0.20.0 - - BRANCH=#ced0527ae334439a10e1719d1eccb727c19dc781 + - BRANCH=0.20.2 + - BRANCH=#44aadd50cfa647a759610a15967960632bf597ce cache: directories: - "$HOME/.choosenim/toolchains/nim-0.19.6" - - "$HOME/.choosenim/toolchains/nim-0.20.0" + - "$HOME/.choosenim/toolchains/nim-0.20.2" install: - export CHOOSENIM_CHOOSE_VERSION=$BRANCH diff --git a/src/nimble.nim b/src/nimble.nim index 1b01502..32b8833 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -840,7 +840,8 @@ proc uninstall(options: Options) = raise newException(NimbleError, "Please specify the package(s) to uninstall.") - var pkgsToDelete: seq[PackageInfo] = @[] + var pkgsToDelete: HashSet[PackageInfo] + pkgsToDelete.init() # Do some verification. for pkgTup in options.action.packages: display("Looking", "for $1 ($2)" % [pkgTup.name, $pkgTup.ver], @@ -851,37 +852,33 @@ proc uninstall(options: Options) = raise newException(NimbleError, "Package not found") display("Checking", "reverse dependencies", priority = HighPriority) - var errors: seq[string] = @[] for pkg in pkgList: # Check whether any packages depend on the ones the user is trying to # uninstall. if options.uninstallRevDeps: getAllRevDeps(options, pkg, pkgsToDelete) else: - let revDeps = getRevDeps(options, pkg) + let + revDeps = getRevDeps(options, pkg) var reason = "" - if revDeps.len == 1: - reason = "$1 ($2) depends on it" % [revDeps[0].name, $revDeps[0].ver] - else: - for i in 0 ..< revDeps.len: - reason.add("$1 ($2)" % [revDeps[i].name, $revDeps[i].ver]) - if i != revDeps.len-1: - reason.add ", " - reason.add " depend on it" + for revDep in revDeps: + if reason.len != 0: reason.add ", " + reason.add("$1 ($2)" % [revDep.name, revDep.version]) + if reason.len != 0: + reason &= " depend" & (if revDeps.len == 1: "s" else: "") & " on it" - if revDeps.len > 0: - errors.add("Cannot uninstall $1 ($2) because $3" % - [pkgTup.name, pkg.specialVersion, reason]) + if len(revDeps - pkgsToDelete) > 0: + display("Cannot", "uninstall $1 ($2) because $3" % + [pkgTup.name, pkg.specialVersion, reason], Warning, HighPriority) else: - pkgsToDelete.add pkg + pkgsToDelete.incl pkg - if pkgsToDelete.len == 0: - raise newException(NimbleError, "\n " & errors.join("\n ")) + if pkgsToDelete.len == 0: + raise newException(NimbleError, "Failed uninstall - no packages selected") var pkgNames = "" - for i in 0 ..< pkgsToDelete.len: - if i != 0: pkgNames.add ", " - let pkg = pkgsToDelete[i] + for pkg in pkgsToDelete.items: + if pkgNames.len != 0: pkgNames.add ", " pkgNames.add("$1 ($2)" % [pkg.name, pkg.specialVersion]) # Let's confirm that the user wants these packages removed. diff --git a/src/nimblepkg/packageinfo.nim b/src/nimblepkg/packageinfo.nim index c54b97d..ec7daf0 100644 --- a/src/nimblepkg/packageinfo.nim +++ b/src/nimblepkg/packageinfo.nim @@ -3,7 +3,7 @@ # Stdlib imports import system except TResult -import parsecfg, json, streams, strutils, parseutils, os, sets, tables +import hashes, parsecfg, json, streams, strutils, parseutils, os, sets, tables import httpclient # Local imports @@ -542,6 +542,11 @@ proc `==`*(pkg1: PackageInfo, pkg2: PackageInfo): bool = if pkg1.name == pkg2.name and pkg1.myPath == pkg2.myPath: return true +proc hash*(x: PackageInfo): Hash = + var h: Hash = 0 + h = h !& hash(x.myPath) + result = !$h + when isMainModule: doAssert getNameVersion("/home/user/.nimble/libs/packagea-0.1") == ("packagea", "0.1") diff --git a/src/nimblepkg/reversedeps.nim b/src/nimblepkg/reversedeps.nim index 0da2a84..45d9940 100644 --- a/src/nimblepkg/reversedeps.nim +++ b/src/nimblepkg/reversedeps.nim @@ -1,7 +1,7 @@ # Copyright (C) Dominik Picheta. All rights reserved. # BSD License. Look at license.txt for more info. -import os, json +import os, json, sets import options, common, version, download, packageinfo @@ -58,7 +58,7 @@ proc removeRevDep*(nimbleData: JsonNode, pkg: PackageInfo) = newData[key] = newVal nimbleData["reverseDeps"] = newData -proc getRevDeps*(options: Options, pkg: PackageInfo): seq[PkgTuple] = +proc getRevDepTups*(options: Options, pkg: PackageInfo): seq[PkgTuple] = ## Returns a list of *currently installed* reverse dependencies for `pkg`. result = @[] let thisPkgsDep = @@ -76,18 +76,25 @@ proc getRevDeps*(options: Options, pkg: PackageInfo): seq[PkgTuple] = result.add(pkgTup) -proc getAllRevDeps*(options: Options, pkg: PackageInfo, result: var seq[PackageInfo]) = +proc getRevDeps*(options: Options, pkg: PackageInfo): HashSet[PackageInfo] = + result.init() + let installedPkgs = getInstalledPkgsMin(options.getPkgsDir(), options) + for rdepTup in getRevDepTups(options, pkg): + for rdepInfo in findAllPkgs(installedPkgs, rdepTup): + result.incl rdepInfo + +proc getAllRevDeps*(options: Options, pkg: PackageInfo, result: var HashSet[PackageInfo]) = if pkg in result: return let installedPkgs = getInstalledPkgsMin(options.getPkgsDir(), options) - for rdepTup in getRevDeps(options, pkg): + for rdepTup in getRevDepTups(options, pkg): for rdepInfo in findAllPkgs(installedPkgs, rdepTup): if rdepInfo in result: continue getAllRevDeps(options, rdepInfo, result) - result.add pkg + result.incl pkg when isMainModule: var nimbleData = %{"reverseDeps": newJObject()} diff --git a/tests/tester.nim b/tests/tester.nim index fcd4752..99f7c64 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -465,8 +465,7 @@ test "can uninstall": let ls = outp.strip.processOutput() check exitCode != QuitSuccess - check "Cannot uninstall issue27b (0.1.0) because issue27a (0.1.0) depends" & - " on it" in ls[ls.len-1] + check inLines(ls, "Cannot uninstall issue27b (0.1.0) because issue27a (0.1.0) depends") check execNimble("uninstall", "-y", "issue27").exitCode == QuitSuccess check execNimble("uninstall", "-y", "issue27a").exitCode == QuitSuccess @@ -827,3 +826,17 @@ test "init does not overwrite existing files (#581)": check execNimbleYes("init").exitCode == QuitSuccess check readFile("src/issue581.nim") == Src removeDir("issue581") + +test "remove skips packages with revDeps (#504)": + check execNimble("install", "nimboost@0.5.5", "nimfp@0.4.4", "-y").exitCode == QuitSuccess + + var (output, exitCode) = execNimble("uninstall", "nimboost", "nimfp", "-n") + var lines = output.strip.processOutput() + check inLines(lines, "Cannot uninstall nimboost") + + (output, exitCode) = execNimble("uninstall", "nimfp", "nimboost", "-y") + lines = output.strip.processOutput() + check (not inLines(lines, "Cannot uninstall nimboost")) + + check execNimble("path", "nimboost").exitCode != QuitSuccess + check execNimble("path", "nimfp").exitCode != QuitSuccess From 5ec2ecea77434798133e5a1ec5f1a437d3947128 Mon Sep 17 00:00:00 2001 From: SolitudeSF Date: Fri, 26 Jul 2019 17:49:04 +0300 Subject: [PATCH 30/95] Add `passNim/p` flag to pass compiler flags explicitly. Remove redundant buildFromDir overload. --- src/nimble.nim | 14 +++----------- src/nimblepkg/options.nim | 5 +++++ 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/nimble.nim b/src/nimble.nim index 32b8833..69086be 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -213,13 +213,13 @@ proc processDeps(pkginfo: PackageInfo, options: Options): seq[PackageInfo] = for i in reverseDeps: addRevDep(options.nimbleData, i, pkginfo) -proc buildFromDir(pkgInfo: PackageInfo, paths: seq[string], - args: var seq[string]) = +proc buildFromDir(pkgInfo: PackageInfo, paths, args: seq[string]) = ## Builds a package as specified by ``pkgInfo``. if pkgInfo.bin.len == 0: raise newException(NimbleError, "Nothing to build. Did you specify a module to build using the" & " `bin` key in your .nimble file?") + var args = args let realDir = pkgInfo.getRealDir() for path in paths: args.add("--path:\"" & path & "\" ") for bin in pkgInfo.bin: @@ -244,14 +244,6 @@ proc buildFromDir(pkgInfo: PackageInfo, paths: seq[string], exc.hint = hint raise exc -proc buildFromDir(pkgInfo: PackageInfo, paths: seq[string], forRelease: bool) = - var args: seq[string] - if forRelease: - args = @["-d:release"] - else: - args = @[] - buildFromDir(pkgInfo, paths, args) - proc removePkgDir(dir: string, options: Options) = ## Removes files belonging to the package in ``dir``. try: @@ -356,7 +348,7 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, # if the build fails then the old package will still be installed. if pkgInfo.bin.len > 0: let paths = result.deps.map(dep => dep.getRealDir()) - buildFromDir(pkgInfo, paths, true) + buildFromDir(pkgInfo, paths, options.action.passNimFlags & "-d:release") let pkgDestDir = pkgInfo.getPkgDest(options) if existsDir(pkgDestDir) and existsFile(pkgDestDir / "nimblemeta.json"): diff --git a/src/nimblepkg/options.nim b/src/nimblepkg/options.nim index f49ed04..e345121 100644 --- a/src/nimblepkg/options.nim +++ b/src/nimblepkg/options.nim @@ -41,6 +41,7 @@ type of actionInstall, actionPath, actionUninstall, actionDevelop: packages*: seq[PkgTuple] # Optional only for actionInstall # and actionDevelop. + passNimFlags*: seq[string] of actionSearch: search*: seq[string] # Search string. of actionInit, actionDump: @@ -61,6 +62,7 @@ Usage: nimble COMMAND [opts] Commands: install [pkgname, ...] Installs a list of packages. [-d, --depsOnly] Install only dependencies. + [-p, --passNim] Forward specified flag to compiler. develop [pkgname, ...] Clones a list of packages for development. Symlinks the cloned packages or any package in the current working directory. @@ -175,6 +177,7 @@ proc initAction*(options: var Options, key: string) = case options.action.typ of actionInstall, actionPath, actionDevelop, actionUninstall: options.action.packages = @[] + options.action.passNimFlags = @[] of actionCompile, actionDoc, actionBuild: options.action.compileOptions = @[] options.action.file = "" @@ -303,6 +306,8 @@ proc parseFlag*(flag, val: string, result: var Options, kind = cmdLongOption) = case f of "depsonly", "d": result.depsOnly = true + of "passnim", "p": + result.action.passNimFlags.add(val) else: wasFlagHandled = false of actionUninstall: From b5b85489fab710d2a77182030ac6ef98352328ec Mon Sep 17 00:00:00 2001 From: SolitudeSF Date: Sat, 27 Jul 2019 19:38:50 +0300 Subject: [PATCH 31/95] Add tests for passNim feature. --- tests/passNimFlags/passNimFlags.nim | 1 + tests/passNimFlags/passNimFlags.nimble | 11 +++++++++++ tests/tester.nim | 4 ++++ 3 files changed, 16 insertions(+) create mode 100644 tests/passNimFlags/passNimFlags.nim create mode 100644 tests/passNimFlags/passNimFlags.nimble diff --git a/tests/passNimFlags/passNimFlags.nim b/tests/passNimFlags/passNimFlags.nim new file mode 100644 index 0000000..b4c0b97 --- /dev/null +++ b/tests/passNimFlags/passNimFlags.nim @@ -0,0 +1 @@ +when not defined(passNimIsWorking): {.error: "-d:passNimIsWorking wasn't passed to the compiler"} diff --git a/tests/passNimFlags/passNimFlags.nimble b/tests/passNimFlags/passNimFlags.nimble new file mode 100644 index 0000000..8530524 --- /dev/null +++ b/tests/passNimFlags/passNimFlags.nimble @@ -0,0 +1,11 @@ +# Package + +version = "0.1.0" +author = "SolitudeSF" +description = "Test nimble install flag forwarding" +license = "BSD" +bin = @["passNimFlags"] + +# Dependencies + +requires "nim >= 0.13.0" diff --git a/tests/tester.nim b/tests/tester.nim index 99f7c64..2a1bb00 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -840,3 +840,7 @@ test "remove skips packages with revDeps (#504)": check execNimble("path", "nimboost").exitCode != QuitSuccess check execNimble("path", "nimfp").exitCode != QuitSuccess + +test "pass options to the compiler with `nimble install`": + cd "passNimFlags": + check execNimble("install", "--passNim:-d:passNimIsWorking").exitCode == QuitSuccess From 5b7b061465d77f4798816d7b813a4fe888fa87ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ois=C3=ADn=20Mac=20Fheara=C3=AD?= Date: Sat, 27 Jul 2019 17:55:59 +0100 Subject: [PATCH 32/95] Remove escaping of author name and package description in `nimble init`, since it replaces characters used in many names and languages with escape sequences. Slightly refactor the code for determining author name, to make it easier to add other version control systems in future (right now it's just git and hg). Also, add some binary test artifacts to .gitignore so they don't accidentally get committed in future. --- .gitignore | 11 ++++++++++- src/nimble.nim | 22 +++++++++++----------- src/nimblepkg/init.nim | 2 +- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 5744c47..76ef2d8 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,14 @@ nimcache/ /nimble /tests/nimscript/nimscript /tests/issue27/issue27 +src/nimblepkg/cli +src/nimblepkg/packageinfo +src/nimblepkg/packageparser +src/nimblepkg/reversedeps +src/nimblepkg/version +tests/nimble-test/ +tests/packageStructure/validBinary/y +tests/testCommand/testsFail/tests/t2 # Windows executables *.exe @@ -19,4 +27,5 @@ nimcache/ # VCC compiler and linker artifacts *.ilk -*.pdb \ No newline at end of file +*.pdb + diff --git a/src/nimble.nim b/src/nimble.nim index 69086be..f62795b 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -724,20 +724,20 @@ proc init(options: Options) = display("Using", "$# for new package name" % [pkgName.escape()], priority = HighPriority) - # Ask for package author + # Determine author by running an external command + proc getAuthorWithCmd(cmd: string): string = + let (name, exitCode) = doCmdEx(cmd) + if exitCode == QuitSuccess and name.len > 0: + result = name.strip() + display("Using", "$# for new package author" % [result], + priority = HighPriority) + + # Determine package author via git/hg or asking proc getAuthor(): string = if findExe("git") != "": - let (name, exitCode) = doCmdEx("git config --global user.name") - if exitCode == QuitSuccess and name.len > 0: - result = name.strip() - display("Using", "$# for new package author" % [result.escape()], - priority = HighPriority) + result = getAuthorWithCmd("git config --global user.name") elif findExe("hg") != "": - let (name, exitCode) = doCmdEx("hg config ui.username") - if exitCode == QuitSuccess and name.len > 0: - result = name.strip() - display("Using", "$# for new package author" % [result.escape()], - priority = HighPriority) + result = getAuthorWithCmd("hg config ui.username") if result.len == 0: result = promptCustom(options, "Your name?", "Anonymous") let pkgAuthor = getAuthor() diff --git a/src/nimblepkg/init.nim b/src/nimblepkg/init.nim index 085c616..2378b5f 100644 --- a/src/nimblepkg/init.nim +++ b/src/nimblepkg/init.nim @@ -175,7 +175,7 @@ $# requires "nim >= $#" """ % [ - info.pkgVersion.escape(), info.pkgAuthor.escape(), info.pkgDesc.escape(), + info.pkgVersion.escape(), info.pkgAuthor, info.pkgDesc.escape(), info.pkgLicense.escape(), info.pkgSrcDir.escape(), nimbleFileOptions, pkgBackend, info.pkgNimDep ] From 522ef4cf129b20faf8071c25074e03141eb9d92c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ois=C3=ADn=20Mac=20Fheara=C3=AD?= Date: Sat, 27 Jul 2019 19:53:07 +0100 Subject: [PATCH 33/95] Unescape package description in nimble init --- src/nimblepkg/init.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nimblepkg/init.nim b/src/nimblepkg/init.nim index 2378b5f..6648877 100644 --- a/src/nimblepkg/init.nim +++ b/src/nimblepkg/init.nim @@ -175,7 +175,7 @@ $# requires "nim >= $#" """ % [ - info.pkgVersion.escape(), info.pkgAuthor, info.pkgDesc.escape(), + info.pkgVersion.escape(), info.pkgAuthor, info.pkgDesc, info.pkgLicense.escape(), info.pkgSrcDir.escape(), nimbleFileOptions, pkgBackend, info.pkgNimDep ] From 871e5c65d19eb3b5f69dca136edd92a2dfab1586 Mon Sep 17 00:00:00 2001 From: Ivan Bobev Date: Sat, 27 Jul 2019 21:39:38 +0300 Subject: [PATCH 34/95] Add additional files and folders to .gitignore Several additional file and folder types are added to .gitignore: - VSCode directory for for user and workspace settings. - *.orig files created by Git on merge conflicts. - Some artifacts related to the test procedure. --- .gitignore | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.gitignore b/.gitignore index 76ef2d8..5c629ae 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,12 @@ tests/testCommand/testsFail/tests/t2 *.ilk *.pdb +# Editors and IDEs project files and folders +.vscode + +# VCS artifacts +*.orig + +# Test procedure artifacts +tests/nimble-test/ +nimble_*.nims From 8cf97e0e0682f72181d8c2f2b142dcb582584a50 Mon Sep 17 00:00:00 2001 From: Ivan Bobev Date: Sun, 28 Jul 2019 00:12:46 +0300 Subject: [PATCH 35/95] Add an unit test for #678 This is an unit test for the issue with multiple downloads and installs of the same dependency package. Related to #678 --- tests/issue678/issue678.nimble | 12 ++++++++++++ tests/issue678/packages.json | 19 +++++++++++++++++++ tests/tester.nim | 16 ++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 tests/issue678/issue678.nimble create mode 100644 tests/issue678/packages.json diff --git a/tests/issue678/issue678.nimble b/tests/issue678/issue678.nimble new file mode 100644 index 0000000..20239e7 --- /dev/null +++ b/tests/issue678/issue678.nimble @@ -0,0 +1,12 @@ +# Package + +version = "0.1.0" +author = "Ivan Bobev" +description = "Package for ensuring that issue #678 is resolved." +license = "MIT" + +# Dependencies + +requires "nim >= 0.19.6" +# to reproduce dependency 2 must be before 1 +requires "issue678_dependency_2", "issue678_dependency_1" diff --git a/tests/issue678/packages.json b/tests/issue678/packages.json new file mode 100644 index 0000000..25338c9 --- /dev/null +++ b/tests/issue678/packages.json @@ -0,0 +1,19 @@ +[ + { + "name": "issue678_dependency_1", + "url": "https://github.com/bobeff/issue678?subdir=dependency_1", + "method": "git", + "tags": [ "test" ], + "description": + "Both first and second level dependency of the issue678 package.", + "license": "MIT" + }, + { + "name": "issue678_dependency_2", + "url": "https://github.com/bobeff/issue678?subdir=dependency_2", + "method": "git", + "tags": [ "test" ], + "description": "First level dependency of the issue678 package.", + "license": "MIT" + } +] diff --git a/tests/tester.nim b/tests/tester.nim index 2a1bb00..b08d0cf 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -844,3 +844,19 @@ test "remove skips packages with revDeps (#504)": test "pass options to the compiler with `nimble install`": cd "passNimFlags": check execNimble("install", "--passNim:-d:passNimIsWorking").exitCode == QuitSuccess + +test "do not install single dependency multiple times (#678)": + # for the test to be correct, the tested package and its dependencies must not + # exist in the local cache + removeDir("nimbleDir") + cd "issue678": + testRefresh(): + writeFile(configFile, """ + [PackageList] + name = "local" + path = "$1" + """.unindent % (getCurrentDir() / "packages.json").replace("\\", "\\\\")) + check execNimble(["refresh"]).exitCode == QuitSuccess + let (output, exitCode) = execNimble("install", "-y") + check exitCode == QuitSuccess + check output.find("issue678_dependency_1@0.1.0 already exists") == -1 From f46179268655a3177fec7d1b60b2c701c859ce7e Mon Sep 17 00:00:00 2001 From: Ivan Bobev Date: Sat, 27 Jul 2019 22:01:16 +0300 Subject: [PATCH 36/95] Fix multiple installs of the same package When one package is dependency both of some other package and some other dependency of the other package it has been downloaded and installed multiple times. Resolves #678 --- src/nimble.nim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/nimble.nim b/src/nimble.nim index f62795b..999595e 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -156,7 +156,8 @@ proc processDeps(pkginfo: PackageInfo, options: Options): seq[PackageInfo] = "dependencies for $1@$2" % [pkginfo.name, pkginfo.specialVersion], priority = HighPriority) - var pkgList = getInstalledPkgsMin(options.getPkgsDir(), options) + var pkgList {.global.}: seq[tuple[pkginfo: PackageInfo, meta: MetaData]] = @[] + once: pkgList = getInstalledPkgsMin(options.getPkgsDir(), options) var reverseDeps: seq[tuple[name, version: string]] = @[] for dep in pkginfo.requires: if dep.name == "nimrod" or dep.name == "nim": From 8e3af03e4699a56e2321dfa90a0dfafd527ab51e Mon Sep 17 00:00:00 2001 From: Ivan Bobev Date: Sun, 28 Jul 2019 00:29:48 +0300 Subject: [PATCH 37/95] Add a changelog entry for the issue #678 fix Related to #678 --- changelog.markdown | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.markdown b/changelog.markdown index c8ae9cf..a011485 100644 --- a/changelog.markdown +++ b/changelog.markdown @@ -3,6 +3,8 @@ # Nimble changelog +- Fixed multiple downloads and installs of the same package (#678). + ## 0.10.2 - 03/06/2019 This is a small release which avoids object variant changes that are now From da82e3111e662fc1b12f96b3cddd66c749c0f686 Mon Sep 17 00:00:00 2001 From: Ivan Bobev Date: Sun, 4 Aug 2019 19:31:40 +0300 Subject: [PATCH 38/95] Fix issue678 test repository links After the ownership of the issue678 test repository was transferred to the nimble-test account, the links to the repository required for performing the test had to be updated in order to avoid possible confusion, despite the fact that GitHub does automatic link redirection for transferred repositories. Related to #687 --- tests/issue678/packages.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/issue678/packages.json b/tests/issue678/packages.json index 25338c9..972eb70 100644 --- a/tests/issue678/packages.json +++ b/tests/issue678/packages.json @@ -1,7 +1,7 @@ [ { "name": "issue678_dependency_1", - "url": "https://github.com/bobeff/issue678?subdir=dependency_1", + "url": "https://github.com/nimble-test/issue678?subdir=dependency_1", "method": "git", "tags": [ "test" ], "description": @@ -10,7 +10,7 @@ }, { "name": "issue678_dependency_2", - "url": "https://github.com/bobeff/issue678?subdir=dependency_2", + "url": "https://github.com/nimble-test/issue678?subdir=dependency_2", "method": "git", "tags": [ "test" ], "description": "First level dependency of the issue678 package.", From 2243e3fbc2dd277ad81df5d795307bf8389b9240 Mon Sep 17 00:00:00 2001 From: Hitesh Jasani Date: Sat, 24 Aug 2019 05:15:29 -0400 Subject: [PATCH 39/95] Fix #567: Display package versions in sorted order (#688) * Fix #567: Display package versions in sorted order * Add docs * Fix docs to focus on git tags * Refactor to use version module * Streamline docs * Refactor to make things work in nim <= 0.19.6 * Improve code readability --- readme.markdown | 13 ++++++++++ src/nimblepkg/download.nim | 52 +++++++++++++++++++++++++------------- src/nimblepkg/version.nim | 14 +++++++--- tests/tester.nim | 4 +++ 4 files changed, 63 insertions(+), 20 deletions(-) diff --git a/readme.markdown b/readme.markdown index 5f3ef52..9470aa8 100644 --- a/readme.markdown +++ b/readme.markdown @@ -786,6 +786,19 @@ To summarise, the steps for release are: Once the new tag is in the remote repository, Nimble will be able to detect the new version. +##### Git Version Tagging + +Use dot separated numbers to represent the release version in the git +tag label. Nimble will parse these git tag labels to know which +versions of a package are published. + +``` text +v0.2.0 # 0.2.0 +v1 # 1 +v1.2.3-zuzu # 1.2.3 +foo-1.2.3.4 # 1.2.3.4 +``` + ## Publishing packages Publishing packages isn't a requirement. But doing so allows people to associate diff --git a/src/nimblepkg/download.nim b/src/nimblepkg/download.nim index b504b61..639354f 100644 --- a/src/nimblepkg/download.nim +++ b/src/nimblepkg/download.nim @@ -2,8 +2,9 @@ # BSD License. Look at license.txt for more info. import parseutils, os, osproc, strutils, tables, pegs, uri - import packageinfo, packageparser, version, tools, common, options, cli +from algorithm import SortOrder, sorted +from sequtils import toSeq, filterIt, map type DownloadMethod* {.pure.} = enum @@ -103,14 +104,21 @@ proc getTagsListRemote*(url: string, meth: DownloadMethod): seq[string] = # http://stackoverflow.com/questions/2039150/show-tags-for-remote-hg-repository raise newException(ValueError, "Hg doesn't support remote tag querying.") -proc getVersionList*(tags: seq[string]): Table[Version, string] = - # Returns: TTable of version -> git tag name - result = initTable[Version, string]() - for tag in tags: - if tag != "": - let i = skipUntil(tag, Digits) # skip any chars before the version - # TODO: Better checking, tags can have any names. Add warnings and such. - result[newVersion(tag[i .. tag.len-1])] = tag +proc getVersionList*(tags: seq[string]): OrderedTable[Version, string] = + ## Return an ordered table of Version -> git tag label. Ordering is + ## in descending order with the most recent version first. + let taggedVers: seq[tuple[ver: Version, tag: string]] = + tags + .filterIt(it != "") + .map(proc(s: string): tuple[ver: Version, tag: string] = + # skip any chars before the version + let i = skipUntil(s, Digits) + # TODO: Better checking, tags can have any + # names. Add warnings and such. + result = (newVersion(s[i .. s.len-1]), s)) + .sorted(proc(a, b: (Version, string)): int = cmp(a[0], b[0]), + SortOrder.Descending) + result = toOrderedTable[Version, string](taggedVers) proc getDownloadMethod*(meth: string): DownloadMethod = case meth @@ -268,14 +276,8 @@ proc echoPackageVersions*(pkg: Package) = try: let versions = getTagsListRemote(pkg.url, downMethod).getVersionList() if versions.len > 0: - var vstr = "" - var i = 0 - for v in values(versions): - if i != 0: - vstr.add(", ") - vstr.add(v) - i.inc - echo(" versions: " & vstr) + let sortedVersions = toSeq(values(versions)) + echo(" versions: " & join(sortedVersions, ", ")) else: echo(" versions: (No versions tagged in the remote repository)") except OSError: @@ -283,3 +285,19 @@ proc echoPackageVersions*(pkg: Package) = of DownloadMethod.hg: echo(" versions: (Remote tag retrieval not supported by " & pkg.downloadMethod & ")") + +when isMainModule: + # Test version sorting + block: + let data = @["v9.0.0-taeyeon", "v9.0.1-jessica", "v9.2.0-sunny", + "v9.4.0-tiffany", "v9.4.2-hyoyeon"] + let expected = toOrderedTable[Version, string]({ + newVersion("9.4.2-hyoyeon"): "v9.4.2-hyoyeon", + newVersion("9.4.0-tiffany"): "v9.4.0-tiffany", + newVersion("9.2.0-sunny"): "v9.2.0-sunny", + newVersion("9.0.1-jessica"): "v9.0.1-jessica", + newVersion("9.0.0-taeyeon"): "v9.0.0-taeyeon" + }) + doAssert expected == getVersionList(data) + + echo("Everything works!") diff --git a/src/nimblepkg/version.nim b/src/nimblepkg/version.nim index a3e7c4a..4834b6d 100644 --- a/src/nimblepkg/version.nim +++ b/src/nimblepkg/version.nim @@ -93,6 +93,11 @@ proc `==`*(ver: Version, ver2: Version): bool = else: return false +proc cmp*(a, b: Version): int = + if a < b: -1 + elif a > b: 1 + else: 0 + proc `<=`*(ver: Version, ver2: Version): bool = return (ver == ver2) or (ver < ver2) @@ -272,7 +277,7 @@ proc newVREq*(ver: string): VersionRange = result.ver = newVersion(ver) proc findLatest*(verRange: VersionRange, - versions: Table[Version, string]): tuple[ver: Version, tag: string] = + versions: OrderedTable[Version, string]): tuple[ver: Version, tag: string] = result = (newVersion(""), "") for ver, tag in versions: if not withinRange(ver, verRange): continue @@ -309,8 +314,11 @@ when isMainModule: doAssert(newVersion("") < newVersion("1.0.0")) doAssert(newVersion("") < newVersion("0.1.0")) - var versions = toTable[Version, string]({newVersion("0.1.1"): "v0.1.1", - newVersion("0.2.3"): "v0.2.3", newVersion("0.5"): "v0.5"}) + var versions = toOrderedTable[Version, string]({ + newVersion("0.1.1"): "v0.1.1", + newVersion("0.2.3"): "v0.2.3", + newVersion("0.5"): "v0.5" + }) doAssert findLatest(parseVersionRange(">= 0.1 & <= 0.4"), versions) == (newVersion("0.2.3"), "v0.2.3") diff --git a/tests/tester.nim b/tests/tester.nim index b08d0cf..09427e8 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -818,6 +818,10 @@ suite "Module tests": cd "..": check execCmdEx("nim c -r src/nimblepkg/cli").exitCode == QuitSuccess + test "download": + cd "..": + check execCmdEx("nim c -r src/nimblepkg/download").exitCode == QuitSuccess + test "init does not overwrite existing files (#581)": createDir("issue581/src") cd "issue581": From e39c57482a156fb98e530c85a77766f67929ef29 Mon Sep 17 00:00:00 2001 From: genotrance Date: Mon, 2 Sep 2019 16:54:48 -0500 Subject: [PATCH 40/95] Fix #633 - pass CLI to tasks (#686) * Fix #633 - pass CLI to tasks * Add test case --- src/nimblepkg/nimscriptwrapper.nim | 10 +++++++++- tests/issue633/issue633.nimble | 16 ++++++++++++++++ tests/tester.nim | 5 +++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests/issue633/issue633.nimble diff --git a/src/nimblepkg/nimscriptwrapper.nim b/src/nimblepkg/nimscriptwrapper.nim index 10cfae8..fa8f8f0 100644 --- a/src/nimblepkg/nimscriptwrapper.nim +++ b/src/nimblepkg/nimscriptwrapper.nim @@ -40,10 +40,18 @@ proc execNimscript(nimsFile, projectDir, actionName: string, options: Options): if not isScriptResultCopied: nimsFileCopied.removeFile() - let + var cmd = ("nim e --hints:off --verbosity:0 -p:" & (getTempDir() / "nimblecache").quoteShell & " " & nimsFileCopied.quoteShell & " " & outFile.quoteShell & " " & actionName).strip() + if options.action.typ == actionCustom and actionName != "printPkgInfo": + for i in options.action.arguments: + cmd &= " " & i.quoteShell() + for key, val in options.action.flags.pairs(): + cmd &= " $#$#" % [if key.len == 1: "-" else: "--", key] + if val.len != 0: + cmd &= ":" & val.quoteShell() + displayDebug("Executing " & cmd) result.exitCode = execCmd(cmd) diff --git a/tests/issue633/issue633.nimble b/tests/issue633/issue633.nimble new file mode 100644 index 0000000..cb786eb --- /dev/null +++ b/tests/issue633/issue633.nimble @@ -0,0 +1,16 @@ +# Package + +version = "0.1.0" +author = "GT" +description = "Package for ensuring that issue #633 is resolved." +license = "MIT" + +# Dependencies + +requires "nim >= 0.19.6" +# to reproduce dependency 2 must be before 1 + +task testTask, "Test": + for i in 0 .. paramCount(): + if paramStr(i) == "--testTask": + echo "Got it" diff --git a/tests/tester.nim b/tests/tester.nim index 09427e8..c2aefc3 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -864,3 +864,8 @@ test "do not install single dependency multiple times (#678)": let (output, exitCode) = execNimble("install", "-y") check exitCode == QuitSuccess check output.find("issue678_dependency_1@0.1.0 already exists") == -1 + +test "Passing command line arguments to a task (#633)": + cd "issue633": + var (output, exitCode) = execNimble("testTask --testTask") + check output.contains("Got it") From fb57d47421cae847d67e3c1c1281fec6312227fd Mon Sep 17 00:00:00 2001 From: Ganesh Viswanathan Date: Wed, 4 Sep 2019 13:17:17 -0500 Subject: [PATCH 41/95] Fix #640 - strip out blank spaces --- src/nimblepkg/packageparser.nim | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/nimblepkg/packageparser.nim b/src/nimblepkg/packageparser.nim index 4a6d5db..ddae76d 100644 --- a/src/nimblepkg/packageparser.nim +++ b/src/nimblepkg/packageparser.nim @@ -212,7 +212,10 @@ proc multiSplit(s: string): seq[string] = result.del(i) # Huh, nothing to return? Return given input. if len(result) < 1: - return @[s] + if s.strip().len != 0: + return @[s] + else: + return @[] proc readPackageInfoFromNimble(path: string; result: var PackageInfo) = var fs = newFileStream(path, fmRead) From 38cd55e71af3df66c26d02d127c454d5b0a31e58 Mon Sep 17 00:00:00 2001 From: Ganesh Viswanathan Date: Tue, 3 Sep 2019 18:39:06 -0500 Subject: [PATCH 42/95] Fix #689 - don't lookup versions for aliases --- src/nimble.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nimble.nim b/src/nimble.nim index 999595e..20d81ed 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -551,7 +551,7 @@ proc search(options: Options) = var found = false template onFound {.dirty.} = echoPackage(pkg) - if options.queryVersions: + if pkg.alias.len == 0 and options.queryVersions: echoPackageVersions(pkg) echo(" ") found = true @@ -577,7 +577,7 @@ proc list(options: Options) = let pkgList = getPackageList(options) for pkg in pkgList: echoPackage(pkg) - if options.queryVersions: + if pkg.alias.len == 0 and options.queryVersions: echoPackageVersions(pkg) echo(" ") From 2ec470d2875a3d9d5d06f23c780f82281826c779 Mon Sep 17 00:00:00 2001 From: Ivan Bobev Date: Sat, 3 Aug 2019 18:38:41 +0300 Subject: [PATCH 43/95] Clear unused imports Related to #680 --- src/nimble.nim | 3 +-- src/nimblepkg/cli.nim | 7 ++----- src/nimblepkg/common.nim | 1 - src/nimblepkg/config.nim | 2 +- src/nimblepkg/nimscriptexecutor.nim | 5 +++-- src/nimblepkg/nimscriptsupport.nim | 11 ++++------- src/nimblepkg/nimscriptwrapper.nim | 5 ++--- src/nimblepkg/options.nim | 2 +- src/nimblepkg/packageinfo.nim | 3 +-- src/nimblepkg/packageinstaller.nim | 2 +- src/nimblepkg/packageparser.nim | 2 +- src/nimblepkg/publish.nim | 4 ++-- src/nimblepkg/tools.nim | 2 +- tests/tester.nim | 2 +- 14 files changed, 21 insertions(+), 30 deletions(-) diff --git a/src/nimble.nim b/src/nimble.nim index 20d81ed..a35f262 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -3,8 +3,7 @@ import system except TResult -import httpclient, parseopt, os, osproc, pegs, tables, parseutils, - strtabs, json, algorithm, sets, uri, sugar, sequtils +import os, tables, strtabs, json, algorithm, sets, uri, sugar, sequtils import strutils except toLower from unicode import toLower diff --git a/src/nimblepkg/cli.nim b/src/nimblepkg/cli.nim index 8d96015..9544373 100644 --- a/src/nimblepkg/cli.nim +++ b/src/nimblepkg/cli.nim @@ -12,11 +12,8 @@ # - Bright for HighPriority. # - Normal for MediumPriority. -import logging, terminal, sets, strutils, os -import ./common - -when defined(windows): - import winlean +import terminal, sets, strutils +import version type CLI* = ref object diff --git a/src/nimblepkg/common.nim b/src/nimblepkg/common.nim index abb8094..5cacb86 100644 --- a/src/nimblepkg/common.nim +++ b/src/nimblepkg/common.nim @@ -8,7 +8,6 @@ when not defined(nimscript): import sets import version - export version.NimbleError # TODO: Surely there is a better way? type BuildFailed* = object of NimbleError diff --git a/src/nimblepkg/config.nim b/src/nimblepkg/config.nim index 5706485..ca8dcf2 100644 --- a/src/nimblepkg/config.nim +++ b/src/nimblepkg/config.nim @@ -2,7 +2,7 @@ # BSD License. Look at license.txt for more info. import parsecfg, streams, strutils, os, tables, Uri -import tools, version, common, cli +import version, cli type Config* = object diff --git a/src/nimblepkg/nimscriptexecutor.nim b/src/nimblepkg/nimscriptexecutor.nim index fad0d1e..bf8afd1 100644 --- a/src/nimblepkg/nimscriptexecutor.nim +++ b/src/nimblepkg/nimscriptexecutor.nim @@ -1,9 +1,10 @@ # Copyright (C) Dominik Picheta. All rights reserved. # BSD License. Look at license.txt for more info. -import os, tables, strutils, sets +import os, strutils, sets -import packageparser, common, packageinfo, options, nimscriptwrapper, cli +import packageparser, common, packageinfo, options, nimscriptwrapper, cli, + version proc execHook*(options: Options, before: bool): bool = ## Returns whether to continue. diff --git a/src/nimblepkg/nimscriptsupport.nim b/src/nimblepkg/nimscriptsupport.nim index 4c8747b..cbe83d3 100644 --- a/src/nimblepkg/nimscriptsupport.nim +++ b/src/nimblepkg/nimscriptsupport.nim @@ -5,18 +5,15 @@ ## scripting language. import - compiler/ast, compiler/modules, compiler/passes, compiler/passaux, - compiler/condsyms, compiler/sem, compiler/semdata, - compiler/llstream, compiler/vm, compiler/vmdef, compiler/commands, - compiler/msgs, compiler/magicsys, compiler/idents, + compiler/ast, compiler/modules, compiler/passes, compiler/condsyms, + compiler/sem, compiler/llstream, compiler/vm, compiler/vmdef, compiler/idents, compiler/nimconf, compiler/nversion -from compiler/scriptconfig import setupVM from compiler/astalgo import strTableGet import compiler/options as compiler_options -import common, version, options, packageinfo, cli, tools -import os, strutils, strtabs, tables, times, osproc, sets, pegs +import common, version, options, cli, tools +import os, strutils, tables, times, osproc, sets, pegs when not declared(resetAllModulesHard): import compiler/modulegraphs diff --git a/src/nimblepkg/nimscriptwrapper.nim b/src/nimblepkg/nimscriptwrapper.nim index fa8f8f0..3f7ba51 100644 --- a/src/nimblepkg/nimscriptwrapper.nim +++ b/src/nimblepkg/nimscriptwrapper.nim @@ -4,9 +4,8 @@ ## Implements the new configuration system for Nimble. Uses Nim as a ## scripting language. -import common, version, options, packageinfo, cli, tools -import hashes, json, os, streams, strutils, strtabs, - tables, times, osproc, sets, pegs +import version, options, cli, tools +import hashes, json, os, strutils, tables, times, osproc type Flags = TableRef[string, seq[string]] diff --git a/src/nimblepkg/options.nim b/src/nimblepkg/options.nim index e345121..6ef2f95 100644 --- a/src/nimblepkg/options.nim +++ b/src/nimblepkg/options.nim @@ -4,7 +4,7 @@ import json, strutils, os, parseopt, strtabs, uri, tables, terminal from httpclient import Proxy, newProxy -import config, version, tools, common, cli +import config, version, common, cli type Options* = object diff --git a/src/nimblepkg/packageinfo.nim b/src/nimblepkg/packageinfo.nim index ec7daf0..6b2d6ae 100644 --- a/src/nimblepkg/packageinfo.nim +++ b/src/nimblepkg/packageinfo.nim @@ -3,8 +3,7 @@ # Stdlib imports import system except TResult -import hashes, parsecfg, json, streams, strutils, parseutils, os, sets, tables -import httpclient +import hashes, json, strutils, os, sets, tables, httpclient # Local imports import version, tools, common, options, cli, config diff --git a/src/nimblepkg/packageinstaller.nim b/src/nimblepkg/packageinstaller.nim index 125db93..f131942 100644 --- a/src/nimblepkg/packageinstaller.nim +++ b/src/nimblepkg/packageinstaller.nim @@ -3,7 +3,7 @@ import os, strutils, sets, json # Local imports -import cli, common, options, tools +import version, cli, options, tools when defined(windows): # This is just for Win XP support. diff --git a/src/nimblepkg/packageparser.nim b/src/nimblepkg/packageparser.nim index ddae76d..59c2ca8 100644 --- a/src/nimblepkg/packageparser.nim +++ b/src/nimblepkg/packageparser.nim @@ -1,6 +1,6 @@ # Copyright (C) Dominik Picheta. All rights reserved. # BSD License. Look at license.txt for more info. -import parsecfg, json, sets, streams, strutils, parseutils, os, tables, sugar +import parsecfg, sets, streams, strutils, os, tables, sugar from sequtils import apply, map import version, tools, common, nimscriptwrapper, options, packageinfo, cli diff --git a/src/nimblepkg/publish.nim b/src/nimblepkg/publish.nim index 333bbbd..186a3a5 100644 --- a/src/nimblepkg/publish.nim +++ b/src/nimblepkg/publish.nim @@ -5,8 +5,8 @@ ## nim-lang/packages automatically. import system except TResult -import httpclient, base64, strutils, rdstdin, json, os, browsers, times, uri -import tools, common, cli, config, options +import httpclient, strutils, json, os, browsers, times, uri +import version, tools, common, cli, config, options type Auth = object diff --git a/src/nimblepkg/tools.nim b/src/nimblepkg/tools.nim index acfa9a9..b39fea7 100644 --- a/src/nimblepkg/tools.nim +++ b/src/nimblepkg/tools.nim @@ -3,7 +3,7 @@ # # Various miscellaneous utility functions reside here. import osproc, pegs, strutils, os, uri, sets, json, parseutils -import version, common, cli +import version, cli proc extractBin(cmd: string): string = if cmd[0] == '"': diff --git a/tests/tester.nim b/tests/tester.nim index c2aefc3..2629817 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -1,6 +1,6 @@ # Copyright (C) Dominik Picheta. All rights reserved. # BSD License. Look at license.txt for more info. -import osproc, streams, unittest, strutils, os, sequtils, sugar +import osproc, unittest, strutils, os, sequtils, sugar # TODO: Each test should start off with a clean slate. Currently installed # packages are shared between each test which causes a multitude of issues From db018f235babd5b6e64d9524fafd02e110499dcd Mon Sep 17 00:00:00 2001 From: Ivan Bobev Date: Tue, 6 Aug 2019 16:34:53 +0300 Subject: [PATCH 44/95] Clear deprecation warnings This fix clears deprecation warnings related to Nim's HashSet procedures. There were two types of warnings which have been fixed: - Warning: Deprecated since v0.20, use 'initHashSet'; initSet is deprecated [Deprecated] - Warning: Deprecated since v0.20, use 'toHashSet'; toSet is deprecated [Deprecated] Backward compatibility with older Nim versions is also implemented. Related to #680 --- src/nimble.nim | 6 +++--- src/nimblepkg/cli.nim | 5 ++++- src/nimblepkg/common.nim | 12 ++++++++++++ src/nimblepkg/packageinfo.nim | 2 +- src/nimblepkg/packageinstaller.nim | 5 ++++- 5 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/nimble.nim b/src/nimble.nim index a35f262..6c13ca9 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -94,7 +94,7 @@ proc copyFilesRec(origDir, currentDir, dest: string, ## Copies all the required files, skips files specified in the .nimble file ## (PackageInfo). ## Returns a list of filepaths to files which have been installed. - result = initSet[string]() + result = initHashSet[string]() let whitelistMode = pkgInfo.installDirs.len != 0 or pkgInfo.installFiles.len != 0 or @@ -373,7 +373,7 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, createDir(pkgDestDir) # Copy this package's files based on the preferences specified in PkgInfo. - var filesInstalled = initSet[string]() + var filesInstalled = initHashSet[string]() iterInstallFiles(realDir, pkgInfo, options, proc (file: string) = createDir(changeRoot(realDir, pkgDestDir, file.splitFile.dir)) @@ -386,7 +386,7 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, pkgInfo.myPath) filesInstalled.incl copyFileD(pkgInfo.myPath, dest) - var binariesInstalled = initSet[string]() + var binariesInstalled = initHashSet[string]() if pkgInfo.bin.len > 0: # Make sure ~/.nimble/bin directory is created. createDir(binDir) diff --git a/src/nimblepkg/cli.nim b/src/nimblepkg/cli.nim index 9544373..a3ac714 100644 --- a/src/nimblepkg/cli.nim +++ b/src/nimblepkg/cli.nim @@ -15,6 +15,9 @@ import terminal, sets, strutils import version +when not declared(initHashSet): + import common + type CLI* = ref object level: Priority @@ -46,7 +49,7 @@ const proc newCLI(): CLI = result = CLI( level: HighPriority, - warnings: initSet[(string, string)](), + warnings: initHashSet[(string, string)](), suppressionCount: 0, showColor: true, suppressMessages: false diff --git a/src/nimblepkg/common.nim b/src/nimblepkg/common.nim index 5cacb86..f015d24 100644 --- a/src/nimblepkg/common.nim +++ b/src/nimblepkg/common.nim @@ -63,3 +63,15 @@ when not defined(nimscript): const nimbleVersion* = "0.10.2" + +when not declared(initHashSet): + import sets + + template initHashSet*[A](initialSize = 64): HashSet[A] = + initSet[A](initialSize) + +when not declared(toHashSet): + import sets + + template toHashSet*[A](keys: openArray[A]): HashSet[A] = + toSet(keys) diff --git a/src/nimblepkg/packageinfo.nim b/src/nimblepkg/packageinfo.nim index 6b2d6ae..e3cac25 100644 --- a/src/nimblepkg/packageinfo.nim +++ b/src/nimblepkg/packageinfo.nim @@ -277,7 +277,7 @@ proc getPackage*(pkg: string, options: Options, resPkg: var Package): bool = proc getPackageList*(options: Options): seq[Package] = ## Returns the list of packages found in the downloaded packages.json files. result = @[] - var namesAdded = initSet[string]() + var namesAdded = initHashSet[string]() for name, list in options.config.packageLists: let packages = readPackageList(name, options) for p in packages: diff --git a/src/nimblepkg/packageinstaller.nim b/src/nimblepkg/packageinstaller.nim index f131942..71f305f 100644 --- a/src/nimblepkg/packageinstaller.nim +++ b/src/nimblepkg/packageinstaller.nim @@ -5,6 +5,9 @@ import os, strutils, sets, json # Local imports import version, cli, options, tools +when not declared(initHashSet) or not declared(toHashSet): + import common + when defined(windows): # This is just for Win XP support. # TODO: Drop XP support? @@ -103,4 +106,4 @@ proc saveNimbleMeta*(pkgDestDir, pkgDir, vcsRevision, nimbleLinkPath: string) = ## pkgDir - The directory where the original package files are. ## For example: ~/projects/jester/ saveNimbleMeta(pkgDestDir, "file://" & pkgDir, vcsRevision, - toSet[string]([nimbleLinkPath]), initSet[string](), true) \ No newline at end of file + toHashSet[string]([nimbleLinkPath]), initHashSet[string](), true) \ No newline at end of file From 6e5761b1923ce213c2b956900591d7a6f50cde84 Mon Sep 17 00:00:00 2001 From: Ivan Bobev Date: Tue, 6 Aug 2019 17:25:08 +0300 Subject: [PATCH 45/95] Clear unused symbols warnings Unused functions and variables are removed from the Nimble's source code. Resolves #680 --- src/nimblepkg/download.nim | 20 -------------------- src/nimblepkg/nimscriptsupport.nim | 7 ------- src/nimblepkg/packageinfo.nim | 1 - 3 files changed, 28 deletions(-) diff --git a/src/nimblepkg/download.nim b/src/nimblepkg/download.nim index 639354f..d2e0074 100644 --- a/src/nimblepkg/download.nim +++ b/src/nimblepkg/download.nim @@ -10,13 +10,6 @@ type DownloadMethod* {.pure.} = enum git = "git", hg = "hg" -proc getSpecificDir(meth: DownloadMethod): string = - case meth - of DownloadMethod.git: - ".git" - of DownloadMethod.hg: - ".hg" - proc doCheckout(meth: DownloadMethod, downloadDir, branch: string) = case meth of DownloadMethod.git: @@ -30,19 +23,6 @@ proc doCheckout(meth: DownloadMethod, downloadDir, branch: string) = cd downloadDir: doCmd("hg checkout " & branch) -proc doPull(meth: DownloadMethod, downloadDir: string) = - case meth - of DownloadMethod.git: - doCheckout(meth, downloadDir, "") - cd downloadDir: - doCmd("git pull") - if existsFile(".gitmodules"): - doCmd("git submodule update") - of DownloadMethod.hg: - doCheckout(meth, downloadDir, "default") - cd downloadDir: - doCmd("hg pull") - proc doClone(meth: DownloadMethod, url, downloadDir: string, branch = "", onlyTip = true) = case meth diff --git a/src/nimblepkg/nimscriptsupport.nim b/src/nimblepkg/nimscriptsupport.nim index cbe83d3..fd45364 100644 --- a/src/nimblepkg/nimscriptsupport.nim +++ b/src/nimblepkg/nimscriptsupport.nim @@ -375,8 +375,6 @@ proc execScript(scriptName: string, flags: Flags, options: Options): PSym = "more info." raiseNimbleError(msg, hint) - let pkgName = scriptName.splitFile.name - # Ensure that "nimblepkg/nimscriptapi" is in the PATH. block: let t = getNimbleUserTempDir() / "nimblecache" @@ -692,11 +690,6 @@ proc execHook*(scriptName, actionName: string, before: bool, cleanup() -proc getNimScriptCommand(): string = - when declared(NimCompilerApiVersion): - let conf = graph.config - nimCommand() - proc setNimScriptCommand(command: string) = when declared(NimCompilerApiVersion): let conf = graph.config diff --git a/src/nimblepkg/packageinfo.nim b/src/nimblepkg/packageinfo.nim index e3cac25..f00c59f 100644 --- a/src/nimblepkg/packageinfo.nim +++ b/src/nimblepkg/packageinfo.nim @@ -310,7 +310,6 @@ proc findNimbleFile*(dir: string; error: bool): string = if result.splitFile.ext == ".nimble-link": # Return the path of the real .nimble file. - let nimbleLinkPath = result result = readNimbleLink(result).nimbleFilePath if not fileExists(result): let msg = "The .nimble-link file is pointing to a missing file: " & result From bfc4f255489801f248fed1d14ae558d207a49fb6 Mon Sep 17 00:00:00 2001 From: Ivan Bobev Date: Tue, 6 Aug 2019 18:08:51 +0300 Subject: [PATCH 46/95] Fix compilation of nimscriptapi.nim To compile the nimscriptapi.nim file, it is needed to import the os module, but if so the getParams procedure cannot be executed from nimscript any more. The problem is resolved by conditionally importing the module. Related to #680 --- src/nimblepkg/nimscriptapi.nim | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/nimblepkg/nimscriptapi.nim b/src/nimblepkg/nimscriptapi.nim index fffad4d..a8b4ba3 100644 --- a/src/nimblepkg/nimscriptapi.nim +++ b/src/nimblepkg/nimscriptapi.nim @@ -6,6 +6,9 @@ import system except getCommand, setCommand, switch, `--` import strformat, strutils, tables +when not defined(nimscript): + import os + var packageName* = "" ## Set this to the package name. It ## is usually not required to do that, nims' filename is From 1eb9f0f01cc8228e44635217658a9356b06dd2fe Mon Sep 17 00:00:00 2001 From: Ivan Bobev Date: Wed, 7 Aug 2019 17:01:05 +0300 Subject: [PATCH 47/95] Remove unused file The file nimscriptsupport.nim is no longer used in favor of the newer version nimscriptwrapper.nim. Related to #680 --- src/nimblepkg/nimscriptsupport.nim | 708 ----------------------------- 1 file changed, 708 deletions(-) delete mode 100644 src/nimblepkg/nimscriptsupport.nim diff --git a/src/nimblepkg/nimscriptsupport.nim b/src/nimblepkg/nimscriptsupport.nim deleted file mode 100644 index fd45364..0000000 --- a/src/nimblepkg/nimscriptsupport.nim +++ /dev/null @@ -1,708 +0,0 @@ -# Copyright (C) Andreas Rumpf. All rights reserved. -# BSD License. Look at license.txt for more info. - -## Implements the new configuration system for Nimble. Uses Nim as a -## scripting language. - -import - compiler/ast, compiler/modules, compiler/passes, compiler/condsyms, - compiler/sem, compiler/llstream, compiler/vm, compiler/vmdef, compiler/idents, - compiler/nimconf, compiler/nversion - -from compiler/astalgo import strTableGet -import compiler/options as compiler_options - -import common, version, options, cli, tools -import os, strutils, tables, times, osproc, sets, pegs - -when not declared(resetAllModulesHard): - import compiler/modulegraphs - -type - Flags = TableRef[string, seq[string]] - ExecutionResult*[T] = object - success*: bool - command*: string - arguments*: seq[string] - flags*: Flags - retVal*: T - -const - internalCmd = "NimbleInternal" - nimscriptApi = staticRead("nimscriptapi.nim") - -proc raiseVariableError(ident, typ: string) {.noinline.} = - raise newException(NimbleError, - "NimScript's variable '" & ident & "' needs a value of type '" & typ & "'.") - -proc isStrLit(n: PNode): bool = n.kind in {nkStrLit..nkTripleStrLit} - -when declared(NimCompilerApiVersion): - const finalApi = NimCompilerApiVersion >= 2 - - when NimCompilerApiVersion >= 3: - import compiler / pathutils -else: - const finalApi = false - -proc getGlobal(g: ModuleGraph; ident: PSym): string = - when finalApi: - let n = vm.getGlobalValue(PCtx g.vm, ident) - else: - let n = vm.globalCtx.getGlobalValue(ident) - if n.isStrLit: - result = n.strVal - else: - raiseVariableError(ident.name.s, "string") - -proc getGlobalAsSeq(g: ModuleGraph; ident: PSym): seq[string] = - when finalApi: - let n = vm.getGlobalValue(PCtx g.vm, ident) - else: - let n = vm.globalCtx.getGlobalValue(ident) - result = @[] - if n.kind == nkBracket: - for x in n: - if x.isStrLit: - result.add x.strVal - else: - raiseVariableError(ident.name.s, "seq[string]") - else: - raiseVariableError(ident.name.s, "seq[string]") - -proc extractRequires(g: ModuleGraph; ident: PSym, result: var seq[PkgTuple]) = - when finalApi: - let n = vm.getGlobalValue(PCtx g.vm, ident) - else: - let n = vm.globalCtx.getGlobalValue(ident) - if n.kind == nkBracket: - for x in n: - if x.kind == nkPar and x.len == 2 and x[0].isStrLit and x[1].isStrLit: - result.add(parseRequires(x[0].strVal & x[1].strVal)) - elif x.isStrLit: - result.add(parseRequires(x.strVal)) - else: - raiseVariableError("requiresData", "seq[(string, VersionReq)]") - else: - raiseVariableError("requiresData", "seq[(string, VersionReq)]") - -when declared(newIdentCache): - var identCache = newIdentCache() - -proc setupVM(graph: ModuleGraph; module: PSym; scriptName: string, flags: Flags): PEvalContext = - ## This procedure is exported in the compiler sources, but its implementation - ## is too Nim-specific to be used by Nimble. - ## Specifically, the implementation of ``switch`` is problematic. Sooo - ## I simply copied it here and edited it :) - when declared(NimCompilerApiVersion): - result = newCtx(module, identCache, graph) - elif declared(newIdentCache): - result = newCtx(module, identCache) - else: - result = newCtx(module) - result.mode = emRepl - registerAdditionalOps(result) - - # captured vars: - let conf = graph.config - var errorMsg: string - var vthisDir = scriptName.splitFile.dir - - proc listDirs(a: VmArgs, filter: set[PathComponent]) = - let dir = getString(a, 0) - var res: seq[string] = @[] - for kind, path in walkDir(dir): - if kind in filter: res.add path - setResult(a, res) - - template cbconf(name, body) {.dirty.} = - result.registerCallback "stdlib.system." & astToStr(name), - proc (a: VmArgs) = - body - - template cbos(name, body) {.dirty.} = - result.registerCallback "stdlib.system." & astToStr(name), - proc (a: VmArgs) = - try: - body - except OSError: - errorMsg = getCurrentExceptionMsg() - - # Idea: Treat link to file as a file, but ignore link to directory to prevent - # endless recursions out of the box. - cbos listFiles: - listDirs(a, {pcFile, pcLinkToFile}) - cbos listDirs: - listDirs(a, {pcDir}) - cbos removeDir: - os.removeDir getString(a, 0) - cbos removeFile: - os.removeFile getString(a, 0) - cbos createDir: - os.createDir getString(a, 0) - cbos getOsError: - setResult(a, errorMsg) - cbos setCurrentDir: - os.setCurrentDir getString(a, 0) - cbos getCurrentDir: - setResult(a, os.getCurrentDir()) - cbos moveFile: - os.moveFile(getString(a, 0), getString(a, 1)) - cbos copyFile: - os.copyFile(getString(a, 0), getString(a, 1)) - cbos getLastModificationTime: - setResult(a, toUnix(getLastModificationTime(getString(a, 0)))) - cbos findExe: - setResult(a, os.findExe(getString(a, 0))) - - cbos rawExec: - setResult(a, osproc.execCmd getString(a, 0)) - - cbconf getEnv: - setResult(a, os.getEnv(a.getString 0)) - cbconf existsEnv: - setResult(a, os.existsEnv(a.getString 0)) - cbconf dirExists: - setResult(a, os.dirExists(a.getString 0)) - cbconf fileExists: - setResult(a, os.fileExists(a.getString 0)) - - cbconf thisDir: - setResult(a, vthisDir) - cbconf put: - when declared(NimCompilerApiVersion): - compiler_options.setConfigVar(conf, getString(a, 0), getString(a, 1)) - else: - compiler_options.setConfigVar(getString(a, 0), getString(a, 1)) - cbconf get: - when declared(NimCompilerApiVersion): - setResult(a, compiler_options.getConfigVar(conf, a.getString 0)) - else: - setResult(a, compiler_options.getConfigVar(a.getString 0)) - cbconf exists: - when declared(NimCompilerApiVersion): - setResult(a, compiler_options.existsConfigVar(conf, a.getString 0)) - else: - setResult(a, compiler_options.existsConfigVar(a.getString 0)) - cbconf nimcacheDir: - when declared(NimCompilerApiVersion): - setResult(a, compiler_options.getNimcacheDir(conf)) - else: - setResult(a, compiler_options.getNimcacheDir()) - cbconf paramStr: - setResult(a, os.paramStr(int a.getInt 0)) - cbconf paramCount: - setResult(a, os.paramCount()) - cbconf cmpIgnoreStyle: - setResult(a, strutils.cmpIgnoreStyle(a.getString 0, a.getString 1)) - cbconf cmpIgnoreCase: - setResult(a, strutils.cmpIgnoreCase(a.getString 0, a.getString 1)) - cbconf setCommand: - when declared(NimCompilerApiVersion): - conf.command = a.getString 0 - let arg = a.getString 1 - if arg.len > 0: - conf.projectName = arg - when NimCompilerApiVersion >= 3: - try: - conf.projectFull = canonicalizePath(conf, - conf.projectPath / RelativeFile(conf.projectName)) - except OSError: - conf.projectFull = AbsoluteFile conf.projectName - else: - try: - conf.projectFull = canonicalizePath(conf, conf.projectPath / conf.projectName) - except OSError: - conf.projectFull = conf.projectName - else: - compiler_options.command = a.getString 0 - let arg = a.getString 1 - if arg.len > 0: - gProjectName = arg - try: - gProjectFull = canonicalizePath(gProjectPath / gProjectName) - except OSError: - gProjectFull = gProjectName - cbconf getCommand: - when declared(NimCompilerApiVersion): - setResult(a, conf.command) - else: - setResult(a, compiler_options.command) - cbconf switch: - if not flags.isNil: - let - key = a.getString 0 - value = a.getString 1 - if flags.hasKey(key): - flags[key].add(value) - else: - flags[key] = @[value] - -proc isValidLibPath(lib: string): bool = - return fileExists(lib / "system.nim") - -proc getNimPrefixDir(options: Options): string = - let env = getEnv("NIM_LIB_PREFIX") - if env != "": - let msg = "Using env var NIM_LIB_PREFIX: " & env - display("Warning:", msg, Warning, HighPriority) - return env - - if options.config.nimLibPrefix != "": - result = options.config.nimLibPrefix - let msg = "Using Nim stdlib prefix from Nimble config file: " & result - display("Warning:", msg, Warning, HighPriority) - return - - result = splitPath(findExe("nim")).head.parentDir - # The above heuristic doesn't work for 'choosenim' proxies. Thankfully in - # that case the `nimble` binary is beside the `nim` binary so things should - # just work. - if not dirExists(result / "lib"): - # By specifying an empty string we instruct the Nim compiler to use - # getAppDir().head as the prefix dir. See compiler/options module for - # the code responsible for this. - result = "" - -proc getLibVersion(lib: string): Version = - ## This is quite a hacky procedure, but there is no other way to extract - ## this out of the ``system`` module. We could evaluate it, but that would - ## cause an error if the stdlib is out of date. The purpose of this - ## proc is to give a nice error message to the user instead of a confusing - ## Nim compile error. - let systemPath = lib / "system.nim" - if not fileExists(systemPath): - raiseNimbleError("system module not found in stdlib path: " & lib) - - let systemFile = readFile(systemPath) - let majorPeg = peg"'NimMajor' @ '=' \s* {\d*}" - let minorPeg = peg"'NimMinor' @ '=' \s* {\d*}" - let patchPeg = peg"'NimPatch' @ '=' \s* {\d*}" - - var majorMatches: array[1, string] - let major = find(systemFile, majorPeg, majorMatches) - var minorMatches: array[1, string] - let minor = find(systemFile, minorPeg, minorMatches) - var patchMatches: array[1, string] - let patch = find(systemFile, patchPeg, patchMatches) - - if major != -1 and minor != -1 and patch != -1: - return newVersion(majorMatches[0] & "." & minorMatches[0] & "." & patchMatches[0]) - else: - return system.NimVersion.newVersion() - -when finalApi: - var graph = newModuleGraph(identCache, newConfigRef()) - -elif declared(ModuleGraph): - var graph = newModuleGraph() - -proc execScript(scriptName: string, flags: Flags, options: Options): PSym = - ## Executes the specified script. Returns the script's module symbol. - ## - ## No clean up is performed and must be done manually! - when finalApi: - graph = newModuleGraph(graph.cache, graph.config) - else: - graph = newModuleGraph(graph.config) - - let conf = graph.config - when declared(NimCompilerApiVersion): - if "nimblepkg/nimscriptapi" notin conf.implicitImports: - conf.implicitImports.add("nimblepkg/nimscriptapi") - elif declared(resetAllModulesHard): - # for compatibility with older Nim versions: - if "nimblepkg/nimscriptapi" notin compiler_options.implicitIncludes: - compiler_options.implicitIncludes.add("nimblepkg/nimscriptapi") - else: - if "nimblepkg/nimscriptapi" notin compiler_options.implicitImports: - compiler_options.implicitImports.add("nimblepkg/nimscriptapi") - - # Ensure the compiler can find its standard library #220. - when declared(NimCompilerApiVersion): - when NimCompilerApiVersion >= 3: - conf.prefixDir = AbsoluteDir getNimPrefixDir(options) - display("Setting", "Nim stdlib prefix to " & conf.prefixDir.string, - priority=LowPriority) - - template myLibPath(): untyped = conf.libpath.string - - else: - conf.prefixDir = getNimPrefixDir(options) - display("Setting", "Nim stdlib prefix to " & conf.prefixDir, - priority=LowPriority) - - template myLibPath(): untyped = conf.libpath - - # Verify that lib path points to existing stdlib. - setDefaultLibpath(conf) - else: - compiler_options.gPrefixDir = getNimPrefixDir(options) - display("Setting", "Nim stdlib prefix to " & compiler_options.gPrefixDir, - priority=LowPriority) - - template myLibPath(): untyped = compiler_options.libpath - - # Verify that lib path points to existing stdlib. - compiler_options.setDefaultLibpath() - - display("Setting", "Nim stdlib path to " & myLibPath(), - priority=LowPriority) - if not isValidLibPath(myLibPath()): - let msg = "Nimble cannot find Nim's standard library.\nLast try in:\n - $1" % - myLibPath() - let hint = "Nimble does its best to find Nim's standard library, " & - "sometimes this fails. You can set the environment variable " & - "NIM_LIB_PREFIX to where Nim's `lib` directory is located as " & - "a workaround. " & - "See https://github.com/nim-lang/nimble#troubleshooting for " & - "more info." - raiseNimbleError(msg, hint) - - # Verify that the stdlib that was found isn't older than the stdlib that Nimble - # was compiled with. - let libVersion = getLibVersion(myLibPath()) - if NimVersion.newVersion() > libVersion: - let msg = ("Nimble cannot use an older stdlib than the one it was compiled " & - "with.\n Stdlib in '$#' has version: $#.\n Nimble needs at least: $#.") % - [myLibPath(), $libVersion, NimVersion] - let hint = "You may be running a newer version of Nimble than you intended " & - "to. Run an older version of Nimble that is compatible with " & - "the stdlib that Nimble is attempting to use or set the environment variable " & - "NIM_LIB_PREFIX to where a different stdlib's `lib` directory is located as " & - "a workaround." & - "See https://github.com/nim-lang/nimble#troubleshooting for " & - "more info." - raiseNimbleError(msg, hint) - - # Ensure that "nimblepkg/nimscriptapi" is in the PATH. - block: - let t = getNimbleUserTempDir() / "nimblecache" - let tmpNimscriptApiPath = t / "nimblepkg" / "nimscriptapi.nim" - createDir(tmpNimscriptApiPath.splitFile.dir) - writeFile(tmpNimscriptApiPath, nimscriptApi) - when declared(NimCompilerApiVersion): - when NimCompilerApiVersion >= 3: - conf.searchPaths.add(AbsoluteDir t) - else: - conf.searchPaths.add(t) - else: - searchPaths.add(t) - - when declared(NimCompilerApiVersion): - initDefines(conf.symbols) - when NimCompilerApiVersion >= 2: - loadConfigs(DefaultConfig, graph.cache, conf) - else: - loadConfigs(DefaultConfig, conf) - passes.gIncludeFile = includeModule - passes.gImportModule = importModule - - defineSymbol(conf.symbols, "nimscript") - defineSymbol(conf.symbols, "nimconfig") - defineSymbol(conf.symbols, "nimble") - when NimCompilerApiVersion >= 2: - registerPass(graph, semPass) - registerPass(graph, evalPass) - else: - registerPass(semPass) - registerPass(evalPass) - - conf.searchPaths.add(conf.libpath) - else: - initDefines() - loadConfigs(DefaultConfig) - passes.gIncludeFile = includeModule - passes.gImportModule = importModule - - defineSymbol("nimscript") - defineSymbol("nimconfig") - defineSymbol("nimble") - registerPass(semPass) - registerPass(evalPass) - - searchPaths.add(compiler_options.libpath) - - when declared(resetAllModulesHard): - result = makeModule(scriptName) - else: - result = graph.makeModule(scriptName) - - incl(result.flags, sfMainModule) - when finalApi: - graph.vm = setupVM(graph, result, scriptName, flags) - - # Setup builtins defined in nimscriptapi.nim - template cbApi(name, body) {.dirty.} = - PCtx(graph.vm).registerCallback "nimscriptapi." & astToStr(name), - proc (a: VmArgs) = - body - - else: - vm.globalCtx = setupVM(graph, result, scriptName, flags) - - # Setup builtins defined in nimscriptapi.nim - template cbApi(name, body) {.dirty.} = - vm.globalCtx.registerCallback "nimscriptapi." & astToStr(name), - proc (a: VmArgs) = - body - - cbApi getPkgDir: - setResult(a, scriptName.splitFile.dir) - - when finalApi: - graph.compileSystemModule() - when NimCompilerApiVersion >= 3: - graph.processModule(result, llStreamOpen(AbsoluteFile scriptName, fmRead)) - else: - graph.processModule(result, llStreamOpen(scriptName, fmRead)) - elif declared(newIdentCache): - graph.compileSystemModule(identCache) - graph.processModule(result, llStreamOpen(scriptName, fmRead), nil, identCache) - else: - compileSystemModule() - processModule(result, llStreamOpen(scriptName, fmRead), nil) - -proc cleanup() = - # ensure everything can be called again: - when declared(NimCompilerApiVersion): - let conf = graph.config - conf.projectName = "" - conf.command = "" - else: - compiler_options.gProjectName = "" - compiler_options.command = "" - when declared(NimCompilerApiVersion): - resetSystemArtifacts(graph) - elif declared(resetAllModulesHard): - resetAllModulesHard() - else: - resetSystemArtifacts() - when finalApi: - clearPasses(graph) - else: - clearPasses() - when declared(NimCompilerApiVersion): - conf.errorMax = 1 - when NimCompilerApiVersion >= 2: - conf.writeLnHook = nil - graph.vm = nil - else: - msgs.writeLnHook = nil - vm.globalCtx = nil - initDefines(conf.symbols) - else: - msgs.gErrorMax = 1 - msgs.writeLnHook = nil - vm.globalCtx = nil - initDefines() - -proc readPackageInfoFromNims*(scriptName: string, options: Options, - result: var PackageInfo) = - ## Executes the `scriptName` nimscript file. Reads the package information - ## that it populates. - - # Setup custom error handling. - when declared(NimCompilerApiVersion): - let conf = graph.config - conf.errorMax = high(int) - else: - msgs.gErrorMax = high(int) - - template errCounter(): int = - when declared(NimCompilerApiVersion): conf.errorCounter - else: msgs.gErrorCounter - - var previousMsg = "" - - proc writelnHook(output: string) = - # The error counter is incremented after the writeLnHook is invoked. - if errCounter() > 0: - raise newException(NimbleError, previousMsg) - elif previousMsg.len > 0: - display("Info", previousMsg, priority = MediumPriority) - if output.normalize.startsWith("error"): - raise newException(NimbleError, output) - previousMsg = output - - when finalApi: - conf.writelnHook = writelnHook - else: - msgs.writeLnHook = writelnHook - - when declared(NimCompilerApiVersion): - conf.command = internalCmd - else: - compiler_options.command = internalCmd - - # Execute the nimscript file. - let thisModule = execScript(scriptName, nil, options) - - when declared(resetAllModulesHard): - let apiModule = thisModule - else: - var apiModule: PSym - for i in 0.. 0: - raise newException(NimbleError, previousMsg) - - # Extract all the necessary fields populated by the nimscript file. - proc getSym(apiModule: PSym, ident: string): PSym = - result = apiModule.tab.strTableGet(getIdent(identCache, ident)) - if result.isNil: - raise newException(NimbleError, "Ident not found: " & ident) - - template trivialField(field) = - result.field = getGlobal(graph, getSym(apiModule, astToStr field)) - - template trivialFieldSeq(field) = - result.field.add getGlobalAsSeq(graph, getSym(apiModule, astToStr field)) - - # keep reasonable default: - let name = getGlobal(graph, apiModule.tab.strTableGet(getIdent(identCache, "packageName"))) - if name.len > 0: result.name = name - - trivialField version - trivialField author - trivialField description - trivialField license - trivialField srcdir - trivialField bindir - trivialFieldSeq skipDirs - trivialFieldSeq skipFiles - trivialFieldSeq skipExt - trivialFieldSeq installDirs - trivialFieldSeq installFiles - trivialFieldSeq installExt - trivialFieldSeq foreignDeps - - extractRequires(graph, getSym(apiModule, "requiresData"), result.requires) - - let binSeq = getGlobalAsSeq(graph, getSym(apiModule, "bin")) - for i in binSeq: - result.bin.add(i.addFileExt(ExeExt)) - - let backend = getGlobal(graph, getSym(apiModule, "backend")) - if backend.len == 0: - result.backend = "c" - elif cmpIgnoreStyle(backend, "javascript") == 0: - result.backend = "js" - else: - result.backend = backend.toLowerAscii() - - # Grab all the global procs - for i in thisModule.tab.data: - if not i.isNil(): - let name = i.name.s.normalize() - if name.endsWith("before"): - result.preHooks.incl(name[0 .. ^7]) - if name.endsWith("after"): - result.postHooks.incl(name[0 .. ^6]) - - cleanup() - -when declared(NimCompilerApiVersion): - template nimCommand(): untyped = conf.command - template nimProjectName(): untyped = conf.projectName -else: - template nimCommand(): untyped = compiler_options.command - template nimProjectName(): untyped = compiler_options.gProjectName - -proc execTask*(scriptName, taskName: string, - options: Options): ExecutionResult[void] = - ## Executes the specified task in the specified script. - ## - ## `scriptName` should be a filename pointing to the nimscript file. - result.success = true - result.flags = newTable[string, seq[string]]() - when declared(NimCompilerApiVersion): - let conf = graph.config - nimCommand() = internalCmd - display("Executing", "task $# in $#" % [taskName, scriptName], - priority = HighPriority) - - let thisModule = execScript(scriptName, result.flags, options) - let prc = thisModule.tab.strTableGet(getIdent(identCache, taskName & "Task")) - if prc.isNil: - # Procedure not defined in the NimScript module. - result.success = false - cleanup() - return - when finalApi: - discard vm.execProc(PCtx(graph.vm), prc, []) - else: - discard vm.globalCtx.execProc(prc, []) - - # Read the command, arguments and flags set by the executed task. - result.command = nimCommand() - result.arguments = @[] - for arg in nimProjectName().split(): - result.arguments.add(arg) - - cleanup() - -proc execHook*(scriptName, actionName: string, before: bool, - options: Options): ExecutionResult[bool] = - ## Executes the specified action's hook. Depending on ``before``, either - ## the "before" or the "after" hook. - ## - ## `scriptName` should be a filename pointing to the nimscript file. - when declared(NimCompilerApiVersion): - let conf = graph.config - result.success = true - result.flags = newTable[string, seq[string]]() - nimCommand() = internalCmd - let hookName = - if before: actionName.toLowerAscii & "Before" - else: actionName.toLowerAscii & "After" - display("Attempting", "to execute hook $# in $#" % [hookName, scriptName], - priority = MediumPriority) - - let thisModule = execScript(scriptName, result.flags, options) - # Explicitly execute the task procedure, instead of relying on hack. - let prc = thisModule.tab.strTableGet(getIdent(identCache, hookName)) - if prc.isNil: - # Procedure not defined in the NimScript module. - result.success = false - cleanup() - return - when finalApi: - let returnVal = vm.execProc(PCtx(graph.vm), prc, []) - else: - let returnVal = vm.globalCtx.execProc(prc, []) - case returnVal.kind - of nkCharLit..nkUInt64Lit: - result.retVal = returnVal.intVal == 1 - else: assert false - - # Read the command, arguments and flags set by the executed task. - result.command = nimCommand() - result.arguments = @[] - for arg in nimProjectName().split(): - result.arguments.add(arg) - - cleanup() - -proc setNimScriptCommand(command: string) = - when declared(NimCompilerApiVersion): - let conf = graph.config - nimCommand() = command - -proc hasTaskRequestedCommand*(execResult: ExecutionResult): bool = - ## Determines whether the last executed task used ``setCommand`` - return execResult.command != internalCmd - -proc listTasks*(scriptName: string, options: Options) = - setNimScriptCommand("help") - - discard execScript(scriptName, nil, options) - # TODO (#402): Make the 'task' template generate explicit data structure - # containing all the task names + descriptions. - cleanup() From bb1dd212247a98fdf007f883de77ba7200a20e2b Mon Sep 17 00:00:00 2001 From: Ivan Bobev Date: Wed, 7 Aug 2019 19:24:07 +0300 Subject: [PATCH 48/95] Add an unit test for compilation without warnings An unit test which checks whether Nimble compiles without warnings is added. Checking for three warning types cleaned in previous commits is implemented: - [UnusedImport] cleaned in e8c7d5c - [Deprecated] cleaned in 3d6172e - [XDeclaredButNotUsed] cleaned in 7df2ef3 Other types of warnings easily can be added to the test by extending the warnings list. Related to #680 --- src/nimblepkg/nimscriptwrapper.nim | 2 +- tests/tester.nim | 39 ++++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/nimblepkg/nimscriptwrapper.nim b/src/nimblepkg/nimscriptwrapper.nim index 3f7ba51..d1f8d7a 100644 --- a/src/nimblepkg/nimscriptwrapper.nim +++ b/src/nimblepkg/nimscriptwrapper.nim @@ -5,7 +5,7 @@ ## scripting language. import version, options, cli, tools -import hashes, json, os, strutils, tables, times, osproc +import hashes, json, os, strutils, tables, times, osproc, strtabs type Flags = TableRef[string, seq[string]] diff --git a/tests/tester.nim b/tests/tester.nim index 2629817..079d9f5 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -1,6 +1,6 @@ # Copyright (C) Dominik Picheta. All rights reserved. # BSD License. Look at license.txt for more info. -import osproc, unittest, strutils, os, sequtils, sugar +import osproc, unittest, strutils, os, sequtils, sugar, strformat # TODO: Each test should start off with a clean slate. Currently installed # packages are shared between each test which causes a multitude of issues @@ -10,6 +10,7 @@ var rootDir = getCurrentDir().parentDir() var nimblePath = rootDir / "src" / addFileExt("nimble", ExeExt) var installDir = rootDir / "tests" / "nimbleDir" const path = "../src/nimble" +const stringNotFound = -1 # Clear nimble dir. removeDir(installDir) @@ -863,9 +864,43 @@ test "do not install single dependency multiple times (#678)": check execNimble(["refresh"]).exitCode == QuitSuccess let (output, exitCode) = execNimble("install", "-y") check exitCode == QuitSuccess - check output.find("issue678_dependency_1@0.1.0 already exists") == -1 + let index = output.find("issue678_dependency_1@0.1.0 already exists") + check index == stringNotFound test "Passing command line arguments to a task (#633)": cd "issue633": var (output, exitCode) = execNimble("testTask --testTask") + check exitCode == QuitSuccess check output.contains("Got it") + +test "compilation without warnings": + const buildDir = "./buildDir/" + const filesToBuild = [ + "../src/nimble.nim", + "../src/nimblepkg/nimscriptapi.nim", + "./tester.nim", + ] + + proc execBuild(fileName: string): tuple[output: string, exitCode: int] = + result = execCmdEx(fmt"nim c -o:{buildDir} {fileName}") + + proc checkOutput(output: string): uint = + const warningsToCheck = [ + "[UnusedImport]", + "[Deprecated]", + "[XDeclaredButNotUsed]", + ] + + for line in output.splitLines(): + for warning in warningsToCheck: + if line.find(warning) != stringNotFound: + once: checkpoint("Detected warnings:") + checkpoint(line) + inc(result) + + var linesWithWarningsCount: uint = 0 + for file in filesToBuild: + let (output, exitCode) = execBuild(file) + check exitCode == QuitSuccess + linesWithWarningsCount += checkOutput(output) + check linesWithWarningsCount == 0 From 427b412ff9e6bb72f447b2886b7ff5cfec9393cc Mon Sep 17 00:00:00 2001 From: Ivan Bobev Date: Wed, 7 Aug 2019 23:03:14 +0300 Subject: [PATCH 49/95] Fix unused import warning on Unix systems The NimbleError exception from the version module is being used only on Windows platforms in the packageinstaller.nim file. Conditional import of the module is used to fix the warning. Related to #680 --- src/nimblepkg/packageinstaller.nim | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/nimblepkg/packageinstaller.nim b/src/nimblepkg/packageinstaller.nim index 71f305f..4bdc921 100644 --- a/src/nimblepkg/packageinstaller.nim +++ b/src/nimblepkg/packageinstaller.nim @@ -3,7 +3,10 @@ import os, strutils, sets, json # Local imports -import version, cli, options, tools +import cli, options, tools + +when defined(windows): + import version when not declared(initHashSet) or not declared(toHashSet): import common From f0c454a3995b92ae50ecb3f511168e2bef21019b Mon Sep 17 00:00:00 2001 From: Ivan Bobev Date: Thu, 8 Aug 2019 16:34:48 +0300 Subject: [PATCH 50/95] Fix compilation without warnings test case Compilation without warnings test case on Unix systems with older Nim version 0.19.6 had been failing before the fix, because the compiler expects full file name to be passed after the '-o' switch, but not only an output directory name. Related to #680 --- tests/tester.nim | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/tester.nim b/tests/tester.nim index 079d9f5..2c39a83 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -882,7 +882,8 @@ test "compilation without warnings": ] proc execBuild(fileName: string): tuple[output: string, exitCode: int] = - result = execCmdEx(fmt"nim c -o:{buildDir} {fileName}") + result = execCmdEx( + fmt"nim c -o:{buildDir/fileName.splitFile.name} {fileName}") proc checkOutput(output: string): uint = const warningsToCheck = [ @@ -898,6 +899,8 @@ test "compilation without warnings": checkpoint(line) inc(result) + removeDir(buildDir) + var linesWithWarningsCount: uint = 0 for file in filesToBuild: let (output, exitCode) = execBuild(file) From 9d8cc0672455d5cf34adc32c2562ffc68b92c226 Mon Sep 17 00:00:00 2001 From: Ivan Bobev Date: Thu, 8 Aug 2019 17:11:25 +0300 Subject: [PATCH 51/95] Add additional directory to .gitignore The directory "tests/buildDir/" where the build artifacts from the "compilation without warnings" unit test execution are being written is added to the .gitignore file. Related to #680 --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5c629ae..3388f5f 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,5 @@ tests/testCommand/testsFail/tests/t2 # Test procedure artifacts tests/nimble-test/ +tests/buildDir/ nimble_*.nims From df11a6f6cf253b2433e37b90dab2967c86afc810 Mon Sep 17 00:00:00 2001 From: Ivan Bobev Date: Sun, 11 Aug 2019 15:48:14 +0300 Subject: [PATCH 52/95] Fix tests to pass with the latest Nim There are two issues fixed: - With the latest Nim version sometimes the files `_.nims`' generated on `nim install` contain warning for unused imports which causes the test "can validate package structure (#144)" to fail, because it was searching for the word "warning" in the output. - On Windows Subsystem for Linux, when an import starts sometimes with a lowercase, and sometimes with an uppercase, for example `import uri` and `import Uri`, this causes Nim to create and compile both `stdlib_uri.nim.c` and `stdlib_Uri.nim.c` and to fail on the linking step, because of the same symbols are being redefined. Also the Travis CI build script is changed to test against currently the latest working Nim version 212ae2f. Related to #680 --- .travis.yml | 3 ++- src/nimblepkg/config.nim | 2 +- tests/tester.nim | 33 +++++++++++++++++++++------------ 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index 13b8678..8cfa912 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,8 @@ language: c env: - BRANCH=0.19.6 - BRANCH=0.20.2 - - BRANCH=#44aadd50cfa647a759610a15967960632bf597ce + # This is the latest working Nim version against which Nimble is being tested + - BRANCH=#212ae2f1257628bd5d1760593ce0a1bad768831a cache: directories: diff --git a/src/nimblepkg/config.nim b/src/nimblepkg/config.nim index ca8dcf2..c4a48fc 100644 --- a/src/nimblepkg/config.nim +++ b/src/nimblepkg/config.nim @@ -1,6 +1,6 @@ # Copyright (C) Dominik Picheta. All rights reserved. # BSD License. Look at license.txt for more info. -import parsecfg, streams, strutils, os, tables, Uri +import parsecfg, streams, strutils, os, tables, uri import version, cli diff --git a/tests/tester.nim b/tests/tester.nim index 2c39a83..bc9722e 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -72,6 +72,12 @@ proc inLines(lines: seq[string], line: string): bool = for i in lines: if line.normalize in i.normalize: return true +proc hasLineStartingWith(lines: seq[string], prefix: string): bool = + for line in lines: + if line.strip(trailing = false).startsWith(prefix): + return true + return false + test "caching of nims and ini detects changes": cd "caching": var (output, exitCode) = execNimble("dump") @@ -117,7 +123,7 @@ test "can validate package structure (#144)": let (output, exitCode) = execNimble(["install", "-y"]) check exitCode == QuitSuccess let lines = output.strip.processOutput() - check(not inLines(lines, "warning")) + check(not lines.hasLineStartingWith("Warning:")) # Test that warnings are produced for the incorrectly structured packages. for package in ["x", "y", "z"]: @@ -128,19 +134,22 @@ test "can validate package structure (#144)": checkpoint(output) case package of "x": - check inLines(lines, "Package 'x' has an incorrect structure. It should" & - " contain a single directory hierarchy for source files," & - " named 'x', but file 'foobar.nim' is in a directory named" & - " 'incorrect' instead.") + check lines.hasLineStartingWith( + "Warning: Package 'x' has an incorrect structure. It should" & + " contain a single directory hierarchy for source files," & + " named 'x', but file 'foobar.nim' is in a directory named" & + " 'incorrect' instead.") of "y": - check inLines(lines, "Package 'y' has an incorrect structure. It should" & - " contain a single directory hierarchy for source files," & - " named 'ypkg', but file 'foobar.nim' is in a directory named" & - " 'yWrong' instead.") + check lines.hasLineStartingWith( + "Warning: Package 'y' has an incorrect structure. It should" & + " contain a single directory hierarchy for source files," & + " named 'ypkg', but file 'foobar.nim' is in a directory named" & + " 'yWrong' instead.") of "z": - check inLines(lines, "Package 'z' has an incorrect structure. The top level" & - " of the package source directory should contain at most one module," & - " named 'z.nim', but a file named 'incorrect.nim' was found.") + check lines.hasLineStartingWith( + "Warning: Package 'z' has an incorrect structure. The top level" & + " of the package source directory should contain at most one module," & + " named 'z.nim', but a file named 'incorrect.nim' was found.") else: assert false From 62699afaa8b63888b6d719b4d877d79291bcafe9 Mon Sep 17 00:00:00 2001 From: Ivan Bobev Date: Tue, 27 Aug 2019 15:17:08 +0300 Subject: [PATCH 53/95] Revert deletion of two not used procedures The two currently not used procedures `getSpecificDir` and `doPull` from `download.nim` file are reverted according to dom96's suggestion in the code review of pull request #692. Now they are marked with used pragma to disable the warning. Related to #680 --- src/nimblepkg/download.nim | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/nimblepkg/download.nim b/src/nimblepkg/download.nim index d2e0074..85bf54f 100644 --- a/src/nimblepkg/download.nim +++ b/src/nimblepkg/download.nim @@ -10,6 +10,13 @@ type DownloadMethod* {.pure.} = enum git = "git", hg = "hg" +proc getSpecificDir(meth: DownloadMethod): string {.used.} = + case meth + of DownloadMethod.git: + ".git" + of DownloadMethod.hg: + ".hg" + proc doCheckout(meth: DownloadMethod, downloadDir, branch: string) = case meth of DownloadMethod.git: @@ -23,6 +30,19 @@ proc doCheckout(meth: DownloadMethod, downloadDir, branch: string) = cd downloadDir: doCmd("hg checkout " & branch) +proc doPull(meth: DownloadMethod, downloadDir: string) {.used.} = + case meth + of DownloadMethod.git: + doCheckout(meth, downloadDir, "") + cd downloadDir: + doCmd("git pull") + if existsFile(".gitmodules"): + doCmd("git submodule update") + of DownloadMethod.hg: + doCheckout(meth, downloadDir, "default") + cd downloadDir: + doCmd("hg pull") + proc doClone(meth: DownloadMethod, url, downloadDir: string, branch = "", onlyTip = true) = case meth From 1880730762d298af883d633be5c86ec33bcd8e7a Mon Sep 17 00:00:00 2001 From: Ivan Bobev Date: Sun, 1 Sep 2019 21:19:06 +0300 Subject: [PATCH 54/95] Fix the test for recursive calling of Nimble tasks The test case for recursive calling of Nimble tasks was not working properly on Windows so far. Related to #680 --- tests/recursive/recursive.nimble | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/recursive/recursive.nimble b/tests/recursive/recursive.nimble index a188170..972dd0b 100644 --- a/tests/recursive/recursive.nimble +++ b/tests/recursive/recursive.nimble @@ -9,13 +9,18 @@ license = "BSD" requires "nim >= 0.12.1" +when defined(windows): + let callNimble = "..\\..\\src\\nimble.exe" +else: + let callNimble = "../../src/nimble" + task recurse, "Level 1": echo 1 - exec "../../src/nimble recurse2" + exec callNimble & " recurse2" task recurse2, "Level 2": echo 2 - exec "../../src/nimble recurse3" + exec callNimble & " recurse3" task recurse3, "Level 3": echo 3 From 46f26e1d4a05ff447d80cce4edd7d9549d45ba80 Mon Sep 17 00:00:00 2001 From: Ivan Bobev Date: Fri, 6 Sep 2019 18:57:58 +0300 Subject: [PATCH 55/95] Move ignored from tests dir to its .gitignore Related to #680 --- .gitignore | 9 +-------- tests/.gitignore | 8 ++++++++ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 3388f5f..bac71d6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,20 +6,15 @@ nimcache/ # Absolute paths /src/babel /src/nimble -/tests/tester # executables from test and build /nimble -/tests/nimscript/nimscript -/tests/issue27/issue27 src/nimblepkg/cli src/nimblepkg/packageinfo src/nimblepkg/packageparser src/nimblepkg/reversedeps src/nimblepkg/version -tests/nimble-test/ -tests/packageStructure/validBinary/y -tests/testCommand/testsFail/tests/t2 +src/nimblepkg/download # Windows executables *.exe @@ -36,6 +31,4 @@ tests/testCommand/testsFail/tests/t2 *.orig # Test procedure artifacts -tests/nimble-test/ -tests/buildDir/ nimble_*.nims diff --git a/tests/.gitignore b/tests/.gitignore index 8258bb3..42549a0 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1,6 +1,10 @@ +tester +/nimble-test +/buildDir /binaryPackage/v1/binaryPackage /binaryPackage/v2/binaryPackage /develop/dependent/src/dependent +/issue27/issue27 /issue206/issue/issue206bin /issue289/issue289 /issue428/nimbleDir/ @@ -13,3 +17,7 @@ /testCommand/testsPass/tests/one /testCommand/testsPass/tests/three /testCommand/testsPass/tests/two +/nimscript/nimscript +/packageStructure/validBinary/y +/testCommand/testsFail/tests/t2 +/passNimFlags/passNimFlags From d6a6a47dd9c4bca64fc06f1362929c377e2262af Mon Sep 17 00:00:00 2001 From: Neelesh Chandola Date: Mon, 16 Sep 2019 15:30:01 +0530 Subject: [PATCH 56/95] Fix https://github.com/nim-lang/nimble/issues/700 --- src/nimblepkg/init.nim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nimblepkg/init.nim b/src/nimblepkg/init.nim index 6648877..8e7c33b 100644 --- a/src/nimblepkg/init.nim +++ b/src/nimblepkg/init.nim @@ -164,8 +164,8 @@ test "correct welcome": writeFile(nimbleFile, """# Package version = $# -author = $# -description = $# +author = "$#" +description = "$#" license = $# srcDir = $# $# @@ -175,7 +175,7 @@ $# requires "nim >= $#" """ % [ - info.pkgVersion.escape(), info.pkgAuthor, info.pkgDesc, + info.pkgVersion.escape(), info.pkgAuthor.replace("\"", "\\\""), info.pkgDesc.replace("\"", "\\\""), info.pkgLicense.escape(), info.pkgSrcDir.escape(), nimbleFileOptions, pkgBackend, info.pkgNimDep ] From 8bdc0548175ac3feba968a89625143bacc005d4a Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 20 Sep 2019 09:44:50 +0200 Subject: [PATCH 57/95] fixes #703 --- src/nimble.nim | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/nimble.nim b/src/nimble.nim index 6c13ca9..4685eb0 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -348,7 +348,11 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, # if the build fails then the old package will still be installed. if pkgInfo.bin.len > 0: let paths = result.deps.map(dep => dep.getRealDir()) - buildFromDir(pkgInfo, paths, options.action.passNimFlags & "-d:release") + let flags = if options.action.typ in {actionInstall, actionPath, actionUninstall, actionDevelop}: + options.action.passNimFlags + else: + @[] + buildFromDir(pkgInfo, paths, flags & "-d:release") let pkgDestDir = pkgInfo.getPkgDest(options) if existsDir(pkgDestDir) and existsFile(pkgDestDir / "nimblemeta.json"): From bc8856632a39e201b01bb98a43c97d26bd24ff78 Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 20 Sep 2019 09:49:30 +0200 Subject: [PATCH 58/95] version bump to 0.11.0 --- nimble.nimble | 2 +- src/nimblepkg/common.nim | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nimble.nimble b/nimble.nimble index 5344094..6cd7e9e 100644 --- a/nimble.nimble +++ b/nimble.nimble @@ -1,6 +1,6 @@ # Package -version = "0.10.2" +version = "0.11.0" author = "Dominik Picheta" description = "Nim package manager." license = "BSD" diff --git a/src/nimblepkg/common.nim b/src/nimblepkg/common.nim index f015d24..c40a82d 100644 --- a/src/nimblepkg/common.nim +++ b/src/nimblepkg/common.nim @@ -62,7 +62,7 @@ when not defined(nimscript): return (error, hint) const - nimbleVersion* = "0.10.2" + nimbleVersion* = "0.11.0" when not declared(initHashSet): import sets From 3eae8d7d616e84cfa4bf9c4264e9513050a780d9 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sat, 21 Sep 2019 12:46:42 +0100 Subject: [PATCH 59/95] Add examples to tag prompt in `nimble publish`. --- src/nimblepkg/publish.nim | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/nimblepkg/publish.nim b/src/nimblepkg/publish.nim index 186a3a5..f11b979 100644 --- a/src/nimblepkg/publish.nim +++ b/src/nimblepkg/publish.nim @@ -213,7 +213,10 @@ proc publish*(p: PackageInfo, o: Options) = url = promptCustom("Github URL of " & p.name & "?", "") if url.len == 0: userAborted() - let tags = promptCustom("Whitespace separated list of tags?", "") + let tags = promptCustom( + "Whitespace separated list of tags? (For example: web library wrapper)", + "" + ) cd pkgsDir: editJson(p, url, tags, downloadMethod) From 445ecfe9461029184c1e710902262e2149a3564b Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sat, 21 Sep 2019 13:02:04 +0100 Subject: [PATCH 60/95] Fixes #649. The `dump` command now has implicit `-y`. --- src/nimblepkg/cli.nim | 19 +++++++++++++------ src/nimblepkg/options.nim | 6 ++++-- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/nimblepkg/cli.nim b/src/nimblepkg/cli.nim index a3ac714..3afda35 100644 --- a/src/nimblepkg/cli.nim +++ b/src/nimblepkg/cli.nim @@ -62,8 +62,18 @@ proc calculateCategoryOffset(category: string): int = assert category.len <= longestCategory return longestCategory - category.len +proc isSuppressed(displayType: DisplayType): bool = + # Don't print any Warning, Message or Success messages when suppression of + # warnings is enabled. That is, unless the user asked for --verbose output. + if globalCLI.suppressMessages and displayType >= Warning and + globalCLI.level == HighPriority: + return true + proc displayCategory(category: string, displayType: DisplayType, priority: Priority) = + if isSuppressed(displayType): + return + # Calculate how much the `category` must be offset to align along a center # line. let offset = calculateCategoryOffset(category) @@ -80,6 +90,9 @@ proc displayCategory(category: string, displayType: DisplayType, proc displayLine(category, line: string, displayType: DisplayType, priority: Priority) = + if isSuppressed(displayType): + return + displayCategory(category, displayType, priority) # Display the message. @@ -87,12 +100,6 @@ proc displayLine(category, line: string, displayType: DisplayType, proc display*(category, msg: string, displayType = Message, priority = MediumPriority) = - # Don't print any Warning, Message or Success messages when suppression of - # warnings is enabled. That is, unless the user asked for --verbose output. - if globalCLI.suppressMessages and displayType >= Warning and - globalCLI.level == HighPriority: - return - # Multiple warnings containing the same messages should not be shown. let warningPair = (category, msg) if displayType == Warning: diff --git a/src/nimblepkg/options.nim b/src/nimblepkg/options.nim index 6ef2f95..3482f34 100644 --- a/src/nimblepkg/options.nim +++ b/src/nimblepkg/options.nim @@ -187,6 +187,7 @@ proc initAction*(options: var Options, key: string) = options.action.projName = "" of actionDump: options.action.projName = "" + options.forcePrompts = forcePromptYes of actionRefresh: options.action.optionalURL = "" of actionSearch: @@ -264,8 +265,9 @@ proc parseArgument*(key: string, result: var Options) = result.action.search.add(key) of actionInit, actionDump: if result.action.projName != "": - raise newException(NimbleError, - "Can only initialize one package at a time.") + raise newException( + NimbleError, "Can only perform this action on one package at a time." + ) result.action.projName = key of actionCompile, actionDoc: result.action.file = key From 36c4a39674c0f81baa87d930265f5903d7a32fed Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sat, 21 Sep 2019 22:54:08 +0100 Subject: [PATCH 61/95] Temp files are no longer removed when --debug is present. --- src/nimble.nim | 10 +++++++--- src/nimblepkg/nimscriptwrapper.nim | 7 ++++--- src/nimblepkg/options.nim | 7 +++++++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/nimble.nim b/src/nimble.nim index 4685eb0..666a60f 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -1040,7 +1040,7 @@ proc test(options: Options) = display("Error:", error, Error, HighPriority) proc check(options: Options) = - ## Validates a package a in the current working directory. + ## Validates a package in the current working directory. let nimbleFile = findNimbleFile(getCurrentDir(), true) var error: ValidationError var pkgInfo: PackageInfo @@ -1134,8 +1134,10 @@ when isMainModule: var error = "" var hint = "" + var opt: Options try: - parseCmdLine().doAction() + opt = parseCmdLine() + opt.doAction() except NimbleError: let currentExc = (ref NimbleError)(getCurrentException()) (error, hint) = getOutputInfo(currentExc) @@ -1143,7 +1145,9 @@ when isMainModule: discard finally: try: - removeDir(getNimbleTempDir()) + let folder = getNimbleTempDir() + if opt.shouldRemoveTmp(folder): + removeDir(folder) except OSError: let msg = "Couldn't remove Nimble's temp dir" display("Warning:", msg, Warning, MediumPriority) diff --git a/src/nimblepkg/nimscriptwrapper.nim b/src/nimblepkg/nimscriptwrapper.nim index d1f8d7a..d7b7a3b 100644 --- a/src/nimblepkg/nimscriptwrapper.nim +++ b/src/nimblepkg/nimscriptwrapper.nim @@ -36,8 +36,8 @@ proc execNimscript(nimsFile, projectDir, actionName: string, options: Options): defer: # Only if copied in this invocation, allows recursive calls of nimble - if not isScriptResultCopied: - nimsFileCopied.removeFile() + if not isScriptResultCopied and options.shouldRemoveTmp(nimsFileCopied): + nimsFileCopied.removeFile() var cmd = ("nim e --hints:off --verbosity:0 -p:" & (getTempDir() / "nimblecache").quoteShell & @@ -56,7 +56,8 @@ proc execNimscript(nimsFile, projectDir, actionName: string, options: Options): result.exitCode = execCmd(cmd) if outFile.fileExists(): result.output = outFile.readFile() - discard outFile.tryRemoveFile() + if options.shouldRemoveTmp(outFile): + discard outFile.tryRemoveFile() proc getNimsFile(scriptName: string, options: Options): string = let diff --git a/src/nimblepkg/options.nim b/src/nimblepkg/options.nim index 3482f34..80a649d 100644 --- a/src/nimblepkg/options.nim +++ b/src/nimblepkg/options.nim @@ -425,3 +425,10 @@ proc briefClone*(options: Options): Options = newOptions.forcePrompts = options.forcePrompts newOptions.pkgInfoCache = options.pkgInfoCache return newOptions + +proc shouldRemoveTmp*(options: Options, file: string): bool = + result = true + if options.verbosity <= DebugPriority: + let msg = "Not removing temporary path because of debug verbosity: " & file + display("Warning:", msg, Warning, MediumPriority) + return false \ No newline at end of file From c834faf60e1dbdd8ae46456e1fb2dc2db05e4e91 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sat, 21 Sep 2019 23:34:02 +0100 Subject: [PATCH 62/95] Implements `nimble run`. Fixes #614. --- src/nimble.nim | 50 +++++++++-- src/nimblepkg/options.nim | 180 +++++++++++++++++++++++++++----------- tests/run/run.nimble | 14 +++ tests/run/src/run.nim | 4 + tests/tester.nim | 27 +++++- 5 files changed, 216 insertions(+), 59 deletions(-) create mode 100644 tests/run/run.nimble create mode 100644 tests/run/src/run.nim diff --git a/src/nimble.nim b/src/nimble.nim index 666a60f..7eb5ee4 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -4,6 +4,7 @@ import system except TResult import os, tables, strtabs, json, algorithm, sets, uri, sugar, sequtils +import std/options as std_opt import strutils except toLower from unicode import toLower @@ -213,7 +214,10 @@ proc processDeps(pkginfo: PackageInfo, options: Options): seq[PackageInfo] = for i in reverseDeps: addRevDep(options.nimbleData, i, pkginfo) -proc buildFromDir(pkgInfo: PackageInfo, paths, args: seq[string]) = +proc buildFromDir( + pkgInfo: PackageInfo, paths, args: seq[string], + binToBuild: Option[string] = none[string]() +) = ## Builds a package as specified by ``pkgInfo``. if pkgInfo.bin.len == 0: raise newException(NimbleError, @@ -223,6 +227,12 @@ proc buildFromDir(pkgInfo: PackageInfo, paths, args: seq[string]) = let realDir = pkgInfo.getRealDir() for path in paths: args.add("--path:\"" & path & "\" ") for bin in pkgInfo.bin: + # Check if this is the only binary that we want to build. + if binToBuild.isSome() and binToBuild.get() != bin: + let binToBuild = binToBuild.get() + if bin.extractFilename().changeFileExt("") != binToBuild: + continue + let outputOpt = "-o:\"" & pkgInfo.getOutputDir(bin) & "\"" display("Building", "$1/$2 using $3 backend" % [pkginfo.name, bin, pkgInfo.backend], priority = HighPriority) @@ -502,12 +512,12 @@ proc build(options: Options) = nimScriptHint(pkgInfo) let deps = processDeps(pkginfo, options) let paths = deps.map(dep => dep.getRealDir()) - var args = options.action.compileOptions - buildFromDir(pkgInfo, paths, args) + var args = options.getCompilationFlags() + buildFromDir(pkgInfo, paths, args, options.getCompilationBinary()) proc execBackend(options: Options) = let - bin = options.action.file + bin = options.getCompilationBinary().get() binDotNim = bin.addFileExt("nim") if bin == "": raise newException(NimbleError, "You need to specify a file.") @@ -522,7 +532,7 @@ proc execBackend(options: Options) = var args = "" for dep in deps: args.add("--path:\"" & dep.getRealDir() & "\" ") - for option in options.action.compileOptions: + for option in options.getCompilationFlags(): args.add("\"" & option & "\" ") let backend = @@ -1011,9 +1021,9 @@ proc test(options: Options) = optsCopy.action = Action(typ: actionCompile) optsCopy.action.file = file.path optsCopy.action.backend = "c" - optsCopy.action.compileOptions = @[] - optsCopy.action.compileOptions.add("-r") - optsCopy.action.compileOptions.add("--path:.") + optsCopy.getCompilationFlags() = @[] + optsCopy.getCompilationFlags().add("-r") + optsCopy.getCompilationFlags().add("--path:.") let binFileName = file.path.changeFileExt(ExeExt) existsBefore = existsFile(binFileName) @@ -1058,6 +1068,28 @@ proc check(options: Options) = display("Failure:", "Validation failed", Error, HighPriority) quit(QuitFailure) +proc run(options: Options) = + # Verify parameters. + let binary = options.getCompilationBinary().get() + if binary.len == 0: + raiseNimbleError("Please specify a binary to run") + + var pkgInfo = getPkgInfo(getCurrentDir(), options) + if binary notin pkgInfo.bin: + raiseNimbleError( + "Binary '$#' is not defined in '$#' package." % [binary, pkgInfo.name] + ) + + let binaryPath = pkgInfo.getOutputDir(binary) + + # Build the binary. + build(options) + + # Now run it. + let args = options.action.runFlags.join(" ") + + doCmd("$# $#" % [binaryPath, args], showOutput = true) + proc doAction(options: Options) = if options.showHelp: writeHelp() @@ -1094,6 +1126,8 @@ proc doAction(options: Options) = listPaths(options) of actionBuild: build(options) + of actionRun: + run(options) of actionCompile, actionDoc: execBackend(options) of actionInit: diff --git a/src/nimblepkg/options.nim b/src/nimblepkg/options.nim index 80a649d..28560e7 100644 --- a/src/nimblepkg/options.nim +++ b/src/nimblepkg/options.nim @@ -2,6 +2,8 @@ # BSD License. Look at license.txt for more info. import json, strutils, os, parseopt, strtabs, uri, tables, terminal +import sequtils, sugar +import std/options as std_opt from httpclient import Proxy, newProxy import config, version, common, cli @@ -26,12 +28,15 @@ type continueTestsOnFailure*: bool ## Whether packages' repos should always be downloaded with their history. forceFullClone*: bool + # Temporary storage of flags that have not been captured by any specific Action. + unknownFlags*: seq[(CmdLineKind, string, string)] ActionType* = enum actionNil, actionRefresh, actionInit, actionDump, actionPublish, actionInstall, actionSearch, actionList, actionBuild, actionPath, actionUninstall, actionCompile, - actionDoc, actionCustom, actionTasks, actionDevelop, actionCheck + actionDoc, actionCustom, actionTasks, actionDevelop, actionCheck, + actionRun Action* = object case typ*: ActionType @@ -49,7 +54,11 @@ type of actionCompile, actionDoc, actionBuild: file*: string backend*: string - compileOptions*: seq[string] + compileOptions: seq[string] + of actionRun: + runFile: string + compileFlags: seq[string] + runFlags*: seq[string] of actionCustom: command*: string arguments*: seq[string] @@ -76,7 +85,8 @@ Commands: toplevel directory of the Nimble package. uninstall [pkgname, ...] Uninstalls a list of packages. [-i, --inclDeps] Uninstall package and dependent package(s). - build Builds a package. + build [opts, ...] Builds a package. + run [opts, ...] bin Builds and runs a package. c, cc, js [opts, ...] f.nim Builds a file inside a package. Passes options to the Nim compiler. test Compiles and executes tests @@ -143,6 +153,8 @@ proc parseActionType*(action: string): ActionType = result = actionPath of "build": result = actionBuild + of "run": + result = actionRun of "c", "compile", "js", "cpp", "cc": result = actionCompile of "doc", "doc2": @@ -196,7 +208,7 @@ proc initAction*(options: var Options, key: string) = options.action.command = key options.action.arguments = @[] options.action.flags = newStringTable() - of actionPublish, actionList, actionTasks, actionCheck, + of actionPublish, actionList, actionTasks, actionCheck, actionRun, actionNil: discard proc prompt*(options: Options, question: string): bool = @@ -271,18 +283,37 @@ proc parseArgument*(key: string, result: var Options) = result.action.projName = key of actionCompile, actionDoc: result.action.file = key - of actionList, actionBuild, actionPublish: + of actionList, actionPublish: result.showHelp = true + of actionBuild: + result.action.file = key + of actionRun: + if result.action.runFile.len == 0: + result.action.runFile = key + else: + result.action.runFlags.add(key) of actionCustom: result.action.arguments.add(key) else: discard +proc getFlagString(kind: CmdLineKind, flag, val: string): string = + let prefix = + case kind + of cmdShortOption: "-" + of cmdLongOption: "--" + else: "" + if val == "": + return prefix & flag + else: + return prefix & flag & ":" & val + proc parseFlag*(flag, val: string, result: var Options, kind = cmdLongOption) = - var wasFlagHandled = true + let f = flag.normalize() # Global flags. + var isGlobalFlag = true case f of "help", "h": result.showHelp = true of "version", "v": result.showVersion = true @@ -293,54 +324,56 @@ proc parseFlag*(flag, val: string, result: var Options, kind = cmdLongOption) = of "debug": result.verbosity = DebugPriority of "nocolor": result.noColor = true of "disablevalidation": result.disableValidation = true + else: isGlobalFlag = false + + var wasFlagHandled = true # Action-specific flags. - else: - case result.action.typ - of actionSearch, actionList: - case f - of "installed", "i": - result.queryInstalled = true - of "ver": - result.queryVersions = true - else: - wasFlagHandled = false - of actionInstall: - case f - of "depsonly", "d": - result.depsOnly = true - of "passnim", "p": - result.action.passNimFlags.add(val) - else: - wasFlagHandled = false - of actionUninstall: - case f - of "incldeps", "i": - result.uninstallRevDeps = true - else: - wasFlagHandled = false - of actionCompile, actionDoc, actionBuild: - let prefix = if kind == cmdShortOption: "-" else: "--" - if val == "": - result.action.compileOptions.add(prefix & flag) - else: - result.action.compileOptions.add(prefix & flag & ":" & val) - of actionCustom: - if result.action.command.normalize == "test": - if f == "continue" or f == "c": - result.continueTestsOnFailure = true - result.action.flags[flag] = val + case result.action.typ + of actionSearch, actionList: + case f + of "installed", "i": + result.queryInstalled = true + of "ver": + result.queryVersions = true else: wasFlagHandled = false + of actionInstall: + case f + of "depsonly", "d": + result.depsOnly = true + of "passnim", "p": + result.action.passNimFlags.add(val) + else: + wasFlagHandled = false + of actionUninstall: + case f + of "incldeps", "i": + result.uninstallRevDeps = true + else: + wasFlagHandled = false + of actionCompile, actionDoc, actionBuild: + if not isGlobalFlag: + result.action.compileOptions.add(getFlagString(kind, flag, val)) + of actionRun: + result.action.runFlags.add(getFlagString(kind, flag, val)) + of actionCustom: + if result.action.command.normalize == "test": + if f == "continue" or f == "c": + result.continueTestsOnFailure = true + result.action.flags[flag] = val + else: + wasFlagHandled = false - if not wasFlagHandled: - raise newException(NimbleError, "Unknown option: --" & flag) + if not wasFlagHandled and not isGlobalFlag: + result.unknownFlags.add((kind, flag, val)) -proc initOptions*(): Options = - result.action = Action(typ: actionNil) - result.pkgInfoCache = newTable[string, PackageInfo]() - result.nimbleDir = "" - result.verbosity = HighPriority - result.noColor = not isatty(stdout) +proc initOptions(): Options = + Options( + action: Action(typ: actionNil), + pkgInfoCache: newTable[string, PackageInfo](), + verbosity: HighPriority, + noColor: not isatty(stdout) + ) proc parseMisc(options: var Options) = # Load nimbledata.json @@ -355,6 +388,20 @@ proc parseMisc(options: var Options) = else: options.nimbleData = %{"reverseDeps": newJObject()} +proc handleUnknownFlags(options: var Options) = + if options.action.typ == actionRun: + options.action.compileFlags = + map(options.unknownFlags, x => getFlagString(x[0], x[1], x[2])) + options.unknownFlags = @[] + + # Any unhandled flags? + if options.unknownFlags.len > 0: + let flag = options.unknownFlags[0] + raise newException( + NimbleError, + "Unknown option: " & getFlagString(flag[0], flag[1], flag[2]) + ) + proc parseCmdLine*(): Options = result = initOptions() @@ -371,6 +418,8 @@ proc parseCmdLine*(): Options = parseFlag(key, val, result, kind) of cmdEnd: assert(false) # cannot happen + handleUnknownFlags(result) + # Set verbosity level. setVerbosity(result.verbosity) @@ -386,6 +435,11 @@ proc parseCmdLine*(): Options = if result.action.typ == actionNil and not result.showVersion: result.showHelp = true + if result.action.typ != actionNil and result.showVersion: + # We've got another command that should be handled. For example: + # nimble run foobar -v + result.showVersion = false + proc getProxy*(options: Options): Proxy = ## Returns ``nil`` if no proxy is specified. var url = "" @@ -431,4 +485,30 @@ proc shouldRemoveTmp*(options: Options, file: string): bool = if options.verbosity <= DebugPriority: let msg = "Not removing temporary path because of debug verbosity: " & file display("Warning:", msg, Warning, MediumPriority) - return false \ No newline at end of file + return false + +proc getCompilationFlags*(options: var Options): var seq[string] = + case options.action.typ + of actionBuild, actionDoc, actionCompile: + return options.action.compileOptions + of actionRun: + return options.action.compileFlags + else: + assert false + +proc getCompilationFlags*(options: Options): seq[string] = + var opt = options + return opt.getCompilationFlags() + +proc getCompilationBinary*(options: Options): Option[string] = + case options.action.typ + of actionBuild, actionDoc, actionCompile: + let file = options.action.file.changeFileExt("") + if file.len > 0: + return some(file) + of actionRun: + let runFile = options.action.runFile.changeFileExt("") + if runFile.len > 0: + return some(runFile) + else: + discard \ No newline at end of file diff --git a/tests/run/run.nimble b/tests/run/run.nimble new file mode 100644 index 0000000..4b09bdd --- /dev/null +++ b/tests/run/run.nimble @@ -0,0 +1,14 @@ +# Package + +version = "0.1.0" +author = "Dominik Picheta" +description = "A new awesome nimble package" +license = "MIT" +srcDir = "src" +bin = @["run"] + + + +# Dependencies + +requires "nim >= 0.20.0" diff --git a/tests/run/src/run.nim b/tests/run/src/run.nim new file mode 100644 index 0000000..af98995 --- /dev/null +++ b/tests/run/src/run.nim @@ -0,0 +1,4 @@ +import os + +when isMainModule: + echo("Testing `nimble run`: ", commandLineParams()) diff --git a/tests/tester.nim b/tests/tester.nim index bc9722e..c70c5b7 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -33,8 +33,8 @@ template cd*(dir: string, body: untyped) = proc execNimble(args: varargs[string]): tuple[output: string, exitCode: int] = var quotedArgs = @args + quotedArgs.insert("--nimbleDir:" & installDir) quotedArgs.insert(nimblePath) - quotedArgs.add("--nimbleDir:" & installDir) quotedArgs = quotedArgs.map((x: string) => ("\"" & x & "\"")) let path {.used.} = getCurrentDir().parentDir() / "src" @@ -49,6 +49,7 @@ proc execNimble(args: varargs[string]): tuple[output: string, exitCode: int] = cmd = "DYLD_LIBRARY_PATH=/usr/local/opt/openssl@1.1/lib " & cmd result = execCmdEx(cmd) + checkpoint(cmd) checkpoint(result.output) proc execNimbleYes(args: varargs[string]): tuple[output: string, exitCode: int]= @@ -882,6 +883,30 @@ test "Passing command line arguments to a task (#633)": check exitCode == QuitSuccess check output.contains("Got it") +suite "nimble run": + test "Invalid binary": + cd "run": + var (output, exitCode) = execNimble( + "--debug", # Flag to enable debug verbosity in Nimble + "run", # Run command invokation + "blahblah", # The command to run + ) + check exitCode == QuitFailure + check output.contains("Binary 'blahblah' is not defined in 'run' package.") + + test "Parameters passed to executable": + cd "run": + var (output, exitCode) = execNimble( + "--debug", # Flag to enable debug verbosity in Nimble + "run", # Run command invokation + "run", # The command to run + "--debug", # First argument passed to the executed command + "check" # Second argument passed to the executed command. + ) + check exitCode == QuitSuccess + check output.contains("tests/run/run --debug check") + check output.contains("""Testing `nimble run`: @["--debug", "check"]""") + test "compilation without warnings": const buildDir = "./buildDir/" const filesToBuild = [ From 55dd892aab3caeba73709f4ac45e93411a5d3846 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 22 Sep 2019 00:11:28 +0100 Subject: [PATCH 63/95] Fixes #631. Test command now respects backend. --- src/nimble.nim | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/nimble.nim b/src/nimble.nim index 7eb5ee4..402601a 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -1004,6 +1004,8 @@ proc develop(options: Options) = proc test(options: Options) = ## Executes all tests starting with 't' in the ``tests`` directory. ## Subdirectories are not walked. + var pkgInfo = getPkgInfo(getCurrentDir(), options) + var files = toSeq(walkDir(getCurrentDir() / "tests")) tests, failures: int @@ -1020,7 +1022,7 @@ proc test(options: Options) = var optsCopy = options.briefClone() optsCopy.action = Action(typ: actionCompile) optsCopy.action.file = file.path - optsCopy.action.backend = "c" + optsCopy.action.backend = pkgInfo.backend optsCopy.getCompilationFlags() = @[] optsCopy.getCompilationFlags().add("-r") optsCopy.getCompilationFlags().add("--path:.") From 28ff2e04a7f76c4ed0c305a7138f67a10b00ea4d Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 22 Sep 2019 00:15:37 +0100 Subject: [PATCH 64/95] Fixes tests on 0.19.6. --- tests/run/run.nimble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/run/run.nimble b/tests/run/run.nimble index 4b09bdd..055bc1e 100644 --- a/tests/run/run.nimble +++ b/tests/run/run.nimble @@ -11,4 +11,4 @@ bin = @["run"] # Dependencies -requires "nim >= 0.20.0" +requires "nim >= 0.19.0" From 4a2aaa07dc6b52247bc0b7d40fac30a1f7eabac0 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 22 Sep 2019 00:53:52 +0100 Subject: [PATCH 65/95] Implements NimblePkgVersion define. Fixes #625. --- src/nimble.nim | 10 ++++++---- .../nimbleVersionDefine.nimble | 14 ++++++++++++++ .../nimbleVersionDefine/src/nimbleVersionDefine | Bin 0 -> 86500 bytes .../src/nimbleVersionDefine.nim | 3 +++ tests/tester.nim | 10 ++++++++++ 5 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 tests/nimbleVersionDefine/nimbleVersionDefine.nimble create mode 100755 tests/nimbleVersionDefine/src/nimbleVersionDefine create mode 100644 tests/nimbleVersionDefine/src/nimbleVersionDefine.nim diff --git a/src/nimble.nim b/src/nimble.nim index 402601a..5cf9652 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -225,6 +225,7 @@ proc buildFromDir( " `bin` key in your .nimble file?") var args = args let realDir = pkgInfo.getRealDir() + let nimblePkgVersion = "-d:NimblePkgVersion=" & pkgInfo.version for path in paths: args.add("--path:\"" & path & "\" ") for bin in pkgInfo.bin: # Check if this is the only binary that we want to build. @@ -242,8 +243,8 @@ proc buildFromDir( createDir(outputDir) try: - doCmd("\"" & getNimBin() & "\" $# --noBabelPath $# $# \"$#\"" % - [pkgInfo.backend, join(args, " "), outputOpt, + doCmd("\"" & getNimBin() & "\" $# --noNimblePath $# $# $# \"$#\"" % + [pkgInfo.backend, nimblePkgVersion, join(args, " "), outputOpt, realDir / bin.changeFileExt("nim")]) except NimbleError: let currentExc = (ref NimbleError)(getCurrentException()) @@ -530,6 +531,7 @@ proc execBackend(options: Options) = nimScriptHint(pkgInfo) let deps = processDeps(pkginfo, options) + let nimblePkgVersion = "-d:NimblePkgVersion=" & pkgInfo.version var args = "" for dep in deps: args.add("--path:\"" & dep.getRealDir() & "\" ") for option in options.getCompilationFlags(): @@ -547,8 +549,8 @@ proc execBackend(options: Options) = else: display("Generating", ("documentation for $1 (from package $2) using $3 " & "backend") % [bin, pkgInfo.name, backend], priority = HighPriority) - doCmd("\"" & getNimBin() & "\" $# --noNimblePath $# \"$#\"" % - [backend, args, bin], showOutput = true) + doCmd("\"" & getNimBin() & "\" $# --noNimblePath $# $# \"$#\"" % + [backend, nimblePkgVersion, args, bin], showOutput = true) display("Success:", "Execution finished", Success, HighPriority) proc search(options: Options) = diff --git a/tests/nimbleVersionDefine/nimbleVersionDefine.nimble b/tests/nimbleVersionDefine/nimbleVersionDefine.nimble new file mode 100644 index 0000000..b47b049 --- /dev/null +++ b/tests/nimbleVersionDefine/nimbleVersionDefine.nimble @@ -0,0 +1,14 @@ +# Package + +version = "0.1.0" +author = "Dominik Picheta" +description = "A new awesome nimble package" +license = "MIT" +srcDir = "src" +bin = @["nimbleVersionDefine"] + + + +# Dependencies + +requires "nim >= 0.16.0" diff --git a/tests/nimbleVersionDefine/src/nimbleVersionDefine b/tests/nimbleVersionDefine/src/nimbleVersionDefine new file mode 100755 index 0000000000000000000000000000000000000000..3b089c01cfe8bed84e2cacb812590f1b97d73f10 GIT binary patch literal 86500 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 PpazRKkMYRB4(R+}iPK7{ literal 0 HcmV?d00001 diff --git a/tests/nimbleVersionDefine/src/nimbleVersionDefine.nim b/tests/nimbleVersionDefine/src/nimbleVersionDefine.nim new file mode 100644 index 0000000..572dab8 --- /dev/null +++ b/tests/nimbleVersionDefine/src/nimbleVersionDefine.nim @@ -0,0 +1,3 @@ +when isMainModule: + const NimblePkgVersion {.strdefine.} = "Unknown" + echo(NimblePkgVersion) diff --git a/tests/tester.nim b/tests/tester.nim index c70c5b7..19c52cf 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -907,6 +907,16 @@ suite "nimble run": check output.contains("tests/run/run --debug check") check output.contains("""Testing `nimble run`: @["--debug", "check"]""") +test "NimbleVersion is defined": + cd "nimbleVersionDefine": + var (output, exitCode) = execNimble("c", "-r", "src/nimbleVersionDefine.nim") + check output.contains("0.1.0") + check exitCode == QuitSuccess + + var (output2, exitCode2) = execNimble("run", "nimbleVersionDefine") + check output2.contains("0.1.0") + check exitCode2 == QuitSuccess + test "compilation without warnings": const buildDir = "./buildDir/" const filesToBuild = [ From 137bb1ed075708da3807c9e4b89f5c6e8d103210 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 22 Sep 2019 10:45:26 +0100 Subject: [PATCH 66/95] Fixes #606. Build before/after hook executed every time package is built. --- src/nimble.nim | 30 +++++++++++++++++++---------- src/nimblepkg/nimscriptexecutor.nim | 10 +++++----- tests/nimscript/nimscript.nimble | 6 ++++++ tests/tester.nim | 9 +++++++++ 4 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/nimble.nim b/src/nimble.nim index 5cf9652..2185211 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -216,15 +216,21 @@ proc processDeps(pkginfo: PackageInfo, options: Options): seq[PackageInfo] = proc buildFromDir( pkgInfo: PackageInfo, paths, args: seq[string], - binToBuild: Option[string] = none[string]() + options: Options ) = ## Builds a package as specified by ``pkgInfo``. + let binToBuild = options.getCompilationBinary() + # Handle pre-`build` hook. + let realDir = pkgInfo.getRealDir() + cd realDir: # Make sure `execHook` executes the correct .nimble file. + if not execHook(options, actionBuild, true): + raise newException(NimbleError, "Pre-hook prevented further execution.") + if pkgInfo.bin.len == 0: raise newException(NimbleError, "Nothing to build. Did you specify a module to build using the" & " `bin` key in your .nimble file?") var args = args - let realDir = pkgInfo.getRealDir() let nimblePkgVersion = "-d:NimblePkgVersion=" & pkgInfo.version for path in paths: args.add("--path:\"" & path & "\" ") for bin in pkgInfo.bin: @@ -255,6 +261,10 @@ proc buildFromDir( exc.hint = hint raise exc + # Handle post-`build` hook. + cd realDir: # Make sure `execHook` executes the correct .nimble file. + discard execHook(options, actionBuild, false) + proc removePkgDir(dir: string, options: Options) = ## Removes files belonging to the package in ``dir``. try: @@ -332,7 +342,7 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, # Handle pre-`install` hook. if not options.depsOnly: cd dir: # Make sure `execHook` executes the correct .nimble file. - if not execHook(options, true): + if not execHook(options, actionInstall, true): raise newException(NimbleError, "Pre-hook prevented further execution.") var pkgInfo = getPkgInfo(dir, options) @@ -363,7 +373,7 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, options.action.passNimFlags else: @[] - buildFromDir(pkgInfo, paths, flags & "-d:release") + buildFromDir(pkgInfo, paths, flags & "-d:release", options) let pkgDestDir = pkgInfo.getPkgDest(options) if existsDir(pkgDestDir) and existsFile(pkgDestDir / "nimblemeta.json"): @@ -446,7 +456,7 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, # executes the hook defined in the CWD, so we set it to where the package # has been installed. cd dest.splitFile.dir: - discard execHook(options, false) + discard execHook(options, actionInstall, false) proc getDownloadInfo*(pv: PkgTuple, options: Options, doPrompt: bool): (DownloadMethod, string, @@ -514,7 +524,7 @@ proc build(options: Options) = let deps = processDeps(pkginfo, options) let paths = deps.map(dep => dep.getRealDir()) var args = options.getCompilationFlags() - buildFromDir(pkgInfo, paths, args, options.getCompilationBinary()) + buildFromDir(pkgInfo, paths, args, options) proc execBackend(options: Options) = let @@ -917,7 +927,7 @@ proc developFromDir(dir: string, options: Options) = raiseNimbleError("Cannot develop dependencies only.") cd dir: # Make sure `execHook` executes the correct .nimble file. - if not execHook(options, true): + if not execHook(options, actionDevelop, true): raise newException(NimbleError, "Pre-hook prevented further execution.") var pkgInfo = getPkgInfo(dir, options) @@ -970,7 +980,7 @@ proc developFromDir(dir: string, options: Options) = # Execute the post-develop hook. cd dir: - discard execHook(options, false) + discard execHook(options, actionDevelop, false) proc develop(options: Options) = if options.action.packages == @[]: @@ -1150,7 +1160,7 @@ proc doAction(options: Options) = of actionNil: assert false of actionCustom: - if not execHook(options, true): + if not execHook(options, actionCustom, true): display("Warning", "Pre-hook prevented further execution.", Warning, HighPriority) return @@ -1166,7 +1176,7 @@ proc doAction(options: Options) = if isPreDefined: test(options) # Run the post hook for `test` in case it exists. - discard execHook(options, false) + discard execHook(options, actionCustom, false) when isMainModule: var error = "" diff --git a/src/nimblepkg/nimscriptexecutor.nim b/src/nimblepkg/nimscriptexecutor.nim index bf8afd1..122c41c 100644 --- a/src/nimblepkg/nimscriptexecutor.nim +++ b/src/nimblepkg/nimscriptexecutor.nim @@ -6,12 +6,12 @@ import os, strutils, sets import packageparser, common, packageinfo, options, nimscriptwrapper, cli, version -proc execHook*(options: Options, before: bool): bool = +proc execHook*(options: Options, hookAction: ActionType, before: bool): bool = ## Returns whether to continue. result = true # For certain commands hooks should not be evaluated. - if options.action.typ in noHookActions: + if hookAction in noHookActions: return var nimbleFile = "" @@ -21,8 +21,8 @@ proc execHook*(options: Options, before: bool): bool = # PackageInfos are cached so we can read them as many times as we want. let pkgInfo = getPkgInfoFromFile(nimbleFile, options) let actionName = - if options.action.typ == actionCustom: options.action.command - else: ($options.action.typ)[6 .. ^1] + if hookAction == actionCustom: options.action.command + else: ($hookAction)[6 .. ^1] let hookExists = if before: actionName.normalize in pkgInfo.preHooks else: actionName.normalize in pkgInfo.postHooks @@ -58,7 +58,7 @@ proc execCustom*(options: Options, HighPriority) return - if not execHook(options, false): + if not execHook(options, actionCustom, false): return return true diff --git a/tests/nimscript/nimscript.nimble b/tests/nimscript/nimscript.nimble index f27631a..39f3710 100644 --- a/tests/nimscript/nimscript.nimble +++ b/tests/nimscript/nimscript.nimble @@ -54,3 +54,9 @@ before install: after install: echo("After PkgDir: ", getPkgDir()) + +before build: + echo("Before build") + +after build: + echo("After build") \ No newline at end of file diff --git a/tests/tester.nim b/tests/tester.nim index 19c52cf..21c7011 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -286,12 +286,21 @@ suite "nimscript": cd "nimscript": let (output, exitCode) = execNimble(["install", "-y"]) check exitCode == QuitSuccess + check output.contains("Before build") + check output.contains("After build") let lines = output.strip.processOutput() check lines[0].startsWith("Before PkgDir:") check lines[0].endsWith("tests" / "nimscript") check lines[^1].startsWith("After PkgDir:") check lines[^1].endsWith("tests" / "nimbleDir" / "pkgs" / "nimscript-0.1.0") + test "before/after on build": + cd "nimscript": + let (output, exitCode) = execNimble(["build"]) + check exitCode == QuitSuccess + check output.contains("Before build") + check output.contains("After build") + test "can execute nimscript tasks": cd "nimscript": let (output, exitCode) = execNimble("--verbose", "work") From 419eba036b439ca4a07c201c1969ddbe475719ac Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 22 Sep 2019 11:15:00 +0100 Subject: [PATCH 67/95] Improve error message when there are no packages to uninstall. --- src/nimble.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nimble.nim b/src/nimble.nim index 2185211..6b879ac 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -892,7 +892,7 @@ proc uninstall(options: Options) = pkgsToDelete.incl pkg if pkgsToDelete.len == 0: - raise newException(NimbleError, "Failed uninstall - no packages selected") + raise newException(NimbleError, "Failed uninstall - no packages to delete") var pkgNames = "" for pkg in pkgsToDelete.items: From da3f70cb98ef60598583eb889aeadfd63f96c135 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 22 Sep 2019 11:46:29 +0100 Subject: [PATCH 68/95] Fixes #713 and adds test for --depsOnly. --- src/nimblepkg/options.nim | 9 +++++++++ tests/tester.nim | 7 +++++++ 2 files changed, 16 insertions(+) diff --git a/src/nimblepkg/options.nim b/src/nimblepkg/options.nim index 28560e7..72f4a3a 100644 --- a/src/nimblepkg/options.nim +++ b/src/nimblepkg/options.nim @@ -390,9 +390,18 @@ proc parseMisc(options: var Options) = proc handleUnknownFlags(options: var Options) = if options.action.typ == actionRun: + # ActionRun uses flags that come before the command as compilation flags + # and flags that come after as run flags. options.action.compileFlags = map(options.unknownFlags, x => getFlagString(x[0], x[1], x[2])) options.unknownFlags = @[] + else: + # For everything else, handle the flags that came before the command + # normally. + let unknownFlags = options.unknownFlags + options.unknownFlags = @[] + for flag in unknownFlags: + parseFlag(flag[1], flag[2], options, flag[0]) # Any unhandled flags? if options.unknownFlags.len > 0: diff --git a/tests/tester.nim b/tests/tester.nim index 21c7011..efa0107 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -79,6 +79,13 @@ proc hasLineStartingWith(lines: seq[string], prefix: string): bool = return true return false +test "depsOnly + flag order test": + var (output, exitCode) = execNimble( + "--depsOnly", "install", "-y", "https://github.com/nimble-test/packagebin2" + ) + check(not output.contains("Success: packagebin2 installed successfully.")) + check exitCode == QuitSuccess + test "caching of nims and ini detects changes": cd "caching": var (output, exitCode) = execNimble("dump") From 5ea2ac34fefa9339945db4c473b3f7dd6ba77e0c Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 22 Sep 2019 11:53:03 +0100 Subject: [PATCH 69/95] Update CI to test against latest Nim HEAD commit. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8cfa912..3f824f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ env: - BRANCH=0.19.6 - BRANCH=0.20.2 # This is the latest working Nim version against which Nimble is being tested - - BRANCH=#212ae2f1257628bd5d1760593ce0a1bad768831a + - BRANCH=#2565d3d102efd21ba02ed1f3b96d892fe2637d2b cache: directories: From c85cdfd8144461757e4d83e0df4a1ae88e16cd12 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 22 Sep 2019 12:00:15 +0100 Subject: [PATCH 70/95] Fixes #706. No more unused import warnings. --- src/nimblepkg/nimscriptwrapper.nim | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/nimblepkg/nimscriptwrapper.nim b/src/nimblepkg/nimscriptwrapper.nim index d7b7a3b..23c23c9 100644 --- a/src/nimblepkg/nimscriptwrapper.nim +++ b/src/nimblepkg/nimscriptwrapper.nim @@ -39,9 +39,15 @@ proc execNimscript(nimsFile, projectDir, actionName: string, options: Options): if not isScriptResultCopied and options.shouldRemoveTmp(nimsFileCopied): nimsFileCopied.removeFile() - var - cmd = ("nim e --hints:off --verbosity:0 -p:" & (getTempDir() / "nimblecache").quoteShell & - " " & nimsFileCopied.quoteShell & " " & outFile.quoteShell & " " & actionName).strip() + var cmd = ( + "nim e $# -p:$# $# $# $#" % [ + "--hints:off --warning[UnusedImport]:off --verbosity:0", + (getTempDir() / "nimblecache").quoteShell, + nimsFileCopied.quoteShell, + outFile.quoteShell, + actionName + ] + ).strip() if options.action.typ == actionCustom and actionName != "printPkgInfo": for i in options.action.arguments: From 46dc98bb62d8250af9f13f2f3628d67a462f50d3 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sat, 21 Sep 2019 23:52:28 +0100 Subject: [PATCH 71/95] Fixes #710. --- src/nimblepkg/nimscriptwrapper.nim | 61 ++++++++++++++-------- tests/invalidPackage/invalidPackage.nimble | 13 +++++ tests/tester.nim | 7 +++ 3 files changed, 60 insertions(+), 21 deletions(-) create mode 100644 tests/invalidPackage/invalidPackage.nimble diff --git a/src/nimblepkg/nimscriptwrapper.nim b/src/nimblepkg/nimscriptwrapper.nim index 23c23c9..baad4e7 100644 --- a/src/nimblepkg/nimscriptwrapper.nim +++ b/src/nimblepkg/nimscriptwrapper.nim @@ -4,9 +4,10 @@ ## Implements the new configuration system for Nimble. Uses Nim as a ## scripting language. -import version, options, cli, tools import hashes, json, os, strutils, tables, times, osproc, strtabs +import version, options, cli, tools + type Flags = TableRef[string, seq[string]] ExecutionResult*[T] = object @@ -15,13 +16,23 @@ type arguments*: seq[string] flags*: Flags retVal*: T + stdout*: string const internalCmd = "e" nimscriptApi = staticRead("nimscriptapi.nim") + printPkgInfo = "printPkgInfo" -proc execNimscript(nimsFile, projectDir, actionName: string, options: Options): - tuple[output: string, exitCode: int] = +proc isCustomTask(actionName: string, options: Options): bool = + options.action.typ == actionCustom and actionName != printPkgInfo + +proc needsLiveOutput(actionName: string, options: Options, isHook: bool): bool = + let isCustomTask = isCustomTask(actionName, options) + return isCustomTask or isHook or actionName == "" + +proc execNimscript( + nimsFile, projectDir, actionName: string, options: Options, isHook: bool +): tuple[output: string, exitCode: int, stdout: string] = let nimsFileCopied = projectDir / nimsFile.splitFile().name & "_" & getProcessId() & ".nims" outFile = getNimbleTempDir() & ".out" @@ -41,7 +52,7 @@ proc execNimscript(nimsFile, projectDir, actionName: string, options: Options): var cmd = ( "nim e $# -p:$# $# $# $#" % [ - "--hints:off --warning[UnusedImport]:off --verbosity:0", + "--hints:off --verbosity:0", (getTempDir() / "nimblecache").quoteShell, nimsFileCopied.quoteShell, outFile.quoteShell, @@ -49,7 +60,8 @@ proc execNimscript(nimsFile, projectDir, actionName: string, options: Options): ] ).strip() - if options.action.typ == actionCustom and actionName != "printPkgInfo": + let isCustomTask = isCustomTask(actionName, options) + if isCustomTask: for i in options.action.arguments: cmd &= " " & i.quoteShell() for key, val in options.action.flags.pairs(): @@ -59,7 +71,12 @@ proc execNimscript(nimsFile, projectDir, actionName: string, options: Options): displayDebug("Executing " & cmd) - result.exitCode = execCmd(cmd) + if needsLiveOutput(actionName, options, isHook): + result.exitCode = execCmd(cmd) + else: + # We want to capture any possible errors when parsing a .nimble + # file's metadata. See #710. + (result.stdout, result.exitCode) = execCmdEx(cmd) if outFile.fileExists(): result.output = outFile.readFile() if options.shouldRemoveTmp(outFile): @@ -112,27 +129,29 @@ proc getIniFile*(scriptName: string, options: Options): string = scriptName.getLastModificationTime() if not isIniResultCached: - let - (output, exitCode) = - execNimscript(nimsFile, scriptName.parentDir(), "printPkgInfo", options) + let (output, exitCode, stdout) = execNimscript( + nimsFile, scriptName.parentDir(), printPkgInfo, options, isHook=false + ) if exitCode == 0 and output.len != 0: result.writeFile(output) else: - raise newException(NimbleError, output & "\nprintPkgInfo() failed") + raise newException(NimbleError, stdout & "\nprintPkgInfo() failed") -proc execScript(scriptName, actionName: string, options: Options): - ExecutionResult[bool] = - let - nimsFile = getNimsFile(scriptName, options) +proc execScript( + scriptName, actionName: string, options: Options, isHook: bool +): ExecutionResult[bool] = + let nimsFile = getNimsFile(scriptName, options) - let - (output, exitCode) = execNimscript(nimsFile, scriptName.parentDir(), actionName, options) + let (output, exitCode, stdout) = + execNimscript( + nimsFile, scriptName.parentDir(), actionName, options, isHook + ) if exitCode != 0: let errMsg = - if output.len != 0: - output + if stdout.len != 0: + stdout else: "Exception raised during nimble script execution" raise newException(NimbleError, errMsg) @@ -164,7 +183,7 @@ proc execTask*(scriptName, taskName: string, display("Executing", "task $# in $#" % [taskName, scriptName], priority = HighPriority) - result = execScript(scriptName, taskName, options) + result = execScript(scriptName, taskName, options, isHook=false) proc execHook*(scriptName, actionName: string, before: bool, options: Options): ExecutionResult[bool] = @@ -178,11 +197,11 @@ proc execHook*(scriptName, actionName: string, before: bool, display("Attempting", "to execute hook $# in $#" % [hookName, scriptName], priority = MediumPriority) - result = execScript(scriptName, hookName, options) + result = execScript(scriptName, hookName, options, isHook=true) proc hasTaskRequestedCommand*(execResult: ExecutionResult): bool = ## Determines whether the last executed task used ``setCommand`` return execResult.command != internalCmd proc listTasks*(scriptName: string, options: Options) = - discard execScript(scriptName, "", options) + discard execScript(scriptName, "", options, isHook=false) diff --git a/tests/invalidPackage/invalidPackage.nimble b/tests/invalidPackage/invalidPackage.nimble new file mode 100644 index 0000000..08fcfcb --- /dev/null +++ b/tests/invalidPackage/invalidPackage.nimble @@ -0,0 +1,13 @@ +# Package + +version = "0.1.0" +author = "Dominik Picheta" +description = "A new awesome nimble package" +license = "MIT" +srcDir = "src" + +thisFieldDoesNotExist = "hello" + +# Dependencies + +requires "nim >= 0.20.0" diff --git a/tests/tester.nim b/tests/tester.nim index efa0107..32d16a0 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -86,6 +86,13 @@ test "depsOnly + flag order test": check(not output.contains("Success: packagebin2 installed successfully.")) check exitCode == QuitSuccess +test "nimscript evaluation error message": + cd "invalidPackage": + var (output, exitCode) = execNimble("check") + let lines = output.strip.processOutput() + check(lines[^2].endsWith("Error: undeclared identifier: 'thisFieldDoesNotExist'")) + check exitCode == QuitFailure + test "caching of nims and ini detects changes": cd "caching": var (output, exitCode) = execNimble("dump") From a703de5dbd94e7c150f002edbe866576c4165868 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 22 Sep 2019 13:55:40 +0100 Subject: [PATCH 72/95] Attempt to reproduce #564. --- tests/issue564/issue564.nimble | 14 ++++++++++++++ tests/issue564/src/issue564/issue564build.nim | 5 +++++ tests/tester.nim | 5 +++++ 3 files changed, 24 insertions(+) create mode 100644 tests/issue564/issue564.nimble create mode 100644 tests/issue564/src/issue564/issue564build.nim diff --git a/tests/issue564/issue564.nimble b/tests/issue564/issue564.nimble new file mode 100644 index 0000000..7dc0013 --- /dev/null +++ b/tests/issue564/issue564.nimble @@ -0,0 +1,14 @@ +# Package + +version = "0.1.0" +author = "Dominik Picheta" +description = "A new awesome nimble package" +license = "MIT" +srcDir = "src" +bin = @["issue564/issue564build"] + + + +# Dependencies + +requires "nim >= 0.16.0" diff --git a/tests/issue564/src/issue564/issue564build.nim b/tests/issue564/src/issue564/issue564build.nim new file mode 100644 index 0000000..862d40c --- /dev/null +++ b/tests/issue564/src/issue564/issue564build.nim @@ -0,0 +1,5 @@ +# This is just an example to get you started. A typical binary package +# uses this file as the main entry point of the application. + +when isMainModule: + echo("Hello, World!") diff --git a/tests/tester.nim b/tests/tester.nim index 32d16a0..5065606 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -79,6 +79,11 @@ proc hasLineStartingWith(lines: seq[string], prefix: string): bool = return true return false +test "issue 564": + cd "issue564": + var (output, exitCode) = execNimble("build") + check exitCode == QuitSuccess + test "depsOnly + flag order test": var (output, exitCode) = execNimble( "--depsOnly", "install", "-y", "https://github.com/nimble-test/packagebin2" From 281a1d129afad7acc0226cdd28d3e3d5cd104d34 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 22 Sep 2019 14:39:22 +0100 Subject: [PATCH 73/95] Fixes #708. Refs #571. Output from Nimble scripts' top-level statements is now only visible with --verbose. --- src/nimblepkg/nimscriptwrapper.nim | 9 +++++++++ tests/issue708/issue708.nimble | 17 +++++++++++++++++ tests/issue708/src/issue708.nim | 7 +++++++ tests/tester.nim | 12 +++++++++++- 4 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 tests/issue708/issue708.nimble create mode 100644 tests/issue708/src/issue708.nim diff --git a/src/nimblepkg/nimscriptwrapper.nim b/src/nimblepkg/nimscriptwrapper.nim index baad4e7..4cfe6ec 100644 --- a/src/nimblepkg/nimscriptwrapper.nim +++ b/src/nimblepkg/nimscriptwrapper.nim @@ -30,6 +30,12 @@ proc needsLiveOutput(actionName: string, options: Options, isHook: bool): bool = let isCustomTask = isCustomTask(actionName, options) return isCustomTask or isHook or actionName == "" +proc writeExecutionOutput(data: string) = + # TODO: in the future we will likely want this to be live, users will + # undoubtedly be doing loops and other crazy things in their top-level + # Nimble files. + display("Info", data) + proc execNimscript( nimsFile, projectDir, actionName: string, options: Options, isHook: bool ): tuple[output: string, exitCode: int, stdout: string] = @@ -135,6 +141,7 @@ proc getIniFile*(scriptName: string, options: Options): string = if exitCode == 0 and output.len != 0: result.writeFile(output) + stdout.writeExecutionOutput() else: raise newException(NimbleError, stdout & "\nprintPkgInfo() failed") @@ -175,6 +182,8 @@ proc execScript( result.flags[flag].add val.getStr() result.retVal = j{"retVal"}.getBool() + stdout.writeExecutionOutput() + proc execTask*(scriptName, taskName: string, options: Options): ExecutionResult[bool] = ## Executes the specified task in the specified script. diff --git a/tests/issue708/issue708.nimble b/tests/issue708/issue708.nimble new file mode 100644 index 0000000..ebb079d --- /dev/null +++ b/tests/issue708/issue708.nimble @@ -0,0 +1,17 @@ +# Package + +version = "0.1.0" +author = "Dominik Picheta" +description = "A new awesome nimble package" +license = "MIT" +srcDir = "src" + + + +# Dependencies + +requires "nim >= 0.16.0" + + +echo "hello" +echo "hello2" diff --git a/tests/issue708/src/issue708.nim b/tests/issue708/src/issue708.nim new file mode 100644 index 0000000..4b2a270 --- /dev/null +++ b/tests/issue708/src/issue708.nim @@ -0,0 +1,7 @@ +# This is just an example to get you started. A typical library package +# exports the main API in this file. Note that you cannot rename this file +# but you can remove it if you wish. + +proc add*(x, y: int): int = + ## Adds two files together. + return x + y diff --git a/tests/tester.nim b/tests/tester.nim index 5065606..c2d2a59 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -79,9 +79,19 @@ proc hasLineStartingWith(lines: seq[string], prefix: string): bool = return true return false +test "issue 708": + cd "issue708": + # TODO: We need a way to filter out compiler messages from the messages + # written by our nimble scripts. + var (output, exitCode) = execNimble("install", "-y", "--verbose") + check exitCode == QuitSuccess + let lines = output.strip.processOutput() + check(inLines(lines, "hello")) + check(inLines(lines, "hello2")) + test "issue 564": cd "issue564": - var (output, exitCode) = execNimble("build") + var (_, exitCode) = execNimble("build") check exitCode == QuitSuccess test "depsOnly + flag order test": From fe252c6ed6e05910f0ca23c1203522f5fa1e60ca Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 22 Sep 2019 16:37:47 +0100 Subject: [PATCH 74/95] Fixes #432. Fixes #672. --- src/nimble.nim | 2 +- src/nimblepkg/common.nim | 3 ++- src/nimblepkg/packageparser.nim | 9 +++++++++ tests/issue432/issue432.nimble | 15 +++++++++++++++ tests/issue432/src/issue432.nim | 7 +++++++ tests/tester.nim | 5 +++++ 6 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 tests/issue432/issue432.nimble create mode 100644 tests/issue432/src/issue432.nim diff --git a/src/nimble.nim b/src/nimble.nim index 6b879ac..79f8768 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -205,7 +205,7 @@ proc processDeps(pkginfo: PackageInfo, options: Options): seq[PackageInfo] = raise newException(NimbleError, "Cannot satisfy the dependency on $1 $2 and $1 $3" % [pkgInfo.name, pkgInfo.version, pkgsInPath[pkgInfo.name]]) - pkgsInPath[pkgInfo.name] = pkgInfo.version + pkgsInPath[pkgInfo.name] = pkgInfo.getConcreteVersion(options) # We add the reverse deps to the JSON file here because we don't want # them added if the above errorenous condition occurs diff --git a/src/nimblepkg/common.nim b/src/nimblepkg/common.nim index c40a82d..ab83175 100644 --- a/src/nimblepkg/common.nim +++ b/src/nimblepkg/common.nim @@ -22,7 +22,8 @@ when not defined(nimscript): preHooks*: HashSet[string] name*: string ## The version specified in the .nimble file.Assuming info is non-minimal, - ## it will always be a non-special version such as '0.1.4' + ## it will always be a non-special version such as '0.1.4'. + ## If in doubt, use `getConcreteVersion` instead. version*: string specialVersion*: string ## Either `myVersion` or a special version such as #head. author*: string diff --git a/src/nimblepkg/packageparser.nim b/src/nimblepkg/packageparser.nim index 59c2ca8..9458074 100644 --- a/src/nimblepkg/packageparser.nim +++ b/src/nimblepkg/packageparser.nim @@ -487,6 +487,15 @@ proc toFullInfo*(pkg: PackageInfo, options: Options): PackageInfo = else: return pkg +proc getConcreteVersion*(pkgInfo: PackageInfo, options: Options): string = + ## Returns a non-special version from the specified ``pkgInfo``. If the + ## ``pkgInfo`` is minimal it looks it up and retrieves the concrete version. + result = pkgInfo.version + if pkgInfo.isMinimal: + let pkgInfo = pkgInfo.toFullInfo(options) + result = pkgInfo.version + assert(not newVersion(result).isSpecial) + when isMainModule: validatePackageName("foo_bar") validatePackageName("f_oo_b_a_r") diff --git a/tests/issue432/issue432.nimble b/tests/issue432/issue432.nimble new file mode 100644 index 0000000..92937c5 --- /dev/null +++ b/tests/issue432/issue432.nimble @@ -0,0 +1,15 @@ +# Package + +version = "0.1.0" +author = "Dominik Picheta" +description = "A new awesome nimble package" +license = "MIT" +srcDir = "src" + + + +# Dependencies + +requires "nim >= 0.16.0" +requires "https://github.com/nimble-test/packagea#head", + "https://github.com/nimble-test/packagebin2" diff --git a/tests/issue432/src/issue432.nim b/tests/issue432/src/issue432.nim new file mode 100644 index 0000000..4b2a270 --- /dev/null +++ b/tests/issue432/src/issue432.nim @@ -0,0 +1,7 @@ +# This is just an example to get you started. A typical library package +# exports the main API in this file. Note that you cannot rename this file +# but you can remove it if you wish. + +proc add*(x, y: int): int = + ## Adds two files together. + return x + y diff --git a/tests/tester.nim b/tests/tester.nim index c2d2a59..1bf9caa 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -79,6 +79,11 @@ proc hasLineStartingWith(lines: seq[string], prefix: string): bool = return true return false +test "issue 432": + cd "issue432": + check execNimble("install", "-y", "--depsOnly").exitCode == QuitSuccess + check execNimble("install", "-y", "--depsOnly").exitCode == QuitSuccess + test "issue 708": cd "issue708": # TODO: We need a way to filter out compiler messages from the messages From 10e22fec98c2a74109e2cc52deb1875a761f9e49 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 22 Sep 2019 17:58:51 +0100 Subject: [PATCH 75/95] Move issue 432 test to the bottom of tester to fix broken uninstall test. --- tests/tester.nim | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/tester.nim b/tests/tester.nim index 1bf9caa..ceb1fc6 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -79,11 +79,6 @@ proc hasLineStartingWith(lines: seq[string], prefix: string): bool = return true return false -test "issue 432": - cd "issue432": - check execNimble("install", "-y", "--depsOnly").exitCode == QuitSuccess - check execNimble("install", "-y", "--depsOnly").exitCode == QuitSuccess - test "issue 708": cd "issue708": # TODO: We need a way to filter out compiler messages from the messages @@ -960,6 +955,11 @@ test "NimbleVersion is defined": check output2.contains("0.1.0") check exitCode2 == QuitSuccess +test "issue 432": + cd "issue432": + check execNimble("install", "-y", "--depsOnly").exitCode == QuitSuccess + check execNimble("install", "-y", "--depsOnly").exitCode == QuitSuccess + test "compilation without warnings": const buildDir = "./buildDir/" const filesToBuild = [ From 1db54cc736c25b1c1def6e969c80bd0f2356f99a Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 22 Sep 2019 18:11:28 +0100 Subject: [PATCH 76/95] Fixes some small input validation errors and help text. --- src/nimble.nim | 9 ++++++++- src/nimblepkg/options.nim | 6 +++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/nimble.nim b/src/nimble.nim index 79f8768..27e4774 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -233,6 +233,7 @@ proc buildFromDir( var args = args let nimblePkgVersion = "-d:NimblePkgVersion=" & pkgInfo.version for path in paths: args.add("--path:\"" & path & "\" ") + var binariesBuilt = 0 for bin in pkgInfo.bin: # Check if this is the only binary that we want to build. if binToBuild.isSome() and binToBuild.get() != bin: @@ -252,6 +253,7 @@ proc buildFromDir( doCmd("\"" & getNimBin() & "\" $# --noNimblePath $# $# $# \"$#\"" % [pkgInfo.backend, nimblePkgVersion, join(args, " "), outputOpt, realDir / bin.changeFileExt("nim")]) + binariesBuilt.inc() except NimbleError: let currentExc = (ref NimbleError)(getCurrentException()) let exc = newException(BuildFailed, "Build failed for package: " & @@ -261,6 +263,11 @@ proc buildFromDir( exc.hint = hint raise exc + if binariesBuilt == 0: + raiseNimbleError( + "No binaries built, did you specify a valid binary name?" + ) + # Handle post-`build` hook. cd realDir: # Make sure `execHook` executes the correct .nimble file. discard execHook(options, actionBuild, false) @@ -1084,7 +1091,7 @@ proc check(options: Options) = proc run(options: Options) = # Verify parameters. - let binary = options.getCompilationBinary().get() + let binary = options.getCompilationBinary().get("") if binary.len == 0: raiseNimbleError("Please specify a binary to run") diff --git a/src/nimblepkg/options.nim b/src/nimblepkg/options.nim index 72f4a3a..a162a0f 100644 --- a/src/nimblepkg/options.nim +++ b/src/nimblepkg/options.nim @@ -85,8 +85,12 @@ Commands: toplevel directory of the Nimble package. uninstall [pkgname, ...] Uninstalls a list of packages. [-i, --inclDeps] Uninstall package and dependent package(s). - build [opts, ...] Builds a package. + build [opts, ...] [bin] Builds a package. run [opts, ...] bin Builds and runs a package. + A binary name needs + to be specified after any compilation options, + any flags after the binary name are passed to + the binary when it is run. c, cc, js [opts, ...] f.nim Builds a file inside a package. Passes options to the Nim compiler. test Compiles and executes tests From b68f7849e6eaf1c17aaf85af634fa9105d259537 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 22 Sep 2019 21:21:26 +0100 Subject: [PATCH 77/95] Add 0.11.0 changelog. --- changelog.markdown | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/changelog.markdown b/changelog.markdown index a011485..92f1d31 100644 --- a/changelog.markdown +++ b/changelog.markdown @@ -3,7 +3,33 @@ # Nimble changelog +## 0.11.0 - 22/09/2019 + +This is a major release containing nearly 60 commits. Most changes are +bug fixes, but this release also includes a couple new features: + +- Binaries can now be built and run using the new ``run`` command. +- The ``NimblePkgVersion`` is now defined so you can easily get the package + version in your source code + ([example](https://github.com/nim-lang/nimble/blob/4a2aaa07d/tests/nimbleVersionDefine/src/nimbleVersionDefine.nim)). + +Some other highlights: + +- Temporary files are now kept when the ``--debug`` flag is used. +- Fixed dependency resolution issues with "#head" packages (#432 and #672). +- The `install` command can now take Nim compiler flags via the new + ``--passNim`` flag. +- Command line arguments are now passed properly to tasks (#633). +- The ``test`` command now respects the specified backend (#631). +- The ``dump`` command will no longer prompt and now has an implicit ``-y``. +- Fixed bugs with the new nimscript executor (#665). - Fixed multiple downloads and installs of the same package (#678). +- Nimble init no longer overwrites existing files (#581). +- Fixed incorrect submodule version being pulled when in a non-master branch (#675). + +---- + +Full changelog: https://github.com/nim-lang/nimble/compare/v0.10.2...v0.11.0 ## 0.10.2 - 03/06/2019 From 3625c1f861fd0573d271f700bc69e676fd911f97 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 22 Sep 2019 21:34:05 +0100 Subject: [PATCH 78/95] Expand Nimble readme slightly (installation/nimble-run). --- readme.markdown | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/readme.markdown b/readme.markdown index 9470aa8..e047e1b 100644 --- a/readme.markdown +++ b/readme.markdown @@ -15,6 +15,7 @@ Interested in learning **how to create a package**? Skip directly to that sectio - [nimble install](#nimble-install) - [nimble uninstall](#nimble-uninstall) - [nimble build](#nimble-build) + - [nimble run](#nimble-run) - [nimble c](#nimble-c) - [nimble list](#nimble-list) - [nimble search](#nimble-search) @@ -74,10 +75,15 @@ not need to install Nimble manually**. But in case you still want to install Nimble manually, you can follow the following instructions. -There are two ways to install Nimble manually. The first is using the -``koch`` tool included in the Nim distribution and +There are two ways to install Nimble manually. Using ``koch`` and using Nimble +itself. + +### Using koch + +The ``koch`` tool is included in the Nim distribution and [repository](https://github.com/nim-lang/Nim/blob/devel/koch.nim). -Simply execute the following command to compile and install Nimble. +Simply navigate to the location of your Nim installation and execute the +following command to compile and install Nimble. ``` ./koch nimble @@ -86,6 +92,19 @@ Simply execute the following command to compile and install Nimble. This will clone the Nimble repository, compile Nimble and copy it into Nim's bin directory. +### Using Nimble + +In most cases you will already have Nimble installed, you can install a newer +version of Nimble by simply running the following command: + +``` +nimble install nimble +``` + +This will download the latest release of Nimble and install it on your system. + +Note that you must have `~/.nimble/bin` in your PATH for this to work, if you're +using choosenim then you likely already have this set up correctly. ## Nimble usage @@ -219,6 +238,13 @@ flags, i.e. a debug build which includes stack traces but no GDB debug information. The ``install`` command will build the package in release mode instead. +### nimble run + +The ``run`` command can be used to build and run any binary specified in your +package's ``bin`` list. You can pass any compilation flags you wish by specifying +them before the ``run`` command, and you can specify arguments for your binary +by specifying them after the ``run`` command. + ### nimble c The ``c`` (or ``compile``, ``js``, ``cc``, ``cpp``) command can be used by From 901afa8c71505ad8f6aa1118e59d8c876fee84bb Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 22 Sep 2019 22:13:31 +0100 Subject: [PATCH 79/95] Further fix for #432. --- src/nimble.nim | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/nimble.nim b/src/nimble.nim index 27e4774..634d191 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -200,12 +200,13 @@ proc processDeps(pkginfo: PackageInfo, options: Options): seq[PackageInfo] = # in the path. var pkgsInPath: StringTableRef = newStringTable(modeCaseSensitive) for pkgInfo in result: + let currentVer = pkgInfo.getConcreteVersion(options) if pkgsInPath.hasKey(pkgInfo.name) and - pkgsInPath[pkgInfo.name] != pkgInfo.version: + pkgsInPath[pkgInfo.name] != currentVer: raise newException(NimbleError, "Cannot satisfy the dependency on $1 $2 and $1 $3" % - [pkgInfo.name, pkgInfo.version, pkgsInPath[pkgInfo.name]]) - pkgsInPath[pkgInfo.name] = pkgInfo.getConcreteVersion(options) + [pkgInfo.name, currentVer, pkgsInPath[pkgInfo.name]]) + pkgsInPath[pkgInfo.name] = currentVer # We add the reverse deps to the JSON file here because we don't want # them added if the above errorenous condition occurs From 0ed8e6403c8a826e776f36484e2e6ae0e9df9c4f Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 22 Sep 2019 22:22:21 +0100 Subject: [PATCH 80/95] Implicitly disable package validation for run/build/compile. --- src/nimble.nim | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/nimble.nim b/src/nimble.nim index 634d191..bb74ac0 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -1112,7 +1112,7 @@ proc run(options: Options) = doCmd("$# $#" % [binaryPath, args], showOutput = true) -proc doAction(options: Options) = +proc doAction(options: var Options) = if options.showHelp: writeHelp() if options.showVersion: @@ -1123,6 +1123,10 @@ proc doAction(options: Options) = if not existsDir(options.getPkgsDir): createDir(options.getPkgsDir) + if options.action.typ in {actionTasks, actionRun, actionBuild, actionCompile}: + # Implicitly disable package validation for these commands. + options.disableValidation = true + case options.action.typ of actionRefresh: refresh(options) @@ -1177,7 +1181,8 @@ proc doAction(options: Options) = var execResult: ExecutionResult[bool] if execCustom(options, execResult, failFast=not isPreDefined): if execResult.hasTaskRequestedCommand(): - doAction(execResult.getOptionsForCommand(options)) + var options = execResult.getOptionsForCommand(options) + doAction(options) else: # If there is no task defined for the `test` task, we run the pre-defined # fallback logic. From 4007b2a778429a978e12307bf13a038029b4c4d9 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Mon, 23 Sep 2019 00:09:26 +0100 Subject: [PATCH 81/95] Fixes `nimble run` on Windows. Cherry picked from https://github.com/nim-lang/nimble/commit/331a33711dc0610a426941e441e6d4630e023371 --- src/nimblepkg/options.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nimblepkg/options.nim b/src/nimblepkg/options.nim index a162a0f..bf050fe 100644 --- a/src/nimblepkg/options.nim +++ b/src/nimblepkg/options.nim @@ -520,7 +520,7 @@ proc getCompilationBinary*(options: Options): Option[string] = if file.len > 0: return some(file) of actionRun: - let runFile = options.action.runFile.changeFileExt("") + let runFile = options.action.runFile.changeFileExt(ExeExt) if runFile.len > 0: return some(runFile) else: From a2ec2db8f261c5fd3f2d977a969babeda2a91fb0 Mon Sep 17 00:00:00 2001 From: genotrance Date: Thu, 3 Oct 2019 18:38:51 -0500 Subject: [PATCH 82/95] Enable Windows CI (#714) Also fix #676 --- .travis.yml | 33 +++++++++++++----- src/nimble.nim | 6 +++- .../src/nimbleVersionDefine | Bin 86500 -> 93432 bytes tests/recursive/recursive.nimble | 7 ++-- tests/tester.nim | 21 ++++++----- 5 files changed, 45 insertions(+), 22 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3f824f3..a8b511b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ os: + - windows - linux - osx @@ -7,23 +8,37 @@ language: c env: - BRANCH=0.19.6 - BRANCH=0.20.2 + - BRANCH=1.0.0 # This is the latest working Nim version against which Nimble is being tested - - BRANCH=#2565d3d102efd21ba02ed1f3b96d892fe2637d2b + - BRANCH=#16c39f9b2edc963655889cfd33e165bfae91c96d cache: directories: - - "$HOME/.choosenim/toolchains/nim-0.19.6" - - "$HOME/.choosenim/toolchains/nim-0.20.2" + - "$HOME/.nimble/bin" + - "$HOME/.choosenim" install: - - export CHOOSENIM_CHOOSE_VERSION=$BRANCH - - | - curl https://nim-lang.org/choosenim/init.sh -sSf > init.sh - sh init.sh -y - -before_script: - export CHOOSENIM_NO_ANALYTICS=1 - export PATH=$HOME/.nimble/bin:$PATH + - | + if ! type -P choosenim &> /dev/null; then + if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then + # Latest choosenim binary doesn't have + # https://github.com/dom96/choosenim/pull/117 + # https://github.com/dom96/choosenim/pull/135 + # + # Using custom build with these PRs for Windows + curl -L -s "https://bintray.com/genotrance/binaries/download_file?file_path=choosenim.exe" -o choosenim.exe + curl -L -s "https://bintray.com/genotrance/binaries/download_file?file_path=libeay32.dll" -o libeay32.dll + curl -L -s "https://bintray.com/genotrance/binaries/download_file?file_path=ssleay32.dll" -o ssleay32.dll + ./choosenim.exe $BRANCH -y + cp ./choosenim.exe $HOME/.nimble/bin/. + else + export CHOOSENIM_CHOOSE_VERSION=$BRANCH + curl https://nim-lang.org/choosenim/init.sh -sSf > init.sh + sh init.sh -y + fi + fi script: - cd tests diff --git a/src/nimble.nim b/src/nimble.nim index bb74ac0..e214538 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -1063,7 +1063,11 @@ proc test(options: Options) = existsAfter = existsFile(binFileName) canRemove = not existsBefore and existsAfter if canRemove: - removeFile(binFileName) + try: + removeFile(binFileName) + except OSError as exc: + display("Warning:", "Failed to delete " & binFileName & ": " & + exc.msg, Warning, MediumPriority) if failures == 0: display("Success:", "All tests passed", Success, HighPriority) diff --git a/tests/nimbleVersionDefine/src/nimbleVersionDefine b/tests/nimbleVersionDefine/src/nimbleVersionDefine index 3b089c01cfe8bed84e2cacb812590f1b97d73f10..7aa3ee894aae8f7caa3ef0731bd0ee9452010a3f 100755 GIT binary patch literal 93432 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

QzGccFt5JNK)G;L?5C|Peqf%X%M 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

t`PuWKkA=4TL(qpjXbJj| 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 literal 86500 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 PpazRKkMYRB4(R+}iPK7{ diff --git a/tests/recursive/recursive.nimble b/tests/recursive/recursive.nimble index 972dd0b..d6b155d 100644 --- a/tests/recursive/recursive.nimble +++ b/tests/recursive/recursive.nimble @@ -9,10 +9,9 @@ license = "BSD" requires "nim >= 0.12.1" -when defined(windows): - let callNimble = "..\\..\\src\\nimble.exe" -else: - let callNimble = "../../src/nimble" +let + callNimble = getEnv("NIMBLE_TEST_BINARY_PATH") +doAssert callNimble.len != 0, "NIMBLE_TEST_BINARY_PATH not set" task recurse, "Level 1": echo 1 diff --git a/tests/tester.nim b/tests/tester.nim index ceb1fc6..f969f66 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -12,6 +12,9 @@ var installDir = rootDir / "tests" / "nimbleDir" const path = "../src/nimble" const stringNotFound = -1 +# Set env var to propagate nimble binary path +putEnv("NIMBLE_TEST_BINARY_PATH", nimblePath) + # Clear nimble dir. removeDir(installDir) createDir(installDir) @@ -480,17 +483,17 @@ test "issue #349": ] proc checkName(name: string) = - when defined(windows): - if name.toLowerAscii() in @["con", "nul"]: - return let (outp, code) = execNimble("init", "-y", name) let msg = outp.strip.processOutput() check code == QuitFailure check inLines(msg, "\"$1\" is an invalid package name: reserved name" % name) - removeFile(name.changeFileExt("nimble")) - removeDir("src") - removeDir("tests") + try: + removeFile(name.changeFileExt("nimble")) + removeDir("src") + removeDir("tests") + except OSError: + discard for reserved in reservedNames: checkName(reserved.toUpperAscii()) @@ -930,7 +933,8 @@ suite "nimble run": "blahblah", # The command to run ) check exitCode == QuitFailure - check output.contains("Binary 'blahblah' is not defined in 'run' package.") + check output.contains("Binary '$1' is not defined in 'run' package." % + "blahblah".changeFileExt(ExeExt)) test "Parameters passed to executable": cd "run": @@ -942,7 +946,8 @@ suite "nimble run": "check" # Second argument passed to the executed command. ) check exitCode == QuitSuccess - check output.contains("tests/run/run --debug check") + check output.contains("tests$1run$1$2 --debug check" % + [$DirSep, "run".changeFileExt(ExeExt)]) check output.contains("""Testing `nimble run`: @["--debug", "check"]""") test "NimbleVersion is defined": From 5bb795a364a431f897c3864186dbe1aa138c85b9 Mon Sep 17 00:00:00 2001 From: Ganesh Viswanathan Date: Wed, 23 Oct 2019 16:20:59 -0500 Subject: [PATCH 83/95] Export initOptions for choosenim --- src/nimblepkg/options.nim | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/nimblepkg/options.nim b/src/nimblepkg/options.nim index bf050fe..42fffb1 100644 --- a/src/nimblepkg/options.nim +++ b/src/nimblepkg/options.nim @@ -371,7 +371,8 @@ proc parseFlag*(flag, val: string, result: var Options, kind = cmdLongOption) = if not wasFlagHandled and not isGlobalFlag: result.unknownFlags.add((kind, flag, val)) -proc initOptions(): Options = +proc initOptions*(): Options = + # Exported for choosenim Options( action: Action(typ: actionNil), pkgInfoCache: newTable[string, PackageInfo](), @@ -524,4 +525,4 @@ proc getCompilationBinary*(options: Options): Option[string] = if runFile.len > 0: return some(runFile) else: - discard \ No newline at end of file + discard From 703abe3d41fb60ad03957d78bf73df48360d67f7 Mon Sep 17 00:00:00 2001 From: yuchunzhou Date: Wed, 23 Oct 2019 16:43:59 +0800 Subject: [PATCH 84/95] fix #725 --- src/nimblepkg/options.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nimblepkg/options.nim b/src/nimblepkg/options.nim index 42fffb1..9deb357 100644 --- a/src/nimblepkg/options.nim +++ b/src/nimblepkg/options.nim @@ -359,6 +359,7 @@ proc parseFlag*(flag, val: string, result: var Options, kind = cmdLongOption) = if not isGlobalFlag: result.action.compileOptions.add(getFlagString(kind, flag, val)) of actionRun: + result.showHelp = false result.action.runFlags.add(getFlagString(kind, flag, val)) of actionCustom: if result.action.command.normalize == "test": From b3abee937dad1012493a195c6c3531693caf8f7c Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Thu, 24 Oct 2019 23:24:06 +0100 Subject: [PATCH 85/95] Add tests for #666. --- src/nimblepkg/download.nim | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/nimblepkg/download.nim b/src/nimblepkg/download.nim index 85bf54f..3bbfa25 100644 --- a/src/nimblepkg/download.nim +++ b/src/nimblepkg/download.nim @@ -300,4 +300,17 @@ when isMainModule: }) doAssert expected == getVersionList(data) + + block: + let data2 = @["v0.1.0", "v0.1.1", "v0.2.0", + "0.4.0", "v0.4.2"] + let expected2 = toOrderedTable[Version, string]({ + newVersion("0.4.2"): "v0.4.2", + newVersion("0.4.0"): "0.4.0", + newVersion("0.2.0"): "v0.2.0", + newVersion("0.1.1"): "v0.1.1", + newVersion("0.1.0"): "v0.1.0", + }) + doAssert expected2 == getVersionList(data2) + echo("Everything works!") From bbb586dbfc0b5c3cd8e7684223b88f150cc3fa3f Mon Sep 17 00:00:00 2001 From: yuchunzhou Date: Tue, 29 Oct 2019 05:28:46 +0800 Subject: [PATCH 86/95] add vcs to the new created project (#729) * add git vcs and a default .nim.cfg file to the new created project * remove the gitapi dependency * add vcs support for new nimble project * add vcs support for new nimble project * update pull request #729 * add --git/hg flag instead of --vcs flag for nimble init command --- src/nimble.nim | 19 ++++++++++++++++++- src/nimblepkg/options.nim | 11 +++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/nimble.nim b/src/nimble.nim index e214538..412c987 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -3,12 +3,13 @@ import system except TResult -import os, tables, strtabs, json, algorithm, sets, uri, sugar, sequtils +import os, tables, strtabs, json, algorithm, sets, uri, sugar, sequtils, osproc import std/options as std_opt import strutils except toLower from unicode import toLower from sequtils import toSeq +from strformat import fmt import nimblepkg/packageinfo, nimblepkg/version, nimblepkg/tools, nimblepkg/download, nimblepkg/config, nimblepkg/common, @@ -724,6 +725,11 @@ proc dump(options: Options) = echo "backend: ", p.backend.escape proc init(options: Options) = + # Check whether the vcs is installed. + let vcsBin = options.action.vcsOption + if vcsBin != "" and findExe(vcsBin, true) == "": + raise newException(NimbleError, "Please install git or mercurial first") + # Determine the package name. let pkgName = if options.action.projName != "": @@ -858,6 +864,17 @@ js - Compile using JavaScript backend.""", pkgRoot ) + # Create a git or hg repo in the new nimble project. + if vcsBin != "": + let cmd = fmt"cd {pkgRoot} && {vcsBin} init" + let ret: tuple[output: string, exitCode: int] = execCmdEx(cmd) + if ret.exitCode != 0: quit ret.output + + var ignoreFile = if vcsBin == "git": ".gitignore" else: ".hgignore" + var fd = open(joinPath(pkgRoot, ignoreFile), fmWrite) + fd.write(pkgName & "\n") + fd.close() + display("Success:", "Package $# created successfully" % [pkgName], Success, HighPriority) diff --git a/src/nimblepkg/options.nim b/src/nimblepkg/options.nim index 9deb357..a338978 100644 --- a/src/nimblepkg/options.nim +++ b/src/nimblepkg/options.nim @@ -51,6 +51,7 @@ type search*: seq[string] # Search string. of actionInit, actionDump: projName*: string + vcsOption*: string of actionCompile, actionDoc, actionBuild: file*: string backend*: string @@ -80,6 +81,8 @@ Commands: init [pkgname] Initializes a new Nimble project in the current directory or if a name is provided a new directory of the same name. + --git + --hg Create a git or hg repo in the new nimble project. publish Publishes a package on nim-lang/packages. The current working directory needs to be the toplevel directory of the Nimble package. @@ -201,8 +204,10 @@ proc initAction*(options: var Options, key: string) = else: options.action.backend = keyNorm of actionInit: options.action.projName = "" + options.action.vcsOption = "" of actionDump: options.action.projName = "" + options.action.vcsOption = "" options.forcePrompts = forcePromptYes of actionRefresh: options.action.optionalURL = "" @@ -349,6 +354,12 @@ proc parseFlag*(flag, val: string, result: var Options, kind = cmdLongOption) = result.action.passNimFlags.add(val) else: wasFlagHandled = false + of actionInit: + case f + of "git", "hg": + result.action.vcsOption = f + else: + wasFlagHandled = false of actionUninstall: case f of "incldeps", "i": From a8a5bdd863ab311f39c242c705207b937695e270 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Tue, 29 Oct 2019 22:43:04 +0000 Subject: [PATCH 87/95] Fixes #734 --- readme.markdown | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/readme.markdown b/readme.markdown index e047e1b..d40fded 100644 --- a/readme.markdown +++ b/readme.markdown @@ -172,12 +172,13 @@ example: This is of course Git-specific, for Mercurial, use ``tip`` instead of ``head``. A branch, tag, or commit hash may also be specified in the place of ``head``. -Instead of specifying a VCS branch, you may also specify a version range, for -example: +Instead of specifying a VCS branch, you may also specify a concrete version or a +version range, for example: + $ nimble install nimgame@0.5 $ nimble install nimgame@"> 0.5" -In this case a version which is greater than ``0.5`` will be installed. +The latter command will install a version which is greater than ``0.5``. If you don't specify a parameter and there is a ``package.nimble`` file in your current working directory then Nimble will install the package residing in From 051cfa6cd34539db32b3a4d77ca87e4c59cf6fdf Mon Sep 17 00:00:00 2001 From: itmuckel Date: Sun, 3 Nov 2019 12:22:57 +0100 Subject: [PATCH 88/95] Add '==' as operator for pinning versions. --- src/nimblepkg/version.nim | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/nimblepkg/version.nim b/src/nimblepkg/version.nim index 4834b6d..e4114f1 100644 --- a/src/nimblepkg/version.nim +++ b/src/nimblepkg/version.nim @@ -147,7 +147,7 @@ proc makeRange*(version: string, op: string): VersionRange = result = VersionRange(kind: verEqLater) of "<=": result = VersionRange(kind: verEqEarlier) - of "": + of "", "==": result = VersionRange(kind: verEq) else: raise newException(ParseVersionError, "Invalid operator: " & op) @@ -298,9 +298,10 @@ when isMainModule: doAssert(newVersion("0.1.0") <= newVersion("0.1")) var inter1 = parseVersionRange(">= 1.0 & <= 1.5") - doAssert inter1.kind == verIntersect + doAssert(inter1.kind == verIntersect) var inter2 = parseVersionRange("1.0") doAssert(inter2.kind == verEq) + doAssert(parseVersionRange("== 3.4.2") == parseVersionRange("3.4.2")) doAssert(not withinRange(newVersion("1.5.1"), inter1)) doAssert(withinRange(newVersion("1.0.2.3.4.5.6.7.8.9.10.11.12"), inter1)) From 16ba5db44e0a9132f966a2082012cc3520bfec06 Mon Sep 17 00:00:00 2001 From: Xie Yanbo Date: Sun, 10 Nov 2019 11:37:11 +0800 Subject: [PATCH 89/95] tests' config file should be config.nims which is created by `nimble init`. --- readme.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.markdown b/readme.markdown index d40fded..89f7a22 100644 --- a/readme.markdown +++ b/readme.markdown @@ -505,7 +505,7 @@ For a package named "foobar", the recommended project structure is the following └── src └── foobar.nim # Imported via `import foobar` └── tests # Contains the tests - ├── nim.cfg + ├── config.nims ├── tfoo1.nim # First test └── tfoo2.nim # Second test From e9d45ca6830c3e9af6b15c0efd252b7c59170854 Mon Sep 17 00:00:00 2001 From: Taylor Hoff Date: Tue, 19 Nov 2019 09:41:26 -0500 Subject: [PATCH 90/95] Capitalize the "D" in srcDir to avoid errors when using styleChecks Fixes #740 --- src/nimblepkg/nimscriptapi.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nimblepkg/nimscriptapi.nim b/src/nimblepkg/nimscriptapi.nim index a8b4ba3..ec2f46c 100644 --- a/src/nimblepkg/nimscriptapi.nim +++ b/src/nimblepkg/nimscriptapi.nim @@ -17,7 +17,7 @@ var author*: string ## The package's author. description*: string ## The package's description. license*: string ## The package's license. - srcdir*: string ## The package's source directory. + srcDir*: string ## The package's source directory. binDir*: string ## The package's binary directory. backend*: string ## The package's backend. @@ -101,7 +101,7 @@ proc printPkgInfo(): string = printIfLen author printIfLen description printIfLen license - printIfLen srcdir + printIfLen srcDir printIfLen binDir printIfLen backend From 9391fbc56d9e4d3080c10583268f243b8b78edd5 Mon Sep 17 00:00:00 2001 From: inv2004 Date: Sat, 18 Jan 2020 01:15:34 +0300 Subject: [PATCH 91/95] Run can work without additional option if only one bin in .nimble (#761) * nimble run can work if only one binary in config * usage changed * runFile is option now * usage updated * small refactoring to reduce condition * setRunOptions fix * some code optimisation --- src/nimble.nim | 18 +++++++++-------- src/nimblepkg/options.nim | 41 +++++++++++++++++++++++++-------------- tests/tester.nim | 26 +++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 23 deletions(-) diff --git a/src/nimble.nim b/src/nimble.nim index 412c987..6d233f6 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -221,7 +221,7 @@ proc buildFromDir( options: Options ) = ## Builds a package as specified by ``pkgInfo``. - let binToBuild = options.getCompilationBinary() + let binToBuild = options.getCompilationBinary(pkgInfo) # Handle pre-`build` hook. let realDir = pkgInfo.getRealDir() cd realDir: # Make sure `execHook` executes the correct .nimble file. @@ -535,9 +535,9 @@ proc build(options: Options) = var args = options.getCompilationFlags() buildFromDir(pkgInfo, paths, args, options) -proc execBackend(options: Options) = +proc execBackend(pkgInfo: PackageInfo, options: Options) = let - bin = options.getCompilationBinary().get() + bin = options.getCompilationBinary(pkgInfo).get() binDotNim = bin.addFileExt("nim") if bin == "": raise newException(NimbleError, "You need to specify a file.") @@ -1070,11 +1070,11 @@ proc test(options: Options) = if options.continueTestsOnFailure: inc tests try: - execBackend(optsCopy) + execBackend(pkgInfo, optsCopy) except NimbleError: inc failures else: - execBackend(optsCopy) + execBackend(pkgInfo, optsCopy) let existsAfter = existsFile(binFileName) @@ -1113,11 +1113,12 @@ proc check(options: Options) = proc run(options: Options) = # Verify parameters. - let binary = options.getCompilationBinary().get("") + var pkgInfo = getPkgInfo(getCurrentDir(), options) + + let binary = options.getCompilationBinary(pkgInfo).get("") if binary.len == 0: raiseNimbleError("Please specify a binary to run") - var pkgInfo = getPkgInfo(getCurrentDir(), options) if binary notin pkgInfo.bin: raiseNimbleError( "Binary '$#' is not defined in '$#' package." % [binary, pkgInfo.name] @@ -1176,7 +1177,8 @@ proc doAction(options: var Options) = of actionRun: run(options) of actionCompile, actionDoc: - execBackend(options) + var pkgInfo = getPkgInfo(getCurrentDir(), options) + execBackend(pkgInfo, options) of actionInit: init(options) of actionPublish: diff --git a/src/nimblepkg/options.nim b/src/nimblepkg/options.nim index a338978..14d1c31 100644 --- a/src/nimblepkg/options.nim +++ b/src/nimblepkg/options.nim @@ -57,7 +57,7 @@ type backend*: string compileOptions: seq[string] of actionRun: - runFile: string + runFile: Option[string] compileFlags: seq[string] runFlags*: seq[string] of actionCustom: @@ -89,11 +89,11 @@ Commands: uninstall [pkgname, ...] Uninstalls a list of packages. [-i, --inclDeps] Uninstall package and dependent package(s). build [opts, ...] [bin] Builds a package. - run [opts, ...] bin Builds and runs a package. - A binary name needs - to be specified after any compilation options, - any flags after the binary name are passed to - the binary when it is run. + run [opts, ...] [bin] Builds and runs a package. + Binary needs to be specified after any + compilation options if there are several + binaries defined, any flags after the binary + or -- arg are passed to the binary when it is run. c, cc, js [opts, ...] f.nim Builds a file inside a package. Passes options to the Nim compiler. test Compiles and executes tests @@ -266,6 +266,12 @@ proc parseCommand*(key: string, result: var Options) = result.action = Action(typ: parseActionType(key)) initAction(result, key) +proc setRunOptions(result: var Options, key, val: string, isArg: bool) = + if result.action.runFile.isNone() and (isArg or val == "--"): + result.action.runFile = some(key) + else: + result.action.runFlags.add(val) + proc parseArgument*(key: string, result: var Options) = case result.action.typ of actionNil: @@ -297,10 +303,7 @@ proc parseArgument*(key: string, result: var Options) = of actionBuild: result.action.file = key of actionRun: - if result.action.runFile.len == 0: - result.action.runFile = key - else: - result.action.runFlags.add(key) + result.setRunOptions(key, key, true) of actionCustom: result.action.arguments.add(key) else: @@ -371,7 +374,7 @@ proc parseFlag*(flag, val: string, result: var Options, kind = cmdLongOption) = result.action.compileOptions.add(getFlagString(kind, flag, val)) of actionRun: result.showHelp = false - result.action.runFlags.add(getFlagString(kind, flag, val)) + result.setRunOptions(flag, getFlagString(kind, flag, val), false) of actionCustom: if result.action.command.normalize == "test": if f == "continue" or f == "c": @@ -441,7 +444,7 @@ proc parseCmdLine*(): Options = else: parseArgument(key, result) of cmdLongOption, cmdShortOption: - parseFlag(key, val, result, kind) + parseFlag(key, val, result, kind) of cmdEnd: assert(false) # cannot happen handleUnknownFlags(result) @@ -526,15 +529,23 @@ proc getCompilationFlags*(options: Options): seq[string] = var opt = options return opt.getCompilationFlags() -proc getCompilationBinary*(options: Options): Option[string] = +proc getCompilationBinary*(options: Options, pkgInfo: PackageInfo): Option[string] = case options.action.typ of actionBuild, actionDoc, actionCompile: let file = options.action.file.changeFileExt("") if file.len > 0: return some(file) of actionRun: - let runFile = options.action.runFile.changeFileExt(ExeExt) + let optRunFile = options.action.runFile + let runFile = + if optRunFile.get("").len > 0: + optRunFile.get() + elif pkgInfo.bin.len == 1: + pkgInfo.bin[0] + else: + "" + if runFile.len > 0: - return some(runFile) + return some(runFile.changeFileExt(ExeExt)) else: discard diff --git a/tests/tester.nim b/tests/tester.nim index f969f66..c5d9925 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -950,6 +950,32 @@ suite "nimble run": [$DirSep, "run".changeFileExt(ExeExt)]) check output.contains("""Testing `nimble run`: @["--debug", "check"]""") + test "Parameters not passed to single executable": + cd "run": + var (output, exitCode) = execNimble( + "--debug", # Flag to enable debug verbosity in Nimble + "run", # Run command invokation + "--debug" # First argument passed to the executed command + ) + check exitCode == QuitSuccess + check output.contains("tests$1run$1$2 --debug" % + [$DirSep, "run".changeFileExt(ExeExt)]) + check output.contains("""Testing `nimble run`: @["--debug"]""") + + test "Parameters passed to single executable": + cd "run": + var (output, exitCode) = execNimble( + "--debug", # Flag to enable debug verbosity in Nimble + "run", # Run command invokation + "--", # Flag to set run file to "" before next argument + "--debug", # First argument passed to the executed command + "check" # Second argument passed to the executed command. + ) + check exitCode == QuitSuccess + check output.contains("tests$1run$1$2 --debug check" % + [$DirSep, "run".changeFileExt(ExeExt)]) + check output.contains("""Testing `nimble run`: @["--debug", "check"]""") + test "NimbleVersion is defined": cd "nimbleVersionDefine": var (output, exitCode) = execNimble("c", "-r", "src/nimbleVersionDefine.nim") From 85e5bc7c375caf07977c58481dfbac58e326dde8 Mon Sep 17 00:00:00 2001 From: Ganesh Viswanathan Date: Wed, 5 Feb 2020 12:31:05 -0600 Subject: [PATCH 92/95] Use travis gist --- .travis.yml | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/.travis.yml b/.travis.yml index a8b511b..d132ced 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,9 +8,9 @@ language: c env: - BRANCH=0.19.6 - BRANCH=0.20.2 - - BRANCH=1.0.0 + - BRANCH=1.0.6 # This is the latest working Nim version against which Nimble is being tested - - BRANCH=#16c39f9b2edc963655889cfd33e165bfae91c96d + - BRANCH=#ab525cc48abdbbbed1f772e58e9fe21474f70f07 cache: directories: @@ -18,27 +18,8 @@ cache: - "$HOME/.choosenim" install: - - export CHOOSENIM_NO_ANALYTICS=1 - - export PATH=$HOME/.nimble/bin:$PATH - - | - if ! type -P choosenim &> /dev/null; then - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then - # Latest choosenim binary doesn't have - # https://github.com/dom96/choosenim/pull/117 - # https://github.com/dom96/choosenim/pull/135 - # - # Using custom build with these PRs for Windows - curl -L -s "https://bintray.com/genotrance/binaries/download_file?file_path=choosenim.exe" -o choosenim.exe - curl -L -s "https://bintray.com/genotrance/binaries/download_file?file_path=libeay32.dll" -o libeay32.dll - curl -L -s "https://bintray.com/genotrance/binaries/download_file?file_path=ssleay32.dll" -o ssleay32.dll - ./choosenim.exe $BRANCH -y - cp ./choosenim.exe $HOME/.nimble/bin/. - else - export CHOOSENIM_CHOOSE_VERSION=$BRANCH - curl https://nim-lang.org/choosenim/init.sh -sSf > init.sh - sh init.sh -y - fi - fi + - curl https://gist.github.com/genotrance/fb53504a4fba88bc5201d3783df5c522/raw/travis.sh -LsSf -o travis.sh + - source travis.sh script: - cd tests From 27d56f8e9f6d7cee207e66c5a4d3766e73975f15 Mon Sep 17 00:00:00 2001 From: Ganesh Viswanathan Date: Tue, 11 Feb 2020 18:58:31 +0000 Subject: [PATCH 93/95] Remove ~/.nimble from cache --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d132ced..f1ff69e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,6 @@ env: cache: directories: - - "$HOME/.nimble/bin" - "$HOME/.choosenim" install: From 68a9c4c955c6abc501a7b5d2f197081a79347d57 Mon Sep 17 00:00:00 2001 From: Daniel M Date: Wed, 19 Feb 2020 15:20:59 -0800 Subject: [PATCH 94/95] fix and improve the behavior of `nimble run` quote arguments appropriately: - whitespace in arguments is preserved; previously arguments with whitespace were split - quotes are now escaped the exit code of the command that has been "run" is re-used when nimble exits show the output of the "run" command regardless of whether nimble is debugging --- src/nimble.nim | 9 ++++----- tests/tester.nim | 20 +++++++++++++++++++- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/nimble.nim b/src/nimble.nim index 6d233f6..e923eec 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -1124,15 +1124,14 @@ proc run(options: Options) = "Binary '$#' is not defined in '$#' package." % [binary, pkgInfo.name] ) - let binaryPath = pkgInfo.getOutputDir(binary) - # Build the binary. build(options) - # Now run it. - let args = options.action.runFlags.join(" ") + let binaryPath = pkgInfo.getOutputDir(binary) + let cmd = quoteShellCommand(binaryPath & options.action.runFlags) + displayDebug("Executing", cmd) + cmd.execCmd.quit - doCmd("$# $#" % [binaryPath, args], showOutput = true) proc doAction(options: var Options) = if options.showHelp: diff --git a/tests/tester.nim b/tests/tester.nim index c5d9925..446fecb 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -38,7 +38,7 @@ proc execNimble(args: varargs[string]): tuple[output: string, exitCode: int] = var quotedArgs = @args quotedArgs.insert("--nimbleDir:" & installDir) quotedArgs.insert(nimblePath) - quotedArgs = quotedArgs.map((x: string) => ("\"" & x & "\"")) + quotedArgs = quotedArgs.map((x: string) => x.quoteShell) let path {.used.} = getCurrentDir().parentDir() / "src" @@ -976,6 +976,24 @@ suite "nimble run": [$DirSep, "run".changeFileExt(ExeExt)]) check output.contains("""Testing `nimble run`: @["--debug", "check"]""") + test "Executable output is shown even when not debugging": + cd "run": + var (output, exitCode) = + execNimble("run", "run", "--option1", "arg1") + check exitCode == QuitSuccess + check output.contains("""Testing `nimble run`: @["--option1", "arg1"]""") + + test "Quotes and whitespace are well handled": + cd "run": + var (output, exitCode) = execNimble( + "run", "run", "\"", "\'", "\t", "arg with spaces" + ) + check exitCode == QuitSuccess + check output.contains( + """Testing `nimble run`: @["\"", "\'", "\t", "arg with spaces"]""" + ) + + test "NimbleVersion is defined": cd "nimbleVersionDefine": var (output, exitCode) = execNimble("c", "-r", "src/nimbleVersionDefine.nim") From a10691bdf23cde83d87c8b7df947fa625fe96c7d Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Thu, 26 Mar 2020 16:14:09 -0700 Subject: [PATCH 95/95] show cmd used with --verbose (even on success); fix #783 (#781) * show cmd used with --verbose (even on success) * honor -p:-u:release * address comment * remove --showCmds flag * remove quoteShell * fix #783 * Revert "fix #783" This reverts commit af0ac004c00f17e9983c63ab99e40cd38ba6aaa4. * fix #783 by fixing the gist instead * Revert "fix #783 by fixing the gist instead" now that upstream gist was updated with my patch This reverts commit 8bec86039d8335af152acf238ab14d0268e003e5. --- src/nimble.nim | 12 ++++++++---- src/nimblepkg/tools.nim | 7 +++++-- .../src/nimbleVersionDefine | Bin 93432 -> 89112 bytes 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/nimble.nim b/src/nimble.nim index e923eec..d7e011e 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -251,10 +251,14 @@ proc buildFromDir( if not existsDir(outputDir): createDir(outputDir) + let input = realDir / bin.changeFileExt("nim") + # `quoteShell` would be more robust than `\"` (and avoid quoting when + # un-necessary) but would require changing `extractBin` + let cmd = "\"$#\" $# --noNimblePath $# $# $# \"$#\"" % + [getNimBin(), pkgInfo.backend, nimblePkgVersion, + join(args, " "), outputOpt, input] try: - doCmd("\"" & getNimBin() & "\" $# --noNimblePath $# $# $# \"$#\"" % - [pkgInfo.backend, nimblePkgVersion, join(args, " "), outputOpt, - realDir / bin.changeFileExt("nim")]) + doCmd(cmd, showCmd = true) binariesBuilt.inc() except NimbleError: let currentExc = (ref NimbleError)(getCurrentException()) @@ -382,7 +386,7 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, options.action.passNimFlags else: @[] - buildFromDir(pkgInfo, paths, flags & "-d:release", options) + buildFromDir(pkgInfo, paths, "-d:release" & flags, options) let pkgDestDir = pkgInfo.getPkgDest(options) if existsDir(pkgDestDir) and existsFile(pkgDestDir / "nimblemeta.json"): diff --git a/src/nimblepkg/tools.nim b/src/nimblepkg/tools.nim index b39fea7..a0e6a0e 100644 --- a/src/nimblepkg/tools.nim +++ b/src/nimblepkg/tools.nim @@ -11,7 +11,7 @@ proc extractBin(cmd: string): string = else: return cmd.split(' ')[0] -proc doCmd*(cmd: string, showOutput = false) = +proc doCmd*(cmd: string, showOutput = false, showCmd = false) = let bin = extractBin(cmd) if findExe(bin) == "": raise newException(NimbleError, "'" & bin & "' not in PATH.") @@ -20,7 +20,10 @@ proc doCmd*(cmd: string, showOutput = false) = stdout.flushFile() stderr.flushFile() - displayDebug("Executing", cmd) + if showCmd: + display("Executing", cmd, priority = MediumPriority) + else: + displayDebug("Executing", cmd) if showOutput: let exitCode = execCmd(cmd) displayDebug("Finished", "with exit code " & $exitCode) diff --git a/tests/nimbleVersionDefine/src/nimbleVersionDefine b/tests/nimbleVersionDefine/src/nimbleVersionDefine index 7aa3ee894aae8f7caa3ef0731bd0ee9452010a3f..af3cc22a318be8838339bcde68bf318266324d06 100755 GIT binary patch literal 89112 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

QzGccFt5JNK)G;L?5C|Peqf%X%M zjmEGqdfl#IG|86GH7V<@>p3PRihl zEnvvj9?Isz6fNMqy01vl0wAYj1oDBU6d3H4_o%e}IXGf*I zEJOdObR!c*vhP~?8TO%|WSk?{qc@z0tik@2Gb+9E_nlhJdSi^U@`)h8un+kH8~Izw z4Ths_$CDAtr3X)ZPAq|jWKjuz{NqL47I4hUI~F!EyV9rD_Cp5CADfmTU#-DFm>n?- z2Sw<`w#B7=5%EX&LAN+r2d3H97XeEF$q^*my*O1u??3)kk;v;=veun(4%Bi8;hTY% z*i(LT(2T2K(8OH~mHFUyWcry#AE*d5lf!;4p;Kj?8}>(Ce8VY=b&402BEi~dux2Qh z+DNg^uvm88AIuIy|CcEN5*(&}&IbI)dfh{(!s`|zkYSj58?K9?c~kIqT?j};9OIN4^b>H^2MmtKFaMf?BXETgO>oN*wq)($r2$MegYV-v z5)bYV1i&9e4TlKD-*D9j@E^Yr_`SHrU2o6>``;r4`^TegjX&XP2f$|#Egt+1x41h^ zb|iuUjCz`auc0D}uo}PA3h)&~iwB!20v_*Vj*bUc>D?Rri3b;HO((fEos6qp!RAE# z@~cMpG{xAOmbtTpv|TjoF3rR(OK)_?Fa-6RG&(*uR{D}wOJOV$NV4tIus42KDuC_%<2*1j| z2v>NohA04vr$NyayaD8_ErnnH0e%wj0KZE4chlMy>D^-f#DiC8ZF~O1w##0)+8u2A zGbYfS)J27^-A0~{#O7Wgeyft~{%nEreye(=>!Nq@nOde^KKj-nKq;M>5|UiJ>I z2B73k{PM3U*n}$>>n6kh2K){H037cH1pJd^g|iUK|4O}E!k>7s73wzyufh#wBdknm zn@0(Jp<Z_E{b&N>!EebX6FknP-_y|V zqVyjsyfghn1i18vy7b!_`d&Hohn{4+-C?+505SB#aLYT{_y~^VT_)J8+-ln!SCsaS zM?~75xCQ(tG%x>}f^Xr9+OB4U$Ae#KC#P++rT>E~*0!90c<>7foeVyL1d6+_d6x;! z$B)`(0j^kEnbvlx!k4?X-E3-0X>Ah~zMHGt?nbvV0^-4)RJWyE^@|7h#GQ@d?je5W z){Ju^mc+baPpM4uY~D@;&mcD}#O9q(J_^OZc0PG`l1e6H9!Ek8_BJKH^T|iNE{WR; zkO;O?ZVm>5AA2(UM9-Th!C(mg96hx=XK)AH%#pT5q4RSG3NY&5)WR`F&S2%e-Rxk+ zxdDyYbp=k0?32OQOf9woeu1wG-t%4FJOmxD{t1H+Oe(S409hjO;0{nM5&kJ242rH| znyql@T*^8U!EGKY4Y+G!Wt|m%8>1I92K`hoyBLEGpo|#&FfKxcCk?V)Y&l|RX}}5f zhoPcC>|~;)yV69V-H#3gMx8e5)MMlCj2bs;?C9j)-wi1ZjEf`>;O{A;;y)i7|8Dd^ zzVoa&vhwFGoIijrQuCk6(jo7Mc3;G<5*Qu-VRZbvqvKzV+O+)#k=V0Ek>MX!eHn>8 zSA6E#qs|(2_Na44jej;ZWst@H=2-|#e!#$GpeWEy_yi_TnmX&qX){lmIAt;m22Y(e zWAfnhC(oQ!IcMU`Ns|W;0({oo$upt~s{~tVk+PQ=0 zUOKmO@{GaLW(@*B`2KHvDksmGSUGFXTw%AB3Yz=hUCpieEk9va>D5V6f)wC&_L0q_xNGzR*n=*M*#1v0NYbjH- zmdVCV?FlA(Qq<^#ogb8sqGKlun_~~c^PUM@5 zCBw^7@&0X@eDzF3%+r(2Hc?CQzkn)Q$i$JXM3H59tz1C_6{HodDH1nk5Tyr$4?ko& z+vSb_gqPvbaq)xXz*%RN(dIH0WL2_U$erGv|E$Af2g!kR&S?)Jc=`8eIw2{`rMVlsPwVXZGxk^dBpY%yG{g8Wb5(cu_L@JU+ zZ&fHqP;Vl!SfWztG;Ot`VdRQcRxMCzMKPH2nd}GJn;y5kO8V@MAQEVg<_qNJR=`b@;NxV; zC{CtJk^>o3+XYhrHVR2ow18S%ImJ{>!+IfK#gLpRGs~<3HMu5Ri?D+v=?jegW!sqS zAUR;dfX_NfGkRtd@KmDekHu=mOd>v(z>xl?#&dLrg%}^p`4}C_Eb_Sqi~mj8O~uq$ zA(ul1XVVU}>A^@XB}~65yODafT23Um)pRD6UNNf_%ZXy7obVLNyeG&M%7Fqp_e9y5 zPQ>Oj`IJ&gR0D-dhShzRi*}3}^+brxO_WTf^{Q4WhYQX`Hv4~&4?L+zM<`JpOjH%B z6cwe28ad3W4v(AlG#OM$4%_4LvJy+9Vo&62XblJvLcfu9GDykfgKSuX#J%Az?W_nR z==B2CGQe0ckyOg88rF&|`ERZq3zd}`lB+~BRV5cG7Ll2uM5G)`D_M>u(FQ`bOqDe% z2U0Vy@BzzTtNN3?j=t%1aXyOttho(9Qhb1C+fT%h(Zh0sw`KtN~M_1RC!2? z%!J{5B~h-jdgVaQz%-}Bg|SGroLOkmP-0o+7YkHIvg-*R&V$ihgtV~g=*Xlv%+6bi zJDaIgQ6Us2gIt*f7(#2(Lpuu9Y9XialCsyczCt{qROS;6kpPx1lt+W^pVgMtQ=wqs z&nihYFFh&ogu!S$kyYXeJx>c2m*7S2GtHHBAx2`M!maSy_$KRP42=@mEtVhbB3ziL zlx5qO5#o+#onfV1%_8U684}H!MLw}mMGX--WmcFznq@H3pn@d8ozF(f)LXr|^zsTz zR#`=&)7Fl_kM0WDjxu#+DzP#qrTIvn)v0VER&}tIURqK9be*Psc*p<#KMVg8`roSe z4YDI8<5*aB9ZzKPsG$50ODD29n7|+OK2BUB^BVr>oUbs&Pl`nUAE)F_b%7Td$T5h5o zPP3_n7hQFBEK;6N;10xb)}Zqowp?Mue3=4c{=jFNtY^=f>XCAWb**P$23jy7Kd?es zjjt&73hR(CzKK-mGaaM{Nnj-u!^luo_`oB=urU)zK9Wl)xk?I6sSx8ASxjA&WG0Kb z5GcNQ7v1`B96hB>y)KvZGk!8KAg}kO6Osew&aO@~FysyHX)?i=R;~obkTvE9NKvXB z$)RDS*zN|D;)!}9ds-EJH{b7QH*;F09HWp1^SqpteU93AMjVr`?;s7k4$`!{aDMf{ zn)Wp0F32Uw+uy2bdmsV+EV0&r{mVQSA$31wAD*sHLuT;|ZUJ%!a%B78W0kahd$(f}eMs}aKp!%B9`qqyAA~;S4

t`PuWKkA=4TL(qpjXbJj| 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