Compare commits

..

2 commits

Author SHA1 Message Date
Dominik Picheta
0f8a7e178d Fixes NIM_PREFIX_DIR affecting testing logs. 2018-08-28 14:56:03 +01:00
Dominik Picheta
f566e8bcd1 Attempt to fix travis. 2018-08-28 14:26:45 +01:00
69 changed files with 1190 additions and 2294 deletions

20
.gitignore vendored
View file

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

View file

@ -1,24 +1,26 @@
os:
- windows
- linux
- osx
dist: trusty
language: c
env:
- BRANCH=0.19.6
- BRANCH=0.20.2
- BRANCH=1.0.6
# This is the latest working Nim version against which Nimble is being tested
- BRANCH=#ab525cc48abdbbbed1f772e58e9fe21474f70f07
cache:
directories:
- "$HOME/.nimble"
- "$HOME/.choosenim"
install:
- curl https://gist.github.com/genotrance/fb53504a4fba88bc5201d3783df5c522/raw/travis.sh -LsSf -o travis.sh
- source travis.sh
- export CHOOSENIM_CHOOSE_VERSION="#7bb93c730ea87f"
- export NIM_LIB_PREFIX="$HOME/.choosenim/toolchains/nim-"$CHOOSENIM_CHOOSE_VERSION
- |
curl https://nim-lang.org/choosenim/init.sh -sSf > init.sh
sh init.sh -y
before_script:
- set -e
- set -x
- export CHOOSENIM_NO_ANALYTICS=1
- export PATH=$HOME/.nimble/bin:$PATH
script:
- cd tests

View file

