diff --git a/.gitignore b/.gitignore index 5744c47..bac71d6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,12 +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 +src/nimblepkg/download # Windows executables *.exe @@ -19,4 +22,13 @@ nimcache/ # VCC compiler and linker artifacts *.ilk -*.pdb \ No newline at end of file +*.pdb + +# Editors and IDEs project files and folders +.vscode + +# VCS artifacts +*.orig + +# Test procedure artifacts +nimble_*.nims diff --git a/.travis.yml b/.travis.yml index 0e5d3a5..f1ff69e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,26 +1,24 @@ os: + - windows - linux - osx language: c env: - - BRANCH=0.19.4 - - BRANCH=#4f9366975441be889a8cd4fbfb4e41f6830dc542 + - BRANCH=0.19.6 + - BRANCH=0.20.2 + - BRANCH=1.0.6 + # This is the latest working Nim version against which Nimble is being tested + - BRANCH=#ab525cc48abdbbbed1f772e58e9fe21474f70f07 cache: directories: - - "$HOME/.choosenim/toolchains/nim-0.19.4" + - "$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 + - curl https://gist.github.com/genotrance/fb53504a4fba88bc5201d3783df5c522/raw/travis.sh -LsSf -o travis.sh + - source travis.sh script: - cd tests diff --git a/changelog.markdown b/changelog.markdown index 1f417e6..92f1d31 100644 --- a/changelog.markdown +++ b/changelog.markdown @@ -3,6 +3,52 @@ # 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 + +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..6cd7e9e 100644 --- a/nimble.nimble +++ b/nimble.nimble @@ -1,6 +1,6 @@ # Package -version = "0.10.0" +version = "0.11.0" author = "Dominik Picheta" description = "Nim package manager." license = "BSD" diff --git a/readme.markdown b/readme.markdown index f246c7e..89f7a22 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 @@ -153,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 @@ -219,6 +239,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 @@ -401,6 +428,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 @@ -477,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 @@ -785,6 +813,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/nimble.nim b/src/nimble.nim index 94e677a..d7e011e 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -3,12 +3,13 @@ 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, 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, @@ -95,7 +96,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 @@ -156,7 +157,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": @@ -199,12 +201,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.version + [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 @@ -213,16 +216,33 @@ 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], + options: Options +) = ## Builds a package as specified by ``pkgInfo``. + let binToBuild = options.getCompilationBinary(pkgInfo) + # 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?") - let realDir = pkgInfo.getRealDir() + 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: + 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) @@ -231,10 +251,15 @@ proc buildFromDir(pkgInfo: PackageInfo, paths: seq[string], 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() & "\" $# --noBabelPath $# $# \"$#\"" % - [pkgInfo.backend, join(args, " "), outputOpt, - realDir / bin.changeFileExt("nim")]) + doCmd(cmd, showCmd = true) + binariesBuilt.inc() except NimbleError: let currentExc = (ref NimbleError)(getCurrentException()) let exc = newException(BuildFailed, "Build failed for package: " & @@ -244,13 +269,14 @@ 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) + 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) proc removePkgDir(dir: string, options: Options) = ## Removes files belonging to the package in ``dir``. @@ -329,7 +355,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) @@ -356,7 +382,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, true) + let flags = if options.action.typ in {actionInstall, actionPath, actionUninstall, actionDevelop}: + options.action.passNimFlags + else: + @[] + buildFromDir(pkgInfo, paths, "-d:release" & flags, options) let pkgDestDir = pkgInfo.getPkgDest(options) if existsDir(pkgDestDir) and existsFile(pkgDestDir / "nimblemeta.json"): @@ -381,7 +411,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)) @@ -394,7 +424,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) @@ -439,7 +469,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, @@ -506,12 +536,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) -proc execBackend(options: Options) = +proc execBackend(pkgInfo: PackageInfo, options: Options) = let - bin = options.action.file + bin = options.getCompilationBinary(pkgInfo).get() binDotNim = bin.addFileExt("nim") if bin == "": raise newException(NimbleError, "You need to specify a file.") @@ -524,9 +554,10 @@ 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.action.compileOptions: + for option in options.getCompilationFlags(): args.add("\"" & option & "\" ") let backend = @@ -541,8 +572,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) = @@ -558,7 +589,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 @@ -584,7 +615,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(" ") @@ -698,6 +729,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 != "": @@ -732,20 +768,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() @@ -802,6 +838,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 +860,7 @@ Please specify a valid SPDX identifier.""", pkgAuthor, pkgDesc, pkgLicense, + pkgBackend, pkgSrcDir, pkgNimDep, pkgType @@ -823,6 +868,17 @@ Please specify a valid SPDX identifier.""", 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) @@ -831,7 +887,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], @@ -842,37 +899,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 to delete") 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. @@ -903,7 +956,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) @@ -956,7 +1009,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 == @[]: @@ -992,6 +1045,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 @@ -1006,12 +1061,12 @@ 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 = @[] - optsCopy.action.compileOptions.add("-r") - optsCopy.action.compileOptions.add("--path:.") + optsCopy.action.backend = pkgInfo.backend + optsCopy.getCompilationFlags() = @[] + optsCopy.getCompilationFlags().add("-r") + optsCopy.getCompilationFlags().add("--path:.") let binFileName = file.path.changeFileExt(ExeExt) existsBefore = existsFile(binFileName) @@ -1019,17 +1074,21 @@ 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) 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) @@ -1038,7 +1097,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 @@ -1056,7 +1115,29 @@ proc check(options: Options) = display("Failure:", "Validation failed", Error, HighPriority) quit(QuitFailure) -proc doAction(options: Options) = +proc run(options: Options) = + # Verify parameters. + var pkgInfo = getPkgInfo(getCurrentDir(), options) + + let binary = options.getCompilationBinary(pkgInfo).get("") + if binary.len == 0: + raiseNimbleError("Please specify a binary to run") + + if binary notin pkgInfo.bin: + raiseNimbleError( + "Binary '$#' is not defined in '$#' package." % [binary, pkgInfo.name] + ) + + # Build the binary. + build(options) + + let binaryPath = pkgInfo.getOutputDir(binary) + let cmd = quoteShellCommand(binaryPath & options.action.runFlags) + displayDebug("Executing", cmd) + cmd.execCmd.quit + + +proc doAction(options: var Options) = if options.showHelp: writeHelp() if options.showVersion: @@ -1067,6 +1148,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) @@ -1092,8 +1177,11 @@ proc doAction(options: Options) = listPaths(options) of actionBuild: build(options) + of actionRun: + run(options) of actionCompile, actionDoc: - execBackend(options) + var pkgInfo = getPkgInfo(getCurrentDir(), options) + execBackend(pkgInfo, options) of actionInit: init(options) of actionPublish: @@ -1110,7 +1198,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 @@ -1119,21 +1207,24 @@ 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. 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 = "" var hint = "" + var opt: Options try: - parseCmdLine().doAction() + opt = parseCmdLine() + opt.doAction() except NimbleError: let currentExc = (ref NimbleError)(getCurrentException()) (error, hint) = getOutputInfo(currentExc) @@ -1141,7 +1232,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/cli.nim b/src/nimblepkg/cli.nim index 8d96015..3afda35 100644 --- a/src/nimblepkg/cli.nim +++ b/src/nimblepkg/cli.nim @@ -12,11 +12,11 @@ # - Bright for HighPriority. # - Normal for MediumPriority. -import logging, terminal, sets, strutils, os -import ./common +import terminal, sets, strutils +import version -when defined(windows): - import winlean +when not declared(initHashSet): + import common type CLI* = ref object @@ -49,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 @@ -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/common.nim b/src/nimblepkg/common.nim index 676a2ff..ab83175 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 @@ -23,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 @@ -63,4 +63,16 @@ when not defined(nimscript): return (error, hint) const - nimbleVersion* = "0.10.0" + nimbleVersion* = "0.11.0" + +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/config.nim b/src/nimblepkg/config.nim index 5706485..c4a48fc 100644 --- a/src/nimblepkg/config.nim +++ b/src/nimblepkg/config.nim @@ -1,8 +1,8 @@ # 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 tools, version, common, cli +import version, cli type Config* = object diff --git a/src/nimblepkg/download.nim b/src/nimblepkg/download.nim index f8954e9..3bbfa25 100644 --- a/src/nimblepkg/download.nim +++ b/src/nimblepkg/download.nim @@ -2,14 +2,15 @@ # 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 git = "git", hg = "hg" -proc getSpecificDir(meth: DownloadMethod): string = +proc getSpecificDir(meth: DownloadMethod): string {.used.} = case meth of DownloadMethod.git: ".git" @@ -24,11 +25,12 @@ 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) -proc doPull(meth: DownloadMethod, downloadDir: string) = +proc doPull(meth: DownloadMethod, downloadDir: string) {.used.} = case meth of DownloadMethod.git: doCheckout(meth, downloadDir, "") @@ -102,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 @@ -267,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: @@ -282,3 +285,32 @@ 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) + + + 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!") diff --git a/src/nimblepkg/init.nim b/src/nimblepkg/init.nim index 42c7f4e..8e7c33b 100644 --- a/src/nimblepkg/init.nim +++ b/src/nimblepkg/init.nim @@ -9,10 +9,18 @@ type pkgAuthor: string pkgDesc: string pkgLicense: string + pkgBackend: string pkgSrcDir: string pkgNimDep: string pkgType: 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", priority = HighPriority) + proc createPkgStructure*(info: PkgInitInfo, pkgRoot: string) = # Create source directory createDirD(pkgRoot / info.pkgSrcDir) @@ -22,7 +30,7 @@ proc createPkgStructure*(info: PkgInitInfo, pkgRoot: string) = case info.pkgType of "binary": let mainFile = pkgRoot / info.pkgSrcDir / info.pkgName.changeFileExt("nim") - writeFile(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. @@ -34,7 +42,7 @@ when isMainModule: nimbleFileOptions.add("bin = @[\"$1\"]\n" % info.pkgName) of "library": let mainFile = pkgRoot / info.pkgSrcDir / info.pkgName.changeFileExt("nim") - writeFile(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 @@ -49,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") - writeFile(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 @@ -67,7 +75,7 @@ proc initSubmodule*(): Submodule = ) of "hybrid": let mainFile = pkgRoot / info.pkgSrcDir / info.pkgName.changeFileExt("nim") - writeFile(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. @@ -82,7 +90,7 @@ when isMainModule: let pkgSubDir = pkgRoot / info.pkgSrcDir / info.pkgName & "pkg" createDirD(pkgSubDir) let submodule = pkgSubDir / "submodule".addFileExt("nim") - writeFile(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 @@ -111,7 +119,7 @@ proc getWelcomeMessage*(): string = "Hello, World!" ) if info.pkgType == "library": - writeFile(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` @@ -128,7 +136,7 @@ test "can add": """ % info.pkgName ) else: - writeFile(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` @@ -149,23 +157,28 @@ 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 = $# -author = $# -description = $# +author = "$#" +description = "$#" license = $# srcDir = $# $# +$# # Dependencies requires "nim >= $#" """ % [ - info.pkgVersion.escape(), info.pkgAuthor.escape(), info.pkgDesc.escape(), + info.pkgVersion.escape(), info.pkgAuthor.replace("\"", "\\\""), info.pkgDesc.replace("\"", "\\\""), 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) diff --git a/src/nimblepkg/nimscriptapi.nim b/src/nimblepkg/nimscriptapi.nim old mode 100755 new mode 100644 index eba18cd..ec2f46c --- 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 @@ -14,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. @@ -35,6 +38,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 +46,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 @@ -75,25 +84,24 @@ 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 printIfLen license - printIfLen srcdir + printIfLen srcDir printIfLen binDir printIfLen backend @@ -108,14 +116,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 = "" @@ -136,7 +143,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/nimscriptexecutor.nim b/src/nimblepkg/nimscriptexecutor.nim index fad0d1e..122c41c 100644 --- a/src/nimblepkg/nimscriptexecutor.nim +++ b/src/nimblepkg/nimscriptexecutor.nim @@ -1,16 +1,17 @@ # 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 = +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 = "" @@ -20,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 @@ -57,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/src/nimblepkg/nimscriptsupport.nim b/src/nimblepkg/nimscriptsupport.nim deleted file mode 100644 index 4c8747b..0000000 --- a/src/nimblepkg/nimscriptsupport.nim +++ /dev/null @@ -1,718 +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/passaux, - compiler/condsyms, compiler/sem, compiler/semdata, - compiler/llstream, compiler/vm, compiler/vmdef, compiler/commands, - compiler/msgs, compiler/magicsys, 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 - -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) - - let pkgName = scriptName.splitFile.name - - # 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 getNimScriptCommand(): string = - when declared(NimCompilerApiVersion): - let conf = graph.config - nimCommand() - -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() diff --git a/src/nimblepkg/nimscriptwrapper.nim b/src/nimblepkg/nimscriptwrapper.nim old mode 100755 new mode 100644 index 0956b79..4cfe6ec --- a/src/nimblepkg/nimscriptwrapper.nim +++ b/src/nimblepkg/nimscriptwrapper.nim @@ -4,9 +4,9 @@ ## 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 +import hashes, json, os, strutils, tables, times, osproc, strtabs + +import version, options, cli, tools type Flags = TableRef[string, seq[string]] @@ -16,16 +16,32 @@ 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, - live = true): 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 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] = let - shash = $projectDir.hash().abs() - nimsFileCopied = projectDir / nimsFile.splitFile().name & "_" & shash & ".nims" + nimsFileCopied = projectDir / nimsFile.splitFile().name & "_" & getProcessId() & ".nims" + outFile = getNimbleTempDir() & ".out" let isScriptResultCopied = @@ -37,22 +53,40 @@ 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() - let - cmd = ("nim e --hints:off --verbosity:0 -p:" & (getTempDir() / "nimblecache").quoteShell & - " " & nimsFileCopied.quoteShell & " " & actionName).strip() + var cmd = ( + "nim e $# -p:$# $# $# $#" % [ + "--hints:off --verbosity:0", + (getTempDir() / "nimblecache").quoteShell, + nimsFileCopied.quoteShell, + outFile.quoteShell, + actionName + ] + ).strip() - if live: + let isCustomTask = isCustomTask(actionName, options) + if isCustomTask: + 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) + + if needsLiveOutput(actionName, options, isHook): result.exitCode = execCmd(cmd) - let - outFile = nimsFileCopied & ".out" - if outFile.fileExists(): - result.output = outFile.readFile() - discard outFile.tryRemoveFile() else: - result = execCmdEx(cmd, options = {poUsePath, poStdErrToStdOut}) + # 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): + discard outFile.tryRemoveFile() proc getNimsFile(scriptName: string, options: Options): string = let @@ -101,25 +135,33 @@ proc getIniFile*(scriptName: string, options: Options): string = scriptName.getLastModificationTime() if not isIniResultCached: - let - (output, exitCode) = - execNimscript(nimsFile, scriptName.parentDir(), "printPkgInfo", options, live=false) + let (output, exitCode, stdout) = execNimscript( + nimsFile, scriptName.parentDir(), printPkgInfo, options, isHook=false + ) if exitCode == 0 and output.len != 0: result.writeFile(output) + stdout.writeExecutionOutput() 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: - raise newException(NimbleError, output) + let errMsg = + if stdout.len != 0: + stdout + else: + "Exception raised during nimble script execution" + raise newException(NimbleError, errMsg) let j = @@ -140,6 +182,8 @@ proc execScript(scriptName, actionName: string, options: Options): 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. @@ -148,7 +192,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] = @@ -162,11 +206,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/src/nimblepkg/options.nim b/src/nimblepkg/options.nim index 70924bd..14d1c31 100644 --- a/src/nimblepkg/options.nim +++ b/src/nimblepkg/options.nim @@ -2,9 +2,11 @@ # 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, tools, common, cli +import config, version, common, cli type Options* = object @@ -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 @@ -41,14 +46,20 @@ 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: projName*: string + vcsOption*: string of actionCompile, actionDoc, actionBuild: file*: string backend*: string - compileOptions*: seq[string] + compileOptions: seq[string] + of actionRun: + runFile: Option[string] + compileFlags: seq[string] + runFlags*: seq[string] of actionCustom: command*: string arguments*: seq[string] @@ -61,6 +72,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. @@ -69,12 +81,19 @@ 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. uninstall [pkgname, ...] Uninstalls a list of packages. [-i, --inclDeps] Uninstall package and dependent package(s). - build Builds a package. + build [opts, ...] [bin] Builds a package. + 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 @@ -141,6 +160,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": @@ -175,6 +196,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 = "" @@ -182,8 +204,11 @@ 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 = "" of actionSearch: @@ -192,7 +217,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 = @@ -238,9 +263,15 @@ 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 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: @@ -261,23 +292,40 @@ 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 - of actionList, actionBuild, actionPublish: + of actionList, actionPublish: result.showHelp = true + of actionBuild: + result.action.file = key + of actionRun: + result.setRunOptions(key, key, true) 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 @@ -288,52 +336,64 @@ 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 - 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 actionInit: + case f + of "git", "hg": + result.action.vcsOption = f + 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.showHelp = false + result.setRunOptions(flag, getFlagString(kind, flag, val), false) + 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.typ = actionNil - result.pkgInfoCache = newTable[string, PackageInfo]() - result.nimbleDir = "" - result.verbosity = HighPriority - result.noColor = not isatty(stdout) + # Exported for choosenim + Options( + action: Action(typ: actionNil), + pkgInfoCache: newTable[string, PackageInfo](), + verbosity: HighPriority, + noColor: not isatty(stdout) + ) proc parseMisc(options: var Options) = # Load nimbledata.json @@ -348,6 +408,29 @@ proc parseMisc(options: var Options) = else: options.nimbleData = %{"reverseDeps": newJObject()} +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: + let flag = options.unknownFlags[0] + raise newException( + NimbleError, + "Unknown option: " & getFlagString(flag[0], flag[1], flag[2]) + ) + proc parseCmdLine*(): Options = result = initOptions() @@ -361,9 +444,11 @@ 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) + # Set verbosity level. setVerbosity(result.verbosity) @@ -379,6 +464,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 = "" @@ -418,3 +508,44 @@ 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 + +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, 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 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.changeFileExt(ExeExt)) + else: + discard diff --git a/src/nimblepkg/packageinfo.nim b/src/nimblepkg/packageinfo.nim index c54b97d..f00c59f 100644 --- a/src/nimblepkg/packageinfo.nim +++ b/src/nimblepkg/packageinfo.nim @@ -3,8 +3,7 @@ # Stdlib imports import system except TResult -import 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 @@ -278,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: @@ -311,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 @@ -542,6 +540,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/packageinstaller.nim b/src/nimblepkg/packageinstaller.nim index 125db93..4bdc921 100644 --- a/src/nimblepkg/packageinstaller.nim +++ b/src/nimblepkg/packageinstaller.nim @@ -3,7 +3,13 @@ import os, strutils, sets, json # Local imports -import cli, common, options, tools +import cli, options, tools + +when defined(windows): + import version + +when not declared(initHashSet) or not declared(toHashSet): + import common when defined(windows): # This is just for Win XP support. @@ -103,4 +109,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 diff --git a/src/nimblepkg/packageparser.nim b/src/nimblepkg/packageparser.nim old mode 100755 new mode 100644 index 5c034eb..9458074 --- 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 @@ -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) @@ -253,6 +256,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() @@ -482,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/src/nimblepkg/publish.nim b/src/nimblepkg/publish.nim index 333bbbd..f11b979 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 @@ -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) 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/src/nimblepkg/tools.nim b/src/nimblepkg/tools.nim index 2073154..a0e6a0e 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] == '"': @@ -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) @@ -148,6 +151,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 +167,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. diff --git a/src/nimblepkg/version.nim b/src/nimblepkg/version.nim index ae85e99..e4114f1 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) @@ -130,34 +135,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 - of "": - result.kind = verEq + result = VersionRange(kind: verEqEarlier) + of "", "==": + 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 +172,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 +207,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,21 +266,18 @@ 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, - 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 @@ -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)) @@ -314,8 +315,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/.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 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/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/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/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/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/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..972eb70 --- /dev/null +++ b/tests/issue678/packages.json @@ -0,0 +1,19 @@ +[ + { + "name": "issue678_dependency_1", + "url": "https://github.com/nimble-test/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/nimble-test/issue678?subdir=dependency_2", + "method": "git", + "tags": [ "test" ], + "description": "First level dependency of the issue678 package.", + "license": "MIT" + } +] 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/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 0000000..af3cc22 Binary files /dev/null and b/tests/nimbleVersionDefine/src/nimbleVersionDefine differ 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/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/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/recursive/recursive.nimble b/tests/recursive/recursive.nimble index ec09ab9..d6b155d 100644 --- a/tests/recursive/recursive.nimble +++ b/tests/recursive/recursive.nimble @@ -9,13 +9,17 @@ license = "BSD" requires "nim >= 0.12.1" +let + callNimble = getEnv("NIMBLE_TEST_BINARY_PATH") +doAssert callNimble.len != 0, "NIMBLE_TEST_BINARY_PATH not set" + task recurse, "Level 1": echo 1 - exec "nimble recurse2" + exec callNimble & " recurse2" task recurse2, "Level 2": echo 2 - exec "nimble recurse3" + exec callNimble & " recurse3" task recurse3, "Level 3": echo 3 diff --git a/tests/run/run.nimble b/tests/run/run.nimble new file mode 100644 index 0000000..055bc1e --- /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.19.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 1106998..446fecb 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, 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,10 @@ var rootDir = getCurrentDir().parentDir() var nimblePath = rootDir / "src" / addFileExt("nimble", ExeExt) 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) @@ -32,9 +36,9 @@ 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 & "\"")) + quotedArgs = quotedArgs.map((x: string) => x.quoteShell) let path {.used.} = getCurrentDir().parentDir() / "src" @@ -48,6 +52,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]= @@ -71,7 +76,42 @@ proc inLines(lines: seq[string], line: string): bool = for i in lines: if line.normalize in i.normalize: return true -test "caching works": +proc hasLineStartingWith(lines: seq[string], prefix: string): bool = + for line in lines: + if line.strip(trailing = false).startsWith(prefix): + 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 (_, exitCode) = execNimble("build") + check exitCode == QuitSuccess + +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 "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") check output.contains("0.1.0") @@ -82,7 +122,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 @@ -116,7 +156,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"]: @@ -127,19 +167,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 @@ -275,12 +318,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") @@ -431,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()) @@ -465,8 +517,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 @@ -585,6 +636,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": @@ -812,3 +869,177 @@ suite "Module tests": test "cli": 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": + const Src = "echo \"OK\"" + writeFile("src/issue581.nim", Src) + 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 + +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 + 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") + +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 '$1' is not defined in 'run' package." % + "blahblah".changeFileExt(ExeExt)) + + 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$1run$1$2 --debug check" % + [$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 "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") + 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 "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 = [ + "../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.splitFile.name} {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) + + removeDir(buildDir) + + var linesWithWarningsCount: uint = 0 + for file in filesToBuild: + let (output, exitCode) = execBuild(file) + check exitCode == QuitSuccess + linesWithWarningsCount += checkOutput(output) + check linesWithWarningsCount == 0