Implements develop command. Refs #240.

This commit is contained in:
Dominik Picheta 2017-08-13 15:04:40 +01:00
commit 56dd401831
12 changed files with 234 additions and 57 deletions

View file

@ -306,7 +306,8 @@ proc buildFromDir(pkgInfo: PackageInfo, paths: seq[string], forRelease: bool) =
buildFromDir(pkgInfo, paths, args) buildFromDir(pkgInfo, paths, args)
proc saveNimbleMeta(pkgDestDir, url, vcsRevision: string, proc saveNimbleMeta(pkgDestDir, url, vcsRevision: string,
filesInstalled, bins: HashSet[string]) = filesInstalled, bins: HashSet[string],
isLink: bool = false) =
## Saves the specified data into a ``nimblemeta.json`` file inside ## Saves the specified data into a ``nimblemeta.json`` file inside
## ``pkgDestDir``. ## ``pkgDestDir``.
## ##
@ -314,6 +315,9 @@ proc saveNimbleMeta(pkgDestDir, url, vcsRevision: string,
## installed. ## installed.
## bins - A list of binary filenames which have been installed for this ## bins - A list of binary filenames which have been installed for this
## package. ## package.
##
## isLink - Determines whether the installed package is a .nimble-link.
# TODO: Move to packageinstaller.nim
var nimblemeta = %{"url": %url} var nimblemeta = %{"url": %url}
if not vcsRevision.isNil: if not vcsRevision.isNil:
nimblemeta["vcsRevision"] = %vcsRevision nimblemeta["vcsRevision"] = %vcsRevision
@ -325,8 +329,20 @@ proc saveNimbleMeta(pkgDestDir, url, vcsRevision: string,
nimblemeta["binaries"] = binaries nimblemeta["binaries"] = binaries
for bin in bins: for bin in bins:
binaries.add(%bin) binaries.add(%bin)
nimblemeta["isLink"] = %isLink
writeFile(pkgDestDir / "nimblemeta.json", $nimblemeta) writeFile(pkgDestDir / "nimblemeta.json", $nimblemeta)
proc saveNimbleMeta(pkgDestDir, pkgDir, vcsRevision: 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,
initSet[string](), initSet[string](), true)
proc removePkgDir(dir: string, options: Options) = proc removePkgDir(dir: string, options: Options) =
## Removes files belonging to the package in ``dir``. ## Removes files belonging to the package in ``dir``.
try: try:
@ -399,7 +415,6 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options,
var pkgInfo = getPkgInfo(dir, options) var pkgInfo = getPkgInfo(dir, options)
let realDir = pkgInfo.getRealDir() let realDir = pkgInfo.getRealDir()
let binDir = options.getBinDir() let binDir = options.getBinDir()
let pkgsDir = options.getPkgsDir()
var depsOptions = options var depsOptions = options
depsOptions.depsOnly = false depsOptions.depsOnly = false
@ -421,12 +436,11 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options,
# if the build fails then the old package will still be installed. # if the build fails then the old package will still be installed.
if pkgInfo.bin.len > 0: buildFromDir(pkgInfo, result.paths, true) if pkgInfo.bin.len > 0: buildFromDir(pkgInfo, result.paths, true)
let versionStr = '-' & pkgInfo.specialVersion let pkgDestDir = pkgInfo.getPkgDest(options)
let pkgDestDir = pkgsDir / (pkgInfo.name & versionStr)
if existsDir(pkgDestDir) and existsFile(pkgDestDir / "nimblemeta.json"): if existsDir(pkgDestDir) and existsFile(pkgDestDir / "nimblemeta.json"):
if not options.prompt(pkgInfo.name & versionStr & let msg = "$1@$2 already exists. Overwrite?" %
" already exists. Overwrite?"): [pkgInfo.name, pkgInfo.specialVersion]
if not options.prompt(msg):
raise NimbleQuit(msg: "") raise NimbleQuit(msg: "")
removePkgDir(pkgDestDir, options) removePkgDir(pkgDestDir, options)
# Remove any symlinked binaries # Remove any symlinked binaries
@ -495,49 +509,6 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options,
display("Success:", pkgInfo.name & " installed successfully.", display("Success:", pkgInfo.name & " installed successfully.",
Success, HighPriority) Success, HighPriority)
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_"
when defined(windows):
proc GetCurrentProcessId(): int32 {.stdcall, dynlib: "kernel32",
importc: "GetCurrentProcessId".}
result.add($GetCurrentProcessId())
else:
result.add($getpid())
proc downloadPkg(url: string, verRange: VersionRange,
downMethod: DownloadMethod,
options: Options): (string, Version) =
## Downloads the repository as specified by ``url`` and ``verRange`` using
## the download method specified.
##
## Returns the directory where it was downloaded and the concrete version
## which was downloaded.
let downloadDir = (getNimbleTempDir() / getDownloadDirName(url, verRange))
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]
display("Downloading", "$1 using $2" % [modUrl, $downMethod],
priority = HighPriority)
result = (
downloadDir,
doDownload(modUrl, downloadDir, verRange, downMethod, options)
)
proc getDownloadInfo*(pv: PkgTuple, options: Options, proc getDownloadInfo*(pv: PkgTuple, options: Options,
doPrompt: bool): (DownloadMethod, string) = doPrompt: bool): (DownloadMethod, string) =
if pv.name.isURL: if pv.name.isURL:
@ -931,6 +902,70 @@ proc listTasks(options: Options) =
let nimbleFile = findNimbleFile(getCurrentDir(), true) let nimbleFile = findNimbleFile(getCurrentDir(), true)
nimscriptsupport.listTasks(nimbleFile, options) nimscriptsupport.listTasks(nimbleFile, options)
proc developFromDir(dir: string, options: Options) =
if options.depsOnly:
raiseNimbleError("Cannot develop dependencies only.")
var pkgInfo = getPkgInfo(dir, options)
if pkgInfo.bin.len > 0:
if "nim" in pkgInfo.skipExt:
raiseNimbleError("Cannot develop packages that are binaries only.")
display("Warning:", "This package's binaries will not be compiled " &
"nor symlinked for development.", Warning, HighPriority)
# Overwrite the version to #head always.
pkgInfo.specialVersion = "#head"
# Dependencies need to be processed before the creation of the pkg dir.
discard processDeps(pkgInfo, options)
# This is similar to the code in `installFromDir`, except that we
# *consciously* not worry about the package's binaries.
let pkgDestDir = pkgInfo.getPkgDest(options)
if existsDir(pkgDestDir) and existsFile(pkgDestDir / "nimblemeta.json"):
let msg = "$1@$2 already exists. Overwrite?" %
[pkgInfo.name, pkgInfo.specialVersion]
if not options.prompt(msg):
raise NimbleQuit(msg: "")
removePkgDir(pkgDestDir, options)
createDir(pkgDestDir)
# The .nimble-link file contains the path to the real .nimble file,
# and a secondary path to the source directory of the package.
# The secondary path is necessary so that the package's .nimble file doesn't
# need to be read. This will mean that users will need to re-run
# `nimble develop` if they change their `srcDir` but I think it's a worthy
# compromise.
let contents = pkgInfo.myPath & "\n" & pkgInfo.getRealDir()
writeFile(pkgDestDir / pkgInfo.name.addFileExt("nimble-link"), contents)
# TODO: Handle dependencies of the package we are developing (they need to be
# installed).
# Save a nimblemeta.json file.
saveNimbleMeta(pkgDestDir, "file://" & dir, vcsRevisionInDir(dir))
# Save the nimble data (which might now contain reverse deps added in
# processDeps).
saveNimbleData(options)
proc develop(options: Options) =
if options.action.packages == @[]:
developFromDir(getCurrentDir(), options)
else:
# Install each package.
for pv in options.action.packages:
let downloadDir = getCurrentDir() / pv.name
if dirExists(downloadDir):
let msg = "Cannot clone into '$1': directory exists." % downloadDir
let hint = "Remove the directory, or run this command somewhere else."
raiseNimbleError(msg, hint)
let (meth, url) = getDownloadInfo(pv, options, true)
discard downloadPkg(url, pv.ver, meth, options, downloadDir)
developFromDir(downloadDir, options)
proc execHook(options: Options, before: bool): bool = proc execHook(options: Options, before: bool): bool =
## Returns whether to continue. ## Returns whether to continue.
result = true result = true
@ -1002,6 +1037,8 @@ proc doAction(options: Options) =
dump(options) dump(options)
of actionTasks: of actionTasks:
listTasks(options) listTasks(options)
of actionDevelop:
develop(options)
of actionNil: of actionNil:
assert false assert false
of actionCustom: of actionCustom:

View file

@ -212,6 +212,42 @@ proc doDownload*(url: string, downloadDir: string, verRange: VersionRange,
verifyClone() verifyClone()
proc downloadPkg*(url: string, verRange: VersionRange,
downMethod: DownloadMethod,
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 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]
display("Downloading", "$1 using $2" % [modUrl, $downMethod],
priority = HighPriority)
result = (
downloadDir,
doDownload(modUrl, downloadDir, verRange, downMethod, options)
)
proc echoPackageVersions*(pkg: Package) = proc echoPackageVersions*(pkg: Package) =
let downMethod = pkg.downloadMethod.getDownloadMethod() let downMethod = pkg.downloadMethod.getDownloadMethod()
case downMethod case downMethod

View file

@ -27,15 +27,16 @@ type
actionNil, actionRefresh, actionInit, actionDump, actionPublish, actionNil, actionRefresh, actionInit, actionDump, actionPublish,
actionInstall, actionSearch, actionInstall, actionSearch,
actionList, actionBuild, actionPath, actionUninstall, actionCompile, actionList, actionBuild, actionPath, actionUninstall, actionCompile,
actionDoc, actionCustom, actionTasks actionDoc, actionCustom, actionTasks, actionDevelop
Action* = object Action* = object
case typ*: ActionType case typ*: ActionType
of actionNil, actionList, actionPublish, actionTasks: nil of actionNil, actionList, actionPublish, actionTasks: nil
of actionRefresh: of actionRefresh:
optionalURL*: string # Overrides default package list. optionalURL*: string # Overrides default package list.
of actionInstall, actionPath, actionUninstall: of actionInstall, actionPath, actionUninstall, actionDevelop:
packages*: seq[PkgTuple] # Optional only for actionInstall. packages*: seq[PkgTuple] # Optional only for actionInstall
# and actionDevelop.
of actionSearch: of actionSearch:
search*: seq[string] # Search string. search*: seq[string] # Search string.
of actionInit, actionDump: of actionInit, actionDump:
@ -56,6 +57,9 @@ 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.
develop [pkgname, ...] Clones a list of packages for development.
Symlinks the cloned packages or any package
in the current working directory.
init [pkgname] Initializes a new Nimble project. init [pkgname] Initializes a new Nimble project.
publish Publishes a package on nim-lang/packages. publish Publishes a package on nim-lang/packages.
The current working directory needs to be the The current working directory needs to be the
@ -141,6 +145,8 @@ proc parseActionType*(action: string): ActionType =
result = actionPublish result = actionPublish
of "tasks": of "tasks":
result = actionTasks result = actionTasks
of "develop":
result = actionDevelop
else: else:
result = actionCustom result = actionCustom
@ -149,7 +155,7 @@ 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: of actionInstall, actionPath, actionDevelop, actionUninstall:
options.action.packages = @[] options.action.packages = @[]
of actionCompile, actionDoc, actionBuild: of actionCompile, actionDoc, actionBuild:
options.action.compileOptions = @[] options.action.compileOptions = @[]
@ -164,8 +170,6 @@ proc initAction*(options: var Options, key: string) =
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 = @[]

View file

@ -497,6 +497,11 @@ proc iterInstallFiles*(realDir: string, pkgInfo: PackageInfo,
action(file) action(file)
proc getPkgDest*(pkgInfo: PackageInfo, options: Options): string =
let versionStr = '-' & pkgInfo.specialVersion
let pkgDestDir = options.getPkgsDir() / (pkgInfo.name & versionStr)
return pkgDestDir
when isMainModule: when isMainModule:
doAssert getNameVersion("/home/user/.nimble/libs/packagea-0.1") == doAssert getNameVersion("/home/user/.nimble/libs/packagea-0.1") ==
("packagea", "0.1") ("packagea", "0.1")

View file

@ -141,3 +141,20 @@ 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 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_"
when defined(windows):
proc GetCurrentProcessId(): int32 {.stdcall, dynlib: "kernel32",
importc: "GetCurrentProcessId".}
result.add($GetCurrentProcessId())
else:
result.add($getpid())

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -460,4 +460,41 @@ test "can pass args with spaces to Nim (#351)":
" -d:myVar=\"string with spaces\"" & " -d:myVar=\"string with spaces\"" &
" binaryPackage") " binaryPackage")
checkpoint output checkpoint output
check exitCode == QuitSuccess check exitCode == QuitSuccess
suite "develop feature":
test "can reject binary packages":
cd "develop/binary":
let (output, exitCode) = execNimble("develop")
checkpoint output
check output.processOutput.inLines("cannot develop packages")
check exitCode == QuitFailure
test "can develop hybrid":
cd "develop/hybrid":
let (output, exitCode) = execNimble("develop")
checkpoint output
check output.processOutput.inLines("will not be compiled")
check exitCode == QuitSuccess
let path = installDir / "pkgs" / "hybrid-#head" / "hybrid.nimble-link"
check fileExists(path)
let split = readFile(path).splitLines()
check split.len == 2
check split[0].endsWith("develop/hybrid/hybrid.nimble")
check split[1].endsWith("develop/hybrid")
test "can develop with srcDir":
cd "develop/srcdirtest":
let (output, exitCode) = execNimble("develop")
checkpoint output
check(not output.processOutput.inLines("will not be compiled"))
check exitCode == QuitSuccess
let path = installDir / "pkgs" / "srcdirtest-#head" /
"srcdirtest.nimble-link"
check fileExists(path)
let split = readFile(path).splitLines()
check split.len == 2
check split[0].endsWith("develop/srcdirtest/srcdirtest.nimble")
check split[1].endsWith("develop/srcdirtest/src")