Implements #144. Warnings are now issued for incorrect namespacing.

This commit is contained in:
Dominik Picheta 2016-12-26 18:09:19 +00:00
commit f29a25a10d
3 changed files with 206 additions and 30 deletions

View file

@ -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]

View file

@ -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")

View file

@ -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