Implements #144. Warnings are now issued for incorrect namespacing.
This commit is contained in:
parent
380cb46da8
commit
f29a25a10d
3 changed files with 206 additions and 30 deletions
|
|
@ -163,7 +163,7 @@ proc copyFilesRec(origDir, currentDir, dest: string,
|
|||
if options.prompt("Missing file " & src & ". Continue?"):
|
||||
continue
|
||||
else:
|
||||
quit(QuitSuccess)
|
||||
raise NimbleQuit(msg: "")
|
||||
createDir(dest / file.splitFile.dir)
|
||||
result.incl copyFileD(src, dest / file)
|
||||
|
||||
|
|
@ -174,7 +174,7 @@ proc copyFilesRec(origDir, currentDir, dest: string,
|
|||
if options.prompt("Missing directory " & src & ". Continue?"):
|
||||
continue
|
||||
else:
|
||||
quit(QuitSuccess)
|
||||
raise NimbleQuit(msg: "")
|
||||
result.incl copyDirD(origDir / dir, dest / dir)
|
||||
|
||||
result.incl copyWithExt(origDir, currentDir, dest, pkgInfo)
|
||||
|
|
@ -441,15 +441,23 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options,
|
|||
else:
|
||||
removeFile(binDir / bin)
|
||||
|
||||
## Will contain a list of files which have been installed.
|
||||
var filesInstalled: HashSet[string]
|
||||
|
||||
createDir(pkgDestDir)
|
||||
# Copy this package's files based on the preferences specified in PkgInfo.
|
||||
var filesInstalled = initSet[string]()
|
||||
for file in getInstallFiles(realDir, pkgInfo, options):
|
||||
createDir(changeRoot(realDir, pkgDestDir, file.splitFile.dir))
|
||||
let dest = changeRoot(realDir, pkgDestDir, file)
|
||||
filesInstalled.incl copyFileD(file, dest)
|
||||
|
||||
# Copy the .nimble file.
|
||||
let dest = changeRoot(pkgInfo.mypath.splitFile.dir, pkgDestDir,
|
||||
pkgInfo.mypath)
|
||||
filesInstalled.incl copyFileD(pkgInfo.mypath, dest)
|
||||
|
||||
if pkgInfo.bin.len > 0:
|
||||
# Make sure ~/.nimble/bin directory is created.
|
||||
createDir(binDir)
|
||||
# Copy all binaries and files that are not skipped
|
||||
filesInstalled = copyFilesRec(realDir, realDir, pkgDestDir, options,
|
||||
pkgInfo)
|
||||
# Set file permissions to +x for all binaries built,
|
||||
# and symlink them on *nix OS' to $nimbleDir/bin/
|
||||
for bin in pkgInfo.bin:
|
||||
|
|
@ -495,9 +503,6 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options,
|
|||
writeFile(bashDest, "\"" & pkgDestDir / bin & "\" \"$@\"\n")
|
||||
else:
|
||||
{.error: "Sorry, your platform is not supported.".}
|
||||
else:
|
||||
filesInstalled = copyFilesRec(realDir, realDir, pkgDestDir, options,
|
||||
pkgInfo)
|
||||
|
||||
let vcsRevision = vcsRevisionInDir(realDir)
|
||||
|
||||
|
|
@ -776,9 +781,7 @@ proc join(x: seq[PkgTuple]; y: string): string =
|
|||
result.add x[i][0] & " " & $x[i][1]
|
||||
|
||||
proc dump(options: Options) =
|
||||
let proj = addFileExt(options.action.projName, "nimble")
|
||||
let p = if fileExists(proj): readPackageInfo(proj, options)
|
||||
else: getPkgInfo(os.getCurrentDir(), options)
|
||||
let p = getPkgInfo(os.getCurrentDir(), options)
|
||||
echo "name: ", p.name.escape
|
||||
echo "version: ", p.version.escape
|
||||
echo "author: ", p.author.escape
|
||||
|
|
@ -925,7 +928,7 @@ proc uninstall(options: Options) =
|
|||
|
||||
# removeRevDep needs the package dependency info, so we can't just pass
|
||||
# a minimal pkg info.
|
||||
let pkgFull = readPackageInfo(pkg.mypath, options, false)
|
||||
let pkgFull = getPkgInfo(pkg.mypath.splitFile.dir, options) # TODO: Simplify
|
||||
removeRevDep(options, pkgFull)
|
||||
removePkgDir(options.getPkgsDir / (pkg.name & '-' & pkg.version), options)
|
||||
display("Removed", "$1 ($2)" % [pkg.name, $pkg.version], Success,
|
||||
|
|
@ -943,7 +946,7 @@ proc execHook(options: Options, before: bool): bool =
|
|||
nimbleFile = findNimbleFile(getCurrentDir(), true)
|
||||
except NimbleError: return true
|
||||
# PackageInfos are cached so we can read them as many times as we want.
|
||||
let pkgInfo = readPackageInfo(nimbleFile, options)
|
||||
let pkgInfo = getPkgInfo(nimbleFile.splitFile.dir, options) # TODO: Simplify
|
||||
let actionName =
|
||||
if options.action.typ == actionCustom: options.action.command
|
||||
else: ($options.action.typ)[6 .. ^1]
|
||||
|
|
|
|||
|
|
@ -228,8 +228,7 @@ proc findAllPkgs*(pkglist: seq[tuple[pkginfo: PackageInfo, meta: MetaData]],
|
|||
result.add pkg.pkginfo
|
||||
|
||||
proc getRealDir*(pkgInfo: PackageInfo): string =
|
||||
## Returns the ``pkgInfo.srcDir`` or the .mypath directory if package does
|
||||
## not specify the src dir.
|
||||
## Returns the directory containing the package source files.
|
||||
if pkgInfo.srcDir != "" and not pkgInfo.isInstalled:
|
||||
result = pkgInfo.mypath.splitFile.dir / pkgInfo.srcDir
|
||||
else:
|
||||
|
|
@ -280,6 +279,104 @@ proc validatePackagesList*(path: string): bool =
|
|||
except ValueError, JsonParsingError:
|
||||
return false
|
||||
|
||||
proc checkInstallFile(pkgInfo: PackageInfo,
|
||||
origDir, file: string): bool =
|
||||
## Checks whether ``file`` should be installed.
|
||||
## ``True`` means file should be skipped.
|
||||
|
||||
for ignoreFile in pkgInfo.skipFiles:
|
||||
if ignoreFile.endswith("nimble"):
|
||||
raise newException(NimbleError, ignoreFile & " must be installed.")
|
||||
if samePaths(file, origDir / ignoreFile):
|
||||
result = true
|
||||
break
|
||||
|
||||
for ignoreExt in pkgInfo.skipExt:
|
||||
if file.splitFile.ext == ('.' & ignoreExt):
|
||||
result = true
|
||||
break
|
||||
|
||||
if file.splitFile().name[0] == '.': result = true
|
||||
|
||||
proc checkInstallDir(pkgInfo: PackageInfo,
|
||||
origDir, dir: string): bool =
|
||||
## Determines whether ``dir`` should be installed.
|
||||
## ``True`` means dir should be skipped.
|
||||
for ignoreDir in pkgInfo.skipDirs:
|
||||
if samePaths(dir, origDir / ignoreDir):
|
||||
result = true
|
||||
break
|
||||
|
||||
let thisDir = splitPath(dir).tail
|
||||
assert thisDir != ""
|
||||
if thisDir[0] == '.': result = true
|
||||
if thisDir == "nimcache": result = true
|
||||
|
||||
proc findWithExt(dir: string, pkgInfo: PackageInfo): seq[string] =
|
||||
## Returns the filenames of the files that should be copied.
|
||||
result = @[]
|
||||
for kind, path in walkDir(dir):
|
||||
if kind == pcDir:
|
||||
result.add findWithExt(path, pkgInfo)
|
||||
else:
|
||||
if path.splitFile.ext[1 .. ^1] in pkgInfo.installExt:
|
||||
result.add path
|
||||
|
||||
proc getFilesInDir(dir: string): seq[string] =
|
||||
## Returns a list of paths to files inside the specified directory and any
|
||||
## subdirectories that are in it.
|
||||
result = @[]
|
||||
for kind, path in walkDir(dir):
|
||||
if kind == pcDir:
|
||||
result.add getFilesInDir(path)
|
||||
else:
|
||||
result.add path
|
||||
|
||||
proc getInstallFiles*(realDir: string, pkgInfo: PackageInfo,
|
||||
options: Options): seq[string] =
|
||||
## Returns a list of files within the ``realDir`` that should be installed.
|
||||
result = @[]
|
||||
let whitelistMode =
|
||||
pkgInfo.installDirs.len != 0 or
|
||||
pkgInfo.installFiles.len != 0 or
|
||||
pkgInfo.installExt.len != 0
|
||||
if whitelistMode:
|
||||
for file in pkgInfo.installFiles:
|
||||
let src = realDir / file
|
||||
if not src.existsFile():
|
||||
if options.prompt("Missing file " & src & ". Continue?"):
|
||||
continue
|
||||
else:
|
||||
raise NimbleQuit(msg: "")
|
||||
result.add src
|
||||
|
||||
for dir in pkgInfo.installDirs:
|
||||
# TODO: Allow skipping files inside dirs?
|
||||
let src = realDir / dir
|
||||
if not src.existsDir():
|
||||
if options.prompt("Missing directory " & src & ". Continue?"):
|
||||
continue
|
||||
else:
|
||||
raise NimbleQuit(msg: "")
|
||||
|
||||
result.add getFilesInDir(src)
|
||||
|
||||
result.add findWithExt(realDir, pkgInfo)
|
||||
else:
|
||||
for kind, file in walkDir(realDir):
|
||||
if kind == pcDir:
|
||||
let skip = pkgInfo.checkInstallDir(realDir, file)
|
||||
|
||||
if skip: continue
|
||||
|
||||
result.add getInstallFiles(file, pkgInfo, options)
|
||||
else:
|
||||
let skip = pkgInfo.checkInstallFile(realDir, file)
|
||||
|
||||
if skip: continue
|
||||
|
||||
result.add file
|
||||
|
||||
when isMainModule:
|
||||
doAssert getNameVersion("/home/user/.nimble/libs/packagea-0.1") ==
|
||||
("packagea", "0.1")
|
||||
|
|
|
|||
|
|
@ -14,13 +14,18 @@ type
|
|||
|
||||
ValidationError* = object of NimbleError
|
||||
warnInstalled*: bool # Determines whether to show a warning for installed pkgs
|
||||
warnAll*: bool
|
||||
|
||||
proc newValidationError(msg: string, warnInstalled: bool): ref ValidationError =
|
||||
proc newValidationError(msg: string, warnInstalled: bool,
|
||||
hint: string, warnAll: bool): ref ValidationError =
|
||||
result = newException(ValidationError, msg)
|
||||
result.warnInstalled = warnInstalled
|
||||
result.warnAll = warnAll
|
||||
result.hint = hint
|
||||
|
||||
proc raiseNewValidationError(msg: string, warnInstalled: bool) =
|
||||
raise newValidationError(msg, warnInstalled)
|
||||
proc raiseNewValidationError(msg: string, warnInstalled: bool,
|
||||
hint: string = "", warnAll = false) =
|
||||
raise newValidationError(msg, warnInstalled, hint, warnAll)
|
||||
|
||||
proc validatePackageName*(name: string) =
|
||||
## Raises an error if specified package name contains invalid characters.
|
||||
|
|
@ -49,6 +54,10 @@ proc validatePackageName*(name: string) =
|
|||
else:
|
||||
prevWasUnderscore = false
|
||||
|
||||
if name.endsWith("pkg"):
|
||||
raiseNewValidationError("\"$1\" is an invalid package name: cannot end" &
|
||||
" with \"pkg\"" % name, false)
|
||||
|
||||
proc validateVersion*(ver: string) =
|
||||
for c in ver:
|
||||
if c notin ({'.'} + Digits):
|
||||
|
|
@ -56,7 +65,57 @@ proc validateVersion*(ver: string) =
|
|||
"Version may only consist of numbers and the '.' character " &
|
||||
"but found '" & c & "'.", false)
|
||||
|
||||
proc validatePackageInfo(pkgInfo: PackageInfo, path: string) =
|
||||
proc validatePackageStructure(pkgInfo: PackageInfo, options: Options) =
|
||||
## This ensures that a package's source code does not leak into
|
||||
## another package's namespace.
|
||||
## https://github.com/nim-lang/nimble/issues/144
|
||||
let realDir = pkgInfo.getRealDir()
|
||||
for path in getInstallFiles(realDir, pkgInfo, options):
|
||||
# Remove the root to leave only the package subdirectories.
|
||||
# ~/package-0.1/package/utils.nim -> package/utils.nim.
|
||||
var trailPath = changeRoot(realDir, "", path)
|
||||
if trailPath.startsWith(DirSep): trailPath = trailPath[1 .. ^1]
|
||||
let (dir, file, ext) = trailPath.splitFile
|
||||
# We're only interested in nim files, because only they can pollute our
|
||||
# namespace.
|
||||
if ext != (ExtSep & "nim"):
|
||||
continue
|
||||
|
||||
if dir.len == 0:
|
||||
if file != pkgInfo.name:
|
||||
let msg = ("File inside package '$1' is outside of permitted " &
|
||||
"namespace, should be " &
|
||||
"named '$2' but was named '$3' instead. This will be an error" &
|
||||
" in the future.") %
|
||||
[pkgInfo.name, pkgInfo.name & ext, file & ext]
|
||||
let hint = ("Rename this file to '$1', move it into a '$2' " &
|
||||
"subdirectory, or 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, pkgInfo.name & DirSep, file & ext]
|
||||
raiseNewValidationError(msg, true, hint, true)
|
||||
else:
|
||||
assert(not pkgInfo.isMinimal)
|
||||
let correctDir =
|
||||
if pkgInfo.name in pkgInfo.bin:
|
||||
pkgInfo.name & "pkg"
|
||||
else:
|
||||
pkgInfo.name
|
||||
|
||||
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) =
|
||||
let path = pkgInfo.myPath
|
||||
if pkgInfo.name == "":
|
||||
raiseNewValidationError("Incorrect .nimble file: " & path &
|
||||
" does not contain a name field.", false)
|
||||
|
|
@ -83,7 +142,8 @@ proc validatePackageInfo(pkgInfo: PackageInfo, path: string) =
|
|||
raiseNewValidationError("'" & pkgInfo.backend &
|
||||
"' is an invalid backend.", false)
|
||||
|
||||
validateVersion(pkgInfo.version)
|
||||
validatePackageStructure(pkginfo, options)
|
||||
|
||||
|
||||
proc nimScriptHint*(pkgInfo: PackageInfo) =
|
||||
if not pkgInfo.isNimScript:
|
||||
|
|
@ -172,7 +232,7 @@ proc readPackageInfoFromNimble(path: string; result: var PackageInfo) =
|
|||
else:
|
||||
raise newException(ValueError, "Cannot open package info: " & path)
|
||||
|
||||
proc readPackageInfo*(nf: NimbleFile, options: Options,
|
||||
proc readPackageInfo(nf: NimbleFile, options: Options,
|
||||
onlyMinimalInfo=false): PackageInfo =
|
||||
## Reads package info from the specified Nimble file.
|
||||
##
|
||||
|
|
@ -182,7 +242,7 @@ proc readPackageInfo*(nf: NimbleFile, options: Options,
|
|||
## If both fail then returns an error.
|
||||
##
|
||||
## When ``onlyMinimalInfo`` is true, only the `name` and `version` fields are
|
||||
## populated. The isNimScript field can also be relied on.
|
||||
## populated. The ``isNimScript`` field can also be relied on.
|
||||
##
|
||||
## This version uses a cache stored in ``options``, so calling it multiple
|
||||
## times on the same ``nf`` shouldn't require re-evaluation of the Nimble
|
||||
|
|
@ -225,7 +285,8 @@ proc readPackageInfo*(nf: NimbleFile, options: Options,
|
|||
" " & getCurrentExceptionMsg() & "."
|
||||
raise newException(NimbleError, msg)
|
||||
|
||||
validatePackageInfo(result, nf)
|
||||
# Validate version ahead of time, we will be potentially overwriting it soon.
|
||||
validateVersion(result.version)
|
||||
|
||||
# The package directory name may include a "special" version
|
||||
# (example #head). If so, it is given higher priority and therefore
|
||||
|
|
@ -237,10 +298,21 @@ proc readPackageInfo*(nf: NimbleFile, options: Options,
|
|||
if not result.isMinimal:
|
||||
options.pkgInfoCache[nf] = result
|
||||
|
||||
# Validate the rest of the package info last.
|
||||
validatePackageInfo(result, options)
|
||||
|
||||
proc getPkgInfo*(dir: string, options: Options): PackageInfo =
|
||||
## Find the .nimble file in ``dir`` and parses it, returning a PackageInfo.
|
||||
let nimbleFile = findNimbleFile(dir, true)
|
||||
result = readPackageInfo(nimbleFile, options)
|
||||
try:
|
||||
result = readPackageInfo(nimbleFile, options)
|
||||
except ValidationError:
|
||||
let exc = (ref ValidationError)(getCurrentException())
|
||||
if exc.warnAll:
|
||||
display("Warning:", exc.msg, Warning, HighPriority)
|
||||
display("Hint:", exc.hint, Warning, HighPriority)
|
||||
else:
|
||||
raise
|
||||
|
||||
proc getInstalledPkgs*(libsDir: string, options: Options):
|
||||
seq[tuple[pkginfo: PackageInfo, meta: MetaData]] =
|
||||
|
|
@ -265,16 +337,17 @@ proc getInstalledPkgs*(libsDir: string, options: Options):
|
|||
let nimbleFile = findNimbleFile(path, false)
|
||||
if nimbleFile != "":
|
||||
let meta = readMetaData(path)
|
||||
var pkg: PackageInfo
|
||||
try:
|
||||
var pkg = readPackageInfo(nimbleFile, options, onlyMinimalInfo=false)
|
||||
pkg.isInstalled = true
|
||||
result.add((pkg, meta))
|
||||
pkg = readPackageInfo(nimbleFile, options, onlyMinimalInfo=false)
|
||||
except ValidationError:
|
||||
let exc = (ref ValidationError)(getCurrentException())
|
||||
exc.msg = createErrorMsg(validationErrorMsg, path, exc.msg)
|
||||
exc.hint = hintMsg % path
|
||||
if exc.warnInstalled:
|
||||
if exc.warnInstalled or exc.warnAll:
|
||||
display("Warning:", exc.msg, Warning, HighPriority)
|
||||
# Don't show hints here because they are only useful for package
|
||||
# owners.
|
||||
else:
|
||||
raise exc
|
||||
except:
|
||||
|
|
@ -284,6 +357,9 @@ proc getInstalledPkgs*(libsDir: string, options: Options):
|
|||
exc.hint = hintMsg % path
|
||||
raise exc
|
||||
|
||||
pkg.isInstalled = true
|
||||
result.add((pkg, meta))
|
||||
|
||||
proc isNimScript*(nf: string, options: Options): bool =
|
||||
result = readPackageInfo(nf, options).isNimScript
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue