Compare commits

..

No commits in common. "master" and "stable" have entirely different histories.

44 changed files with 1018 additions and 1151 deletions

20
.gitignore vendored
View file

@ -6,15 +6,12 @@ nimcache/
# Absolute paths # Absolute paths
/src/babel /src/babel
/src/nimble /src/nimble
/tests/tester
# executables from test and build # executables from test and build
/nimble /nimble
src/nimblepkg/cli /tests/nimscript/nimscript
src/nimblepkg/packageinfo /tests/issue27/issue27
src/nimblepkg/packageparser
src/nimblepkg/reversedeps
src/nimblepkg/version
src/nimblepkg/download
# Windows executables # Windows executables
*.exe *.exe
@ -22,13 +19,4 @@ src/nimblepkg/download
# VCC compiler and linker artifacts # VCC compiler and linker artifacts
*.ilk *.ilk
*.pdb *.pdb
# Editors and IDEs project files and folders
.vscode
# VCS artifacts
*.orig
# Test procedure artifacts
nimble_*.nims

View file

@ -1,5 +1,4 @@
os: os:
- windows
- linux - linux
- osx - osx
@ -7,18 +6,21 @@ language: c
env: env:
- BRANCH=0.19.6 - BRANCH=0.19.6
- BRANCH=0.20.2 - BRANCH=#44cc5f6360c7ccc96c296948b2524bd2cdebf1f0
- BRANCH=1.0.6
# This is the latest working Nim version against which Nimble is being tested
- BRANCH=#ab525cc48abdbbbed1f772e58e9fe21474f70f07
cache: cache:
directories: directories:
- "$HOME/.choosenim" - "$HOME/.choosenim/toolchains/nim-0.19.6"
install: install:
- curl https://gist.github.com/genotrance/fb53504a4fba88bc5201d3783df5c522/raw/travis.sh -LsSf -o travis.sh - export CHOOSENIM_CHOOSE_VERSION=$BRANCH
- source travis.sh - |
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
script: script:
- cd tests - cd tests

View file

@ -3,34 +3,6 @@
# Nimble changelog # 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 ## 0.10.2 - 03/06/2019
This is a small release which avoids object variant changes that are now This is a small release which avoids object variant changes that are now

View file

@ -1,6 +1,6 @@
# Package # Package
version = "0.11.0" version = "0.10.2"
author = "Dominik Picheta" author = "Dominik Picheta"
description = "Nim package manager." description = "Nim package manager."
license = "BSD" license = "BSD"

View file

