Compare commits

..

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

99 changed files with 1478 additions and 4536 deletions

26
.gitignore vendored
View file

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

View file

@ -1,24 +1,20 @@
os: os:
- windows
- linux - linux
- osx
language: c 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/.choosenim"
install: install:
- curl https://gist.github.com/genotrance/fb53504a4fba88bc5201d3783df5c522/raw/travis.sh -LsSf -o travis.sh - |
- source travis.sh wget http://nim-lang.org/download/nim-0.15.2.tar.xz
tar -xf nim-0.15.2.tar.xz
cd nim-0.15.2
LDFLAGS=-lrt sh build.sh
cd ..
before_script:
- set -e
- set -x
- export PATH=`pwd`/nim-0.15.2/bin:$PATH
script: script:
- cd tests - cd tests

View file

@ -3,215 +3,6 @@
# Nimble changelog # Nimble changelog
## 0.11.0 - 22/09/2019
This is a major release containing nearly 60 commits. Most changes are
bug fixes, but this release also includes a couple new features:
- Binaries can now be built and run using the new ``run`` command.
- The ``NimblePkgVersion`` is now defined so you can easily get the package
version in your source code
([example](https://github.com/nim-lang/nimble/blob/4a2aaa07d/tests/nimbleVersionDefine/src/nimbleVersionDefine.nim)).
Some other highlights:
- Temporary files are now kept when the ``--debug`` flag is used.
- Fixed dependency resolution issues with "#head" packages (#432 and #672).
- The `install` command can now take Nim compiler flags via the new
``--passNim`` flag.
- Command line arguments are now passed properly to tasks (#633).
- The ``test`` command now respects the specified backend (#631).
- The ``dump`` command will no longer prompt and now has an implicit ``-y``.
- Fixed bugs with the new nimscript executor (#665).
- Fixed multiple downloads and installs of the same package (#678).
- Nimble init no longer overwrites existing files (#581).
- Fixed incorrect submodule version being pulled when in a non-master branch (#675).
----
Full changelog: https://github.com/nim-lang/nimble/compare/v0.10.2...v0.11.0
## 0.10.2 - 03/06/2019
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.
This release fixes many
issues, with most being fixed by our brilliant contributors. Thanks a lot
everyone!
One big new feature is the new support for multiple Nimble packages in a single
Git/Hg repository. You can now specify ``?subdir=<dir>`` at the end of your
repo's URL and Nimble will know to look in ``<dir>`` for your package.
* **Implemented support for multi-package repos.** See
[#421](https://github.com/nim-lang/nimble/issues/421) for the relevant issue.
* **Better error message when the user has an outdated stdlib version that confuses Nimble**
* **The validity of a Nimble package can now be checked using the new ``check`` command**
* Nimble no longer silently ignores an erroneous '@' in for example
``nimble install compiler@``.
* Issues with the ``nimble path`` command have been fixed.
* The ``nimble publish`` command has been improved and stabilised.
* Messages for the ``NIM_LIB_PREFIX`` env var have been improved.
* ``before install`` is now called when packages are installed by their name.
See [#280](https://github.com/nim-lang/nimble/issues/280).
* Fixed issue with ``nimble init``. See [#446](https://github.com/nim-lang/nimble/issues/446).
* Nimble now rejects [reserved names on Windows](https://github.com/nim-lang/nimble/commit/74856a87084b73451254555b2c20ad932cf84270).
* The ``NIMBLE_DIR`` environment variable is now supported, in addition to the
command line flag and config setting.
* The ``init`` command has been improved significantly.
----
Full changelog: https://github.com/nim-lang/nimble/compare/v0.8.8...v0.8.10
## 0.8.8 - 03/09/2017
This is a relatively big release containing 57 commits, with multiple new
features and many bug fixes.
* **Implemented the `develop` command.** See
[readme](https://github.com/nim-lang/nimble#nimble-develop) for details.
* **Implemented a default `test` task** for packages that don't define it.
* **Lowered the memory consumption** in cases where a package contained many files.
* Nimble now accepts .nimble symlinks.
* Locally stored package list files can now be specified in the Nimble config.
* Fixed branch checkout and handling of branch names with dashes.
* Improved URL detection in ``publish`` feature.
* Fixed many issues related to binary management. Packages are now resymlinked
when an newer version is removed.
([#331](https://github.com/nim-lang/nimble/issues/331))
* Fixed issues with CLI arg passing to the Nim compiler.
([#351](https://github.com/nim-lang/nimble/issues/351))
* Improved performance of ``list -i`` command.
* Fixed issue where warnings weren't suppressed for some commands.
([#290](https://github.com/nim-lang/nimble/issues/290))
* Special versions other than `#head` are no longer considered to be newest.
* Improves the reverse dependency lookup by cross checking it with the
installed list of packages.
([#287](https://github.com/nim-lang/nimble/issues/287))
----
Full changelog: https://github.com/nim-lang/nimble/compare/v0.8.6...v0.8.8
## 0.8.6 - 05/05/2017
Yet another point release which includes various bug fixes and improvements.
* Improves heuristic for finding Nim standard library to support choosenim
installations and adds ability to override it via ``NIM_LIB_PREFIX``
environment variable.
* Implement ``--noColor`` option to remove color from the output.
* Fixes bug when ``srcDir`` contains trailing slash.
* Fixes failure when ``-d`` flag is passed to ``c`` command.
* Show raw output for certain commands.
* GitHub API token can now be specified via the ``NIMBLE_GITHUB_API_TOKEN``
environment variable.
* GitHub API token is now stored in ``~/.nimble/api_token`` so that it
doesn't need to be specified each time.
* Fixes multiple flags not being passed in Nimble task.
----
Full changelog: https://github.com/nim-lang/nimble/compare/v0.8.4...v0.8.6
## 0.8.4 - 29/01/2017
Another bug fix release which resolves problems related to stale nimscriptapi
files in /tmp/, no compilation output when ``nimble build`` fails, and issues
with the new package validation on Windows.
----
Full changelog: https://github.com/nim-lang/nimble/compare/v0.8.2...v0.8.4
## 0.8.2 - 08/01/2017 ## 0.8.2 - 08/01/2017
This is a small bug fix release which resolves problems with the installation This is a small bug fix release which resolves problems with the installation

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 # Package
version = "0.11.0" version = nimbleVersion
author = "Dominik Picheta" author = "Dominik Picheta"
description = "Nim package manager." description = "Nim package manager."
license = "BSD" license = "BSD"
bin = @["nimble"] bin = @["nimble"]
srcDir = "src" srcDir = "src"
installExt = @["nim"]
# Dependencies # Dependencies
requires "nim >= 0.13.0" requires "nim >= 0.13.0", "compiler#head"
when defined(nimdistros): when defined(nimdistros):
import distros import distros

View file

@ -1,51 +0,0 @@
#compdef nimble
_nimble() {
local line
_arguments -C \
'1: :(install init publish uninstall build c cc js doc doc2 refresh search list tasks path dump develop)' \
'*::options:->options' \
'(--version)--version[show version]' \
'(--help)--help[show help]' \
'(-)--help[display help information]' \
'(-)--version[display version information]' \
'(-y --accept)'{-y,--accept}'[accept all interactive prompts]' \
{-n,--reject}'[reject all interactive prompts]' \
'--ver[Query remote server for package version information when searching or listing packages]' \
'--nimbleDir dirname[Set the Nimble directory]' \
'(-d --depsOnly)'{-d,--depsOnly}'[Install only dependencies]'
if [ $#line -eq 0 ]; then
# if the command line is empty and "nimble tasks" is successfull, add custom tasks
tasks=$(nimble tasks)
if [ $? -eq 0 ]; then
compadd - $(echo $tasks | cut -f1 -d" " | tr '\n' ' ')
fi
fi
case $line[1] in
install)
_nimble_installable_packages
;;
uninstall|path|dump)
_nimble_installed_packages
;;
init|publish|build|refresh|search|tasks)
(( ret )) && _message 'no more arguments'
;;
*)
(( ret )) && _message 'no more arguments'
;;
esac
}
function _nimble_installable_packages {
compadd - $(nimble list 2> /dev/null | grep -v '^ ' | tr -d ':')
}
function _nimble_installed_packages {
compadd - $(nimble list -i 2> /dev/null | grep ']$' | cut -d' ' -f1)
}
_nimble "$@"

View file

@ -1,7 +1,7 @@
# Nimble [![Build Status](https://travis-ci.org/nim-lang/nimble.svg?branch=master)](https://travis-ci.org/nim-lang/nimble) # Nimble [![Build Status](https://travis-ci.org/nim-lang/nimble.svg?branch=master)](https://travis-ci.org/nim-lang/nimble)
Nimble is a *beta*-grade *package manager* for the [Nim programming Nimble is a *beta*-grade *package manager* for the [Nim programming
language](https://nim-lang.org). language](http://nim-lang.org).
Interested in learning **how to create a package**? Skip directly to that section Interested in learning **how to create a package**? Skip directly to that section
[here](#creating-packages). [here](#creating-packages).
@ -15,7 +15,6 @@ Interested in learning **how to create a package**? Skip directly to that sectio
- [nimble install](#nimble-install) - [nimble install](#nimble-install)
- [nimble uninstall](#nimble-uninstall) - [nimble uninstall](#nimble-uninstall)
- [nimble build](#nimble-build) - [nimble build](#nimble-build)
- [nimble run](#nimble-run)
- [nimble c](#nimble-c) - [nimble c](#nimble-c)
- [nimble list](#nimble-list) - [nimble list](#nimble-list)
- [nimble search](#nimble-search) - [nimble search](#nimble-search)
@ -51,22 +50,23 @@ Interested in learning **how to create a package**? Skip directly to that sectio
## Requirements ## Requirements
Nimble has some runtime dependencies on external tools, these tools are used to Nimble has some runtime dependencies on external tools, these tools are
download Nimble packages. For instance, if a package is hosted on used to download Nimble packages.
[GitHub](https://github.com), you need to have [git](https://www.git-scm.com) For
installed and added to your environment ``PATH``. Same goes for instance, if a package is hosted on [Github](https://github.com) you require to
[Mercurial](http://mercurial.selenic.com) repositories on have [git](http://www.git-scm.com) installed and added to your environment
[Bitbucket](https://bitbucket.org). Nimble packages are typically hosted in Git ``PATH``. Same goes for [Mercurial](http://mercurial.selenic.com) repositories
repositories so you may be able to get away without installing Mercurial. on [Bitbucket](https://bitbucket.org). Nimble packages are typically hosted in
Git repositories so you may be able to get away without installing Mercurial.
**Warning:** Ensure that you have a fairly recent version of Git installed. **Warning:** Ensure that you have a fairly recent version of Git installed.
If the version is older than 1.9.0, then Nimble may have trouble using it. If the version is less recent than 1.9.0 then Nimble may have trouble using it.
See [this issue](https://github.com/nim-lang/nimble/issues/105) for more See [this issue](https://github.com/nim-lang/nimble/issues/105) for more
information. info.
## Installation ## Installation
Nimble is now bundled with [Nim](https://nim-lang.org) Nimble is now bundled with [Nim](http://nim-lang.org)
(since Nim version 0.15.0). (since Nim version 0.15.0).
This means that you should have Nimble installed already, as long as you have This means that you should have Nimble installed already, as long as you have
the latest version of Nim installed as well. Because of this **you likely do the latest version of Nim installed as well. Because of this **you likely do
@ -75,15 +75,10 @@ not need to install Nimble manually**.
But in case you still want to install Nimble manually, you can follow the But in case you still want to install Nimble manually, you can follow the
following instructions. following instructions.
There are two ways to install Nimble manually. Using ``koch`` and using Nimble There are two ways to install Nimble manually. The first is using the
itself. ``koch`` tool included in the Nim distribution and
### Using koch
The ``koch`` tool is included in the Nim distribution and
[repository](https://github.com/nim-lang/Nim/blob/devel/koch.nim). [repository](https://github.com/nim-lang/Nim/blob/devel/koch.nim).
Simply navigate to the location of your Nim installation and execute the Simply execute the following command to compile and install Nimble.
following command to compile and install Nimble.
``` ```
./koch nimble ./koch nimble
@ -92,19 +87,23 @@ following command to compile and install Nimble.
This will clone the Nimble repository, compile Nimble and copy it into This will clone the Nimble repository, compile Nimble and copy it into
Nim's bin directory. Nim's bin directory.
### Using Nimble 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.
In most cases you will already have Nimble installed, you can install a newer
version of Nimble by simply running the following command:
``` ```
nimble install nimble 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 This will install Nimble to the default Nimble packages location:
using choosenim then you likely already have this set up correctly. ``~/.nimble/pkgs``. The binary will be installed to ``~/.nimble/bin``, so you
will need to add this directory to your PATH.
## Nimble usage ## Nimble usage
@ -131,20 +130,6 @@ a third-party package list.
Package lists can be specified in Nimble's config. Take a look at the Package lists can be specified in Nimble's config. Take a look at the
config section below to see how to do this. config section below to see how to do this.
### nimble check
The ``check`` command will read your package's .nimble file. It will then
verify that the package's structure is valid.
Example:
$ nimble check
Error: 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. This will be an error in the future.
Hint: If 'incorrect' contains source files for building 'x', rename it to 'x'. Otherwise, prevent its installation by adding `skipDirs = @["incorrect"]` to the .nimble file.
Failure: Validation failed
### nimble install ### nimble install
The ``install`` command will download and install a package. You need to pass The ``install`` command will download and install a package. You need to pass
@ -159,9 +144,9 @@ Example:
nake installed successfully nake installed successfully
Nimble always fetches and installs the latest version of a package. Note that Nimble always fetches and installs the latest version of a package. Note that
latest version is defined as the latest tagged version in the Git (or Mercurial) latest version is defined as the latest tagged version in the git (or hg)
repository, if the package has no tagged versions then the latest commit in the repository, if the package has no tagged versions then the latest commit in the
remote repository will be installed. If you already have that version installed, remote repository will be installed. If you already have that version installed
Nimble will ask you whether you wish it to overwrite your local copy. Nimble will ask you whether you wish it to overwrite your local copy.
You can force Nimble to download the latest commit from the package's repo, for You can force Nimble to download the latest commit from the package's repo, for
@ -169,16 +154,15 @@ example:
$ nimble install nimgame@#head $ nimble install nimgame@#head
This is of course Git-specific, for Mercurial, use ``tip`` instead of ``head``. A This is of course git specific, for hg use ``tip`` instead of ``head``. A
branch, tag, or commit hash may also be specified in the place of ``head``. branch, tag, or commit hash may also be specified in the place of ``head``.
Instead of specifying a VCS branch, you may also specify a concrete version or a Instead of specifying a VCS branch you may also specify a version range, for
version range, for example: example:
$ nimble install nimgame@0.5
$ nimble install nimgame@"> 0.5" $ nimble install nimgame@"> 0.5"
The latter command will install a version which is greater than ``0.5``. In this case a version which is greater than ``0.5`` will be installed.
If you don't specify a parameter and there is a ``package.nimble`` file in your If you don't specify a parameter and there is a ``package.nimble`` file in your
current working directory then Nimble will install the package residing in current working directory then Nimble will install the package residing in
@ -186,46 +170,14 @@ the current working directory. This can be useful for developers who are testing
locally their ``.nimble`` files before submitting them to the official package locally their ``.nimble`` files before submitting them to the official package
list. See the [Creating Packages](#creating-packages) section for more info on this. list. See the [Creating Packages](#creating-packages) section for more info on this.
#### Package URLs A URL to a repository can also be specified, Nimble will automatically detect
the type of the repository that the url points to and install it.
A valid URL to a Git or Merurial repository can also be specified, Nimble will
automatically detect the type of the repository that the url points to and
install it.
For repositories containing the Nimble package in a subdirectory, you can
instruct Nimble about the location of your package using the ``?subdir=<path>``
query parameter. For example:
$ nimble install https://github.com/nimble-test/multi?subdir=alpha
### nimble develop
The ``develop`` command allows you to link an existing copy of a package into
your installation directory. This is so that when developing a package you
don't need to keep reinstalling it for every single change.
$ cd ~/projects/jester
$ nimble develop
Any packages depending on ``jester`` will now use the code in
``~/projects/jester``.
If you specify a package name to this command, Nimble will clone it into the
current working directory.
$ nimble develop jester
The ``jester`` package will be cloned into ``./jester`` and it will be linked
to your installation directory.
Just as with the ``install`` command, a package URL may also be specified
instead of a name.
### nimble uninstall ### nimble uninstall
The ``uninstall`` command will remove an installed package. Attempting to remove 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 a package which other packages depend on is disallowed and will result in an
``--inclDeps`` or ``-i`` flag to remove all dependent packages along with the package. error. You must currently manually remove the reverse dependencies first.
Similar to the ``install`` command you can specify a version range, for example: Similar to the ``install`` command you can specify a version range, for example:
@ -236,15 +188,8 @@ Similar to the ``install`` command you can specify a version range, for example:
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 with default their ``.nimble`` package. This command will build the package with default
flags, i.e. a debug build which includes stack traces but no GDB debug flags, i.e. a debug build which includes stack traces but no GDB debug
information. The ``install`` command will build the package in release mode information.
instead. 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 ### nimble c
@ -260,7 +205,7 @@ the command ``c`` or ``compile`` is specified. The more specific ``js``, ``cc``,
The ``list`` command will display the known list of packages available for The ``list`` command will display the known list of packages available for
Nimble. An optional ``--ver`` parameter can be specified to tell Nimble to Nimble. An optional ``--ver`` parameter can be specified to tell Nimble to
query remote Git repositories for the list of versions of the packages and to query remote git repositories for the list of versions of the packages and to
then print the versions. Please note however that this can be slow as each then print the versions. Please note however that this can be slow as each
package must be queried separately. package must be queried separately.
@ -288,8 +233,8 @@ substrings). Example:
Searches are case insensitive. Searches are case insensitive.
An optional ``--ver`` parameter can be specified to tell Nimble to An optional ``--ver`` parameter can be specified to tell Nimble to
query remote Git repositories for the list of versions of the packages and query remote git repositories for the list of versions of the packages and to
then print the versions. However, please note that this can be slow as each then print the versions. Please note however that this can be slow as each
package must be queried separately. package must be queried separately.
### nimble path ### nimble path
@ -313,7 +258,7 @@ which can be useful to read the bundled documentation. Example:
### nimble init ### nimble init
The nimble ``init`` command will start a simple wizard which will create The nimble ``init`` command will start a simple wizard which will create
a quick ``.nimble`` file for your project in the current directory. a quick ``.nimble`` file for your project.
As of version 0.7.0, the ``.nimble`` file that this command creates will As of version 0.7.0, the ``.nimble`` file that this command creates will
use the new NimScript format. use the new NimScript format.
@ -323,11 +268,11 @@ Check out the [Creating Packages](#creating-packages) section for more info.
Publishes your Nimble package to the official Nimble package repository. Publishes your Nimble package to the official Nimble package repository.
**Note:** Requires a valid GitHub account with an SSH key attached to it. To upload your public key onto your GitHub account, follow [this link](https://github.com/settings/keys). **Note:** Requires a valid Github account.
### nimble tasks ### nimble tasks
For a Nimble package in the current working directory, list the tasks which that For a nimble package in the current working directory, list the tasks which that
package defines. This is only supported for packages utilising the new package defines. This is only supported for packages utilising the new
nimscript .nimble files. nimscript .nimble files.
@ -352,27 +297,22 @@ nimbleDir = r"C:\Nimble\"
[PackageList] [PackageList]
name = "CustomPackages" name = "CustomPackages"
url = "http://mydomain.org/packages.json" url = "http://mydomain.org/packages.json"
[PackageList]
name = "Local project packages"
path = r"C:\Projects\Nim\packages.json"
``` ```
You can currently configure the following in this file: You can currently configure the following in this file:
* ``nimbleDir`` - The directory which Nimble uses for package installation. * ``nimbleDir`` - The directory which nimble uses for package installation.
**Default:** ``~/.nimble/`` **Default:** ``~/.nimble/``
* ``chcp`` - Whether to change the current code page when executing Nim * ``chcp`` - Whether to change the current code page when executing Nim
application packages. If ``true`` this will add ``chcp 65001`` to the application packages. If ``true`` this will add ``chcp 65001`` to the
.cmd stubs generated in ``~/.nimble/bin/``. .cmd stubs generated in ``~/.nimble/bin/``.
**Default:** ``true`` **Default:** ``true``
* ``[PackageList]`` + ``name`` + (``url``|``path``) - You can use this section to specify * ``[PackageList]`` + ``name`` + ``url`` - You can use this section to specify
a new custom package list. Multiple package lists can be specified. Nimble a new custom package list. Multiple package lists can be specified. Nimble
defaults to the "Official" package list, you can override it by specifying defaults to the "Official" package list, you can override it by specifying
a ``[PackageList]`` section named "official". Multiple URLs can be specified a ``[PackageList]`` section named "official". Multiple URLs can be specified
under each section, Nimble will try each in succession if under each section, Nimble will try each in succession if
downloading from the first fails. Alternately, ``path`` can specify a downloading from the first fails.
local file path to copy a package list .json file from.
* ``cloneUsingHttps`` - Whether to replace any ``git://`` inside URLs with * ``cloneUsingHttps`` - Whether to replace any ``git://`` inside URLs with
``https://``. ``https://``.
**Default: true** **Default: true**
@ -380,28 +320,25 @@ You can currently configure the following in this file:
Nimble will also attempt to read the ``http_proxy`` and ``https_proxy`` Nimble will also attempt to read the ``http_proxy`` and ``https_proxy``
environment variables. environment variables.
**Default: ""** **Default: ""**
* ``nimLibPrefix`` - Specifies the Nim standard library prefix to help Nimble
find the Nim standard library.
**Default: ""**
## Creating Packages ## Creating Packages
Nimble works on Git repositories as its primary source of packages. Its list of Nimble works on git repositories as its primary source of packages. Its list of
packages is stored in a JSON file which is freely accessible in the packages is stored in a JSON file which is freely accessible in the
[nim-lang/packages repository](https://github.com/nim-lang/packages). [nim-lang/packages repository](https://github.com/nim-lang/packages).
This JSON file provides Nimble with the required Git URL to clone the package This JSON file provides nimble with the required Git URL to clone the package
and install it. Installation and build instructions are contained inside a and install it. Installation and build instructions are contained inside a
file with the ``.nimble`` file extension. The Nimble file shares the file with the ``.nimble`` file extension. The nimble file shares the
package's name, i.e. a package package's name, i.e. a package
named "foobar" should have a corresponding ``foobar.nimble`` file. named "foobar" should have a corresponding ``foobar.nimble`` file.
These files specify information about the package including its author, These files specify information about the package including its the author,
license, dependencies and more. Without one, Nimble is not able to install license, dependencies and more. Without one Nimble is not able to install
a package. a package.
A .nimble file can be created easily using Nimble's ``init`` command. This A .nimble file can be created easily using Nimble's ``init`` command. This
command will ask you a bunch of questions about your package, then generate a command will ask you a bunch of questions about your package, then generate a
.nimble file for you in the current directory. .nimble file for you.
A bare minimum .nimble file follows: A bare minimum .nimble file follows:
@ -428,11 +365,10 @@ You can also specify multiple dependencies like so:
requires "nim >= 0.10.0", "foobar >= 0.1.0" requires "nim >= 0.10.0", "foobar >= 0.1.0"
requires "fizzbuzz >= 1.0" requires "fizzbuzz >= 1.0"
requires "https://github.com/user/pkg#5a54b5e"
``` ```
Nimble currently supports installation of packages from a local directory, a Nimble currently supports installation of packages from a local directory, a
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
the root of the directory or repository being installed. the root of the directory or repository being installed.
The .nimble file is very flexible because it is interpreted using NimScript. The .nimble file is very flexible because it is interpreted using NimScript.
@ -460,7 +396,7 @@ Hello World!
You can place any Nim code inside these tasks. As long as that code does not You can place any Nim code inside these tasks. As long as that code does not
access the FFI. The ``nimscript`` access the FFI. The ``nimscript``
[module](https://nim-lang.org/docs/nimscript.html) in Nim's standard library defines [module](http://nim-lang.org/docs/nimscript.html) in Nim's standard library defines
additional functionality such as the ability to execute external processes additional functionality such as the ability to execute external processes
which makes this feature very powerful. which makes this feature very powerful.
@ -495,63 +431,15 @@ which are also useful. Take a look at it for more information.
### Project structure ### Project structure
For a package named "foobar", the recommended project structure is the following: There is nothing surprising about the recommended project structure. The advice
resembles that of many other package managers.
``` | Directory | Purpose |
. # The root directory of the project | ------------- | -------------------------------------- |
├── LICENSE | ``.`` | Root directory containing .nimble file.|
├── README.md | ``./src/`` | Project source code |
├── foobar.nimble # The project .nimble file | ``./tests/`` | Project test files |
└── src | ``./docs/`` | Project documentation |
└── foobar.nim # Imported via `import foobar`
└── tests # Contains the tests
├── config.nims
├── tfoo1.nim # First test
└── tfoo2.nim # Second test
```
Note that the .nimble file needs to be in the project's root directory. This
directory structure will be created if you run ``nimble init`` inside a
``foobar`` directory.
**Warning:** When source files are placed in a ``src`` directory, the
.nimble file must contain a ``srcDir = "src"`` directive. The ``nimble init``
command takes care of that for you.
When introducing more modules into your package, you should place them in a
separate directory named ``foobar`` (i.e. your package's name). For example:
```
. # The root directory of the project
├── ...
├── foobar.nimble # The project .nimble file
├── src
│ ├── foobar
│ │ ├── utils.nim # Imported via `import foobar/utils`
│ │ └── common.nim # Imported via `import foobar/common`
│ └── foobar.nim # Imported via `import foobar`
└── ...
```
#### Private modules
You may wish to hide certain modules in your package from the users. Create a
``private`` directory for that purpose. For example:
```
. # The root directory of the project
├── ...
├── foobar.nimble # The project .nimble file
├── src
│ ├── foobar
│ │ ├── private
│ │ │ └── hidden.nim # Imported via `import foobar/private/hidden`
│ │ ├── utils.nim # Imported via `import foobar/utils`
│ │ └── common.nim # Imported via `import foobar/common`
│ └── foobar.nim # Imported via `import foobar`
└── ...
```
#### Tests #### Tests
@ -573,55 +461,43 @@ your ``tests`` directory with the following contents:
--path:"../src/" --path:"../src/"
``` ```
Nimble offers a pre-defined ``test`` task which compiles and runs all files To make testing even more convenient, you may wish to define a ``test`` task
in the ``tests`` directory beginning with 't' in their filename. in your ``.nimble`` file. Like so:
You may wish to override this ``test`` task in your ``.nimble`` file. This
is particularly useful when you have a single test suite program. Just add
the following to your ``.nimble`` file to override the default ``test`` task.
```nim ```nim
task test, "Runs the test suite": task test, "Runs the test suite":
exec "nim c -r tests/tester" exec "nim c -r tests/tester"
``` ```
Running ``nimble test`` will now use the ``test`` task you have defined. 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 or binary packages. meant to be used by other library or binary packages.
When Nimble installs a library, it will copy all of its files 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.
It is suggested that the layout be as follows. The directory layout is By convention, it is suggested that the layout be as follows. The directory
determined by the nature of your package, that is, whether your package exposes layout is determined by the nature of your package, that is, whether your
only one module or multiple modules. package exposes only one module or multiple modules.
If your package exposes only a single module, then that module should be If your package exposes only a single module, then that module should be
present in the root directory (the directory with the .nimble file) of your Git present in the root directory (the directory with the .nimble file) of your git
repository, and should be named whatever your package's name is. A good example repository, it is recommended that in this case you name that module whatever
of this is the [jester](https://github.com/dom96/jester) package which exposes your package's name is. A good example of this is the
the ``jester`` module. In this case the jester package is imported with [jester](https://github.com/dom96/jester) package which exposes the ``jester``
``import jester``. module. In this case the jester package is imported with ``import jester``.
If your package exposes multiple modules then the modules should be in a If your package exposes multiple modules then the modules should be in a
``PackageName`` directory. This will allow for a certain measure of isolation ``PackageName`` directory. This will allow for a certain measure of isolation
from other packages which expose modules with the same names. In this case from other packages which expose modules with the same names. In this case
the package's modules will be imported with ``import PackageName/module``. the package's modules will be imported with ``import PackageName/module``.
Here's a simple example multi-module library package called `kool`: You are free to combine the two approaches described.
```
.
├── kool
│   ├── useful.nim
│   └── also_useful.nim
└── kool.nimble
```
In regards to modules which you do **not** wish to be exposed. You should place In regards to modules which you do **not** wish to be exposed. You should place
them in a ``PackageName/private`` directory. Your modules may then import these them in a ``PackageName/private`` directory. Your modules may then import these
@ -645,36 +521,33 @@ A package is automatically a binary package as soon as it sets at least one
bin = @["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 .cmd 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 include ``skipExt = @["nim"]`` Binary packages should not install .nim files so include
in your .nimble file, unless you intend for your package to be a binary/library ``skipExt = @["nim"]`` in your .nimble file, unless you intend for your package to
combo. be a binary/library combo which is fine.
Dependencies are automatically installed before building. Dependencies are automatically installed before building.
It's a good idea to test that the dependencies you specified are correct by 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. of your package.
### Hybrids ### Hybrids
One thing to note about binary packages that contain source files aside from One thing to note about library and binary package hybrids is that your binary
the one(s) specified in `bin` (or that also expose multiple library modules, as may share the name of the package. This will mean that you will
above) is that a binary may share the name of the package: this will mean not be able to put your .nim files in a ``pkgname`` directory. The reason you
that you will not be able to put your additional .nim files in a ``pkgname`` will not be able to do this is because binaries on some operating systems
directory. The reason for this is that binaries on some operating systems do do not have an extension so they will clash with a directory of the same name.
not have an extension, so they will clash with a directory of the same name.
If this is the case, you should place your additional .nim files in a directory The current
with `pkg` appended after the name of the project. For instance, if you were convention to get around this problem is to append ``pkg`` to the name as is
building a binary named `project`, you would put any additional source files in done for nimble.
a directory called `projectpkg`. From within project.nim you would then import
those modules namespaced with `projectpkg/`.
### Dependencies ### Dependencies
@ -688,16 +561,16 @@ 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
(``>=``) operators. (``>=``) oeprators.
Two version ranges may be combined using the ``&`` operator, for example 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 ``> 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. If this happens Nimble will refuse to install the package.
In addition to versions you may also specify Git/Mercurial tags, branches and commits. In addition to versions you may also specify git/hg tags, branches and commits.
Although these have to be specific; ranges of commits are not supported. Although these have to be specific; ranges of commits are not supported.
This is done with the ``#`` character, 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
@ -728,7 +601,7 @@ when defined(nimdistros):
The ``when`` branch is important to support installation using older versions The ``when`` branch is important to support installation using older versions
of Nimble. of Nimble.
The [distros module](https://nim-lang.org/docs/distros.html) in Nim's The [distros module](nim-lang.org/docs/distros.html) in Nim's
standard library contains a list of all the supported Operating Systems and standard library contains a list of all the supported Operating Systems and
Linux distributions. Linux distributions.
@ -738,13 +611,13 @@ installing your package (on macOS):
``` ```
Hint: This package requires some external dependencies. Hint: This package requires some external dependencies.
Hint: To install them you may be able to run: Hint: To install them you may be able to run:
Hint: brew install openssl Hint: sudo brew install openssl
``` ```
### Nim compiler ### Nim compiler
The Nim compiler cannot read .nimble files. Its knowledge of Nimble is 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 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 it cannot resolve dependencies, and it can only use the latest version of a
package when compiling. package when compiling.
@ -754,39 +627,18 @@ It resolves the dependencies and feeds the path of each package to
the compiler so that it knows precisely which version to use. the compiler so that it knows precisely which version to use.
This means that you can safely compile using the compiler when developing your 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 software, but you should use nimble to build the package before publishing it
to ensure that the dependencies you specified are correct. 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
Versions of cloned packages via Git or Mercurial are determined through the Versions of cloned packages via git or mercurial are determined through the
repository's *tags*. repository's *tags*.
When installing a package which needs to be downloaded, after the download is When installing a package which needs to be downloaded, after the download is
complete and if the package is distributed through a VCS, Nimble will check the complete and if the package is distributed through a VCS, nimble will check the
cloned repository's tags list. If no tags exist, Nimble will simply install the cloned repository's tags list. If no tags exist, nimble will simply install the
HEAD (or tip in Mercurial) of the repository. If tags exist, Nimble will attempt HEAD (or tip in mercurial) of the repository. If tags exist, nimble will attempt
to look for tags which resemble versions (e.g. v0.1) and will then find the to look for tags which resemble versions (e.g. v0.1) and will then find the
latest version out of the available tags, once it does so it will install the latest version out of the available tags, once it does so it will install the
package after checking out the latest version. package after checking out the latest version.
@ -813,20 +665,7 @@ To summarise, the steps for release are:
Once the new tag is in the remote repository, Nimble will be able to detect Once the new tag is in the remote repository, Nimble will be able to detect
the new version. the new version.
##### Git Version Tagging ##<a name="submitting-your-package-to-the-package-list"> Publishing packages
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 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 a specific name to a URL pointing to your package. This mapping is stored
@ -889,7 +728,7 @@ Nimble includes a ``publish`` command which does this for you automatically.
root dir of the package. root dir of the package.
* ``bin`` - A list of files which should be built separated by commas with * ``bin`` - A list of files which should be built separated by commas with
no file extension required. This option turns your package into a *binary no file extension required. This option turns your package into a *binary
package*, Nimble will build the files specified and install them appropriately. package*, nimble will build the files specified and install them appropriately.
* ``backend`` - Specifies the backend which will be used to build the files * ``backend`` - Specifies the backend which will be used to build the files
listed in ``bin``. Possible values include: ``c``, ``cc``, ``cpp``, ``objc``, listed in ``bin``. Possible values include: ``c``, ``cc``, ``cpp``, ``objc``,
``js``. ``js``.
@ -921,27 +760,15 @@ work properly and you won't be able to run them.
* ```SSL support is not available. Cannot connect over SSL. [HttpRequestError]``` * ```SSL support is not available. Cannot connect over SSL. [HttpRequestError]```
Make sure that Nimble is configured to run with SSL, adding a ```-d:ssl``` Make sure that nimble is configured to run with SSL, adding a ```-d:ssl```
flag to the file ```src/nimble.nim.cfg```. flag to the file ```src/nimble.nim.cfg```.
After that, you can run ```src/nimble install``` and overwrite the existing After that, you can run ```src/nimble install``` and overwrite the existing
installation. installation.
* ``Could not download: error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure``
If you are on macOS, you need to set and export the ```DYLD_LIBRARY_PATH``` environment variable to the directory where your OpenSSL libraries are. For example, if you use OpenSSL, you have to set ```export DYLD_LIBRARY_PATH=/usr/local/opt/openssl/lib``` in your ```$HOME/.bashrc``` file.
* ``Error: ambiguous identifier: 'version' --use nimscriptapi.version or system.version`` * ``Error: ambiguous identifier: 'version' --use nimscriptapi.version or system.version``
Make sure that you are running at least version 0.16.0 of Nim (or the latest nightly). Make sure that you are running at least version 0.16.0 of Nim (or the latest nightly).
* ``Error: cannot open '/home/user/.nimble/lib/system.nim'.``
Nimble cannot find the Nim standard library. This is considered a bug so
please report it. As a workaround you can set the ``NIM_LIB_PREFIX`` environment
variable to the directory where ``lib/system.nim`` (and other standard library
files) are found. Alternatively you can also configure this in Nimble's
config file.
## Repository information ## Repository information
This repository has two main branches: ``master`` and ``stable``. This repository has two main branches: ``master`` and ``stable``.
@ -950,7 +777,7 @@ The ``master`` branch is...
* default * default
* bleeding edge * bleeding edge
* tested to compile with a pinned (close to HEAD) commit of Nim * tested to compile with the latest Nim version
The ``stable`` branch is... The ``stable`` branch is...
@ -961,37 +788,21 @@ The ``stable`` branch is...
Note: The travis build only tests whether Nimble works with the latest Nim Note: The travis build only tests whether Nimble works with the latest Nim
version. version.
A new Nim release (via ``koch xz``) will always bundle the ``stable`` branch. A new Nim release (via ``koch xz``) will always bundle the latest tagged
Nimble release.
## Contribution ## Contribution
If you would like to help, feel free to fork and make any additions you see fit If you would like to help, feel free to fork and make any additions you see fit
and then send a pull request. and then send a pull request.
If you have any questions about the project, you can ask me directly on GitHub, If you have any questions about the project you can ask me directly on github,
ask on the Nim [forum](https://forum.nim-lang.org), or ask on Freenode in ask on the Nim [forum](http://forum.nim-lang.org), or ask on Freenode in
the #nim channel. the #nim channel.
## Implementation details
### .nimble-link
These files are created by Nimble when using the ``develop`` command. They
are very simple and contain two lines.
**The first line:** Always a path to the `.nimble` file.
**The second line:** Always a path to the Nimble package's source code. Usually
``$pkgDir/src``, depending on what ``srcDir`` is set to.
The paths written by Nimble are **always** absolute. But Nimble (and the
Nim compiler) also supports relative paths, which will be read relative to
the `.nimble-link` file.
## About ## About
Nimble has been written by [Dominik Picheta](https://picheta.me/) with help from Nimble has been written by [Dominik Picheta](http://picheta.me/) with help from
a number of a number of
[contributors](https://github.com/nim-lang/nimble/graphs/contributors). [contributors](https://github.com/nim-lang/nimble/graphs/contributors).
It is licensed under the 3-clause BSD license, see [license.txt](license.txt) It is licensed under the BSD license (Look at license.txt for more info).
for more information.

File diff suppressed because it is too large Load diff

View file

@ -3,4 +3,3 @@
--path:"$nim/" --path:"$nim/"
--path:"./vendor/nim" --path:"./vendor/nim"
-d:ssl -d:ssl
-d:nimcore # Enable 'gorge' in Nim's VM. See https://github.com/nim-lang/Nim/issues/8096

View file

@ -12,22 +12,13 @@
# - Bright for HighPriority. # - Bright for HighPriority.
# - Normal for MediumPriority. # - Normal for MediumPriority.
import terminal, sets, strutils import logging, terminal, sets, strutils
import version
when not declared(initHashSet):
import common
type type
CLI* = ref object CLI* = ref object
level: Priority level: Priority
warnings: HashSet[(string, string)] warnings: HashSet[(string, string)]
suppressionCount: int ## Amount of messages which were not shown. suppressionCount: int ## Amount of messages which were not shown.
showColor: bool ## Whether messages should be colored.
suppressMessages: bool ## Whether Warning, Message and Success messages
## should be suppressed, useful for
## commands like `dump` whose output should be
## machine readable.
Priority* = enum Priority* = enum
DebugPriority, LowPriority, MediumPriority, HighPriority DebugPriority, LowPriority, MediumPriority, HighPriority
@ -45,14 +36,11 @@ const
styles: array[DebugPriority .. HighPriority, set[Style]] = styles: array[DebugPriority .. HighPriority, set[Style]] =
[{styleDim}, {styleDim}, {}, {styleBright}] [{styleDim}, {styleDim}, {}, {styleBright}]
proc newCLI(): CLI = proc newCLI(): CLI =
result = CLI( result = CLI(
level: HighPriority, level: HighPriority,
warnings: initHashSet[(string, string)](), warnings: initSet[(string, string)](),
suppressionCount: 0, suppressionCount: 0
showColor: true,
suppressMessages: false
) )
var globalCLI = newCLI() var globalCLI = newCLI()
@ -62,37 +50,19 @@ proc calculateCategoryOffset(category: string): int =
assert category.len <= longestCategory assert category.len <= longestCategory
return longestCategory - category.len return longestCategory - category.len
proc isSuppressed(displayType: DisplayType): bool =
# Don't print any Warning, Message or Success messages when suppression of
# warnings is enabled. That is, unless the user asked for --verbose output.
if globalCLI.suppressMessages and displayType >= Warning and
globalCLI.level == HighPriority:
return true
proc displayCategory(category: string, displayType: DisplayType, proc displayCategory(category: string, displayType: DisplayType,
priority: Priority) = priority: Priority) =
if isSuppressed(displayType):
return
# Calculate how much the `category` must be offset to align along a center # Calculate how much the `category` must be offset to align along a center
# line. # line.
let offset = calculateCategoryOffset(category) let offset = calculateCategoryOffset(category)
# Display the category. # Display the category.
let text = "$1$2 " % [spaces(offset), category] if priority != DebugPriority:
if globalCLI.showColor: setForegroundColor(stdout, foregrounds[displayType])
if priority != DebugPriority: writeStyled("$1$2 " % [repeatChar(offset), category], styles[priority])
setForegroundColor(stdout, foregrounds[displayType]) resetAttributes()
writeStyled(text, styles[priority])
resetAttributes()
else:
stdout.write(text)
proc displayLine(category, line: string, displayType: DisplayType, proc displayLine(category, line: string, displayType: DisplayType,
priority: Priority) = priority: Priority) =
if isSuppressed(displayType):
return
displayCategory(category, displayType, priority) displayCategory(category, displayType, priority)
# Display the message. # Display the message.
@ -147,7 +117,7 @@ proc prompt*(forcePrompts: ForcePrompt, question: string): bool =
display("Prompt:", question & " -> [forced no]", Warning, HighPriority) display("Prompt:", question & " -> [forced no]", Warning, HighPriority)
return false return false
of dontForcePrompt: of dontForcePrompt:
displayLine("Prompt:", question & " [y/N]", Warning, HighPriority) display("Prompt:", question & " [y/N]", Warning, HighPriority)
displayCategory("Answer:", Warning, HighPriority) displayCategory("Answer:", Warning, HighPriority)
let yn = stdin.readLine() let yn = stdin.readLine()
case yn.normalize case yn.normalize
@ -158,120 +128,23 @@ proc prompt*(forcePrompts: ForcePrompt, question: string): bool =
else: else:
return false return false
proc promptCustom*(forcePrompts: ForcePrompt, question, default: string): string =
case forcePrompts:
of forcePromptYes:
display("Prompt:", question & " -> [forced " & default & "]", Warning,
HighPriority)
return default
else:
if default == "":
display("Prompt:", question, Warning, HighPriority)
displayCategory("Answer:", Warning, HighPriority)
let user = stdin.readLine()
if user.len == 0: return promptCustom(forcePrompts, 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 promptCustom*(question, default: string): string = proc promptCustom*(question, default: string): string =
return promptCustom(dontForcePrompt, question, default) if default == "":
display("Prompt:", question, Warning, HighPriority)
proc promptListInteractive(question: string, args: openarray[string]): string = displayCategory("Answer:", Warning, HighPriority)
display("Prompt:", question, Warning, HighPriority) let user = stdin.readLine()
display("Select", "Cycle with 'Tab', 'Enter' when done", Message, if user.len == 0: return promptCustom(question, default)
HighPriority) else: return user
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:
result = args[0]
display("Prompt:", question & " -> [forced " & result & "]", Warning,
HighPriority)
else: else:
if isatty(stdout): display("Prompt:", question & " [" & default & "]", Warning, HighPriority)
return promptListInteractive(question, args) displayCategory("Answer:", Warning, HighPriority)
else: let user = stdin.readLine()
return promptListFallback(question, args) if user == "": return default
else: return user
proc setVerbosity*(level: Priority) = proc setVerbosity*(level: Priority) =
globalCLI.level = level globalCLI.level = level
proc setShowColor*(val: bool) =
globalCLI.showColor = val
proc setSuppressMessages*(val: bool) =
globalCLI.suppressMessages = val
when isMainModule: when isMainModule:
display("Reading", "config file at /Users/dom/.config/nimble/nimble.ini", display("Reading", "config file at /Users/dom/.config/nimble/nimble.ini",
priority = LowPriority) priority = LowPriority)

View file

@ -8,6 +8,7 @@ when not defined(nimscript):
import sets import sets
import version import version
export version.NimbleError # TODO: Surely there is a better way?
type type
BuildFailed* = object of NimbleError BuildFailed* = object of NimbleError
@ -17,13 +18,11 @@ when not defined(nimscript):
isNimScript*: bool ## Determines if this pkg info was read from a nims file isNimScript*: bool ## Determines if this pkg info was read from a nims file
isMinimal*: bool isMinimal*: bool
isInstalled*: bool ## Determines if the pkg this info belongs to is installed isInstalled*: bool ## Determines if the pkg this info belongs to is installed
isLinked*: bool ## Determines if the pkg this info belongs to has been linked via `develop`
postHooks*: HashSet[string] ## Useful to know so that Nimble doesn't execHook unnecessarily postHooks*: HashSet[string] ## Useful to know so that Nimble doesn't execHook unnecessarily
preHooks*: HashSet[string] preHooks*: HashSet[string]
name*: string name*: string
## The version specified in the .nimble file.Assuming info is non-minimal, ## The version specified in the .nimble file.Assuming info is non-minimal,
## it will always be a non-special version such as '0.1.4'. ## it will always be a non-special version such as '0.1.4'
## If in doubt, use `getConcreteVersion` instead.
version*: string version*: string
specialVersion*: string ## Either `myVersion` or a special version such as #head. specialVersion*: string ## Either `myVersion` or a special version such as #head.
author*: string author*: string
@ -50,29 +49,5 @@ when not defined(nimscript):
exc.hint = hint exc.hint = hint
raise exc raise exc
proc getOutputInfo*(err: ref NimbleError): (string, string) =
var error = ""
var hint = ""
error = err.msg
when not defined(release):
let stackTrace = getStackTrace(err)
error = stackTrace & "\n\n" & error
if not err.isNil:
hint = err.hint
return (error, hint)
const const
nimbleVersion* = "0.11.0" nimbleVersion* = "0.8.2"
when not declared(initHashSet):
import sets
template initHashSet*[A](initialSize = 64): HashSet[A] =
initSet[A](initialSize)
when not declared(toHashSet):
import sets
template toHashSet*[A](keys: openArray[A]): HashSet[A] =
toSet(keys)

View file

@ -1,8 +1,8 @@
# Copyright (C) Dominik Picheta. All rights reserved. # Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info. # BSD License. Look at license.txt for more info.
import parsecfg, streams, strutils, os, tables, uri import parsecfg, streams, strutils, os, tables, Uri
import version, cli import tools, version, common, cli
type type
Config* = object Config* = object
@ -11,15 +11,16 @@ type
packageLists*: Table[string, PackageList] ## Names -> 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.
nimLibPrefix*: string # Nim stdlib prefix.
PackageList* = object PackageList* = object
name*: string name*: string
urls*: seq[string] urls*: seq[string]
path*: string
proc initConfig(): Config = proc initConfig(): Config =
result.nimbleDir = getHomeDir() / ".nimble" if getNimrodVersion() > newVersion("0.9.6"):
result.nimbleDir = getHomeDir() / ".nimble"
else:
result.nimbleDir = getHomeDir() / ".babel"
result.httpProxy = initUri() result.httpProxy = initUri()
@ -34,12 +35,9 @@ proc initConfig(): Config =
]) ])
result.packageLists["official"] = defaultPkgList result.packageLists["official"] = defaultPkgList
result.nimLibPrefix = ""
proc initPackageList(): PackageList = proc initPackageList(): PackageList =
result.name = "" result.name = ""
result.urls = @[] result.urls = @[]
result.path = ""
proc addCurrentPkgList(config: var Config, currentPackageList: PackageList) = proc addCurrentPkgList(config: var Config, currentPackageList: PackageList) =
if currentPackageList.name.len > 0: if currentPackageList.name.len > 0:
@ -58,6 +56,7 @@ proc parseConfig*(): Config =
if f != nil: if f != nil:
display("Warning", "Using deprecated config file at " & confFile, display("Warning", "Using deprecated config file at " & confFile,
Warning, HighPriority) Warning, HighPriority)
if f != nil: if f != nil:
display("Reading", "config file at " & confFile, priority = LowPriority) display("Reading", "config file at " & confFile, priority = LowPriority)
var p: CfgParser var p: CfgParser
@ -68,12 +67,7 @@ proc parseConfig*(): Config =
var e = next(p) var e = next(p)
case e.kind case e.kind
of cfgEof: of cfgEof:
if currentSection.len > 0: 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 break
of cfgSectionStart: of cfgSectionStart:
addCurrentPkgList(result, currentPackageList) addCurrentPkgList(result, currentPackageList)
@ -106,16 +100,6 @@ proc parseConfig*(): Config =
of "packagelist": of "packagelist":
currentPackageList.urls.add(e.value) currentPackageList.urls.add(e.value)
else: assert false else: assert false
of "path":
case currentSection.normalize
of "packagelist":
if currentPackageList.path != "":
raise newException(NimbleError, "Attempted to specify more than one `path` for the same package list.")
else:
currentPackageList.path = e.value
else: assert false
of "nimlibprefix":
result.nimLibPrefix = e.value
else: else:
raise newException(NimbleError, "Unable to parse config file:" & raise newException(NimbleError, "Unable to parse config file:" &
" Unknown key: " & e.key) " Unknown key: " & e.key)

View file

@ -1,16 +1,15 @@
# 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 parseutils, os, osproc, strutils, tables, pegs, uri import parseutils, os, osproc, strutils, tables, pegs
import packageinfo, packageparser, version, tools, common, options, cli import packageinfo, packageparser, version, tools, common, options, cli
from algorithm import SortOrder, sorted
from sequtils import toSeq, filterIt, map
type type
DownloadMethod* {.pure.} = enum DownloadMethod* {.pure.} = enum
git = "git", hg = "hg" git = "git", hg = "hg"
proc getSpecificDir(meth: DownloadMethod): string {.used.} = proc getSpecificDir(meth: DownloadMethod): string =
case meth case meth
of DownloadMethod.git: of DownloadMethod.git:
".git" ".git"
@ -25,12 +24,11 @@ proc doCheckout(meth: DownloadMethod, downloadDir, branch: string) =
# clone has happened. Like in the case of git on Windows where it # clone has happened. Like in the case of git on Windows where it
# messes up the damn line endings. # messes up the damn line endings.
doCmd("git checkout --force " & branch) doCmd("git checkout --force " & branch)
doCmd("git submodule update --recursive")
of DownloadMethod.hg: of DownloadMethod.hg:
cd downloadDir: cd downloadDir:
doCmd("hg checkout " & branch) doCmd("hg checkout " & branch)
proc doPull(meth: DownloadMethod, downloadDir: string) {.used.} = proc doPull(meth: DownloadMethod, downloadDir: string) =
case meth case meth
of DownloadMethod.git: of DownloadMethod.git:
doCheckout(meth, downloadDir, "") doCheckout(meth, downloadDir, "")
@ -44,17 +42,17 @@ proc doPull(meth: DownloadMethod, downloadDir: string) {.used.} =
doCmd("hg pull") doCmd("hg pull")
proc doClone(meth: DownloadMethod, url, downloadDir: string, branch = "", proc doClone(meth: DownloadMethod, url, downloadDir: string, branch = "",
onlyTip = true) = tip = true) =
case meth case meth
of DownloadMethod.git: of DownloadMethod.git:
let let
depthArg = if onlyTip: "--depth 1 " else: "" depthArg = if tip: "--depth 1 " else: ""
branchArg = if branch == "": "" else: "-b " & branch & " " branchArg = if branch == "": "" else: "-b " & branch & " "
doCmd("git clone --recursive " & depthArg & branchArg & url & doCmd("git clone --recursive " & depthArg & branchArg & url &
" " & downloadDir) " " & downloadDir)
of DownloadMethod.hg: of DownloadMethod.hg:
let let
tipArg = if onlyTip: "-r tip " else: "" tipArg = if tip: "-r tip " else: ""
branchArg = if branch == "": "" else: "-b " & branch & " " branchArg = if branch == "": "" else: "-b " & branch & " "
doCmd("hg clone " & tipArg & branchArg & url & " " & downloadDir) 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 # http://stackoverflow.com/questions/2039150/show-tags-for-remote-hg-repository
raise newException(ValueError, "Hg doesn't support remote tag querying.") raise newException(ValueError, "Hg doesn't support remote tag querying.")
proc getVersionList*(tags: seq[string]): OrderedTable[Version, string] = proc getVersionList*(tags: seq[string]): Table[Version, string] =
## Return an ordered table of Version -> git tag label. Ordering is # Returns: TTable of version -> git tag name
## in descending order with the most recent version first. result = initTable[Version, string]()
let taggedVers: seq[tuple[ver: Version, tag: string]] = for tag in tags:
tags if tag != "":
.filterIt(it != "") let i = skipUntil(tag, Digits) # skip any chars before the version
.map(proc(s: string): tuple[ver: Version, tag: string] = # TODO: Better checking, tags can have any names. Add warnings and such.
# skip any chars before the version result[newVersion(tag[i .. tag.len-1])] = tag
let i = skipUntil(s, Digits)
# TODO: Better checking, tags can have any
# names. Add warnings and such.
result = (newVersion(s[i .. s.len-1]), s))
.sorted(proc(a, b: (Version, string)): int = cmp(a[0], b[0]),
SortOrder.Descending)
result = toOrderedTable[Version, string](taggedVers)
proc getDownloadMethod*(meth: string): DownloadMethod = proc getDownloadMethod*(meth: string): DownloadMethod =
case meth case meth
@ -141,24 +132,13 @@ proc checkUrlType*(url: string): DownloadMethod =
elif doCmdEx("hg identify " & url).exitCode == QuitSuccess: elif doCmdEx("hg identify " & url).exitCode == QuitSuccess:
return DownloadMethod.hg return DownloadMethod.hg
else: else:
raise newException(NimbleError, "Unable to identify url: " & url) raise newException(NimbleError, "Unable to identify url.")
proc getUrlData*(url: string): (string, Table[string, string]) =
var uri = parseUri(url)
# TODO: use uri.parseQuery once it lands... this code is quick and dirty.
var subdir = ""
if uri.query.startsWith("subdir="):
subdir = uri.query[7 .. ^1]
uri.query = ""
return ($uri, {"subdir": subdir}.toTable())
proc isURL*(name: string): bool = proc isURL*(name: string): bool =
name.startsWith(peg" @'://' ") name.startsWith(peg" @'://' ")
proc doDownload(url: string, downloadDir: string, verRange: VersionRange, proc doDownload*(url: string, downloadDir: string, verRange: VersionRange,
downMethod: DownloadMethod, downMethod: DownloadMethod, options: Options): Version =
options: Options): Version =
## Downloads the repository specified by ``url`` using the specified download ## Downloads the repository specified by ``url`` using the specified download
## method. ## method.
## ##
@ -175,20 +155,30 @@ proc doDownload(url: string, downloadDir: string, verRange: VersionRange,
meth meth
if $latest.ver != "": if $latest.ver != "":
result = latest.ver result = latest.ver
else:
# Result should already be set to #head here.
assert(not result.isNil)
proc verifyClone() =
## Makes sure that the downloaded package's version satisfies the requested
## version range.
let pkginfo = getPkgInfo(downloadDir, options)
if pkginfo.version.newVersion notin verRange:
raise newException(NimbleError,
"Downloaded package's version does not satisfy requested version " &
"range: wanted $1 got $2." %
[$verRange, $pkginfo.version])
removeDir(downloadDir) removeDir(downloadDir)
if verRange.kind == verSpecial: if verRange.kind == verSpecial:
# We want a specific commit/branch/tag here. # We want a specific commit/branch/tag here.
if verRange.spe == getHeadName(downMethod): if verRange.spe == getHeadName(downMethod):
# Grab HEAD. doClone(downMethod, url, downloadDir) # Grab HEAD.
doClone(downMethod, url, downloadDir, onlyTip = not options.forceFullClone)
else: else:
# Grab the full repo. # 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. # Then perform a checkout operation to get the specified branch/commit.
# `spe` starts with '#', trim it. doCheckout(downMethod, downloadDir, $verRange.spe)
doAssert(($verRange.spe)[0] == '#')
doCheckout(downMethod, downloadDir, substr($verRange.spe, 1))
result = verRange.spe result = verRange.spe
else: else:
case downMethod case downMethod
@ -201,13 +191,14 @@ proc doDownload(url: string, downloadDir: string, verRange: VersionRange,
getLatestByTag: getLatestByTag:
display("Cloning", "latest tagged version: " & latest.tag, display("Cloning", "latest tagged version: " & latest.tag,
priority = MediumPriority) priority = MediumPriority)
doClone(downMethod, url, downloadDir, latest.tag, doClone(downMethod, url, downloadDir, latest.tag)
onlyTip = not options.forceFullClone)
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.
doClone(downMethod, url, downloadDir) # Grab HEAD. doClone(downMethod, url, downloadDir) # Grab HEAD.
verifyClone()
of DownloadMethod.hg: of DownloadMethod.hg:
doClone(downMethod, url, downloadDir, onlyTip = not options.forceFullClone) doClone(downMethod, url, downloadDir)
result = getHeadName(downMethod) result = getHeadName(downMethod)
let versions = getTagsList(downloadDir, downMethod).getVersionList() let versions = getTagsList(downloadDir, downMethod).getVersionList()
@ -217,57 +208,7 @@ proc doDownload(url: string, downloadDir: string, verRange: VersionRange,
priority = MediumPriority) priority = MediumPriority)
doCheckout(downMethod, downloadDir, latest.tag) doCheckout(downMethod, downloadDir, latest.tag)
proc downloadPkg*(url: string, verRange: VersionRange, verifyClone()
downMethod: DownloadMethod,
subdir: string,
options: Options,
downloadPath = ""): (string, Version) =
## Downloads the repository as specified by ``url`` and ``verRange`` using
## the download method specified.
##
## If `downloadPath` isn't specified a location in /tmp/ will be used.
##
## Returns the directory where it was downloaded (subdir is appended) and
## the concrete version which was downloaded.
let downloadDir =
if downloadPath == "":
(getNimbleTempDir() / getDownloadDirName(url, verRange))
else:
downloadPath
createDir(downloadDir)
var modUrl =
if url.startsWith("git://") and options.config.cloneUsingHttps:
"https://" & url[6 .. ^1]
else: url
# Fixes issue #204
# github + https + trailing url slash causes a
# checkout/ls-remote to fail with Repository not found
if modUrl.contains("github.com") and modUrl.endswith("/"):
modUrl = modUrl[0 .. ^2]
if subdir.len > 0:
display("Downloading", "$1 using $2 (subdir is '$3')" %
[modUrl, $downMethod, subdir],
priority = HighPriority)
else:
display("Downloading", "$1 using $2" % [modUrl, $downMethod],
priority = HighPriority)
result = (
downloadDir / subdir,
doDownload(modUrl, downloadDir, verRange, downMethod, options)
)
if verRange.kind != verSpecial:
## Makes sure that the downloaded package's version satisfies the requested
## version range.
let pkginfo = getPkgInfo(result[0], options)
if pkginfo.version.newVersion notin verRange:
raise newException(NimbleError,
"Downloaded package's version does not satisfy requested version " &
"range: wanted $1 got $2." %
[$verRange, $pkginfo.version])
proc echoPackageVersions*(pkg: Package) = proc echoPackageVersions*(pkg: Package) =
let downMethod = pkg.downloadMethod.getDownloadMethod() let downMethod = pkg.downloadMethod.getDownloadMethod()
@ -276,8 +217,14 @@ proc echoPackageVersions*(pkg: Package) =
try: try:
let versions = getTagsListRemote(pkg.url, downMethod).getVersionList() let versions = getTagsListRemote(pkg.url, downMethod).getVersionList()
if versions.len > 0: if versions.len > 0:
let sortedVersions = toSeq(values(versions)) var vstr = ""
echo(" versions: " & join(sortedVersions, ", ")) var i = 0
for v in values(versions):
if i != 0:
vstr.add(", ")
vstr.add(v)
i.inc
echo(" versions: " & vstr)
else: else:
echo(" versions: (No versions tagged in the remote repository)") echo(" versions: (No versions tagged in the remote repository)")
except OSError: except OSError:
@ -285,32 +232,3 @@ proc echoPackageVersions*(pkg: Package) =
of DownloadMethod.hg: of DownloadMethod.hg:
echo(" versions: (Remote tag retrieval not supported by " & echo(" versions: (Remote tag retrieval not supported by " &
pkg.downloadMethod & ")") pkg.downloadMethod & ")")
when isMainModule:
# Test version sorting
block:
let data = @["v9.0.0-taeyeon", "v9.0.1-jessica", "v9.2.0-sunny",
"v9.4.0-tiffany", "v9.4.2-hyoyeon"]
let expected = toOrderedTable[Version, string]({
newVersion("9.4.2-hyoyeon"): "v9.4.2-hyoyeon",
newVersion("9.4.0-tiffany"): "v9.4.0-tiffany",
newVersion("9.2.0-sunny"): "v9.2.0-sunny",
newVersion("9.0.1-jessica"): "v9.0.1-jessica",
newVersion("9.0.0-taeyeon"): "v9.0.0-taeyeon"
})
doAssert expected == getVersionList(data)
block:
let data2 = @["v0.1.0", "v0.1.1", "v0.2.0",
"0.4.0", "v0.4.2"]
let expected2 = toOrderedTable[Version, string]({
newVersion("0.4.2"): "v0.4.2",
newVersion("0.4.0"): "0.4.0",
newVersion("0.2.0"): "v0.2.0",
newVersion("0.1.1"): "v0.1.1",
newVersion("0.1.0"): "v0.1.0",
})
doAssert expected2 == getVersionList(data2)
echo("Everything works!")

View file

@ -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. ## 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 var
packageName* = "" ## Set this to the package name. It packageName* = "" ## Set this to the package name. It
## is usually not required to do that, nims' filename is ## is usually not required to do that, nims' filename is
@ -17,7 +11,7 @@ var
author*: string ## The package's author. author*: string ## The package's author.
description*: string ## The package's description. description*: string ## The package's description.
license*: string ## The package's license. license*: string ## The package's license.
srcDir*: string ## The package's source directory. srcdir*: string ## The package's source directory.
binDir*: string ## The package's binary directory. binDir*: string ## The package's binary directory.
backend*: string ## The package's backend. backend*: string ## The package's backend.
@ -28,169 +22,26 @@ var
foreignDeps*: seq[string] = @[] ## The foreign dependencies. Only foreignDeps*: seq[string] = @[] ## The foreign dependencies. Only
## exported for 'distros.nim'. ## 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]) = proc requires*(deps: varargs[string]) =
## Call this to set the list of requirements of your Nimble ## Call this to set the list of requirements of your Nimble
## package. ## package.
for d in deps: requiresData.add(d) 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
if commandLineParams.len == 0 or "help" in commandLineParams:
success = true
echo(astToStr(name), " ", description)
elif astToStr(name).normalize in commandLineParams:
success = true
`name Task`()
template before*(action: untyped, body: untyped): untyped = template before*(action: untyped, body: untyped): untyped =
## Defines a block of code which is evaluated before ``action`` is executed. ## Defines a block of code which is evaluated before ``action`` is executed.
proc `action Before`*(): bool = proc `action Before`*(): bool =
result = true result = true
body body
beforeHooks.add astToStr(action)
if (astToStr(action) & "Before").normalize in commandLineParams:
success = true
retVal = `action Before`()
template after*(action: untyped, body: untyped): untyped = template after*(action: untyped, body: untyped): untyped =
## Defines a block of code which is evaluated after ``action`` is executed. ## Defines a block of code which is evaluated after ``action`` is executed.
proc `action After`*(): bool = proc `action After`*(): bool =
result = true result = true
body body
afterHooks.add astToStr(action) template builtin = discard
if (astToStr(action) & "After").normalize in commandLineParams:
success = true
retVal = `action After`()
proc getPkgDir*(): string = proc getPkgDir*(): string =
## Returns the package directory containing the .nimble file currently ## Returns the package directory containing the .nimble file currently
## being evaluated. ## being evaluated.
result = projectFile.rsplit(seps={'/', '\\', ':'}, maxsplit=1)[0] builtin
getParams()

View file

@ -1,76 +0,0 @@
# Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info.
import os, strutils, sets
import packageparser, common, packageinfo, options, nimscriptwrapper, cli,
version
proc execHook*(options: Options, hookAction: ActionType, before: bool): bool =
## Returns whether to continue.
result = true
# For certain commands hooks should not be evaluated.
if hookAction in noHookActions:
return
var nimbleFile = ""
try:
nimbleFile = findNimbleFile(getCurrentDir(), true)
except NimbleError: return true
# 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]
let hookExists =
if before: actionName.normalize in pkgInfo.preHooks
else: actionName.normalize in pkgInfo.postHooks
if pkgInfo.isNimScript and hookExists:
let res = execHook(nimbleFile, actionName, before, options)
if res.success:
result = res.retVal
proc execCustom*(options: Options,
execResult: var ExecutionResult[bool],
failFast = true): bool =
## Executes the custom command using the nimscript backend.
##
## If failFast is true then exceptions will be raised when something is wrong.
## Otherwise this function will just return false.
# Custom command. Attempt to call a NimScript task.
let nimbleFile = findNimbleFile(getCurrentDir(), true)
if not nimbleFile.isNimScript(options) and failFast:
writeHelp()
execResult = execTask(nimbleFile, options.action.command, options)
if not execResult.success:
if not failFast:
return
raiseNimbleError(msg = "Could not find task $1 in $2" %
[options.action.command, nimbleFile],
hint = "Run `nimble --help` and/or `nimble tasks` for" &
" a list of possible commands.")
if execResult.command.normalize == "nop":
display("Warning:", "Using `setCommand 'nop'` is not necessary.", Warning,
HighPriority)
return
if not execHook(options, actionCustom, false):
return
return true
proc getOptionsForCommand*(execResult: ExecutionResult,
options: Options): Options =
## Creates an Options object for the requested command.
var newOptions = options.briefClone()
parseCommand(execResult.command, newOptions)
for arg in execResult.arguments:
parseArgument(arg, newOptions)
for flag, vals in execResult.flags:
for val in vals:
parseFlag(flag, val, newOptions)
return newOptions

View file

@ -0,0 +1,442 @@
# 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/lists, compiler/idents,
compiler/nimconf
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, times, osproc, sets
when not declared(resetAllModulesHard):
import compiler/modulegraphs
type
ExecutionResult*[T] = object
success*: bool
command*: string
arguments*: seq[string]
flags*: StringTableRef
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}
proc getGlobal(ident: PSym): string =
let n = vm.globalCtx.getGlobalValue(ident)
if n.isStrLit:
result = if n.strVal.isNil: "" else: n.strVal
else:
raiseVariableError(ident.name.s, "string")
proc getGlobalAsSeq(ident: PSym): seq[string] =
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(ident: PSym, result: var seq[PkgTuple]) =
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(module: PSym; scriptName: string,
flags: StringTableRef): 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(newIdentCache):
result = newCtx(module, identCache)
else:
result = newCtx(module)
result.mode = emRepl
registerAdditionalOps(result)
# captured vars:
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:
compiler_options.setConfigVar(getString(a, 0), getString(a, 1))
cbconf get:
setResult(a, compiler_options.getConfigVar(a.getString 0))
cbconf exists:
setResult(a, compiler_options.existsConfigVar(a.getString 0))
cbconf nimcacheDir:
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:
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:
setResult(a, compiler_options.command)
cbconf switch:
if not flags.isNil:
flags[a.getString 0] = a.getString 1
proc getNimPrefixDir(): string = splitPath(findExe("nim")).head.parentDir
when declared(ModuleGraph):
var graph: ModuleGraph
proc execScript(scriptName: string, flags: StringTableRef,
options: Options): PSym =
## Executes the specified script. Returns the script's module symbol.
##
## No clean up is performed and must be done manually!
when 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.
compiler_options.gPrefixDir = getNimPrefixDir()
let pkgName = scriptName.splitFile.name
# Ensure that "nimblepkg/nimscriptapi" is in the PATH.
# TODO: put this in a more isolated directory.
let tmpNimscriptApiPath = getTempDir() / "nimblepkg" / "nimscriptapi.nim"
createDir(tmpNimscriptApiPath.splitFile.dir)
if not existsFile(tmpNimscriptApiPath):
writeFile(tmpNimscriptApiPath, nimscriptApi)
appendStr(searchPaths, getTempDir())
initDefines()
loadConfigs(DefaultConfig)
passes.gIncludeFile = includeModule
passes.gImportModule = importModule
defineSymbol("nimscript")
defineSymbol("nimconfig")
defineSymbol("nimble")
registerPass(semPass)
registerPass(evalPass)
appendStr(searchPaths, compiler_options.libpath)
when declared(resetAllModulesHard):
result = makeModule(scriptName)
else:
graph = newModuleGraph()
result = graph.makeModule(scriptName)
incl(result.flags, sfMainModule)
vm.globalCtx = setupVM(result, scriptName, flags)
# Setup builtins defined in nimscriptapi.nim
template cbApi(name, body) {.dirty.} =
vm.globalCtx.registerCallback pkgName & "." & astToStr(name),
proc (a: VmArgs) =
body
cbApi getPkgDir:
setResult(a, scriptName.splitFile.dir)
when 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:
compiler_options.gProjectName = ""
compiler_options.command = ""
when declared(resetAllModulesHard):
resetAllModulesHard()
else:
resetSystemArtifacts()
clearPasses()
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.
msgs.gErrorMax = high(int)
var previousMsg = ""
msgs.writeLnHook =
proc (output: string) =
# The error counter is incremented after the writeLnHook is invoked.
if msgs.gErrorCounter > 0:
raise newException(NimbleError, previousMsg)
elif previousMsg.len > 0:
display("Info", previousMsg, priority = HighPriority)
if output.normalize.startsWith("error"):
raise newException(NimbleError, output)
previousMsg = output
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 msgs.gErrorCounter > 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(ident))
if result.isNil:
raise newException(NimbleError, "Ident not found: " & ident)
template trivialField(field) =
result.field = getGlobal(getSym(apiModule, astToStr field))
template trivialFieldSeq(field) =
result.field.add getGlobalAsSeq(getSym(apiModule, astToStr field))
# keep reasonable default:
let name = getGlobal(apiModule.tab.strTableGet(getIdent"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(getSym(apiModule, "requiresData"), result.requires)
let binSeq = getGlobalAsSeq(getSym(apiModule, "bin"))
for i in binSeq:
result.bin.add(i.addFileExt(ExeExt))
let backend = getGlobal(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()
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 = newStringTable()
compiler_options.command = internalCmd
display("Executing", "task $# in $#" % [taskName, scriptName],
priority = HighPriority)
let thisModule = execScript(scriptName, result.flags, options)
let prc = thisModule.tab.strTableGet(getIdent(taskName & "Task"))
if prc.isNil:
# Procedure not defined in the NimScript module.
result.success = false
return
discard vm.globalCtx.execProc(prc, [])
# Read the command, arguments and flags set by the executed task.
result.command = compiler_options.command
result.arguments = @[]
for arg in compiler_options.gProjectName.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.
result.success = true
result.flags = newStringTable()
compiler_options.command = 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(hookName))
if prc.isNil:
# Procedure not defined in the NimScript module.
result.success = false
cleanup()
return
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 = compiler_options.command
result.arguments = @[]
for arg in compiler_options.gProjectName.split():
result.arguments.add(arg)
cleanup()
proc getNimScriptCommand(): string =
compiler_options.command
proc setNimScriptCommand(command: string) =
compiler_options.command = 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: 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

@ -1,18 +1,15 @@
# 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 json, strutils, os, parseopt, strtabs, uri, tables, terminal import json, strutils, os, parseopt, strtabs, uri, tables
import sequtils, sugar
import std/options as std_opt
from httpclient import Proxy, newProxy from httpclient import Proxy, newProxy
import config, version, common, cli import config, version, tools, common, cli
type type
Options* = object Options* = object
forcePrompts*: ForcePrompt forcePrompts*: ForcePrompt
depsOnly*: bool depsOnly*: bool
uninstallRevDeps*: bool
queryVersions*: bool queryVersions*: bool
queryInstalled*: bool queryInstalled*: bool
nimbleDir*: string nimbleDir*: string
@ -21,50 +18,34 @@ type
config*: Config config*: Config
nimbleData*: JsonNode ## Nimbledata.json nimbleData*: JsonNode ## Nimbledata.json
pkgInfoCache*: TableRef[string, PackageInfo] pkgInfoCache*: TableRef[string, PackageInfo]
showHelp*: bool
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 ActionType* = enum
actionNil, actionRefresh, actionInit, actionDump, actionPublish, actionNil, actionRefresh, actionInit, actionDump, actionPublish,
actionInstall, actionSearch, actionInstall, actionSearch,
actionList, actionBuild, actionPath, actionUninstall, actionCompile, actionList, actionBuild, actionPath, actionUninstall, actionCompile,
actionDoc, actionCustom, actionTasks, actionDevelop, actionCheck, actionDoc, actionCustom, actionTasks
actionRun
Action* = object Action* = object
case typ*: ActionType case typ*: ActionType
of actionNil, actionList, actionPublish, actionTasks, actionCheck: nil of actionNil, actionList, actionBuild, actionPublish, actionTasks: nil
of actionRefresh: of actionRefresh:
optionalURL*: string # Overrides default package list. optionalURL*: string # Overrides default package list.
of actionInstall, actionPath, actionUninstall, actionDevelop: of actionInstall, actionPath, actionUninstall:
packages*: seq[PkgTuple] # Optional only for actionInstall packages*: seq[PkgTuple] # Optional only for actionInstall.
# and actionDevelop.
passNimFlags*: seq[string]
of actionSearch: of actionSearch:
search*: seq[string] # Search string. search*: seq[string] # Search string.
of actionInit, actionDump: of actionInit, actionDump:
projName*: string projName*: string
vcsOption*: string of actionCompile, actionDoc:
of actionCompile, actionDoc, actionBuild:
file*: string file*: string
backend*: string backend*: string
compileOptions: seq[string] compileOptions*: seq[string]
of actionRun:
runFile: Option[string]
compileFlags: seq[string]
runFlags*: seq[string]
of actionCustom: of actionCustom:
command*: string command*: string
arguments*: seq[string] arguments*: seq[string]
flags*: StringTableRef flags*: StringTableRef
const const
help* = """ help* = """
Usage: nimble COMMAND [opts] Usage: nimble COMMAND [opts]
@ -72,32 +53,14 @@ Usage: nimble COMMAND [opts]
Commands: Commands:
install [pkgname, ...] Installs a list of packages. install [pkgname, ...] Installs a list of packages.
[-d, --depsOnly] Install only dependencies. [-d, --depsOnly] Install only dependencies.
[-p, --passNim] Forward specified flag to compiler. init [pkgname] Initializes a new Nimble project.
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.
publish Publishes a package on nim-lang/packages. publish Publishes a package on nim-lang/packages.
The current working directory needs to be the The current working directory needs to be the
toplevel directory of the Nimble package. toplevel directory of the Nimble package.
uninstall [pkgname, ...] Uninstalls a list of packages. uninstall [pkgname, ...] Uninstalls a list of packages.
[-i, --inclDeps] Uninstall package and dependent package(s). build Builds a package.
build [opts, ...] [bin] Builds a package.
run [opts, ...] [bin] Builds and runs a package.
Binary needs to be specified after any
compilation options if there are several
binaries defined, any flags after the binary
or -- arg are passed to the binary when it is run.
c, cc, js [opts, ...] f.nim Builds a file inside a package. Passes options c, cc, js [opts, ...] f.nim Builds a file inside a package. Passes options
to the Nim compiler. to the Nim compiler.
test Compiles and executes tests
[-c, --continue] Don't stop execution on a failed test.
doc, doc2 [opts, ...] f.nim Builds documentation for a file inside a doc, doc2 [opts, ...] f.nim Builds documentation for a file inside a
package. Passes options to the Nim compiler. package. Passes options to the Nim compiler.
refresh [url] Refreshes the package list. A package list URL refresh [url] Refreshes the package list. A package list URL
@ -125,31 +88,22 @@ Options:
-n, --reject Reject all interactive prompts. -n, --reject Reject all interactive prompts.
--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.
--verbose Show all non-debug output. --verbose Show all non-debug output.
--debug Show all output including debug messages. --debug Show all output including debug messages.
--noColor Don't colorise output.
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
""" """
const noHookActions* = {actionCheck}
proc writeHelp*(quit=true) = proc writeHelp*(quit=true) =
echo(help) echo(help)
if quit: if quit:
raise NimbleQuit(msg: "") raise NimbleQuit(msg: "")
proc writeVersion*() = proc writeVersion() =
echo("nimble v$# compiled at $# $#" % echo("nimble v$# compiled at $# $#" %
[nimbleVersion, CompileDate, CompileTime]) [nimbleVersion, CompileDate, CompileTime])
const execResult = gorgeEx("git rev-parse HEAD")
when execResult[0].len > 0 and execResult[1] == QuitSuccess:
echo "git hash: ", execResult[0]
else:
{.warning: "Couldn't determine GIT hash: " & execResult[0].}
echo "git hash: couldn't determine git hash"
raise NimbleQuit(msg: "") raise NimbleQuit(msg: "")
proc parseActionType*(action: string): ActionType = proc parseActionType*(action: string): ActionType =
@ -160,8 +114,6 @@ proc parseActionType*(action: string): ActionType =
result = actionPath result = actionPath
of "build": of "build":
result = actionBuild result = actionBuild
of "run":
result = actionRun
of "c", "compile", "js", "cpp", "cc": of "c", "compile", "js", "cpp", "cc":
result = actionCompile result = actionCompile
of "doc", "doc2": of "doc", "doc2":
@ -182,10 +134,6 @@ proc parseActionType*(action: string): ActionType =
result = actionPublish result = actionPublish
of "tasks": of "tasks":
result = actionTasks result = actionTasks
of "develop":
result = actionDevelop
of "check":
result = actionCheck
else: else:
result = actionCustom result = actionCustom
@ -194,30 +142,28 @@ proc initAction*(options: var Options, key: string) =
## `key`. ## `key`.
let keyNorm = key.normalize() let keyNorm = key.normalize()
case options.action.typ case options.action.typ
of actionInstall, actionPath, actionDevelop, actionUninstall: of actionInstall, actionPath:
options.action.packages = @[] options.action.packages = @[]
options.action.passNimFlags = @[] of actionCompile, actionDoc:
of actionCompile, actionDoc, actionBuild:
options.action.compileOptions = @[] options.action.compileOptions = @[]
options.action.file = "" options.action.file = ""
if keyNorm == "c" or keyNorm == "compile": options.action.backend = "" if keyNorm == "c" or keyNorm == "compile": options.action.backend = ""
else: options.action.backend = keyNorm else: options.action.backend = keyNorm
of actionInit: of actionInit:
options.action.projName = "" options.action.projName = ""
options.action.vcsOption = ""
of actionDump: of actionDump:
options.action.projName = "" options.action.projName = ""
options.action.vcsOption = ""
options.forcePrompts = forcePromptYes
of actionRefresh: of actionRefresh:
options.action.optionalURL = "" options.action.optionalURL = ""
of actionSearch: of actionSearch:
options.action.search = @[] options.action.search = @[]
of actionUninstall:
options.action.packages = @[]
of actionCustom: of actionCustom:
options.action.command = key options.action.command = key
options.action.arguments = @[] options.action.arguments = @[]
options.action.flags = newStringTable() options.action.flags = newStringTable()
of actionPublish, actionList, actionTasks, actionCheck, actionRun, of actionBuild, actionPublish, actionList, actionTasks,
actionNil: discard actionNil: discard
proc prompt*(options: Options, question: string): bool = proc prompt*(options: Options, question: string): bool =
@ -227,32 +173,24 @@ proc prompt*(options: Options, question: string): bool =
## forcePrompts has a value different than dontForcePrompt. ## forcePrompts has a value different than dontForcePrompt.
return prompt(options.forcePrompts, question) return prompt(options.forcePrompts, question)
proc promptCustom*(options: Options, question, default: string): string = proc renameBabelToNimble(options: Options) {.deprecated.} =
## Asks an interactive question and returns the result. let babelDir = getHomeDir() / ".babel"
## let nimbleDir = getHomeDir() / ".nimble"
## The proc will return "default" without asking the user if the global if dirExists(babelDir):
## forcePrompts is forcePromptYes. if options.prompt("Found deprecated babel package directory, would you " &
return promptCustom(options.forcePrompts, question, default) "like to rename it to nimble?"):
copyDir(babelDir, nimbleDir)
copyFile(babelDir / "babeldata.json", nimbleDir / "nimbledata.json")
proc promptList*(options: Options, question: string, args: openarray[string]): string = removeDir(babelDir)
## Asks an interactive question and returns the result. removeFile(nimbleDir / "babeldata.json")
##
## The proc will return one of the provided args. If not prompting the first
## options is selected.
return promptList(options.forcePrompts, question, args)
proc getNimbleDir*(options: Options): string = proc getNimbleDir*(options: Options): string =
result = options.config.nimbleDir result =
if options.nimbleDir.len != 0: if options.nimbleDir.len == 0:
# --nimbleDir:<dir> takes priority... options.config.nimbleDir
result = options.nimbleDir else:
else: options.nimbleDir
# ...followed by the environment variable.
let env = getEnv("NIMBLE_DIR")
if env.len != 0:
display("Warning:", "Using the environment variable: NIMBLE_DIR='" &
env & "'", Warning)
result = env
return expandTilde(result) return expandTilde(result)
@ -263,27 +201,20 @@ proc getBinDir*(options: Options): string =
options.getNimbleDir() / "bin" options.getNimbleDir() / "bin"
proc parseCommand*(key: string, result: var Options) = proc parseCommand*(key: string, result: var Options) =
result.action = Action(typ: parseActionType(key)) result.action.typ = parseActionType(key)
initAction(result, key) initAction(result, key)
proc setRunOptions(result: var Options, key, val: string, isArg: bool) =
if result.action.runFile.isNone() and (isArg or val == "--"):
result.action.runFile = some(key)
else:
result.action.runFlags.add(val)
proc parseArgument*(key: string, result: var Options) = proc parseArgument*(key: string, result: var Options) =
case result.action.typ case result.action.typ
of actionNil: of actionNil:
assert false assert false
of actionInstall, actionPath, actionDevelop, actionUninstall: of actionInstall, actionPath, actionUninstall:
# Parse pkg@verRange # Parse pkg@verRange
if '@' in key: if '@' in key:
let i = find(key, '@') let i = find(key, '@')
let (pkgName, pkgVer) = (key[0 .. i-1], key[i+1 .. key.len-1]) let pkgTup = (key[0 .. i-1],
if pkgVer.len == 0: key[i+1 .. key.len-1].parseVersionRange())
raise newException(NimbleError, "Version range expected after '@'.") result.action.packages.add(pkgTup)
result.action.packages.add((pkgName, pkgVer.parseVersionRange()))
else: else:
result.action.packages.add((key, VersionRange(kind: verAny))) result.action.packages.add((key, VersionRange(kind: verAny)))
of actionRefresh: of actionRefresh:
@ -292,108 +223,66 @@ proc parseArgument*(key: string, result: var Options) =
result.action.search.add(key) result.action.search.add(key)
of actionInit, actionDump: of actionInit, actionDump:
if result.action.projName != "": if result.action.projName != "":
raise newException( raise newException(NimbleError,
NimbleError, "Can only perform this action on one package at a time." "Can only initialize one package at a time.")
)
result.action.projName = key result.action.projName = key
of actionCompile, actionDoc: of actionCompile, actionDoc:
result.action.file = key result.action.file = key
of actionList, actionPublish: of actionList, actionBuild, actionPublish:
result.showHelp = true writeHelp()
of actionBuild:
result.action.file = key
of actionRun:
result.setRunOptions(key, key, true)
of actionCustom: of actionCustom:
result.action.arguments.add(key) result.action.arguments.add(key)
else: else:
discard discard
proc getFlagString(kind: CmdLineKind, flag, val: string): string = proc parseFlag*(flag, val: string, result: var Options) =
let prefix = var wasFlagHandled = true
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) =
let f = flag.normalize()
# Global flags. # Global flags.
var isGlobalFlag = true case flag.normalize()
case f of "help", "h": writeHelp()
of "help", "h": result.showHelp = true of "version", "v": writeVersion()
of "version", "v": result.showVersion = true
of "accept", "y": result.forcePrompts = forcePromptYes of "accept", "y": result.forcePrompts = forcePromptYes
of "reject", "n": result.forcePrompts = forcePromptNo of "reject", "n": result.forcePrompts = forcePromptNo
of "nimbledir": result.nimbleDir = val of "nimbledir": result.nimbleDir = val
of "verbose": result.verbosity = LowPriority of "verbose": result.verbosity = LowPriority
of "debug": result.verbosity = DebugPriority of "debug": result.verbosity = DebugPriority
of "nocolor": result.noColor = true
of "disablevalidation": result.disableValidation = true
else: isGlobalFlag = false
var wasFlagHandled = true
# Action-specific flags. # Action-specific flags.
case result.action.typ of "installed", "i":
of actionSearch, actionList: if result.action.typ in [actionSearch, actionList]:
case f
of "installed", "i":
result.queryInstalled = true result.queryInstalled = true
of "ver": else:
wasFlagHandled = false
of "depsonly", "d":
if result.action.typ == actionInstall:
result.depsOnly = true
else:
wasFlagHandled = false
of "ver":
if result.action.typ in [actionSearch, actionList]:
result.queryVersions = true result.queryVersions = true
else: else:
wasFlagHandled = false wasFlagHandled = false
of actionInstall:
case f
of "depsonly", "d":
result.depsOnly = true
of "passnim", "p":
result.action.passNimFlags.add(val)
else:
wasFlagHandled = false
of actionInit:
case f
of "git", "hg":
result.action.vcsOption = f
else:
wasFlagHandled = false
of actionUninstall:
case f
of "incldeps", "i":
result.uninstallRevDeps = true
else:
wasFlagHandled = false
of actionCompile, actionDoc, actionBuild:
if not isGlobalFlag:
result.action.compileOptions.add(getFlagString(kind, flag, val))
of actionRun:
result.showHelp = false
result.setRunOptions(flag, getFlagString(kind, flag, val), false)
of actionCustom:
if result.action.command.normalize == "test":
if f == "continue" or f == "c":
result.continueTestsOnFailure = true
result.action.flags[flag] = val
else: else:
wasFlagHandled = false case result.action.typ
of actionCompile, actionDoc:
if val == "":
result.action.compileOptions.add("--" & flag)
else:
result.action.compileOptions.add("--" & flag & ":" & val)
of actionCustom:
result.action.flags[flag] = val
else:
wasFlagHandled = false
if not wasFlagHandled and not isGlobalFlag: if not wasFlagHandled:
result.unknownFlags.add((kind, flag, val)) raise newException(NimbleError, "Unknown option: --" & flag)
proc initOptions*(): Options = proc initOptions*(): Options =
# Exported for choosenim result.action.typ = actionNil
Options( result.pkgInfoCache = newTable[string, PackageInfo]()
action: Action(typ: actionNil), result.nimbleDir = ""
pkgInfoCache: newTable[string, PackageInfo](), result.verbosity = HighPriority
verbosity: HighPriority,
noColor: not isatty(stdout)
)
proc parseMisc(options: var Options) = proc parseMisc(options: var Options) =
# Load nimbledata.json # Load nimbledata.json
@ -408,29 +297,6 @@ proc parseMisc(options: var Options) =
else: else:
options.nimbleData = %{"reverseDeps": newJObject()} options.nimbleData = %{"reverseDeps": newJObject()}
proc handleUnknownFlags(options: var Options) =
if options.action.typ == actionRun:
# ActionRun uses flags that come before the command as compilation flags
# and flags that come after as run flags.
options.action.compileFlags =
map(options.unknownFlags, x => getFlagString(x[0], x[1], x[2]))
options.unknownFlags = @[]
else:
# For everything else, handle the flags that came before the command
# normally.
let unknownFlags = options.unknownFlags
options.unknownFlags = @[]
for flag in unknownFlags:
parseFlag(flag[1], flag[2], options, flag[0])
# Any unhandled flags?
if options.unknownFlags.len > 0:
let flag = options.unknownFlags[0]
raise newException(
NimbleError,
"Unknown option: " & getFlagString(flag[0], flag[1], flag[2])
)
proc parseCmdLine*(): Options = proc parseCmdLine*(): Options =
result = initOptions() result = initOptions()
@ -444,30 +310,20 @@ proc parseCmdLine*(): Options =
else: else:
parseArgument(key, result) parseArgument(key, result)
of cmdLongOption, cmdShortOption: of cmdLongOption, cmdShortOption:
parseFlag(key, val, result, kind) parseFlag(key, val, result)
of cmdEnd: assert(false) # cannot happen of cmdEnd: assert(false) # cannot happen
handleUnknownFlags(result)
# Set verbosity level. # Set verbosity level.
setVerbosity(result.verbosity) setVerbosity(result.verbosity)
# Set whether color should be shown.
setShowColor(not result.noColor)
# Parse config. # Parse config.
result.config = parseConfig() 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)
if result.action.typ == actionNil and not result.showVersion: if result.action.typ == actionNil:
result.showHelp = true writeHelp()
if result.action.typ != actionNil and result.showVersion:
# We've got another command that should be handled. For example:
# nimble run foobar -v
result.showVersion = false
proc getProxy*(options: Options): Proxy = proc getProxy*(options: Options): Proxy =
## Returns ``nil`` if no proxy is specified. ## Returns ``nil`` if no proxy is specified.
@ -480,10 +336,6 @@ proc getProxy*(options: Options): Proxy =
url = getEnv("http_proxy") url = getEnv("http_proxy")
elif existsEnv("https_proxy"): elif existsEnv("https_proxy"):
url = getEnv("https_proxy") url = getEnv("https_proxy")
elif existsEnv("HTTP_PROXY"):
url = getEnv("HTTP_PROXY")
elif existsEnv("HTTPS_PROXY"):
url = getEnv("HTTPS_PROXY")
except ValueError: except ValueError:
display("Warning:", "Unable to parse proxy from environment: " & display("Warning:", "Unable to parse proxy from environment: " &
getCurrentExceptionMsg(), Warning, HighPriority) getCurrentExceptionMsg(), Warning, HighPriority)
@ -498,54 +350,3 @@ proc getProxy*(options: Options): Proxy =
return newProxy($parsed, auth) return newProxy($parsed, auth)
else: else:
return nil return nil
proc briefClone*(options: Options): Options =
## Clones the few important fields and creates a new Options object.
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 # Stdlib imports
import system except TResult import system except TResult
import hashes, json, strutils, os, sets, tables, httpclient import parsecfg, json, streams, strutils, parseutils, os, sets, tables
import httpclient
# Local imports # Local imports
import version, tools, common, options, cli, config import version, tools, common, options, cli, config
@ -26,10 +27,6 @@ type
MetaData* = object MetaData* = object
url*: string url*: string
NimbleLink* = object
nimbleFilePath*: string
packageDir*: string
proc initPackageInfo*(path: string): PackageInfo = proc initPackageInfo*(path: string): PackageInfo =
result.myPath = path result.myPath = path
result.specialVersion = "" result.specialVersion = ""
@ -69,24 +66,21 @@ proc getNameVersion*(pkgpath: string): tuple[name, version: string] =
## ##
## Also works for file paths like: ## Also works for file paths like:
## ``/home/user/.nimble/pkgs/package-0.1/package.nimble`` ## ``/home/user/.nimble/pkgs/package-0.1/package.nimble``
if pkgPath.splitFile.ext in [".nimble", ".nimble-link", ".babel"]: if pkgPath.splitFile.ext == ".nimble" or pkgPath.splitFile.ext == ".babel":
return getNameVersion(pkgPath.splitPath.head) return getNameVersion(pkgPath.splitPath.head)
result.name = "" result.name = ""
result.version = "" result.version = ""
let tail = pkgpath.splitPath.tail let tail = pkgpath.splitPath.tail
if '-' notin tail:
const specialSeparator = "-#"
var sepIdx = tail.find(specialSeparator)
if sepIdx == -1:
sepIdx = tail.rfind('-')
if sepIdx == -1:
result.name = tail result.name = tail
return return
result.name = tail[0 .. sepIdx - 1] for i in countdown(tail.len-1, 0):
result.version = tail.substr(sepIdx + 1) if tail[i] == '-':
result.name = tail[0 .. i-1]
result.version = tail[i+1 .. tail.len-1]
break
proc optionalField(obj: JsonNode, name: string, default = ""): string = proc optionalField(obj: JsonNode, name: string, default = ""): string =
## Queries ``obj`` for the optional ``name`` string. ## Queries ``obj`` for the optional ``name`` string.
@ -106,8 +100,8 @@ proc requiredField(obj: JsonNode, name: string): string =
## Queries ``obj`` for the required ``name`` string. ## Queries ``obj`` for the required ``name`` string.
## ##
## Aborts execution if the field does not exist or is of invalid json type. ## Aborts execution if the field does not exist or is of invalid json type.
result = optionalField(obj, name) result = optionalField(obj, name, nil)
if result.len == 0: if result == nil:
raise newException(NimbleError, raise newException(NimbleError,
"Package in packages.json file does not contain a " & name & " field.") "Package in packages.json file does not contain a " & name & " field.")
@ -144,15 +138,6 @@ proc readMetaData*(path: string): MetaData =
let jsonmeta = parseJson(cont) let jsonmeta = parseJson(cont)
result.url = jsonmeta["url"].str result.url = jsonmeta["url"].str
proc readNimbleLink*(nimbleLinkPath: string): NimbleLink =
let s = readFile(nimbleLinkPath).splitLines()
result.nimbleFilePath = s[0]
result.packageDir = s[1]
proc writeNimbleLink*(nimbleLinkPath: string, contents: NimbleLink) =
let c = contents.nimbleFilePath & "\n" & contents.packageDir
writeFile(nimbleLinkPath, c)
proc needsRefresh*(options: Options): bool = proc needsRefresh*(options: Options): bool =
## Determines whether a ``nimble refresh`` is needed. ## Determines whether a ``nimble refresh`` is needed.
## ##
@ -175,73 +160,55 @@ proc validatePackagesList(path: string): bool =
except ValueError, JsonParsingError: except ValueError, JsonParsingError:
return false return false
proc fetchList*(list: PackageList, options: Options) = proc downloadList*(list: PackageList, options: Options) =
## Downloads or copies the specified package list and saves it in $nimbleDir. ## Downloads the specified package list and saves it in $nimbleDir.
let verb = if list.urls.len > 0: "Downloading" else: "Copying" display("Downloading", list.name & " package list", priority = HighPriority)
display(verb, list.name & " package list", priority = HighPriority)
var var lastError = ""
lastError = "" for i in 0 .. <list.urls.len:
copyFromPath = "" let url = list.urls[i]
if list.urls.len > 0: display("Trying", url)
for i in 0 ..< list.urls.len: let tempPath = options.getNimbleDir() / "packages_temp.json"
let url = list.urls[i]
display("Trying", url)
let tempPath = options.getNimbleDir() / "packages_temp.json"
# Grab the proxy # Grab the proxy
let proxy = getProxy(options) let proxy = getProxy(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 = "***"
display("Connecting", "to proxy at " & $maskedUrl, display("Connecting", "to proxy at " & $maskedUrl,
priority = LowPriority) priority = LowPriority)
try: try:
let client = newHttpClient(proxy = proxy) downloadFile(url, tempPath, proxy = getProxy(options))
client.downloadFile(url, tempPath) except:
except: let message = "Could not download: " & getCurrentExceptionMsg()
let message = "Could not download: " & getCurrentExceptionMsg() display("Warning:", message, Warning)
display("Warning:", message, Warning) lastError = message
lastError = message continue
continue
if not validatePackagesList(tempPath): if not validatePackagesList(tempPath):
lastError = "Downloaded packages.json file is invalid" lastError = "Downloaded packages.json file is invalid"
display("Warning:", lastError & ", discarding.", Warning)
continue
copyFromPath = tempPath
display("Success", "Package list downloaded.", Success, HighPriority)
lastError = ""
break
elif list.path != "":
if not validatePackagesList(list.path):
lastError = "Copied packages.json file is invalid"
display("Warning:", lastError & ", discarding.", Warning) display("Warning:", lastError & ", discarding.", Warning)
else: continue
copyFromPath = list.path
display("Success", "Package list copied.", Success, HighPriority) copyFile(tempPath,
options.getNimbleDir() / "packages_$1.json" % list.name.toLowerAscii())
display("Success", "Package list downloaded.", Success, HighPriority)
lastError = ""
break
if lastError.len != 0: if lastError.len != 0:
raise newException(NimbleError, "Refresh failed\n" & lastError) raise newException(NimbleError, "Refresh failed\n" & lastError)
if copyFromPath.len > 0:
copyFile(copyFromPath,
options.getNimbleDir() / "packages_$1.json" % list.name.toLowerAscii())
proc readPackageList(name: string, options: Options): JsonNode = proc readPackageList(name: string, options: Options): JsonNode =
# If packages.json is not present ask the user if they want to download it. # If packages.json is not present ask the user if they want to download it.
if needsRefresh(options): if needsRefresh(options):
if options.prompt("No local packages.json found, download it from " & if options.prompt("No local packages.json found, download it from " &
"internet?"): "internet?"):
for name, list in options.config.packageLists: for name, list in options.config.packageLists:
fetchList(list, options) downloadList(list, options)
else: else:
# The user might not need a package list for now. So let's try raise newException(NimbleError, "Please run nimble refresh.")
# going further.
return newJArray()
return parseFile(options.getNimbleDir() / "packages_" & return parseFile(options.getNimbleDir() / "packages_" &
name.toLowerAscii() & ".json") name.toLowerAscii() & ".json")
@ -277,7 +244,7 @@ proc getPackage*(pkg: string, options: Options, resPkg: var Package): bool =
proc getPackageList*(options: Options): seq[Package] = proc getPackageList*(options: Options): seq[Package] =
## Returns the list of packages found in the downloaded packages.json files. ## Returns the list of packages found in the downloaded packages.json files.
result = @[] result = @[]
var namesAdded = initHashSet[string]() var namesAdded = initSet[string]()
for name, list in options.config.packageLists: for name, list in options.config.packageLists:
let packages = readPackageList(name, options) let packages = readPackageList(name, options)
for p in packages: for p in packages:
@ -290,10 +257,10 @@ proc findNimbleFile*(dir: string; error: bool): string =
result = "" result = ""
var hits = 0 var hits = 0
for kind, path in walkDir(dir): for kind, path in walkDir(dir):
if kind in {pcFile, pcLinkToFile}: if kind == pcFile:
let ext = path.splitFile.ext let ext = path.splitFile.ext
case ext case ext
of ".babel", ".nimble", ".nimble-link": of ".babel", ".nimble":
result = path result = path
inc hits inc hits
else: discard else: discard
@ -303,52 +270,31 @@ proc findNimbleFile*(dir: string; error: bool): string =
elif hits == 0: elif hits == 0:
if error: if error:
raise newException(NimbleError, raise newException(NimbleError,
"Specified directory ($1) does not contain a .nimble file." % dir) "Specified directory does not contain a .nimble file.")
else: else:
display("Warning:", "No .nimble or .nimble-link file found for " & display("Warning:", "No .nimble file found for " & dir, Warning,
dir, Warning, HighPriority) HighPriority)
if result.splitFile.ext == ".nimble-link":
# 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)
proc getInstalledPkgsMin*(libsDir: string, options: Options): proc getInstalledPkgsMin*(libsDir: string, options: Options):
seq[tuple[pkginfo: PackageInfo, meta: MetaData]] = seq[tuple[pkginfo: PackageInfo, meta: MetaData]] =
## Gets a list of installed packages. The resulting package info is ## Gets a list of installed packages. The resulting package info is
## minimal. This has the advantage that it does not depend on the ## 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) ## ``libsDir`` is in most cases: ~/.nimble/pkgs/
result = @[] result = @[]
for kind, path in walkDir(libsDir): for kind, path in walkDir(libsDir):
if kind == pcDir: if kind == pcDir:
let nimbleFile = findNimbleFile(path, false) let nimbleFile = findNimbleFile(path, false)
if nimbleFile != "": if nimbleFile != "":
let meta = readMetaData(path) let meta = readMetaData(path)
let (name, version) = getNameVersion(path) let (name, version) = getNameVersion(nimbleFile)
var pkg = initPackageInfo(nimbleFile) var pkg = initPackageInfo(nimbleFile)
pkg.name = name pkg.name = name
pkg.version = version pkg.version = version
pkg.specialVersion = version pkg.specialVersion = version
pkg.isMinimal = true pkg.isMinimal = true
pkg.isInstalled = 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)
result.add((pkg, meta)) result.add((pkg, meta))
proc withinRange*(pkgInfo: PackageInfo, verRange: VersionRange): bool = proc withinRange*(pkgInfo: PackageInfo, verRange: VersionRange): bool =
@ -382,7 +328,8 @@ proc findPkg*(pkglist: seq[tuple[pkgInfo: PackageInfo, meta: MetaData]],
if cmpIgnoreStyle(pkg.pkginfo.name, dep.name) != 0 and if cmpIgnoreStyle(pkg.pkginfo.name, dep.name) != 0 and
cmpIgnoreStyle(pkg.meta.url, dep.name) != 0: continue cmpIgnoreStyle(pkg.meta.url, dep.name) != 0: continue
if withinRange(pkg.pkgInfo, dep.ver): if withinRange(pkg.pkgInfo, dep.ver):
let isNewer = newVersion(r.version) < newVersion(pkg.pkginfo.version) let isNewer = (not r.version.isNil) and
newVersion(r.version) < newVersion(pkg.pkginfo.version)
if not result or isNewer: if not result or isNewer:
r = pkg.pkginfo r = pkg.pkginfo
result = true result = true
@ -401,7 +348,7 @@ proc findAllPkgs*(pkglist: seq[tuple[pkgInfo: PackageInfo, meta: MetaData]],
proc getRealDir*(pkgInfo: PackageInfo): string = proc getRealDir*(pkgInfo: PackageInfo): string =
## Returns the directory containing the package source files. ## Returns the directory containing the package source files.
if pkgInfo.srcDir != "" and (not pkgInfo.isInstalled or pkgInfo.isLinked): if pkgInfo.srcDir != "" and not pkgInfo.isInstalled:
result = pkgInfo.mypath.splitFile.dir / pkgInfo.srcDir result = pkgInfo.mypath.splitFile.dir / pkgInfo.srcDir
else: else:
result = pkgInfo.mypath.splitFile.dir result = pkgInfo.mypath.splitFile.dir
@ -465,30 +412,30 @@ proc checkInstallDir(pkgInfo: PackageInfo,
if thisDir[0] == '.': result = true if thisDir[0] == '.': result = true
if thisDir == "nimcache": result = true if thisDir == "nimcache": result = true
proc iterFilesWithExt(dir: string, pkgInfo: PackageInfo, proc findWithExt(dir: string, pkgInfo: PackageInfo): seq[string] =
action: proc (f: string)) = ## Returns the filenames of the files that should be copied.
## Runs `action` for each filename of the files that have a whitelisted result = @[]
## file extension.
for kind, path in walkDir(dir): for kind, path in walkDir(dir):
if kind == pcDir: if kind == pcDir:
iterFilesWithExt(path, pkgInfo, action) result.add findWithExt(path, pkgInfo)
else: else:
if path.splitFile.ext.substr(1) in pkgInfo.installExt: if path.splitFile.ext[1 .. ^1] in pkgInfo.installExt:
action(path) result.add path
proc iterFilesInDir(dir: string, action: proc (f: string)) = proc getFilesInDir(dir: string): seq[string] =
## Runs `action` for each file in ``dir`` and any ## Returns a list of paths to files inside the specified directory and any
## subdirectories that are in it. ## subdirectories that are in it.
result = @[]
for kind, path in walkDir(dir): for kind, path in walkDir(dir):
if kind == pcDir: if kind == pcDir:
iterFilesInDir(path, action) result.add getFilesInDir(path)
else: else:
action(path) result.add path
proc iterInstallFiles*(realDir: string, pkgInfo: PackageInfo, proc getInstallFiles*(realDir: string, pkgInfo: PackageInfo,
options: Options, action: proc (f: string)) = options: Options): seq[string] =
## Runs `action` for each file within the ``realDir`` that should be ## Returns a list of files within the ``realDir`` that should be installed.
## installed. result = @[]
let whitelistMode = let whitelistMode =
pkgInfo.installDirs.len != 0 or pkgInfo.installDirs.len != 0 or
pkgInfo.installFiles.len != 0 or pkgInfo.installFiles.len != 0 or
@ -501,8 +448,7 @@ proc iterInstallFiles*(realDir: string, pkgInfo: PackageInfo,
continue continue
else: else:
raise NimbleQuit(msg: "") raise NimbleQuit(msg: "")
result.add src
action(src)
for dir in pkgInfo.installDirs: for dir in pkgInfo.installDirs:
# TODO: Allow skipping files inside dirs? # TODO: Allow skipping files inside dirs?
@ -513,37 +459,23 @@ proc iterInstallFiles*(realDir: string, pkgInfo: PackageInfo,
else: else:
raise NimbleQuit(msg: "") raise NimbleQuit(msg: "")
iterFilesInDir(src, action) result.add getFilesInDir(src)
iterFilesWithExt(realDir, pkgInfo, action) result.add findWithExt(realDir, pkgInfo)
else: else:
for kind, file in walkDir(realDir): for kind, file in walkDir(realDir):
if kind == pcDir: if kind == pcDir:
let skip = pkgInfo.checkInstallDir(realDir, file) let skip = pkgInfo.checkInstallDir(realDir, file)
if skip: continue
# we also have to stop recursing if we reach an in-place nimbleDir
if file == options.getNimbleDir().expandFilename(): continue
iterInstallFiles(file, pkgInfo, options, action) if skip: continue
result.add getInstallFiles(file, pkgInfo, options)
else: else:
let skip = pkgInfo.checkInstallFile(realDir, file) let skip = pkgInfo.checkInstallFile(realDir, file)
if skip: continue if skip: continue
action(file) result.add file
proc getPkgDest*(pkgInfo: PackageInfo, options: Options): string =
let versionStr = '-' & pkgInfo.specialVersion
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: when isMainModule:
doAssert getNameVersion("/home/user/.nimble/libs/packagea-0.1") == doAssert getNameVersion("/home/user/.nimble/libs/packagea-0.1") ==
@ -554,10 +486,6 @@ when isMainModule:
("package-a", "0.1") ("package-a", "0.1")
doAssert getNameVersion("/home/user/.nimble/libs/package-#head") == doAssert getNameVersion("/home/user/.nimble/libs/package-#head") ==
("package", "#head") ("package", "#head")
doAssert getNameVersion("/home/user/.nimble/libs/package-#branch-with-dashes") ==
("package", "#branch-with-dashes")
# readPackageInfo (and possibly more) depends on this not raising.
doAssert getNameVersion("/home/user/.nimble/libs/package") == ("package", "")
doAssert toValidPackageName("foo__bar") == "foo_bar" doAssert toValidPackageName("foo__bar") == "foo_bar"
doAssert toValidPackageName("jhbasdh!£$@%#^_&*_()qwe") == "jhbasdh_qwe" doAssert toValidPackageName("jhbasdh!£$@%#^_&*_()qwe") == "jhbasdh_qwe"

View file

@ -1,112 +0,0 @@
# Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info.
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
when defined(windows):
# This is just for Win XP support.
# TODO: Drop XP support?
from winlean import WINBOOL, DWORD
type
OSVERSIONINFO* {.final, pure.} = object
dwOSVersionInfoSize*: DWORD
dwMajorVersion*: DWORD
dwMinorVersion*: DWORD
dwBuildNumber*: DWORD
dwPlatformId*: DWORD
szCSDVersion*: array[0..127, char]
proc GetVersionExA*(VersionInformation: var OSVERSIONINFO): WINBOOL{.stdcall,
dynlib: "kernel32", importc: "GetVersionExA".}
proc setupBinSymlink*(symlinkDest, symlinkFilename: string,
options: Options): seq[string] =
result = @[]
let currentPerms = getFilePermissions(symlinkDest)
setFilePermissions(symlinkDest, currentPerms + {fpUserExec})
when defined(unix):
display("Creating", "symlink: $1 -> $2" %
[symlinkDest, symlinkFilename], priority = MediumPriority)
if existsFile(symlinkFilename):
let msg = "Symlink already exists in $1. Replacing." % symlinkFilename
display("Warning:", msg, Warning, HighPriority)
removeFile(symlinkFilename)
createSymlink(symlinkDest, symlinkFilename)
result.add symlinkFilename.extractFilename
elif defined(windows):
# There is a bug on XP, described here:
# http://stackoverflow.com/questions/2182568/batch-script-is-not-executed-if-chcp-was-called
# But this workaround brakes code page on newer systems, so we need to detect OS version
var osver = OSVERSIONINFO()
osver.dwOSVersionInfoSize = cast[DWORD](sizeof(OSVERSIONINFO))
if GetVersionExA(osver) == WINBOOL(0):
raise newException(NimbleError,
"Can't detect OS version: GetVersionExA call failed")
let fixChcp = osver.dwMajorVersion <= 5
# Create cmd.exe/powershell stub.
let dest = symlinkFilename.changeFileExt("cmd")
display("Creating", "stub: $1 -> $2" % [symlinkDest, dest],
priority = MediumPriority)
var contents = "@"
if options.config.chcp:
if fixChcp:
contents.add "chcp 65001 > nul && "
else: contents.add "chcp 65001 > nul\n@"
contents.add "\"" & symlinkDest & "\" %*\n"
writeFile(dest, contents)
result.add dest.extractFilename
# For bash on Windows (Cygwin/Git bash).
let bashDest = dest.changeFileExt("")
display("Creating", "Cygwin stub: $1 -> $2" %
[symlinkDest, bashDest], priority = MediumPriority)
writeFile(bashDest, "\"" & symlinkDest & "\" \"$@\"\n")
result.add bashDest.extractFilename
else:
{.error: "Sorry, your platform is not supported.".}
proc saveNimbleMeta*(pkgDestDir, url, vcsRevision: string,
filesInstalled, bins: HashSet[string],
isLink: bool = false) =
## Saves the specified data into a ``nimblemeta.json`` file inside
## ``pkgDestDir``.
##
## filesInstalled - A list of absolute paths to files which have been
## installed.
## bins - A list of binary filenames which have been installed for this
## package.
##
## isLink - Determines whether the installed package is a .nimble-link.
var nimblemeta = %{"url": %url}
if vcsRevision.len > 0:
nimblemeta["vcsRevision"] = %vcsRevision
let files = newJArray()
nimblemeta["files"] = files
for file in filesInstalled:
files.add(%changeRoot(pkgDestDir, "", file))
let binaries = newJArray()
nimblemeta["binaries"] = binaries
for bin in bins:
binaries.add(%bin)
nimblemeta["isLink"] = %isLink
writeFile(pkgDestDir / "nimblemeta.json", $nimblemeta)
proc saveNimbleMeta*(pkgDestDir, pkgDir, vcsRevision, nimbleLinkPath: string) =
## Overload of saveNimbleMeta for linked (.nimble-link) packages.
##
## pkgDestDir - The directory where the package has been installed.
## For example: ~/.nimble/pkgs/jester-#head/
##
## pkgDir - The directory where the original package files are.
## For example: ~/projects/jester/
saveNimbleMeta(pkgDestDir, "file://" & pkgDir, vcsRevision,
toHashSet[string]([nimbleLinkPath]), initHashSet[string](), true)

View file

@ -1,14 +1,14 @@
# Copyright (C) Dominik Picheta. All rights reserved. # Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info. # BSD License. Look at license.txt for more info.
import parsecfg, sets, streams, strutils, os, tables, sugar import parsecfg, json, streams, strutils, parseutils, os, tables
from sequtils import apply, map import version, tools, common, nimscriptsupport, options, packageinfo, cli
import version, tools, common, nimscriptwrapper, options, packageinfo, cli
## Contains procedures for parsing .nimble files. Moved here from ``packageinfo`` ## 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``. ## depends on other procedures in ``packageinfo``.
from sequtils import apply
type type
NimbleFile* = string NimbleFile* = string
@ -16,31 +16,6 @@ type
warnInstalled*: bool # Determines whether to show a warning for installed pkgs warnInstalled*: bool # Determines whether to show a warning for installed pkgs
warnAll*: bool warnAll*: bool
const reservedNames = [
"CON",
"PRN",
"AUX",
"NUL",
"COM1",
"COM2",
"COM3",
"COM4",
"COM5",
"COM6",
"COM7",
"COM8",
"COM9",
"LPT1",
"LPT2",
"LPT3",
"LPT4",
"LPT5",
"LPT6",
"LPT7",
"LPT8",
"LPT9",
]
proc newValidationError(msg: string, warnInstalled: bool, proc newValidationError(msg: string, warnInstalled: bool,
hint: string, warnAll: bool): ref ValidationError = hint: string, warnAll: bool): ref ValidationError =
result = newException(ValidationError, msg) result = newException(ValidationError, msg)
@ -82,9 +57,6 @@ proc validatePackageName*(name: string) =
if name.endsWith("pkg"): if name.endsWith("pkg"):
raiseNewValidationError("\"$1\" is an invalid package name: cannot end" & raiseNewValidationError("\"$1\" is an invalid package name: cannot end" &
" with \"pkg\"" % name, false) " with \"pkg\"" % name, false)
if name.toUpperAscii() in reservedNames:
raiseNewValidationError(
"\"$1\" is an invalid package name: reserved name" % name, false)
proc validateVersion*(ver: string) = proc validateVersion*(ver: string) =
for c in ver: for c in ver:
@ -97,18 +69,8 @@ proc validatePackageStructure(pkgInfo: PackageInfo, options: Options) =
## This ensures that a package's source code does not leak into ## This ensures that a package's source code does not leak into
## another package's namespace. ## another package's namespace.
## https://github.com/nim-lang/nimble/issues/144 ## https://github.com/nim-lang/nimble/issues/144
let let realDir = pkgInfo.getRealDir()
realDir = pkgInfo.getRealDir() for path in getInstallFiles(realDir, pkgInfo, options):
normalizedBinNames = pkgInfo.bin.map(
(x) => x.changeFileExt("").toLowerAscii()
)
correctDir =
if pkgInfo.name.toLowerAscii() in normalizedBinNames:
pkgInfo.name & "pkg"
else:
pkgInfo.name
proc onFile(path: string) =
# Remove the root to leave only the package subdirectories. # Remove the root to leave only the package subdirectories.
# ~/package-0.1/package/utils.nim -> package/utils.nim. # ~/package-0.1/package/utils.nim -> package/utils.nim.
var trailPath = changeRoot(realDir, "", path) var trailPath = changeRoot(realDir, "", path)
@ -117,47 +79,40 @@ proc validatePackageStructure(pkgInfo: PackageInfo, options: Options) =
# We're only interested in nim files, because only they can pollute our # We're only interested in nim files, because only they can pollute our
# namespace. # namespace.
if ext != (ExtSep & "nim"): if ext != (ExtSep & "nim"):
return continue
if dir.len == 0: if dir.len == 0:
if file != pkgInfo.name: if file != pkgInfo.name:
# A source file was found in the top level of srcDir that doesn't share let msg = ("File inside package '$1' is outside of permitted " &
# a name with the package. "namespace, should be " &
let "named '$2' but was named '$3' instead. This will be an error" &
msg = ("Package '$1' has an incorrect structure. " & " in the future.") %
"The top level of the package source directory " & [pkgInfo.name, pkgInfo.name & ext, file & ext]
"should contain at most one module, " & let hint = ("Rename this file to '$1', move it into a '$2' " &
"named '$2', but a file named '$3' was found. This " & "subdirectory, or prevent its installation by adding " &
"will be an error in the future.") % "`skipFiles = @[\"$3\"]` to the .nimble file. See " &
[pkgInfo.name, pkgInfo.name & ext, file & ext] "https://github.com/nim-lang/nimble#libraries for more info.") %
hint = ("If this is the primary source file in the package, " & [pkgInfo.name & ext, pkgInfo.name & DirSep, file & ext]
"rename it to '$1'. If it's a source file required by " &
"the main module, or if it is one of several " &
"modules exposed by '$4', then move it into a '$2' subdirectory. " &
"If it's a test file or otherwise not required " &
"to build the the package '$1', prevent its installation " &
"by adding `skipFiles = @[\"$3\"]` to the .nimble file. See " &
"https://github.com/nim-lang/nimble#libraries for more info.") %
[pkgInfo.name & ext, correctDir & DirSep, file & ext, pkgInfo.name]
raiseNewValidationError(msg, true, hint, true) raiseNewValidationError(msg, true, hint, true)
else: else:
assert(not pkgInfo.isMinimal) assert(not pkgInfo.isMinimal)
# On Windows `pkgInfo.bin` has a .exe extension, so we need to normalize. let correctDir =
if not (dir.startsWith(correctDir & DirSep) or dir == correctDir): if pkgInfo.name in pkgInfo.bin:
let pkgInfo.name & "pkg"
msg = ("Package '$2' has an incorrect structure. " & else:
"It should contain a single directory hierarchy " & pkgInfo.name
"for source files, named '$3', but file '$1' " &
"is in a directory named '$4' instead. " &
"This will be an error in the future.") %
[file & ext, pkgInfo.name, correctDir, dir]
hint = ("If '$1' contains source files for building '$2', rename it " &
"to '$3'. Otherwise, prevent its installation " &
"by adding `skipDirs = @[\"$1\"]` to the .nimble file.") %
[dir, pkgInfo.name, correctDir]
raiseNewValidationError(msg, true, hint, true)
iterInstallFiles(realDir, pkgInfo, options, onFile) if not (dir.startsWith(correctDir & DirSep) or dir == correctDir):
let msg = ("File '$1' inside package '$2' is outside of the" &
" permitted namespace" &
", should be inside a directory named '$3' but is in a" &
" directory named '$4' instead. This will be an error in the " &
"future.") %
[file & ext, pkgInfo.name, correctDir, dir]
let hint = ("Rename the directory to '$1' or prevent its " &
"installation by adding `skipDirs = @[\"$2\"]` to the " &
".nimble file.") % [correctDir, dir]
raiseNewValidationError(msg, true, hint, true)
proc validatePackageInfo(pkgInfo: PackageInfo, options: Options) = proc validatePackageInfo(pkgInfo: PackageInfo, options: Options) =
let path = pkgInfo.myPath let path = pkgInfo.myPath
@ -212,10 +167,7 @@ proc multiSplit(s: string): seq[string] =
result.del(i) result.del(i)
# Huh, nothing to return? Return given input. # Huh, nothing to return? Return given input.
if len(result) < 1: if len(result) < 1:
if s.strip().len != 0: return @[s]
return @[s]
else:
return @[]
proc readPackageInfoFromNimble(path: string; result: var PackageInfo) = proc readPackageInfoFromNimble(path: string; result: var PackageInfo) =
var fs = newFileStream(path, fmRead) var fs = newFileStream(path, fmRead)
@ -256,20 +208,12 @@ proc readPackageInfoFromNimble(path: string; result: var PackageInfo) =
result.installExt.add(ev.value.multiSplit) result.installExt.add(ev.value.multiSplit)
of "bin": of "bin":
for i in ev.value.multiSplit: 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)) result.bin.add(i.addFileExt(ExeExt))
of "backend": of "backend":
result.backend = ev.value.toLowerAscii() result.backend = ev.value.toLowerAscii()
case result.backend.normalize case result.backend.normalize
of "javascript": result.backend = "js" of "javascript": result.backend = "js"
else: discard 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: else:
raise newException(NimbleError, "Invalid field: " & ev.key) raise newException(NimbleError, "Invalid field: " & ev.key)
of "deps", "dependencies": of "deps", "dependencies":
@ -288,33 +232,6 @@ proc readPackageInfoFromNimble(path: string; result: var PackageInfo) =
else: else:
raise newException(ValueError, "Cannot open package info: " & path) 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, proc readPackageInfo(nf: NimbleFile, options: Options,
onlyMinimalInfo=false): PackageInfo = onlyMinimalInfo=false): PackageInfo =
## Reads package info from the specified Nimble file. ## Reads package info from the specified Nimble file.
@ -358,23 +275,16 @@ proc readPackageInfo(nf: NimbleFile, options: Options,
result.version = minimalInfo.version result.version = minimalInfo.version
result.isNimScript = true result.isNimScript = true
result.isMinimal = true result.isMinimal = true
# It's possible this proc will receive a .nimble-link file eventually,
# I added this assert to hopefully make this error clear for everyone.
let msg = "No version detected. Received nimble-link?"
assert result.version.len > 0, msg
else: else:
try: try:
readPackageInfoFromNims(nf, options, result) readPackageInfoFromNims(nf, options, result)
result.isNimScript = true result.isNimScript = true
except NimbleError as exc: except NimbleError:
if exc.hint.len > 0:
raise
let msg = "Could not read package info file in " & nf & ";\n" & let msg = "Could not read package info file in " & nf & ";\n" &
" Reading as ini file failed with: \n" & " Reading as ini file failed with: \n" &
" " & iniError.msg & ".\n" & " " & iniError.msg & ".\n" &
" Evaluating as NimScript file failed with: \n" & " Evaluating as NimScript file failed with: \n" &
" " & exc.msg & "." " " & getCurrentExceptionMsg() & "."
raise newException(NimbleError, msg) raise newException(NimbleError, msg)
# By default specialVersion is the same as version. # By default specialVersion is the same as version.
@ -389,26 +299,12 @@ proc readPackageInfo(nf: NimbleFile, options: Options,
if version.kind == verSpecial: if version.kind == verSpecial:
result.specialVersion = minimalInfo.version result.specialVersion = minimalInfo.version
# Apply rules to infer which files should/shouldn't be installed. See #469.
inferInstallRules(result, options)
if not result.isMinimal: if not result.isMinimal:
options.pkgInfoCache[nf] = result options.pkgInfoCache[nf] = result
# Validate the rest of the package info last. # Validate the rest of the package info last.
if not options.disableValidation: validateVersion(result.version)
validateVersion(result.version) validatePackageInfo(result, options)
validatePackageInfo(result, options)
proc validate*(file: NimbleFile, options: Options,
error: var ValidationError, pkgInfo: var PackageInfo): bool =
try:
pkgInfo = readPackageInfo(file, options)
except ValidationError as exc:
error = exc[]
return false
return true
proc getPkgInfoFromFile*(file: NimbleFile, options: Options): PackageInfo = proc getPkgInfoFromFile*(file: NimbleFile, options: Options): PackageInfo =
## Reads the specified .nimble file and returns its data as a PackageInfo ## Reads the specified .nimble file and returns its data as a PackageInfo
@ -472,8 +368,6 @@ proc getInstalledPkgs*(libsDir: string, options: Options):
raise exc raise exc
pkg.isInstalled = true pkg.isInstalled = true
pkg.isLinked =
cmpPaths(nimbleFile.splitFile().dir, path) != 0
result.add((pkg, meta)) result.add((pkg, meta))
proc isNimScript*(nf: string, options: Options): bool = proc isNimScript*(nf: string, options: Options): bool =
@ -481,21 +375,10 @@ proc isNimScript*(nf: string, options: Options): bool =
proc toFullInfo*(pkg: PackageInfo, options: Options): PackageInfo = proc toFullInfo*(pkg: PackageInfo, options: Options): PackageInfo =
if pkg.isMinimal: if pkg.isMinimal:
result = getPkgInfoFromFile(pkg.mypath, options) return getPkgInfoFromFile(pkg.mypath, options)
result.isInstalled = pkg.isInstalled
result.isLinked = pkg.isLinked
else: else:
return pkg return pkg
proc getConcreteVersion*(pkgInfo: PackageInfo, options: Options): string =
## Returns a non-special version from the specified ``pkgInfo``. If the
## ``pkgInfo`` is minimal it looks it up and retrieves the concrete version.
result = pkgInfo.version
if pkgInfo.isMinimal:
let pkgInfo = pkgInfo.toFullInfo(options)
result = pkgInfo.version
assert(not newVersion(result).isSpecial)
when isMainModule: when isMainModule:
validatePackageName("foo_bar") validatePackageName("foo_bar")
validatePackageName("f_oo_b_a_r") validatePackageName("f_oo_b_a_r")

View file

@ -5,34 +5,27 @@
## nim-lang/packages automatically. ## nim-lang/packages automatically.
import system except TResult import system except TResult
import httpclient, strutils, json, os, browsers, times, uri import httpclient, base64, strutils, rdstdin, json, os, browsers, times, uri
import version, tools, common, cli, config, options import tools, common, cli
type type
Auth = object Auth = object
user: string user: string
token: string ## Github access token pw: string
http: HttpClient ## http client for doing API requests token: string ## base64 encoding of user:pw
const
ApiKeyFile = "github_api_token"
ApiTokenEnvironmentVariable = "NIMBLE_GITHUB_API_TOKEN"
ReposUrl = "https://api.github.com/repos/"
proc userAborted() = proc userAborted() =
raise newException(NimbleError, "User aborted the process.") raise newException(NimbleError, "User aborted the process.")
proc createHeaders(a: Auth) = proc createHeaders(a: Auth): string =
a.http.headers = newHttpHeaders({ (("Authorization: token $1\c\L" % a.token) &
"Authorization": "token $1" % a.token, "Content-Type: application/x-www-form-urlencoded\c\L" &
"Content-Type": "application/x-www-form-urlencoded", "Accept: */*\c\L")
"Accept": "*/*"
})
proc requestNewToken(cfg: Config): string = proc getGithubAuth(): Auth =
display("Info:", "Please create a new personal access token on Github in" & display("Info:", "Please create a new personal access token on Github in" &
" order to allow Nimble to fork the packages repository.", " order to allow Nimble to fork the packages repository.",
priority = HighPriority) priority = HighPriority)
display("Hint:", "Make sure to give the access token access to public repos" & display("Hint:", "Make sure to give the access token access to public repos" &
" (public_repo scope)!", Warning, HighPriority) " (public_repo scope)!", Warning, HighPriority)
sleep(5000) sleep(5000)
@ -40,35 +33,9 @@ proc requestNewToken(cfg: Config): string =
"https://github.com/settings/tokens/new", priority = HighPriority) "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")
let token = promptCustom("Personal access token?", "").strip() result.token = promptCustom("Personal access token?", "").strip()
# inform the user that their token will be written to disk let resp = getContent("https://api.github.com/user",
let tokenWritePath = cfg.nimbleDir / ApiKeyFile extraHeaders=createHeaders(result)).parseJson()
display("Info:", "Writing access token to file:" & tokenWritePath,
priority = HighPriority)
writeFile(tokenWritePath, token)
sleep(3000)
return token
proc getGithubAuth(o: Options): Auth =
let cfg = o.config
result.http = newHttpClient(proxy = getProxy(o))
# always prefer the environment variable to asking for a new one
if existsEnv(ApiTokenEnvironmentVariable):
result.token = getEnv(ApiTokenEnvironmentVariable)
display("Info:", "Using the '" & ApiTokenEnvironmentVariable &
"' environment variable for the GitHub API Token.",
priority = HighPriority)
else:
# 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()
display("Info:", "Using GitHub API Token in file: " & apiTokenFilePath,
priority = HighPriority)
except IOError:
result.token = requestNewToken(cfg)
createHeaders(result)
let resp = result.http.getContent("https://api.github.com/user").parseJson()
result.user = resp["login"].str result.user = resp["login"].str
display("Success:", "Verified as " & result.user, Success, HighPriority) display("Success:", "Verified as " & result.user, Success, HighPriority)
@ -76,12 +43,13 @@ proc getGithubAuth(o: Options): Auth =
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.
result = false result = false
if j{"fork"}.getBool(): if j{"fork"}.getBVal():
result = j{"parent"}{"full_name"}.getStr() == "nim-lang/packages" result = j{"parent"}{"full_name"}.getStr() == "nim-lang/packages"
proc forkExists(a: Auth): bool = proc forkExists(a: Auth): bool =
try: try:
let x = a.http.getContent(ReposUrl & a.user & "/packages") let x = getContent("https://api.github.com/repos/" & a.user & "/packages",
extraHeaders=createHeaders(a))
let j = parseJson(x) let j = parseJson(x)
result = isCorrectFork(j) result = isCorrectFork(j)
except JsonParsingError, IOError: except JsonParsingError, IOError:
@ -89,18 +57,18 @@ proc forkExists(a: Auth): bool =
proc createFork(a: Auth) = proc createFork(a: Auth) =
try: try:
discard a.http.postContent(ReposUrl & "nim-lang/packages/forks") discard postContent("https://api.github.com/repos/nim-lang/packages/forks",
extraHeaders=createHeaders(a))
except HttpRequestError: except HttpRequestError:
raise newException(NimbleError, "Unable to create fork. Access token" & raise newException(NimbleError, "Unable to create fork. Access token" &
" might not have enough permissions.") " might not have enough permissions.")
proc createPullRequest(a: Auth, packageName, branch: string): string = proc createPullRequest(a: Auth, packageName, branch: string) =
display("Info", "Creating PR", priority = HighPriority) display("Info", "Creating PR", priority = HighPriority)
var body = a.http.postContent(ReposUrl & "nim-lang/packages/pulls", discard postContent("https://api.github.com/repos/nim-lang/packages/pulls",
extraHeaders=createHeaders(a),
body="""{"title": "Add package $1", "head": "$2:$3", body="""{"title": "Add package $1", "head": "$2:$3",
"base": "master"}""" % [packageName, a.user, branch]) "base": "master"}""" % [packageName, a.user, branch])
var pr = parseJson(body)
return pr{"html_url"}.getStr()
proc `%`(s: openArray[string]): JsonNode = proc `%`(s: openArray[string]): JsonNode =
result = newJArray() result = newJArray()
@ -141,21 +109,32 @@ proc cleanupWhitespace(s: string): string =
proc editJson(p: PackageInfo; url, tags, downloadMethod: string) = proc editJson(p: PackageInfo; url, tags, downloadMethod: string) =
var contents = parseFile("packages.json") var contents = parseFile("packages.json")
doAssert contents.kind == JArray doAssert contents.kind == JArray
contents.add(%*{ contents.add(%{
"name": p.name, "name": %p.name,
"url": url, "url": %url,
"method": downloadMethod, "method": %downloadMethod,
"tags": tags.split(), "tags": %tags.split(),
"description": p.description, "description": %p.description,
"license": p.license, "license": %p.license,
"web": url "web": %url})
})
writeFile("packages.json", contents.pretty.cleanupWhitespace) writeFile("packages.json", contents.pretty.cleanupWhitespace)
proc publish*(p: PackageInfo, o: Options) = proc getPackageOriginUrl(a: Auth): string =
## Adds 'user:pw' to the URL so that the user is not asked *again* for it.
## We need this for 'git push'.
let (output, exitCode) = doCmdEx("git config --get remote.origin.url")
result = "origin"
if exitCode == 0:
result = output.string.strip
if result.endsWith(".git"): result.setLen(result.len - 4)
if result.startsWith("https://"):
result = "https://" & a.user & ':' & a.pw & '@' &
result["https://".len .. ^1]
proc publish*(p: PackageInfo) =
## Publishes the package p. ## Publishes the package p.
let auth = getGithubAuth(o) let auth = getGithubAuth()
var pkgsDir = getNimbleUserTempDir() / "nimble-packages-fork" var pkgsDir = getTempDir() / "nimble-packages-fork"
if not forkExists(auth): if not forkExists(auth):
createFork(auth) createFork(auth)
display("Info:", "Waiting 10s to let Github create a fork", display("Info:", "Waiting 10s to let Github create a fork",
@ -167,17 +146,13 @@ proc publish*(p: PackageInfo, o: Options) =
display("Removing", "old packages fork git directory.", display("Removing", "old packages fork git directory.",
priority = LowPriority) priority = LowPriority)
removeDir(pkgsDir) removeDir(pkgsDir)
createDir(pkgsDir) display("Cloning", "packages into: " & pkgsDir, priority = HighPriority)
doCmd("git clone git@github.com:" & auth.user & "/packages " & pkgsDir)
# Make sure to update the clone.
display("Updating", "the fork", priority = HighPriority)
cd pkgsDir: cd pkgsDir:
# Avoid git clone to prevent token from being stored in repo
# https://github.com/blog/1270-easier-builds-and-deployments-using-git-over-https-and-oauth
display("Copying", "packages fork into: " & pkgsDir, priority = HighPriority)
doCmd("git init")
doCmd("git pull https://github.com/" & auth.user & "/packages")
# Make sure to update the fork
display("Updating", "the fork", priority = HighPriority)
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 https://" & auth.token & "@github.com/" & auth.user & "/packages master") doCmd("git push origin master")
if not dirExists(pkgsDir): if not dirExists(pkgsDir):
raise newException(NimbleError, raise newException(NimbleError,
@ -192,7 +167,7 @@ proc publish*(p: PackageInfo, o: Options) =
var url = "" var url = ""
var downloadMethod = "" var downloadMethod = ""
if dirExists(os.getCurrentDir() / ".git"): if dirExists(os.getCurrentDir() / ".git"):
let (output, exitCode) = doCmdEx("git ls-remote --get-url") let (output, exitCode) = doCmdEx("git config --get remote.origin.url")
if exitCode == 0: if exitCode == 0:
url = output.string.strip url = output.string.strip
if url.endsWith(".git"): url.setLen(url.len - 4) if url.endsWith(".git"): url.setLen(url.len - 4)
@ -201,7 +176,7 @@ proc publish*(p: PackageInfo, o: Options) =
if parsed.scheme == "": if parsed.scheme == "":
# Assuming that we got an ssh write/read URL. # Assuming that we got an ssh write/read URL.
let sshUrl = parseUri("ssh://" & url) let sshUrl = parseUri("ssh://" & url)
url = "https://" & sshUrl.hostname & "/" & sshUrl.port & sshUrl.path url = "https://github.com/" & sshUrl.port & sshUrl.path
elif dirExists(os.getCurrentDir() / ".hg"): elif dirExists(os.getCurrentDir() / ".hg"):
downloadMethod = "hg" downloadMethod = "hg"
# TODO: Retrieve URL from hg. # TODO: Retrieve URL from hg.
@ -213,17 +188,19 @@ proc publish*(p: PackageInfo, o: Options) =
url = promptCustom("Github URL of " & p.name & "?", "") url = promptCustom("Github URL of " & p.name & "?", "")
if url.len == 0: userAborted() if url.len == 0: userAborted()
let tags = promptCustom( let tags = promptCustom("Whitespace separated list of tags?", "")
"Whitespace separated list of tags? (For example: web library wrapper)",
""
)
cd pkgsDir: cd pkgsDir:
editJson(p, url, tags, downloadMethod) editJson(p, url, tags, downloadMethod)
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 checkout -B " & branchName)
doCmd("git commit packages.json -m \"Added package " & p.name & "\"") doCmd("git commit packages.json -m \"Added package " & p.name & "\"")
display("Pushing", "to remote of fork.", priority = HighPriority) display("Pushing", "to remote of fork.", priority = HighPriority)
doCmd("git push https://" & auth.token & "@github.com/" & auth.user & "/packages " & branchName) doCmd("git push " & getPackageOriginUrl(auth) & " " & branchName)
let prUrl = createPullRequest(auth, p.name, branchName) createPullRequest(auth, p.name, branchName)
display("Success:", "Pull request successful, check at " & prUrl , Success, HighPriority) display("Success:", "Pull request successful.", Success, HighPriority)
when isMainModule:
import packageinfo
var p = getPkgInfo(getCurrentDir())
publish(p)

View file

@ -1,135 +0,0 @@
# Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info.
import os, json, sets
import options, common, version, download, packageinfo
proc saveNimbleData*(options: Options) =
# TODO: This file should probably be locked.
writeFile(options.getNimbleDir() / "nimbledata.json",
pretty(options.nimbleData))
proc addRevDep*(nimbleData: JsonNode, dep: tuple[name, version: string],
pkg: PackageInfo) =
# Add a record which specifies that `pkg` has a dependency on `dep`, i.e.
# the reverse dependency of `dep` is `pkg`.
if not nimbleData["reverseDeps"].hasKey(dep.name):
nimbleData["reverseDeps"][dep.name] = newJObject()
if not nimbleData["reverseDeps"][dep.name].hasKey(dep.version):
nimbleData["reverseDeps"][dep.name][dep.version] = newJArray()
let revDep = %{ "name": %pkg.name, "version": %pkg.specialVersion}
let thisDep = nimbleData["reverseDeps"][dep.name][dep.version]
if revDep notin thisDep:
thisDep.add revDep
proc removeRevDep*(nimbleData: JsonNode, pkg: PackageInfo) =
## Removes ``pkg`` from the reverse dependencies of every package.
assert(not pkg.isMinimal)
proc remove(pkg: PackageInfo, depTup: PkgTuple, thisDep: JsonNode) =
for ver, val in thisDep:
if ver.newVersion in depTup.ver:
var newVal = newJArray()
for revDep in val:
if not (revDep["name"].str == pkg.name and
revDep["version"].str == pkg.specialVersion):
newVal.add revDep
thisDep[ver] = newVal
for depTup in pkg.requires:
if depTup.name.isURL():
# We sadly must go through everything in this case...
for key, val in nimbleData["reverseDeps"]:
remove(pkg, depTup, val)
else:
let thisDep = nimbleData{"reverseDeps", depTup.name}
if thisDep.isNil: continue
remove(pkg, depTup, thisDep)
# Clean up empty objects/arrays
var newData = newJObject()
for key, val in nimbleData["reverseDeps"]:
if val.len != 0:
var newVal = newJObject()
for ver, elem in val:
if elem.len != 0:
newVal[ver] = elem
if newVal.len != 0:
newData[key] = newVal
nimbleData["reverseDeps"] = newData
proc getRevDepTups*(options: Options, pkg: PackageInfo): seq[PkgTuple] =
## Returns a list of *currently installed* reverse dependencies for `pkg`.
result = @[]
let thisPkgsDep =
options.nimbleData["reverseDeps"]{pkg.name}{pkg.specialVersion}
if not thisPkgsDep.isNil:
let pkgList = getInstalledPkgsMin(options.getPkgsDir(), options)
for pkg in thisPkgsDep:
let pkgTup = (
name: pkg["name"].getStr(),
ver: parseVersionRange(pkg["version"].getStr())
)
var pkgInfo: PackageInfo
if not findPkg(pkgList, pkgTup, pkgInfo):
continue
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()}
let nimforum1 = PackageInfo(
isMinimal: false,
name: "nimforum",
specialVersion: "0.1.0",
requires: @[("jester", parseVersionRange("0.1.0")),
("captcha", parseVersionRange("1.0.0")),
("auth", parseVersionRange("#head"))]
)
let nimforum2 = PackageInfo(isMinimal: false, name: "nimforum", specialVersion: "0.2.0")
let play = PackageInfo(isMinimal: false, name: "play", specialVersion: "#head")
nimbleData.addRevDep(("jester", "0.1.0"), nimforum1)
nimbleData.addRevDep(("jester", "0.1.0"), play)
nimbleData.addRevDep(("captcha", "1.0.0"), nimforum1)
nimbleData.addRevDep(("auth", "#head"), nimforum1)
nimbleData.addRevDep(("captcha", "1.0.0"), nimforum2)
nimbleData.addRevDep(("auth", "#head"), nimforum2)
doAssert nimbleData["reverseDeps"]["jester"]["0.1.0"].len == 2
doAssert nimbleData["reverseDeps"]["captcha"]["1.0.0"].len == 2
doAssert nimbleData["reverseDeps"]["auth"]["#head"].len == 2
block:
nimbleData.removeRevDep(nimforum1)
let jester = nimbleData["reverseDeps"]["jester"]["0.1.0"][0]
doAssert jester["name"].getStr() == play.name
doAssert jester["version"].getStr() == play.specialVersion
let captcha = nimbleData["reverseDeps"]["captcha"]["1.0.0"][0]
doAssert captcha["name"].getStr() == nimforum2.name
doAssert captcha["version"].getStr() == nimforum2.specialVersion
echo("Everything works!")

View file

@ -3,7 +3,7 @@
# #
# Various miscellaneous utility functions reside here. # Various miscellaneous utility functions reside here.
import osproc, pegs, strutils, os, uri, sets, json, parseutils import osproc, pegs, strutils, os, uri, sets, json, parseutils
import version, cli import version, common, cli
proc extractBin(cmd: string): string = proc extractBin(cmd: string): string =
if cmd[0] == '"': if cmd[0] == '"':
@ -11,7 +11,7 @@ proc extractBin(cmd: string): string =
else: else:
return cmd.split(' ')[0] return cmd.split(' ')[0]
proc doCmd*(cmd: string, showOutput = false, showCmd = false) = proc doCmd*(cmd: string, showOutput = false) =
let bin = extractBin(cmd) let bin = extractBin(cmd)
if findExe(bin) == "": if findExe(bin) == "":
raise newException(NimbleError, "'" & bin & "' not in PATH.") raise newException(NimbleError, "'" & bin & "' not in PATH.")
@ -20,26 +20,19 @@ proc doCmd*(cmd: string, showOutput = false, showCmd = false) =
stdout.flushFile() stdout.flushFile()
stderr.flushFile() stderr.flushFile()
if showCmd: displayDebug("Executing", cmd)
display("Executing", cmd, priority = MediumPriority) let (output, exitCode) = execCmdEx(cmd)
else: displayDebug("Finished", "with exit code " & $exitCode)
displayDebug("Executing", cmd) # TODO: Improve to show output in real-time.
if showOutput: if showOutput:
let exitCode = execCmd(cmd) display("Output:", output, priority = HighPriority)
displayDebug("Finished", "with exit code " & $exitCode)
if exitCode != QuitSuccess:
raise newException(NimbleError,
"Execution failed with exit code $1\nCommand: $2" %
[$exitCode, cmd])
else: else:
let (output, exitCode) = execCmdEx(cmd)
displayDebug("Finished", "with exit code " & $exitCode)
displayDebug("Output", output) displayDebug("Output", output)
if exitCode != QuitSuccess: if exitCode != QuitSuccess:
raise newException(NimbleError, raise newException(NimbleError,
"Execution failed with exit code $1\nCommand: $2\nOutput: $3" % "Execution failed with exit code $1\nCommand: $2\nOutput: $3" %
[$exitCode, cmd, output]) [$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)
@ -82,15 +75,8 @@ proc changeRoot*(origRoot, newRoot, path: string): string =
## newRoot: /home/test/ ## newRoot: /home/test/
## path: /home/dom/bar/blah/2/foo.txt ## path: /home/dom/bar/blah/2/foo.txt
## Return value -> /home/test/bar/blah/2/foo.txt ## Return value -> /home/test/bar/blah/2/foo.txt
if path.startsWith(origRoot):
## The additional check of `path.samePaths(origRoot)` is necessary to prevent return newRoot / path[origRoot.len .. path.len-1]
## a regression, where by ending the `srcDir` defintion in a nimble file in a
## trailing separator would cause the `path.startsWith(origRoot)` evaluation to
## fail because of the value of `origRoot` would be longer than `path` due to
## the trailing separator. This would cause this method to throw during package
## installation.
if path.startsWith(origRoot) or path.samePaths(origRoot):
return newRoot / path.substr(origRoot.len, path.len-1)
else: else:
raise newException(ValueError, raise newException(ValueError,
"Cannot change root of path: Path does not begin with original root.") "Cannot change root of path: Path does not begin with original root.")
@ -109,10 +95,6 @@ proc copyDirD*(fro, to: string): seq[string] =
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))
proc createDirD*(dir: string) =
display("Creating", "directory $#" % dir, priority = LowPriority)
createDir(dir)
proc getDownloadDirName*(uri: string, verRange: VersionRange): string = proc getDownloadDirName*(uri: string, verRange: VersionRange): string =
## Creates a directory name based on the specified ``uri`` (url) ## Creates a directory name based on the specified ``uri`` (url)
result = "" result = ""
@ -148,37 +130,3 @@ proc contains*(j: JsonNode, elem: tuple[key: string, val: JsonNode]): bool =
for key, val in pairs(j): for key, val in pairs(j):
if key == elem.key and val == elem.val: if key == elem.key and val == elem.val:
return true return true
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.
##
## 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.
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"))
else:
tmpdir = getTempDir()
return tmpdir

View file

@ -35,28 +35,24 @@ type
NimbleError* = object of Exception NimbleError* = object of Exception
hint*: string hint*: string
proc newVersion*(ver: string): Version =
doAssert(ver[0] in {'#', '\0'} + Digits)
return Version(ver)
proc `$`*(ver: Version): string {.borrow.} proc `$`*(ver: Version): string {.borrow.}
proc hash*(ver: Version): Hash {.borrow.} proc hash*(ver: Version): Hash {.borrow.}
proc newVersion*(ver: string): Version = proc isNil*(ver: Version): bool {.borrow.}
doAssert(ver.len == 0 or ver[0] in {'#', '\0'} + Digits,
"Wrong version: " & ver)
return Version(ver)
proc isSpecial*(ver: Version): bool = proc isSpecial*(ver: Version): bool =
return ($ver).len > 0 and ($ver)[0] == '#' return ($ver)[0] == '#'
proc `<`*(ver: Version, ver2: Version): bool = proc `<`*(ver: Version, ver2: Version): bool =
# Handling for special versions such as "#head" or "#branch". # Handling for special versions such as "#head" or "#branch".
if ver.isSpecial or ver2.isSpecial: if ver.isSpecial or ver2.isSpecial:
# TODO: This may need to be reverted. See #311. # TODO: This may need to be reverted. See #311.
if ver2.isSpecial and ($ver2).normalize == "#head": return ($ver2).normalize == "#head" and ($ver).normalize != "#head"
return ($ver).normalize != "#head"
if not ver2.isSpecial:
# `#aa111 < 1.1`
return ($ver).normalize != "#head"
# Handling for normal versions such as "0.1.0" or "1.0". # Handling for normal versions such as "0.1.0" or "1.0".
var sVer = string(ver).split('.') var sVer = string(ver).split('.')
@ -93,11 +89,6 @@ proc `==`*(ver: Version, ver2: Version): bool =
else: else:
return false return false
proc cmp*(a, b: Version): int =
if a < b: -1
elif a > b: 1
else: 0
proc `<=`*(ver: Version, ver2: Version): bool = proc `<=`*(ver: Version, ver2: Version): bool =
return (ver == ver2) or (ver < ver2) return (ver == ver2) or (ver < ver2)
@ -135,44 +126,46 @@ proc contains*(ran: VersionRange, ver: Version): bool =
return withinRange(ver, ran) return withinRange(ver, ran)
proc makeRange*(version: string, op: string): VersionRange = proc makeRange*(version: string, op: string): VersionRange =
new(result)
if version == "": if version == "":
raise newException(ParseVersionError, raise newException(ParseVersionError,
"A version needs to accompany the operator.") "A version needs to accompany the operator.")
case op case op
of ">": of ">":
result = VersionRange(kind: verLater) result.kind = verLater
of "<": of "<":
result = VersionRange(kind: verEarlier) result.kind = verEarlier
of ">=": of ">=":
result = VersionRange(kind: verEqLater) result.kind = verEqLater
of "<=": of "<=":
result = VersionRange(kind: verEqEarlier) result.kind = verEqEarlier
of "", "==": of "":
result = VersionRange(kind: verEq) result.kind = verEq
else: else:
raise newException(ParseVersionError, "Invalid operator: " & op) raise newException(ParseVersionError, "Invalid operator: " & op)
result.ver = Version(version) result.ver = Version(version)
proc parseVersionRange*(s: string): VersionRange = proc parseVersionRange*(s: string): VersionRange =
# >= 1.5 & <= 1.8 # >= 1.5 & <= 1.8
new(result)
if s.len == 0: if s.len == 0:
result = VersionRange(kind: verAny) result.kind = verAny
return return
if s[0] == '#': if s[0] == '#':
result = VersionRange(kind: verSpecial) result.kind = verSpecial
result.spe = s.Version result.spe = s.Version
return return
var i = 0 var i = 0
var op = "" var op = ""
var version = "" var version = ""
while i < s.len: while true:
case s[i] case s[i]
of '>', '<', '=': of '>', '<', '=':
op.add(s[i]) op.add(s[i])
of '&': of '&':
result = VersionRange(kind: verIntersect) result.kind = verIntersect
result.verILeft = makeRange(version, op) result.verILeft = makeRange(version, op)
# Parse everything after & # Parse everything after &
@ -185,32 +178,35 @@ proc parseVersionRange*(s: string): VersionRange =
raise newException(ParseVersionError, raise newException(ParseVersionError,
"Having more than one `&` in a version range is pointless") "Having more than one `&` in a version range is pointless")
return break
of '0'..'9', '.': of '0'..'9', '.':
version.add(s[i]) version.add(s[i])
of '\0':
result = makeRange(version, op)
break
of ' ': of ' ':
# Make sure '0.9 8.03' is not allowed. # 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', '.'}: if s[i+1] in {'0'..'9', '.'}:
raise newException(ParseVersionError, raise newException(ParseVersionError,
"Whitespace is not allowed in a version literal.") "Whitespace is not allowed in a version literal.")
else: else:
raise newException(ParseVersionError, raise newException(ParseVersionError,
"Unexpected char in version range '" & s & "': " & s[i]) "Unexpected char in version range: " & s[i])
inc(i) inc(i)
result = makeRange(version, op)
proc toVersionRange*(ver: Version): VersionRange = proc toVersionRange*(ver: Version): VersionRange =
## Converts a version to either a verEq or verSpecial VersionRange. ## Converts a version to either a verEq or verSpecial VersionRange.
new(result) new(result)
if ver.isSpecial: if ver.isSpecial:
result = VersionRange(kind: verSpecial) result.kind = verSpecial
result.spe = ver result.spe = ver
else: else:
result = VersionRange(kind: verEq) result.kind = verEq
result.ver = ver result.ver = ver
proc parseRequires*(req: string): PkgTuple = proc parseRequires*(req: string): PkgTuple =
@ -266,18 +262,21 @@ proc getSimpleString*(verRange: VersionRange): string =
result = "" result = ""
proc newVRAny*(): VersionRange = proc newVRAny*(): VersionRange =
result = VersionRange(kind: verAny) new(result)
result.kind = verAny
proc newVREarlier*(ver: string): VersionRange = proc newVREarlier*(ver: string): VersionRange =
result = VersionRange(kind: verEarlier) new(result)
result.kind = verEarlier
result.ver = newVersion(ver) result.ver = newVersion(ver)
proc newVREq*(ver: string): VersionRange = proc newVREq*(ver: string): VersionRange =
result = VersionRange(kind: verEq) new(result)
result.kind = verEq
result.ver = newVersion(ver) result.ver = newVersion(ver)
proc findLatest*(verRange: VersionRange, proc findLatest*(verRange: VersionRange,
versions: OrderedTable[Version, string]): tuple[ver: Version, tag: string] = versions: Table[Version, string]): tuple[ver: Version, tag: string] =
result = (newVersion(""), "") result = (newVersion(""), "")
for ver, tag in versions: for ver, tag in versions:
if not withinRange(ver, verRange): continue if not withinRange(ver, verRange): continue
@ -291,17 +290,16 @@ when isMainModule:
doAssert(newVersion("1.0") < newVersion("1.4")) doAssert(newVersion("1.0") < newVersion("1.4"))
doAssert(newVersion("1.0.1") > newVersion("1.0")) doAssert(newVersion("1.0.1") > newVersion("1.0"))
doAssert(newVersion("1.0.6") <= newVersion("1.0.6")) 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(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.0.0.1"))
doAssert(newVersion("0.1.0") <= newVersion("0.1")) doAssert(newVersion("0.1.0") <= newVersion("0.1"))
var inter1 = parseVersionRange(">= 1.0 & <= 1.5") var inter1 = parseVersionRange(">= 1.0 & <= 1.5")
doAssert(inter1.kind == verIntersect)
var inter2 = parseVersionRange("1.0") var inter2 = parseVersionRange("1.0")
doAssert(inter2.kind == verEq) doAssert(inter2.kind == verEq)
doAssert(parseVersionRange("== 3.4.2") == parseVersionRange("3.4.2")) #echo(parseVersionRange(">= 0.8 0.9"))
doAssert(not withinRange(newVersion("1.5.1"), inter1)) doAssert(not withinRange(newVersion("1.5.1"), inter1))
doAssert(withinRange(newVersion("1.0.2.3.4.5.6.7.8.9.10.11.12"), inter1)) doAssert(withinRange(newVersion("1.0.2.3.4.5.6.7.8.9.10.11.12"), inter1))
@ -315,11 +313,8 @@ when isMainModule:
doAssert(newVersion("") < newVersion("1.0.0")) doAssert(newVersion("") < newVersion("1.0.0"))
doAssert(newVersion("") < newVersion("0.1.0")) doAssert(newVersion("") < newVersion("0.1.0"))
var versions = toOrderedTable[Version, string]({ var versions = toTable[Version, string]({newVersion("0.1.1"): "v0.1.1",
newVersion("0.1.1"): "v0.1.1", newVersion("0.2.3"): "v0.2.3", newVersion("0.5"): "v0.5"})
newVersion("0.2.3"): "v0.2.3",
newVersion("0.5"): "v0.5"
})
doAssert findLatest(parseVersionRange(">= 0.1 & <= 0.4"), versions) == doAssert findLatest(parseVersionRange(">= 0.1 & <= 0.4"), versions) ==
(newVersion("0.2.3"), "v0.2.3") (newVersion("0.2.3"), "v0.2.3")
@ -343,10 +338,6 @@ when isMainModule:
doAssert(newVersion("#head") > newVersion("0.1.0")) doAssert(newVersion("#head") > newVersion("0.1.0"))
doAssert(not(newVersion("#head") > newVersion("#head"))) doAssert(not(newVersion("#head") > newVersion("#head")))
doAssert(withinRange(newVersion("#head"), parseVersionRange(">= 0.5.0"))) doAssert(withinRange(newVersion("#head"), parseVersionRange(">= 0.5.0")))
doAssert newVersion("#a111") < newVersion("#head")
# We assume that all other special versions are not higher than a normal
# version.
doAssert newVersion("#a111") < newVersion("1.1")
# An empty version range should give verAny # An empty version range should give verAny
doAssert parseVersionRange("").kind == verAny doAssert parseVersionRange("").kind == verAny
@ -355,7 +346,4 @@ when isMainModule:
doAssert toVersionRange(newVersion("#head")).kind == verSpecial doAssert toVersionRange(newVersion("#head")).kind == verSpecial
doAssert toVersionRange(newVersion("0.2.0")).kind == verEq doAssert toVersionRange(newVersion("0.2.0")).kind == verEq
# Something raised on IRC
doAssert newVersion("1") == newVersion("1.0")
echo("Everything works!") echo("Everything works!")

23
tests/.gitignore vendored
View file

@ -1,23 +0,0 @@
tester
/nimble-test
/buildDir
/binaryPackage/v1/binaryPackage
/binaryPackage/v2/binaryPackage
/develop/dependent/src/dependent
/issue27/issue27
/issue206/issue/issue206bin
/issue289/issue289
/issue428/nimbleDir/
/nimbleDir/
/packageStructure/c/c
/packageStructure/y/y
/testCommand/testOverride/myTester
/testCommand/testsFail/tests/a
/testCommand/testsFail/tests/b
/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 +0,0 @@
echo("v1")

View file

@ -1,12 +0,0 @@
# Package
version = "1.0"
author = "Dominik Picheta"
description = "binary"
license = "MIT"
bin = @["binaryPackage"]
# Dependencies
requires "nim >= 0.15.3"

View file

@ -1 +0,0 @@
echo("v2")

View file

@ -1,12 +0,0 @@
# Package
version = "2.0"
author = "Dominik Picheta"
description = "binary"
license = "MIT"
bin = @["binaryPackage"]
# Dependencies
requires "nim >= 0.15.3"

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

@ -1 +0,0 @@
echo("hello")

View file

@ -1,14 +0,0 @@
# Package
version = "1.0"
author = "Dominik Picheta"
description = "binary"
license = "MIT"
bin = @["binary"]
skipExt = @["nim"]
# Dependencies
requires "nim >= 0.16.0"

View file

@ -1,12 +0,0 @@
# Package
version = "1.0"
author = "Dominik Picheta"
description = "dependent"
license = "MIT"
srcDir = "src"
# Dependencies
requires "nim >= 0.16.0", "srcdirtest"

View file

@ -1,3 +0,0 @@
import srcdirtest
doAssert foo() == "correct"

View file

@ -1 +0,0 @@
echo("hello")

View file

@ -1,13 +0,0 @@
# Package
version = "1.0"
author = "Dominik Picheta"
description = "hybrid"
license = "MIT"
bin = @["hybrid"]
installExt = @["nim"]
# Dependencies
requires "nim >= 0.16.0"

View file

@ -1,4 +0,0 @@
proc foo*(): string =
return "correct"
echo("hello")

View file

@ -1,12 +0,0 @@
# Package
version = "1.0"
author = "Dominik Picheta"
description = "srcdir"
license = "MIT"
srcDir = "src"
# Dependencies
requires "nim >= 0.16.0"

View file

@ -1,14 +0,0 @@
# Package
version = "0.1.0"
author = "Dominik Picheta"
description = "a"
license = "MIT"
# Dependencies
requires "nim >= 0.15.3", "b", "c"
task test, "test":
echo("hello")

View file

@ -1,11 +0,0 @@
# Package
version = "0.1.0"
author = "Dominik Picheta"
description = "b"
license = "MIT"
# Dependencies
requires "nim >= 0.15.3", "d"

View file

@ -1,11 +0,0 @@
# Package
version = "0.1.0"
author = "Dominik Picheta"
description = "c"
license = "MIT"
# Dependencies
requires "nim >= 0.15.3", "d"

View file

@ -1,11 +0,0 @@
# Package
version = "0.1.0"
author = "Dominik Picheta"
description = "d"
license = "MIT"
# Dependencies
requires "nim >= 0.15.3"

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,7 +0,0 @@
# Package
version = "0.1.0"
author = "Samantha Marshall"
description = "test case to validate successful install when `srcDir` value ends in a directory separator"
license = "MIT"
srcDir = "src/"

View file

@ -1,14 +0,0 @@
[
{
"name": "discordnim",
"url": "https://github.com/Krognol/discordnim",
"method": "git",
"tags": [
"library",
"discord"
],
"description": "Discord library for Nim",
"license": "MIT",
"web": "https://github.com/Krognol/discordnim"
}
]

View file

@ -1,10 +0,0 @@
# Package
version = "0.1.0"
author = "Author"
description = "dummy"
license = "MIT"
# Dependencies
requires "nim >= 0.17.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" version = "0.1.0"
author = "Dominik Picheta" author = "Dominik Picheta"
description = """Test package description = "Test package"
with multi-line description
"""
license = "BSD" license = "BSD"
bin = @["nimscript"] bin = @["nimscript"]
@ -13,7 +11,7 @@ bin = @["nimscript"]
requires "nim >= 0.12.1" requires "nim >= 0.12.1"
task work, "test description": task test, "test description":
echo(5+5) echo(5+5)
task c_test, "Testing `setCommand \"c\", \"nimscript.nim\"`": task c_test, "Testing `setCommand \"c\", \"nimscript.nim\"`":
@ -23,16 +21,8 @@ task cr, "Testing `nimble c -r nimscript.nim` via setCommand":
--r --r
setCommand "c", "nimscript.nim" setCommand "c", "nimscript.nim"
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": task api, "Testing nimscriptapi module functionality":
doAssert(findExe("nim").len != 0) echo(getPkgDir())
echo("PKG_DIR: ", getPkgDir())
before hooks: before hooks:
echo("First") echo("First")
@ -48,15 +38,3 @@ before hooks2:
task hooks2, "Testing the hooks again": task hooks2, "Testing the hooks again":
echo("Shouldn't happen") echo("Shouldn't happen")
before install:
echo("Before PkgDir: ", getPkgDir())
after install:
echo("After PkgDir: ", getPkgDir())
before build:
echo("Before build")
after build:
echo("After build")

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" description = "Incorrectly structured package Y"
license = "MIT" license = "MIT"
installExt = @["nim"]
bin = @["y"] bin = @["y"]
# Dependencies # 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,10 +0,0 @@
# Package
version = "0.1.0"
author = "Dominik Picheta"
description = "Random dep"
license = "MIT"
# Dependencies
requires "nim >= 0.15.0"

View file

@ -1,11 +0,0 @@
# Package
version = "0.1.0"
author = "Dominik Picheta"
description = "Correctly structured package A"
license = "MIT"
# Dependencies
requires "nim >= 0.15.0"

View file

@ -1,11 +0,0 @@
# Package
version = "0.1.0"
author = "Dominik Picheta"
description = "Correctly structured package A"
license = "MIT"
# Dependencies
requires "nim >= 0.15.0", "mydep"

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 +0,0 @@
echo("overriden")

View file

@ -1,9 +0,0 @@
version = "0.1.0"
author = "John Doe"
description = "Nimble Test"
license = "BSD"
skipFiles = @["myTester.nim"]
task test, "Custom tester":
exec "nim c -r myTester.nim"

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,6 +0,0 @@
import testing123, unittest
test "can compile nimble":
echo "First test"
myFunc()

View file

@ -1,6 +0,0 @@
import testing123, unittest
test "can compile nimble":
echo "Failing Second test"
assert(false)

View file

@ -1,7 +0,0 @@
import testing123, unittest
test "can compile nimble":
echo "Third test"
myFunc()

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,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,6 +0,0 @@
import testing123, unittest
test "can compile nimble":
echo "First test"
myFunc()

View file

@ -1,7 +0,0 @@
import testing123, unittest
test "can compile nimble":
echo "Second test"
myFunc()

View file

@ -1,7 +0,0 @@
import testing123, unittest
test "can compile nimble":
echo "Third test"
myFunc()

File diff suppressed because it is too large Load diff