Implements basic support for building with .nimble-link'ed packages.

This commit is contained in:
Dominik Picheta 2017-08-16 22:22:00 +01:00
commit 97dc0ffb45
8 changed files with 90 additions and 31 deletions

View file

@ -4,7 +4,7 @@
import system except TResult import system except TResult
import httpclient, parseopt, os, osproc, pegs, tables, parseutils, import httpclient, parseopt, os, osproc, pegs, tables, parseutils,
strtabs, json, algorithm, sets, uri strtabs, json, algorithm, sets, uri, future, sequtils
import strutils except toLower import strutils except toLower
from unicode import toLower from unicode import toLower
@ -198,11 +198,12 @@ proc removeRevDep(options: Options, pkg: PackageInfo) =
proc install(packages: seq[PkgTuple], proc install(packages: seq[PkgTuple],
options: Options, options: Options,
doPrompt = true): tuple[paths: seq[string], pkg: PackageInfo] doPrompt = true): tuple[deps: seq[PackageInfo], pkg: PackageInfo]
proc processDeps(pkginfo: PackageInfo, options: Options): seq[string] = proc processDeps(pkginfo: PackageInfo, options: Options): seq[PackageInfo] =
## Verifies and installs dependencies. ## Verifies and installs dependencies.
## ##
## Returns the list of paths to pass to the compiler during build phase. ## Returns the list of PackageInfo (for paths) to pass to the compiler
## during build phase.
result = @[] result = @[]
assert(not pkginfo.isMinimal, "processDeps needs pkginfo.requires") assert(not pkginfo.isMinimal, "processDeps needs pkginfo.requires")
display("Verifying", display("Verifying",
@ -233,8 +234,8 @@ proc processDeps(pkginfo: PackageInfo, options: Options): seq[string] =
if not found: if not found:
display("Installing", $resolvedDep, priority = HighPriority) display("Installing", $resolvedDep, priority = HighPriority)
let toInstall = @[(resolvedDep.name, resolvedDep.ver)] let toInstall = @[(resolvedDep.name, resolvedDep.ver)]
let (paths, installedPkg) = install(toInstall, options) let (pkgs, installedPkg) = install(toInstall, options)
result.add(paths) result.add(pkgs)
pkg = installedPkg # For addRevDep pkg = installedPkg # For addRevDep
@ -243,7 +244,12 @@ proc processDeps(pkginfo: PackageInfo, options: Options): seq[string] =
else: else:
display("Info:", "Dependency on $1 already satisfied" % $dep, display("Info:", "Dependency on $1 already satisfied" % $dep,
priority = HighPriority) priority = HighPriority)
result.add(pkg.mypath.splitFile.dir) if pkg.isLinked:
# TODO (#393): This can be optimised since the .nimble-link files have
# a secondary line that specifies the srcDir.
pkg = pkg.toFullInfo(options)
result.add(pkg)
# Process the dependencies of this dependency. # Process the dependencies of this dependency.
result.add(processDeps(pkg.toFullInfo(options), options)) result.add(processDeps(pkg.toFullInfo(options), options))
reverseDeps.add((pkg.name, pkg.specialVersion)) reverseDeps.add((pkg.name, pkg.specialVersion))
@ -251,8 +257,7 @@ proc processDeps(pkginfo: PackageInfo, options: Options): seq[string] =
# Check if two packages of the same name (but different version) are listed # Check if two packages of the same name (but different version) are listed
# in the path. # in the path.
var pkgsInPath: StringTableRef = newStringTable(modeCaseSensitive) var pkgsInPath: StringTableRef = newStringTable(modeCaseSensitive)
for p in result: for pkgInfo in result:
let pkgInfo = getPkgInfo(p, options)
if pkgsInPath.hasKey(pkgInfo.name) and if pkgsInPath.hasKey(pkgInfo.name) and
pkgsInPath[pkgInfo.name] != pkgInfo.version: pkgsInPath[pkgInfo.name] != pkgInfo.version:
raise newException(NimbleError, raise newException(NimbleError,
@ -267,7 +272,8 @@ proc processDeps(pkginfo: PackageInfo, options: Options): seq[string] =
for i in reverseDeps: for i in reverseDeps:
addRevDep(options, i, pkginfo) addRevDep(options, i, pkginfo)
proc buildFromDir(pkgInfo: PackageInfo, paths: seq[string], args: var seq[string]) = proc buildFromDir(pkgInfo: PackageInfo, paths: seq[string],
args: var seq[string]) =
## Builds a package as specified by ``pkgInfo``. ## Builds a package as specified by ``pkgInfo``.
if pkgInfo.bin.len == 0: if pkgInfo.bin.len == 0:
raise newException(NimbleError, raise newException(NimbleError,
@ -369,7 +375,10 @@ proc vcsRevisionInDir(dir: string): string =
discard discard
proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, proc installFromDir(dir: string, requestedVer: VersionRange, options: Options,
url: string): tuple[paths: seq[string], pkg: PackageInfo] = url: string): tuple[
deps: seq[PackageInfo],
pkg: PackageInfo
] =
## Returns where package has been installed to, together with paths ## Returns where package has been installed to, together with paths
## to the packages this package depends on. ## to the packages this package depends on.
## The return value of this function is used by ## The return value of this function is used by
@ -385,7 +394,7 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options,
pkgInfo.specialVersion = $requestedVer.spe pkgInfo.specialVersion = $requestedVer.spe
# Dependencies need to be processed before the creation of the pkg dir. # Dependencies need to be processed before the creation of the pkg dir.
result.paths = processDeps(pkgInfo, depsOptions) result.deps = processDeps(pkgInfo, depsOptions)
if options.depsOnly: if options.depsOnly:
result.pkg = pkgInfo result.pkg = pkgInfo
@ -396,7 +405,9 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options,
# Build before removing an existing package (if one exists). This way # Build before removing an existing package (if one exists). This way
# if the build fails then the old package will still be installed. # if the build fails then the old package will still be installed.
if pkgInfo.bin.len > 0: buildFromDir(pkgInfo, result.paths, true) if pkgInfo.bin.len > 0:
let paths = result.deps.map(dep => dep.getRealDir())
buildFromDir(pkgInfo, paths, true)
let pkgDestDir = pkgInfo.getPkgDest(options) let pkgDestDir = pkgInfo.getPkgDest(options)
if existsDir(pkgDestDir) and existsFile(pkgDestDir / "nimblemeta.json"): if existsDir(pkgDestDir) and existsFile(pkgDestDir / "nimblemeta.json"):
@ -462,8 +473,8 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options,
# processDeps). # processDeps).
saveNimbleData(options) saveNimbleData(options)
# Return the paths to the dependencies of this package. # Return the dependencies of this package (mainly for paths).
result.paths.add pkgDestDir result.deps.add pkgInfo
result.pkg = pkgInfo result.pkg = pkgInfo
result.pkg.isInstalled = true result.pkg.isInstalled = true
result.pkg.myPath = dest result.pkg.myPath = dest
@ -496,7 +507,7 @@ proc getDownloadInfo*(pv: PkgTuple, options: Options,
proc install(packages: seq[PkgTuple], proc install(packages: seq[PkgTuple],
options: Options, options: Options,
doPrompt = true): tuple[paths: seq[string], pkg: PackageInfo] = doPrompt = true): tuple[deps: seq[PackageInfo], pkg: PackageInfo] =
if packages == @[]: if packages == @[]:
result = installFromDir(getCurrentDir(), newVRAny(), options, "") result = installFromDir(getCurrentDir(), newVRAny(), options, "")
else: else:
@ -530,7 +541,8 @@ proc install(packages: seq[PkgTuple],
proc build(options: Options) = proc build(options: Options) =
var pkgInfo = getPkgInfo(getCurrentDir(), options) var pkgInfo = getPkgInfo(getCurrentDir(), options)
nimScriptHint(pkgInfo) nimScriptHint(pkgInfo)
let paths = processDeps(pkginfo, options) let deps = processDeps(pkginfo, options)
let paths = deps.map(dep => dep.getRealDir())
var args = options.action.compileOptions var args = options.action.compileOptions
buildFromDir(pkgInfo, paths, args) buildFromDir(pkgInfo, paths, args)
@ -544,10 +556,10 @@ proc execBackend(options: Options) =
var pkgInfo = getPkgInfo(getCurrentDir(), options) var pkgInfo = getPkgInfo(getCurrentDir(), options)
nimScriptHint(pkgInfo) nimScriptHint(pkgInfo)
let paths = processDeps(pkginfo, options) let deps = processDeps(pkginfo, options)
var args = "" var args = ""
for path in paths: args.add("--path:\"" & path & "\" ") for dep in deps: args.add("--path:\"" & dep.getRealDir() & "\" ")
for option in options.action.compileOptions: for option in options.action.compileOptions:
args.add("\"" & option & "\" ") args.add("\"" & option & "\" ")

View file

@ -18,6 +18,7 @@ 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

View file

@ -66,7 +66,7 @@ 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 == ".nimble" or pkgPath.splitFile.ext == ".babel": if pkgPath.splitFile.ext in [".nimble", ".nimble-link", ".babel"]:
return getNameVersion(pkgPath.splitPath.head) return getNameVersion(pkgPath.splitPath.head)
result.name = "" result.name = ""
@ -281,7 +281,7 @@ proc findNimbleFile*(dir: string; error: bool): string =
if kind in {pcFile, pcLinkToFile}: if kind in {pcFile, pcLinkToFile}:
let ext = path.splitFile.ext let ext = path.splitFile.ext
case ext case ext
of ".babel", ".nimble": of ".babel", ".nimble", ".nimble-link":
result = path result = path
inc hits inc hits
else: discard else: discard
@ -293,8 +293,16 @@ proc findNimbleFile*(dir: string; error: bool): string =
raise newException(NimbleError, raise newException(NimbleError,
"Specified directory does not contain a .nimble file.") "Specified directory does not contain a .nimble file.")
else: else:
display("Warning:", "No .nimble file found for " & dir, Warning, display("Warning:", "No .nimble or .nimble-link file found for " &
HighPriority) dir, Warning, HighPriority)
if result.splitFile.ext == ".nimble-link":
# Return the path of the real .nimble file.
let lines = readFile(result).splitLines()
result = lines[0]
if not fileExists(result):
raiseNimbleError("The .nimble-link file is pointing to a missing" &
" file: " & result)
proc getInstalledPkgsMin*(libsDir: string, options: Options): proc getInstalledPkgsMin*(libsDir: string, options: Options):
seq[tuple[pkginfo: PackageInfo, meta: MetaData]] = seq[tuple[pkginfo: PackageInfo, meta: MetaData]] =
@ -309,13 +317,15 @@ proc getInstalledPkgsMin*(libsDir: string, options: Options):
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(nimbleFile) let (name, version) = getNameVersion(path)
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
pkg.isLinked =
cmpPaths(nimbleFile.splitFile().dir, path) != 0
result.add((pkg, meta)) result.add((pkg, meta))
proc withinRange*(pkgInfo: PackageInfo, verRange: VersionRange): bool = proc withinRange*(pkgInfo: PackageInfo, verRange: VersionRange): bool =
@ -369,7 +379,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: if pkgInfo.srcDir != "" and (not pkgInfo.isInstalled or pkgInfo.isLinked):
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
@ -515,6 +525,8 @@ when isMainModule:
("package", "#head") ("package", "#head")
doAssert getNameVersion("/home/user/.nimble/libs/package-#branch-with-dashes") == doAssert getNameVersion("/home/user/.nimble/libs/package-#branch-with-dashes") ==
("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

@ -292,6 +292,11 @@ 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)
@ -386,6 +391,8 @@ 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 =
@ -393,7 +400,9 @@ 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:
return getPkgInfoFromFile(pkg.mypath, options) result = getPkgInfoFromFile(pkg.mypath, options)
result.isInstalled = pkg.isInstalled
result.isLinked = pkg.isLinked
else: else:
return pkg return pkg

View file

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

View file

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

View file

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

View file

@ -24,10 +24,11 @@ test "can compile with --os:windows":
template cd*(dir: string, body: untyped) = template cd*(dir: string, body: untyped) =
## Sets the current dir to ``dir``, executes ``body`` and restores the ## Sets the current dir to ``dir``, executes ``body`` and restores the
## previous working dir. ## previous working dir.
let lastDir = getCurrentDir() block:
setCurrentDir(dir) let lastDir = getCurrentDir()
body setCurrentDir(dir)
setCurrentDir(lastDir) body
setCurrentDir(lastDir)
proc execNimble(args: varargs[string]): tuple[output: string, exitCode: int] = proc execNimble(args: varargs[string]): tuple[output: string, exitCode: int] =
var quotedArgs = @args var quotedArgs = @args
@ -501,3 +502,9 @@ suite "develop feature":
check split.len == 2 check split.len == 2
check split[0].endsWith("develop/srcdirtest/srcdirtest.nimble") check split[0].endsWith("develop/srcdirtest/srcdirtest.nimble")
check split[1].endsWith("develop/srcdirtest/src") check split[1].endsWith("develop/srcdirtest/src")
cd "develop/dependent":
let (output, exitCode) = execNimble("c", "-r", "src/dependent")
checkpoint output
check(output.processOutput.inLines("hello"))
check exitCode == QuitSuccess