@ -3,122 +3,6 @@
# Nimble changelog
## 0.11.0 - 22/09/2019
This is a major release containing nearly 60 commits. Most changes are
bug fixes, but this release also includes a couple new features:
- Binaries can now be built and run using the new ``run`` command.
- The ``NimblePkgVersion`` is now defined so you can easily get the package
version in your source code
([example](https://github.com/nim-lang/nimble/blob/4a2aaa07d/tests/nimbleVersionDefine/src/nimbleVersionDefine.nim)).
Some other highlights:
- Temporary files are now kept when the ``--debug`` flag is used.
- Fixed dependency resolution issues with "#head" packages (#432 and #672).
- The `install` command can now take Nim compiler flags via the new
``--passNim`` flag.
- Command line arguments are now passed properly to tasks (#633).
- The ``test`` command now respects the specified backend (#631).
- The ``dump`` command will no longer prompt and now has an implicit ``-y``.
- Fixed bugs with the new nimscript executor (#665).
- Fixed multiple downloads and installs of the same package (#678).
- Nimble init no longer overwrites existing files (#581).
- Fixed incorrect submodule version being pulled when in a non-master branch (#675).
----
Full changelog: https://github.com/nim-lang/nimble/compare/v0.10.2...v0.11.0
## 0.10.2 - 03/06/2019
This is a small release which avoids object variant changes that are now
treated as runtime errors (Nim #1286). It also adds support for `backend`
selection during `nimble init`.
Multiple bug fixes are also included:
- Fixed an issue where failing tasks were not returning a non-zero return
value (#655).
- Error out if `bin` is a Nim source file (#597).
- Fixed an issue where nimble task would not run if file of same name exists.
- Fixed an issue that prevented multiple instances of nimble from running on
the same package.
----
Full changelog: https://github.com/nim-lang/nimble/compare/v0.10.0...v0.10.2
## 0.10.0 - 27/05/2019
Nimble now uses the Nim compiler directly via `nim e` to execute nimble
scripts rather than embedding the Nim VM. This has multiple benefits:
- Evolve independently from Nim enabling new versions of Nimble to work
with multiple versions of Nim.
- Inherit all nimscript enhancements and bug fixes rather than having to
duplicate functionality.
- Fast build time and smaller binary.
- No dependency on the compiler package which could cause dependency issues
when nimble is used as a package.
Several other features and fixes have been implemented to improve general
development and test workflows.
- `nimble test` now sports a `-continue` or `-c` flag that allows tests
to continue on failure, removes all created test binaries on completion
and warns if no tests found.
- The `--inclDeps` or `-i` flag enables `nimble uninstall` to remove all
dependent packages during uninstall.
- Added documentation on the usage of a custom `nimbleDir`.
- Package type interactive prompt is more readable.
- Save temporary files in a per-user temp dir to enable Nimble on multi-user
systems.
- CTRL-C is now handled correctly in interactive prompts.
- Fixed issue where empty package list led to error.
- Fixed issue where file:// was prepended incorrectly.
- Fixed miscellaneous issues in version parsing, Github auth and briefClone.
- Miscellaneous cleanup of deprecated procs.
----
Full changelog: https://github.com/nim-lang/nimble/compare/v0.9.0...v0.10.0
## 0.9.0 - 19/09/2018
This is a major new release which contains at least one breaking change.
Unfortunately even though it was planned, support for lock files did not
make it into this release. The release does
however contain a large number of fixes spread across 57 commits.
The breaking change in this release is to do with the handling of binary
package. **Any package that specifies a ``bin`` value in it's .nimble file**
**will no longer install any Nim source code files**, in other words it's not
going to be a hybrid package by default. This means that so called "hybrid
packages" now need to specify ``installExt = @["nim"]`` in their metadata,
otherwise they will become binary packages only.
- **Breaking:** hybrid packages require ``installExt = @["nim"]``
([Commit](https://github.com/nim-lang/nimble/commit/09091792615eacd503e87ca70252c572a4bde2b5))
- **The ``init`` command can now show a list of choices for information such as**
**the license.**
- **The ``init`` command now creates correct project structures for all package**
**types.**
- **Fatal errors are no longer created when the path inside a .nimble-link file**
**doesn't exist.**
- **The ``develop`` command now always clones HEAD and grabs the full repo history.**
- **The default ``test`` task no longer executes all tests (only those starting with 't').**
- Colour is no longer used when `isatty` is false.
- ``publish`` now shows the URL of the created PR.
- The ``getPkgDir`` procedure has been fixed in the Nimble file API.
- Improved handling of proxy environment variables.
- Codebase has been improved not to rely on `nil` in strings and seqs.
- The handling of pre- and post-hooks has been improved significantly.
- Fixed the ``path`` command for packages with a ``srcDir`` and optimised the
package look-up.
----
Full changelog: https://github.com/nim-lang/nimble/compare/v0.8.10...v0.9.0
## 0.8.10 - 23/02/2018
The first release of 2018! Another fairly big release containing 40 commits.

View file

@ -1,17 +1,26 @@
import ospaths
template thisModuleFile: string = instantiationInfo(fullPaths = true).filename
when fileExists(thisModuleFile.parentDir / "src/nimblepkg/common.nim"):
# In the git repository the Nimble sources are in a ``src`` directory.
import src/nimblepkg/common
else:
# When the package is installed, the ``src`` directory disappears.
import nimblepkg/common
# Package
version = "0.11.0"
version = nimbleVersion
author = "Dominik Picheta"
description = "Nim package manager."
license = "BSD"
bin = @["nimble"]
srcDir = "src"
installExt = @["nim"]
# Dependencies
requires "nim >= 0.13.0"
requires "nim >= 0.13.0", "compiler#head"
when defined(nimdistros):
import distros

View file

@ -15,7 +15,6 @@ Interested in learning **how to create a package**? Skip directly to that sectio
- [nimble install](#nimble-install)
- [nimble uninstall](#nimble-uninstall)
- [nimble build](#nimble-build)
- [nimble run](#nimble-run)
- [nimble c](#nimble-c)
- [nimble list](#nimble-list)
- [nimble search](#nimble-search)
@ -75,15 +74,10 @@ not need to install Nimble manually**.
But in case you still want to install Nimble manually, you can follow the
following instructions.
There are two ways to install Nimble manually. Using ``koch`` and using Nimble
itself.
### Using koch
The ``koch`` tool is included in the Nim distribution and
There are two ways to install Nimble manually. The first is using the
``koch`` tool included in the Nim distribution and
[repository](https://github.com/nim-lang/Nim/blob/devel/koch.nim).
Simply navigate to the location of your Nim installation and execute the
following command to compile and install Nimble.
Simply execute the following command to compile and install Nimble.
```
./koch nimble
@ -92,19 +86,23 @@ following command to compile and install Nimble.
This will clone the Nimble repository, compile Nimble and copy it into
Nim's bin directory.
### Using Nimble
In most cases you will already have Nimble installed, you can install a newer
version of Nimble by simply running the following command:
The second approach is to install Nimble as a Nimble package. You can do this
by compiling Nimble, then running ``nimble install`` in Nimble's directory.
```
nimble install nimble
git clone https://github.com/nim-lang/nimble.git
cd nimble
nim c src/nimble
src/nimble install
```
This will download the latest release of Nimble and install it on your system.
**Note for Windows users**: You will need to rename ``nimble.exe`` after
compilation to something else like ``nimble1.exe``, then run
``src\nimble1.exe install``.
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.
This will install Nimble to the default Nimble packages location:
``~/.nimble/pkgs``. The binary will be installed to ``~/.nimble/bin``, so you
will need to add this directory to your PATH.
## Nimble usage
@ -172,13 +170,12 @@ example:
This is of course Git-specific, for Mercurial, use ``tip`` instead of ``head``. A
branch, tag, or commit hash may also be specified in the place of ``head``.
Instead of specifying a VCS branch, you may also specify a concrete version or a
version range, for example:
Instead of specifying a VCS branch, you may also specify a version range, for
example:
$ 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
current working directory then Nimble will install the package residing in
@ -224,8 +221,8 @@ instead of a name.
### nimble uninstall
The ``uninstall`` command will remove an installed package. Attempting to remove
a package which other packages depend on will result in an error. You can use the
``--inclDeps`` or ``-i`` flag to remove all dependent packages along with the package.
a package which other packages depend on is disallowed and will result in an
error. You must currently manually remove the reverse dependencies first.
Similar to the ``install`` command you can specify a version range, for example:
@ -239,13 +236,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
instead.
### nimble run
The ``run`` command can be used to build and run any binary specified in your
package's ``bin`` list. You can pass any compilation flags you wish by specifying
them before the ``run`` command, and you can specify arguments for your binary
by specifying them after the ``run`` command.
### nimble c
The ``c`` (or ``compile``, ``js``, ``cc``, ``cpp``) command can be used by
@ -428,7 +418,6 @@ You can also specify multiple dependencies like so:
requires "nim >= 0.10.0", "foobar >= 0.1.0"
requires "fizzbuzz >= 1.0"
requires "https://github.com/user/pkg#5a54b5e"
```
Nimble currently supports installation of packages from a local directory, a
@ -505,7 +494,7 @@ For a package named "foobar", the recommended project structure is the following
└── src
└── foobar.nim # Imported via `import foobar`
└── tests # Contains the tests
├── config.nims
├── nim.cfg
├── tfoo1.nim # First test
└── tfoo2.nim # Second test
@ -658,7 +647,7 @@ combo.
Dependencies are automatically installed before building.
It's a good idea to test that the dependencies you specified are correct by
running ``nimble build`` or ``nimble install`` in the directory
running by running ``nimble build`` or ``nimble install`` in the directory
of your package.
### Hybrids
@ -738,13 +727,13 @@ installing your package (on macOS):
```
Hint: This package requires some external dependencies.
Hint: To install them you may be able to run:
Hint: brew install openssl
Hint: sudo brew install openssl
```
### Nim compiler
The Nim compiler cannot read .nimble files. Its knowledge of Nimble is
limited to the ``nimblePath`` feature which allows it to use packages installed
limited to the ``nimblePaths`` feature which allows it to use packages installed
in Nimble's package directory when compiling your software. This means that
it cannot resolve dependencies, and it can only use the latest version of a
package when compiling.
@ -757,27 +746,6 @@ This means that you can safely compile using the compiler when developing your
software, but you should use Nimble to build the package before publishing it
to ensure that the dependencies you specified are correct.
### Compile with `nim` after changing the nimble directory
The Nim compiler has been preconfigured to look at the default nimble directory while compiling,
so no extra step is required to use nimble managed packages in your code.
However, if you are using a custom `nimbleDir`, you need to specify the
`--nimblePath:PATH` option. For example,
if your `nimble` directory is located at `/some/custom/path/nimble`, this should work:
```
nim c --nimblePath:/some/custom/path/nimble/pkgs main.nim
```
Some code editors rely on `nim check` to check for errors under the hood (e.g. VScode),
and the editor extension may not allow users to pass custom option to `nim check`, which
will cause `nim check` to scream `Error: cannot open file:<the_package>`. In this case,
you will have to use [Nim compiler's configuration files](https://nim-lang.org/docs/nimc.html#compiler-usage-configuration-files). Simply add the line:
```
nimblePath = "/some/custom/path/nimble/pkgs"
```
to the `nim.cfg` located in any directory listed in the [documentation](https://nim-lang.org/docs/nimc.html#compiler-usage-configuration-files), this should resolve the problem.
### Versions
Versions of cloned packages via Git or Mercurial are determined through the
@ -813,19 +781,6 @@ To summarise, the steps for release are:
Once the new tag is in the remote repository, Nimble will be able to detect
the new version.
##### Git Version Tagging
Use dot separated numbers to represent the release version in the git
tag label. Nimble will parse these git tag labels to know which
versions of a package are published.
``` text
v0.2.0 # 0.2.0
v1 # 1
v1.2.3-zuzu # 1.2.3
foo-1.2.3.4 # 1.2.3.4
```
## Publishing packages
Publishing packages isn't a requirement. But doing so allows people to associate

View file

@ -3,21 +3,20 @@
import system except TResult
import os, tables, strtabs, json, algorithm, sets, uri, sugar, sequtils, osproc
import std/options as std_opt
import httpclient, parseopt, os, osproc, pegs, tables, parseutils,
strtabs, json, algorithm, sets, uri, future, sequtils
import strutils except toLower
from unicode import toLower
from sequtils import toSeq
from strformat import fmt
import nimblepkg/packageinfo, nimblepkg/version, nimblepkg/tools,
nimblepkg/download, nimblepkg/config, nimblepkg/common,
nimblepkg/publish, nimblepkg/options, nimblepkg/packageparser,
nimblepkg/cli, nimblepkg/packageinstaller, nimblepkg/reversedeps,
nimblepkg/nimscriptexecutor, nimblepkg/init
nimblepkg/nimscriptexecutor
import nimblepkg/nimscriptwrapper
import nimblepkg/nimscriptsupport
proc refresh(options: Options) =
## Downloads the package list from the specified URL.
@ -96,7 +95,7 @@ proc copyFilesRec(origDir, currentDir, dest: string,
## Copies all the required files, skips files specified in the .nimble file
## (PackageInfo).
## Returns a list of filepaths to files which have been installed.
result = initHashSet[string]()
result = initSet[string]()
let whitelistMode =
pkgInfo.installDirs.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],
priority = HighPriority)
var pkgList {.global.}: seq[tuple[pkginfo: PackageInfo, meta: MetaData]] = @[]
once: pkgList = getInstalledPkgsMin(options.getPkgsDir(), options)
var pkgList = getInstalledPkgsMin(options.getPkgsDir(), options)
var reverseDeps: seq[tuple[name, version: string]] = @[]
for dep in pkginfo.requires:
if dep.name == "nimrod" or dep.name == "nim":
@ -192,6 +190,11 @@ proc processDeps(pkginfo: PackageInfo, options: Options): seq[PackageInfo] =
else:
display("Info:", "Dependency on $1 already satisfied" % $dep,
priority = HighPriority)
if pkg.isLinked:
# TODO (#393): This can be optimised since the .nimble-link files have
# a secondary line that specifies the srcDir.
pkg = pkg.toFullInfo(options)
result.add(pkg)
# Process the dependencies of this dependency.
result.add(processDeps(pkg.toFullInfo(options), options))
@ -201,13 +204,12 @@ proc processDeps(pkginfo: PackageInfo, options: Options): seq[PackageInfo] =
# in the path.
var pkgsInPath: StringTableRef = newStringTable(modeCaseSensitive)
for pkgInfo in result:
let currentVer = pkgInfo.getConcreteVersion(options)
if pkgsInPath.hasKey(pkgInfo.name) and
pkgsInPath[pkgInfo.name] != currentVer:
pkgsInPath[pkgInfo.name] != pkgInfo.version:
raise newException(NimbleError,
"Cannot satisfy the dependency on $1 $2 and $1 $3" %
[pkgInfo.name, currentVer, pkgsInPath[pkgInfo.name]])
pkgsInPath[pkgInfo.name] = currentVer
[pkgInfo.name, pkgInfo.version, pkgsInPath[pkgInfo.name]])
pkgsInPath[pkgInfo.name] = pkgInfo.version
# We add the reverse deps to the JSON file here because we don't want
# them added if the above errorenous condition occurs
@ -216,33 +218,16 @@ proc processDeps(pkginfo: PackageInfo, options: Options): seq[PackageInfo] =
for i in reverseDeps:
addRevDep(options.nimbleData, i, pkginfo)
proc buildFromDir(
pkgInfo: PackageInfo, paths, args: seq[string],
options: Options
) =
proc buildFromDir(pkgInfo: PackageInfo, paths: seq[string],
args: var seq[string]) =
## Builds a package as specified by ``pkgInfo``.
let binToBuild = options.getCompilationBinary(pkgInfo)
# Handle pre-`build` hook.
let realDir = pkgInfo.getRealDir()
cd realDir: # Make sure `execHook` executes the correct .nimble file.
if not execHook(options, actionBuild, true):
raise newException(NimbleError, "Pre-hook prevented further execution.")
if pkgInfo.bin.len == 0:
raise newException(NimbleError,
"Nothing to build. Did you specify a module to build using the" &
" `bin` key in your .nimble file?")
var args = args
let nimblePkgVersion = "-d:NimblePkgVersion=" & pkgInfo.version
let realDir = pkgInfo.getRealDir()
for path in paths: args.add("--path:\"" & path & "\" ")
var binariesBuilt = 0
for bin in pkgInfo.bin:
# Check if this is the only binary that we want to build.
if binToBuild.isSome() and binToBuild.get() != bin:
let binToBuild = binToBuild.get()
if bin.extractFilename().changeFileExt("") != binToBuild:
continue
let outputOpt = "-o:\"" & pkgInfo.getOutputDir(bin) & "\""
display("Building", "$1/$2 using $3 backend" %
[pkginfo.name, bin, pkgInfo.backend], priority = HighPriority)
@ -251,15 +236,10 @@ proc buildFromDir(
if not existsDir(outputDir):
createDir(outputDir)
let input = realDir / bin.changeFileExt("nim")
# `quoteShell` would be more robust than `\"` (and avoid quoting when
# un-necessary) but would require changing `extractBin`
let cmd = "\"$#\" $# --noNimblePath $# $# $# \"$#\"" %
[getNimBin(), pkgInfo.backend, nimblePkgVersion,
join(args, " "), outputOpt, input]
try:
doCmd(cmd, showCmd = true)
binariesBuilt.inc()
doCmd("\"" & getNimBin() & "\" $# --noBabelPath $# $# \"$#\"" %
[pkgInfo.backend, join(args, " "), outputOpt,
realDir / bin.changeFileExt("nim")])
except NimbleError:
let currentExc = (ref NimbleError)(getCurrentException())
let exc = newException(BuildFailed, "Build failed for package: " &
@ -269,14 +249,13 @@ proc buildFromDir(
exc.hint = hint
raise exc
if binariesBuilt == 0:
raiseNimbleError(
"No binaries built, did you specify a valid binary name?"
)
# Handle post-`build` hook.
cd realDir: # Make sure `execHook` executes the correct .nimble file.
discard execHook(options, actionBuild, false)
proc buildFromDir(pkgInfo: PackageInfo, paths: seq[string], forRelease: bool) =
var args: seq[string]
if forRelease:
args = @["-d:release"]
else:
args = @[]
buildFromDir(pkgInfo, paths, args)
proc removePkgDir(dir: string, options: Options) =
## Removes files belonging to the package in ``dir``.
@ -355,7 +334,7 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options,
# Handle pre-`install` hook.
if not options.depsOnly:
cd dir: # Make sure `execHook` executes the correct .nimble file.
if not execHook(options, actionInstall, true):
if not execHook(options, true):
raise newException(NimbleError, "Pre-hook prevented further execution.")
var pkgInfo = getPkgInfo(dir, options)
@ -382,11 +361,7 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options,
# if the build fails then the old package will still be installed.
if pkgInfo.bin.len > 0:
let paths = result.deps.map(dep => dep.getRealDir())
let flags = if options.action.typ in {actionInstall, actionPath, actionUninstall, actionDevelop}:
options.action.passNimFlags
else:
@[]
buildFromDir(pkgInfo, paths, "-d:release" & flags, options)
buildFromDir(pkgInfo, paths, true)
let pkgDestDir = pkgInfo.getPkgDest(options)
if existsDir(pkgDestDir) and existsFile(pkgDestDir / "nimblemeta.json"):
@ -411,7 +386,7 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options,
createDir(pkgDestDir)
# Copy this package's files based on the preferences specified in PkgInfo.
var filesInstalled = initHashSet[string]()
var filesInstalled = initSet[string]()
iterInstallFiles(realDir, pkgInfo, options,
proc (file: string) =
createDir(changeRoot(realDir, pkgDestDir, file.splitFile.dir))
@ -424,7 +399,7 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options,
pkgInfo.myPath)
filesInstalled.incl copyFileD(pkgInfo.myPath, dest)
var binariesInstalled = initHashSet[string]()
var binariesInstalled = initSet[string]()
if pkgInfo.bin.len > 0:
# Make sure ~/.nimble/bin directory is created.
createDir(binDir)
@ -469,7 +444,7 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options,
# executes the hook defined in the CWD, so we set it to where the package
# has been installed.
cd dest.splitFile.dir:
discard execHook(options, actionInstall, false)
discard execHook(options, false)
proc getDownloadInfo*(pv: PkgTuple, options: Options,
doPrompt: bool): (DownloadMethod, string,
@ -536,12 +511,12 @@ proc build(options: Options) =
nimScriptHint(pkgInfo)
let deps = processDeps(pkginfo, options)
let paths = deps.map(dep => dep.getRealDir())
var args = options.getCompilationFlags()
buildFromDir(pkgInfo, paths, args, options)
var args = options.action.compileOptions
buildFromDir(pkgInfo, paths, args)
proc execBackend(pkgInfo: PackageInfo, options: Options) =
proc execBackend(options: Options) =
let
bin = options.getCompilationBinary(pkgInfo).get()
bin = options.action.file
binDotNim = bin.addFileExt("nim")
if bin == "":
raise newException(NimbleError, "You need to specify a file.")
@ -554,10 +529,9 @@ proc execBackend(pkgInfo: PackageInfo, options: Options) =
nimScriptHint(pkgInfo)
let deps = processDeps(pkginfo, options)
let nimblePkgVersion = "-d:NimblePkgVersion=" & pkgInfo.version
var args = ""
for dep in deps: args.add("--path:\"" & dep.getRealDir() & "\" ")
for option in options.getCompilationFlags():
for option in options.action.compileOptions:
args.add("\"" & option & "\" ")
let backend =
@ -572,8 +546,8 @@ proc execBackend(pkgInfo: PackageInfo, options: Options) =
else:
display("Generating", ("documentation for $1 (from package $2) using $3 " &
"backend") % [bin, pkgInfo.name, backend], priority = HighPriority)
doCmd("\"" & getNimBin() & "\" $# --noNimblePath $# $# \"$#\"" %
[backend, nimblePkgVersion, args, bin], showOutput = true)
doCmd("\"" & getNimBin() & "\" $# --noNimblePath $# \"$#\"" %
[backend, args, bin], showOutput = true)
display("Success:", "Execution finished", Success, HighPriority)
proc search(options: Options) =
@ -589,7 +563,7 @@ proc search(options: Options) =
var found = false
template onFound {.dirty.} =
echoPackage(pkg)
if pkg.alias.len == 0 and options.queryVersions:
if options.queryVersions:
echoPackageVersions(pkg)
echo(" ")
found = true
@ -615,7 +589,7 @@ proc list(options: Options) =
let pkgList = getPackageList(options)
for pkg in pkgList:
echoPackage(pkg)
if pkg.alias.len == 0 and options.queryVersions:
if options.queryVersions:
echoPackageVersions(pkg)
echo(" ")
@ -653,19 +627,23 @@ proc listPaths(options: Options) =
raise newException(NimbleError, "A package name needs to be specified")
var errors = 0
let pkgs = getInstalledPkgsMin(options.getPkgsDir(), options)
for name, version in options.action.packages.items:
var installed: seq[VersionAndPath] = @[]
# There may be several, list all available ones and sort by version.
for x in pkgs.items():
let
pName = x.pkginfo.name
pVer = x.pkginfo.specialVersion
if name == pName:
for kind, path in walkDir(options.getPkgsDir):
if kind != pcDir or not path.startsWith(options.getPkgsDir / name & "-"):
continue
var nimbleFile = findNimbleFile(path, false)
if nimbleFile.existsFile:
var pkgInfo = getPkgInfo(path, options)
var v: VersionAndPath
v.version = newVersion(pVer)
v.path = x.pkginfo.getRealDir()
v.version = newVersion(pkgInfo.specialVersion)
v.path = pkgInfo.getRealDir()
installed.add(v)
else:
display("Warning:", "No .nimble file found for " & path, Warning,
MediumPriority)
if installed.len > 0:
sort(installed, cmp[VersionAndPath], Descending)
@ -729,59 +707,46 @@ proc dump(options: Options) =
echo "backend: ", p.backend.escape
proc init(options: Options) =
# Check whether the vcs is installed.
let vcsBin = options.action.vcsOption
if vcsBin != "" and findExe(vcsBin, true) == "":
raise newException(NimbleError, "Please install git or mercurial first")
var nimbleFile: string = ""
# Determine the package name.
if options.forcePrompts != forcePromptYes:
display("Info:",
"In order to initialise a new Nimble package, I will need to ask you\n" &
"some questions. Default values are shown in square brackets, press\n" &
"enter to use them.", priority = HighPriority)
# Ask for package name.
let pkgName =
if options.action.projName != "":
options.action.projName
else:
os.getCurrentDir().splitPath.tail.toValidPackageName()
# Validate the package name.
validatePackageName(pkgName)
nimbleFile = pkgName.changeFileExt("nimble")
validatePackageName(nimbleFile.changeFileExt(""))
# Determine the package root.
let pkgRoot =
if pkgName == os.getCurrentDir().splitPath.tail:
os.getCurrentDir()
else:
os.getCurrentDir() / pkgName
let nimbleFile = (pkgRoot / pkgName).changeFileExt("nimble")
if existsFile(nimbleFile):
let errMsg = "Nimble file already exists: $#" % nimbleFile
let nimbleFilePath = os.getCurrentDir() / nimbleFile
if existsFile(nimbleFilePath):
let errMsg = "Nimble file already exists: $#" % nimbleFilePath
raise newException(NimbleError, errMsg)
if options.forcePrompts != forcePromptYes:
display(
"Info:",
"Package initialisation requires info which could not be inferred.\n" &
"Default values are shown in square brackets, press\n" &
"enter to use them.",
priority = HighPriority
)
display("Using", "$# for new package name" % [pkgName.escape()],
priority = HighPriority)
# Determine author by running an external command
proc getAuthorWithCmd(cmd: string): string =
let (name, exitCode) = doCmdEx(cmd)
if exitCode == QuitSuccess and name.len > 0:
result = name.strip()
display("Using", "$# for new package author" % [result],
priority = HighPriority)
# Determine package author via git/hg or asking
# Ask for package author
proc getAuthor(): string =
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") != "":
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:
result = promptCustom(options, "Your name?", "Anonymous")
let pkgAuthor = getAuthor()
@ -792,16 +757,10 @@ proc init(options: Options) =
priority = HighPriority)
# Determine the type of package
let pkgType = promptList(
options,
"""Package type?
Library - provides functionality for other packages.
Binary - produces an executable for the end-user.
Hybrid - combination of library and binary
For more information see https://goo.gl/cm2RX5""",
["library", "binary", "hybrid"]
)
let pkgType = promptList(options, "Package type?", [
"lib",
"bin",
])
# Ask for package version.
let pkgVersion = promptCustom(options, "Initial version of package?", "0.1.0")
@ -812,72 +771,105 @@ For more information see https://goo.gl/cm2RX5""",
"A new awesome nimble package")
# Ask for license
# License list is based on:
# https://www.blackducksoftware.com/top-open-source-licenses
var pkgLicense = options.promptList(
"""Package License?
This should ideally be a valid SPDX identifier. See https://spdx.org/licenses/.
""", [
let pkgLicense = options.promptList("Package License?", [
"MIT",
"GPL-2.0",
"Apache-2.0",
"ISC",
"GPL-3.0",
"BSD-3-Clause",
"LGPL-2.1",
"LGPL-3.0",
"EPL-2.0",
# This is what npm calls "UNLICENSED" (which is too similar to "Unlicense")
"Proprietary",
"Other"
"BSD2",
"GPLv3",
"LGPLv3",
"Apache2",
])
if pkgLicense.toLower == "other":
pkgLicense = promptCustom(options,
"""Package license?
Please specify a valid SPDX identifier.""",
"MIT"
)
var pkgBackend = options.promptList(
"""Package Backend?
c - Compile using C backend.
cpp - Compile using C++ backend.
objc - Compile using Objective-C backend.
js - Compile using JavaScript backend.""",
["c", "cpp", "objc", "js"]
)
# Ask for Nim dependency
let nimDepDef = getNimrodVersion()
let pkgNimDep = promptCustom(options, "Lowest supported Nim version?",
$nimDepDef)
validateVersion(pkgNimDep)
createPkgStructure(
(
pkgName,
pkgVersion,
pkgAuthor,
pkgDesc,
pkgLicense,
pkgBackend,
pkgSrcDir,
pkgNimDep,
pkgType
),
pkgRoot
)
let pkgTestDir = "tests"
# 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
# Create source directory
os.createDir(pkgSrcDir)
var ignoreFile = if vcsBin == "git": ".gitignore" else: ".hgignore"
var fd = open(joinPath(pkgRoot, ignoreFile), fmWrite)
fd.write(pkgName & "\n")
fd.close()
display("Success:", "Source directory created successfully", Success,
MediumPriority)
# Create initial source file
cd pkgSrcDir:
let pkgFile = pkgName.changeFileExt("nim")
try:
if pkgType == "bin":
pkgFile.writeFile "# Hello Nim!\necho \"Hello, World!\"\n"
else:
pkgFile.writeFile """# $#
# Copyright $#
# $#
""" % [pkgName, pkgAuthor, pkgDesc]
display("Success:", "Created initial source file successfully", Success,
MediumPriority)
except:
raise newException(NimbleError, "Unable to open file " & pkgFile &
" for writing: " & osErrorMsg(osLastError()))
# Create test directory
os.createDir(pkgTestDir)
display("Success:", "Test directory created successfully", Success,
MediumPriority)
cd pkgTestDir:
try:
"test1.nims".writeFile("""switch("path", "$$projectDir/../$#")""" %
[pkgSrcDir])
display("Success:", "Test config file created successfully", Success,
MediumPriority)
except:
raise newException(NimbleError, "Unable to open file " & "test1.nims" &
" for writing: " & osErrorMsg(osLastError()))
try:
"test1.nim".writeFile("doAssert(1 + 1 == 2)\n")
display("Success:", "Test file created successfully", Success,
MediumPriority)
except:
raise newException(NimbleError, "Unable to open file " & "test1.nim" &
" for writing: " & osErrorMsg(osLastError()))
# Write the nimble file
try:
if pkgType == "lib":
nimbleFile.writeFile """# Package
version = $#
author = $#
description = $#
license = $#
srcDir = $#
# Dependencies
requires "nim >= $#"
""" % [pkgVersion.escape(), pkgAuthor.escape(), pkgDesc.escape(),
pkgLicense.escape(), pkgSrcDir.escape(), pkgNimDep]
else:
nimbleFile.writeFile """# Package
version = $#
author = $#
description = $#
license = $#
srcDir = $#
bin = @[$#]
# Dependencies
requires "nim >= $#"
""" % [pkgVersion.escape(), pkgAuthor.escape(), pkgDesc.escape(),
pkgLicense.escape(), pkgSrcDir.escape(), pkgName.escape(), pkgNimDep]
except:
raise newException(NimbleError, "Unable to open file " & "test1.nim" &
" for writing: " & osErrorMsg(osLastError()))
display("Success:", "Nimble file created successfully", Success,
MediumPriority)
display("Success:", "Package $# created successfully" % [pkgName], Success,
HighPriority)
@ -887,8 +879,7 @@ proc uninstall(options: Options) =
raise newException(NimbleError,
"Please specify the package(s) to uninstall.")
var pkgsToDelete: HashSet[PackageInfo]
pkgsToDelete.init()
var pkgsToDelete: seq[PackageInfo] = @[]
# Do some verification.
for pkgTup in options.action.packages:
display("Looking", "for $1 ($2)" % [pkgTup.name, $pkgTup.ver],
@ -899,33 +890,34 @@ proc uninstall(options: Options) =
raise newException(NimbleError, "Package not found")
display("Checking", "reverse dependencies", priority = HighPriority)
var errors: seq[string] = @[]
for pkg in pkgList:
# Check whether any packages depend on the ones the user is trying to
# uninstall.
if options.uninstallRevDeps:
getAllRevDeps(options, pkg, pkgsToDelete)
let revDeps = getRevDeps(options, pkg)
var reason = ""
if revDeps.len == 1:
reason = "$1 ($2) depends on it" % [revDeps[0].name, $revDeps[0].ver]
else:
let
revDeps = getRevDeps(options, pkg)
var reason = ""
for revDep in revDeps:
if reason.len != 0: reason.add ", "
reason.add("$1 ($2)" % [revDep.name, revDep.version])
if reason.len != 0:
reason &= " depend" & (if revDeps.len == 1: "s" else: "") & " on it"
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 len(revDeps - pkgsToDelete) > 0:
display("Cannot", "uninstall $1 ($2) because $3" %
[pkgTup.name, pkg.specialVersion, reason], Warning, HighPriority)
else:
pkgsToDelete.incl pkg
if revDeps.len > 0:
errors.add("Cannot uninstall $1 ($2) because $3" %
[pkgTup.name, pkg.specialVersion, reason])
else:
pkgsToDelete.add pkg
if pkgsToDelete.len == 0:
raise newException(NimbleError, "Failed uninstall - no packages to delete")
if pkgsToDelete.len == 0:
raise newException(NimbleError, "\n " & errors.join("\n "))
var pkgNames = ""
for pkg in pkgsToDelete.items:
if pkgNames.len != 0: pkgNames.add ", "
for i in 0 ..< pkgsToDelete.len:
if i != 0: pkgNames.add ", "
let pkg = pkgsToDelete[i]
pkgNames.add("$1 ($2)" % [pkg.name, pkg.specialVersion])
# Let's confirm that the user wants these packages removed.
@ -949,14 +941,14 @@ proc uninstall(options: Options) =
proc listTasks(options: Options) =
let nimbleFile = findNimbleFile(getCurrentDir(), true)
nimscriptwrapper.listTasks(nimbleFile, options)
nimscriptsupport.listTasks(nimbleFile, options)
proc developFromDir(dir: string, options: Options) =
if options.depsOnly:
raiseNimbleError("Cannot develop dependencies only.")
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.")
var pkgInfo = getPkgInfo(dir, options)
@ -998,7 +990,8 @@ proc developFromDir(dir: string, options: Options) =
writeNimbleLink(nimbleLinkPath, nimbleLink)
# Save a nimblemeta.json file.
saveNimbleMeta(pkgDestDir, dir, vcsRevisionInDir(dir), nimbleLinkPath)
saveNimbleMeta(pkgDestDir, "file://" & dir, vcsRevisionInDir(dir),
nimbleLinkPath)
# Save the nimble data (which might now contain reverse deps added in
# processDeps).
@ -1009,7 +1002,7 @@ proc developFromDir(dir: string, options: Options) =
# Execute the post-develop hook.
cd dir:
discard execHook(options, actionDevelop, false)
discard execHook(options, false)
proc develop(options: Options) =
if options.action.packages == @[]:
@ -1030,74 +1023,29 @@ proc develop(options: Options) =
let (meth, url, metadata) = getDownloadInfo(pv, options, true)
let subdir = metadata.getOrDefault("subdir")
# Download the HEAD and make sure the full history is downloaded.
let ver =
if pv.ver.kind == verAny:
parseVersionRange("#head")
else:
pv.ver
var options = options
options.forceFullClone = true
discard downloadPkg(url, ver, meth, subdir, options, downloadDir)
discard downloadPkg(url, pv.ver, meth, subdir, options, downloadDir)
developFromDir(downloadDir / subdir, options)
proc test(options: Options) =
## Executes all tests starting with 't' in the ``tests`` directory.
## Subdirectories are not walked.
var pkgInfo = getPkgInfo(getCurrentDir(), options)
var
files = toSeq(walkDir(getCurrentDir() / "tests"))
tests, failures: int
if files.len < 1:
display("Warning:", "No tests found!", Warning, HighPriority)
return
## Executes all tests.
var files = toSeq(walkDir(getCurrentDir() / "tests"))
files.sort((a, b) => cmp(a.path, b.path))
for file in files:
let (_, name, ext) = file.path.splitFile()
if ext == ".nim" and name[0] == 't' and file.kind in {pcFile, pcLinkToFile}:
if file.path.endsWith(".nim") and file.kind in {pcFile, pcLinkToFile}:
var optsCopy = options.briefClone()
optsCopy.action = Action(typ: actionCompile)
optsCopy.action.typ = actionCompile
optsCopy.action.file = file.path
optsCopy.action.backend = pkgInfo.backend
optsCopy.getCompilationFlags() = @[]
optsCopy.getCompilationFlags().add("-r")
optsCopy.getCompilationFlags().add("--path:.")
let
binFileName = file.path.changeFileExt(ExeExt)
existsBefore = existsFile(binFileName)
optsCopy.action.backend = "c"
optsCopy.action.compileOptions = @[]
optsCopy.action.compileOptions.add("-r")
optsCopy.action.compileOptions.add("--path:.")
execBackend(optsCopy)
if options.continueTestsOnFailure:
inc tests
try:
execBackend(pkgInfo, optsCopy)
except NimbleError:
inc failures
else:
execBackend(pkgInfo, optsCopy)
let
existsAfter = existsFile(binFileName)
canRemove = not existsBefore and existsAfter
if canRemove:
try:
removeFile(binFileName)
except OSError as exc:
display("Warning:", "Failed to delete " & binFileName & ": " &
exc.msg, Warning, MediumPriority)
if failures == 0:
display("Success:", "All tests passed", Success, HighPriority)
else:
let error = "Only " & $(tests - failures) & "/" & $tests & " tests passed"
display("Error:", error, Error, HighPriority)
display("Success:", "All tests passed", Success, HighPriority)
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)
var error: ValidationError
var pkgInfo: PackageInfo
@ -1115,29 +1063,7 @@ proc check(options: Options) =
display("Failure:", "Validation failed", Error, HighPriority)
quit(QuitFailure)
proc run(options: Options) =
# Verify parameters.
var pkgInfo = getPkgInfo(getCurrentDir(), options)
let binary = options.getCompilationBinary(pkgInfo).get("")
if binary.len == 0:
raiseNimbleError("Please specify a binary to run")
if binary notin pkgInfo.bin:
raiseNimbleError(
"Binary '$#' is not defined in '$#' package." % [binary, pkgInfo.name]
)
# Build the binary.
build(options)
let binaryPath = pkgInfo.getOutputDir(binary)
let cmd = quoteShellCommand(binaryPath & options.action.runFlags)
displayDebug("Executing", cmd)
cmd.execCmd.quit
proc doAction(options: var Options) =
proc doAction(options: Options) =
if options.showHelp:
writeHelp()
if options.showVersion:
@ -1148,10 +1074,6 @@ proc doAction(options: var Options) =
if not existsDir(options.getPkgsDir):
createDir(options.getPkgsDir)
if options.action.typ in {actionTasks, actionRun, actionBuild, actionCompile}:
# Implicitly disable package validation for these commands.
options.disableValidation = true
case options.action.typ
of actionRefresh:
refresh(options)
@ -1177,11 +1099,8 @@ proc doAction(options: var Options) =
listPaths(options)
of actionBuild:
build(options)
of actionRun:
run(options)
of actionCompile, actionDoc:
var pkgInfo = getPkgInfo(getCurrentDir(), options)
execBackend(pkgInfo, options)
execBackend(options)
of actionInit:
init(options)
of actionPublish:
@ -1198,46 +1117,37 @@ proc doAction(options: var Options) =
of actionNil:
assert false
of actionCustom:
if not execHook(options, actionCustom, true):
if not execHook(options, true):
display("Warning", "Pre-hook prevented further execution.", Warning,
HighPriority)
return
let isPreDefined = options.action.command.normalize == "test"
var execResult: ExecutionResult[bool]
var execResult: ExecutionResult[void]
if execCustom(options, execResult, failFast=not isPreDefined):
if execResult.hasTaskRequestedCommand():
var options = execResult.getOptionsForCommand(options)
doAction(options)
doAction(execResult.getOptionsForCommand(options))
else:
# If there is no task defined for the `test` task, we run the pre-defined
# fallback logic.
if isPreDefined:
test(options)
# Run the post hook for `test` in case it exists.
discard execHook(options, actionCustom, false)
discard execHook(options, false)
when isMainModule:
var error = ""
var hint = ""
var opt: Options
try:
opt = parseCmdLine()
opt.doAction()
parseCmdLine().doAction()
except NimbleError:
let currentExc = (ref NimbleError)(getCurrentException())
(error, hint) = getOutputInfo(currentExc)
except NimbleQuit:
discard
finally:
try:
let folder = getNimbleTempDir()
if opt.shouldRemoveTmp(folder):
removeDir(folder)
except OSError:
let msg = "Couldn't remove Nimble's temp dir"
display("Warning:", msg, Warning, MediumPriority)
removeDir(getNimbleTempDir())
if error.len > 0:
displayTip()

View file

@ -12,11 +12,7 @@
# - Bright for HighPriority.
# - Normal for MediumPriority.
import terminal, sets, strutils
import version
when not declared(initHashSet):
import common
import logging, terminal, sets, strutils
type
CLI* = ref object
@ -45,11 +41,10 @@ const
styles: array[DebugPriority .. HighPriority, set[Style]] =
[{styleDim}, {styleDim}, {}, {styleBright}]
proc newCLI(): CLI =
result = CLI(
level: HighPriority,
warnings: initHashSet[(string, string)](),
warnings: initSet[(string, string)](),
suppressionCount: 0,
showColor: true,
suppressMessages: false
@ -62,18 +57,8 @@ proc calculateCategoryOffset(category: string): int =
assert category.len <= longestCategory
return longestCategory - category.len
proc isSuppressed(displayType: DisplayType): bool =
# Don't print any Warning, Message or Success messages when suppression of
# warnings is enabled. That is, unless the user asked for --verbose output.
if globalCLI.suppressMessages and displayType >= Warning and
globalCLI.level == HighPriority:
return true
proc displayCategory(category: string, displayType: DisplayType,
priority: Priority) =
if isSuppressed(displayType):
return
# Calculate how much the `category` must be offset to align along a center
# line.
let offset = calculateCategoryOffset(category)
@ -90,9 +75,6 @@ proc displayCategory(category: string, displayType: DisplayType,
proc displayLine(category, line: string, displayType: DisplayType,
priority: Priority) =
if isSuppressed(displayType):
return
displayCategory(category, displayType, priority)
# Display the message.
@ -100,6 +82,12 @@ proc displayLine(category, line: string, displayType: DisplayType,
proc display*(category, msg: string, displayType = Message,
priority = MediumPriority) =
# Don't print any Warning, Message or Success messages when suppression of
# warnings is enabled. That is, unless the user asked for --verbose output.
if globalCLI.suppressMessages and displayType >= Warning and
globalCLI.level == HighPriority:
return
# Multiple warnings containing the same messages should not be shown.
let warningPair = (category, msg)
if displayType == Warning:
@ -147,7 +135,7 @@ proc prompt*(forcePrompts: ForcePrompt, question: string): bool =
display("Prompt:", question & " -> [forced no]", Warning, HighPriority)
return false
of dontForcePrompt:
displayLine("Prompt:", question & " [y/N]", Warning, HighPriority)
display("Prompt:", question & " [y/N]", Warning, HighPriority)
displayCategory("Answer:", Warning, HighPriority)
let yn = stdin.readLine()
case yn.normalize
@ -181,76 +169,6 @@ proc promptCustom*(forcePrompts: ForcePrompt, question, default: string): string
proc promptCustom*(question, default: string): string =
return promptCustom(dontForcePrompt, question, default)
proc promptListInteractive(question: string, args: openarray[string]): string =
display("Prompt:", question, Warning, HighPriority)
display("Select", "Cycle with 'Tab', 'Enter' when done", Message,
HighPriority)
displayCategory("Choices:", Warning, HighPriority)
var
current = 0
selected = false
# Incase the cursor is at the bottom of the terminal
for arg in args:
stdout.write "\n"
# Reset the cursor to the start of the selection prompt
cursorUp(stdout, args.len)
cursorForward(stdout, longestCategory)
hideCursor(stdout)
# The selection loop
while not selected:
setForegroundColor(fgDefault)
# Loop through the options
for i, arg in args:
# Check if the option is the current
if i == current:
writeStyled("> " & arg & " <", {styleBright})
else:
writeStyled(" " & arg & " ", {styleDim})
# Move the cursor back to the start
for s in 0..<(arg.len + 4):
cursorBackward(stdout)
# Move down for the next item
cursorDown(stdout)
# Move the cursor back up to the start of the selection prompt
for i in 0..<(args.len()):
cursorUp(stdout)
resetAttributes(stdout)
# Begin key input
while true:
case getch():
of '\t':
current = (current + 1) mod args.len
break
of '\r':
selected = true
break
of '\3':
showCursor(stdout)
raise newException(NimbleError, "Keyboard interrupt")
else: discard
# Erase all lines of the selection
for i in 0..<args.len:
eraseLine(stdout)
cursorDown(stdout)
# Move the cursor back up to the initial selection line
for i in 0..<args.len():
cursorUp(stdout)
showCursor(stdout)
display("Answer:", args[current], Warning,HighPriority)
return args[current]
proc promptListFallback(question: string, args: openarray[string]): string =
display("Prompt:", question & " [" & join(args, "/") & "]", Warning,
HighPriority)
displayCategory("Answer:", Warning, HighPriority)
result = stdin.readLine()
for arg in args:
if arg.cmpIgnoreCase(result) == 0:
return arg
proc promptList*(forcePrompts: ForcePrompt, question: string, args: openarray[string]): string =
case forcePrompts:
of forcePromptYes:
@ -258,10 +176,13 @@ proc promptList*(forcePrompts: ForcePrompt, question: string, args: openarray[st
display("Prompt:", question & " -> [forced " & result & "]", Warning,
HighPriority)
else:
if isatty(stdout):
return promptListInteractive(question, args)
else:
return promptListFallback(question, args)
display("Prompt:", question & " [" & join(args, "/") & "]", Warning, HighPriority)
displayCategory("Answer:", Warning, HighPriority)
result = stdin.readLine()
for arg in args:
if arg.cmpIgnoreCase(result) == 0:
return arg
return promptList(forcePrompts, question, args)
proc setVerbosity*(level: Priority) =
globalCLI.level = level

View file

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

View file

@ -1,8 +1,8 @@
# Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info.
import parsecfg, streams, strutils, os, tables, uri
import parsecfg, streams, strutils, os, tables, Uri
import version, cli
import tools, version, common, cli
type
Config* = object
@ -68,12 +68,11 @@ proc parseConfig*(): Config =
var e = next(p)
case e.kind
of cfgEof:
if currentSection.len > 0:
if currentPackageList.urls.len == 0 and currentPackageList.path == "":
raise newException(NimbleError, "Package list '$1' requires either url or path" % currentPackageList.name)
if currentPackageList.urls.len > 0 and currentPackageList.path != "":
raise newException(NimbleError, "Attempted to specify `url` and `path` for the same package list '$1'" % currentPackageList.name)
addCurrentPkgList(result, currentPackageList)
if currentPackageList.urls.len == 0 and currentPackageList.path == "":
raise newException(NimbleError, "Package list '$1' requires either url or path" % currentPackageList.name)
if currentPackageList.urls.len > 0 and currentPackageList.path != "":
raise newException(NimbleError, "Attempted to specify `url` and `path` for the same package list '$1'" % currentPackageList.name)
addCurrentPkgList(result, currentPackageList)
break
of cfgSectionStart:
addCurrentPkgList(result, currentPackageList)

View file

@ -2,15 +2,14 @@
# BSD License. Look at license.txt for more info.
import parseutils, os, osproc, strutils, tables, pegs, uri
import packageinfo, packageparser, version, tools, common, options, cli
from algorithm import SortOrder, sorted
from sequtils import toSeq, filterIt, map
type
DownloadMethod* {.pure.} = enum
git = "git", hg = "hg"
proc getSpecificDir(meth: DownloadMethod): string {.used.} =
proc getSpecificDir(meth: DownloadMethod): string =
case meth
of DownloadMethod.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
# messes up the damn line endings.
doCmd("git checkout --force " & branch)
doCmd("git submodule update --recursive")
of DownloadMethod.hg:
cd downloadDir:
doCmd("hg checkout " & branch)
proc doPull(meth: DownloadMethod, downloadDir: string) {.used.} =
proc doPull(meth: DownloadMethod, downloadDir: string) =
case meth
of DownloadMethod.git:
doCheckout(meth, downloadDir, "")
@ -44,17 +42,17 @@ proc doPull(meth: DownloadMethod, downloadDir: string) {.used.} =
doCmd("hg pull")
proc doClone(meth: DownloadMethod, url, downloadDir: string, branch = "",
onlyTip = true) =
tip = true) =
case meth
of DownloadMethod.git:
let
depthArg = if onlyTip: "--depth 1 " else: ""
depthArg = if tip: "--depth 1 " else: ""
branchArg = if branch == "": "" else: "-b " & branch & " "
doCmd("git clone --recursive " & depthArg & branchArg & url &
" " & downloadDir)
of DownloadMethod.hg:
let
tipArg = if onlyTip: "-r tip " else: ""
tipArg = if tip: "-r tip " else: ""
branchArg = if branch == "": "" else: "-b " & branch & " "
doCmd("hg clone " & tipArg & branchArg & url & " " & downloadDir)
@ -104,21 +102,14 @@ proc getTagsListRemote*(url: string, meth: DownloadMethod): seq[string] =
# http://stackoverflow.com/questions/2039150/show-tags-for-remote-hg-repository
raise newException(ValueError, "Hg doesn't support remote tag querying.")
proc getVersionList*(tags: seq[string]): OrderedTable[Version, string] =
## Return an ordered table of Version -> git tag label. Ordering is
## in descending order with the most recent version first.
let taggedVers: seq[tuple[ver: Version, tag: string]] =
tags
.filterIt(it != "")
.map(proc(s: string): tuple[ver: Version, tag: string] =
# skip any chars before the version
let i = skipUntil(s, Digits)
# TODO: Better checking, tags can have any
# names. Add warnings and such.
result = (newVersion(s[i .. s.len-1]), s))
.sorted(proc(a, b: (Version, string)): int = cmp(a[0], b[0]),
SortOrder.Descending)
result = toOrderedTable[Version, string](taggedVers)
proc getVersionList*(tags: seq[string]): Table[Version, string] =
# Returns: TTable of version -> git tag name
result = initTable[Version, string]()
for tag in tags:
if tag != "":
let i = skipUntil(tag, Digits) # skip any chars before the version
# TODO: Better checking, tags can have any names. Add warnings and such.
result[newVersion(tag[i .. tag.len-1])] = tag
proc getDownloadMethod*(meth: string): DownloadMethod =
case meth
@ -180,11 +171,10 @@ proc doDownload(url: string, downloadDir: string, verRange: VersionRange,
if verRange.kind == verSpecial:
# We want a specific commit/branch/tag here.
if verRange.spe == getHeadName(downMethod):
# Grab HEAD.
doClone(downMethod, url, downloadDir, onlyTip = not options.forceFullClone)
doClone(downMethod, url, downloadDir) # Grab HEAD.
else:
# Grab the full repo.
doClone(downMethod, url, downloadDir, onlyTip = false)
doClone(downMethod, url, downloadDir, tip = false)
# Then perform a checkout operation to get the specified branch/commit.
# `spe` starts with '#', trim it.
doAssert(($verRange.spe)[0] == '#')
@ -201,13 +191,12 @@ proc doDownload(url: string, downloadDir: string, verRange: VersionRange,
getLatestByTag:
display("Cloning", "latest tagged version: " & latest.tag,
priority = MediumPriority)
doClone(downMethod, url, downloadDir, latest.tag,
onlyTip = not options.forceFullClone)
doClone(downMethod, url, downloadDir, latest.tag)
else:
# If no commits have been tagged on the repo we just clone HEAD.
doClone(downMethod, url, downloadDir) # Grab HEAD.
of DownloadMethod.hg:
doClone(downMethod, url, downloadDir, onlyTip = not options.forceFullClone)
doClone(downMethod, url, downloadDir)
result = getHeadName(downMethod)
let versions = getTagsList(downloadDir, downMethod).getVersionList()
@ -276,8 +265,14 @@ proc echoPackageVersions*(pkg: Package) =
try:
let versions = getTagsListRemote(pkg.url, downMethod).getVersionList()
if versions.len > 0:
let sortedVersions = toSeq(values(versions))
echo(" versions: " & join(sortedVersions, ", "))
var vstr = ""
var i = 0
for v in values(versions):
if i != 0:
vstr.add(", ")
vstr.add(v)
i.inc
echo(" versions: " & vstr)
else:
echo(" versions: (No versions tagged in the remote repository)")
except OSError:
@ -285,32 +280,3 @@ proc echoPackageVersions*(pkg: Package) =
of DownloadMethod.hg:
echo(" versions: (Remote tag retrieval not supported by " &
pkg.downloadMethod & ")")
when isMainModule:
# Test version sorting
block:
let data = @["v9.0.0-taeyeon", "v9.0.1-jessica", "v9.2.0-sunny",
"v9.4.0-tiffany", "v9.4.2-hyoyeon"]
let expected = toOrderedTable[Version, string]({
newVersion("9.4.2-hyoyeon"): "v9.4.2-hyoyeon",
newVersion("9.4.0-tiffany"): "v9.4.0-tiffany",
newVersion("9.2.0-sunny"): "v9.2.0-sunny",
newVersion("9.0.1-jessica"): "v9.0.1-jessica",
newVersion("9.0.0-taeyeon"): "v9.0.0-taeyeon"
})
doAssert expected == getVersionList(data)
block:
let data2 = @["v0.1.0", "v0.1.1", "v0.2.0",
"0.4.0", "v0.4.2"]
let expected2 = toOrderedTable[Version, string]({
newVersion("0.4.2"): "v0.4.2",
newVersion("0.4.0"): "0.4.0",
newVersion("0.2.0"): "v0.2.0",
newVersion("0.1.1"): "v0.1.1",
newVersion("0.1.0"): "v0.1.0",
})
doAssert expected2 == getVersionList(data2)
echo("Everything works!")

View file

@ -1,184 +0,0 @@
import os, strutils
import ./cli, ./tools
type
PkgInitInfo* = tuple
pkgName: string
pkgVersion: string
pkgAuthor: string
pkgDesc: string
pkgLicense: string
pkgBackend: string
pkgSrcDir: string
pkgNimDep: string
pkgType: string
proc writeExampleIfNonExistent(file: string, content: string) =
if not existsFile(file):
writeFile(file, content)
else:
display("Info:", "File " & file & " already exists, did not write " &
"example code", priority = HighPriority)
proc createPkgStructure*(info: PkgInitInfo, pkgRoot: string) =
# Create source directory
createDirD(pkgRoot / info.pkgSrcDir)
# Initialise the source code directories and create some example code.
var nimbleFileOptions = ""
case info.pkgType
of "binary":
let mainFile = pkgRoot / info.pkgSrcDir / info.pkgName.changeFileExt("nim")
writeExampleIfNonExistent(mainFile,
"""
# This is just an example to get you started. A typical binary package
# uses this file as the main entry point of the application.
when isMainModule:
echo("Hello, World!")
"""
)
nimbleFileOptions.add("bin = @[\"$1\"]\n" % info.pkgName)
of "library":
let mainFile = pkgRoot / info.pkgSrcDir / info.pkgName.changeFileExt("nim")
writeExampleIfNonExistent(mainFile,
"""
# This is just an example to get you started. A typical library package
# exports the main API in this file. Note that you cannot rename this file
# but you can remove it if you wish.
proc add*(x, y: int): int =
## Adds two files together.
return x + y
"""
)
createDirD(pkgRoot / info.pkgSrcDir / info.pkgName)
let submodule = pkgRoot / info.pkgSrcDir / info.pkgName /
"submodule".addFileExt("nim")
writeExampleIfNonExistent(submodule,
"""
# This is just an example to get you started. Users of your library will
# import this file by writing ``import $1/submodule``. Feel free to rename or
# remove this file altogether. You may create additional modules alongside
# this file as required.
type
Submodule* = object
name*: string
proc initSubmodule*(): Submodule =
## Initialises a new ``Submodule`` object.
Submodule(name: "Anonymous")
""" % info.pkgName
)
of "hybrid":
let mainFile = pkgRoot / info.pkgSrcDir / info.pkgName.changeFileExt("nim")
writeExampleIfNonExistent(mainFile,
"""
# This is just an example to get you started. A typical hybrid package
# uses this file as the main entry point of the application.
import $1pkg/submodule
when isMainModule:
echo(getWelcomeMessage())
""" % info.pkgName
)
let pkgSubDir = pkgRoot / info.pkgSrcDir / info.pkgName & "pkg"
createDirD(pkgSubDir)
let submodule = pkgSubDir / "submodule".addFileExt("nim")
writeExampleIfNonExistent(submodule,
"""
# This is just an example to get you started. Users of your hybrid library will
# import this file by writing ``import $1pkg/submodule``. Feel free to rename or
# remove this file altogether. You may create additional modules alongside
# this file as required.
proc getWelcomeMessage*(): string = "Hello, World!"
""" % info.pkgName
)
nimbleFileOptions.add("installExt = @[\"nim\"]\n")
nimbleFileOptions.add("bin = @[\"$1\"]\n" % info.pkgName)
else:
assert false, "Invalid package type specified."
let pkgTestDir = "tests"
# Create test directory
case info.pkgType
of "binary":
discard
of "hybrid", "library":
let pkgTestPath = pkgRoot / pkgTestDir
createDirD(pkgTestPath)
writeFile(pkgTestPath / "config".addFileExt("nims"),
"switch(\"path\", \"$$projectDir/../$#\")" % info.pkgSrcDir
)
if info.pkgType == "library":
writeExampleIfNonExistent(pkgTestPath / "test1".addFileExt("nim"),
"""
# This is just an example to get you started. You may wish to put all of your
# tests into a single file, or separate them into multiple `test1`, `test2`
# etc. files (better names are recommended, just make sure the name starts with
# the letter 't').
#
# To run these tests, simply execute `nimble test`.
import unittest
import $1
test "can add":
check add(5, 5) == 10
""" % info.pkgName
)
else:
writeExampleIfNonExistent(pkgTestPath / "test1".addFileExt("nim"),
"""
# This is just an example to get you started. You may wish to put all of your
# tests into a single file, or separate them into multiple `test1`, `test2`
# etc. files (better names are recommended, just make sure the name starts with
# the letter 't').
#
# To run these tests, simply execute `nimble test`.
import unittest
import $1pkg/submodule
test "correct welcome":
check getWelcomeMessage() == "Hello, World!"
""" % info.pkgName
)
else:
assert false, "Invalid package type specified."
# Write the nimble file
let nimbleFile = pkgRoot / info.pkgName.changeFileExt("nimble")
# Only write backend if it isn't "c"
var pkgBackend = ""
if (info.pkgBackend != "c"):
pkgBackend = "backend = " & info.pkgbackend.escape()
writeFile(nimbleFile, """# Package
version = $#
author = "$#"
description = "$#"
license = $#
srcDir = $#
$#
$#
# Dependencies
requires "nim >= $#"
""" % [
info.pkgVersion.escape(), info.pkgAuthor.replace("\"", "\\\""), info.pkgDesc.replace("\"", "\\\""),
info.pkgLicense.escape(), info.pkgSrcDir.escape(), nimbleFileOptions,
pkgBackend, info.pkgNimDep
]
)
display("Info:", "Nimble file created successfully", priority=MediumPriority)

View file

@ -3,12 +3,6 @@
## This module is implicitly imported in NimScript .nimble files.
import system except getCommand, setCommand, switch, `--`
import strformat, strutils, tables
when not defined(nimscript):
import os
var
packageName* = "" ## Set this to the package name. It
## is usually not required to do that, nims' filename is
@ -17,7 +11,7 @@ var
author*: string ## The package's author.
description*: string ## The package's description.
license*: string ## The package's license.
srcDir*: string ## The package's source directory.
srcdir*: string ## The package's source directory.
binDir*: string ## The package's binary directory.
backend*: string ## The package's backend.
@ -28,140 +22,29 @@ var
foreignDeps*: seq[string] = @[] ## The foreign dependencies. Only
## exported for 'distros.nim'.
beforeHooks: seq[string] = @[]
afterHooks: seq[string] = @[]
commandLineParams: seq[string] = @[]
flags: TableRef[string, seq[string]]
command = "e"
project = ""
success = false
retVal = true
projectFile = ""
outFile = ""
proc requires*(deps: varargs[string]) =
## Call this to set the list of requirements of your Nimble
## package.
for d in deps: requiresData.add(d)
proc getParams() =
# Called by nimscriptwrapper.nim:execNimscript()
# nim e --flags /full/path/to/file.nims /full/path/to/file.out action
for i in 2 .. paramCount():
let
param = paramStr(i)
if param[0] != '-':
if projectFile.len == 0:
projectFile = param
elif outFile.len == 0:
outFile = param
else:
commandLineParams.add param.normalize
proc getCommand*(): string =
return command
proc setCommand*(cmd: string, prj = "") =
command = cmd
if prj.len != 0:
project = prj
proc switch*(key: string, value="") =
if flags.isNil:
flags = newTable[string, seq[string]]()
if flags.hasKey(key):
flags[key].add(value)
else:
flags[key] = @[value]
template `--`*(key, val: untyped) =
switch(astToStr(key), strip astToStr(val))
template `--`*(key: untyped) =
switch(astToStr(key), "")
template printIfLen(varName) =
if varName.len != 0:
result &= astToStr(varName) & ": \"\"\"" & varName & "\"\"\"\n"
template printSeqIfLen(varName) =
if varName.len != 0:
result &= astToStr(varName) & ": \"" & varName.join(", ") & "\"\n"
proc printPkgInfo(): string =
if backend.len == 0:
backend = "c"
result = "[Package]\n"
if packageName.len != 0:
result &= "name: \"" & packageName & "\"\n"
printIfLen version
printIfLen author
printIfLen description
printIfLen license
printIfLen srcDir
printIfLen binDir
printIfLen backend
printSeqIfLen skipDirs
printSeqIfLen skipFiles
printSeqIfLen skipExt
printSeqIfLen installDirs
printSeqIfLen installFiles
printSeqIfLen installExt
printSeqIfLen bin
printSeqIfLen beforeHooks
printSeqIfLen afterHooks
if requiresData.len != 0:
result &= "\n[Deps]\n"
result &= &"requires: \"{requiresData.join(\", \")}\"\n"
proc onExit*() =
if "printPkgInfo".normalize in commandLineParams:
if outFile.len != 0:
writeFile(outFile, printPkgInfo())
else:
var
output = ""
output &= "\"success\": " & $success & ", "
output &= "\"command\": \"" & command & "\", "
if project.len != 0:
output &= "\"project\": \"" & project & "\", "
if not flags.isNil and flags.len != 0:
output &= "\"flags\": {"
for key, val in flags.pairs:
output &= "\"" & key & "\": ["
for v in val:
let v = if v.len > 0 and v[0] == '"': strutils.unescape(v)
else: v
output &= v.escape & ", "
output = output[0 .. ^3] & "], "
output = output[0 .. ^3] & "}, "
output &= "\"retVal\": " & $retVal
if outFile.len != 0:
writeFile(outFile, "{" & output & "}")
# TODO: New release of Nim will move this `task` template under a
# `when not defined(nimble)`. This will allow us to override it in the future.
template task*(name: untyped; description: string; body: untyped): untyped =
## Defines a task. Hidden tasks are supported via an empty description.
## Example:
##
## .. code-block:: nim
## task build, "default build is via the C backend":
## setCommand "c"
proc `name Task`*() = body
when not declared(task):
template task*(name: untyped; description: string; body: untyped): untyped =
## Defines a task. Hidden tasks are supported via an empty description.
## Example:
##
## .. code-block:: nim
## task build, "default build is via the C backend":
## setCommand "c"
proc `name Task`*() = body
if commandLineParams.len == 0 or "help" in commandLineParams:
success = true
echo(astToStr(name), " ", description)
elif astToStr(name).normalize in commandLineParams:
success = true
let cmd = getCommand()
if cmd.len == 0 or cmd ==? "help":
setCommand "help"
writeTask(astToStr(name), description)
elif cmd ==? astToStr(name):
setCommand "nop"
`name Task`()
template before*(action: untyped, body: untyped): untyped =
@ -170,27 +53,15 @@ template before*(action: untyped, body: untyped): untyped =
result = true
body
beforeHooks.add astToStr(action)
if (astToStr(action) & "Before").normalize in commandLineParams:
success = true
retVal = `action Before`()
template after*(action: untyped, body: untyped): untyped =
## Defines a block of code which is evaluated after ``action`` is executed.
proc `action After`*(): bool =
result = true
body
afterHooks.add astToStr(action)
if (astToStr(action) & "After").normalize in commandLineParams:
success = true
retVal = `action After`()
template builtin = discard
proc getPkgDir*(): string =
## Returns the package directory containing the .nimble file currently
## being evaluated.
result = projectFile.rsplit(seps={'/', '\\', ':'}, maxsplit=1)[0]
getParams()
builtin

View file

@ -1,17 +1,16 @@
# Copyright (C) Dominik Picheta. All rights reserved.
# 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,
version
import packageparser, common, packageinfo, options, nimscriptsupport, cli
proc execHook*(options: Options, hookAction: ActionType, before: bool): bool =
proc execHook*(options: Options, before: bool): bool =
## Returns whether to continue.
result = true
# For certain commands hooks should not be evaluated.
if hookAction in noHookActions:
if options.action.typ in noHookActions:
return
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.
let pkgInfo = getPkgInfoFromFile(nimbleFile, options)
let actionName =
if hookAction == actionCustom: options.action.command
else: ($hookAction)[6 .. ^1]
if options.action.typ == actionCustom: options.action.command
else: ($options.action.typ)[6 .. ^1]
let hookExists =
if before: actionName.normalize in pkgInfo.preHooks
else: actionName.normalize in pkgInfo.postHooks
@ -32,7 +31,7 @@ proc execHook*(options: Options, hookAction: ActionType, before: bool): bool =
result = res.retVal
proc execCustom*(options: Options,
execResult: var ExecutionResult[bool],
execResult: var ExecutionResult[void],
failFast = true): bool =
## Executes the custom command using the nimscript backend.
##
@ -58,7 +57,7 @@ proc execCustom*(options: Options,
HighPriority)
return
if not execHook(options, actionCustom, false):
if not execHook(options, false):
return
return true

View file

@ -0,0 +1,692 @@
# 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
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
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, toSeconds(getLastModificationTime(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
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):
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 = getTempDir() / "nimblecache"
let tmpNimscriptApiPath = t / "nimblepkg" / "nimscriptapi.nim"
createDir(tmpNimscriptApiPath.splitFile.dir)
writeFile(tmpNimscriptApiPath, nimscriptApi)
when declared(NimCompilerApiVersion):
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()
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

@ -1,216 +0,0 @@
# Copyright (C) Andreas Rumpf. All rights reserved.
# BSD License. Look at license.txt for more info.
## Implements the new configuration system for Nimble. Uses Nim as a
## scripting language.
import hashes, json, os, strutils, tables, times, osproc, strtabs
import version, options, cli, tools
type
Flags = TableRef[string, seq[string]]
ExecutionResult*[T] = object
success*: bool
command*: string
arguments*: seq[string]
flags*: Flags
retVal*: T
stdout*: string
const
internalCmd = "e"
nimscriptApi = staticRead("nimscriptapi.nim")
printPkgInfo = "printPkgInfo"
proc isCustomTask(actionName: string, options: Options): bool =
options.action.typ == actionCustom and actionName != printPkgInfo
proc needsLiveOutput(actionName: string, options: Options, isHook: bool): bool =
let isCustomTask = isCustomTask(actionName, options)
return isCustomTask or isHook or actionName == ""
proc writeExecutionOutput(data: string) =
# TODO: in the future we will likely want this to be live, users will
# undoubtedly be doing loops and other crazy things in their top-level
# Nimble files.
display("Info", data)
proc execNimscript(
nimsFile, projectDir, actionName: string, options: Options, isHook: bool
): tuple[output: string, exitCode: int, stdout: string] =
let
nimsFileCopied = projectDir / nimsFile.splitFile().name & "_" & getProcessId() & ".nims"
outFile = getNimbleTempDir() & ".out"
let
isScriptResultCopied =
nimsFileCopied.fileExists() and
nimsFileCopied.getLastModificationTime() >= nimsFile.getLastModificationTime()
if not isScriptResultCopied:
nimsFile.copyFile(nimsFileCopied)
defer:
# Only if copied in this invocation, allows recursive calls of nimble
if not isScriptResultCopied and options.shouldRemoveTmp(nimsFileCopied):
nimsFileCopied.removeFile()
var cmd = (
"nim e $# -p:$# $# $# $#" % [
"--hints:off --verbosity:0",
(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)
if needsLiveOutput(actionName, options, isHook):
result.exitCode = execCmd(cmd)
else:
# We want to capture any possible errors when parsing a .nimble
# file's metadata. See #710.
(result.stdout, result.exitCode) = execCmdEx(cmd)
if outFile.fileExists():
result.output = outFile.readFile()
if options.shouldRemoveTmp(outFile):
discard outFile.tryRemoveFile()
proc getNimsFile(scriptName: string, options: Options): string =
let
cacheDir = getTempDir() / "nimblecache"
shash = $scriptName.parentDir().hash().abs()
prjCacheDir = cacheDir / scriptName.splitFile().name & "_" & shash
nimscriptApiFile = cacheDir / "nimscriptapi.nim"
result = prjCacheDir / scriptName.extractFilename().changeFileExt ".nims"
let
iniFile = result.changeFileExt(".ini")
isNimscriptApiCached =
nimscriptApiFile.fileExists() and nimscriptApiFile.getLastModificationTime() >
getAppFilename().getLastModificationTime()
isScriptResultCached =
isNimscriptApiCached and result.fileExists() and result.getLastModificationTime() >
scriptName.getLastModificationTime()
if not isNimscriptApiCached:
createDir(cacheDir)
writeFile(nimscriptApiFile, nimscriptApi)
if not isScriptResultCached:
createDir(result.parentDir())
writeFile(result, """
import system except getCommand, setCommand, switch, `--`,
packageName, version, author, description, license, srcDir, binDir, backend,
skipDirs, skipFiles, skipExt, installDirs, installFiles, installExt, bin, foreignDeps,
requires, task, packageName
""" &
"import nimscriptapi, strutils\n" & scriptName.readFile() & "\nonExit()\n")
discard tryRemoveFile(iniFile)
proc getIniFile*(scriptName: string, options: Options): string =
let
nimsFile = getNimsFile(scriptName, options)
result = nimsFile.changeFileExt(".ini")
let
isIniResultCached =
result.fileExists() and result.getLastModificationTime() >
scriptName.getLastModificationTime()
if not isIniResultCached:
let (output, exitCode, stdout) = execNimscript(
nimsFile, scriptName.parentDir(), printPkgInfo, options, isHook=false
)
if exitCode == 0 and output.len != 0:
result.writeFile(output)
stdout.writeExecutionOutput()
else:
raise newException(NimbleError, stdout & "\nprintPkgInfo() failed")
proc execScript(
scriptName, actionName: string, options: Options, isHook: bool
): ExecutionResult[bool] =
let nimsFile = getNimsFile(scriptName, options)
let (output, exitCode, stdout) =
execNimscript(
nimsFile, scriptName.parentDir(), actionName, options, isHook
)
if exitCode != 0:
let errMsg =
if stdout.len != 0:
stdout
else:
"Exception raised during nimble script execution"
raise newException(NimbleError, errMsg)
let
j =
if output.len != 0:
parseJson(output)
else:
parseJson("{}")
result.flags = newTable[string, seq[string]]()
result.success = j{"success"}.getBool()
result.command = j{"command"}.getStr()
if "project" in j:
result.arguments.add j["project"].getStr()
if "flags" in j:
for flag, vals in j["flags"].pairs:
result.flags[flag] = @[]
for val in vals.items():
result.flags[flag].add val.getStr()
result.retVal = j{"retVal"}.getBool()
stdout.writeExecutionOutput()
proc execTask*(scriptName, taskName: string,
options: Options): ExecutionResult[bool] =
## Executes the specified task in the specified script.
##
## `scriptName` should be a filename pointing to the nimscript file.
display("Executing", "task $# in $#" % [taskName, scriptName],
priority = HighPriority)
result = execScript(scriptName, taskName, options, isHook=false)
proc execHook*(scriptName, actionName: string, before: bool,
options: Options): ExecutionResult[bool] =
## Executes the specified action's hook. Depending on ``before``, either
## the "before" or the "after" hook.
##
## `scriptName` should be a filename pointing to the nimscript file.
let hookName =
if before: actionName.toLowerAscii & "Before"
else: actionName.toLowerAscii & "After"
display("Attempting", "to execute hook $# in $#" % [hookName, scriptName],
priority = MediumPriority)
result = execScript(scriptName, hookName, options, isHook=true)
proc hasTaskRequestedCommand*(execResult: ExecutionResult): bool =
## Determines whether the last executed task used ``setCommand``
return execResult.command != internalCmd
proc listTasks*(scriptName: string, options: Options) =
discard execScript(scriptName, "", options, isHook=false)

View file

@ -2,17 +2,14 @@
# BSD License. Look at license.txt for more info.
import json, strutils, os, parseopt, strtabs, uri, tables, terminal
import sequtils, sugar
import std/options as std_opt
from httpclient import Proxy, newProxy
import config, version, common, cli
import config, version, tools, common, cli
type
Options* = object
forcePrompts*: ForcePrompt
depsOnly*: bool
uninstallRevDeps*: bool
queryVersions*: bool
queryInstalled*: bool
nimbleDir*: string
@ -25,18 +22,12 @@ type
showVersion*: bool
noColor*: bool
disableValidation*: bool
continueTestsOnFailure*: bool
## Whether packages' repos should always be downloaded with their history.
forceFullClone*: bool
# Temporary storage of flags that have not been captured by any specific Action.
unknownFlags*: seq[(CmdLineKind, string, string)]
ActionType* = enum
actionNil, actionRefresh, actionInit, actionDump, actionPublish,
actionInstall, actionSearch,
actionList, actionBuild, actionPath, actionUninstall, actionCompile,
actionDoc, actionCustom, actionTasks, actionDevelop, actionCheck,
actionRun
actionDoc, actionCustom, actionTasks, actionDevelop, actionCheck
Action* = object
case typ*: ActionType
@ -46,20 +37,14 @@ type
of actionInstall, actionPath, actionUninstall, actionDevelop:
packages*: seq[PkgTuple] # Optional only for actionInstall
# and actionDevelop.
passNimFlags*: seq[string]
of actionSearch:
search*: seq[string] # Search string.
of actionInit, actionDump:
projName*: string
vcsOption*: string
of actionCompile, actionDoc, actionBuild:
file*: string
backend*: string
compileOptions: seq[string]
of actionRun:
runFile: Option[string]
compileFlags: seq[string]
runFlags*: seq[string]
compileOptions*: seq[string]
of actionCustom:
command*: string
arguments*: seq[string]
@ -72,32 +57,21 @@ Usage: nimble COMMAND [opts]
Commands:
install [pkgname, ...] Installs a list of packages.
[-d, --depsOnly] Install only dependencies.
[-p, --passNim] Forward specified flag to compiler.
develop [pkgname, ...] Clones a list of packages for development.
Symlinks the cloned packages or any package
in the current working directory.
check Verifies the validity of a package in the
current working directory.
init [pkgname] Initializes a new Nimble project in the
current directory or if a name is provided a
new directory of the same name.
--git
--hg Create a git or hg repo in the new nimble project.
current directory.
publish Publishes a package on nim-lang/packages.
The current working directory needs to be the
toplevel directory of the Nimble package.
uninstall [pkgname, ...] Uninstalls a list of packages.
[-i, --inclDeps] Uninstall package and dependent package(s).
build [opts, ...] [bin] Builds a package.
run [opts, ...] [bin] Builds and runs a package.
Binary needs to be specified after any
compilation options if there are several
binaries defined, any flags after the binary
or -- arg are passed to the binary when it is run.
build Builds a package.
c, cc, js [opts, ...] f.nim Builds a file inside a package. Passes options
to the Nim compiler.
test Compiles and executes tests
[-c, --continue] Don't stop execution on a failed test.
doc, doc2 [opts, ...] f.nim Builds documentation for a file inside a
package. Passes options to the Nim compiler.
refresh [url] Refreshes the package list. A package list URL
@ -160,8 +134,6 @@ proc parseActionType*(action: string): ActionType =
result = actionPath
of "build":
result = actionBuild
of "run":
result = actionRun
of "c", "compile", "js", "cpp", "cc":
result = actionCompile
of "doc", "doc2":
@ -196,7 +168,6 @@ proc initAction*(options: var Options, key: string) =
case options.action.typ
of actionInstall, actionPath, actionDevelop, actionUninstall:
options.action.packages = @[]
options.action.passNimFlags = @[]
of actionCompile, actionDoc, actionBuild:
options.action.compileOptions = @[]
options.action.file = ""
@ -204,11 +175,8 @@ proc initAction*(options: var Options, key: string) =
else: options.action.backend = keyNorm
of actionInit:
options.action.projName = ""
options.action.vcsOption = ""
of actionDump:
options.action.projName = ""
options.action.vcsOption = ""
options.forcePrompts = forcePromptYes
of actionRefresh:
options.action.optionalURL = ""
of actionSearch:
@ -217,7 +185,7 @@ proc initAction*(options: var Options, key: string) =
options.action.command = key
options.action.arguments = @[]
options.action.flags = newStringTable()
of actionPublish, actionList, actionTasks, actionCheck, actionRun,
of actionPublish, actionList, actionTasks, actionCheck,
actionNil: discard
proc prompt*(options: Options, question: string): bool =
@ -241,6 +209,18 @@ proc promptList*(options: Options, question: string, args: openarray[string]): s
## options is selected.
return promptList(options.forcePrompts, question, args)
proc renameBabelToNimble(options: Options) {.deprecated.} =
let babelDir = getHomeDir() / ".babel"
let nimbleDir = getHomeDir() / ".nimble"
if dirExists(babelDir):
if options.prompt("Found deprecated babel package directory, would you " &
"like to rename it to nimble?"):
copyDir(babelDir, nimbleDir)
copyFile(babelDir / "babeldata.json", nimbleDir / "nimbledata.json")
removeDir(babelDir)
removeFile(nimbleDir / "babeldata.json")
proc getNimbleDir*(options: Options): string =
result = options.config.nimbleDir
if options.nimbleDir.len != 0:
@ -263,15 +243,9 @@ proc getBinDir*(options: Options): string =
options.getNimbleDir() / "bin"
proc parseCommand*(key: string, result: var Options) =
result.action = Action(typ: parseActionType(key))
result.action.typ = parseActionType(key)
initAction(result, key)
proc setRunOptions(result: var Options, key, val: string, isArg: bool) =
if result.action.runFile.isNone() and (isArg or val == "--"):
result.action.runFile = some(key)
else:
result.action.runFlags.add(val)
proc parseArgument*(key: string, result: var Options) =
case result.action.typ
of actionNil:
@ -292,40 +266,23 @@ proc parseArgument*(key: string, result: var Options) =
result.action.search.add(key)
of actionInit, actionDump:
if result.action.projName != "":
raise newException(
NimbleError, "Can only perform this action on one package at a time."
)
raise newException(NimbleError,
"Can only initialize one package at a time.")
result.action.projName = key
of actionCompile, actionDoc:
result.action.file = key
of actionList, actionPublish:
of actionList, actionBuild, actionPublish:
result.showHelp = true
of actionBuild:
result.action.file = key
of actionRun:
result.setRunOptions(key, key, true)
of actionCustom:
result.action.arguments.add(key)
else:
discard
proc getFlagString(kind: CmdLineKind, flag, val: string): string =
let prefix =
case kind
of cmdShortOption: "-"
of cmdLongOption: "--"
else: ""
if val == "":
return prefix & flag
else:
return prefix & flag & ":" & val
proc parseFlag*(flag, val: string, result: var Options, kind = cmdLongOption) =
var wasFlagHandled = true
let f = flag.normalize()
# Global flags.
var isGlobalFlag = true
case f
of "help", "h": result.showHelp = true
of "version", "v": result.showVersion = true
@ -336,64 +293,43 @@ proc parseFlag*(flag, val: string, result: var Options, kind = cmdLongOption) =
of "debug": result.verbosity = DebugPriority
of "nocolor": result.noColor = true
of "disablevalidation": result.disableValidation = true
else: isGlobalFlag = false
var wasFlagHandled = true
# Action-specific flags.
case result.action.typ
of actionSearch, actionList:
case f
of "installed", "i":
result.queryInstalled = true
of "ver":
result.queryVersions = true
else:
wasFlagHandled = false
of actionInstall:
case f
of "depsonly", "d":
result.depsOnly = true
of "passnim", "p":
result.action.passNimFlags.add(val)
else:
wasFlagHandled = false
of actionInit:
case f
of "git", "hg":
result.action.vcsOption = f
else:
wasFlagHandled = false
of actionUninstall:
case f
of "incldeps", "i":
result.uninstallRevDeps = true
else:
wasFlagHandled = false
of actionCompile, actionDoc, actionBuild:
if not isGlobalFlag:
result.action.compileOptions.add(getFlagString(kind, flag, val))
of actionRun:
result.showHelp = false
result.setRunOptions(flag, getFlagString(kind, flag, val), false)
of actionCustom:
if result.action.command.normalize == "test":
if f == "continue" or f == "c":
result.continueTestsOnFailure = true
result.action.flags[flag] = val
else:
wasFlagHandled = false
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 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:
result.action.flags[flag] = val
else:
wasFlagHandled = false
if not wasFlagHandled and not isGlobalFlag:
result.unknownFlags.add((kind, flag, val))
if not wasFlagHandled:
raise newException(NimbleError, "Unknown option: --" & flag)
proc initOptions*(): Options =
# Exported for choosenim
Options(
action: Action(typ: actionNil),
pkgInfoCache: newTable[string, PackageInfo](),
verbosity: HighPriority,
noColor: not isatty(stdout)
)
result.action.typ = actionNil
result.pkgInfoCache = newTable[string, PackageInfo]()
result.nimbleDir = ""
result.verbosity = HighPriority
result.noColor = not isatty(stdout)
proc parseMisc(options: var Options) =
# Load nimbledata.json
@ -408,29 +344,6 @@ proc parseMisc(options: var Options) =
else:
options.nimbleData = %{"reverseDeps": newJObject()}
proc handleUnknownFlags(options: var Options) =
if options.action.typ == actionRun:
# ActionRun uses flags that come before the command as compilation flags
# and flags that come after as run flags.
options.action.compileFlags =
map(options.unknownFlags, x => getFlagString(x[0], x[1], x[2]))
options.unknownFlags = @[]
else:
# For everything else, handle the flags that came before the command
# normally.
let unknownFlags = options.unknownFlags
options.unknownFlags = @[]
for flag in unknownFlags:
parseFlag(flag[1], flag[2], options, flag[0])
# Any unhandled flags?
if options.unknownFlags.len > 0:
let flag = options.unknownFlags[0]
raise newException(
NimbleError,
"Unknown option: " & getFlagString(flag[0], flag[1], flag[2])
)
proc parseCmdLine*(): Options =
result = initOptions()
@ -444,11 +357,9 @@ proc parseCmdLine*(): Options =
else:
parseArgument(key, result)
of cmdLongOption, cmdShortOption:
parseFlag(key, val, result, kind)
parseFlag(key, val, result, kind)
of cmdEnd: assert(false) # cannot happen
handleUnknownFlags(result)
# Set verbosity level.
setVerbosity(result.verbosity)
@ -464,11 +375,6 @@ proc parseCmdLine*(): Options =
if result.action.typ == actionNil and not result.showVersion:
result.showHelp = true
if result.action.typ != actionNil and result.showVersion:
# We've got another command that should be handled. For example:
# nimble run foobar -v
result.showVersion = false
proc getProxy*(options: Options): Proxy =
## Returns ``nil`` if no proxy is specified.
var url = ""
@ -504,48 +410,5 @@ proc briefClone*(options: Options): Options =
var newOptions = initOptions()
newOptions.config = options.config
newOptions.nimbleData = options.nimbleData
newOptions.nimbleDir = options.nimbleDir
newOptions.forcePrompts = options.forcePrompts
newOptions.pkgInfoCache = options.pkgInfoCache
return newOptions
proc shouldRemoveTmp*(options: Options, file: string): bool =
result = true
if options.verbosity <= DebugPriority:
let msg = "Not removing temporary path because of debug verbosity: " & file
display("Warning:", msg, Warning, MediumPriority)
return false
proc getCompilationFlags*(options: var Options): var seq[string] =
case options.action.typ
of actionBuild, actionDoc, actionCompile:
return options.action.compileOptions
of actionRun:
return options.action.compileFlags
else:
assert false
proc getCompilationFlags*(options: Options): seq[string] =
var opt = options
return opt.getCompilationFlags()
proc getCompilationBinary*(options: Options, pkgInfo: PackageInfo): Option[string] =
case options.action.typ
of actionBuild, actionDoc, actionCompile:
let file = options.action.file.changeFileExt("")
if file.len > 0:
return some(file)
of actionRun:
let optRunFile = options.action.runFile
let runFile =
if optRunFile.get("").len > 0:
optRunFile.get()
elif pkgInfo.bin.len == 1:
pkgInfo.bin[0]
else:
""
if runFile.len > 0:
return some(runFile.changeFileExt(ExeExt))
else:
discard

View file

@ -3,7 +3,8 @@
# Stdlib imports
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
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] =
## Returns the list of packages found in the downloaded packages.json files.
result = @[]
var namesAdded = initHashSet[string]()
var namesAdded = initSet[string]()
for name, list in options.config.packageLists:
let packages = readPackageList(name, options)
for p in packages:
@ -312,17 +313,14 @@ proc findNimbleFile*(dir: string; error: bool): string =
# Return the path of the real .nimble file.
result = readNimbleLink(result).nimbleFilePath
if not fileExists(result):
let msg = "The .nimble-link file is pointing to a missing file: " & result
let hintMsg =
"Remove '$1' or restore the file it points to." % dir
display("Warning:", msg, Warning, HighPriority)
display("Hint:", hintMsg, Warning, HighPriority)
raiseNimbleError("The .nimble-link file is pointing to a missing" &
" file: " & result)
proc getInstalledPkgsMin*(libsDir: string, options: Options):
seq[tuple[pkginfo: PackageInfo, meta: MetaData]] =
## Gets a list of installed packages. The resulting package info is
## minimal. This has the advantage that it does not depend on the
## ``packageparser`` module, and so can be used by ``nimscriptwrapper``.
## ``packageparser`` module, and so can be used by ``nimscriptsupport``.
##
## ``libsDir`` is in most cases: ~/.nimble/pkgs/ (options.getPkgsDir)
result = @[]
@ -338,17 +336,8 @@ proc getInstalledPkgsMin*(libsDir: string, options: Options):
pkg.specialVersion = version
pkg.isMinimal = true
pkg.isInstalled = true
let nimbleFileDir = nimbleFile.splitFile().dir
pkg.isLinked = cmpPaths(nimbleFileDir, path) != 0
# Read the package's 'srcDir' (this is stored in the .nimble-link so
# we can easily grab it)
if pkg.isLinked:
let nimbleLinkPath = path / name.addFileExt("nimble-link")
let realSrcPath = readNimbleLink(nimbleLinkPath).packageDir
assert realSrcPath.startsWith(nimbleFileDir)
pkg.srcDir = realSrcPath.replace(nimbleFileDir)
pkg.srcDir.removePrefix(DirSep)
pkg.isLinked =
cmpPaths(nimbleFile.splitFile().dir, path) != 0
result.add((pkg, meta))
proc withinRange*(pkgInfo: PackageInfo, verRange: VersionRange): bool =
@ -536,15 +525,6 @@ proc getPkgDest*(pkgInfo: PackageInfo, options: Options): string =
let pkgDestDir = options.getPkgsDir() / (pkgInfo.name & versionStr)
return pkgDestDir
proc `==`*(pkg1: PackageInfo, pkg2: PackageInfo): bool =
if pkg1.name == pkg2.name and pkg1.myPath == pkg2.myPath:
return true
proc hash*(x: PackageInfo): Hash =
var h: Hash = 0
h = h !& hash(x.myPath)
result = !$h
when isMainModule:
doAssert getNameVersion("/home/user/.nimble/libs/packagea-0.1") ==
("packagea", "0.1")

View file

@ -3,13 +3,7 @@
import os, strutils, sets, json
# Local imports
import cli, options, tools
when defined(windows):
import version
when not declared(initHashSet) or not declared(toHashSet):
import common
import cli, common, options, tools
when defined(windows):
# 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.
## For example: ~/projects/jester/
saveNimbleMeta(pkgDestDir, "file://" & pkgDir, vcsRevision,
toHashSet[string]([nimbleLinkPath]), initHashSet[string](), true)
toSet[string]([nimbleLinkPath]), initSet[string](), true)

View file

@ -1,12 +1,12 @@
# Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info.
import parsecfg, sets, streams, strutils, os, tables, sugar
import parsecfg, json, streams, strutils, parseutils, os, tables, future
from sequtils import apply, map
import version, tools, common, nimscriptwrapper, options, packageinfo, cli
import version, tools, common, nimscriptsupport, options, packageinfo, cli
## Contains procedures for parsing .nimble files. Moved here from ``packageinfo``
## because it depends on ``nimscriptwrapper`` (``nimscriptwrapper`` also
## because it depends on ``nimscriptsupport`` (``nimscriptsupport`` also
## depends on other procedures in ``packageinfo``.
type
@ -212,10 +212,7 @@ proc multiSplit(s: string): seq[string] =
result.del(i)
# Huh, nothing to return? Return given input.
if len(result) < 1:
if s.strip().len != 0:
return @[s]
else:
return @[]
return @[s]
proc readPackageInfoFromNimble(path: string; result: var PackageInfo) =
var fs = newFileStream(path, fmRead)
@ -256,20 +253,12 @@ proc readPackageInfoFromNimble(path: string; result: var PackageInfo) =
result.installExt.add(ev.value.multiSplit)
of "bin":
for i in ev.value.multiSplit:
if i.splitFile().ext == ".nim":
raise newException(NimbleError, "`bin` entry should not be a source file: " & i)
result.bin.add(i.addFileExt(ExeExt))
of "backend":
result.backend = ev.value.toLowerAscii()
case result.backend.normalize
of "javascript": result.backend = "js"
else: discard
of "beforehooks":
for i in ev.value.multiSplit:
result.preHooks.incl(i.normalize)
of "afterhooks":
for i in ev.value.multiSplit:
result.postHooks.incl(i.normalize)
else:
raise newException(NimbleError, "Invalid field: " & ev.key)
of "deps", "dependencies":
@ -288,33 +277,6 @@ proc readPackageInfoFromNimble(path: string; result: var PackageInfo) =
else:
raise newException(ValueError, "Cannot open package info: " & path)
proc readPackageInfoFromNims(scriptName: string, options: Options,
result: var PackageInfo) =
let
iniFile = getIniFile(scriptName, options)
if iniFile.fileExists():
readPackageInfoFromNimble(iniFile, result)
proc inferInstallRules(pkgInfo: var PackageInfo, options: Options) =
# Binary packages shouldn't install .nim files by default.
# (As long as the package info doesn't explicitly specify what should be
# installed.)
let installInstructions =
pkgInfo.installDirs.len + pkgInfo.installExt.len + pkgInfo.installFiles.len
if installInstructions == 0 and pkgInfo.bin.len > 0:
pkgInfo.skipExt.add("nim")
# When a package doesn't specify a `srcDir` it's fair to assume that
# the .nim files are in the root of the package. So we can explicitly select
# them and prevent the installation of anything else. The user can always
# override this with `installFiles`.
if pkgInfo.srcDir == "":
if dirExists(pkgInfo.getRealDir() / pkgInfo.name):
pkgInfo.installDirs.add(pkgInfo.name)
if fileExists(pkgInfo.getRealDir() / pkgInfo.name.addFileExt("nim")):
pkgInfo.installFiles.add(pkgInfo.name.addFileExt("nim"))
proc readPackageInfo(nf: NimbleFile, options: Options,
onlyMinimalInfo=false): PackageInfo =
## Reads package info from the specified Nimble file.
@ -389,9 +351,6 @@ proc readPackageInfo(nf: NimbleFile, options: Options,
if version.kind == verSpecial:
result.specialVersion = minimalInfo.version
# Apply rules to infer which files should/shouldn't be installed. See #469.
inferInstallRules(result, options)
if not result.isMinimal:
options.pkgInfoCache[nf] = result
@ -487,15 +446,6 @@ proc toFullInfo*(pkg: PackageInfo, options: Options): PackageInfo =
else:
return pkg
proc getConcreteVersion*(pkgInfo: PackageInfo, options: Options): string =
## Returns a non-special version from the specified ``pkgInfo``. If the
## ``pkgInfo`` is minimal it looks it up and retrieves the concrete version.
result = pkgInfo.version
if pkgInfo.isMinimal:
let pkgInfo = pkgInfo.toFullInfo(options)
result = pkgInfo.version
assert(not newVersion(result).isSpecial)
when isMainModule:
validatePackageName("foo_bar")
validatePackageName("f_oo_b_a_r")

View file

@ -5,8 +5,8 @@
## nim-lang/packages automatically.
import system except TResult
import httpclient, strutils, json, os, browsers, times, uri
import version, tools, common, cli, config, options
import httpclient, base64, strutils, rdstdin, json, os, browsers, times, uri
import tools, common, cli, config, options
type
Auth = object
@ -62,7 +62,7 @@ proc getGithubAuth(o: Options): Auth =
# try to read from disk, if it cannot be found write a new one
try:
let apiTokenFilePath = cfg.nimbleDir / ApiKeyFile
result.token = readFile(apiTokenFilePath).strip()
result.token = readFile(apiTokenFilePath)
display("Info:", "Using GitHub API Token in file: " & apiTokenFilePath,
priority = HighPriority)
except IOError:
@ -76,7 +76,7 @@ proc getGithubAuth(o: Options): Auth =
proc isCorrectFork(j: JsonNode): bool =
# Check whether this is a fork of the nimble packages repo.
result = false
if j{"fork"}.getBool():
if j{"fork"}.getBVal():
result = j{"parent"}{"full_name"}.getStr() == "nim-lang/packages"
proc forkExists(a: Auth): bool =
@ -155,7 +155,7 @@ proc editJson(p: PackageInfo; url, tags, downloadMethod: string) =
proc publish*(p: PackageInfo, o: Options) =
## Publishes the package p.
let auth = getGithubAuth(o)
var pkgsDir = getNimbleUserTempDir() / "nimble-packages-fork"
var pkgsDir = getTempDir() / "nimble-packages-fork"
if not forkExists(auth):
createFork(auth)
display("Info:", "Waiting 10s to let Github create a fork",
@ -213,14 +213,11 @@ proc publish*(p: PackageInfo, o: Options) =
url = promptCustom("Github URL of " & p.name & "?", "")
if url.len == 0: userAborted()
let tags = promptCustom(
"Whitespace separated list of tags? (For example: web library wrapper)",
""
)
let tags = promptCustom("Whitespace separated list of tags?", "")
cd pkgsDir:
editJson(p, url, tags, downloadMethod)
let branchName = "add-" & p.name & getTime().utc.format("HHmm")
let branchName = "add-" & p.name & getTime().getGMTime().format("HHmm")
doCmd("git checkout -B " & branchName)
doCmd("git commit packages.json -m \"Added package " & p.name & "\"")
display("Pushing", "to remote of fork.", priority = HighPriority)

View file

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

View file

@ -3,7 +3,7 @@
#
# Various miscellaneous utility functions reside here.
import osproc, pegs, strutils, os, uri, sets, json, parseutils
import version, cli
import version, common, cli
proc extractBin(cmd: string): string =
if cmd[0] == '"':
@ -11,7 +11,7 @@ proc extractBin(cmd: string): string =
else:
return cmd.split(' ')[0]
proc doCmd*(cmd: string, showOutput = false, showCmd = false) =
proc doCmd*(cmd: string, showOutput = false) =
let bin = extractBin(cmd)
if findExe(bin) == "":
raise newException(NimbleError, "'" & bin & "' not in PATH.")
@ -20,10 +20,7 @@ proc doCmd*(cmd: string, showOutput = false, showCmd = false) =
stdout.flushFile()
stderr.flushFile()
if showCmd:
display("Executing", cmd, priority = MediumPriority)
else:
displayDebug("Executing", cmd)
displayDebug("Executing", cmd)
if showOutput:
let exitCode = execCmd(cmd)
displayDebug("Finished", "with exit code " & $exitCode)
@ -109,10 +106,6 @@ proc copyDirD*(fro, to: string): seq[string] =
createDir(changeRoot(fro, to, path.splitFile.dir))
result.add copyFileD(path, changeRoot(fro, to, path))
proc createDirD*(dir: string) =
display("Creating", "directory $#" % dir, priority = LowPriority)
createDir(dir)
proc getDownloadDirName*(uri: string, verRange: VersionRange): string =
## Creates a directory name based on the specified ``uri`` (url)
result = ""
@ -151,15 +144,6 @@ proc contains*(j: JsonNode, elem: tuple[key: string, val: JsonNode]): bool =
when not defined(windows):
from posix import getpid
proc getProcessId*(): string =
when defined(windows):
proc GetCurrentProcessId(): int32 {.stdcall, dynlib: "kernel32",
importc: "GetCurrentProcessId".}
result = $GetCurrentProcessId()
else:
result = $getpid()
proc getNimbleTempDir*(): string =
## Returns a path to a temporary directory.
##
@ -167,18 +151,10 @@ proc getNimbleTempDir*(): string =
## different for different runs of it. You have to make sure to create it
## first. In release builds the directory will be removed when nimble finishes
## its work.
result = getTempDir() / "nimble_" & getProcessId()
proc getNimbleUserTempDir*(): string =
## Returns a path to a temporary directory.
##
## The returned path will be the same for the duration of the process but
## different for different runs of it. You have to make sure to create it
## first. In release builds the directory will be removed when nimble finishes
## its work.
var tmpdir: string
if existsEnv("TMPDIR") and existsEnv("USER"):
tmpdir = joinPath(getEnv("TMPDIR"), getEnv("USER"))
result = getTempDir() / "nimble_"
when defined(windows):
proc GetCurrentProcessId(): int32 {.stdcall, dynlib: "kernel32",
importc: "GetCurrentProcessId".}
result.add($GetCurrentProcessId())
else:
tmpdir = getTempDir()
return tmpdir
result.add($getpid())

View file

@ -93,11 +93,6 @@ proc `==`*(ver: Version, ver2: Version): bool =
else:
return false
proc cmp*(a, b: Version): int =
if a < b: -1
elif a > b: 1
else: 0
proc `<=`*(ver: Version, ver2: Version): bool =
return (ver == ver2) or (ver < ver2)
@ -135,32 +130,34 @@ proc contains*(ran: VersionRange, ver: Version): bool =
return withinRange(ver, ran)
proc makeRange*(version: string, op: string): VersionRange =
new(result)
if version == "":
raise newException(ParseVersionError,
"A version needs to accompany the operator.")
case op
of ">":
result = VersionRange(kind: verLater)
result.kind = verLater
of "<":
result = VersionRange(kind: verEarlier)
result.kind = verEarlier
of ">=":
result = VersionRange(kind: verEqLater)
result.kind = verEqLater
of "<=":
result = VersionRange(kind: verEqEarlier)
of "", "==":
result = VersionRange(kind: verEq)
result.kind = verEqEarlier
of "":
result.kind = verEq
else:
raise newException(ParseVersionError, "Invalid operator: " & op)
result.ver = Version(version)
proc parseVersionRange*(s: string): VersionRange =
# >= 1.5 & <= 1.8
new(result)
if s.len == 0:
result = VersionRange(kind: verAny)
result.kind = verAny
return
if s[0] == '#':
result = VersionRange(kind: verSpecial)
result.kind = verSpecial
result.spe = s.Version
return
@ -172,7 +169,7 @@ proc parseVersionRange*(s: string): VersionRange =
of '>', '<', '=':
op.add(s[i])
of '&':
result = VersionRange(kind: verIntersect)
result.kind = verIntersect
result.verILeft = makeRange(version, op)
# Parse everything after &
@ -185,14 +182,14 @@ proc parseVersionRange*(s: string): VersionRange =
raise newException(ParseVersionError,
"Having more than one `&` in a version range is pointless")
return
break
of '0'..'9', '.':
version.add(s[i])
of ' ':
# Make sure '0.9 8.03' is not allowed.
if version != "" and i < s.len - 1:
if version != "" and i < s.len:
if s[i+1] in {'0'..'9', '.'}:
raise newException(ParseVersionError,
"Whitespace is not allowed in a version literal.")
@ -207,10 +204,10 @@ proc toVersionRange*(ver: Version): VersionRange =
## Converts a version to either a verEq or verSpecial VersionRange.
new(result)
if ver.isSpecial:
result = VersionRange(kind: verSpecial)
result.kind = verSpecial
result.spe = ver
else:
result = VersionRange(kind: verEq)
result.kind = verEq
result.ver = ver
proc parseRequires*(req: string): PkgTuple =
@ -266,18 +263,21 @@ proc getSimpleString*(verRange: VersionRange): string =
result = ""
proc newVRAny*(): VersionRange =
result = VersionRange(kind: verAny)
new(result)
result.kind = verAny
proc newVREarlier*(ver: string): VersionRange =
result = VersionRange(kind: verEarlier)
new(result)
result.kind = verEarlier
result.ver = newVersion(ver)
proc newVREq*(ver: string): VersionRange =
result = VersionRange(kind: verEq)
new(result)
result.kind = verEq
result.ver = newVersion(ver)
proc findLatest*(verRange: VersionRange,
versions: OrderedTable[Version, string]): tuple[ver: Version, tag: string] =
versions: Table[Version, string]): tuple[ver: Version, tag: string] =
result = (newVersion(""), "")
for ver, tag in versions:
if not withinRange(ver, verRange): continue
@ -291,17 +291,16 @@ when isMainModule:
doAssert(newVersion("1.0") < newVersion("1.4"))
doAssert(newVersion("1.0.1") > newVersion("1.0"))
doAssert(newVersion("1.0.6") <= newVersion("1.0.6"))
doAssert(not withinRange(newVersion("0.1.0"), parseVersionRange("> 0.1")))
#doAssert(not withinRange(newVersion("0.1.0"), parseVersionRange("> 0.1")))
doAssert(not (newVersion("0.1.0") < newVersion("0.1")))
doAssert(not (newVersion("0.1.0") > newVersion("0.1")))
doAssert(newVersion("0.1.0") < newVersion("0.1.0.0.1"))
doAssert(newVersion("0.1.0") <= newVersion("0.1"))
var inter1 = parseVersionRange(">= 1.0 & <= 1.5")
doAssert(inter1.kind == verIntersect)
var inter2 = parseVersionRange("1.0")
doAssert(inter2.kind == verEq)
doAssert(parseVersionRange("== 3.4.2") == parseVersionRange("3.4.2"))
#echo(parseVersionRange(">= 0.8 0.9"))
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))
@ -315,11 +314,8 @@ when isMainModule:
doAssert(newVersion("") < newVersion("1.0.0"))
doAssert(newVersion("") < newVersion("0.1.0"))
var versions = toOrderedTable[Version, string]({
newVersion("0.1.1"): "v0.1.1",
newVersion("0.2.3"): "v0.2.3",
newVersion("0.5"): "v0.5"
})
var versions = toTable[Version, string]({newVersion("0.1.1"): "v0.1.1",
newVersion("0.2.3"): "v0.2.3", newVersion("0.5"): "v0.5"})
doAssert findLatest(parseVersionRange(">= 0.1 & <= 0.4"), versions) ==
(newVersion("0.2.3"), "v0.2.3")
@ -355,7 +351,4 @@ when isMainModule:
doAssert toVersionRange(newVersion("#head")).kind == verSpecial
doAssert toVersionRange(newVersion("0.2.0")).kind == verEq
# Something raised on IRC
doAssert newVersion("1") == newVersion("1.0")
echo("Everything works!")

8
tests/.gitignore vendored
View file

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

View file

@ -1,10 +0,0 @@
# Package
version = "0.1.0"
author = "Dominik Picheta"
description = "Test package"
license = "BSD"
# Dependencies
requires "nim >= 0.12.1"

View file

@ -6,7 +6,6 @@ description = "hybrid"
license = "MIT"
bin = @["hybrid"]
installExt = @["nim"]
# Dependencies

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,12 +0,0 @@
# Package
version = "0.1.0"
author = "Author"
description = "dummy"
license = "MIT"
# Dependencies
requires "nim >= 0.17.0"
bin = @["test.nim"]

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

@ -2,9 +2,7 @@
version = "0.1.0"
author = "Dominik Picheta"
description = """Test package
with multi-line description
"""
description = "Test package"
license = "BSD"
bin = @["nimscript"]
@ -26,12 +24,9 @@ task cr, "Testing `nimble c -r nimscript.nim` via setCommand":
task repeated, "Testing `nimble c nimscript.nim` with repeated flags":
--define: foo
--define: bar
--define: "quoted"
--define: "quoted\\\"with\\\"quotes"
setCommand "c", "nimscript.nim"
task api, "Testing nimscriptapi module functionality":
doAssert(findExe("nim").len != 0)
echo("PKG_DIR: ", getPkgDir())
before hooks:
@ -53,10 +48,4 @@ before install:
echo("Before PkgDir: ", getPkgDir())
after install:
echo("After PkgDir: ", getPkgDir())
before build:
echo("Before build")
after build:
echo("After build")
echo("After PkgDir: ", getPkgDir())

View file

@ -1,15 +0,0 @@
# Package
version = "0.1.0"
author = "Dominik Picheta"
description = "Correctly structured package myPkg. See #469."
license = "MIT"
# This is inferred implicitly by Nimble:
# installDirs = @["myPkg"]
# installFiles = @["myPkg.nim"]
# Dependencies
requires "nim >= 0.15.0"

View file

@ -1,13 +0,0 @@
# Package
version = "0.1.0"
author = "Dominik Picheta"
description = "Correctly structured package Y"
license = "MIT"
bin = @["y"]
# Dependencies
requires "nim >= 0.15.0"

View file

@ -5,7 +5,6 @@ author = "Dominik Picheta"
description = "Incorrectly structured package Y"
license = "MIT"
installExt = @["nim"]
bin = @["y"]
# Dependencies

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

@ -1,25 +0,0 @@
# Package
version = "0.1.0"
author = "Dominik Picheta"
description = "Test package"
license = "BSD"
# Dependencies
requires "nim >= 0.12.1"
let
callNimble = getEnv("NIMBLE_TEST_BINARY_PATH")
doAssert callNimble.len != 0, "NIMBLE_TEST_BINARY_PATH not set"
task recurse, "Level 1":
echo 1
exec callNimble & " recurse2"
task recurse2, "Level 2":
echo 2
exec callNimble & " recurse3"
task recurse3, "Level 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,4 +0,0 @@
version = "0.1.0"
author = "John Doe"
description = "Nimble Test"
license = "BSD"

View file

@ -1,2 +0,0 @@
import os
echo(getCurrentDir())

View file

@ -1,4 +0,0 @@
proc myFunc*() =
echo "Executing my func"

View file

@ -1,4 +0,0 @@
version = "0.1.0"
author = "John Doe"
description = "Nimble Test"
license = "BSD"

View file

@ -1 +0,0 @@
echo "Should be ignored"

View file

@ -1 +0,0 @@
echo "Should be ignored"

View file

@ -1,7 +0,0 @@
import testing123, unittest
test "can accept":
echo "First test"
myFunc()

View file

@ -1,6 +1,6 @@
# Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info.
import osproc, unittest, strutils, os, sequtils, sugar, strformat
import osproc, streams, unittest, strutils, os, sequtils, future
# TODO: Each test should start off with a clean slate. Currently installed
# packages are shared between each test which causes a multitude of issues
@ -10,10 +10,6 @@ var rootDir = getCurrentDir().parentDir()
var nimblePath = rootDir / "src" / addFileExt("nimble", ExeExt)
var installDir = rootDir / "tests" / "nimbleDir"
const path = "../src/nimble"
const stringNotFound = -1
# Set env var to propagate nimble binary path
putEnv("NIMBLE_TEST_BINARY_PATH", nimblePath)
# Clear nimble dir.
removeDir(installDir)
@ -36,23 +32,14 @@ template cd*(dir: string, body: untyped) =
proc execNimble(args: varargs[string]): tuple[output: string, exitCode: int] =
var quotedArgs = @args
quotedArgs.insert("--nimbleDir:" & installDir)
quotedArgs.insert(nimblePath)
quotedArgs = quotedArgs.map((x: string) => x.quoteShell)
quotedArgs.add("--nimbleDir:" & installDir)
quotedArgs = quotedArgs.map((x: string) => ("\"" & x & "\""))
let path {.used.} = getCurrentDir().parentDir() / "src"
var cmd =
when not defined(windows):
"PATH=" & path & ":$PATH " & quotedArgs.join(" ")
else:
quotedArgs.join(" ")
when defined(macosx):
# TODO: Yeah, this is really specific to my machine but for my own sanity...
cmd = "DYLD_LIBRARY_PATH=/usr/local/opt/openssl@1.1/lib " & cmd
let path = getCurrentDir().parentDir() / "src"
let cmd = "PATH=" & path & ":$PATH " & quotedArgs.join(" ")
result = execCmdEx(cmd)
checkpoint(cmd)
checkpoint(result.output)
proc execNimbleYes(args: varargs[string]): tuple[output: string, exitCode: int]=
@ -76,56 +63,6 @@ proc inLines(lines: seq[string], line: string): bool =
for i in lines:
if line.normalize in i.normalize: return true
proc hasLineStartingWith(lines: seq[string], prefix: string): bool =
for line in lines:
if line.strip(trailing = false).startsWith(prefix):
return true
return false
test "issue 708":
cd "issue708":
# TODO: We need a way to filter out compiler messages from the messages
# written by our nimble scripts.
var (output, exitCode) = execNimble("install", "-y", "--verbose")
check exitCode == QuitSuccess
let lines = output.strip.processOutput()
check(inLines(lines, "hello"))
check(inLines(lines, "hello2"))
test "issue 564":
cd "issue564":
var (_, exitCode) = execNimble("build")
check exitCode == QuitSuccess
test "depsOnly + flag order test":
var (output, exitCode) = execNimble(
"--depsOnly", "install", "-y", "https://github.com/nimble-test/packagebin2"
)
check(not output.contains("Success: packagebin2 installed successfully."))
check exitCode == QuitSuccess
test "nimscript evaluation error message":
cd "invalidPackage":
var (output, exitCode) = execNimble("check")
let lines = output.strip.processOutput()
check(lines[^2].endsWith("Error: undeclared identifier: 'thisFieldDoesNotExist'"))
check exitCode == QuitFailure
test "caching of nims and ini detects changes":
cd "caching":
var (output, exitCode) = execNimble("dump")
check output.contains("0.1.0")
let
nfile = "caching.nimble"
writeFile(nfile, readFile(nfile).replace("0.1.0", "0.2.0"))
(output, exitCode) = execNimble("dump")
check output.contains("0.2.0")
writeFile(nfile, readFile(nfile).replace("0.2.0", "0.1.0"))
test "tasks can be called recursively":
cd "recursive":
check execNimble("recurse").exitCode == QuitSuccess
test "picks #head when looking for packages":
cd "versionClashes" / "aporiaScenario":
let (output, exitCode) = execNimble("install", "-y", "--verbose")
@ -151,12 +88,12 @@ test "can build with #head and versioned package (#289)":
test "can validate package structure (#144)":
# Test that no warnings are produced for correctly structured packages.
for package in ["a", "b", "c", "validBinary", "softened"]:
for package in ["a", "b", "c"]:
cd "packageStructure/" & package:
let (output, exitCode) = execNimble(["install", "-y"])
check exitCode == QuitSuccess
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.
for package in ["x", "y", "z"]:
@ -167,22 +104,19 @@ test "can validate package structure (#144)":
checkpoint(output)
case package
of "x":
check lines.hasLineStartingWith(
"Warning: Package 'x' has an incorrect structure. It should" &
" contain a single directory hierarchy for source files," &
" named 'x', but file 'foobar.nim' is in a directory named" &
" 'incorrect' instead.")
check inLines(lines, "Package 'x' has an incorrect structure. It should" &
" contain a single directory hierarchy for source files," &
" named 'x', but file 'foobar.nim' is in a directory named" &
" 'incorrect' instead.")
of "y":
check lines.hasLineStartingWith(
"Warning: Package 'y' has an incorrect structure. It should" &
" contain a single directory hierarchy for source files," &
" named 'ypkg', but file 'foobar.nim' is in a directory named" &
" 'yWrong' instead.")
check inLines(lines, "Package 'y' has an incorrect structure. It should" &
" contain a single directory hierarchy for source files," &
" named 'ypkg', but file 'foobar.nim' is in a directory named" &
" 'yWrong' instead.")
of "z":
check lines.hasLineStartingWith(
"Warning: Package 'z' has an incorrect structure. The top level" &
" of the package source directory should contain at most one module," &
" named 'z.nim', but a file named 'incorrect.nim' was found.")
check inLines(lines, "Package 'z' has an incorrect structure. The top level" &
" of the package source directory should contain at most one module," &
" named 'z.nim', but a file named 'incorrect.nim' was found.")
else:
assert false
@ -275,7 +209,7 @@ test "can refresh with local package list":
[PackageList]
name = "local"
path = "$1"
""".unindent % (getCurrentDir() / "issue368" / "packages.json").replace("\\", "\\\\"))
""".unindent % (getCurrentDir() / "issue368" / "packages.json"))
let (output, exitCode) = execNimble(["refresh", "--verbose"])
let lines = output.strip.processOutput()
check inLines(lines, "config file at")
@ -318,20 +252,11 @@ suite "nimscript":
cd "nimscript":
let (output, exitCode) = execNimble(["install", "-y"])
check exitCode == QuitSuccess
check output.contains("Before build")
check output.contains("After build")
let lines = output.strip.processOutput()
check lines[0].startsWith("Before PkgDir:")
check lines[0].endsWith("tests" / "nimscript")
check lines[0].endsWith("tests/nimscript")
check lines[^1].startsWith("After PkgDir:")
check lines[^1].endsWith("tests" / "nimbleDir" / "pkgs" / "nimscript-0.1.0")
test "before/after on build":
cd "nimscript":
let (output, exitCode) = execNimble(["build"])
check exitCode == QuitSuccess
check output.contains("Before build")
check output.contains("After build")
check lines[^1].endsWith("tests/nimbleDir/pkgs/nimscript-0.1.0")
test "can execute nimscript tasks":
cd "nimscript":
@ -368,8 +293,7 @@ suite "nimscript":
test "can list nimscript tasks":
cd "nimscript":
let (output, exitCode) = execNimble("tasks")
check "work".normalize in output.normalize
check "test description".normalize in output.normalize
check "work test description".normalize in output.normalize
check exitCode == QuitSuccess
test "can use pre/post hooks":
@ -488,12 +412,9 @@ test "issue #349":
check code == QuitFailure
check inLines(msg,
"\"$1\" is an invalid package name: reserved name" % name)
try:
removeFile(name.changeFileExt("nimble"))
removeDir("src")
removeDir("tests")
except OSError:
discard
removeFile(name.changeFileExt("nimble"))
removeDir("src")
removeDir("tests")
for reserved in reservedNames:
checkName(reserved.toUpperAscii())
@ -517,7 +438,8 @@ test "can uninstall":
let ls = outp.strip.processOutput()
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", "issue27a").exitCode == QuitSuccess
@ -599,15 +521,9 @@ suite "can handle two binary versions":
cd "binaryPackage/v2":
check execNimble("install", "-y").exitCode == QuitSuccess
var
cmd = installDir / "bin" / "binaryPackage"
when defined(windows):
cmd = "cmd /c " & cmd & ".cmd"
test "can execute v2":
let (output, exitCode) =
execCmdEx(cmd)
execCmdEx(installDir / "bin" / "binaryPackage".addFileExt(ExeExt))
check exitCode == QuitSuccess
check output.strip() == "v2"
@ -615,7 +531,7 @@ suite "can handle two binary versions":
check execNimble("remove", "binaryPackage@2.0", "-y").exitCode==QuitSuccess
let (output, exitCode) =
execCmdEx(cmd)
execCmdEx(installDir / "bin" / "binaryPackage".addFileExt(ExeExt))
check exitCode == QuitSuccess
check output.strip() == "v1"
@ -623,7 +539,7 @@ suite "can handle two binary versions":
check execNimble("remove", "binaryPackage@1.0", "-y").exitCode==QuitSuccess
let (output, exitCode) =
execCmdEx(cmd)
execCmdEx(installDir / "bin" / "binaryPackage".addFileExt(ExeExt))
check exitCode == QuitSuccess
check output.strip() == "v2"
@ -636,12 +552,6 @@ test "can pass args with spaces to Nim (#351)":
checkpoint output
check exitCode == QuitSuccess
test "error if `bin` is a source file (#597)":
cd "issue597":
var (output, exitCode) = execNimble("build")
check exitCode != QuitSuccess
check output.contains("entry should not be a source file: test.nim")
suite "reverse dependencies":
test "basic test":
cd "revdep/mydep":
@ -653,27 +563,6 @@ suite "reverse dependencies":
verify execNimbleYes("remove", "pkgA")
verify execNimbleYes("remove", "mydep")
test "revdep fail test":
cd "revdep/mydep":
verify execNimbleYes("install")
cd "revdep/pkgWithDep":
verify execNimbleYes("install")
let (output, exitCode) = execNimble("uninstall", "mydep")
checkpoint output
check output.processOutput.inLines("cannot uninstall mydep")
check exitCode == QuitFailure
test "revdep -i test":
cd "revdep/mydep":
verify execNimbleYes("install")
cd "revdep/pkgWithDep":
verify execNimbleYes("install")
verify execNimbleYes("remove", "mydep", "-i")
test "issue #373":
cd "revdep/mydep":
verify execNimbleYes("install")
@ -705,8 +594,8 @@ suite "develop feature":
check fileExists(path)
let split = readFile(path).processOutput()
check split.len == 2
check split[0].endsWith("develop" / "hybrid" / "hybrid.nimble")
check split[1].endsWith("develop" / "hybrid")
check split[0].endsWith("develop/hybrid/hybrid.nimble")
check split[1].endsWith("develop/hybrid")
test "can develop with srcDir":
cd "develop/srcdirtest":
@ -720,11 +609,11 @@ suite "develop feature":
check fileExists(path)
let split = readFile(path).processOutput()
check split.len == 2
check split[0].endsWith("develop" / "srcdirtest" / "srcdirtest.nimble")
check split[1].endsWith("develop" / "srcdirtest" / "src")
check split[0].endsWith("develop/srcdirtest/srcdirtest.nimble")
check split[1].endsWith("develop/srcdirtest/src")
cd "develop/dependent":
let (output, exitCode) = execNimble("c", "-r", "src" / "dependent.nim")
let (output, exitCode) = execNimble("c", "-r", "src/dependent.nim")
checkpoint output
check(output.processOutput.inLines("hello"))
check exitCode == QuitSuccess
@ -754,20 +643,10 @@ suite "develop feature":
check exitCode == QuitSuccess
(output, exitCode) = execNimble("path", "srcdirtest")
checkpoint output
check exitCode == QuitSuccess
check output.strip() == getCurrentDir() / "src"
suite "path command":
test "can get correct path for srcDir (#531)":
check execNimble("uninstall", "srcdirtest", "-y").exitCode == QuitSuccess
cd "develop/srcdirtest":
let (_, exitCode) = execNimble("install", "-y")
check exitCode == QuitSuccess
let (output, _) = execNimble("path", "srcdirtest")
check output.strip() == installDir / "pkgs" / "srcdirtest-1.0"
suite "test command":
test "Runs passing unit tests":
cd "testCommand/testsPass":
@ -792,19 +671,6 @@ suite "test command":
check exitCode == QuitSuccess
check outp.processOutput.inLines("overriden")
test "certain files are ignored":
cd "testCommand/testsIgnore":
let (outp, exitCode) = execNimble("test")
check exitCode == QuitSuccess
check(not outp.processOutput.inLines("Should be ignored"))
check outp.processOutput.inLines("First test")
test "CWD is root of package":
cd "testCommand/testsCWD":
let (outp, exitCode) = execNimble("test")
check exitCode == QuitSuccess
check outp.processOutput.inLines(getCurrentDir())
suite "check command":
test "can succeed package":
cd "binaryPackage/v1":
@ -847,199 +713,4 @@ suite "multi":
test "can develop package from git subdir":
removeDir("nimble-test/multi")
let args = ["develop", "-y", "https://github.com/nimble-test/multi?subdir=beta"]
check execNimble(args).exitCode == QuitSuccess
suite "Module tests":
test "version":
cd "..":
check execCmdEx("nim c -r src/nimblepkg/version").exitCode == QuitSuccess
test "reversedeps":
cd "..":
check execCmdEx("nim c -r src/nimblepkg/reversedeps").exitCode == QuitSuccess
test "packageparser":
cd "..":
check execCmdEx("nim c -r src/nimblepkg/packageparser").exitCode == QuitSuccess
test "packageinfo":
cd "..":
check execCmdEx("nim c -r src/nimblepkg/packageinfo").exitCode == QuitSuccess
test "cli":
cd "..":
check execCmdEx("nim c -r src/nimblepkg/cli").exitCode == QuitSuccess
test "download":
cd "..":
check execCmdEx("nim c -r src/nimblepkg/download").exitCode == QuitSuccess
test "init does not overwrite existing files (#581)":
createDir("issue581/src")
cd "issue581":
const Src = "echo \"OK\""
writeFile("src/issue581.nim", Src)
check execNimbleYes("init").exitCode == QuitSuccess
check readFile("src/issue581.nim") == Src
removeDir("issue581")
test "remove skips packages with revDeps (#504)":
check execNimble("install", "nimboost@0.5.5", "nimfp@0.4.4", "-y").exitCode == QuitSuccess
var (output, exitCode) = execNimble("uninstall", "nimboost", "nimfp", "-n")
var lines = output.strip.processOutput()
check inLines(lines, "Cannot uninstall nimboost")
(output, exitCode) = execNimble("uninstall", "nimfp", "nimboost", "-y")
lines = output.strip.processOutput()
check (not inLines(lines, "Cannot uninstall nimboost"))
check execNimble("path", "nimboost").exitCode != QuitSuccess
check execNimble("path", "nimfp").exitCode != QuitSuccess
test "pass options to the compiler with `nimble install`":
cd "passNimFlags":
check execNimble("install", "--passNim:-d:passNimIsWorking").exitCode == QuitSuccess
test "do not install single dependency multiple times (#678)":
# for the test to be correct, the tested package and its dependencies must not
# exist in the local cache
removeDir("nimbleDir")
cd "issue678":
testRefresh():
writeFile(configFile, """
[PackageList]
name = "local"
path = "$1"
""".unindent % (getCurrentDir() / "packages.json").replace("\\", "\\\\"))
check execNimble(["refresh"]).exitCode == QuitSuccess
let (output, exitCode) = execNimble("install", "-y")
check exitCode == QuitSuccess
let index = output.find("issue678_dependency_1@0.1.0 already exists")
check index == stringNotFound
test "Passing command line arguments to a task (#633)":
cd "issue633":
var (output, exitCode) = execNimble("testTask --testTask")
check exitCode == QuitSuccess
check output.contains("Got it")
suite "nimble run":
test "Invalid binary":
cd "run":
var (output, exitCode) = execNimble(
"--debug", # Flag to enable debug verbosity in Nimble
"run", # Run command invokation
"blahblah", # The command to run
)
check exitCode == QuitFailure
check output.contains("Binary '$1' is not defined in 'run' package." %
"blahblah".changeFileExt(ExeExt))
test "Parameters passed to executable":
cd "run":
var (output, exitCode) = execNimble(
"--debug", # Flag to enable debug verbosity in Nimble
"run", # Run command invokation
"run", # The command to run
"--debug", # First argument passed to the executed command
"check" # Second argument passed to the executed command.
)
check exitCode == QuitSuccess
check output.contains("tests$1run$1$2 --debug check" %
[$DirSep, "run".changeFileExt(ExeExt)])
check output.contains("""Testing `nimble run`: @["--debug", "check"]""")
test "Parameters not passed to single executable":
cd "run":
var (output, exitCode) = execNimble(
"--debug", # Flag to enable debug verbosity in Nimble
"run", # Run command invokation
"--debug" # First argument passed to the executed command
)
check exitCode == QuitSuccess
check output.contains("tests$1run$1$2 --debug" %
[$DirSep, "run".changeFileExt(ExeExt)])
check output.contains("""Testing `nimble run`: @["--debug"]""")
test "Parameters passed to single executable":
cd "run":
var (output, exitCode) = execNimble(
"--debug", # Flag to enable debug verbosity in Nimble
"run", # Run command invokation
"--", # Flag to set run file to "" before next argument
"--debug", # First argument passed to the executed command
"check" # Second argument passed to the executed command.
)
check exitCode == QuitSuccess
check output.contains("tests$1run$1$2 --debug check" %
[$DirSep, "run".changeFileExt(ExeExt)])
check output.contains("""Testing `nimble run`: @["--debug", "check"]""")
test "Executable output is shown even when not debugging":
cd "run":
var (output, exitCode) =
execNimble("run", "run", "--option1", "arg1")
check exitCode == QuitSuccess
check output.contains("""Testing `nimble run`: @["--option1", "arg1"]""")
test "Quotes and whitespace are well handled":
cd "run":
var (output, exitCode) = execNimble(
"run", "run", "\"", "\'", "\t", "arg with spaces"
)
check exitCode == QuitSuccess
check output.contains(
"""Testing `nimble run`: @["\"", "\'", "\t", "arg with spaces"]"""
)
test "NimbleVersion is defined":
cd "nimbleVersionDefine":
var (output, exitCode) = execNimble("c", "-r", "src/nimbleVersionDefine.nim")
check output.contains("0.1.0")
check exitCode == QuitSuccess
var (output2, exitCode2) = execNimble("run", "nimbleVersionDefine")
check output2.contains("0.1.0")
check exitCode2 == QuitSuccess
test "issue 432":
cd "issue432":
check execNimble("install", "-y", "--depsOnly").exitCode == QuitSuccess
check execNimble("install", "-y", "--depsOnly").exitCode == QuitSuccess
test "compilation without warnings":
const buildDir = "./buildDir/"
const filesToBuild = [
"../src/nimble.nim",
"../src/nimblepkg/nimscriptapi.nim",
"./tester.nim",
]
proc execBuild(fileName: string): tuple[output: string, exitCode: int] =
result = execCmdEx(
fmt"nim c -o:{buildDir/fileName.splitFile.name} {fileName}")
proc checkOutput(output: string): uint =
const warningsToCheck = [
"[UnusedImport]",
"[Deprecated]",
"[XDeclaredButNotUsed]",
]
for line in output.splitLines():
for warning in warningsToCheck:
if line.find(warning) != stringNotFound:
once: checkpoint("Detected warnings:")
checkpoint(line)
inc(result)
removeDir(buildDir)
var linesWithWarningsCount: uint = 0
for file in filesToBuild:
let (output, exitCode) = execBuild(file)
check exitCode == QuitSuccess
linesWithWarningsCount += checkOutput(output)
check linesWithWarningsCount == 0
check execNimble(args).exitCode == QuitSuccess