@ -15,7 +15,6 @@ Interested in learning **how to create a package**? Skip directly to that sectio
- [nimble install](#nimble-install) - [nimble install](#nimble-install)
- [nimble uninstall](#nimble-uninstall) - [nimble uninstall](#nimble-uninstall)
- [nimble build](#nimble-build) - [nimble build](#nimble-build)
- [nimble run](#nimble-run)
- [nimble c](#nimble-c) - [nimble c](#nimble-c)
- [nimble list](#nimble-list) - [nimble list](#nimble-list)
- [nimble search](#nimble-search) - [nimble search](#nimble-search)
@ -75,15 +74,10 @@ not need to install Nimble manually**.
But in case you still want to install Nimble manually, you can follow the But in case you still want to install Nimble manually, you can follow the
following instructions. following instructions.
There are two ways to install Nimble manually. Using ``koch`` and using Nimble There are two ways to install Nimble manually. The first is using the
itself. ``koch`` tool included in the Nim distribution and
### Using koch
The ``koch`` tool is included in the Nim distribution and
[repository](https://github.com/nim-lang/Nim/blob/devel/koch.nim). [repository](https://github.com/nim-lang/Nim/blob/devel/koch.nim).
Simply navigate to the location of your Nim installation and execute the Simply execute the following command to compile and install Nimble.
following command to compile and install Nimble.
``` ```
./koch nimble ./koch nimble
@ -92,19 +86,6 @@ following command to compile and install Nimble.
This will clone the Nimble repository, compile Nimble and copy it into This will clone the Nimble repository, compile Nimble and copy it into
Nim's bin directory. 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 ## Nimble usage
@ -172,13 +153,12 @@ example:
This is of course Git-specific, for Mercurial, use ``tip`` instead of ``head``. A 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``. 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 concrete version or a Instead of specifying a VCS branch, you may also specify a version range, for
version range, for example: example:
$ nimble install nimgame@0.5
$ nimble install nimgame@"> 0.5" $ nimble install nimgame@"> 0.5"
The latter command will install a version which is greater than ``0.5``. In this case a version which is greater than ``0.5`` will be installed.
If you don't specify a parameter and there is a ``package.nimble`` file in your 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 current working directory then Nimble will install the package residing in
@ -239,13 +219,6 @@ 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 information. The ``install`` command will build the package in release mode
instead. 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 ### nimble c
The ``c`` (or ``compile``, ``js``, ``cc``, ``cpp``) command can be used by The ``c`` (or ``compile``, ``js``, ``cc``, ``cpp``) command can be used by
@ -428,7 +401,6 @@ You can also specify multiple dependencies like so:
requires "nim >= 0.10.0", "foobar >= 0.1.0" requires "nim >= 0.10.0", "foobar >= 0.1.0"
requires "fizzbuzz >= 1.0" requires "fizzbuzz >= 1.0"
requires "https://github.com/user/pkg#5a54b5e"
``` ```
Nimble currently supports installation of packages from a local directory, a Nimble currently supports installation of packages from a local directory, a
@ -505,7 +477,7 @@ For a package named "foobar", the recommended project structure is the following
└── src └── src
└── foobar.nim # Imported via `import foobar` └── foobar.nim # Imported via `import foobar`
└── tests # Contains the tests └── tests # Contains the tests
├── config.nims ├── nim.cfg
├── tfoo1.nim # First test ├── tfoo1.nim # First test
└── tfoo2.nim # Second test └── tfoo2.nim # Second test
@ -813,19 +785,6 @@ To summarise, the steps for release are:
Once the new tag is in the remote repository, Nimble will be able to detect Once the new tag is in the remote repository, Nimble will be able to detect
the new version. 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
Publishing packages isn't a requirement. But doing so allows people to associate Publishing packages isn't a requirement. But doing so allows people to associate

View file

@ -3,13 +3,12 @@
import system except TResult import system except TResult
import os, tables, strtabs, json, algorithm, sets, uri, sugar, sequtils, osproc import httpclient, parseopt, os, osproc, pegs, tables, parseutils,
import std/options as std_opt strtabs, json, algorithm, sets, uri, sugar, sequtils
import strutils except toLower import strutils except toLower
from unicode import toLower from unicode import toLower
from sequtils import toSeq from sequtils import toSeq
from strformat import fmt
import nimblepkg/packageinfo, nimblepkg/version, nimblepkg/tools, import nimblepkg/packageinfo, nimblepkg/version, nimblepkg/tools,
nimblepkg/download, nimblepkg/config, nimblepkg/common, nimblepkg/download, nimblepkg/config, nimblepkg/common,
@ -96,7 +95,7 @@ proc copyFilesRec(origDir, currentDir, dest: string,
## Copies all the required files, skips files specified in the .nimble file ## Copies all the required files, skips files specified in the .nimble file
## (PackageInfo). ## (PackageInfo).
## Returns a list of filepaths to files which have been installed. ## Returns a list of filepaths to files which have been installed.
result = initHashSet[string]() result = initSet[string]()
let whitelistMode = let whitelistMode =
pkgInfo.installDirs.len != 0 or pkgInfo.installDirs.len != 0 or
pkgInfo.installFiles.len != 0 or pkgInfo.installFiles.len != 0 or
@ -157,8 +156,7 @@ proc processDeps(pkginfo: PackageInfo, options: Options): seq[PackageInfo] =
"dependencies for $1@$2" % [pkginfo.name, pkginfo.specialVersion], "dependencies for $1@$2" % [pkginfo.name, pkginfo.specialVersion],
priority = HighPriority) priority = HighPriority)
var pkgList {.global.}: seq[tuple[pkginfo: PackageInfo, meta: MetaData]] = @[] var pkgList = getInstalledPkgsMin(options.getPkgsDir(), options)
once: pkgList = getInstalledPkgsMin(options.getPkgsDir(), options)
var reverseDeps: seq[tuple[name, version: string]] = @[] var reverseDeps: seq[tuple[name, version: string]] = @[]
for dep in pkginfo.requires: for dep in pkginfo.requires:
if dep.name == "nimrod" or dep.name == "nim": if dep.name == "nimrod" or dep.name == "nim":
@ -201,13 +199,12 @@ proc processDeps(pkginfo: PackageInfo, options: Options): seq[PackageInfo] =
# in the path. # in the path.
var pkgsInPath: StringTableRef = newStringTable(modeCaseSensitive) var pkgsInPath: StringTableRef = newStringTable(modeCaseSensitive)
for pkgInfo in result: for pkgInfo in result:
let currentVer = pkgInfo.getConcreteVersion(options)
if pkgsInPath.hasKey(pkgInfo.name) and if pkgsInPath.hasKey(pkgInfo.name) and
pkgsInPath[pkgInfo.name] != currentVer: pkgsInPath[pkgInfo.name] != pkgInfo.version:
raise newException(NimbleError, raise newException(NimbleError,
"Cannot satisfy the dependency on $1 $2 and $1 $3" % "Cannot satisfy the dependency on $1 $2 and $1 $3" %
[pkgInfo.name, currentVer, pkgsInPath[pkgInfo.name]]) [pkgInfo.name, pkgInfo.version, pkgsInPath[pkgInfo.name]])
pkgsInPath[pkgInfo.name] = currentVer pkgsInPath[pkgInfo.name] = pkgInfo.version
# We add the reverse deps to the JSON file here because we don't want # We add the reverse deps to the JSON file here because we don't want
# them added if the above errorenous condition occurs # them added if the above errorenous condition occurs
@ -216,33 +213,16 @@ proc processDeps(pkginfo: PackageInfo, options: Options): seq[PackageInfo] =
for i in reverseDeps: for i in reverseDeps:
addRevDep(options.nimbleData, i, pkginfo) addRevDep(options.nimbleData, i, pkginfo)
proc buildFromDir( proc buildFromDir(pkgInfo: PackageInfo, paths: seq[string],
pkgInfo: PackageInfo, paths, args: seq[string], args: var seq[string]) =
options: Options
) =
## Builds a package as specified by ``pkgInfo``. ## 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: if pkgInfo.bin.len == 0:
raise newException(NimbleError, raise newException(NimbleError,
"Nothing to build. Did you specify a module to build using the" & "Nothing to build. Did you specify a module to build using the" &
" `bin` key in your .nimble file?") " `bin` key in your .nimble file?")
var args = args let realDir = pkgInfo.getRealDir()
let nimblePkgVersion = "-d:NimblePkgVersion=" & pkgInfo.version
for path in paths: args.add("--path:\"" & path & "\" ") for path in paths: args.add("--path:\"" & path & "\" ")
var binariesBuilt = 0
for bin in pkgInfo.bin: 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) & "\"" let outputOpt = "-o:\"" & pkgInfo.getOutputDir(bin) & "\""
display("Building", "$1/$2 using $3 backend" % display("Building", "$1/$2 using $3 backend" %
[pkginfo.name, bin, pkgInfo.backend], priority = HighPriority) [pkginfo.name, bin, pkgInfo.backend], priority = HighPriority)
@ -251,15 +231,10 @@ proc buildFromDir(
if not existsDir(outputDir): if not existsDir(outputDir):
createDir(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: try:
doCmd(cmd, showCmd = true) doCmd("\"" & getNimBin() & "\" $# --noBabelPath $# $# \"$#\"" %
binariesBuilt.inc() [pkgInfo.backend, join(args, " "), outputOpt,
realDir / bin.changeFileExt("nim")])
except NimbleError: except NimbleError:
let currentExc = (ref NimbleError)(getCurrentException()) let currentExc = (ref NimbleError)(getCurrentException())
let exc = newException(BuildFailed, "Build failed for package: " & let exc = newException(BuildFailed, "Build failed for package: " &
@ -269,14 +244,13 @@ proc buildFromDir(
exc.hint = hint exc.hint = hint
raise exc raise exc
if binariesBuilt == 0: proc buildFromDir(pkgInfo: PackageInfo, paths: seq[string], forRelease: bool) =
raiseNimbleError( var args: seq[string]
"No binaries built, did you specify a valid binary name?" if forRelease:
) args = @["-d:release"]
else:
# Handle post-`build` hook. args = @[]
cd realDir: # Make sure `execHook` executes the correct .nimble file. buildFromDir(pkgInfo, paths, args)
discard execHook(options, actionBuild, false)
proc removePkgDir(dir: string, options: Options) = proc removePkgDir(dir: string, options: Options) =
## Removes files belonging to the package in ``dir``. ## Removes files belonging to the package in ``dir``.
@ -355,7 +329,7 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options,
# Handle pre-`install` hook. # Handle pre-`install` hook.
if not options.depsOnly: if not options.depsOnly:
cd dir: # Make sure `execHook` executes the correct .nimble file. cd dir: # Make sure `execHook` executes the correct .nimble file.
if not execHook(options, actionInstall, true): if not execHook(options, true):
raise newException(NimbleError, "Pre-hook prevented further execution.") raise newException(NimbleError, "Pre-hook prevented further execution.")
var pkgInfo = getPkgInfo(dir, options) var pkgInfo = getPkgInfo(dir, options)
@ -382,11 +356,7 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options,
# if the build fails then the old package will still be installed. # if the build fails then the old package will still be installed.
if pkgInfo.bin.len > 0: if pkgInfo.bin.len > 0:
let paths = result.deps.map(dep => dep.getRealDir()) let paths = result.deps.map(dep => dep.getRealDir())
let flags = if options.action.typ in {actionInstall, actionPath, actionUninstall, actionDevelop}: buildFromDir(pkgInfo, paths, true)
options.action.passNimFlags
else:
@[]
buildFromDir(pkgInfo, paths, "-d:release" & flags, options)
let pkgDestDir = pkgInfo.getPkgDest(options) let pkgDestDir = pkgInfo.getPkgDest(options)
if existsDir(pkgDestDir) and existsFile(pkgDestDir / "nimblemeta.json"): if existsDir(pkgDestDir) and existsFile(pkgDestDir / "nimblemeta.json"):
@ -411,7 +381,7 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options,
createDir(pkgDestDir) createDir(pkgDestDir)
# Copy this package's files based on the preferences specified in PkgInfo. # Copy this package's files based on the preferences specified in PkgInfo.
var filesInstalled = initHashSet[string]() var filesInstalled = initSet[string]()
iterInstallFiles(realDir, pkgInfo, options, iterInstallFiles(realDir, pkgInfo, options,
proc (file: string) = proc (file: string) =
createDir(changeRoot(realDir, pkgDestDir, file.splitFile.dir)) createDir(changeRoot(realDir, pkgDestDir, file.splitFile.dir))
@ -424,7 +394,7 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options,
pkgInfo.myPath) pkgInfo.myPath)
filesInstalled.incl copyFileD(pkgInfo.myPath, dest) filesInstalled.incl copyFileD(pkgInfo.myPath, dest)
var binariesInstalled = initHashSet[string]() var binariesInstalled = initSet[string]()
if pkgInfo.bin.len > 0: if pkgInfo.bin.len > 0:
# Make sure ~/.nimble/bin directory is created. # Make sure ~/.nimble/bin directory is created.
createDir(binDir) createDir(binDir)
@ -469,7 +439,7 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options,
# executes the hook defined in the CWD, so we set it to where the package # executes the hook defined in the CWD, so we set it to where the package
# has been installed. # has been installed.
cd dest.splitFile.dir: cd dest.splitFile.dir:
discard execHook(options, actionInstall, false) discard execHook(options, false)
proc getDownloadInfo*(pv: PkgTuple, options: Options, proc getDownloadInfo*(pv: PkgTuple, options: Options,
doPrompt: bool): (DownloadMethod, string, doPrompt: bool): (DownloadMethod, string,
@ -536,12 +506,12 @@ proc build(options: Options) =
nimScriptHint(pkgInfo) nimScriptHint(pkgInfo)
let deps = processDeps(pkginfo, options) let deps = processDeps(pkginfo, options)
let paths = deps.map(dep => dep.getRealDir()) let paths = deps.map(dep => dep.getRealDir())
var args = options.getCompilationFlags() var args = options.action.compileOptions
buildFromDir(pkgInfo, paths, args, options) buildFromDir(pkgInfo, paths, args)
proc execBackend(pkgInfo: PackageInfo, options: Options) = proc execBackend(options: Options) =
let let
bin = options.getCompilationBinary(pkgInfo).get() bin = options.action.file
binDotNim = bin.addFileExt("nim") binDotNim = bin.addFileExt("nim")
if bin == "": if bin == "":
raise newException(NimbleError, "You need to specify a file.") raise newException(NimbleError, "You need to specify a file.")
@ -554,10 +524,9 @@ proc execBackend(pkgInfo: PackageInfo, options: Options) =
nimScriptHint(pkgInfo) nimScriptHint(pkgInfo)
let deps = processDeps(pkginfo, options) let deps = processDeps(pkginfo, options)
let nimblePkgVersion = "-d:NimblePkgVersion=" & pkgInfo.version
var args = "" var args = ""
for dep in deps: args.add("--path:\"" & dep.getRealDir() & "\" ") for dep in deps: args.add("--path:\"" & dep.getRealDir() & "\" ")
for option in options.getCompilationFlags(): for option in options.action.compileOptions:
args.add("\"" & option & "\" ") args.add("\"" & option & "\" ")
let backend = let backend =
@ -572,8 +541,8 @@ proc execBackend(pkgInfo: PackageInfo, options: Options) =
else: else:
display("Generating", ("documentation for $1 (from package $2) using $3 " & display("Generating", ("documentation for $1 (from package $2) using $3 " &
"backend") % [bin, pkgInfo.name, backend], priority = HighPriority) "backend") % [bin, pkgInfo.name, backend], priority = HighPriority)
doCmd("\"" & getNimBin() & "\" $# --noNimblePath $# $# \"$#\"" % doCmd("\"" & getNimBin() & "\" $# --noNimblePath $# \"$#\"" %
[backend, nimblePkgVersion, args, bin], showOutput = true) [backend, args, bin], showOutput = true)
display("Success:", "Execution finished", Success, HighPriority) display("Success:", "Execution finished", Success, HighPriority)
proc search(options: Options) = proc search(options: Options) =
@ -589,7 +558,7 @@ proc search(options: Options) =
var found = false var found = false
template onFound {.dirty.} = template onFound {.dirty.} =
echoPackage(pkg) echoPackage(pkg)
if pkg.alias.len == 0 and options.queryVersions: if options.queryVersions:
echoPackageVersions(pkg) echoPackageVersions(pkg)
echo(" ") echo(" ")
found = true found = true
@ -615,7 +584,7 @@ proc list(options: Options) =
let pkgList = getPackageList(options) let pkgList = getPackageList(options)
for pkg in pkgList: for pkg in pkgList:
echoPackage(pkg) echoPackage(pkg)
if pkg.alias.len == 0 and options.queryVersions: if options.queryVersions:
echoPackageVersions(pkg) echoPackageVersions(pkg)
echo(" ") echo(" ")
@ -729,11 +698,6 @@ proc dump(options: Options) =
echo "backend: ", p.backend.escape echo "backend: ", p.backend.escape
proc init(options: Options) = 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. # Determine the package name.
let pkgName = let pkgName =
if options.action.projName != "": if options.action.projName != "":
@ -768,20 +732,20 @@ proc init(options: Options) =
display("Using", "$# for new package name" % [pkgName.escape()], display("Using", "$# for new package name" % [pkgName.escape()],
priority = HighPriority) priority = HighPriority)
# Determine author by running an external command # Ask for package author
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 = proc getAuthor(): string =
if findExe("git") != "": if findExe("git") != "":
result = getAuthorWithCmd("git config --global user.name") 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)
elif findExe("hg") != "": elif findExe("hg") != "":
result = getAuthorWithCmd("hg config ui.username") 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)
if result.len == 0: if result.len == 0:
result = promptCustom(options, "Your name?", "Anonymous") result = promptCustom(options, "Your name?", "Anonymous")
let pkgAuthor = getAuthor() let pkgAuthor = getAuthor()
@ -868,17 +832,6 @@ js - Compile using JavaScript backend.""",
pkgRoot 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, display("Success:", "Package $# created successfully" % [pkgName], Success,
HighPriority) HighPriority)
@ -887,8 +840,7 @@ proc uninstall(options: Options) =
raise newException(NimbleError, raise newException(NimbleError,
"Please specify the package(s) to uninstall.") "Please specify the package(s) to uninstall.")
var pkgsToDelete: HashSet[PackageInfo] var pkgsToDelete: seq[PackageInfo] = @[]
pkgsToDelete.init()
# Do some verification. # Do some verification.
for pkgTup in options.action.packages: for pkgTup in options.action.packages:
display("Looking", "for $1 ($2)" % [pkgTup.name, $pkgTup.ver], display("Looking", "for $1 ($2)" % [pkgTup.name, $pkgTup.ver],
@ -899,33 +851,37 @@ proc uninstall(options: Options) =
raise newException(NimbleError, "Package not found") raise newException(NimbleError, "Package not found")
display("Checking", "reverse dependencies", priority = HighPriority) display("Checking", "reverse dependencies", priority = HighPriority)
var errors: seq[string] = @[]
for pkg in pkgList: for pkg in pkgList:
# Check whether any packages depend on the ones the user is trying to # Check whether any packages depend on the ones the user is trying to
# uninstall. # uninstall.
if options.uninstallRevDeps: if options.uninstallRevDeps:
getAllRevDeps(options, pkg, pkgsToDelete) getAllRevDeps(options, pkg, pkgsToDelete)
else: else:
let let revDeps = getRevDeps(options, pkg)
revDeps = getRevDeps(options, pkg)
var reason = "" var reason = ""
for revDep in revDeps: if revDeps.len == 1:
if reason.len != 0: reason.add ", " reason = "$1 ($2) depends on it" % [revDeps[0].name, $revDeps[0].ver]
reason.add("$1 ($2)" % [revDep.name, revDep.version])
if reason.len != 0:
reason &= " depend" & (if revDeps.len == 1: "s" else: "") & " on it"
if len(revDeps - pkgsToDelete) > 0:
display("Cannot", "uninstall $1 ($2) because $3" %
[pkgTup.name, pkg.specialVersion, reason], Warning, HighPriority)
else: else:
pkgsToDelete.incl pkg 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"
if pkgsToDelete.len == 0: if revDeps.len > 0:
raise newException(NimbleError, "Failed uninstall - no packages to delete") errors.add("Cannot uninstall $1 ($2) because $3" %
[pkgTup.name, pkg.specialVersion, reason])
else:
pkgsToDelete.add pkg
if pkgsToDelete.len == 0:
raise newException(NimbleError, "\n " & errors.join("\n "))
var pkgNames = "" var pkgNames = ""
for pkg in pkgsToDelete.items: for i in 0 ..< pkgsToDelete.len:
if pkgNames.len != 0: pkgNames.add ", " if i != 0: pkgNames.add ", "
let pkg = pkgsToDelete[i]
pkgNames.add("$1 ($2)" % [pkg.name, pkg.specialVersion]) pkgNames.add("$1 ($2)" % [pkg.name, pkg.specialVersion])
# Let's confirm that the user wants these packages removed. # Let's confirm that the user wants these packages removed.
@ -956,7 +912,7 @@ proc developFromDir(dir: string, options: Options) =
raiseNimbleError("Cannot develop dependencies only.") raiseNimbleError("Cannot develop dependencies only.")
cd dir: # Make sure `execHook` executes the correct .nimble file. cd dir: # Make sure `execHook` executes the correct .nimble file.
if not execHook(options, actionDevelop, true): if not execHook(options, true):
raise newException(NimbleError, "Pre-hook prevented further execution.") raise newException(NimbleError, "Pre-hook prevented further execution.")
var pkgInfo = getPkgInfo(dir, options) var pkgInfo = getPkgInfo(dir, options)
@ -1009,7 +965,7 @@ proc developFromDir(dir: string, options: Options) =
# Execute the post-develop hook. # Execute the post-develop hook.
cd dir: cd dir:
discard execHook(options, actionDevelop, false) discard execHook(options, false)
proc develop(options: Options) = proc develop(options: Options) =
if options.action.packages == @[]: if options.action.packages == @[]:
@ -1045,8 +1001,6 @@ proc develop(options: Options) =
proc test(options: Options) = proc test(options: Options) =
## Executes all tests starting with 't' in the ``tests`` directory. ## Executes all tests starting with 't' in the ``tests`` directory.
## Subdirectories are not walked. ## Subdirectories are not walked.
var pkgInfo = getPkgInfo(getCurrentDir(), options)
var var
files = toSeq(walkDir(getCurrentDir() / "tests")) files = toSeq(walkDir(getCurrentDir() / "tests"))
tests, failures: int tests, failures: int
@ -1063,10 +1017,10 @@ proc test(options: Options) =
var optsCopy = options.briefClone() var optsCopy = options.briefClone()
optsCopy.action = Action(typ: actionCompile) optsCopy.action = Action(typ: actionCompile)
optsCopy.action.file = file.path optsCopy.action.file = file.path
optsCopy.action.backend = pkgInfo.backend optsCopy.action.backend = "c"
optsCopy.getCompilationFlags() = @[] optsCopy.action.compileOptions = @[]
optsCopy.getCompilationFlags().add("-r") optsCopy.action.compileOptions.add("-r")
optsCopy.getCompilationFlags().add("--path:.") optsCopy.action.compileOptions.add("--path:.")
let let
binFileName = file.path.changeFileExt(ExeExt) binFileName = file.path.changeFileExt(ExeExt)
existsBefore = existsFile(binFileName) existsBefore = existsFile(binFileName)
@ -1074,21 +1028,17 @@ proc test(options: Options) =
if options.continueTestsOnFailure: if options.continueTestsOnFailure:
inc tests inc tests
try: try:
execBackend(pkgInfo, optsCopy) execBackend(optsCopy)
except NimbleError: except NimbleError:
inc failures inc failures
else: else:
execBackend(pkgInfo, optsCopy) execBackend(optsCopy)
let let
existsAfter = existsFile(binFileName) existsAfter = existsFile(binFileName)
canRemove = not existsBefore and existsAfter canRemove = not existsBefore and existsAfter
if canRemove: if canRemove:
try: removeFile(binFileName)
removeFile(binFileName)
except OSError as exc:
display("Warning:", "Failed to delete " & binFileName & ": " &
exc.msg, Warning, MediumPriority)
if failures == 0: if failures == 0:
display("Success:", "All tests passed", Success, HighPriority) display("Success:", "All tests passed", Success, HighPriority)
@ -1097,7 +1047,7 @@ proc test(options: Options) =
display("Error:", error, Error, HighPriority) display("Error:", error, Error, HighPriority)
proc check(options: Options) = proc check(options: Options) =
## Validates a package in the current working directory. ## Validates a package a in the current working directory.
let nimbleFile = findNimbleFile(getCurrentDir(), true) let nimbleFile = findNimbleFile(getCurrentDir(), true)
var error: ValidationError var error: ValidationError
var pkgInfo: PackageInfo var pkgInfo: PackageInfo
@ -1115,29 +1065,7 @@ proc check(options: Options) =
display("Failure:", "Validation failed", Error, HighPriority) display("Failure:", "Validation failed", Error, HighPriority)
quit(QuitFailure) quit(QuitFailure)
proc run(options: Options) = proc doAction(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: if options.showHelp:
writeHelp() writeHelp()
if options.showVersion: if options.showVersion:
@ -1148,10 +1076,6 @@ proc doAction(options: var Options) =
if not existsDir(options.getPkgsDir): if not existsDir(options.getPkgsDir):
createDir(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 case options.action.typ
of actionRefresh: of actionRefresh:
refresh(options) refresh(options)
@ -1177,11 +1101,8 @@ proc doAction(options: var Options) =
listPaths(options) listPaths(options)
of actionBuild: of actionBuild:
build(options) build(options)
of actionRun:
run(options)
of actionCompile, actionDoc: of actionCompile, actionDoc:
var pkgInfo = getPkgInfo(getCurrentDir(), options) execBackend(options)
execBackend(pkgInfo, options)
of actionInit: of actionInit:
init(options) init(options)
of actionPublish: of actionPublish:
@ -1198,7 +1119,7 @@ proc doAction(options: var Options) =
of actionNil: of actionNil:
assert false assert false
of actionCustom: of actionCustom:
if not execHook(options, actionCustom, true): if not execHook(options, true):
display("Warning", "Pre-hook prevented further execution.", Warning, display("Warning", "Pre-hook prevented further execution.", Warning,
HighPriority) HighPriority)
return return
@ -1207,24 +1128,21 @@ proc doAction(options: var Options) =
var execResult: ExecutionResult[bool] var execResult: ExecutionResult[bool]
if execCustom(options, execResult, failFast=not isPreDefined): if execCustom(options, execResult, failFast=not isPreDefined):
if execResult.hasTaskRequestedCommand(): if execResult.hasTaskRequestedCommand():
var options = execResult.getOptionsForCommand(options) doAction(execResult.getOptionsForCommand(options))
doAction(options)
else: else:
# If there is no task defined for the `test` task, we run the pre-defined # If there is no task defined for the `test` task, we run the pre-defined
# fallback logic. # fallback logic.
if isPreDefined: if isPreDefined:
test(options) test(options)
# Run the post hook for `test` in case it exists. # Run the post hook for `test` in case it exists.
discard execHook(options, actionCustom, false) discard execHook(options, false)
when isMainModule: when isMainModule:
var error = "" var error = ""
var hint = "" var hint = ""
var opt: Options
try: try:
opt = parseCmdLine() parseCmdLine().doAction()
opt.doAction()
except NimbleError: except NimbleError:
let currentExc = (ref NimbleError)(getCurrentException()) let currentExc = (ref NimbleError)(getCurrentException())
(error, hint) = getOutputInfo(currentExc) (error, hint) = getOutputInfo(currentExc)
@ -1232,9 +1150,7 @@ when isMainModule:
discard discard
finally: finally:
try: try:
let folder = getNimbleTempDir() removeDir(getNimbleTempDir())
if opt.shouldRemoveTmp(folder):
removeDir(folder)
except OSError: except OSError:
let msg = "Couldn't remove Nimble's temp dir" let msg = "Couldn't remove Nimble's temp dir"
display("Warning:", msg, Warning, MediumPriority) display("Warning:", msg, Warning, MediumPriority)

View file

@ -12,11 +12,11 @@
# - Bright for HighPriority. # - Bright for HighPriority.
# - Normal for MediumPriority. # - Normal for MediumPriority.
import terminal, sets, strutils import logging, terminal, sets, strutils, os
import version import ./common
when not declared(initHashSet): when defined(windows):
import common import winlean
type type
CLI* = ref object CLI* = ref object
@ -49,7 +49,7 @@ const
proc newCLI(): CLI = proc newCLI(): CLI =
result = CLI( result = CLI(
level: HighPriority, level: HighPriority,
warnings: initHashSet[(string, string)](), warnings: initSet[(string, string)](),
suppressionCount: 0, suppressionCount: 0,
showColor: true, showColor: true,
suppressMessages: false suppressMessages: false
@ -62,18 +62,8 @@ proc calculateCategoryOffset(category: string): int =
assert category.len <= longestCategory assert category.len <= longestCategory
return longestCategory - category.len 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, proc displayCategory(category: string, displayType: DisplayType,
priority: Priority) = priority: Priority) =
if isSuppressed(displayType):
return
# Calculate how much the `category` must be offset to align along a center # Calculate how much the `category` must be offset to align along a center
# line. # line.
let offset = calculateCategoryOffset(category) let offset = calculateCategoryOffset(category)
@ -90,9 +80,6 @@ proc displayCategory(category: string, displayType: DisplayType,
proc displayLine(category, line: string, displayType: DisplayType, proc displayLine(category, line: string, displayType: DisplayType,
priority: Priority) = priority: Priority) =
if isSuppressed(displayType):
return
displayCategory(category, displayType, priority) displayCategory(category, displayType, priority)
# Display the message. # Display the message.
@ -100,6 +87,12 @@ proc displayLine(category, line: string, displayType: DisplayType,
proc display*(category, msg: string, displayType = Message, proc display*(category, msg: string, displayType = Message,
priority = MediumPriority) = 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. # Multiple warnings containing the same messages should not be shown.
let warningPair = (category, msg) let warningPair = (category, msg)
if displayType == Warning: if displayType == Warning:

View file

@ -8,6 +8,7 @@ when not defined(nimscript):
import sets import sets
import version import version
export version.NimbleError # TODO: Surely there is a better way?
type type
BuildFailed* = object of NimbleError BuildFailed* = object of NimbleError
@ -22,8 +23,7 @@ when not defined(nimscript):
preHooks*: HashSet[string] preHooks*: HashSet[string]
name*: string name*: string
## The version specified in the .nimble file.Assuming info is non-minimal, ## 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 version*: string
specialVersion*: string ## Either `myVersion` or a special version such as #head. specialVersion*: string ## Either `myVersion` or a special version such as #head.
author*: string author*: string
@ -63,16 +63,4 @@ when not defined(nimscript):
return (error, hint) return (error, hint)
const const
nimbleVersion* = "0.11.0" nimbleVersion* = "0.10.2"
when not declared(initHashSet):
import sets
template initHashSet*[A](initialSize = 64): HashSet[A] =
initSet[A](initialSize)
when not declared(toHashSet):
import sets
template toHashSet*[A](keys: openArray[A]): HashSet[A] =
toSet(keys)

View file

@ -1,8 +1,8 @@
# Copyright (C) Dominik Picheta. All rights reserved. # Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info. # BSD License. Look at license.txt for more info.
import parsecfg, streams, strutils, os, tables, uri import parsecfg, streams, strutils, os, tables, Uri
import version, cli import tools, version, common, cli
type type
Config* = object Config* = object

View file

@ -2,15 +2,14 @@
# BSD License. Look at license.txt for more info. # BSD License. Look at license.txt for more info.
import parseutils, os, osproc, strutils, tables, pegs, uri import parseutils, os, osproc, strutils, tables, pegs, uri
import packageinfo, packageparser, version, tools, common, options, cli import packageinfo, packageparser, version, tools, common, options, cli
from algorithm import SortOrder, sorted
from sequtils import toSeq, filterIt, map
type type
DownloadMethod* {.pure.} = enum DownloadMethod* {.pure.} = enum
git = "git", hg = "hg" git = "git", hg = "hg"
proc getSpecificDir(meth: DownloadMethod): string {.used.} = proc getSpecificDir(meth: DownloadMethod): string =
case meth case meth
of DownloadMethod.git: of DownloadMethod.git:
".git" ".git"
@ -25,12 +24,11 @@ proc doCheckout(meth: DownloadMethod, downloadDir, branch: string) =
# clone has happened. Like in the case of git on Windows where it # clone has happened. Like in the case of git on Windows where it
# messes up the damn line endings. # messes up the damn line endings.
doCmd("git checkout --force " & branch) doCmd("git checkout --force " & branch)
doCmd("git submodule update --recursive")
of DownloadMethod.hg: of DownloadMethod.hg:
cd downloadDir: cd downloadDir:
doCmd("hg checkout " & branch) doCmd("hg checkout " & branch)
proc doPull(meth: DownloadMethod, downloadDir: string) {.used.} = proc doPull(meth: DownloadMethod, downloadDir: string) =
case meth case meth
of DownloadMethod.git: of DownloadMethod.git:
doCheckout(meth, downloadDir, "") doCheckout(meth, downloadDir, "")
@ -104,21 +102,14 @@ proc getTagsListRemote*(url: string, meth: DownloadMethod): seq[string] =
# http://stackoverflow.com/questions/2039150/show-tags-for-remote-hg-repository # http://stackoverflow.com/questions/2039150/show-tags-for-remote-hg-repository
raise newException(ValueError, "Hg doesn't support remote tag querying.") raise newException(ValueError, "Hg doesn't support remote tag querying.")
proc getVersionList*(tags: seq[string]): OrderedTable[Version, string] = proc getVersionList*(tags: seq[string]): Table[Version, string] =
## Return an ordered table of Version -> git tag label. Ordering is # Returns: TTable of version -> git tag name
## in descending order with the most recent version first. result = initTable[Version, string]()
let taggedVers: seq[tuple[ver: Version, tag: string]] = for tag in tags:
tags if tag != "":
.filterIt(it != "") let i = skipUntil(tag, Digits) # skip any chars before the version
.map(proc(s: string): tuple[ver: Version, tag: string] = # TODO: Better checking, tags can have any names. Add warnings and such.
# skip any chars before the version result[newVersion(tag[i .. tag.len-1])] = tag
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 = proc getDownloadMethod*(meth: string): DownloadMethod =
case meth case meth
@ -276,8 +267,14 @@ proc echoPackageVersions*(pkg: Package) =
try: try:
let versions = getTagsListRemote(pkg.url, downMethod).getVersionList() let versions = getTagsListRemote(pkg.url, downMethod).getVersionList()
if versions.len > 0: if versions.len > 0:
let sortedVersions = toSeq(values(versions)) var vstr = ""
echo(" versions: " & join(sortedVersions, ", ")) var i = 0
for v in values(versions):
if i != 0:
vstr.add(", ")
vstr.add(v)
i.inc
echo(" versions: " & vstr)
else: else:
echo(" versions: (No versions tagged in the remote repository)") echo(" versions: (No versions tagged in the remote repository)")
except OSError: except OSError:
@ -285,32 +282,3 @@ proc echoPackageVersions*(pkg: Package) =
of DownloadMethod.hg: of DownloadMethod.hg:
echo(" versions: (Remote tag retrieval not supported by " & echo(" versions: (Remote tag retrieval not supported by " &
pkg.downloadMethod & ")") 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!")

View file

@ -14,13 +14,6 @@ type
pkgNimDep: string pkgNimDep: string
pkgType: 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) = proc createPkgStructure*(info: PkgInitInfo, pkgRoot: string) =
# Create source directory # Create source directory
createDirD(pkgRoot / info.pkgSrcDir) createDirD(pkgRoot / info.pkgSrcDir)
@ -30,7 +23,7 @@ proc createPkgStructure*(info: PkgInitInfo, pkgRoot: string) =
case info.pkgType case info.pkgType
of "binary": of "binary":
let mainFile = pkgRoot / info.pkgSrcDir / info.pkgName.changeFileExt("nim") let mainFile = pkgRoot / info.pkgSrcDir / info.pkgName.changeFileExt("nim")
writeExampleIfNonExistent(mainFile, writeFile(mainFile,
""" """
# This is just an example to get you started. A typical binary package # This is just an example to get you started. A typical binary package
# uses this file as the main entry point of the application. # uses this file as the main entry point of the application.
@ -42,7 +35,7 @@ when isMainModule:
nimbleFileOptions.add("bin = @[\"$1\"]\n" % info.pkgName) nimbleFileOptions.add("bin = @[\"$1\"]\n" % info.pkgName)
of "library": of "library":
let mainFile = pkgRoot / info.pkgSrcDir / info.pkgName.changeFileExt("nim") let mainFile = pkgRoot / info.pkgSrcDir / info.pkgName.changeFileExt("nim")
writeExampleIfNonExistent(mainFile, writeFile(mainFile,
""" """
# This is just an example to get you started. A typical library package # 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 # exports the main API in this file. Note that you cannot rename this file
@ -57,7 +50,7 @@ proc add*(x, y: int): int =
createDirD(pkgRoot / info.pkgSrcDir / info.pkgName) createDirD(pkgRoot / info.pkgSrcDir / info.pkgName)
let submodule = pkgRoot / info.pkgSrcDir / info.pkgName / let submodule = pkgRoot / info.pkgSrcDir / info.pkgName /
"submodule".addFileExt("nim") "submodule".addFileExt("nim")
writeExampleIfNonExistent(submodule, writeFile(submodule,
""" """
# This is just an example to get you started. Users of your library will # 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 # import this file by writing ``import $1/submodule``. Feel free to rename or
@ -75,7 +68,7 @@ proc initSubmodule*(): Submodule =
) )
of "hybrid": of "hybrid":
let mainFile = pkgRoot / info.pkgSrcDir / info.pkgName.changeFileExt("nim") let mainFile = pkgRoot / info.pkgSrcDir / info.pkgName.changeFileExt("nim")
writeExampleIfNonExistent(mainFile, writeFile(mainFile,
""" """
# This is just an example to get you started. A typical hybrid package # This is just an example to get you started. A typical hybrid package
# uses this file as the main entry point of the application. # uses this file as the main entry point of the application.
@ -90,7 +83,7 @@ when isMainModule:
let pkgSubDir = pkgRoot / info.pkgSrcDir / info.pkgName & "pkg" let pkgSubDir = pkgRoot / info.pkgSrcDir / info.pkgName & "pkg"
createDirD(pkgSubDir) createDirD(pkgSubDir)
let submodule = pkgSubDir / "submodule".addFileExt("nim") let submodule = pkgSubDir / "submodule".addFileExt("nim")
writeExampleIfNonExistent(submodule, writeFile(submodule,
""" """
# This is just an example to get you started. Users of your hybrid library will # 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 # import this file by writing ``import $1pkg/submodule``. Feel free to rename or
@ -119,7 +112,7 @@ proc getWelcomeMessage*(): string = "Hello, World!"
) )
if info.pkgType == "library": if info.pkgType == "library":
writeExampleIfNonExistent(pkgTestPath / "test1".addFileExt("nim"), writeFile(pkgTestPath / "test1".addFileExt("nim"),
""" """
# This is just an example to get you started. You may wish to put all of your # 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` # tests into a single file, or separate them into multiple `test1`, `test2`
@ -136,7 +129,7 @@ test "can add":
""" % info.pkgName """ % info.pkgName
) )
else: else:
writeExampleIfNonExistent(pkgTestPath / "test1".addFileExt("nim"), writeFile(pkgTestPath / "test1".addFileExt("nim"),
""" """
# This is just an example to get you started. You may wish to put all of your # 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` # tests into a single file, or separate them into multiple `test1`, `test2`
@ -164,8 +157,8 @@ test "correct welcome":
writeFile(nimbleFile, """# Package writeFile(nimbleFile, """# Package
version = $# version = $#
author = "$#" author = $#
description = "$#" description = $#
license = $# license = $#
srcDir = $# srcDir = $#
$# $#
@ -175,7 +168,7 @@ $#
requires "nim >= $#" requires "nim >= $#"
""" % [ """ % [
info.pkgVersion.escape(), info.pkgAuthor.replace("\"", "\\\""), info.pkgDesc.replace("\"", "\\\""), info.pkgVersion.escape(), info.pkgAuthor.escape(), info.pkgDesc.escape(),
info.pkgLicense.escape(), info.pkgSrcDir.escape(), nimbleFileOptions, info.pkgLicense.escape(), info.pkgSrcDir.escape(), nimbleFileOptions,
pkgBackend, info.pkgNimDep pkgBackend, info.pkgNimDep
] ]

View file

@ -6,9 +6,6 @@
import system except getCommand, setCommand, switch, `--` import system except getCommand, setCommand, switch, `--`
import strformat, strutils, tables import strformat, strutils, tables
when not defined(nimscript):
import os
var var
packageName* = "" ## Set this to the package name. It packageName* = "" ## Set this to the package name. It
## is usually not required to do that, nims' filename is ## is usually not required to do that, nims' filename is
@ -17,7 +14,7 @@ var
author*: string ## The package's author. author*: string ## The package's author.
description*: string ## The package's description. description*: string ## The package's description.
license*: string ## The package's license. 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. binDir*: string ## The package's binary directory.
backend*: string ## The package's backend. backend*: string ## The package's backend.
@ -84,24 +81,25 @@ template `--`*(key: untyped) =
template printIfLen(varName) = template printIfLen(varName) =
if varName.len != 0: if varName.len != 0:
result &= astToStr(varName) & ": \"\"\"" & varName & "\"\"\"\n" iniOut &= astToStr(varName) & ": \"\"\"" & varName & "\"\"\"\n"
template printSeqIfLen(varName) = template printSeqIfLen(varName) =
if varName.len != 0: if varName.len != 0:
result &= astToStr(varName) & ": \"" & varName.join(", ") & "\"\n" iniOut &= astToStr(varName) & ": \"" & varName.join(", ") & "\"\n"
proc printPkgInfo(): string = proc printPkgInfo() =
if backend.len == 0: if backend.len == 0:
backend = "c" backend = "c"
result = "[Package]\n" var
iniOut = "[Package]\n"
if packageName.len != 0: if packageName.len != 0:
result &= "name: \"" & packageName & "\"\n" iniOut &= "name: \"" & packageName & "\"\n"
printIfLen version printIfLen version
printIfLen author printIfLen author
printIfLen description printIfLen description
printIfLen license printIfLen license
printIfLen srcDir printIfLen srcdir
printIfLen binDir printIfLen binDir
printIfLen backend printIfLen backend
@ -116,13 +114,14 @@ proc printPkgInfo(): string =
printSeqIfLen afterHooks printSeqIfLen afterHooks
if requiresData.len != 0: if requiresData.len != 0:
result &= "\n[Deps]\n" iniOut &= "\n[Deps]\n"
result &= &"requires: \"{requiresData.join(\", \")}\"\n" iniOut &= &"requires: \"{requiresData.join(\", \")}\"\n"
echo iniOut
proc onExit*() = proc onExit*() =
if "printPkgInfo".normalize in commandLineParams: if "printPkgInfo".normalize in commandLineParams:
if outFile.len != 0: printPkgInfo()
writeFile(outFile, printPkgInfo())
else: else:
var var
output = "" output = ""

View file

@ -1,17 +1,16 @@
# Copyright (C) Dominik Picheta. All rights reserved. # Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info. # BSD License. Look at license.txt for more info.
import os, strutils, sets import os, tables, strutils, sets
import packageparser, common, packageinfo, options, nimscriptwrapper, cli, import packageparser, common, packageinfo, options, nimscriptwrapper, cli
version
proc execHook*(options: Options, hookAction: ActionType, before: bool): bool = proc execHook*(options: Options, before: bool): bool =
## Returns whether to continue. ## Returns whether to continue.
result = true result = true
# For certain commands hooks should not be evaluated. # For certain commands hooks should not be evaluated.
if hookAction in noHookActions: if options.action.typ in noHookActions:
return return
var nimbleFile = "" var nimbleFile = ""
@ -21,8 +20,8 @@ proc execHook*(options: Options, hookAction: ActionType, before: bool): bool =
# PackageInfos are cached so we can read them as many times as we want. # PackageInfos are cached so we can read them as many times as we want.
let pkgInfo = getPkgInfoFromFile(nimbleFile, options) let pkgInfo = getPkgInfoFromFile(nimbleFile, options)
let actionName = let actionName =
if hookAction == actionCustom: options.action.command if options.action.typ == actionCustom: options.action.command
else: ($hookAction)[6 .. ^1] else: ($options.action.typ)[6 .. ^1]
let hookExists = let hookExists =
if before: actionName.normalize in pkgInfo.preHooks if before: actionName.normalize in pkgInfo.preHooks
else: actionName.normalize in pkgInfo.postHooks else: actionName.normalize in pkgInfo.postHooks
@ -58,7 +57,7 @@ proc execCustom*(options: Options,
HighPriority) HighPriority)
return return
if not execHook(options, actionCustom, false): if not execHook(options, false):
return return
return true return true

View file

@ -0,0 +1,718 @@
# 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..<graph.modules.len:
if graph.modules[i] != nil and
graph.modules[i].name.s == "nimscriptapi":
apiModule = graph.modules[i]
break
doAssert apiModule != nil
# Check whether an error has occurred.
if errCounter() > 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()

View file

@ -4,9 +4,9 @@
## Implements the new configuration system for Nimble. Uses Nim as a ## Implements the new configuration system for Nimble. Uses Nim as a
## scripting language. ## scripting language.
import hashes, json, os, strutils, tables, times, osproc, strtabs import common, version, options, packageinfo, cli, tools
import hashes, json, os, streams, strutils, strtabs,
import version, options, cli, tools tables, times, osproc, sets, pegs
type type
Flags = TableRef[string, seq[string]] Flags = TableRef[string, seq[string]]
@ -16,29 +16,13 @@ type
arguments*: seq[string] arguments*: seq[string]
flags*: Flags flags*: Flags
retVal*: T retVal*: T
stdout*: string
const const
internalCmd = "e" internalCmd = "e"
nimscriptApi = staticRead("nimscriptapi.nim") nimscriptApi = staticRead("nimscriptapi.nim")
printPkgInfo = "printPkgInfo"
proc isCustomTask(actionName: string, options: Options): bool = proc execNimscript(nimsFile, projectDir, actionName: string, options: Options,
options.action.typ == actionCustom and actionName != printPkgInfo live = true): tuple[output: string, exitCode: int] =
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 let
nimsFileCopied = projectDir / nimsFile.splitFile().name & "_" & getProcessId() & ".nims" nimsFileCopied = projectDir / nimsFile.splitFile().name & "_" & getProcessId() & ".nims"
outFile = getNimbleTempDir() & ".out" outFile = getNimbleTempDir() & ".out"
@ -53,40 +37,22 @@ proc execNimscript(
defer: defer:
# Only if copied in this invocation, allows recursive calls of nimble # Only if copied in this invocation, allows recursive calls of nimble
if not isScriptResultCopied and options.shouldRemoveTmp(nimsFileCopied): if not isScriptResultCopied:
nimsFileCopied.removeFile() nimsFileCopied.removeFile()
var cmd = ( let
"nim e $# -p:$# $# $# $#" % [ cmd = ("nim e --hints:off --verbosity:0 -p:" & (getTempDir() / "nimblecache").quoteShell &
"--hints:off --verbosity:0", " " & nimsFileCopied.quoteShell & " " & outFile.quoteShell & " " & actionName).strip()
(getTempDir() / "nimblecache").quoteShell,
nimsFileCopied.quoteShell,
outFile.quoteShell,
actionName
]
).strip()
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) displayDebug("Executing " & cmd)
if needsLiveOutput(actionName, options, isHook): if live:
result.exitCode = execCmd(cmd) result.exitCode = execCmd(cmd)
else: if outFile.fileExists():
# We want to capture any possible errors when parsing a .nimble result.output = outFile.readFile()
# 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() discard outFile.tryRemoveFile()
else:
result = execCmdEx(cmd, options = {poUsePath, poStdErrToStdOut})
proc getNimsFile(scriptName: string, options: Options): string = proc getNimsFile(scriptName: string, options: Options): string =
let let
@ -135,30 +101,27 @@ proc getIniFile*(scriptName: string, options: Options): string =
scriptName.getLastModificationTime() scriptName.getLastModificationTime()
if not isIniResultCached: if not isIniResultCached:
let (output, exitCode, stdout) = execNimscript( let
nimsFile, scriptName.parentDir(), printPkgInfo, options, isHook=false (output, exitCode) =
) execNimscript(nimsFile, scriptName.parentDir(), "printPkgInfo", options, live=false)
if exitCode == 0 and output.len != 0: if exitCode == 0 and output.len != 0:
result.writeFile(output) result.writeFile(output)
stdout.writeExecutionOutput()
else: else:
raise newException(NimbleError, stdout & "\nprintPkgInfo() failed") raise newException(NimbleError, output & "\nprintPkgInfo() failed")
proc execScript( proc execScript(scriptName, actionName: string, options: Options):
scriptName, actionName: string, options: Options, isHook: bool ExecutionResult[bool] =
): ExecutionResult[bool] = let
let nimsFile = getNimsFile(scriptName, options) nimsFile = getNimsFile(scriptName, options)
let (output, exitCode, stdout) = let
execNimscript( (output, exitCode) = execNimscript(nimsFile, scriptName.parentDir(), actionName, options)
nimsFile, scriptName.parentDir(), actionName, options, isHook
)
if exitCode != 0: if exitCode != 0:
let errMsg = let errMsg =
if stdout.len != 0: if output.len != 0:
stdout output
else: else:
"Exception raised during nimble script execution" "Exception raised during nimble script execution"
raise newException(NimbleError, errMsg) raise newException(NimbleError, errMsg)
@ -182,8 +145,6 @@ proc execScript(
result.flags[flag].add val.getStr() result.flags[flag].add val.getStr()
result.retVal = j{"retVal"}.getBool() result.retVal = j{"retVal"}.getBool()
stdout.writeExecutionOutput()
proc execTask*(scriptName, taskName: string, proc execTask*(scriptName, taskName: string,
options: Options): ExecutionResult[bool] = options: Options): ExecutionResult[bool] =
## Executes the specified task in the specified script. ## Executes the specified task in the specified script.
@ -192,7 +153,7 @@ proc execTask*(scriptName, taskName: string,
display("Executing", "task $# in $#" % [taskName, scriptName], display("Executing", "task $# in $#" % [taskName, scriptName],
priority = HighPriority) priority = HighPriority)
result = execScript(scriptName, taskName, options, isHook=false) result = execScript(scriptName, taskName, options)
proc execHook*(scriptName, actionName: string, before: bool, proc execHook*(scriptName, actionName: string, before: bool,
options: Options): ExecutionResult[bool] = options: Options): ExecutionResult[bool] =
@ -206,11 +167,11 @@ proc execHook*(scriptName, actionName: string, before: bool,
display("Attempting", "to execute hook $# in $#" % [hookName, scriptName], display("Attempting", "to execute hook $# in $#" % [hookName, scriptName],
priority = MediumPriority) priority = MediumPriority)
result = execScript(scriptName, hookName, options, isHook=true) result = execScript(scriptName, hookName, options)
proc hasTaskRequestedCommand*(execResult: ExecutionResult): bool = proc hasTaskRequestedCommand*(execResult: ExecutionResult): bool =
## Determines whether the last executed task used ``setCommand`` ## Determines whether the last executed task used ``setCommand``
return execResult.command != internalCmd return execResult.command != internalCmd
proc listTasks*(scriptName: string, options: Options) = proc listTasks*(scriptName: string, options: Options) =
discard execScript(scriptName, "", options, isHook=false) discard execScript(scriptName, "", options)

View file

@ -2,11 +2,9 @@
# BSD License. Look at license.txt for more info. # BSD License. Look at license.txt for more info.
import json, strutils, os, parseopt, strtabs, uri, tables, terminal import json, strutils, os, parseopt, strtabs, uri, tables, terminal
import sequtils, sugar
import std/options as std_opt
from httpclient import Proxy, newProxy from httpclient import Proxy, newProxy
import config, version, common, cli import config, version, tools, common, cli
type type
Options* = object Options* = object
@ -28,15 +26,12 @@ type
continueTestsOnFailure*: bool continueTestsOnFailure*: bool
## Whether packages' repos should always be downloaded with their history. ## Whether packages' repos should always be downloaded with their history.
forceFullClone*: bool forceFullClone*: bool
# Temporary storage of flags that have not been captured by any specific Action.
unknownFlags*: seq[(CmdLineKind, string, string)]
ActionType* = enum ActionType* = enum
actionNil, actionRefresh, actionInit, actionDump, actionPublish, actionNil, actionRefresh, actionInit, actionDump, actionPublish,
actionInstall, actionSearch, actionInstall, actionSearch,
actionList, actionBuild, actionPath, actionUninstall, actionCompile, actionList, actionBuild, actionPath, actionUninstall, actionCompile,
actionDoc, actionCustom, actionTasks, actionDevelop, actionCheck, actionDoc, actionCustom, actionTasks, actionDevelop, actionCheck
actionRun
Action* = object Action* = object
case typ*: ActionType case typ*: ActionType
@ -46,20 +41,14 @@ type
of actionInstall, actionPath, actionUninstall, actionDevelop: of actionInstall, actionPath, actionUninstall, actionDevelop:
packages*: seq[PkgTuple] # Optional only for actionInstall packages*: seq[PkgTuple] # Optional only for actionInstall
# and actionDevelop. # and actionDevelop.
passNimFlags*: seq[string]
of actionSearch: of actionSearch:
search*: seq[string] # Search string. search*: seq[string] # Search string.
of actionInit, actionDump: of actionInit, actionDump:
projName*: string projName*: string
vcsOption*: string
of actionCompile, actionDoc, actionBuild: of actionCompile, actionDoc, actionBuild:
file*: string file*: string
backend*: string backend*: string
compileOptions: seq[string] compileOptions*: seq[string]
of actionRun:
runFile: Option[string]
compileFlags: seq[string]
runFlags*: seq[string]
of actionCustom: of actionCustom:
command*: string command*: string
arguments*: seq[string] arguments*: seq[string]
@ -72,7 +61,6 @@ Usage: nimble COMMAND [opts]
Commands: Commands:
install [pkgname, ...] Installs a list of packages. install [pkgname, ...] Installs a list of packages.
[-d, --depsOnly] Install only dependencies. [-d, --depsOnly] Install only dependencies.
[-p, --passNim] Forward specified flag to compiler.
develop [pkgname, ...] Clones a list of packages for development. develop [pkgname, ...] Clones a list of packages for development.
Symlinks the cloned packages or any package Symlinks the cloned packages or any package
in the current working directory. in the current working directory.
@ -81,19 +69,12 @@ Commands:
init [pkgname] Initializes a new Nimble project in the init [pkgname] Initializes a new Nimble project in the
current directory or if a name is provided a current directory or if a name is provided a
new directory of the same name. 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. publish Publishes a package on nim-lang/packages.
The current working directory needs to be the The current working directory needs to be the
toplevel directory of the Nimble package. toplevel directory of the Nimble package.
uninstall [pkgname, ...] Uninstalls a list of packages. uninstall [pkgname, ...] Uninstalls a list of packages.
[-i, --inclDeps] Uninstall package and dependent package(s). [-i, --inclDeps] Uninstall package and dependent package(s).
build [opts, ...] [bin] Builds a package. build 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 c, cc, js [opts, ...] f.nim Builds a file inside a package. Passes options
to the Nim compiler. to the Nim compiler.
test Compiles and executes tests test Compiles and executes tests
@ -160,8 +141,6 @@ proc parseActionType*(action: string): ActionType =
result = actionPath result = actionPath
of "build": of "build":
result = actionBuild result = actionBuild
of "run":
result = actionRun
of "c", "compile", "js", "cpp", "cc": of "c", "compile", "js", "cpp", "cc":
result = actionCompile result = actionCompile
of "doc", "doc2": of "doc", "doc2":
@ -196,7 +175,6 @@ proc initAction*(options: var Options, key: string) =
case options.action.typ case options.action.typ
of actionInstall, actionPath, actionDevelop, actionUninstall: of actionInstall, actionPath, actionDevelop, actionUninstall:
options.action.packages = @[] options.action.packages = @[]
options.action.passNimFlags = @[]
of actionCompile, actionDoc, actionBuild: of actionCompile, actionDoc, actionBuild:
options.action.compileOptions = @[] options.action.compileOptions = @[]
options.action.file = "" options.action.file = ""
@ -204,11 +182,8 @@ proc initAction*(options: var Options, key: string) =
else: options.action.backend = keyNorm else: options.action.backend = keyNorm
of actionInit: of actionInit:
options.action.projName = "" options.action.projName = ""
options.action.vcsOption = ""
of actionDump: of actionDump:
options.action.projName = "" options.action.projName = ""
options.action.vcsOption = ""
options.forcePrompts = forcePromptYes
of actionRefresh: of actionRefresh:
options.action.optionalURL = "" options.action.optionalURL = ""
of actionSearch: of actionSearch:
@ -217,7 +192,7 @@ proc initAction*(options: var Options, key: string) =
options.action.command = key options.action.command = key
options.action.arguments = @[] options.action.arguments = @[]
options.action.flags = newStringTable() options.action.flags = newStringTable()
of actionPublish, actionList, actionTasks, actionCheck, actionRun, of actionPublish, actionList, actionTasks, actionCheck,
actionNil: discard actionNil: discard
proc prompt*(options: Options, question: string): bool = proc prompt*(options: Options, question: string): bool =
@ -266,12 +241,6 @@ proc parseCommand*(key: string, result: var Options) =
result.action = Action(typ: parseActionType(key)) result.action = Action(typ: parseActionType(key))
initAction(result, 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) = proc parseArgument*(key: string, result: var Options) =
case result.action.typ case result.action.typ
of actionNil: of actionNil:
@ -292,40 +261,23 @@ proc parseArgument*(key: string, result: var Options) =
result.action.search.add(key) result.action.search.add(key)
of actionInit, actionDump: of actionInit, actionDump:
if result.action.projName != "": if result.action.projName != "":
raise newException( raise newException(NimbleError,
NimbleError, "Can only perform this action on one package at a time." "Can only initialize one package at a time.")
)
result.action.projName = key result.action.projName = key
of actionCompile, actionDoc: of actionCompile, actionDoc:
result.action.file = key result.action.file = key
of actionList, actionPublish: of actionList, actionBuild, actionPublish:
result.showHelp = true result.showHelp = true
of actionBuild:
result.action.file = key
of actionRun:
result.setRunOptions(key, key, true)
of actionCustom: of actionCustom:
result.action.arguments.add(key) result.action.arguments.add(key)
else: else:
discard 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) = proc parseFlag*(flag, val: string, result: var Options, kind = cmdLongOption) =
var wasFlagHandled = true
let f = flag.normalize() let f = flag.normalize()
# Global flags. # Global flags.
var isGlobalFlag = true
case f case f
of "help", "h": result.showHelp = true of "help", "h": result.showHelp = true
of "version", "v": result.showVersion = true of "version", "v": result.showVersion = true
@ -336,64 +288,52 @@ proc parseFlag*(flag, val: string, result: var Options, kind = cmdLongOption) =
of "debug": result.verbosity = DebugPriority of "debug": result.verbosity = DebugPriority
of "nocolor": result.noColor = true of "nocolor": result.noColor = true
of "disablevalidation": result.disableValidation = true of "disablevalidation": result.disableValidation = true
else: isGlobalFlag = false
var wasFlagHandled = true
# Action-specific flags. # Action-specific flags.
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: else:
wasFlagHandled = false 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
else:
wasFlagHandled = false
if not wasFlagHandled and not isGlobalFlag: if not wasFlagHandled:
result.unknownFlags.add((kind, flag, val)) raise newException(NimbleError, "Unknown option: --" & flag)
proc initOptions*(): Options = proc initOptions*(): Options =
# Exported for choosenim result.action = Action(typ: actionNil)
Options( result.pkgInfoCache = newTable[string, PackageInfo]()
action: Action(typ: actionNil), result.nimbleDir = ""
pkgInfoCache: newTable[string, PackageInfo](), result.verbosity = HighPriority
verbosity: HighPriority, result.noColor = not isatty(stdout)
noColor: not isatty(stdout)
)
proc parseMisc(options: var Options) = proc parseMisc(options: var Options) =
# Load nimbledata.json # Load nimbledata.json
@ -408,29 +348,6 @@ proc parseMisc(options: var Options) =
else: else:
options.nimbleData = %{"reverseDeps": newJObject()} 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 = proc parseCmdLine*(): Options =
result = initOptions() result = initOptions()
@ -444,11 +361,9 @@ proc parseCmdLine*(): Options =
else: else:
parseArgument(key, result) parseArgument(key, result)
of cmdLongOption, cmdShortOption: of cmdLongOption, cmdShortOption:
parseFlag(key, val, result, kind) parseFlag(key, val, result, kind)
of cmdEnd: assert(false) # cannot happen of cmdEnd: assert(false) # cannot happen
handleUnknownFlags(result)
# Set verbosity level. # Set verbosity level.
setVerbosity(result.verbosity) setVerbosity(result.verbosity)
@ -464,11 +379,6 @@ proc parseCmdLine*(): Options =
if result.action.typ == actionNil and not result.showVersion: if result.action.typ == actionNil and not result.showVersion:
result.showHelp = true 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 = proc getProxy*(options: Options): Proxy =
## Returns ``nil`` if no proxy is specified. ## Returns ``nil`` if no proxy is specified.
var url = "" var url = ""
@ -508,44 +418,3 @@ proc briefClone*(options: Options): Options =
newOptions.forcePrompts = options.forcePrompts newOptions.forcePrompts = options.forcePrompts
newOptions.pkgInfoCache = options.pkgInfoCache newOptions.pkgInfoCache = options.pkgInfoCache
return newOptions 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

View file

@ -3,7 +3,8 @@
# Stdlib imports # Stdlib imports
import system except TResult import system except TResult
import hashes, json, strutils, os, sets, tables, httpclient import parsecfg, json, streams, strutils, parseutils, os, sets, tables
import httpclient
# Local imports # Local imports
import version, tools, common, options, cli, config import version, tools, common, options, cli, config
@ -277,7 +278,7 @@ proc getPackage*(pkg: string, options: Options, resPkg: var Package): bool =
proc getPackageList*(options: Options): seq[Package] = proc getPackageList*(options: Options): seq[Package] =
## Returns the list of packages found in the downloaded packages.json files. ## Returns the list of packages found in the downloaded packages.json files.
result = @[] result = @[]
var namesAdded = initHashSet[string]() var namesAdded = initSet[string]()
for name, list in options.config.packageLists: for name, list in options.config.packageLists:
let packages = readPackageList(name, options) let packages = readPackageList(name, options)
for p in packages: for p in packages:
@ -310,6 +311,7 @@ proc findNimbleFile*(dir: string; error: bool): string =
if result.splitFile.ext == ".nimble-link": if result.splitFile.ext == ".nimble-link":
# Return the path of the real .nimble file. # Return the path of the real .nimble file.
let nimbleLinkPath = result
result = readNimbleLink(result).nimbleFilePath result = readNimbleLink(result).nimbleFilePath
if not fileExists(result): if not fileExists(result):
let msg = "The .nimble-link file is pointing to a missing file: " & result let msg = "The .nimble-link file is pointing to a missing file: " & result
@ -540,11 +542,6 @@ proc `==`*(pkg1: PackageInfo, pkg2: PackageInfo): bool =
if pkg1.name == pkg2.name and pkg1.myPath == pkg2.myPath: if pkg1.name == pkg2.name and pkg1.myPath == pkg2.myPath:
return true return true
proc hash*(x: PackageInfo): Hash =
var h: Hash = 0
h = h !& hash(x.myPath)
result = !$h
when isMainModule: when isMainModule:
doAssert getNameVersion("/home/user/.nimble/libs/packagea-0.1") == doAssert getNameVersion("/home/user/.nimble/libs/packagea-0.1") ==
("packagea", "0.1") ("packagea", "0.1")

View file

@ -3,13 +3,7 @@
import os, strutils, sets, json import os, strutils, sets, json
# Local imports # Local imports
import cli, options, tools import cli, common, options, tools
when defined(windows):
import version
when not declared(initHashSet) or not declared(toHashSet):
import common
when defined(windows): when defined(windows):
# This is just for Win XP support. # This is just for Win XP support.
@ -109,4 +103,4 @@ proc saveNimbleMeta*(pkgDestDir, pkgDir, vcsRevision, nimbleLinkPath: string) =
## pkgDir - The directory where the original package files are. ## pkgDir - The directory where the original package files are.
## For example: ~/projects/jester/ ## For example: ~/projects/jester/
saveNimbleMeta(pkgDestDir, "file://" & pkgDir, vcsRevision, saveNimbleMeta(pkgDestDir, "file://" & pkgDir, vcsRevision,
toHashSet[string]([nimbleLinkPath]), initHashSet[string](), true) toSet[string]([nimbleLinkPath]), initSet[string](), true)

View file

@ -1,6 +1,6 @@
# Copyright (C) Dominik Picheta. All rights reserved. # Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info. # BSD License. Look at license.txt for more info.
import parsecfg, sets, streams, strutils, os, tables, sugar import parsecfg, json, sets, streams, strutils, parseutils, os, tables, sugar
from sequtils import apply, map from sequtils import apply, map
import version, tools, common, nimscriptwrapper, options, packageinfo, cli import version, tools, common, nimscriptwrapper, options, packageinfo, cli
@ -212,10 +212,7 @@ proc multiSplit(s: string): seq[string] =
result.del(i) result.del(i)
# Huh, nothing to return? Return given input. # Huh, nothing to return? Return given input.
if len(result) < 1: if len(result) < 1:
if s.strip().len != 0: return @[s]
return @[s]
else:
return @[]
proc readPackageInfoFromNimble(path: string; result: var PackageInfo) = proc readPackageInfoFromNimble(path: string; result: var PackageInfo) =
var fs = newFileStream(path, fmRead) var fs = newFileStream(path, fmRead)
@ -487,15 +484,6 @@ proc toFullInfo*(pkg: PackageInfo, options: Options): PackageInfo =
else: else:
return pkg 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: when isMainModule:
validatePackageName("foo_bar") validatePackageName("foo_bar")
validatePackageName("f_oo_b_a_r") validatePackageName("f_oo_b_a_r")

View file

@ -5,8 +5,8 @@
## nim-lang/packages automatically. ## nim-lang/packages automatically.
import system except TResult import system except TResult
import httpclient, strutils, json, os, browsers, times, uri import httpclient, base64, strutils, rdstdin, json, os, browsers, times, uri
import version, tools, common, cli, config, options import tools, common, cli, config, options
type type
Auth = object Auth = object
@ -213,10 +213,7 @@ proc publish*(p: PackageInfo, o: Options) =
url = promptCustom("Github URL of " & p.name & "?", "") url = promptCustom("Github URL of " & p.name & "?", "")
if url.len == 0: userAborted() if url.len == 0: userAborted()
let tags = promptCustom( let tags = promptCustom("Whitespace separated list of tags?", "")
"Whitespace separated list of tags? (For example: web library wrapper)",
""
)
cd pkgsDir: cd pkgsDir:
editJson(p, url, tags, downloadMethod) editJson(p, url, tags, downloadMethod)

View file

@ -1,7 +1,7 @@
# Copyright (C) Dominik Picheta. All rights reserved. # Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info. # BSD License. Look at license.txt for more info.
import os, json, sets import os, json
import options, common, version, download, packageinfo import options, common, version, download, packageinfo
@ -58,7 +58,7 @@ proc removeRevDep*(nimbleData: JsonNode, pkg: PackageInfo) =
newData[key] = newVal newData[key] = newVal
nimbleData["reverseDeps"] = newData nimbleData["reverseDeps"] = newData
proc getRevDepTups*(options: Options, pkg: PackageInfo): seq[PkgTuple] = proc getRevDeps*(options: Options, pkg: PackageInfo): seq[PkgTuple] =
## Returns a list of *currently installed* reverse dependencies for `pkg`. ## Returns a list of *currently installed* reverse dependencies for `pkg`.
result = @[] result = @[]
let thisPkgsDep = let thisPkgsDep =
@ -76,25 +76,18 @@ proc getRevDepTups*(options: Options, pkg: PackageInfo): seq[PkgTuple] =
result.add(pkgTup) result.add(pkgTup)
proc getRevDeps*(options: Options, pkg: PackageInfo): HashSet[PackageInfo] = proc getAllRevDeps*(options: Options, pkg: PackageInfo, result: var seq[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: if pkg in result:
return return
let installedPkgs = getInstalledPkgsMin(options.getPkgsDir(), options) let installedPkgs = getInstalledPkgsMin(options.getPkgsDir(), options)
for rdepTup in getRevDepTups(options, pkg): for rdepTup in getRevDeps(options, pkg):
for rdepInfo in findAllPkgs(installedPkgs, rdepTup): for rdepInfo in findAllPkgs(installedPkgs, rdepTup):
if rdepInfo in result: if rdepInfo in result:
continue continue
getAllRevDeps(options, rdepInfo, result) getAllRevDeps(options, rdepInfo, result)
result.incl pkg result.add pkg
when isMainModule: when isMainModule:
var nimbleData = %{"reverseDeps": newJObject()} var nimbleData = %{"reverseDeps": newJObject()}

View file

@ -3,7 +3,7 @@
# #
# Various miscellaneous utility functions reside here. # Various miscellaneous utility functions reside here.
import osproc, pegs, strutils, os, uri, sets, json, parseutils import osproc, pegs, strutils, os, uri, sets, json, parseutils
import version, cli import version, common, cli
proc extractBin(cmd: string): string = proc extractBin(cmd: string): string =
if cmd[0] == '"': if cmd[0] == '"':
@ -11,7 +11,7 @@ proc extractBin(cmd: string): string =
else: else:
return cmd.split(' ')[0] return cmd.split(' ')[0]
proc doCmd*(cmd: string, showOutput = false, showCmd = false) = proc doCmd*(cmd: string, showOutput = false) =
let bin = extractBin(cmd) let bin = extractBin(cmd)
if findExe(bin) == "": if findExe(bin) == "":
raise newException(NimbleError, "'" & bin & "' not in PATH.") raise newException(NimbleError, "'" & bin & "' not in PATH.")
@ -20,10 +20,7 @@ proc doCmd*(cmd: string, showOutput = false, showCmd = false) =
stdout.flushFile() stdout.flushFile()
stderr.flushFile() stderr.flushFile()
if showCmd: displayDebug("Executing", cmd)
display("Executing", cmd, priority = MediumPriority)
else:
displayDebug("Executing", cmd)
if showOutput: if showOutput:
let exitCode = execCmd(cmd) let exitCode = execCmd(cmd)
displayDebug("Finished", "with exit code " & $exitCode) displayDebug("Finished", "with exit code " & $exitCode)

View file

@ -93,11 +93,6 @@ proc `==`*(ver: Version, ver2: Version): bool =
else: else:
return false return false
proc cmp*(a, b: Version): int =
if a < b: -1
elif a > b: 1
else: 0
proc `<=`*(ver: Version, ver2: Version): bool = proc `<=`*(ver: Version, ver2: Version): bool =
return (ver == ver2) or (ver < ver2) return (ver == ver2) or (ver < ver2)
@ -147,7 +142,7 @@ proc makeRange*(version: string, op: string): VersionRange =
result = VersionRange(kind: verEqLater) result = VersionRange(kind: verEqLater)
of "<=": of "<=":
result = VersionRange(kind: verEqEarlier) result = VersionRange(kind: verEqEarlier)
of "", "==": of "":
result = VersionRange(kind: verEq) result = VersionRange(kind: verEq)
else: else:
raise newException(ParseVersionError, "Invalid operator: " & op) raise newException(ParseVersionError, "Invalid operator: " & op)
@ -277,7 +272,7 @@ proc newVREq*(ver: string): VersionRange =
result.ver = newVersion(ver) result.ver = newVersion(ver)
proc findLatest*(verRange: VersionRange, proc findLatest*(verRange: VersionRange,
versions: OrderedTable[Version, string]): tuple[ver: Version, tag: string] = versions: Table[Version, string]): tuple[ver: Version, tag: string] =
result = (newVersion(""), "") result = (newVersion(""), "")
for ver, tag in versions: for ver, tag in versions:
if not withinRange(ver, verRange): continue if not withinRange(ver, verRange): continue
@ -298,10 +293,9 @@ when isMainModule:
doAssert(newVersion("0.1.0") <= newVersion("0.1")) doAssert(newVersion("0.1.0") <= newVersion("0.1"))
var inter1 = parseVersionRange(">= 1.0 & <= 1.5") var inter1 = parseVersionRange(">= 1.0 & <= 1.5")
doAssert(inter1.kind == verIntersect) doAssert inter1.kind == verIntersect
var inter2 = parseVersionRange("1.0") var inter2 = parseVersionRange("1.0")
doAssert(inter2.kind == verEq) doAssert(inter2.kind == verEq)
doAssert(parseVersionRange("== 3.4.2") == parseVersionRange("3.4.2"))
doAssert(not withinRange(newVersion("1.5.1"), inter1)) 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)) doAssert(withinRange(newVersion("1.0.2.3.4.5.6.7.8.9.10.11.12"), inter1))
@ -315,11 +309,8 @@ when isMainModule:
doAssert(newVersion("") < newVersion("1.0.0")) doAssert(newVersion("") < newVersion("1.0.0"))
doAssert(newVersion("") < newVersion("0.1.0")) doAssert(newVersion("") < newVersion("0.1.0"))
var versions = toOrderedTable[Version, string]({ var versions = toTable[Version, string]({newVersion("0.1.1"): "v0.1.1",
newVersion("0.1.1"): "v0.1.1", newVersion("0.2.3"): "v0.2.3", newVersion("0.5"): "v0.5"})
newVersion("0.2.3"): "v0.2.3",
newVersion("0.5"): "v0.5"
})
doAssert findLatest(parseVersionRange(">= 0.1 & <= 0.4"), versions) == doAssert findLatest(parseVersionRange(">= 0.1 & <= 0.4"), versions) ==
(newVersion("0.2.3"), "v0.2.3") (newVersion("0.2.3"), "v0.2.3")

8
tests/.gitignore vendored
View file

@ -1,10 +1,6 @@
tester
/nimble-test
/buildDir
/binaryPackage/v1/binaryPackage /binaryPackage/v1/binaryPackage
/binaryPackage/v2/binaryPackage /binaryPackage/v2/binaryPackage
/develop/dependent/src/dependent /develop/dependent/src/dependent
/issue27/issue27
/issue206/issue/issue206bin /issue206/issue/issue206bin
/issue289/issue289 /issue289/issue289
/issue428/nimbleDir/ /issue428/nimbleDir/
@ -17,7 +13,3 @@ tester
/testCommand/testsPass/tests/one /testCommand/testsPass/tests/one
/testCommand/testsPass/tests/three /testCommand/testsPass/tests/three
/testCommand/testsPass/tests/two /testCommand/testsPass/tests/two
/nimscript/nimscript
/packageStructure/validBinary/y
/testCommand/testsFail/tests/t2
/passNimFlags/passNimFlags

View file

@ -1,13 +0,0 @@
# 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"

View file

@ -1,15 +0,0 @@
# 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"

View file

@ -1,7 +0,0 @@
# 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

View file

@ -1,14 +0,0 @@
# 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"

View file

@ -1,5 +0,0 @@
# 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!")

View file

@ -1,16 +0,0 @@
# 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"

View file

@ -1,12 +0,0 @@
# 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"

View file

@ -1,19 +0,0 @@
[
{
"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"
}
]

View file

@ -1,17 +0,0 @@
# 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"

View file

@ -1,7 +0,0 @@
# 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

View file

@ -1,14 +0,0 @@
# 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"

View file

@ -1,3 +0,0 @@
when isMainModule:
const NimblePkgVersion {.strdefine.} = "Unknown"
echo(NimblePkgVersion)

View file

@ -54,9 +54,3 @@ before install:
after install: after install:
echo("After PkgDir: ", getPkgDir()) echo("After PkgDir: ", getPkgDir())
before build:
echo("Before build")
after build:
echo("After build")

View file

@ -1 +0,0 @@
when not defined(passNimIsWorking): {.error: "-d:passNimIsWorking wasn't passed to the compiler"}

View file

@ -1,11 +0,0 @@
# Package
version = "0.1.0"
author = "SolitudeSF"
description = "Test nimble install flag forwarding"
license = "BSD"
bin = @["passNimFlags"]
# Dependencies
requires "nim >= 0.13.0"

View file

@ -9,17 +9,13 @@ license = "BSD"
requires "nim >= 0.12.1" 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": task recurse, "Level 1":
echo 1 echo 1
exec callNimble & " recurse2" exec "../../src/nimble recurse2"
task recurse2, "Level 2": task recurse2, "Level 2":
echo 2 echo 2
exec callNimble & " recurse3" exec "../../src/nimble recurse3"
task recurse3, "Level 3": task recurse3, "Level 3":
echo 3 echo 3

View file

@ -1,14 +0,0 @@
# 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"

View file

@ -1,4 +0,0 @@
import os
when isMainModule:
echo("Testing `nimble run`: ", commandLineParams())

View file

@ -1,6 +1,6 @@
# Copyright (C) Dominik Picheta. All rights reserved. # Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info. # BSD License. Look at license.txt for more info.
import osproc, unittest, strutils, os, sequtils, sugar, strformat import osproc, streams, unittest, strutils, os, sequtils, sugar
# TODO: Each test should start off with a clean slate. Currently installed # TODO: Each test should start off with a clean slate. Currently installed
# packages are shared between each test which causes a multitude of issues # packages are shared between each test which causes a multitude of issues
@ -10,10 +10,6 @@ var rootDir = getCurrentDir().parentDir()
var nimblePath = rootDir / "src" / addFileExt("nimble", ExeExt) var nimblePath = rootDir / "src" / addFileExt("nimble", ExeExt)
var installDir = rootDir / "tests" / "nimbleDir" var installDir = rootDir / "tests" / "nimbleDir"
const path = "../src/nimble" const path = "../src/nimble"
const stringNotFound = -1
# Set env var to propagate nimble binary path
putEnv("NIMBLE_TEST_BINARY_PATH", nimblePath)
# Clear nimble dir. # Clear nimble dir.
removeDir(installDir) removeDir(installDir)
@ -36,9 +32,9 @@ template cd*(dir: string, body: untyped) =
proc execNimble(args: varargs[string]): tuple[output: string, exitCode: int] = proc execNimble(args: varargs[string]): tuple[output: string, exitCode: int] =
var quotedArgs = @args var quotedArgs = @args
quotedArgs.insert("--nimbleDir:" & installDir)
quotedArgs.insert(nimblePath) quotedArgs.insert(nimblePath)
quotedArgs = quotedArgs.map((x: string) => x.quoteShell) quotedArgs.add("--nimbleDir:" & installDir)
quotedArgs = quotedArgs.map((x: string) => ("\"" & x & "\""))
let path {.used.} = getCurrentDir().parentDir() / "src" let path {.used.} = getCurrentDir().parentDir() / "src"
@ -52,7 +48,6 @@ proc execNimble(args: varargs[string]): tuple[output: string, exitCode: int] =
cmd = "DYLD_LIBRARY_PATH=/usr/local/opt/openssl@1.1/lib " & cmd cmd = "DYLD_LIBRARY_PATH=/usr/local/opt/openssl@1.1/lib " & cmd
result = execCmdEx(cmd) result = execCmdEx(cmd)
checkpoint(cmd)
checkpoint(result.output) checkpoint(result.output)
proc execNimbleYes(args: varargs[string]): tuple[output: string, exitCode: int]= proc execNimbleYes(args: varargs[string]): tuple[output: string, exitCode: int]=
@ -76,41 +71,6 @@ proc inLines(lines: seq[string], line: string): bool =
for i in lines: for i in lines:
if line.normalize in i.normalize: return true if line.normalize in i.normalize: return true
proc hasLineStartingWith(lines: seq[string], prefix: string): bool =
for line in lines:
if line.strip(trailing = false).startsWith(prefix):
return true
return false
test "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": test "caching of nims and ini detects changes":
cd "caching": cd "caching":
var (output, exitCode) = execNimble("dump") var (output, exitCode) = execNimble("dump")
@ -156,7 +116,7 @@ test "can validate package structure (#144)":
let (output, exitCode) = execNimble(["install", "-y"]) let (output, exitCode) = execNimble(["install", "-y"])
check exitCode == QuitSuccess check exitCode == QuitSuccess
let lines = output.strip.processOutput() let lines = output.strip.processOutput()
check(not lines.hasLineStartingWith("Warning:")) check(not inLines(lines, "warning"))
# Test that warnings are produced for the incorrectly structured packages. # Test that warnings are produced for the incorrectly structured packages.
for package in ["x", "y", "z"]: for package in ["x", "y", "z"]:
@ -167,22 +127,19 @@ test "can validate package structure (#144)":
checkpoint(output) checkpoint(output)
case package case package
of "x": of "x":
check lines.hasLineStartingWith( check inLines(lines, "Package 'x' has an incorrect structure. It should" &
"Warning: Package 'x' has an incorrect structure. It should" & " contain a single directory hierarchy for source files," &
" contain a single directory hierarchy for source files," & " named 'x', but file 'foobar.nim' is in a directory named" &
" named 'x', but file 'foobar.nim' is in a directory named" & " 'incorrect' instead.")
" 'incorrect' instead.")
of "y": of "y":
check lines.hasLineStartingWith( check inLines(lines, "Package 'y' has an incorrect structure. It should" &
"Warning: Package 'y' has an incorrect structure. It should" & " contain a single directory hierarchy for source files," &
" contain a single directory hierarchy for source files," & " named 'ypkg', but file 'foobar.nim' is in a directory named" &
" named 'ypkg', but file 'foobar.nim' is in a directory named" & " 'yWrong' instead.")
" 'yWrong' instead.")
of "z": of "z":
check lines.hasLineStartingWith( check inLines(lines, "Package 'z' has an incorrect structure. The top level" &
"Warning: Package 'z' has an incorrect structure. The top level" & " of the package source directory should contain at most one module," &
" of the package source directory should contain at most one module," & " named 'z.nim', but a file named 'incorrect.nim' was found.")
" named 'z.nim', but a file named 'incorrect.nim' was found.")
else: else:
assert false assert false
@ -318,21 +275,12 @@ suite "nimscript":
cd "nimscript": cd "nimscript":
let (output, exitCode) = execNimble(["install", "-y"]) let (output, exitCode) = execNimble(["install", "-y"])
check exitCode == QuitSuccess check exitCode == QuitSuccess
check output.contains("Before build")
check output.contains("After build")
let lines = output.strip.processOutput() let lines = output.strip.processOutput()
check lines[0].startsWith("Before PkgDir:") check lines[0].startsWith("Before PkgDir:")
check lines[0].endsWith("tests" / "nimscript") check lines[0].endsWith("tests" / "nimscript")
check lines[^1].startsWith("After PkgDir:") check lines[^1].startsWith("After PkgDir:")
check lines[^1].endsWith("tests" / "nimbleDir" / "pkgs" / "nimscript-0.1.0") check lines[^1].endsWith("tests" / "nimbleDir" / "pkgs" / "nimscript-0.1.0")
test "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": test "can execute nimscript tasks":
cd "nimscript": cd "nimscript":
let (output, exitCode) = execNimble("--verbose", "work") let (output, exitCode) = execNimble("--verbose", "work")
@ -483,17 +431,17 @@ test "issue #349":
] ]
proc checkName(name: string) = proc checkName(name: string) =
when defined(windows):
if name.toLowerAscii() in @["con", "nul"]:
return
let (outp, code) = execNimble("init", "-y", name) let (outp, code) = execNimble("init", "-y", name)
let msg = outp.strip.processOutput() let msg = outp.strip.processOutput()
check code == QuitFailure check code == QuitFailure
check inLines(msg, check inLines(msg,
"\"$1\" is an invalid package name: reserved name" % name) "\"$1\" is an invalid package name: reserved name" % name)
try: removeFile(name.changeFileExt("nimble"))
removeFile(name.changeFileExt("nimble")) removeDir("src")
removeDir("src") removeDir("tests")
removeDir("tests")
except OSError:
discard
for reserved in reservedNames: for reserved in reservedNames:
checkName(reserved.toUpperAscii()) checkName(reserved.toUpperAscii())
@ -517,7 +465,8 @@ test "can uninstall":
let ls = outp.strip.processOutput() let ls = outp.strip.processOutput()
check exitCode != QuitSuccess check exitCode != QuitSuccess
check inLines(ls, "Cannot uninstall issue27b (0.1.0) because issue27a (0.1.0) depends") check "Cannot uninstall issue27b (0.1.0) because issue27a (0.1.0) depends" &
" on it" in ls[ls.len-1]
check execNimble("uninstall", "-y", "issue27").exitCode == QuitSuccess check execNimble("uninstall", "-y", "issue27").exitCode == QuitSuccess
check execNimble("uninstall", "-y", "issue27a").exitCode == QuitSuccess check execNimble("uninstall", "-y", "issue27a").exitCode == QuitSuccess
@ -869,177 +818,3 @@ suite "Module tests":
test "cli": test "cli":
cd "..": cd "..":
check execCmdEx("nim c -r src/nimblepkg/cli").exitCode == QuitSuccess 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