diff --git a/src/nimblepkg/declarativeparser.nim b/src/nimblepkg/declarativeparser.nim index 13ea08956..267f268bb 100644 --- a/src/nimblepkg/declarativeparser.nim +++ b/src/nimblepkg/declarativeparser.nim @@ -683,6 +683,10 @@ proc toRequiresInfo*(pkgInfo: PackageInfo, options: Options, nimBin: Option[stri display("Warning", msg, Warning, HighPriority) raise newNimbleError[BabelPackageError](msg) + # Skip re-parsing installed packages that have cached requires in metadata + if pkgInfo.source == psInstalled and pkgInfo.requires.len > 0 and pkgInfo.infoKind >= pikRequires: + return pkgInfo + let nimbleFileInfo = nimbleFileInfo.get(extractRequiresInfo(pkgInfo.myPath, options)) result.requires = getRequires(nimbleFileInfo, result.activeFeatures) if pkgInfo.basicInfo.name.isNim: diff --git a/src/nimblepkg/install.nim b/src/nimblepkg/install.nim index 3361d2419..0da84bc09 100644 --- a/src/nimblepkg/install.nim +++ b/src/nimblepkg/install.nim @@ -322,6 +322,26 @@ proc installFromDirDownloadInfo(nimBin: Option[string], downloadDir: string, url if pv.ver.kind == verSpecial: pkgInfo.metadata.specialVersions.incl pv.ver.spe + # Cache parsed requires/features to avoid re-parsing installed packages at runtime + let nimbleFileInfo = extractRequiresInfo(pkgInfo.myPath, options) + proc serializeRequires(dep: PkgTuple): string = + ## Serializes a PkgTuple to a string that parseRequires can parse. + ## Matches toJsonHook behavior: no space for verSpecial, space for others. + case dep.ver.kind + of verAny: dep.name + of verSpecial: dep.name & $dep.ver + else: dep.name & " " & $dep.ver + pkgInfo.metaData.requires = pkgInfo.requires.map(serializeRequires) + for feature, reqs in pkgInfo.features: + pkgInfo.metaData.features[feature] = reqs.map(serializeRequires) + pkgInfo.metaData.srcDir = pkgInfo.srcDir + pkgInfo.metaData.paths = pkgInfo.paths + for hook in pkgInfo.preHooks: + pkgInfo.metaData.preHooks.add hook + for hook in pkgInfo.postHooks: + pkgInfo.metaData.postHooks.add hook + pkgInfo.metaData.nestedRequires = nimbleFileInfo.nestedRequires + saveMetaData(pkgInfo.metaData, pkgDestDir) # Run after-install hook diff --git a/src/nimblepkg/packageinfo.nim b/src/nimblepkg/packageinfo.nim index 5e2785384..4c49cefbe 100644 --- a/src/nimblepkg/packageinfo.nim +++ b/src/nimblepkg/packageinfo.nim @@ -3,7 +3,7 @@ # Stdlib imports import system except TResult -import hashes, strutils, os, sets, tables, times, httpclient, strformat +import hashes, strutils, os, sets, tables, times, httpclient, strformat, sequtils from net import SslError import zippy @@ -304,6 +304,25 @@ proc getInstalledPackageMin*(options: Options, pkgDir, nimbleFilePath: string): fillMetaData(result, pkgDir, true, options) except MetaDataError: discard + + # Populate cached requires/features from metadata to avoid re-parsing + if result.metaData.requires.len > 0: + try: + result.requires = result.metaData.requires.map(parseRequires) + for feature, reqs in result.metaData.features: + result.features[feature] = reqs.map(parseRequires) + result.srcDir = result.metaData.srcDir + result.paths = result.metaData.paths + for hook in result.metaData.preHooks: + result.preHooks.incl hook + for hook in result.metaData.postHooks: + result.postHooks.incl hook + result.infoKind = if result.metaData.nestedRequires: pikFull else: pikRequires + except NimbleError: + # Corrupted or old-format cache data; ignore and let normal parsing handle it + result.requires = @[] + result.features = initTable[string, seq[PkgTuple]]() + result.infoKind = pikMinimal proc getInstalledPkgsMin*(libsDir: string, options: Options): seq[PackageInfo] = ## Gets a list of installed packages. The resulting package info is diff --git a/src/nimblepkg/packageinfotypes.nim b/src/nimblepkg/packageinfotypes.nim index 742c3a1b6..ae6f671e1 100644 --- a/src/nimblepkg/packageinfotypes.nim +++ b/src/nimblepkg/packageinfotypes.nim @@ -35,6 +35,17 @@ type # Special versions are aliases with which a single package can be # referred. For example a package can be versions `0.1.0`, `#head` and # `#master` at the same time. + # Cached fields from declarative/VM parser to avoid re-parsing installed packages + requires*: seq[string] + # String-form requires as extracted by the declarative parser (or VM fallback) + features*: Table[string, seq[string]] + # String-form features as extracted by the declarative parser + srcDir*: string + paths*: seq[string] + preHooks*: seq[string] + postHooks*: seq[string] + nestedRequires*: bool + # True if the package has requires nested in control flow (needs VM parser) PackageBasicInfo* = tuple name: string diff --git a/src/nimblepkg/packagemetadatafile.nim b/src/nimblepkg/packagemetadatafile.nim index ca8260b5f..bd899a427 100644 --- a/src/nimblepkg/packagemetadatafile.nim +++ b/src/nimblepkg/packagemetadatafile.nim @@ -1,7 +1,7 @@ # Copyright (C) Dominik Picheta. All rights reserved. # BSD License. Look at license.txt for more info. -import os, strformat, sets, sequtils +import os, strformat, sets, sequtils, tables import common, version, packageinfotypes, cli, tools, sha1hashes, options import compat/json @@ -42,6 +42,58 @@ proc initFromJson(specialVersions: var HashSet[Version], jsonNode: JsonNode, else: assert false, "The `jsonNode` must be of kind JArray." +proc `%`(features: Table[string, seq[string]]): JsonNode = + result = newJObject() + for k, v in features: + result[k] = %v + +proc initFromJson(features: var Table[string, seq[string]], jsonNode: JsonNode, + jsonPath: var string) = + features = initTable[string, seq[string]]() + if jsonNode.kind == JObject: + for k, v in jsonNode: + var seqVal: seq[string] + if v.kind == JArray: + for item in v: + if item.kind == JString: + seqVal.add item.str + features[k] = seqVal + +proc initFromJson(dst: var PackageMetaData, jsonNode: JsonNode, jsonPath: var string) = + ## Custom initFromJson that tolerates missing fields for backward compatibility. + if jsonNode.kind != JObject: return + for key, val in jsonNode: + case key + of "url": dst.url = val.getStr + of "downloadMethod": + if val.kind == JString: + case val.str + of "git": dst.downloadMethod = git + of "hg": dst.downloadMethod = hg + of "vcsRevision": initFromJson(dst.vcsRevision, val, jsonPath) + of "files": + if val.kind == JArray: + for item in val: dst.files.add item.getStr + of "binaries": + if val.kind == JArray: + for item in val: dst.binaries.add item.getStr + of "specialVersions": initFromJson(dst.specialVersions, val, jsonPath) + of "requires": + if val.kind == JArray: + for item in val: dst.requires.add item.getStr + of "features": initFromJson(dst.features, val, jsonPath) + of "srcDir": dst.srcDir = val.getStr + of "paths": + if val.kind == JArray: + for item in val: dst.paths.add item.getStr + of "preHooks": + if val.kind == JArray: + for item in val: dst.preHooks.add item.getStr + of "postHooks": + if val.kind == JArray: + for item in val: dst.postHooks.add item.getStr + of "nestedRequires": dst.nestedRequires = val.getBool + proc saveMetaData*(metaData: PackageMetaData, dirName: string, changeRoots = true) = ## Saves some important data to file in the package installation directory.