Merge branch 'master' into native-pkg-support

This commit is contained in:
Araq 2016-12-23 16:01:37 +01:00
commit e813aa6448
14 changed files with 646 additions and 305 deletions

View file

@ -12,34 +12,36 @@ Interested in learning **how to create a package**? Skip directly to that sectio
- [Installation](#installation) - [Installation](#installation)
- [Nimble's folder structure and packages](#nimbles-folder-structure-and-packages) - [Nimble's folder structure and packages](#nimbles-folder-structure-and-packages)
- [Nimble usage](#nimble-usage) - [Nimble usage](#nimble-usage)
- [nimble refresh](#nimble-refresh) - [nimble refresh](#nimble-refresh)
- [nimble install](#nimble-install) - [nimble install](#nimble-install)
- [nimble uninstall](#nimble-uninstall) - [nimble uninstall](#nimble-uninstall)
- [nimble build](#nimble-build) - [nimble build](#nimble-build)
- [nimble c](#nimble-c) - [nimble c](#nimble-c)
- [nimble list](#nimble-list) - [nimble list](#nimble-list)
- [nimble search](#nimble-search) - [nimble search](#nimble-search)
- [nimble path](#nimble-path) - [nimble path](#nimble-path)
- [nimble init](#nimble-init) - [nimble init](#nimble-init)
- [nimble publish](#nimble-publish) - [nimble publish](#nimble-publish)
- [nimble tasks](#nimble-tasks) - [nimble tasks](#nimble-tasks)
- [nimble dump](#nimble-dump) - [nimble dump](#nimble-dump)
- [Configuration](#configuration) - [Configuration](#configuration)
- [Creating Packages](#creating-packages) - [Creating Packages](#creating-packages)
- [The new NimScript format](#the-new-nimscript-format) - [Project structure](#project-structure)
- [Libraries](#libraries) - [Tests](#tests)
- [Binary packages](#binary-packages) - [Libraries](#libraries)
- [Hybrids](#hybrids) - [Binary packages](#binary-packages)
- [Dependencies](#dependencies) - [Hybrids](#hybrids)
- [Nim compiler](#nim-compiler) - [Dependencies](#dependencies)
- [Versions](#versions) - [Nim compiler](#nim-compiler)
- [Submitting your package to the package list.](#submitting-your-package-to-the-package-list) - [Versions](#versions)
- [Releasing a new version](#releasing-a-new-version)
- [Publishing packages](#publishing-packages)
- [.nimble reference](#nimble-reference) - [.nimble reference](#nimble-reference)
- [[Package]](#package) - [[Package]](#package)
- [Required](#required) - [Required](#required)
- [Optional](#optional) - [Optional](#optional)
- [[Deps]/[Dependencies]](#depsdependencies) - [[Deps]/[Dependencies]](#depsdependencies)
- [Optional](#optional) - [Optional](#optional)
- [Troubleshooting](#troubleshooting) - [Troubleshooting](#troubleshooting)
- [Contribution](#contribution) - [Contribution](#contribution)
- [About](#about) - [About](#about)
@ -195,9 +197,10 @@ Similar to the ``install`` command you can specify a version range, for example:
### nimble build ### nimble build
The ``build`` command is mostly used by developers who want to test building The ``build`` command is mostly used by developers who want to test building
their ``.nimble`` package. This command will build the package in debug mode, their ``.nimble`` package. This command will build the package with default
without installing anything. The ``install`` command will build the package flags, i.e. a debug build which includes stack traces but no GDB debug
in release mode instead. information.
The ``install`` command will build the package in release mode instead.
### nimble c ### nimble c
@ -373,6 +376,7 @@ You can also specify multiple dependencies like so:
requires "nim >= 0.10.0", "foobar >= 0.1.0" requires "nim >= 0.10.0", "foobar >= 0.1.0"
requires "fizzbuzz >= 1.0" requires "fizzbuzz >= 1.0"
```
Nimble currently supports installation of packages from a local directory, a Nimble currently supports installation of packages from a local directory, a
git repository and a mercurial repository. The .nimble file must be present in git repository and a mercurial repository. The .nimble file must be present in
@ -436,12 +440,54 @@ also return ``false`` from these blocks to stop further execution.
The ``nimscriptapi.nim`` module specifies this and includes other definitions The ``nimscriptapi.nim`` module specifies this and includes other definitions
which are also useful. Take a look at it for more information. which are also useful. Take a look at it for more information.
### Project structure
There is nothing surprising about the recommended project structure. The advice
resembles that of many other package managers.
| Directory | Purpose |
| ------------- | -------------------------------------- |
| ``.`` | Root directory containing .nimble file.|
| ``./src/`` | Project source code |
| ``./tests/`` | Project test files |
| ``./docs/`` | Project documentation |
#### Tests
A common problem that arises with tests is the fact that they need to import
the associated package. But the package is in the parent directory. This can
be solved in a few different ways:
* Expect that the package has been installed locally into your
``~/.nimble`` directory.
* Use a simple path modification to resolve the package properly.
The latter is highly recommended. Reinstalling the package to test an actively
changing code base is a massive pain.
To modify the path for your tests only, simply add a ``nim.cfg`` file into
your ``tests`` directory with the following contents:
```
--path:"../src/"
```
To make testing even more convenient, you may wish to define a ``test`` task
in your ``.nimble`` file. Like so:
```nim
task "test", "Runs the test suite":
exec "nim c -r tests/tester"
```
You can compile and run a single tester application or multiple test files.
### Libraries ### Libraries
Library packages are likely the most popular form of Nimble packages. They are Library packages are likely the most popular form of Nimble packages. They are
meant to be used by other library packages or the ultimate binary packages. meant to be used by other library or binary packages.
When nimble installs a library it will copy all the files in the package When nimble installs a library it will copy all of its files
into ``$nimbleDir/pkgs/pkgname-ver``. It's up to the package creator to make sure into ``$nimbleDir/pkgs/pkgname-ver``. It's up to the package creator to make sure
that the package directory layout is correct, this is so that users of the that the package directory layout is correct, this is so that users of the
package can correctly import the package. package can correctly import the package.
@ -483,23 +529,23 @@ A package is automatically a binary package as soon as it sets at least one
``bin`` value, like so: ``bin`` value, like so:
```ini ```ini
bin = "main" # NimScript config expects a seq instead, e.g. @["main"] bin = @["main"]
``` ```
In this case when ``nimble install`` is invoked, nimble will build the ``main.nim`` In this case when ``nimble install`` is invoked, nimble will build the ``main.nim``
file, copy it into ``$nimbleDir/pkgs/pkgname-ver/`` and subsequently create a file, copy it into ``$nimbleDir/pkgs/pkgname-ver/`` and subsequently create a
symlink to the binary in ``$nimbleDir/bin/``. On Windows a stub .bat file is symlink to the binary in ``$nimbleDir/bin/``. On Windows a stub .cmd file is
created instead. created instead.
Other files will be copied in the same way as they are for library packages. Other files will be copied in the same way as they are for library packages.
Binary packages should not install .nim files so you should include Binary packages should not install .nim files so include
``SkipExt = "nim"`` in your .nimble file, unless you intend for your package to ``SkipExt = "nim"`` in your .nimble file, unless you intend for your package to
be a binary/library combo which is fine. be a binary/library combo which is fine.
Dependencies are automatically installed before building. Before publishing your Dependencies are automatically installed before building.
package you should ensure that the dependencies you specified are correct. It's a good idea to test that the dependencies you specified are correct by
You can do this by running ``nimble build`` or ``nimble install`` in the directory running by running ``nimble build`` or ``nimble install`` in the directory
of your package. of your package.
### Hybrids ### Hybrids
@ -516,28 +562,28 @@ done for nimble.
### Dependencies ### Dependencies
Dependencies are specified under the ``[Deps]`` section in a nimble file. Dependencies are specified using the ``requires`` function. For example:
The ``requires`` key field is used to specify them. For example:
```ini ```
[Deps] # Dependencies
Requires: "nim >= 0.10.0, jester > 0.1 & <= 0.5" requires "nim >= 0.10.0", "jester > 0.1 & <= 0.5"
``` ```
Dependency lists support version ranges. These versions may either be a concrete Dependency lists support version ranges. These versions may either be a concrete
version like ``0.1``, or they may contain any of the less-than (``<``), version like ``0.1``, or they may contain any of the less-than (``<``),
greater-than (``>``), less-than-or-equal-to (``<=``) and greater-than-or-equal-to greater-than (``>``), less-than-or-equal-to (``<=``) and greater-than-or-equal-to
(``>=``). Two version ranges may be combined using the ``&`` operator for example: (``>=``) oeprators.
``> 0.2 & < 1.0`` which will install a package with the version greater than 0.2 Two version ranges may be combined using the ``&`` operator, for example
``> 0.2 & < 1.0``, which will install a package with the version greater than 0.2
and less than 1.0. and less than 1.0.
Specifying a concrete version as a dependency is not a good idea because your Specifying a concrete version as a dependency is not a good idea because your
package may end up depending on two different versions of the same package. package may end up depending on two different versions of the same package.
If this happens Nimble will refuse to install the package. Similarly you should If this happens Nimble will refuse to install the package.
not specify an upper-bound as this can lead to a similar issue.
In addition to versions you may also specify git/hg tags, branches and commits. In addition to versions you may also specify git/hg tags, branches and commits.
These have to be concrete however. This is done with the ``#`` character, Although these have to be specific; ranges of commits are not supported.
This is done with the ``#`` character,
for example: ``jester#head``. Which will make your package depend on the for example: ``jester#head``. Which will make your package depend on the
latest commit of Jester. latest commit of Jester.
@ -573,11 +619,43 @@ package after checking out the latest version.
You can force the installation of the HEAD of the repository by specifying You can force the installation of the HEAD of the repository by specifying
``#head`` after the package name in your dependency list. ``#head`` after the package name in your dependency list.
## Submitting your package to the package list. #### Releasing a new version
Nimble's packages list is stored on github and everyone is encouraged to add Version releases are done by creating a tag in your Git or Mercurial
their own packages to it! Take a look at repository.
[nim-lang/packages](https://github.com/nim-lang/packages) to learn more.
Whenever you want to release a new version, you should remember to first
increment the version in your ``.nimble`` file and commit your changes. Only
after that is done should you tag the release.
To summarise, the steps for release are:
* Increment the version in your ``.nimble`` file.
* Commit your changes.
* Tag your release, by for example running ``git tag v0.2.0``.
* Push your tags and commits.
Once the new tag is in the remote repository, Nimble will be able to detect
the new version.
##<a name="submitting-your-package-to-the-package-list"> Publishing packages
Publishing packages isn't a requirement. But doing so allows people to associate
a specific name to a URL pointing to your package. This mapping is stored
in an official packages repository located
[here](https://github.com/nim-lang/packages).
This repository contains a ``packages.json`` file which lists all the published
packages. It contains a set of package names with associated metadata. You
can read more about this metadata in the
[readme for the packages repository](https://github.com/nim-lang/packages#readme).
To publish your package you need to fork that repository, and add an entry
into the ``packages.json`` file for your package. Then create a pull request
with your changes. **You only need to do this
once**.
Nimble includes a ``publish`` command which does this for you automatically.
## .nimble reference ## .nimble reference

View file

@ -1,6 +1,8 @@
# Copyright (C) Dominik Picheta. All rights reserved. # Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info. # BSD License. Look at license.txt for more info.
import system except TResult
import httpclient, parseopt, os, osproc, pegs, tables, parseutils, import httpclient, parseopt, os, osproc, pegs, tables, parseutils,
strtabs, json, algorithm, sets, uri strtabs, json, algorithm, sets, uri
@ -10,7 +12,8 @@ from sequtils import toSeq
import nimblepkg/packageinfo, nimblepkg/version, nimblepkg/tools, import nimblepkg/packageinfo, nimblepkg/version, nimblepkg/tools,
nimblepkg/download, nimblepkg/config, nimblepkg/common, nimblepkg/download, nimblepkg/config, nimblepkg/common,
nimblepkg/publish, nimblepkg/options, nimblepkg/packageparser nimblepkg/publish, nimblepkg/options, nimblepkg/packageparser,
nimblepkg/cli
import nimblepkg/nimscriptsupport import nimblepkg/nimscriptsupport
@ -32,24 +35,7 @@ else:
proc GetVersionExA*(VersionInformation: var OSVERSIONINFO): WINBOOL{.stdcall, proc GetVersionExA*(VersionInformation: var OSVERSIONINFO): WINBOOL{.stdcall,
dynlib: "kernel32", importc: "GetVersionExA".} dynlib: "kernel32", importc: "GetVersionExA".}
proc writeVersion() = proc refresh(options: Options) =
echo("nimble v$# compiled at $# $#" %
[nimbleVersion, CompileDate, CompileTime])
quit(QuitSuccess)
proc promptCustom(question, default: string): string =
if default == "":
stdout.write(question, ": ")
let user = stdin.readLine()
if user.len == 0: return promptCustom(question, default)
else: return user
else:
stdout.write(question, " [", default, "]: ")
let user = stdin.readLine()
if user == "": return default
else: return user
proc update(options: Options) =
## Downloads the package list from the specified URL. ## Downloads the package list from the specified URL.
## ##
## If the download is not successful, an exception is raised. ## If the download is not successful, an exception is raised.
@ -60,10 +46,12 @@ proc update(options: Options) =
"" ""
proc downloadList(list: PackageList, options: Options) = proc downloadList(list: PackageList, options: Options) =
echo("Downloading \"", list.name, "\" package list") display("Downloading", list.name & " package list", priority = HighPriority)
var lastError = ""
for i in 0 .. <list.urls.len: for i in 0 .. <list.urls.len:
let url = list.urls[i] let url = list.urls[i]
echo("Trying ", url, "...") display("Trying", url)
let tempPath = options.getNimbleDir() / "packages_temp.json" let tempPath = options.getNimbleDir() / "packages_temp.json"
# Grab the proxy # Grab the proxy
@ -71,25 +59,41 @@ proc update(options: Options) =
if not proxy.isNil: if not proxy.isNil:
var maskedUrl = proxy.url var maskedUrl = proxy.url
if maskedUrl.password.len > 0: maskedUrl.password = "***" if maskedUrl.password.len > 0: maskedUrl.password = "***"
echo("Using proxy ", maskedUrl) display("Connecting", "to proxy at " & $maskedUrl,
priority = LowPriority)
try: try:
downloadFile(url, tempPath, proxy = getProxy(options)) downloadFile(url, tempPath, proxy = getProxy(options))
except: except:
if i == <list.urls.len: let message = "Could not download: " & getCurrentExceptionMsg()
raise display("Warning:", message, Warning)
echo("Could not download: ", getCurrentExceptionMsg()) lastError = message
continue continue
if not validatePackagesList(tempPath): if not validatePackagesList(tempPath):
echo("Downloaded packages.json file is invalid, discarding.") lastError = "Downloaded packages.json file is invalid"
display("Warning:", lastError & ", discarding.", Warning)
continue continue
copyFile(tempPath, copyFile(tempPath,
options.getNimbleDir() / "packages_$1.json" % list.name.toLowerAscii()) options.getNimbleDir() / "packages_$1.json" % list.name.toLowerAscii())
echo("Done.") display("Success", "Package list downloaded.", Success, HighPriority)
lastError = ""
break break
if parameter.isUrl: if lastError.len != 0:
downloadList(PackageList(name: "commandline", urls: @[parameter]), options) raise newException(NimbleError, "Refresh failed\n" & lastError)
if parameter.len > 0:
if parameter.isUrl:
let cmdLine = PackageList(name: "commandline", urls: @[parameter])
downloadList(cmdLine, options)
else:
if parameter notin options.config.packageLists:
let msg = "Package list with the specified name not found."
raise newException(NimbleError, msg)
downloadList(options.config.packageLists[parameter], options)
else: else:
# Try each package list in config # Try each package list in config
for name, list in options.config.packageLists: for name, list in options.config.packageLists:
@ -258,24 +262,31 @@ proc processDeps(pkginfo: PackageInfo, options: Options): seq[string] =
## Returns the list of paths to pass to the compiler during build phase. ## Returns the list of paths to pass to the compiler during build phase.
result = @[] result = @[]
assert(not pkginfo.isMinimal, "processDeps needs pkginfo.requires") assert(not pkginfo.isMinimal, "processDeps needs pkginfo.requires")
display("Verifying",
"dependencies for $1 v$2" % [pkginfo.name, pkginfo.version],
priority = HighPriority)
let pkglist = getInstalledPkgs(options.getPkgsDir(), options) let pkglist = getInstalledPkgs(options.getPkgsDir(), options)
var reverseDeps: seq[tuple[name, version: string]] = @[] var reverseDeps: seq[tuple[name, version: string]] = @[]
for dep in pkginfo.requires: for dep in pkginfo.requires:
if dep.name == "nimrod" or dep.name == "nim": if dep.name == "nimrod" or dep.name == "nim":
let nimVer = getNimrodVersion() let nimVer = getNimrodVersion()
if not withinRange(nimVer, dep.ver): if not withinRange(nimVer, dep.ver):
quit("Unsatisfied dependency: " & dep.name & " (" & $dep.ver & ")") let msg = "Unsatisfied dependency: " & dep.name & " (" & $dep.ver & ")"
raise newException(NimbleError, msg)
else: else:
echo("Looking for ", dep.name, " (", $dep.ver, ")...") let depDesc = "$1 ($2)" % [dep.name, $dep.ver]
display("Checking", "for $1" % depDesc, priority = MediumPriority)
var pkg: PackageInfo var pkg: PackageInfo
if not findPkg(pkglist, dep, pkg): if not findPkg(pkglist, dep, pkg):
echo("None found, installing...") display("Installing", depDesc, priority = HighPriority)
let (paths, installedPkg) = install(@[(dep.name, dep.ver)], options) let (paths, installedPkg) = install(@[(dep.name, dep.ver)], options)
result.add(paths) result.add(paths)
pkg = installedPkg # For addRevDep pkg = installedPkg # For addRevDep
else: else:
echo("Dependency already satisfied.") display("Info:", "Dependency on $1 already satisfied" % depDesc,
priority = HighPriority)
result.add(pkg.mypath.splitFile.dir) result.add(pkg.mypath.splitFile.dir)
# Process the dependencies of this dependency. # Process the dependencies of this dependency.
result.add(processDeps(pkg, options)) result.add(processDeps(pkg, options))
@ -311,8 +322,8 @@ proc buildFromDir(pkgInfo: PackageInfo, paths: seq[string], forRelease: bool) =
for path in paths: args.add("--path:\"" & path & "\" ") for path in paths: args.add("--path:\"" & path & "\" ")
for bin in pkgInfo.bin: for bin in pkgInfo.bin:
let outputOpt = "-o:\"" & pkgInfo.getOutputDir(bin) & "\"" let outputOpt = "-o:\"" & pkgInfo.getOutputDir(bin) & "\""
echo("Building ", pkginfo.name, "/", bin, " using ", pkgInfo.backend, display("Building", "$1/$2 using $3 backend" %
" backend...") [pkginfo.name, bin, pkgInfo.backend], priority = HighPriority)
let outputDir = pkgInfo.getOutputDir("") let outputDir = pkgInfo.getOutputDir("")
if not existsDir(outputDir): if not existsDir(outputDir):
@ -352,13 +363,14 @@ proc removePkgDir(dir: string, options: Options) =
if toSeq(walkDirRec(dir)).len == 0: if toSeq(walkDirRec(dir)).len == 0:
removeDir(dir) removeDir(dir)
else: else:
echo("WARNING: Cannot completely remove " & dir & display("Warning:", ("Cannot completely remove $1. Files not installed " &
". Files not installed by nimble are present.") "by nimble are present.") % dir, Warning, HighPriority)
except OSError, JsonParsingError: except OSError, JsonParsingError:
echo("Error: Unable to read nimblemeta.json: ", getCurrentExceptionMsg()) display("Warning", "Unable to read nimblemeta.json: " &
getCurrentExceptionMsg(), Warning, HighPriority)
if not options.prompt("Would you like to COMPLETELY remove ALL files " & if not options.prompt("Would you like to COMPLETELY remove ALL files " &
"in " & dir & "?"): "in " & dir & "?"):
quit(QuitSuccess) raise NimbleQuit(msg: "")
removeDir(dir) removeDir(dir)
proc vcsRevisionInDir(dir: string): string = proc vcsRevisionInDir(dir: string): string =
@ -398,7 +410,8 @@ proc installFromDir(dir: string, latest: bool, options: Options,
result.pkg = pkgInfo result.pkg = pkgInfo
return result return result
echo("Installing ", pkginfo.name, "-", pkginfo.version) display("Installing", "$1 v$2" % [pkginfo.name, pkginfo.version],
priority = HighPriority)
# Build before removing an existing package (if one exists). This way # Build before removing an existing package (if one exists). This way
# if the build fails then the old package will still be installed. # if the build fails then the old package will still be installed.
@ -409,7 +422,7 @@ proc installFromDir(dir: string, latest: bool, options: Options,
if existsDir(pkgDestDir) and existsFile(pkgDestDir / "nimblemeta.json"): if existsDir(pkgDestDir) and existsFile(pkgDestDir / "nimblemeta.json"):
if not options.prompt(pkgInfo.name & versionStr & if not options.prompt(pkgInfo.name & versionStr &
" already exists. Overwrite?"): " already exists. Overwrite?"):
quit(QuitSuccess) raise NimbleQuit(msg: "")
removePkgDir(pkgDestDir, options) removePkgDir(pkgDestDir, options)
# Remove any symlinked binaries # Remove any symlinked binaries
for bin in pkgInfo.bin: for bin in pkgInfo.bin:
@ -446,12 +459,13 @@ proc installFromDir(dir: string, latest: bool, options: Options,
# TODO: Verify that we are removing an old bin of this package, not # TODO: Verify that we are removing an old bin of this package, not
# some other package's binary! # some other package's binary!
if existsFile(binDir / cleanBin): removeFile(binDir / cleanBin) if existsFile(binDir / cleanBin): removeFile(binDir / cleanBin)
echo("Creating symlink: ", pkgDestDir / bin, " -> ", binDir / cleanBin) display("Creating", "symlink: $1 -> $2" %
[pkgDestDir / bin, binDir / cleanBin], priority = MediumPriority)
createSymlink(pkgDestDir / bin, binDir / cleanBin) createSymlink(pkgDestDir / bin, binDir / cleanBin)
elif defined(windows): elif defined(windows):
# There is a bug on XP, described here: # There is a bug on XP, described here:
# http://stackoverflow.com/questions/2182568/batch-script-is-not-executed-if-chcp-was-called # http://stackoverflow.com/questions/2182568/batch-script-is-not-executed-if-chcp-was-called
# But this workaround brokes code page on newer systems, so we need to detect OS version # But this workaround brakes code page on newer systems, so we need to detect OS version
var osver = OSVERSIONINFO() var osver = OSVERSIONINFO()
osver.dwOSVersionInfoSize = cast[DWORD](sizeof(OSVERSIONINFO)) osver.dwOSVersionInfoSize = cast[DWORD](sizeof(OSVERSIONINFO))
if GetVersionExA(osver) == WINBOOL(0): if GetVersionExA(osver) == WINBOOL(0):
@ -460,7 +474,8 @@ proc installFromDir(dir: string, latest: bool, options: Options,
let fixChcp = osver.dwMajorVersion <= 5 let fixChcp = osver.dwMajorVersion <= 5
let dest = binDir / cleanBin.changeFileExt("cmd") let dest = binDir / cleanBin.changeFileExt("cmd")
echo("Creating stub: ", pkgDestDir / bin, " -> ", dest) display("Creating", "stub: $1 -> $2" % [pkgDestDir / bin, dest],
priority = MediumPriority)
var contents = "@" var contents = "@"
if options.config.chcp: if options.config.chcp:
if fixChcp: if fixChcp:
@ -470,7 +485,8 @@ proc installFromDir(dir: string, latest: bool, options: Options,
writeFile(dest, contents) writeFile(dest, contents)
# For bash on Windows (Cygwin/Git bash). # For bash on Windows (Cygwin/Git bash).
let bashDest = dest.changeFileExt("") let bashDest = dest.changeFileExt("")
echo("Creating Cygwin stub: ", pkgDestDir / bin, " -> ", bashDest) display("Creating", "Cygwin stub: $1 -> $2" %
[pkgDestDir / bin, bashDest], priority = MediumPriority)
writeFile(bashDest, "\"" & pkgDestDir / bin & "\" \"$@\"\n") writeFile(bashDest, "\"" & pkgDestDir / bin & "\" \"$@\"\n")
else: else:
{.error: "Sorry, your platform is not supported.".} {.error: "Sorry, your platform is not supported.".}
@ -491,7 +507,8 @@ proc installFromDir(dir: string, latest: bool, options: Options,
result.paths.add pkgDestDir result.paths.add pkgDestDir
result.pkg = pkgInfo result.pkg = pkgInfo
echo(pkgInfo.name & " installed successfully.") display("Success:", pkgInfo.name & " installed successfully.",
Success, HighPriority)
proc getNimbleTempDir(): string = proc getNimbleTempDir(): string =
## Returns a path to a temporary directory. ## Returns a path to a temporary directory.
@ -529,8 +546,8 @@ proc downloadPkg(url: string, verRange: VersionRange,
if modUrl.contains("github.com") and modUrl.endswith("/"): if modUrl.contains("github.com") and modUrl.endswith("/"):
modUrl = modUrl[0 .. ^2] modUrl = modUrl[0 .. ^2]
echo("Downloading ", modUrl, " into ", downloadDir, " using ", display("Downloading", "$1 using $2" % [modUrl, $downMethod],
downMethod, "...") priority = HighPriority)
result = ( result = (
downloadDir, downloadDir,
doDownload(modUrl, downloadDir, verRange, downMethod, options) doDownload(modUrl, downloadDir, verRange, downMethod, options)
@ -545,15 +562,15 @@ proc getDownloadInfo*(pv: PkgTuple, options: Options,
if getPackage(pv.name, options, pkg): if getPackage(pv.name, options, pkg):
return (pkg.downloadMethod.getDownloadMethod(), pkg.url) return (pkg.downloadMethod.getDownloadMethod(), pkg.url)
else: else:
# If package is not found give the user a chance to update # If package is not found give the user a chance to refresh
# package.json # package.json
if doPrompt and if doPrompt and
options.prompt(pv.name & " not found in any local packages.json, " & options.prompt(pv.name & " not found in any local packages.json, " &
"check internet for updated packages?"): "check internet for updated packages?"):
update(options) refresh(options)
# Once we've updated, try again, but don't prompt if not found # Once we've refreshed, try again, but don't prompt if not found
# (as we've already updated and a failure means it really # (as we've already refreshed and a failure means it really
# isn't there) # isn't there)
return getDownloadInfo(pv, options, false) return getDownloadInfo(pv, options, false)
else: else:
@ -570,9 +587,9 @@ proc install(packages: seq[PkgTuple],
if doPrompt and if doPrompt and
options.prompt("No local packages.json found, download it from " & options.prompt("No local packages.json found, download it from " &
"internet?"): "internet?"):
update(options) refresh(options)
else: else:
quit("Please run nimble refresh.", QuitFailure) raise newException(NimbleError, "Please run nimble refresh.")
# Install each package. # Install each package.
for pv in packages: for pv in packages:
@ -608,6 +625,13 @@ proc build(options: Options) =
buildFromDir(pkgInfo, paths, false) buildFromDir(pkgInfo, paths, false)
proc compile(options: Options) = proc compile(options: Options) =
let bin = options.action.file
if bin == "":
raise newException(NimbleError, "You need to specify a file to compile.")
if not fileExists(bin):
raise newException(NimbleError, "Specified file does not exist.")
var pkgInfo = getPkgInfo(getCurrentDir(), options) var pkgInfo = getPkgInfo(getCurrentDir(), options)
nimScriptHint(pkgInfo) nimScriptHint(pkgInfo)
let paths = processDeps(pkginfo, options) let paths = processDeps(pkginfo, options)
@ -617,20 +641,17 @@ proc compile(options: Options) =
for option in options.action.compileOptions: for option in options.action.compileOptions:
args.add(option & " ") args.add(option & " ")
let bin = options.action.file
let backend = let backend =
if options.action.backend.len > 0: if options.action.backend.len > 0:
options.action.backend options.action.backend
else: else:
pkgInfo.backend pkgInfo.backend
if bin == "": display("Compiling", "$1 (from package $2) using $3 backend" %
raise newException(NimbleError, "You need to specify a file to compile.") [bin, pkgInfo.name, backend], priority = HighPriority)
echo("Compiling ", bin, " (", pkgInfo.name, ") using ", backend,
" backend...")
doCmd("\"" & getNimBin() & "\" $# --noBabelPath $# \"$#\"" % doCmd("\"" & getNimBin() & "\" $# --noBabelPath $# \"$#\"" %
[backend, args, bin]) [backend, args, bin])
display("Success:", "Compilation finished", Success, HighPriority)
proc search(options: Options) = proc search(options: Options) =
## Searches for matches in ``options.action.search``. ## Searches for matches in ``options.action.search``.
@ -663,7 +684,7 @@ proc search(options: Options) =
onFound() onFound()
if not found: if not found:
echo("No package found.") display("Error", "No package found.", Error, HighPriority)
proc list(options: Options) = proc list(options: Options) =
if needsRefresh(options): if needsRefresh(options):
@ -694,7 +715,7 @@ proc listInstalled(options: Options) =
type VersionAndPath = tuple[version: Version, path: string] type VersionAndPath = tuple[version: Version, path: string]
proc listPaths(options: Options) = proc listPaths(options: Options) =
## Loops over installing packages displaying their installed paths. ## Loops over the specified packages displaying their installed paths.
## ##
## If there are several packages installed, only the last one (the version ## If there are several packages installed, only the last one (the version
## listed in the packages.json) will be displayed. If any package name is not ## listed in the packages.json) will be displayed. If any package name is not
@ -704,6 +725,10 @@ proc listPaths(options: Options) =
## On success the proc returns normally. ## On success the proc returns normally.
assert options.action.typ == actionPath assert options.action.typ == actionPath
assert(not options.action.packages.isNil) assert(not options.action.packages.isNil)
if options.action.packages.len == 0:
raise newException(NimbleError, "A package name needs to be specified")
var errors = 0 var errors = 0
for name, version in options.action.packages.items: for name, version in options.action.packages.items:
var installed: seq[VersionAndPath] = @[] var installed: seq[VersionAndPath] = @[]
@ -713,11 +738,9 @@ proc listPaths(options: Options) =
continue continue
let let
nimScriptFile = path / name.addFileExt("nims")
babelFile = path / name.addFileExt("babel")
nimbleFile = path / name.addFileExt("nimble") nimbleFile = path / name.addFileExt("nimble")
hasSpec = nimScriptFile.existsFile or hasSpec = nimbleFile.existsFile
nimbleFile.existsFile or babelFile.existsFile
if hasSpec: if hasSpec:
var pkgInfo = getPkgInfo(path, options) var pkgInfo = getPkgInfo(path, options)
var v: VersionAndPath var v: VersionAndPath
@ -725,13 +748,16 @@ proc listPaths(options: Options) =
v.path = options.getPkgsDir / (pkgInfo.name & '-' & pkgInfo.version) v.path = options.getPkgsDir / (pkgInfo.name & '-' & pkgInfo.version)
installed.add(v) installed.add(v)
else: else:
echo "Warning: No .nimble file found for ", path display("Warning:", "No .nimble file found for " & path, Warning,
MediumPriority)
if installed.len > 0: if installed.len > 0:
sort(installed, system.cmp[VersionAndPath], Descending) sort(installed, system.cmp[VersionAndPath], Descending)
# The output for this command is used by tools so we do not use display().
echo installed[0].path echo installed[0].path
else: else:
echo "Warning: Package '" & name & "' not installed" display("Warning:", "Package '$1' is not installed" % name, Warning,
MediumPriority)
errors += 1 errors += 1
if errors > 0: if errors > 0:
raise newException(NimbleError, raise newException(NimbleError,
@ -768,9 +794,10 @@ proc dump(options: Options) =
proc init(options: Options) = proc init(options: Options) =
var nimbleFile: string = "" var nimbleFile: string = ""
echo("In order to initialise a new Nimble package, I will need to ask you\n" & 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" & "some questions. Default values are shown in square brackets, press\n" &
"enter to use them.") "enter to use them.", priority = HighPriority)
# Ask for package name. # Ask for package name.
if options.action.projName != "": if options.action.projName != "":
@ -778,7 +805,7 @@ proc init(options: Options) =
nimbleFile = pkgName.changeFileExt("nimble") nimbleFile = pkgName.changeFileExt("nimble")
else: else:
var pkgName = os.getCurrentDir().splitPath.tail.toValidPackageName() var pkgName = os.getCurrentDir().splitPath.tail.toValidPackageName()
pkgName = promptCustom("Enter package name", pkgName) pkgName = promptCustom("Package name?", pkgName)
nimbleFile = pkgName.changeFileExt("nimble") nimbleFile = pkgName.changeFileExt("nimble")
validatePackageName(nimbleFile.changeFileExt("")) validatePackageName(nimbleFile.changeFileExt(""))
@ -787,7 +814,7 @@ proc init(options: Options) =
raise newException(NimbleError, "Nimble file already exists.") raise newException(NimbleError, "Nimble file already exists.")
# Ask for package version. # Ask for package version.
let pkgVersion = promptCustom("Enter initial version of package", "0.1.0") let pkgVersion = promptCustom("Initial version of package?", "0.1.0")
validateVersion(pkgVersion) validateVersion(pkgVersion)
# Ask for package author # Ask for package author
@ -800,25 +827,20 @@ proc init(options: Options) =
let (name, exitCode) = doCmdEx("hg config ui.username") let (name, exitCode) = doCmdEx("hg config ui.username")
if exitCode == QuitSuccess and name.len > 0: if exitCode == QuitSuccess and name.len > 0:
defaultAuthor = name.strip() defaultAuthor = name.strip()
let pkgAuthor = promptCustom("Enter your name", defaultAuthor) let pkgAuthor = promptCustom("Your name?", defaultAuthor)
# Ask for description # Ask for description
let pkgDesc = promptCustom("Enter package description", "") let pkgDesc = promptCustom("Package description?", "")
# Ask for license # Ask for license
# TODO: Provide selection of licenses, or select random default license. # TODO: Provide selection of licenses, or select random default license.
let pkgLicense = promptCustom("Enter package license", "MIT") let pkgLicense = promptCustom("Package license?", "MIT")
# Ask for Nim dependency # Ask for Nim dependency
let nimDepDef = getNimrodVersion() let nimDepDef = getNimrodVersion()
let pkgNimDep = promptCustom("Enter lowest supported Nim version", $nimDepDef) let pkgNimDep = promptCustom("Lowest supported Nim version?", $nimDepDef)
validateVersion(pkgNimDep) validateVersion(pkgNimDep)
# Now generate the .nimble file.
if existsFile(os.getCurrentDir() / nimbleFile):
raise newException(NimbleError,
"Looks like a Nimble file has already been created.")
var outFile: File var outFile: File
if open(f = outFile, filename = nimbleFile, mode = fmWrite): if open(f = outFile, filename = nimbleFile, mode = fmWrite):
outFile.writeLine """# Package outFile.writeLine """# Package
@ -838,6 +860,8 @@ requires "nim >= $#"
raise newException(NimbleError, "Unable to open file " & nimbleFile & raise newException(NimbleError, "Unable to open file " & nimbleFile &
" for writing: " & osErrorMsg(osLastError())) " for writing: " & osErrorMsg(osLastError()))
display("Success:", "Nimble file created successfully", Success, HighPriority)
proc uninstall(options: Options) = proc uninstall(options: Options) =
if options.action.packages.len == 0: if options.action.packages.len == 0:
raise newException(NimbleError, raise newException(NimbleError,
@ -846,13 +870,14 @@ proc uninstall(options: Options) =
var pkgsToDelete: seq[PackageInfo] = @[] var pkgsToDelete: seq[PackageInfo] = @[]
# Do some verification. # Do some verification.
for pkgTup in options.action.packages: for pkgTup in options.action.packages:
echo("Looking for ", pkgTup.name, " (", $pkgTup.ver, ")...") display("Looking", "for $1 ($2)" % [pkgTup.name, $pkgTup.ver],
priority = HighPriority)
let installedPkgs = getInstalledPkgs(options.getPkgsDir(), options) let installedPkgs = getInstalledPkgs(options.getPkgsDir(), options)
var pkgList = findAllPkgs(installedPkgs, pkgTup) var pkgList = findAllPkgs(installedPkgs, pkgTup)
if pkgList.len == 0: if pkgList.len == 0:
raise newException(NimbleError, "Package not found") raise newException(NimbleError, "Package not found")
echo("Checking reverse dependencies...") display("Checking", "reverse dependencies", priority = HighPriority)
var errors: seq[string] = @[] var errors: seq[string] = @[]
for pkg in pkgList: for pkg in pkgList:
# Check whether any packages depend on the ones the user is trying to # Check whether any packages depend on the ones the user is trying to
@ -861,17 +886,17 @@ proc uninstall(options: Options) =
if not thisPkgsDep.isNil: if not thisPkgsDep.isNil:
var reason = "" var reason = ""
if thisPkgsDep.len == 1: if thisPkgsDep.len == 1:
reason = thisPkgsDep[0]["name"].str & reason = "$1 ($2) depends on it" % [thisPkgsDep[0]["name"].str,
" (" & thisPkgsDep[0]["version"].str & ") depends on it" thisPkgsDep[0]["version"].str]
else: else:
for i in 0 .. <thisPkgsDep.len: for i in 0 .. <thisPkgsDep.len:
reason.add thisPkgsDep[i]["name"].str & reason.add("$1 ($2)" % [thisPkgsDep[i]["name"].str,
" (" & thisPkgsDep[i]["version"].str & ")" thisPkgsDep[i]["version"].str])
if i != <thisPkgsDep.len: if i != <thisPkgsDep.len:
reason.add ", " reason.add ", "
reason.add " depend on it" reason.add " depend on it"
errors.add("Cannot uninstall " & pkgTup.name & " (" & pkg.version & errors.add("Cannot uninstall $1 ($2) because $3" % [pkgTup.name,
")" & " because " & reason) pkg.version, reason])
else: else:
pkgsToDelete.add pkg pkgsToDelete.add pkg
@ -882,12 +907,13 @@ proc uninstall(options: Options) =
for i in 0 .. <pkgsToDelete.len: for i in 0 .. <pkgsToDelete.len:
if i != 0: pkgNames.add ", " if i != 0: pkgNames.add ", "
let pkg = pkgsToDelete[i] let pkg = pkgsToDelete[i]
pkgNames.add pkg.name & " (" & pkg.version & ")" pkgNames.add("$1 ($2)" % [pkg.name, pkg.version])
# Let's confirm that the user wants these packages removed. # Let's confirm that the user wants these packages removed.
if not options.prompt("The following packages will be removed:\n " & let msg = ("The following packages will be removed:\n $1\n" &
pkgNames & "\nDo you wish to continue?"): "Do you wish to continue?") % pkgNames
quit(QuitSuccess) if not options.prompt(msg):
raise NimbleQuit(msg: "")
for pkg in pkgsToDelete: for pkg in pkgsToDelete:
# If we reach this point then the package can be safely removed. # If we reach this point then the package can be safely removed.
@ -897,7 +923,8 @@ proc uninstall(options: Options) =
let pkgFull = readPackageInfo(pkg.mypath, options, false) let pkgFull = readPackageInfo(pkg.mypath, options, false)
removeRevDep(options, pkgFull) removeRevDep(options, pkgFull)
removePkgDir(options.getPkgsDir / (pkg.name & '-' & pkg.version), options) removePkgDir(options.getPkgsDir / (pkg.name & '-' & pkg.version), options)
echo("Removed ", pkg.name, " (", $pkg.version, ")") display("Removed", "$1 ($2)" % [pkg.name, $pkg.version], Success,
HighPriority)
proc listTasks(options: Options) = proc listTasks(options: Options) =
let nimbleFile = findNimbleFile(getCurrentDir(), true) let nimbleFile = findNimbleFile(getCurrentDir(), true)
@ -930,11 +957,12 @@ proc doAction(options: Options) =
createDir(options.getPkgsDir) createDir(options.getPkgsDir)
if not execHook(options, true): if not execHook(options, true):
echo("Pre-hook prevented further execution.") display("Warning", "Pre-hook prevented further execution.", Warning,
HighPriority)
return return
case options.action.typ case options.action.typ
of actionRefresh: of actionRefresh:
update(options) refresh(options)
of actionInstall: of actionInstall:
let (_, pkgInfo) = install(options.action.packages, options) let (_, pkgInfo) = install(options.action.packages, options)
if options.action.packages.len == 0: if options.action.packages.len == 0:
@ -965,8 +993,6 @@ proc doAction(options: Options) =
dump(options) dump(options)
of actionTasks: of actionTasks:
listTasks(options) listTasks(options)
of actionVersion:
writeVersion()
of actionNil: of actionNil:
assert false assert false
of actionCustom: of actionCustom:
@ -977,14 +1003,19 @@ proc doAction(options: Options) =
let execResult = execTask(nimbleFile, options.action.command, options) let execResult = execTask(nimbleFile, options.action.command, options)
if not execResult.success: if not execResult.success:
echo("FAILURE: Could not find task ", options.action.command, " in ", raiseNimbleError(msg = "Could not find task $1 in $2" %
nimbleFile) [options.action.command, nimbleFile],
writeHelp() hint = "Run `nimble --help` and/or `nimble tasks` for" &
" a list of possible commands.")
if execResult.command.normalize == "nop": if execResult.command.normalize == "nop":
echo("WARNING: Using `setCommand 'nop'` is not necessary.") display("Warning:", "Using `setCommand 'nop'` is not necessary.", Warning,
HighPriority)
return return
if not execHook(options, false): if not execHook(options, false):
return return
if execResult.hasTaskRequestedCommand(): if execResult.hasTaskRequestedCommand():
var newOptions = initOptions() var newOptions = initOptions()
newOptions.config = options.config newOptions.config = options.config
@ -1000,12 +1031,27 @@ proc doAction(options: Options) =
discard execHook(options, false) discard execHook(options, false)
when isMainModule: when isMainModule:
when defined(release): var error = ""
try: var hint = ""
parseCmdLine().doAction()
except NimbleError: try:
quit("FAILURE: " & getCurrentExceptionMsg())
finally:
removeDir(getNimbleTempDir())
else:
parseCmdLine().doAction() parseCmdLine().doAction()
except NimbleError:
let err = (ref NimbleError)(getCurrentException())
error = err.msg
when not defined(release):
let stackTrace = getStackTrace(getCurrentException())
error = stackTrace & "\n\n" & error
if not err.isNil:
hint = err.hint
except NimbleQuit:
nil
finally:
removeDir(getNimbleTempDir())
if error.len > 0:
displayTip()
display("Error:", error, Error, HighPriority)
if hint.len > 0:
display("Hint:", hint, Warning, HighPriority)
quit(1)

166
src/nimblepkg/cli.nim Normal file
View file

@ -0,0 +1,166 @@
# Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info.
#
# Rough rules/philosophy for the messages that Nimble displays are the following:
# - Green is only shown when the requested operation is successful.
# - Blue can be used to emphasise certain keywords, for example actions such
# as "Downloading" or "Reading".
# - Red is used when the requested operation fails with an error.
# - Yellow is used for warnings.
#
# - Dim for LowPriority.
# - Bright for HighPriority.
# - Normal for MediumPriority.
import logging, terminal, sets, strutils
type
CLI* = ref object
level: Priority
warnings: HashSet[(string, string)]
suppressionCount: int ## Amount of messages which were not shown.
Priority* = enum
DebugPriority, LowPriority, MediumPriority, HighPriority
DisplayType* = enum
Error, Warning, Message, Success
ForcePrompt* = enum
dontForcePrompt, forcePromptYes, forcePromptNo
const
longestCategory = len("Downloading")
foregrounds: array[Error .. Success, ForegroundColor] =
[fgRed, fgYellow, fgCyan, fgGreen]
styles: array[DebugPriority .. HighPriority, set[Style]] =
[{styleDim}, {styleDim}, {}, {styleBright}]
proc newCLI(): CLI =
result = CLI(
level: HighPriority,
warnings: initSet[(string, string)](),
suppressionCount: 0
)
var globalCLI = newCLI()
proc calculateCategoryOffset(category: string): int =
assert category.len <= longestCategory
return longestCategory - category.len
proc displayCategory(category: string, displayType: DisplayType,
priority: Priority) =
# Calculate how much the `category` must be offset to align along a center
# line.
let offset = calculateCategoryOffset(category)
# Display the category.
if priority != DebugPriority:
setForegroundColor(stdout, foregrounds[displayType])
writeStyled("$1$2 " % [repeatChar(offset), category], styles[priority])
resetAttributes()
proc displayLine(category, line: string, displayType: DisplayType,
priority: Priority) =
displayCategory(category, displayType, priority)
# Display the message.
echo(line)
proc display*(category, msg: string, displayType = Message,
priority = MediumPriority) =
# Multiple warnings containing the same messages should not be shown.
let warningPair = (category, msg)
if displayType == Warning:
if warningPair in globalCLI.warnings:
return
else:
globalCLI.warnings.incl(warningPair)
# Suppress this message if its priority isn't high enough.
# TODO: Per-priority suppression counts?
if priority < globalCLI.level:
if priority != DebugPriority:
globalCLI.suppressionCount.inc
return
# Display each line in the message.
var i = 0
for line in msg.splitLines():
if len(line) == 0: continue
displayLine(if i == 0: category else: "...", line, displayType, priority)
i.inc
proc displayDebug*(category, msg: string) =
## Convenience for displaying debug messages.
display(category, msg, priority = DebugPriority)
proc displayDebug*(msg: string) =
## Convenience for displaying debug messages with a default category.
displayDebug("Debug:", msg)
proc displayTip*() =
## Called just before Nimble exits. Shows some tips for the user, for example
## the amount of messages that were suppressed and how to show them.
if globalCLI.suppressionCount > 0:
let msg = "$1 messages have been suppressed, use --verbose to show them." %
$globalCLI.suppressionCount
display("Tip:", msg, Warning, HighPriority)
proc prompt*(forcePrompts: ForcePrompt, question: string): bool =
case forcePrompts
of forcePromptYes:
display("Prompt:", question & " -> [forced yes]", Warning, HighPriority)
return true
of forcePromptNo:
display("Prompt:", question & " -> [forced no]", Warning, HighPriority)
return false
of dontForcePrompt:
display("Prompt:", question & " [y/N]", Warning, HighPriority)
displayCategory("Answer:", Warning, HighPriority)
let yn = stdin.readLine()
case yn.normalize
of "y", "yes":
return true
of "n", "no":
return false
else:
return false
proc promptCustom*(question, default: string): string =
if default == "":
display("Prompt:", question, Warning, HighPriority)
displayCategory("Answer:", Warning, HighPriority)
let user = stdin.readLine()
if user.len == 0: return promptCustom(question, default)
else: return user
else:
display("Prompt:", question & " [" & default & "]", Warning, HighPriority)
displayCategory("Answer:", Warning, HighPriority)
let user = stdin.readLine()
if user == "": return default
else: return user
proc setVerbosity*(level: Priority) =
globalCLI.level = level
when isMainModule:
display("Reading", "config file at /Users/dom/.config/nimble/nimble.ini",
priority = LowPriority)
display("Reading", "official package list",
priority = LowPriority)
display("Downloading", "daemonize v0.0.2 using Git",
priority = HighPriority)
display("Warning", "dashes in package names will be deprecated", Warning,
priority = HighPriority)
display("Error", """Unable to read package info for /Users/dom/.nimble/pkgs/nimble-0.7.11
Reading as ini file failed with:
Invalid section: .
Evaluating as NimScript file failed with:
Users/dom/.nimble/pkgs/nimble-0.7.11/nimble.nimble(3, 23) Error: cannot open 'src/nimblepkg/common'.
""", Error, HighPriority)

View file

@ -1,3 +1,4 @@
# Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info. # BSD License. Look at license.txt for more info.
# #
# Various miscellaneous common types reside here, to avoid problems with # Various miscellaneous common types reside here, to avoid problems with
@ -7,7 +8,7 @@ when not defined(nimscript):
import sets import sets
import version import version
export version.NimbleError export version.NimbleError # TODO: Surely there is a better way?
type type
BuildFailed* = object of NimbleError BuildFailed* = object of NimbleError
@ -37,5 +38,13 @@ when not defined(nimscript):
backend*: string backend*: string
foreignDeps*: seq[string] foreignDeps*: seq[string]
## Same as quit(QuitSuccess), but allows cleanup.
NimbleQuit* = ref object of Exception
proc raiseNimbleError*(msg: string, hint = "") =
var exc = newException(NimbleError, msg)
exc.hint = hint
raise exc
const const
nimbleVersion* = "0.7.11" nimbleVersion* = "0.7.11"

View file

@ -2,13 +2,13 @@
# BSD License. Look at license.txt for more info. # BSD License. Look at license.txt for more info.
import parsecfg, streams, strutils, os, tables, Uri import parsecfg, streams, strutils, os, tables, Uri
import tools, version, common import tools, version, common, cli
type type
Config* = object Config* = object
nimbleDir*: string nimbleDir*: string
chcp*: bool # Whether to change the code page in .cmd files on Win. chcp*: bool # Whether to change the code page in .cmd files on Win.
packageLists*: Table[string, PackageList] ## URLs to packages.json files packageLists*: Table[string, PackageList] ## Names -> packages.json files
cloneUsingHttps*: bool # Whether to replace git:// for https:// cloneUsingHttps*: bool # Whether to replace git:// for https://
httpProxy*: Uri # Proxy for package list downloads. httpProxy*: Uri # Proxy for package list downloads.
@ -50,13 +50,15 @@ proc parseConfig*(): Config =
var f = newFileStream(confFile, fmRead) var f = newFileStream(confFile, fmRead)
if f == nil: if f == nil:
# Try the old deprecated babel.ini # Try the old deprecated babel.ini
# TODO: This can be removed.
confFile = getConfigDir() / "babel" / "babel.ini" confFile = getConfigDir() / "babel" / "babel.ini"
f = newFileStream(confFile, fmRead) f = newFileStream(confFile, fmRead)
if f != nil: if f != nil:
echo("[Warning] Using deprecated config file at ", confFile) display("Warning", "Using deprecated config file at " & confFile,
Warning, HighPriority)
if f != nil: if f != nil:
echo("Reading from config file at ", confFile) display("Reading", "config file at " & confFile, priority = LowPriority)
var p: CfgParser var p: CfgParser
open(p, f, confFile) open(p, f, confFile)
var currentSection = "" var currentSection = ""

View file

@ -3,7 +3,7 @@
import parseutils, os, osproc, strutils, tables, pegs import parseutils, os, osproc, strutils, tables, pegs
import packageinfo, packageparser, version, tools, common, options import packageinfo, packageparser, version, tools, common, options, cli
type type
DownloadMethod* {.pure.} = enum DownloadMethod* {.pure.} = enum
@ -142,7 +142,6 @@ proc doDownload*(url: string, downloadDir: string, verRange: VersionRange,
## ##
## Returns the version of the repository which has been downloaded. ## Returns the version of the repository which has been downloaded.
template getLatestByTag(meth: untyped) {.dirty.} = template getLatestByTag(meth: untyped) {.dirty.} =
echo("Found tags...")
# Find latest version that fits our ``verRange``. # Find latest version that fits our ``verRange``.
var latest = findLatest(verRange, versions) var latest = findLatest(verRange, versions)
## Note: HEAD is not used when verRange.kind is verAny. This is ## Note: HEAD is not used when verRange.kind is verAny. This is
@ -150,7 +149,7 @@ proc doDownload*(url: string, downloadDir: string, verRange: VersionRange,
# If no tagged versions satisfy our range latest.tag will be "". # If no tagged versions satisfy our range latest.tag will be "".
# We still clone in that scenario because we want to try HEAD in that case. # We still clone in that scenario because we want to try HEAD in that case.
# https://github.com/nimrod-code/nimble/issues/22 # https://github.com/nim-lang/nimble/issues/22
meth meth
if $latest.ver != "": if $latest.ver != "":
result = parseVersionRange($latest.ver) result = parseVersionRange($latest.ver)
@ -188,7 +187,8 @@ proc doDownload*(url: string, downloadDir: string, verRange: VersionRange,
let versions = getTagsListRemote(url, downMethod).getVersionList() let versions = getTagsListRemote(url, downMethod).getVersionList()
if versions.len > 0: if versions.len > 0:
getLatestByTag: getLatestByTag:
echo("Cloning latest tagged version: ", latest.tag) display("Cloning", "latest tagged version: " & latest.tag,
priority = MediumPriority)
doClone(downMethod, url, downloadDir, latest.tag) doClone(downMethod, url, downloadDir, latest.tag)
else: else:
# If no commits have been tagged on the repo we just clone HEAD. # If no commits have been tagged on the repo we just clone HEAD.
@ -202,7 +202,8 @@ proc doDownload*(url: string, downloadDir: string, verRange: VersionRange,
if versions.len > 0: if versions.len > 0:
getLatestByTag: getLatestByTag:
echo("Switching to latest tagged version: ", latest.tag) display("Switching", "to latest tagged version: " & latest.tag,
priority = MediumPriority)
doCheckout(downMethod, downloadDir, latest.tag) doCheckout(downMethod, downloadDir, latest.tag)
verifyClone() verifyClone()

View file

@ -15,7 +15,7 @@ from compiler/scriptconfig import setupVM
from compiler/astalgo import strTableGet from compiler/astalgo import strTableGet
import compiler/options as compiler_options import compiler/options as compiler_options
import common, version, options, packageinfo import common, version, options, packageinfo, cli
import os, strutils, strtabs, times, osproc, sets import os, strutils, strtabs, times, osproc, sets
when not declared(resetAllModulesHard): when not declared(resetAllModulesHard):
@ -299,7 +299,7 @@ proc readPackageInfoFromNims*(scriptName: string, options: Options,
if msgs.gErrorCounter > 0: if msgs.gErrorCounter > 0:
raise newException(NimbleError, previousMsg) raise newException(NimbleError, previousMsg)
elif previousMsg.len > 0: elif previousMsg.len > 0:
echo(previousMsg) display("Info", previousMsg, priority = HighPriority)
if output.normalize.startsWith("error"): if output.normalize.startsWith("error"):
raise newException(NimbleError, output) raise newException(NimbleError, output)
previousMsg = output previousMsg = output
@ -387,7 +387,8 @@ proc execTask*(scriptName, taskName: string,
result.success = true result.success = true
result.flags = newStringTable() result.flags = newStringTable()
compiler_options.command = internalCmd compiler_options.command = internalCmd
echo("Executing task ", taskName, " in ", scriptName) display("Executing", "task $# in $#" % [taskName, scriptName],
priority = HighPriority)
let thisModule = execScript(scriptName, result.flags, options) let thisModule = execScript(scriptName, result.flags, options)
let prc = thisModule.tab.strTableGet(getIdent(taskName & "Task")) let prc = thisModule.tab.strTableGet(getIdent(taskName & "Task"))
@ -417,7 +418,8 @@ proc execHook*(scriptName, actionName: string, before: bool,
let hookName = let hookName =
if before: actionName.toLowerAscii & "Before" if before: actionName.toLowerAscii & "Before"
else: actionName.toLowerAscii & "After" else: actionName.toLowerAscii & "After"
echo("Attempting to execute hook ", hookName, " in ", scriptName) display("Attempting", "to execute hook $# in $#" % [hookName, scriptName],
priority = MediumPriority)
let thisModule = execScript(scriptName, result.flags, options) let thisModule = execScript(scriptName, result.flags, options)
# Explicitly execute the task procedure, instead of relying on hack. # Explicitly execute the task procedure, instead of relying on hack.

View file

@ -4,7 +4,7 @@
import json, strutils, os, parseopt, strtabs, uri, tables import json, strutils, os, parseopt, strtabs, uri, tables
from httpclient import Proxy, newProxy from httpclient import Proxy, newProxy
import config, version, tools, common import config, version, tools, common, cli
type type
Options* = object Options* = object
@ -12,6 +12,8 @@ type
depsOnly*: bool depsOnly*: bool
queryVersions*: bool queryVersions*: bool
queryInstalled*: bool queryInstalled*: bool
nimbleDir*: string
verbosity*: cli.Priority
action*: Action action*: Action
config*: Config config*: Config
nimbleData*: JsonNode ## Nimbledata.json nimbleData*: JsonNode ## Nimbledata.json
@ -21,12 +23,11 @@ type
actionNil, actionRefresh, actionInit, actionDump, actionPublish, actionNil, actionRefresh, actionInit, actionDump, actionPublish,
actionInstall, actionSearch, actionInstall, actionSearch,
actionList, actionBuild, actionPath, actionUninstall, actionCompile, actionList, actionBuild, actionPath, actionUninstall, actionCompile,
actionCustom, actionTasks, actionVersion actionCustom, actionTasks
Action* = object Action* = object
case typ*: ActionType case typ*: ActionType
of actionNil, actionList, actionBuild, actionPublish, actionTasks, of actionNil, actionList, actionBuild, actionPublish, actionTasks: nil
actionVersion: nil
of actionRefresh: of actionRefresh:
optionalURL*: string # Overrides default package list. optionalURL*: string # Overrides default package list.
of actionInstall, actionPath, actionUninstall: of actionInstall, actionPath, actionUninstall:
@ -44,9 +45,6 @@ type
arguments*: seq[string] arguments*: seq[string]
flags*: StringTableRef flags*: StringTableRef
ForcePrompt* = enum
dontForcePrompt, forcePromptYes, forcePromptNo
const const
help* = """ help* = """
@ -54,6 +52,7 @@ Usage: nimble COMMAND [opts]
Commands: Commands:
install [pkgname, ...] Installs a list of packages. install [pkgname, ...] Installs a list of packages.
[-d, --depsOnly] Install only dependencies.
init [pkgname] Initializes a new Nimble project. init [pkgname] Initializes a new Nimble project.
publish Publishes a package on nim-lang/packages. publish Publishes a package on nim-lang/packages.
The current working directory needs to be the The current working directory needs to be the
@ -64,9 +63,11 @@ Commands:
to the Nim compiler. to the Nim compiler.
refresh [url] Refreshes the package list. A package list URL refresh [url] Refreshes the package list. A package list URL
can be optionally specified. can be optionally specified.
search [--ver] pkg/tag Searches for a specified package. Search is search pkg/tag Searches for a specified package. Search is
performed by tag and by name. performed by tag and by name.
list [--ver] Lists all packages. [--ver] Query remote server for package version.
list Lists all packages.
[--ver] Query remote server for package version.
[-i, --installed] Lists all installed packages. [-i, --installed] Lists all installed packages.
tasks Lists the tasks specified in the Nimble tasks Lists the tasks specified in the Nimble
package's Nimble file. package's Nimble file.
@ -83,26 +84,29 @@ Options:
--ver Query remote server for package version --ver Query remote server for package version
information when searching or listing packages information when searching or listing packages
--nimbleDir dirname Set the Nimble directory. --nimbleDir dirname Set the Nimble directory.
-d --depsOnly Install only dependencies. --verbose Show all non-debug output.
--debug Show all output including debug messages.
For more information read the Github readme: For more information read the Github readme:
https://github.com/nim-lang/nimble#readme https://github.com/nim-lang/nimble#readme
""" """
proc writeHelp*() = proc writeHelp*(quit=true) =
echo(help) echo(help)
quit(QuitSuccess) if quit:
raise NimbleQuit(msg: "")
proc writeVersion() =
echo("nimble v$# compiled at $# $#" %
[nimbleVersion, CompileDate, CompileTime])
raise NimbleQuit(msg: "")
proc parseActionType*(action: string): ActionType = proc parseActionType*(action: string): ActionType =
case action.normalize() case action.normalize()
of "install", "path": of "install":
case action.normalize() result = actionInstall
of "install": of "path":
result = actionInstall result = actionPath
of "path":
result = actionPath
else:
discard
of "build": of "build":
result = actionBuild result = actionBuild
of "c", "compile", "js", "cpp", "cc": of "c", "compile", "js", "cpp", "cc":
@ -153,30 +157,14 @@ proc initAction*(options: var Options, key: string) =
options.action.arguments = @[] options.action.arguments = @[]
options.action.flags = newStringTable() options.action.flags = newStringTable()
of actionBuild, actionPublish, actionList, actionTasks, of actionBuild, actionPublish, actionList, actionTasks,
actionNil, actionVersion: discard actionNil: discard
proc prompt*(options: Options, question: string): bool = proc prompt*(options: Options, question: string): bool =
## Asks an interactive question and returns the result. ## Asks an interactive question and returns the result.
## ##
## The proc will return immediately without asking the user if the global ## The proc will return immediately without asking the user if the global
## forcePrompts has a value different than dontForcePrompt. ## forcePrompts has a value different than dontForcePrompt.
case options.forcePrompts return prompt(options.forcePrompts, question)
of forcePromptYes:
echo(question & " -> [forced yes]")
return true
of forcePromptNo:
echo(question & " -> [forced no]")
return false
of dontForcePrompt:
echo(question & " [y/N]")
let yn = stdin.readLine()
case yn.normalize
of "y", "yes":
return true
of "n", "no":
return false
else:
return false
proc renameBabelToNimble(options: Options) {.deprecated.} = proc renameBabelToNimble(options: Options) {.deprecated.} =
let babelDir = getHomeDir() / ".babel" let babelDir = getHomeDir() / ".babel"
@ -191,7 +179,13 @@ proc renameBabelToNimble(options: Options) {.deprecated.} =
removeFile(nimbleDir / "babeldata.json") removeFile(nimbleDir / "babeldata.json")
proc getNimbleDir*(options: Options): string = proc getNimbleDir*(options: Options): string =
expandTilde(options.config.nimbleDir) result =
if options.nimbleDir.len == 0:
options.config.nimbleDir
else:
options.nimbleDir
return expandTilde(result)
proc getPkgsDir*(options: Options): string = proc getPkgsDir*(options: Options): string =
options.getNimbleDir() / "pkgs" options.getNimbleDir() / "pkgs"
@ -235,33 +229,53 @@ proc parseArgument*(key: string, result: var Options) =
discard discard
proc parseFlag*(flag, val: string, result: var Options) = proc parseFlag*(flag, val: string, result: var Options) =
case result.action.typ var wasFlagHandled = true
of actionCompile:
if val == "": # Global flags.
result.action.compileOptions.add("--" & flag) case flag.normalize()
of "help", "h": writeHelp()
of "version", "v": writeVersion()
of "accept", "y": result.forcePrompts = forcePromptYes
of "reject", "n": result.forcePrompts = forcePromptNo
of "nimbledir": result.nimbleDir = val
of "verbose": result.verbosity = LowPriority
of "debug": result.verbosity = DebugPriority
# Action-specific flags.
of "installed", "i":
if result.action.typ in [actionSearch, actionList]:
result.queryInstalled = true
else: else:
result.action.compileOptions.add("--" & flag & ":" & val) wasFlagHandled = false
of actionCustom: of "depsonly", "d":
result.action.flags[flag] = val if result.action.typ == actionInstall:
result.depsOnly = true
else:
wasFlagHandled = false
of "ver":
if result.action.typ in [actionSearch, actionList]:
result.queryVersions = true
else:
wasFlagHandled = false
else: else:
# TODO: These should not be global. case result.action.typ
case flag.normalize() of actionCompile:
of "help", "h": writeHelp() if val == "":
of "version", "v": result.action.compileOptions.add("--" & flag)
assert result.action.typ == actionNil else:
result.action.typ = actionVersion result.action.compileOptions.add("--" & flag & ":" & val)
of "accept", "y": result.forcePrompts = forcePromptYes of actionCustom:
of "reject", "n": result.forcePrompts = forcePromptNo result.action.flags[flag] = val
of "ver": result.queryVersions = true
of "nimbledir": result.config.nimbleDir = val # overrides option from file
of "installed", "i": result.queryInstalled = true
of "depsonly", "d": result.depsOnly = true
else: else:
raise newException(NimbleError, "Unknown option: --" & flag) wasFlagHandled = false
if not wasFlagHandled:
raise newException(NimbleError, "Unknown option: --" & flag)
proc initOptions*(): Options = proc initOptions*(): Options =
result.action.typ = actionNil result.action.typ = actionNil
result.pkgInfoCache = newTable[string, PackageInfo]() result.pkgInfoCache = newTable[string, PackageInfo]()
result.nimbleDir = ""
result.verbosity = HighPriority
proc parseMisc(options: var Options) = proc parseMisc(options: var Options) =
# Load nimbledata.json # Load nimbledata.json
@ -278,9 +292,9 @@ proc parseMisc(options: var Options) =
proc parseCmdLine*(): Options = proc parseCmdLine*(): Options =
result = initOptions() result = initOptions()
result.config = parseConfig()
# Parse command line params. # Parse command line params first. A simple `--version` shouldn't require
# a config to be parsed.
for kind, key, val in getOpt(): for kind, key, val in getOpt():
case kind case kind
of cmdArgument: of cmdArgument:
@ -292,6 +306,12 @@ proc parseCmdLine*(): Options =
parseFlag(key, val, result) parseFlag(key, val, result)
of cmdEnd: assert(false) # cannot happen of cmdEnd: assert(false) # cannot happen
# Set verbosity level.
setVerbosity(result.verbosity)
# Parse config.
result.config = parseConfig()
# Parse other things, for example the nimbledata.json file. # Parse other things, for example the nimbledata.json file.
parseMisc(result) parseMisc(result)
@ -310,8 +330,8 @@ proc getProxy*(options: Options): Proxy =
elif existsEnv("https_proxy"): elif existsEnv("https_proxy"):
url = getEnv("https_proxy") url = getEnv("https_proxy")
except ValueError: except ValueError:
echo("WARNING: Unable to parse proxy from environment: ", display("Warning:", "Unable to parse proxy from environment: " &
getCurrentExceptionMsg()) getCurrentExceptionMsg(), Warning, HighPriority)
if url.len > 0: if url.len > 0:
var parsed = parseUri(url) var parsed = parseUri(url)

View file

@ -1,7 +1,7 @@
# Copyright (C) Dominik Picheta. All rights reserved. # Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info. # BSD License. Look at license.txt for more info.
import parsecfg, json, streams, strutils, parseutils, os, sets, tables import parsecfg, json, streams, strutils, parseutils, os, sets, tables
import version, tools, common, options import version, tools, common, options, cli
type type
Package* = object Package* = object
@ -117,13 +117,10 @@ proc fromJson(obj: JSonNode): Package =
proc readMetaData*(path: string): MetaData = proc readMetaData*(path: string): MetaData =
## Reads the metadata present in ``~/.nimble/pkgs/pkg-0.1/nimblemeta.json`` ## Reads the metadata present in ``~/.nimble/pkgs/pkg-0.1/nimblemeta.json``
var bmeta = path / "nimblemeta.json" var bmeta = path / "nimblemeta.json"
if not existsFile(bmeta):
bmeta = path / "babelmeta.json"
if existsFile(bmeta):
echo("WARNING: using deprecated babelmeta.json file in " & path)
if not existsFile(bmeta): if not existsFile(bmeta):
result.url = "" result.url = ""
echo("WARNING: No nimblemeta.json file found in " & path) display("Warning:", "No nimblemeta.json file found in " & path,
Warning, HighPriority)
return return
# TODO: Make this an error. # TODO: Make this an error.
let cont = readFile(bmeta) let cont = readFile(bmeta)
@ -139,7 +136,7 @@ proc getPackage*(pkg: string, options: Options,
## convenience the proc returns a boolean specifying if the ``resPkg`` was ## convenience the proc returns a boolean specifying if the ``resPkg`` was
## successfully filled with good data. ## successfully filled with good data.
for name, list in options.config.packageLists: for name, list in options.config.packageLists:
echo("Searching in \"", name, "\" package list...") display("Reading", "$1 package list" % name, priority = LowPriority)
let packages = parseFile(options.getNimbleDir() / let packages = parseFile(options.getNimbleDir() /
"packages_" & name.toLowerAscii() & ".json") "packages_" & name.toLowerAscii() & ".json")
for p in packages: for p in packages:
@ -179,8 +176,8 @@ proc findNimbleFile*(dir: string; error: bool): string =
raise newException(NimbleError, raise newException(NimbleError,
"Specified directory does not contain a .nimble file.") "Specified directory does not contain a .nimble file.")
else: else:
# TODO: Abstract logging. display("Warning:", "No .nimble file found for " & dir, Warning,
echo("WARNING: No .nimble file found for ", dir) HighPriority)
proc getInstalledPkgsMin*(libsDir: string, options: Options): proc getInstalledPkgsMin*(libsDir: string, options: Options):
seq[tuple[pkginfo: PackageInfo, meta: MetaData]] = seq[tuple[pkginfo: PackageInfo, meta: MetaData]] =
@ -279,7 +276,8 @@ proc validatePackagesList*(path: string): bool =
let pkgList = parseFile(path) let pkgList = parseFile(path)
if pkgList.kind == JArray: if pkgList.kind == JArray:
if pkgList.len == 0: if pkgList.len == 0:
echo("WARNING: ", path, " contains no packages.") display("Warning:", path & " contains no packages.", Warning,
HighPriority)
return true return true
except ValueError, JsonParsingError: except ValueError, JsonParsingError:
return false return false

View file

@ -1,7 +1,7 @@
# Copyright (C) Dominik Picheta. All rights reserved. # Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info. # BSD License. Look at license.txt for more info.
import parsecfg, json, streams, strutils, parseutils, os, tables import parsecfg, json, streams, strutils, parseutils, os, tables
import version, tools, common, nimscriptsupport, options, packageinfo import version, tools, common, nimscriptsupport, options, packageinfo, cli
## Contains procedures for parsing .nimble files. Moved here from ``packageinfo`` ## Contains procedures for parsing .nimble files. Moved here from ``packageinfo``
## because it depends on ``nimscriptsupport`` (``nimscriptsupport`` also ## because it depends on ``nimscriptsupport`` (``nimscriptsupport`` also
@ -20,11 +20,7 @@ proc newValidationError(msg: string, warnInstalled: bool): ref ValidationError =
result.warnInstalled = warnInstalled result.warnInstalled = warnInstalled
proc raiseNewValidationError(msg: string, warnInstalled: bool) = proc raiseNewValidationError(msg: string, warnInstalled: bool) =
if warnInstalled: raise newValidationError(msg, warnInstalled)
# TODO: We warn everywhere for now. Raise the error in the next version.
echo("WARNING: ", msg, ". Will be an error in next version!")
else:
raise newValidationError(msg, warnInstalled)
proc validatePackageName*(name: string) = proc validatePackageName*(name: string) =
## Raises an error if specified package name contains invalid characters. ## Raises an error if specified package name contains invalid characters.
@ -91,10 +87,11 @@ proc validatePackageInfo(pkgInfo: PackageInfo, path: string) =
proc nimScriptHint*(pkgInfo: PackageInfo) = proc nimScriptHint*(pkgInfo: PackageInfo) =
if not pkgInfo.isNimScript: if not pkgInfo.isNimScript:
# TODO: Turn this into a warning. display("Warning:", "The .nimble file for this project could make use of " &
# TODO: Add a URL explaining more. "additional features, if converted into the new NimScript format." &
echo("NOTE: The .nimble file for this project could make use of " & "\nFor more details see:" &
"additional features, if converted into the new NimScript format.") "https://github.com/nim-lang/nimble#creating-packages",
Warning, HighPriority)
proc multiSplit(s: string): seq[string] = proc multiSplit(s: string): seq[string] =
## Returns ``s`` split by newline and comma characters. ## Returns ``s`` split by newline and comma characters.
@ -242,6 +239,18 @@ proc getInstalledPkgs*(libsDir: string, options: Options):
## Gets a list of installed packages. ## Gets a list of installed packages.
## ##
## ``libsDir`` is in most cases: ~/.nimble/pkgs/ ## ``libsDir`` is in most cases: ~/.nimble/pkgs/
const
readErrorMsg = "Installed package $1 v$2 is outdated or corrupt."
validationErrorMsg = readErrorMsg & "\nPackage did not pass validation: $3"
hintMsg = "The corrupted package will need to be removed manually. To fix" &
" this error message, remove $1."
proc createErrorMsg(tmplt, path, msg: string): string =
let (name, version) = getNameVersion(path)
return tmplt % [name, version, msg]
display("Loading", "list of installed packages", priority = MediumPriority)
result = @[] result = @[]
for kind, path in walkDir(libsDir): for kind, path in walkDir(libsDir):
if kind == pcDir: if kind == pcDir:
@ -254,17 +263,17 @@ proc getInstalledPkgs*(libsDir: string, options: Options):
result.add((pkg, meta)) result.add((pkg, meta))
except ValidationError: except ValidationError:
let exc = (ref ValidationError)(getCurrentException()) let exc = (ref ValidationError)(getCurrentException())
exc.msg = createErrorMsg(validationErrorMsg, path, exc.msg)
exc.hint = hintMsg % path
if exc.warnInstalled: if exc.warnInstalled:
echo("WARNING: Unable to read package info for " & path & "\n" & display("Warning:", exc.msg, Warning, HighPriority)
" Package did not pass validation: " & exc.msg)
else: else:
exc.msg = "Unable to read package info for " & path & "\n" &
" Package did not pass validation: " & exc.msg
raise exc raise exc
except: except:
let exc = getCurrentException() let tmplt = readErrorMsg & "\nMore info: $3"
exc.msg = "Unable to read package info for " & path & "\n" & let msg = createErrorMsg(tmplt, path, getCurrentException().msg)
" Error: " & exc.msg var exc = newException(NimbleError, msg)
exc.hint = hintMsg % path
raise exc raise exc
proc isNimScript*(nf: string, options: Options): bool = proc isNimScript*(nf: string, options: Options): bool =

View file

@ -4,8 +4,9 @@
## Implements 'nimble publish' to create a pull request against ## Implements 'nimble publish' to create a pull request against
## nim-lang/packages automatically. ## nim-lang/packages automatically.
import system except TResult
import httpclient, base64, strutils, rdstdin, json, os, browsers, times, uri import httpclient, base64, strutils, rdstdin, json, os, browsers, times, uri
import tools, common import tools, common, cli
type type
Auth = object Auth = object
@ -22,19 +23,20 @@ proc createHeaders(a: Auth): string =
"Accept: */*\c\L") "Accept: */*\c\L")
proc getGithubAuth(): Auth = proc getGithubAuth(): Auth =
echo("Please create a new personal access token on Github in order to " & display("Info:", "Please create a new personal access token on Github in" &
"allow Nimble to fork the packages repository.") " order to allow Nimble to fork the packages repository.",
priority = HighPriority)
sleep(5000) sleep(5000)
echo("Your default browser should open with the following URL: " & display("Info:", "Your default browser should open with the following URL: " &
"https://github.com/settings/tokens/new") "https://github.com/settings/tokens/new", priority = HighPriority)
sleep(3000) sleep(3000)
openDefaultBrowser("https://github.com/settings/tokens/new") openDefaultBrowser("https://github.com/settings/tokens/new")
result.token = readLineFromStdin("Personal access token: ").strip() result.token = promptCustom("Personal access token?", "").strip()
let resp = getContent("https://api.github.com/user", let resp = getContent("https://api.github.com/user",
extraHeaders=createHeaders(result)).parseJson() extraHeaders=createHeaders(result)).parseJson()
result.user = resp["login"].str result.user = resp["login"].str
echo("Successfully verified as ", result.user) display("Success:", "Verified as " & result.user, Success, HighPriority)
proc isCorrectFork(j: JsonNode): bool = proc isCorrectFork(j: JsonNode): bool =
# Check whether this is a fork of the nimble packages repo. # Check whether this is a fork of the nimble packages repo.
@ -56,7 +58,7 @@ proc createFork(a: Auth) =
extraHeaders=createHeaders(a)) extraHeaders=createHeaders(a))
proc createPullRequest(a: Auth, packageName, branch: string) = proc createPullRequest(a: Auth, packageName, branch: string) =
echo("Creating PR") display("Info", "Creating PR", priority = HighPriority)
discard postContent("https://api.github.com/repos/nim-lang/packages/pulls", discard postContent("https://api.github.com/repos/nim-lang/packages/pulls",
extraHeaders=createHeaders(a), extraHeaders=createHeaders(a),
body="""{"title": "Add package $1", "head": "$2:$3", body="""{"title": "Add package $1", "head": "$2:$3",
@ -129,17 +131,19 @@ proc publish*(p: PackageInfo) =
var pkgsDir = getTempDir() / "nimble-packages-fork" var pkgsDir = getTempDir() / "nimble-packages-fork"
if not forkExists(auth): if not forkExists(auth):
createFork(auth) createFork(auth)
echo "waiting 10s to let Github create a fork ..." display("Info:", "Waiting 10s to let Github create a fork",
priority = HighPriority)
os.sleep(10_000) os.sleep(10_000)
echo "... done" display("Info:", "Finished waiting", priority = LowPriority)
if dirExists(pkgsDir): if dirExists(pkgsDir):
echo("Removing old packages fork git directory.") display("Removing", "old packages fork git directory.",
priority = LowPriority)
removeDir(pkgsDir) removeDir(pkgsDir)
echo "Cloning packages into: ", pkgsDir display("Cloning", "packages into: " & pkgsDir, priority = HighPriority)
doCmd("git clone git@github.com:" & auth.user & "/packages " & pkgsDir) doCmd("git clone git@github.com:" & auth.user & "/packages " & pkgsDir)
# Make sure to update the clone. # Make sure to update the clone.
echo("Updating the fork...") display("Updating", "the fork", priority = HighPriority)
cd pkgsDir: cd pkgsDir:
doCmd("git pull https://github.com/nim-lang/packages.git master") doCmd("git pull https://github.com/nim-lang/packages.git master")
doCmd("git push origin master") doCmd("git push origin master")
@ -175,20 +179,20 @@ proc publish*(p: PackageInfo) =
"No .git nor .hg directory found. Stopping.") "No .git nor .hg directory found. Stopping.")
if url.len == 0: if url.len == 0:
url = readLineFromStdin("Github URL of " & p.name & ": ") url = promptCustom("Github URL of " & p.name & "?", "")
if url.len == 0: userAborted() if url.len == 0: userAborted()
let tags = readLineFromStdin("Please enter a whitespace separated list of tags: ") let tags = promptCustom("Whitespace separated list of tags?", "")
cd pkgsDir: cd pkgsDir:
editJson(p, url, tags, downloadMethod) editJson(p, url, tags, downloadMethod)
let branchName = "add-" & p.name & getTime().getGMTime().format("HHmm") let branchName = "add-" & p.name & getTime().getGMTime().format("HHmm")
doCmd("git checkout -B " & branchName) doCmd("git checkout -B " & branchName)
doCmd("git commit packages.json -m \"Added package " & p.name & "\"") doCmd("git commit packages.json -m \"Added package " & p.name & "\"")
echo("Pushing to remote of fork.") display("Pushing", "to remote of fork.", priority = HighPriority)
doCmd("git push " & getPackageOriginUrl(auth) & " " & branchName) doCmd("git push " & getPackageOriginUrl(auth) & " " & branchName)
createPullRequest(auth, p.name, branchName) createPullRequest(auth, p.name, branchName)
echo "Pull request successful." display("Success:", "Pull request successful.", Success, HighPriority)
when isMainModule: when isMainModule:
import packageinfo import packageinfo

View file

@ -3,7 +3,7 @@
# #
# Various miscellaneous utility functions reside here. # Various miscellaneous utility functions reside here.
import osproc, pegs, strutils, os, uri, sets, json, parseutils import osproc, pegs, strutils, os, uri, sets, json, parseutils
import version, common import version, common, cli
proc extractBin(cmd: string): string = proc extractBin(cmd: string): string =
if cmd[0] == '"': if cmd[0] == '"':
@ -20,11 +20,16 @@ proc doCmd*(cmd: string) =
stdout.flushFile() stdout.flushFile()
stderr.flushFile() stderr.flushFile()
let exitCode = execCmd(cmd) displayDebug("Executing", cmd)
let (output, exitCode) = execCmdEx(cmd)
displayDebug("Finished", "with exit code " & $exitCode)
# TODO: Improve to show output in real-time.
displayDebug("Output", output)
if exitCode != QuitSuccess: if exitCode != QuitSuccess:
raise newException(NimbleError, raise newException(NimbleError,
"Execution failed with exit code " & $exitCode) "Execution failed with exit code $1\nCommand: $2\nOutput: $3" %
[$exitCode, cmd, output])
proc doCmdEx*(cmd: string): tuple[output: TaintedString, exitCode: int] = proc doCmdEx*(cmd: string): tuple[output: TaintedString, exitCode: int] =
let bin = extractBin(cmd) let bin = extractBin(cmd)
@ -50,7 +55,7 @@ proc getNimrodVersion*: Version =
let vOutput = doCmdEx('"' & nimBin & "\" -v").output let vOutput = doCmdEx('"' & nimBin & "\" -v").output
var matches: array[0..MaxSubpatterns, string] var matches: array[0..MaxSubpatterns, string]
if vOutput.find(peg"'Version'\s{(\d+\.)+\d}", matches) == -1: if vOutput.find(peg"'Version'\s{(\d+\.)+\d}", matches) == -1:
quit("Couldn't find Nim version.", QuitFailure) raise newException(NimbleError, "Couldn't find Nim version.")
newVersion(matches[0]) newVersion(matches[0])
proc samePaths*(p1, p2: string): bool = proc samePaths*(p1, p2: string): bool =
@ -75,14 +80,14 @@ proc changeRoot*(origRoot, newRoot, path: string): string =
proc copyFileD*(fro, to: string): string = proc copyFileD*(fro, to: string): string =
## Returns the destination (``to``). ## Returns the destination (``to``).
echo(fro, " -> ", to) display("Copying", "file $# to $#" % [fro, to], priority = LowPriority)
copyFileWithPermissions(fro, to) copyFileWithPermissions(fro, to)
result = to result = to
proc copyDirD*(fro, to: string): seq[string] = proc copyDirD*(fro, to: string): seq[string] =
## Returns the filenames of the files in the directory that were copied. ## Returns the filenames of the files in the directory that were copied.
result = @[] result = @[]
echo("Copying directory: ", fro, " -> ", to) display("Copying", "directory $# to $#" % [fro, to], priority = LowPriority)
for path in walkDirRec(fro): for path in walkDirRec(fro):
createDir(changeRoot(fro, to, path.splitFile.dir)) createDir(changeRoot(fro, to, path.splitFile.dir))
result.add copyFileD(path, changeRoot(fro, to, path)) result.add copyFileD(path, changeRoot(fro, to, path))

View file

@ -34,6 +34,7 @@ type
ParseVersionError* = object of ValueError ParseVersionError* = object of ValueError
NimbleError* = object of Exception NimbleError* = object of Exception
hint*: string
proc newVersion*(ver: string): Version = return Version(ver) proc newVersion*(ver: string): Version = return Version(ver)
proc newSpecial*(spe: string): Special = return Special(spe) proc newSpecial*(spe: string): Special = return Special(spe)

View file

@ -93,15 +93,15 @@ test "can refresh with custom urls":
url = "http://nim-lang.org/nimble/packages.json" url = "http://nim-lang.org/nimble/packages.json"
""".unindent) """.unindent)
let (output, exitCode) = execNimble(["refresh"]) let (output, exitCode) = execNimble(["refresh", "--verbose"])
let lines = output.strip.splitLines() let lines = output.strip.splitLines()
check exitCode == QuitSuccess check exitCode == QuitSuccess
check inLines(lines, "reading from config file") check inLines(lines, "config file at")
check inLines(lines, "downloading \"official\" package list") check inLines(lines, "official package list")
check inLines(lines, "trying http://google.com") check inLines(lines, "http://google.com")
check inLines(lines, "packages.json file is invalid") check inLines(lines, "packages.json file is invalid")
check inLines(lines, "404 not found") check inLines(lines, "404 not found")
check inLines(lines, "done") check inLines(lines, "Package list downloaded.")
# Restore config # Restore config
if fileExists(configBakFile): if fileExists(configBakFile):
@ -113,24 +113,24 @@ test "can install nimscript package":
test "can execute nimscript tasks": test "can execute nimscript tasks":
cd "nimscript": cd "nimscript":
let (output, exitCode) = execNimble("test") let (output, exitCode) = execNimble("--verbose", "test")
let lines = output.strip.splitLines() let lines = output.strip.splitLines()
check exitCode == QuitSuccess check exitCode == QuitSuccess
check lines[^1] == "10" check lines[^1] == "10"
test "can use nimscript's setCommand": test "can use nimscript's setCommand":
cd "nimscript": cd "nimscript":
let (output, exitCode) = execNimble("cTest") let (output, exitCode) = execNimble("--verbose", "cTest")
let lines = output.strip.splitLines() let lines = output.strip.splitLines()
check exitCode == QuitSuccess check exitCode == QuitSuccess
check "Hint: operation successful".normalize in lines[^1].normalize check "Compilation finished".normalize in lines[^1].normalize
test "can use nimscript's setCommand with flags": test "can use nimscript's setCommand with flags":
cd "nimscript": cd "nimscript":
let (output, exitCode) = execNimble("cr") let (output, exitCode) = execNimble("--debug", "cr")
let lines = output.strip.splitLines() let lines = output.strip.splitLines()
check exitCode == QuitSuccess check exitCode == QuitSuccess
check "Hello World".normalize in lines[^1].normalize check "Hello World".normalize in lines[^2].normalize
test "can list nimscript tasks": test "can list nimscript tasks":
cd "nimscript": cd "nimscript":
@ -166,8 +166,8 @@ test "can reject same version dependencies":
# stderr output being generated and flushed without first flushing stdout # stderr output being generated and flushed without first flushing stdout
let ls = outp.strip.splitLines() let ls = outp.strip.splitLines()
check exitCode != QuitSuccess check exitCode != QuitSuccess
check ls[ls.len-1] == "Error: unhandled exception: Cannot satisfy the " & check "Cannot satisfy the dependency on PackageA 0.2.0 and PackageA 0.5.0" in
"dependency on PackageA 0.2.0 and PackageA 0.5.0 [NimbleError]" ls[ls.len-1]
test "can update": test "can update":
check execNimble("update").exitCode == QuitSuccess check execNimble("update").exitCode == QuitSuccess
@ -194,7 +194,7 @@ test "issue #126":
cd "issue126/b": cd "issue126/b":
let (output1, exitCode1) = execNimble("install", "-y") let (output1, exitCode1) = execNimble("install", "-y")
let lines1 = output1.strip.splitLines() let lines1 = output1.strip.splitLines()
check exitCode1 == QuitSuccess check exitCode1 != QuitSuccess
check inLines(lines1, "The .nimble file name must match name specified inside") check inLines(lines1, "The .nimble file name must match name specified inside")
test "issue #108": test "issue #108":
@ -202,7 +202,7 @@ test "issue #108":
let (output, exitCode) = execNimble("build") let (output, exitCode) = execNimble("build")
let lines = output.strip.splitLines() let lines = output.strip.splitLines()
check exitCode != QuitSuccess check exitCode != QuitSuccess
check "Nothing to build" in lines[^1] check inLines(lines, "Nothing to build")
test "issue #206": test "issue #206":
cd "issue206": cd "issue206":
@ -222,8 +222,8 @@ test "can uninstall":
let ls = outp.strip.splitLines() let ls = outp.strip.splitLines()
check exitCode != QuitSuccess check exitCode != QuitSuccess
check ls[ls.len-1] == " Cannot uninstall issue27b (0.1.0) because " & check "Cannot uninstall issue27b (0.1.0) because issue27a (0.1.0) depends" &
"issue27a (0.1.0) depends on it [NimbleError]" " on it" in ls[ls.len-1]
check execNimble("uninstall", "-y", "issue27").exitCode == QuitSuccess check execNimble("uninstall", "-y", "issue27").exitCode == QuitSuccess
check execNimble("uninstall", "-y", "issue27a").exitCode == QuitSuccess check execNimble("uninstall", "-y", "issue27a").exitCode == QuitSuccess
@ -234,8 +234,8 @@ test "can uninstall":
let (outp, exitCode) = execNimble("uninstall", "-y", "PackageA") let (outp, exitCode) = execNimble("uninstall", "-y", "PackageA")
check exitCode != QuitSuccess check exitCode != QuitSuccess
let ls = outp.processOutput() let ls = outp.processOutput()
check ls[ls.len-2].startsWith(" Cannot uninstall PackageA ") check inLines(ls, "Cannot uninstall PackageA (0.2.0)")
check ls[ls.len-1].startsWith(" Cannot uninstall PackageA ") check inLines(ls, "Cannot uninstall PackageA (0.6.0)")
check execNimble("uninstall", "-y", "PackageBin2").exitCode == QuitSuccess check execNimble("uninstall", "-y", "PackageBin2").exitCode == QuitSuccess
# Case insensitive # Case insensitive