From 2e0e4ef8808229a811a9a208713932317fdb0a43 Mon Sep 17 00:00:00 2001 From: Jake Leahy Date: Sat, 17 Dec 2022 15:10:35 +1100 Subject: [PATCH 01/14] Fix problem were URL's weren't being resolved This caused the lockfiles to ignore certain dependencies --- src/nimble.nim | 12 ++++++++---- src/nimblepkg/packageinfo.nim | 11 +++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/nimble.nim b/src/nimble.nim index e175d02c..d6aa74ad 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -635,15 +635,18 @@ proc processLockedDependencies(pkgInfo: PackageInfo, options: Options): let developModeDeps = getDevelopDependencies(pkgInfo, options) + # We need to resolve URL's into names + var pkgList {.global.}: seq[PackageInfo] = @[] + once: pkgList = initPkgList(pkgInfo, options) + # Build list of packages allowed for running. # This is to stop requirements from unrelated tasks # needing to be downloaded var allowedPackages: HashSet[string] - for requirement in pkgInfo.requires: - allowedPackages.incl requirement.name + for requirement in pkgInfo.requires & pkgInfo.taskRequires.getOrDefault(options.task): + let name = pkgList.resolveURL(requirement.name) + allowedPackages.incl name - for requirement in pkgInfo.taskRequires.getOrDefault(options.task): - allowedPackages.incl requirement.name for name, dep in pkgInfo.lockedDeps: if name in allowedPackages: @@ -1638,6 +1641,7 @@ proc lock(options: Options) = let dependencies = pkgInfo.processFreeDependencies(options).map( pkg => pkg.toFullInfo(options)).toSeq + pkgInfo.validateDevelopDependenciesVersionRanges(dependencies, options) var dependencyGraph = buildDependencyGraph(dependencies, options) diff --git a/src/nimblepkg/packageinfo.nim b/src/nimblepkg/packageinfo.nim index 3d4f9863..aa912d1a 100644 --- a/src/nimblepkg/packageinfo.nim +++ b/src/nimblepkg/packageinfo.nim @@ -346,6 +346,17 @@ proc findPkg*(pkglist: seq[PackageInfo], dep: PkgTuple, r = pkg result = true +proc resolveURL*(pkglist: seq[PackageInfo], str: string): string = + ## Resolves a URL into a package name from a package list. + ## Just returns **str** if its not a url + # We can't just check if it starts with "http" since thats a valid package name + if str.startsWith("http://") or str.startsWith("https://"): + for pkg in pkglist: + if cmpIgnoreStyle(pkg.metaData.url, str) == 0: + return pkg.basicInfo.name + else: + return str + proc findAllPkgs*(pkglist: seq[PackageInfo], dep: PkgTuple): seq[PackageInfo] = ## Searches ``pkglist`` for packages of which version is within the range ## of ``dep.ver``. This is similar to ``findPkg`` but returns multiple From 92bb33238b83a3c406be786a82dbaf1d06b36825 Mon Sep 17 00:00:00 2001 From: Jake Leahy Date: Sat, 17 Dec 2022 21:14:38 +1100 Subject: [PATCH 02/14] Tasks are now stored as subgraphs in lock files Removes previous hack that needed to read current nimble file state This changes the lock file structure, but compaitability shouldn't be affected --- src/nimble.nim | 203 +++++++++++++++-------------- src/nimblepkg/developfile.nim | 2 +- src/nimblepkg/lockfile.nim | 21 ++- src/nimblepkg/packageinfo.nim | 13 +- src/nimblepkg/packageinfotypes.nim | 4 +- 5 files changed, 123 insertions(+), 120 deletions(-) diff --git a/src/nimble.nim b/src/nimble.nim index d6aa74ad..294ce675 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -67,19 +67,27 @@ proc initPkgList(pkgInfo: PackageInfo, options: Options): seq[PackageInfo] = proc install(packages: seq[PkgTuple], options: Options, doPrompt, first, fromLockFile: bool): PackageDependenciesInfo -proc processFreeDependencies(pkgInfo: PackageInfo, options: Options): - HashSet[PackageInfo] = +proc checkSatisfied(options: Options, dependencies: HashSet[PackageInfo]) = + ## Check if two packages of the same name (but different version) are listed + ## in the path. Throws error if it fails + var pkgsInPath: Table[string, Version] + for pkgInfo in dependencies: + let currentVer = pkgInfo.getConcreteVersion(options) + if pkgsInPath.hasKey(pkgInfo.basicInfo.name) and + pkgsInPath[pkgInfo.basicInfo.name] != currentVer: + raise nimbleError( + "Cannot satisfy the dependency on $1 $2 and $1 $3" % + [pkgInfo.basicInfo.name, $currentVer, $pkgsInPath[pkgInfo.basicInfo.name]]) + pkgsInPath[pkgInfo.basicInfo.name] = currentVer + +proc processFreeDependencies(pkgInfo: PackageInfo, requirements: seq[PkgTuple], + options: Options, dependencies: var HashSet[PackageInfo]) = ## Verifies and installs dependencies. ## ## Returns set of PackageInfo (for paths) to pass to the compiler ## during build phase. assert not pkgInfo.isMinimal, "processFreeDependencies needs pkgInfo.requires" - # If the folder we are operating in is the same folder that the .nimble - # file is in then we know we are working the main package - let - isMain = options.startDir == pkgInfo.myPath.splitpath().head - task = options.task var pkgList {.global.}: seq[PackageInfo] = @[] once: pkgList = initPkgList(pkgInfo, options) @@ -88,25 +96,7 @@ proc processFreeDependencies(pkgInfo: PackageInfo, options: Options): [pkgInfo.basicInfo.name, $pkgInfo.basicInfo.version], priority = HighPriority) - var - reverseDependencies: seq[PackageBasicInfo] = @[] - requirements = pkgInfo.requires - - template addTaskRequirements = - ## Adds all task requirements to list of requirements - for task, requires in pkgInfo.taskRequires: - requirements &= requires - - # Check what task level dependencies need to be added - if isMain: - if task in pkgInfo.taskRequires: - # If this is the main file then add its needed requirements for running a task. - requirements &= pkgInfo.taskRequires[task] - elif options.action.typ in {actionLock, actionSync}: - # We only add top level task requirements into lock file - addTaskRequirements() - if options.action.typ == actionDeps: - addTaskRequirements() + var reverseDependencies: seq[PackageBasicInfo] = @[] for dep in requirements: if dep.name == "nimrod" or dep.name == "nim": @@ -134,14 +124,14 @@ proc processFreeDependencies(pkgInfo: PackageInfo, options: Options): doPrompt = false, first = false, fromLockFile = false) for pkg in packages: - if result.contains pkg: + if dependencies.contains pkg: # If the result already contains the newly tried to install package # we had to merge its special versions set into the set of the old # one. - result[pkg].metaData.specialVersions.incl( + dependencies[pkg].metaData.specialVersions.incl( pkg.metaData.specialVersions) else: - result.incl pkg + dependencies.incl pkg pkg = installedPkg # For addRevDep fillMetaData(pkg, pkg.getRealDir(), false) @@ -150,23 +140,14 @@ proc processFreeDependencies(pkgInfo: PackageInfo, options: Options): pkgList.add pkg else: displayInfo(pkgDepsAlreadySatisfiedMsg(dep)) - result.incl pkg + dependencies.incl pkg # Process the dependencies of this dependency. - result.incl processFreeDependencies(pkg.toFullInfo(options), options) + processFreeDependencies(pkg.toFullInfo(options), pkg.requires, options, dependencies) + if not pkg.isLink: reverseDependencies.add(pkg.basicInfo) - # Check if two packages of the same name (but different version) are listed - # in the path. - var pkgsInPath: Table[string, Version] - for pkgInfo in result: - let currentVer = pkgInfo.getConcreteVersion(options) - if pkgsInPath.hasKey(pkgInfo.basicInfo.name) and - pkgsInPath[pkgInfo.basicInfo.name] != currentVer: - raise nimbleError( - "Cannot satisfy the dependency on $1 $2 and $1 $3" % - [pkgInfo.basicInfo.name, $currentVer, $pkgsInPath[pkgInfo.basicInfo.name]]) - pkgsInPath[pkgInfo.basicInfo.name] = currentVer + options.checkSatisfied(dependencies) # We add the reverse deps to the JSON file here because we don't want # them added if the above errorenous condition occurs @@ -349,9 +330,16 @@ proc processLockedDependencies(pkgInfo: PackageInfo, options: Options): proc processAllDependencies(pkgInfo: PackageInfo, options: Options): HashSet[PackageInfo] = if pkgInfo.lockedDeps.len > 0: - pkgInfo.processLockedDependencies(options) + result = pkgInfo.processLockedDependencies(options) else: - pkgInfo.processFreeDependencies(options) + var requires = pkgInfo.requires & pkgInfo.taskRequires.getOrDefault(options.task) + pkgInfo.processFreeDependencies(requires, options, result) + +proc allDependencies(pkgInfo: PackageInfo, options: Options): HashSet[PackageInfo] = + ## Returns all dependencies for a package (Including tasks) + pkgInfo.processFreeDependencies(pkgInfo.requires, options, result) + for requires in pkgInfo.taskRequires.values: + pkgInfo.processFreeDependencies(requires, options, result) proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, url: string, first: bool, fromLockFile: bool, @@ -399,7 +387,7 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, if first and pkgInfo.lockedDeps.len > 0: result.deps = pkgInfo.processLockedDependencies(depsOptions) elif not fromLockFile: - result.deps = pkgInfo.processFreeDependencies(depsOptions) + pkgInfo.processFreeDependencies(pkgInfo.requires, depsOptions, result.deps) if options.depsOnly: result.pkg = pkgInfo @@ -616,9 +604,9 @@ proc installDependency(pkgInfo: PackageInfo, downloadInfo: DownloadInfo, downloadInfo.vcsRevision) downloadInfo.downloadDir.removeDir - + let deps = pkgInfo.lockedDeps[""] for depDepName in downloadInfo.dependency.dependencies: - let depDep = pkgInfo.lockedDeps[depDepName] + let depDep = deps[depDepName] let revDep = (name: depDepName, version: depDep.version, checksum: depDep.checksums.sha1) options.nimbleData.addRevDep(revDep, newlyInstalledPkgInfo) @@ -635,30 +623,18 @@ proc processLockedDependencies(pkgInfo: PackageInfo, options: Options): let developModeDeps = getDevelopDependencies(pkgInfo, options) - # We need to resolve URL's into names - var pkgList {.global.}: seq[PackageInfo] = @[] - once: pkgList = initPkgList(pkgInfo, options) - - # Build list of packages allowed for running. - # This is to stop requirements from unrelated tasks - # needing to be downloaded - var allowedPackages: HashSet[string] - for requirement in pkgInfo.requires & pkgInfo.taskRequires.getOrDefault(options.task): - let name = pkgList.resolveURL(requirement.name) - allowedPackages.incl name - - - for name, dep in pkgInfo.lockedDeps: - if name in allowedPackages: - if developModeDeps.hasKey(name): - result.incl developModeDeps[name][] - elif isInstalled(name, dep, options): - result.incl getDependency(name, dep, options) - elif not options.offline: - let downloadResult = downloadDependency(name, dep, options) - result.incl installDependency(pkgInfo, downloadResult, options) - else: - raise nimbleError("Unsatisfied dependency: " & pkgInfo.basicInfo.name) + for task, deps in pkgInfo.lockedDeps: + if task in ["", options.task]: + for name, dep in deps: + if developModeDeps.hasKey(name): + result.incl developModeDeps[name][] + elif isInstalled(name, dep, options): + result.incl getDependency(name, dep, options) + elif not options.offline: + let downloadResult = downloadDependency(name, dep, options) + result.incl installDependency(pkgInfo, downloadResult, options) + else: + raise nimbleError("Unsatisfied dependency: " & pkgInfo.basicInfo.name) proc getDownloadInfo*(pv: PkgTuple, options: Options, doPrompt: bool, ignorePackageCache = false): (DownloadMethod, string, @@ -1315,13 +1291,13 @@ proc installDevelopPackage(pkgTup: PkgTuple, options: var Options): proc developLockedDependencies(pkgInfo: PackageInfo, alreadyDownloaded: var HashSet[string], options: var Options) = ## Downloads for develop the dependencies from the lock file. - - for name, dep in pkgInfo.lockedDeps: - if dep.url.removeTrailingGitString notin alreadyDownloaded: - let downloadResult = downloadDependency(name, dep, options) - alreadyDownloaded.incl downloadResult.url.removeTrailingGitString - options.action.devActions.add( - (datAdd, downloadResult.downloadDir.normalizedPath)) + for task, deps in pkgInfo.lockedDeps: + for name, dep in deps: + if dep.url.removeTrailingGitString notin alreadyDownloaded: + let downloadResult = downloadDependency(name, dep, options) + alreadyDownloaded.incl downloadResult.url.removeTrailingGitString + options.action.devActions.add( + (datAdd, downloadResult.downloadDir.normalizedPath)) proc check(alreadyDownloaded: HashSet[string], dep: PkgTuple, options: Options): bool = @@ -1577,11 +1553,11 @@ proc validateDevModeDepsWorkingCopiesBeforeLock( result.del name proc mergeLockedDependencies*(pkgInfo: PackageInfo, newDeps: LockFileDeps, - options: Options): LockFileDeps = + options: Options, task: string): LockFileDeps = ## Updates the lock file data of already generated lock file with the data ## from a new lock operation. - result = pkgInfo.lockedDeps + result = pkgInfo.lockedDeps[task] let developDeps = pkgInfo.getDevelopDependencies(options) for name, dep in newDeps: @@ -1627,6 +1603,16 @@ proc displayLockOperationFinish(didLockFileExist: bool) = lockFileIsGeneratedMsg displaySuccess(msg) +proc check(errors: var ValidationErrors, graph: LockFileDeps) = + ## Checks that the dependency graph has no errors + # throw error only for dependencies that are part of the graph + for name, error in common.dup(errors): + if name notin graph: + errors.del name + + if errors.len > 0: + raise validationErrors(errors) + proc lock(options: Options) = ## Generates a lock file for the package in the current directory or updates ## it if it already exists. @@ -1636,24 +1622,32 @@ proc lock(options: Options) = pkgInfo = getPkgInfo(currentDir, options) currentLockFile = options.lockFile(currentDir) doesLockFileExist = displayLockOperationStart(currentLockFile) + lockExists = currentLockFile.fileExists var errors = validateDevModeDepsWorkingCopiesBeforeLock(pkgInfo, options) - let dependencies = pkgInfo.processFreeDependencies(options).map( - pkg => pkg.toFullInfo(options)).toSeq + # We need to process free dependencies for all tasks. + # Then we can store each task as a seperate sub graph. + var deps = initHashSet[PackageInfo]() + pkgInfo.processFreeDependencies(pkgInfo.requires, options, deps) + # Remember the base deps so we can keep them seperate from tasks + var baseDepNames: HashSet[string] + for dep in deps: + baseDepNames.incl dep.name - pkgInfo.validateDevelopDependenciesVersionRanges(dependencies, options) - var dependencyGraph = buildDependencyGraph(dependencies, options) + var lockDeps: AllLockFileDeps + let fullInfo = deps.toSeq().map(pkg => pkg.toFullInfo(options)) - # throw error only for dependencies that are part of the graph - for name, error in common.dup(errors): - if not dependencyGraph.contains(name): - errors.del name + pkgInfo.validateDevelopDependenciesVersionRanges(fullInfo, options) + var graph = buildDependencyGraph(fullInfo, options) + errors.check(graph) + let (topologicalOrder, _) = topologicalSort(graph) - if errors.len > 0: - raise validationErrors(errors) + lockDeps[""] = LockFileDeps() + for dep in topologicalOrder: + lockDeps[""][dep] = graph[dep] - if currentLockFile.fileExists: + if lockExists: # If we already have a lock file, merge its data with the newly generated # one. # @@ -1663,11 +1657,28 @@ proc lock(options: Options) = # satisfaction between all packages, but just greedily picks the best # matching version of dependencies for the currently processed package. - dependencyGraph = mergeLockedDependencies(pkgInfo, dependencyGraph, options) + graph = mergeLockedDependencies(pkgInfo, graph, options, "") + + # Add each individual tasks as partial sub graphs + for task, requires in pkgInfo.taskRequires: + pkgInfo.processFreeDependencies(requires, options, deps) + let fullInfo = deps.toSeq().map(pkg => pkg.toFullInfo(options)) + + pkgInfo.validateDevelopDependenciesVersionRanges(fullInfo, options) + var graph = buildDependencyGraph(fullInfo, options) + lockDeps[task] = LockFileDeps() + errors.check(graph) + let (topologicalOrder, _) = topologicalSort(graph) + # Only store task deps + for dep in topologicalOrder: + if dep notin baseDepNames: + lockDeps[task][dep] = graph[dep] + # Still unsure the point of this + graph = mergeLockedDependencies(pkgInfo, graph, options, task) + - let (topologicalOrder, _) = topologicalSort(dependencyGraph) - writeLockFile(currentLockFile, dependencyGraph, topologicalOrder) + writeLockFile(currentLockFile, lockDeps) updateSyncFile(pkgInfo, options) displayLockOperationFinish(doesLockFileExist) @@ -1679,7 +1690,7 @@ proc depsTree(options: Options) = var errors = validateDevModeDepsWorkingCopiesBeforeLock(pkgInfo, options) - let dependencies = pkgInfo.processFreeDependencies(options).map( + let dependencies = pkgInfo.allDependencies(options).map( pkg => pkg.toFullInfo(options)).toSeq pkgInfo.validateDevelopDependenciesVersionRanges(dependencies, options) var dependencyGraph = buildDependencyGraph(dependencies, options) @@ -1710,7 +1721,7 @@ proc syncWorkingCopy(name: string, path: Path, dependentPkg: PackageInfo, assert lockedDeps.hasKey(name), &"Package \"{name}\" must be present in the lock file." - let vcsRevision = lockedDeps[name].vcsRevision + let vcsRevision = lockedDeps[""][name].vcsRevision assert vcsRevision != path.getVcsRevision, "If here the working copy VCS revision must be different from the " & "revision written in the lock file." diff --git a/src/nimblepkg/developfile.nim b/src/nimblepkg/developfile.nim index 505a50fd..c21cdbe6 100644 --- a/src/nimblepkg/developfile.nim +++ b/src/nimblepkg/developfile.nim @@ -854,7 +854,7 @@ proc workingCopyNeeds*(dependencyPkg, dependentPkg: PackageInfo, ## if any. let - lockFileVcsRev = dependentPkg.lockedDeps.getOrDefault( + lockFileVcsRev = dependentPkg.lockedDeps[""].getOrDefault( dependencyPkg.basicInfo.name, notSetLockFileDep).vcsRevision syncFile = getSyncFile(dependentPkg) syncFileVcsRev = syncFile.getDepVcsRevision(dependencyPkg.basicInfo.name) diff --git a/src/nimblepkg/lockfile.nim b/src/nimblepkg/lockfile.nim index 19941cec..1c7fbc26 100644 --- a/src/nimblepkg/lockfile.nim +++ b/src/nimblepkg/lockfile.nim @@ -11,7 +11,7 @@ type lfjkPkgVcsRevision = "vcsRevision" const - lockFileVersion = 1 + lockFileVersion = 2 proc initLockFileDep*: LockFileDep = result = LockFileDep( @@ -22,31 +22,30 @@ proc initLockFileDep*: LockFileDep = const notSetLockFileDep* = initLockFileDep() -proc writeLockFile*(fileName: string, packages: LockFileDeps, - topologicallySortedOrder: seq[string]) = +proc writeLockFile*(fileName: string, packages: AllLockFileDeps) = ## Saves lock file on the disk in topologically sorted order of the ## dependencies. - let packagesJsonNode = newJObject() - for packageName in topologicallySortedOrder: - packagesJsonNode.add packageName, %packages[packageName] - let mainJsonNode = %{ $lfjkVersion: %lockFileVersion, - $lfjkPackages: packagesJsonNode + $lfjkPackages: %packages } var s = mainJsonNode.pretty s.add '\n' writeFile(fileName, s) -proc readLockFile*(filePath: string): LockFileDeps = +proc readLockFile*(filePath: string): AllLockFileDeps = {.warning[UnsafeDefault]: off.} {.warning[ProveInit]: off.} - result = parseFile(filePath)[$lfjkPackages].to(result.typeof) + let data = parseFile(filePath) + if data["version"].getInt() == 1: + result[""] = data[$lfjkPackages].to(LockFileDeps) + else: + result = data[$lfjkPackages].to(result.typeof) {.warning[ProveInit]: on.} {.warning[UnsafeDefault]: on.} -proc getLockedDependencies*(lockFile: string): LockFileDeps = +proc getLockedDependencies*(lockFile: string): AllLockFileDeps = if lockFile.fileExists: result = lockFile.readLockFile diff --git a/src/nimblepkg/packageinfo.nim b/src/nimblepkg/packageinfo.nim index aa912d1a..e37bca19 100644 --- a/src/nimblepkg/packageinfo.nim +++ b/src/nimblepkg/packageinfo.nim @@ -346,17 +346,6 @@ proc findPkg*(pkglist: seq[PackageInfo], dep: PkgTuple, r = pkg result = true -proc resolveURL*(pkglist: seq[PackageInfo], str: string): string = - ## Resolves a URL into a package name from a package list. - ## Just returns **str** if its not a url - # We can't just check if it starts with "http" since thats a valid package name - if str.startsWith("http://") or str.startsWith("https://"): - for pkg in pkglist: - if cmpIgnoreStyle(pkg.metaData.url, str) == 0: - return pkg.basicInfo.name - else: - return str - proc findAllPkgs*(pkglist: seq[PackageInfo], dep: PkgTuple): seq[PackageInfo] = ## Searches ``pkglist`` for packages of which version is within the range ## of ``dep.ver``. This is similar to ``findPkg`` but returns multiple @@ -536,6 +525,8 @@ proc fullRequirements*(pkgInfo: PackageInfo): seq[PkgTuple] = for requirements in pkgInfo.taskRequires.values: result &= requirements +proc name*(pkgInfo: PackageInfo): string {.inline.} = + pkgInfo.basicInfo.name proc `==`*(pkg1: PackageInfo, pkg2: PackageInfo): bool = pkg1.myPath == pkg2.myPath diff --git a/src/nimblepkg/packageinfotypes.nim b/src/nimblepkg/packageinfotypes.nim index 508e7520..997d1f39 100644 --- a/src/nimblepkg/packageinfotypes.nim +++ b/src/nimblepkg/packageinfotypes.nim @@ -21,6 +21,8 @@ type LockFileDeps* = OrderedTable[string, LockFileDep] + AllLockFileDeps* = Table[string, LockFileDeps] + PackageMetaData* = object url*: string downloadMethod*: DownloadMethod @@ -62,7 +64,7 @@ type backend*: string foreignDeps*: seq[string] basicInfo*: PackageBasicInfo - lockedDeps*: LockFileDeps + lockedDeps*: AllLockFileDeps metaData*: PackageMetaData isLink*: bool From 57f6695714072e64755e9536b56109a24d910bef Mon Sep 17 00:00:00 2001 From: Jake Leahy Date: Sat, 17 Dec 2022 22:10:50 +1100 Subject: [PATCH 03/14] Add test case for regression Using nimlangserver since that was that project I first noticed it with --- tests/ttaskdeps.nim | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/ttaskdeps.nim b/tests/ttaskdeps.nim index 616243e1..2d3bae1c 100644 --- a/tests/ttaskdeps.nim +++ b/tests/ttaskdeps.nim @@ -136,3 +136,9 @@ suite "Task level dependencies": check exitCode == QuitSuccess check output.processOutput.inLines("benchmarkRequires: \"benchy 0.0.1\"") check output.processOutput.inLines("testRequires: \"unittest2 0.0.4\"") + + test "Lock files don't break": + # Tests for regression caused by tasks deps. + # nimlangserver is good candidate, has locks and quite a few dependencies + let (_, exitCode) = execNimble("install", "nimlangserver@#19715af") + check exitCode == QuitSuccess From 451b63afc4dab7120a77c95047b900cea4cbff7a Mon Sep 17 00:00:00 2001 From: Jake Leahy Date: Sun, 18 Dec 2022 10:46:43 +1100 Subject: [PATCH 04/14] Task dependency tests are passing again Will now investigate other tests --- src/nimble.nim | 87 +++++++++++++++--------------- src/nimblepkg/developfile.nim | 2 +- src/nimblepkg/lockfile.nim | 19 ++++--- src/nimblepkg/packageinfo.nim | 15 ++++-- src/nimblepkg/packageinfotypes.nim | 4 ++ tests/taskdeps/main/foo.nim | 1 + tests/taskdeps/main/main.nimble | 2 +- tests/taskdeps/main/src/main.nim | 0 tests/testscommon.nim | 7 +-- tests/ttaskdeps.nim | 16 +++--- 10 files changed, 86 insertions(+), 67 deletions(-) create mode 100644 tests/taskdeps/main/foo.nim create mode 100644 tests/taskdeps/main/src/main.nim diff --git a/src/nimble.nim b/src/nimble.nim index 294ce675..72f73505 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -332,8 +332,9 @@ proc processAllDependencies(pkgInfo: PackageInfo, options: Options): if pkgInfo.lockedDeps.len > 0: result = pkgInfo.processLockedDependencies(options) else: - var requires = pkgInfo.requires & pkgInfo.taskRequires.getOrDefault(options.task) - pkgInfo.processFreeDependencies(requires, options, result) + pkgInfo.processFreeDependencies(pkgInfo.requires, options, result) + if options.task in pkgInfo.taskRequires: + pkgInfo.processFreeDependencies(pkgInfo.taskRequires[options.task], options, result) proc allDependencies(pkgInfo: PackageInfo, options: Options): HashSet[PackageInfo] = ## Returns all dependencies for a package (Including tasks) @@ -604,7 +605,7 @@ proc installDependency(pkgInfo: PackageInfo, downloadInfo: DownloadInfo, downloadInfo.vcsRevision) downloadInfo.downloadDir.removeDir - let deps = pkgInfo.lockedDeps[""] + let deps = pkgInfo.lockedDeps[noTask] for depDepName in downloadInfo.dependency.dependencies: let depDep = deps[depDepName] let revDep = (name: depDepName, version: depDep.version, @@ -623,18 +624,16 @@ proc processLockedDependencies(pkgInfo: PackageInfo, options: Options): let developModeDeps = getDevelopDependencies(pkgInfo, options) - for task, deps in pkgInfo.lockedDeps: - if task in ["", options.task]: - for name, dep in deps: - if developModeDeps.hasKey(name): - result.incl developModeDeps[name][] - elif isInstalled(name, dep, options): - result.incl getDependency(name, dep, options) - elif not options.offline: - let downloadResult = downloadDependency(name, dep, options) - result.incl installDependency(pkgInfo, downloadResult, options) - else: - raise nimbleError("Unsatisfied dependency: " & pkgInfo.basicInfo.name) + for name, dep in pkgInfo.lockedDepsFor(options): + if developModeDeps.hasKey(name): + result.incl developModeDeps[name][] + elif isInstalled(name, dep, options): + result.incl getDependency(name, dep, options) + elif not options.offline: + let downloadResult = downloadDependency(name, dep, options) + result.incl installDependency(pkgInfo, downloadResult, options) + else: + raise nimbleError("Unsatisfied dependency: " & pkgInfo.basicInfo.name) proc getDownloadInfo*(pv: PkgTuple, options: Options, doPrompt: bool, ignorePackageCache = false): (DownloadMethod, string, @@ -1556,30 +1555,30 @@ proc mergeLockedDependencies*(pkgInfo: PackageInfo, newDeps: LockFileDeps, options: Options, task: string): LockFileDeps = ## Updates the lock file data of already generated lock file with the data ## from a new lock operation. - - result = pkgInfo.lockedDeps[task] - let developDeps = pkgInfo.getDevelopDependencies(options) - - for name, dep in newDeps: - if result.hasKey(name): - # If the dependency is already present in the old lock file - if developDeps.hasKey(name): - # and it is a develop mode dependency update it with the newly locked - # version, - result[name] = dep + if task in pkgInfo.lockedDeps: + result = pkgInfo.lockedDeps[task] + let developDeps = pkgInfo.getDevelopDependencies(options) + + for name, dep in newDeps: + if result.hasKey(name): + # If the dependency is already present in the old lock file + if developDeps.hasKey(name): + # and it is a develop mode dependency update it with the newly locked + # version, + result[name] = dep + else: + # but if it is installed dependency just leave it at the current + # version. + discard else: - # but if it is installed dependency just leave it at the current - # version. - discard - else: - # If the dependency is missing from the old develop file add it. - result[name] = dep + # If the dependency is missing from the old develop file add it. + result[name] = dep - # Clean dependencies which are missing from the newly locked list. - let deps = result - for name, dep in deps: - if not newDeps.hasKey(name): - result.del name + # Clean dependencies which are missing from the newly locked list. + let deps = result + for name, dep in deps: + if not newDeps.hasKey(name): + result.del name proc displayLockOperationStart(lockFile: string): bool = ## Displays a proper log message for starting generating or updating the lock @@ -1621,8 +1620,7 @@ proc lock(options: Options) = currentDir = getCurrentDir() pkgInfo = getPkgInfo(currentDir, options) currentLockFile = options.lockFile(currentDir) - doesLockFileExist = displayLockOperationStart(currentLockFile) - lockExists = currentLockFile.fileExists + lockExists = displayLockOperationStart(currentLockFile) var errors = validateDevModeDepsWorkingCopiesBeforeLock(pkgInfo, options) @@ -1639,13 +1637,14 @@ proc lock(options: Options) = let fullInfo = deps.toSeq().map(pkg => pkg.toFullInfo(options)) pkgInfo.validateDevelopDependenciesVersionRanges(fullInfo, options) + var graph = buildDependencyGraph(fullInfo, options) errors.check(graph) let (topologicalOrder, _) = topologicalSort(graph) - lockDeps[""] = LockFileDeps() + lockDeps[noTask] = LockFileDeps() for dep in topologicalOrder: - lockDeps[""][dep] = graph[dep] + lockDeps[noTask][dep] = graph[dep] if lockExists: # If we already have a lock file, merge its data with the newly generated @@ -1680,7 +1679,7 @@ proc lock(options: Options) = writeLockFile(currentLockFile, lockDeps) updateSyncFile(pkgInfo, options) - displayLockOperationFinish(doesLockFileExist) + displayLockOperationFinish(lockExists) proc depsTree(options: Options) = @@ -1717,11 +1716,11 @@ proc syncWorkingCopy(name: string, path: Path, dependentPkg: PackageInfo, displayInfo(&"Syncing working copy of package \"{name}\" at \"{path}\"...") - let lockedDeps = dependentPkg.lockedDeps + let lockedDeps = dependentPkg.lockedDeps[noTask] assert lockedDeps.hasKey(name), &"Package \"{name}\" must be present in the lock file." - let vcsRevision = lockedDeps[""][name].vcsRevision + let vcsRevision = lockedDeps[name].vcsRevision assert vcsRevision != path.getVcsRevision, "If here the working copy VCS revision must be different from the " & "revision written in the lock file." diff --git a/src/nimblepkg/developfile.nim b/src/nimblepkg/developfile.nim index c21cdbe6..c1e94996 100644 --- a/src/nimblepkg/developfile.nim +++ b/src/nimblepkg/developfile.nim @@ -854,7 +854,7 @@ proc workingCopyNeeds*(dependencyPkg, dependentPkg: PackageInfo, ## if any. let - lockFileVcsRev = dependentPkg.lockedDeps[""].getOrDefault( + lockFileVcsRev = dependentPkg.lockedDeps.getOrDefault("").getOrDefault( dependencyPkg.basicInfo.name, notSetLockFileDep).vcsRevision syncFile = getSyncFile(dependentPkg) syncFileVcsRev = syncFile.getDepVcsRevision(dependencyPkg.basicInfo.name) diff --git a/src/nimblepkg/lockfile.nim b/src/nimblepkg/lockfile.nim index 1c7fbc26..bf36288e 100644 --- a/src/nimblepkg/lockfile.nim +++ b/src/nimblepkg/lockfile.nim @@ -9,6 +9,7 @@ type lfjkVersion = "version" lfjkPackages = "packages" lfjkPkgVcsRevision = "vcsRevision" + lfjkTasks = "tasks" const lockFileVersion = 2 @@ -28,8 +29,13 @@ proc writeLockFile*(fileName: string, packages: AllLockFileDeps) = let mainJsonNode = %{ $lfjkVersion: %lockFileVersion, - $lfjkPackages: %packages - } + $lfjkPackages: %packages[noTask] + } + # Store task graph seperate + mainJsonNode[$lfjkTasks] = newJObject() + for task, deps in packages: + if task != noTask: + mainJsonNode[$lfjkTasks][task] = %deps var s = mainJsonNode.pretty s.add '\n' @@ -39,10 +45,11 @@ proc readLockFile*(filePath: string): AllLockFileDeps = {.warning[UnsafeDefault]: off.} {.warning[ProveInit]: off.} let data = parseFile(filePath) - if data["version"].getInt() == 1: - result[""] = data[$lfjkPackages].to(LockFileDeps) - else: - result = data[$lfjkPackages].to(result.typeof) + result[noTask] = data[$lfjkPackages].to(LockFileDeps) + if $lfjkTasks in data: + for task, deps in data[$lfjkTasks]: + echo "Reading in ", task + result[task] = deps.to(LockFileDeps) {.warning[ProveInit]: on.} {.warning[UnsafeDefault]: on.} diff --git a/src/nimblepkg/packageinfo.nim b/src/nimblepkg/packageinfo.nim index e37bca19..093352ee 100644 --- a/src/nimblepkg/packageinfo.nim +++ b/src/nimblepkg/packageinfo.nim @@ -22,7 +22,7 @@ proc isLoaded*(pkgInfo: PackageInfo): bool = return pkgInfo.myPath.len > 0 proc assertIsLoaded*(pkgInfo: PackageInfo) = - assert pkgInfo.isLoaded, "The package info must be loaded." + assert pkgInfo.isLoaded, "The package info must be loaded. " proc areLockedDepsLoaded*(pkgInfo: PackageInfo): bool = pkgInfo.lockedDeps.len > 0 @@ -200,8 +200,11 @@ proc readPackageList(name: string, options: Options, ignorePackageCache = false) # going further. gPackageJson[name] = newJArray() return gPackageJson[name] - gPackageJson[name] = parseFile(options.getNimbleDir() / "packages_" & - name.toLowerAscii() & ".json") + let file = options.getNimbleDir() / "packages_" & name.toLowerAscii() & ".json" + if file.fileExists: + gPackageJson[name] = parseFile(file) + else: + gPackageJson[name] = newJArray() return gPackageJson[name] proc getPackage*(pkg: string, options: Options, resPkg: var Package, ignorePackageCache = false): bool @@ -528,6 +531,12 @@ proc fullRequirements*(pkgInfo: PackageInfo): seq[PkgTuple] = proc name*(pkgInfo: PackageInfo): string {.inline.} = pkgInfo.basicInfo.name +iterator lockedDepsFor*(pkgInfo: PackageInfo, options: Options): (string, LockFileDep) = + for task, deps in pkgInfo.lockedDeps: + if task in ["", options.task]: + for name, dep in deps: + yield (name, dep) + proc `==`*(pkg1: PackageInfo, pkg2: PackageInfo): bool = pkg1.myPath == pkg2.myPath diff --git a/src/nimblepkg/packageinfotypes.nim b/src/nimblepkg/packageinfotypes.nim index 997d1f39..8f2cf531 100644 --- a/src/nimblepkg/packageinfotypes.nim +++ b/src/nimblepkg/packageinfotypes.nim @@ -22,6 +22,8 @@ type LockFileDeps* = OrderedTable[string, LockFileDep] AllLockFileDeps* = Table[string, LockFileDeps] + ## Base deps is stored with empty string key "" + ## Other tasks have task name as key PackageMetaData* = object url*: string @@ -83,3 +85,5 @@ type alias*: string ## A name of another package, that this package aliases. PackageDependenciesInfo* = tuple[deps: HashSet[PackageInfo], pkg: PackageInfo] + +const noTask* = "" # Means that noTask is being ran. Use this as key for base dependencies diff --git a/tests/taskdeps/main/foo.nim b/tests/taskdeps/main/foo.nim new file mode 100644 index 00000000..4b703fb7 --- /dev/null +++ b/tests/taskdeps/main/foo.nim @@ -0,0 +1 @@ +import json_serialization diff --git a/tests/taskdeps/main/main.nimble b/tests/taskdeps/main/main.nimble index a1b3b6bd..cfc876f2 100644 --- a/tests/taskdeps/main/main.nimble +++ b/tests/taskdeps/main/main.nimble @@ -4,7 +4,7 @@ version = "0.1.0" author = "John Doe" description = "A new awesome nimble package" license = "MIT" -srcDir = "." +srcDir = "src" bin = @[] diff --git a/tests/taskdeps/main/src/main.nim b/tests/taskdeps/main/src/main.nim new file mode 100644 index 00000000..e69de29b diff --git a/tests/testscommon.nim b/tests/testscommon.nim index 6ba398ec..f948bb58 100644 --- a/tests/testscommon.nim +++ b/tests/testscommon.nim @@ -131,11 +131,8 @@ proc safeMoveFile(src, dest: string) = proc uninstallDeps*() = ## Uninstalls all installed dependencies. ## Useful for cleaning up after a test case - let (output, _) = execNimble("list", "-i") - for line in output.splitLines: - let package = line.split(" ")[0] - if package != "": - verify execNimbleYes("uninstall", "-i", package) + removeDir pkgsDir + removeFile installDir / "nimbledata2.json" template testRefresh*(body: untyped) = # Backup current config diff --git a/tests/ttaskdeps.nim b/tests/ttaskdeps.nim index 2d3bae1c..c186a19e 100644 --- a/tests/ttaskdeps.nim +++ b/tests/ttaskdeps.nim @@ -32,29 +32,31 @@ suite "Task level dependencies": inDir: let (output, exitCode) = execNimble("benchmark") check exitCode == QuitSuccess - check output.contains("dependencies for benchy@0.0.1") - check not output.contains("dependencies for unittest2@0.0.4") + check output.contains("benchy@0.0.1") + check not output.contains("unittest2@0.0.4") test "Dependency is not used when not running task": inDir: let (output, exitCode) = execNimble("install") check exitCode == QuitSuccess - check not output.contains("dependencies for unittest2@0.0.4") - check not output.contains("dependencies for benchy@0.0.1") + check not output.contains("unittest2@0.0.4") + check not output.contains("benchy@0.0.1") test "Dependency can be defined for test task": inDir: let (output, exitCode) = execNimble("test") check exitCode == QuitSuccess - check output.contains("dependencies for unittest2@0.0.4") + check output.contains("unittest2@0.0.4") test "Lock file has dependencies added to it": inDir: makeLockFile() # Check task level dependencies are in the lock file let json = parseFile("nimble.lock") - check "unittest2" in json["packages"] - let pkgInfo = json["packages"]["unittest2"] + check: + "test" in json["packages"] + "benchmark" in json["packages"] + let pkgInfo = json["packages"]["test"]["unittest2"] check pkgInfo["version"].getStr() == "0.0.4" test "Task dependencies from lock file are used": From 45d5ed5e52bc5078db95d7b637a6a5f1c6575e7e Mon Sep 17 00:00:00 2001 From: Jake Leahy Date: Sun, 18 Dec 2022 14:49:23 +1100 Subject: [PATCH 05/14] Properly merge lock files Force yes in tests Fix tests Go back to non destructive uninstall for deps --- src/nimble.nim | 130 ++++++++++-------- src/nimblepkg/packageinfo.nim | 12 ++ .../taskdeps/dependencies/dependencies.nimble | 15 ++ tests/testscommon.nim | 8 +- tests/tlockfile.nim | 1 + tests/ttaskdeps.nim | 59 +++++--- 6 files changed, 145 insertions(+), 80 deletions(-) create mode 100644 tests/taskdeps/dependencies/dependencies.nimble diff --git a/src/nimble.nim b/src/nimble.nim index 72f73505..6922c8ec 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -142,7 +142,8 @@ proc processFreeDependencies(pkgInfo: PackageInfo, requirements: seq[PkgTuple], displayInfo(pkgDepsAlreadySatisfiedMsg(dep)) dependencies.incl pkg # Process the dependencies of this dependency. - processFreeDependencies(pkg.toFullInfo(options), pkg.requires, options, dependencies) + let fullInfo = pkg.toFullInfo(options) + processFreeDependencies(fullInfo, fullInfo.requires, options, dependencies) if not pkg.isLink: reverseDependencies.add(pkg.basicInfo) @@ -329,7 +330,7 @@ proc processLockedDependencies(pkgInfo: PackageInfo, options: Options): proc processAllDependencies(pkgInfo: PackageInfo, options: Options): HashSet[PackageInfo] = - if pkgInfo.lockedDeps.len > 0: + if pkgInfo.hasLockedDeps(): result = pkgInfo.processLockedDependencies(options) else: pkgInfo.processFreeDependencies(pkgInfo.requires, options, result) @@ -385,7 +386,7 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, pkgInfo.metaData.specialVersions.incl requestedVer.spe # Dependencies need to be processed before the creation of the pkg dir. - if first and pkgInfo.lockedDeps.len > 0: + if first and pkgInfo.hasLockedDeps(): result.deps = pkgInfo.processLockedDependencies(depsOptions) elif not fromLockFile: pkgInfo.processFreeDependencies(pkgInfo.requires, depsOptions, result.deps) @@ -1337,7 +1338,7 @@ proc developAllDependencies(pkgInfo: PackageInfo, options: var Options) = var alreadyDownloadedDependencies {.global.}: HashSet[string] alreadyDownloadedDependencies.incl pkgInfo.metaData.url.removeTrailingGitString - if pkgInfo.lockedDeps.len > 0: + if pkgInfo.hasLockedDeps(): pkgInfo.developLockedDependencies(alreadyDownloadedDependencies, options) else: pkgInfo.developFreeDependencies(alreadyDownloadedDependencies, options) @@ -1552,33 +1553,36 @@ proc validateDevModeDepsWorkingCopiesBeforeLock( result.del name proc mergeLockedDependencies*(pkgInfo: PackageInfo, newDeps: LockFileDeps, - options: Options, task: string): LockFileDeps = + options: Options): LockFileDeps = ## Updates the lock file data of already generated lock file with the data ## from a new lock operation. - if task in pkgInfo.lockedDeps: - result = pkgInfo.lockedDeps[task] - let developDeps = pkgInfo.getDevelopDependencies(options) - - for name, dep in newDeps: - if result.hasKey(name): - # If the dependency is already present in the old lock file - if developDeps.hasKey(name): - # and it is a develop mode dependency update it with the newly locked - # version, - result[name] = dep - else: - # but if it is installed dependency just leave it at the current - # version. - discard - else: - # If the dependency is missing from the old develop file add it. + # Copy across the data in the existing lock file + for deps in pkgInfo.lockedDeps.values: + for name, dep in deps: + result[name] = dep + + let developDeps = pkgInfo.getDevelopDependencies(options) + + for name, dep in newDeps: + if result.hasKey(name): + # If the dependency is already present in the old lock file + if developDeps.hasKey(name): + # and it is a develop mode dependency update it with the newly locked + # version, result[name] = dep + else: + # but if it is installed dependency just leave it at the current + # version. + discard + else: + # If the dependency is missing from the old develop file add it. + result[name] = dep - # Clean dependencies which are missing from the newly locked list. - let deps = result - for name, dep in deps: - if not newDeps.hasKey(name): - result.del name + # Clean dependencies which are missing from the newly locked list. + let deps = result + for name, dep in deps: + if not newDeps.hasKey(name): + result.del name proc displayLockOperationStart(lockFile: string): bool = ## Displays a proper log message for starting generating or updating the lock @@ -1626,25 +1630,41 @@ proc lock(options: Options) = # We need to process free dependencies for all tasks. # Then we can store each task as a seperate sub graph. - var deps = initHashSet[PackageInfo]() + var deps = initHashSet[PackageInfo]() # Base deps pkgInfo.processFreeDependencies(pkgInfo.requires, options, deps) - # Remember the base deps so we can keep them seperate from tasks - var baseDepNames: HashSet[string] + var fullDeps = deps # Deps shared by base and tasks + + # We need to seperate the graph into seperate tasks later + var + baseDepNames: HashSet[string] + taskDepNames: Table[string, HashSet[string]] + for dep in deps: baseDepNames.incl dep.name - var lockDeps: AllLockFileDeps - let fullInfo = deps.toSeq().map(pkg => pkg.toFullInfo(options)) - pkgInfo.validateDevelopDependenciesVersionRanges(fullInfo, options) + # Add each individual tasks as partial sub graphs + for task, requires in pkgInfo.taskRequires: + pkgInfo.processFreeDependencies(requires, options, deps) + let fullInfo = deps.toSeq().map(pkg => pkg.toFullInfo(options)) + pkgInfo.validateDevelopDependenciesVersionRanges(fullInfo, options) + var origBaseDeps = initHashSet[PackageInfo](baseDepNames.len) + # Add in the dependencies that are in this task but not in base + taskDepNames[task] = initHashSet[string]() + for dep in deps: + fullDeps.incl dep + if dep.name notin baseDepNames: + taskDepNames[task].incl dep.name + else: + origBaseDeps.incl dep + # Reset the deps to what they were before hand. + # Stops dependencies in this task overflowing into the next + deps = origBaseDeps + let fullInfo = fullDeps.toSeq().map(pkg => pkg.toFullInfo(options)) + pkgInfo.validateDevelopDependenciesVersionRanges(fullInfo, options) var graph = buildDependencyGraph(fullInfo, options) errors.check(graph) - let (topologicalOrder, _) = topologicalSort(graph) - - lockDeps[noTask] = LockFileDeps() - for dep in topologicalOrder: - lockDeps[noTask][dep] = graph[dep] if lockExists: # If we already have a lock file, merge its data with the newly generated @@ -1655,27 +1675,23 @@ proc lock(options: Options) = # currently Nimble does not check properly for `require` clauses # satisfaction between all packages, but just greedily picks the best # matching version of dependencies for the currently processed package. + graph = mergeLockedDependencies(pkgInfo, graph, options) - graph = mergeLockedDependencies(pkgInfo, graph, options, "") - - # Add each individual tasks as partial sub graphs - for task, requires in pkgInfo.taskRequires: - pkgInfo.processFreeDependencies(requires, options, deps) - let fullInfo = deps.toSeq().map(pkg => pkg.toFullInfo(options)) - - pkgInfo.validateDevelopDependenciesVersionRanges(fullInfo, options) - var graph = buildDependencyGraph(fullInfo, options) + let (topologicalOrder, _) = topologicalSort(graph) + var lockDeps: AllLockFileDeps + # Now we break up tasks into seperate graphs + lockDeps[noTask] = LockFileDeps() + for task in pkgInfo.taskRequires.keys: lockDeps[task] = LockFileDeps() - errors.check(graph) - let (topologicalOrder, _) = topologicalSort(graph) - # Only store task deps - for dep in topologicalOrder: - if dep notin baseDepNames: - lockDeps[task][dep] = graph[dep] - # Still unsure the point of this - graph = mergeLockedDependencies(pkgInfo, graph, options, task) - + for dep in topologicalOrder: + if dep in baseDepNames: + lockDeps[noTask][dep] = graph[dep] + else: + # Add the dependency for any task that requires it + for task in pkgInfo.taskRequires.keys: + if dep in taskDepNames[task]: + lockDeps[task][dep] = graph[dep] writeLockFile(currentLockFile, lockDeps) updateSyncFile(pkgInfo, options) @@ -1841,7 +1857,7 @@ proc sync(options: Options) = findValidationErrorsOfDevDepsWithLockFile(pkgInfo, options, errors) for name, error in common.dup(errors): - if not pkgInfo.lockedDeps.contains(name): + if not pkgInfo.lockedDeps.hasPackage(name): errors.del name elif error.kind == vekWorkingCopyNeedsSync: if not options.action.listOnly: diff --git a/src/nimblepkg/packageinfo.nim b/src/nimblepkg/packageinfo.nim index 093352ee..ea0ed191 100644 --- a/src/nimblepkg/packageinfo.nim +++ b/src/nimblepkg/packageinfo.nim @@ -537,6 +537,18 @@ iterator lockedDepsFor*(pkgInfo: PackageInfo, options: Options): (string, LockFi for name, dep in deps: yield (name, dep) +proc hasLockedDeps*(pkgInfo: PackageInfo): bool = + ## Returns true if pkgInfo has any locked deps (including any tasks) + # Check if any tasks have locked deps + for deps in pkgInfo.lockedDeps.values: + if deps.len > 0: + return true + +proc hasPackage*(deps: AllLockFileDeps, pkgName: string): bool = + for deps in deps.values: + if pkgName in deps: + return true + proc `==`*(pkg1: PackageInfo, pkg2: PackageInfo): bool = pkg1.myPath == pkg2.myPath diff --git a/tests/taskdeps/dependencies/dependencies.nimble b/tests/taskdeps/dependencies/dependencies.nimble new file mode 100644 index 00000000..06f1ec62 --- /dev/null +++ b/tests/taskdeps/dependencies/dependencies.nimble @@ -0,0 +1,15 @@ +# Package + +version = "0.1.0" +author = "John Doe" +description = "A new awesome nimble package" +license = "MIT" +srcDir = "." +bin = @["foo"] + + +# Dependencies + +requires "nim >= 0.19.0" + +taskRequires "test", "unittest2 == 0.0.4" diff --git a/tests/testscommon.nim b/tests/testscommon.nim index f948bb58..4729588e 100644 --- a/tests/testscommon.nim +++ b/tests/testscommon.nim @@ -131,8 +131,12 @@ proc safeMoveFile(src, dest: string) = proc uninstallDeps*() = ## Uninstalls all installed dependencies. ## Useful for cleaning up after a test case - removeDir pkgsDir - removeFile installDir / "nimbledata2.json" + let (output, _) = execNimble("list", "-i") + for line in output.splitLines: + let package = line.split(" ")[0] + if package != "": + discard execNimbleYes("uninstall", "-i", package) + template testRefresh*(body: untyped) = # Backup current config diff --git a/tests/tlockfile.nim b/tests/tlockfile.nim index 32f70dd9..9c382534 100644 --- a/tests/tlockfile.nim +++ b/tests/tlockfile.nim @@ -172,6 +172,7 @@ requires "nim >= 1.5.1" let json = lockFileName.readFile.parseJson for (depName, depPath) in deps: let expectedVcsRevision = depPath.getVcsRevision + check depName in json{$lfjkPackages} let lockedVcsRevision = json{$lfjkPackages}{depName}{$lfjkPkgVcsRevision}.str.initSha1Hash check lockedVcsRevision == expectedVcsRevision diff --git a/tests/ttaskdeps.nim b/tests/ttaskdeps.nim index c186a19e..6393a86b 100644 --- a/tests/ttaskdeps.nim +++ b/tests/ttaskdeps.nim @@ -10,41 +10,43 @@ from nimblepkg/common import cd template makeLockFile() = ## Makes lock file, cleans up after itself - verify execNimble("lock") + verify execNimbleYes("lock") defer: removeFile("nimble.lock") template inDir(body: untyped) = ## Runs code inside taskdeps folder cd "taskdeps/main/": + removeFile("nimble.lock") body suite "Task level dependencies": + uninstallDeps() verify execNimbleYes("update") - teardown: uninstallDeps() test "Can specify custom requirement for a task": inDir: - verify execNimble("tasks") + verify execNimbleYes("tasks") test "Dependency is used when running task": inDir: - let (output, exitCode) = execNimble("benchmark") + let (output, exitCode) = execNimbleYes("benchmark") check exitCode == QuitSuccess check output.contains("benchy@0.0.1") + # Check other tasks aren't used check not output.contains("unittest2@0.0.4") test "Dependency is not used when not running task": inDir: - let (output, exitCode) = execNimble("install") + let (output, exitCode) = execNimbleYes("install") check exitCode == QuitSuccess check not output.contains("unittest2@0.0.4") check not output.contains("benchy@0.0.1") test "Dependency can be defined for test task": inDir: - let (output, exitCode) = execNimble("test") + let (output, exitCode) = execNimbleYes("test") check exitCode == QuitSuccess check output.contains("unittest2@0.0.4") @@ -52,18 +54,21 @@ suite "Task level dependencies": inDir: makeLockFile() # Check task level dependencies are in the lock file - let json = parseFile("nimble.lock") + let + json = parseFile("nimble.lock") + tasks = json["tasks"] + packages = json["packages"] check: - "test" in json["packages"] - "benchmark" in json["packages"] - let pkgInfo = json["packages"]["test"]["unittest2"] - check pkgInfo["version"].getStr() == "0.0.4" + "test" in tasks + "benchmark" in tasks + "unittest2" notin packages + check tasks["test"]["unittest2"]["version"].getStr() == "0.0.4" test "Task dependencies from lock file are used": inDir: makeLockFile() uninstallDeps() - let (output, exitCode) = execNimble("test") + let (output, exitCode) = execNimbleYes("test") check exitCode == QuitSuccess check not output.contains("benchy installed successfully") check output.contains("unittest2 installed successfully") @@ -76,7 +81,7 @@ suite "Task level dependencies": # tries to install them later uninstallDeps() - let (output, exitCode) = execNimble("install") + let (output, exitCode) = execNimbleYes("install") check exitCode == QuitSuccess check not output.contains("benchy installed successfully") check not output.contains("unittest2 installed successfully") @@ -86,7 +91,7 @@ suite "Task level dependencies": # Uninstall the dependencies fist to make sure deps command # still installs everything correctly uninstallDeps() - let (output, exitCode) = execNimble("--format:json", "--silent", "deps") + let (output, exitCode) = execNimbleYes("--format:json", "--silent", "deps") check exitCode == QuitSuccess let json = parseJson(output) @@ -102,39 +107,51 @@ suite "Task level dependencies": removeDir("nim-unittest2") removeFile("nimble.develop") - verify execNimble("develop", "unittest2") + verify execNimbleYes("develop", "unittest2") # Add in a file to the develop file # We will then try and import this createDir "nim-unittest2/unittest2" "nim-unittest2/unittest2/customFile.nim".writeFile("") - let (output, exitCode) = execNimble("-d:useDevelop", "test") + let (output, exitCode) = execNimbleYes("-d:useDevelop", "test") check exitCode == QuitSuccess check "Using custom file" in output test "Dependencies aren't verified twice": inDir: - let (output, _) = execNimble("test") + let (output, _) = execNimbleYes("test") check output.count("dependencies for unittest2@0.0.4") == 1 test "Requirements for tasks in dependencies aren't used": cd "taskdeps/subdep/": + removeFile("nimble.lock") + let (output, _) = execNimbleYes("install") + check "threading" notin output + + inDir: + let (output, exitCode) = execNimbleYes("test") + check exitCode == QuitSuccess + check "threading" notin output + + test "Requirements for tasks in dependencies aren't used (When using lock file)": + cd "taskdeps/subdep/": + makeLockFile() let (output, _) = execNimbleYes("install") check "threading" notin output inDir: - let (output, exitCode) = execNimble("test") + let (output, exitCode) = execNimbleYes("test") check exitCode == QuitSuccess check "threading" notin output test "Error thrown when setting requirement for task that doesn't exist": cd "taskdeps/error/": - let (output, exitCode) = execNimble("check") + let (output, exitCode) = execNimbleYes("check") check exitCode == QuitFailure check "Task benchmark doesn't exist for requirement benchy == 0.0.1" in output test "Dump contains information": inDir: - let (output, exitCode) = execNimble("dump") + let (output, exitCode) = execNimbleYes("dump") check exitCode == QuitSuccess check output.processOutput.inLines("benchmarkRequires: \"benchy 0.0.1\"") check output.processOutput.inLines("testRequires: \"unittest2 0.0.4\"") @@ -142,5 +159,5 @@ suite "Task level dependencies": test "Lock files don't break": # Tests for regression caused by tasks deps. # nimlangserver is good candidate, has locks and quite a few dependencies - let (_, exitCode) = execNimble("install", "nimlangserver@#19715af") + let (_, exitCode) = execNimbleYes("install", "nimlangserver@#19715af") check exitCode == QuitSuccess From d1a007db4dd7debda0391dd012d617641797855f Mon Sep 17 00:00:00 2001 From: Jake Leahy Date: Sun, 18 Dec 2022 23:08:24 +1100 Subject: [PATCH 06/14] Fix package list getting reset after every call --- src/nimble.nim | 42 +++++++++++++++++++----------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/src/nimble.nim b/src/nimble.nim index 6922c8ec..19a329e2 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -81,7 +81,7 @@ proc checkSatisfied(options: Options, dependencies: HashSet[PackageInfo]) = pkgsInPath[pkgInfo.basicInfo.name] = currentVer proc processFreeDependencies(pkgInfo: PackageInfo, requirements: seq[PkgTuple], - options: Options, dependencies: var HashSet[PackageInfo]) = + options: Options): HashSet[PackageInfo] = ## Verifies and installs dependencies. ## ## Returns set of PackageInfo (for paths) to pass to the compiler @@ -89,9 +89,8 @@ proc processFreeDependencies(pkgInfo: PackageInfo, requirements: seq[PkgTuple], assert not pkgInfo.isMinimal, "processFreeDependencies needs pkgInfo.requires" - var pkgList {.global.}: seq[PackageInfo] = @[] + var pkgList {.global.}: seq[PackageInfo] once: pkgList = initPkgList(pkgInfo, options) - display("Verifying", "dependencies for $1@$2" % [pkgInfo.basicInfo.name, $pkgInfo.basicInfo.version], priority = HighPriority) @@ -124,14 +123,14 @@ proc processFreeDependencies(pkgInfo: PackageInfo, requirements: seq[PkgTuple], doPrompt = false, first = false, fromLockFile = false) for pkg in packages: - if dependencies.contains pkg: + if result.contains pkg: # If the result already contains the newly tried to install package # we had to merge its special versions set into the set of the old # one. - dependencies[pkg].metaData.specialVersions.incl( + result[pkg].metaData.specialVersions.incl( pkg.metaData.specialVersions) else: - dependencies.incl pkg + result.incl pkg pkg = installedPkg # For addRevDep fillMetaData(pkg, pkg.getRealDir(), false) @@ -140,15 +139,15 @@ proc processFreeDependencies(pkgInfo: PackageInfo, requirements: seq[PkgTuple], pkgList.add pkg else: displayInfo(pkgDepsAlreadySatisfiedMsg(dep)) - dependencies.incl pkg + result.incl pkg # Process the dependencies of this dependency. let fullInfo = pkg.toFullInfo(options) - processFreeDependencies(fullInfo, fullInfo.requires, options, dependencies) + result.incl processFreeDependencies(fullInfo, fullInfo.requires, options) if not pkg.isLink: reverseDependencies.add(pkg.basicInfo) - options.checkSatisfied(dependencies) + options.checkSatisfied(result) # We add the reverse deps to the JSON file here because we don't want # them added if the above errorenous condition occurs @@ -333,15 +332,15 @@ proc processAllDependencies(pkgInfo: PackageInfo, options: Options): if pkgInfo.hasLockedDeps(): result = pkgInfo.processLockedDependencies(options) else: - pkgInfo.processFreeDependencies(pkgInfo.requires, options, result) + result.incl pkgInfo.processFreeDependencies(pkgInfo.requires, options) if options.task in pkgInfo.taskRequires: - pkgInfo.processFreeDependencies(pkgInfo.taskRequires[options.task], options, result) + result.incl pkgInfo.processFreeDependencies(pkgInfo.taskRequires[options.task], options) proc allDependencies(pkgInfo: PackageInfo, options: Options): HashSet[PackageInfo] = ## Returns all dependencies for a package (Including tasks) - pkgInfo.processFreeDependencies(pkgInfo.requires, options, result) + result.incl pkgInfo.processFreeDependencies(pkgInfo.requires, options) for requires in pkgInfo.taskRequires.values: - pkgInfo.processFreeDependencies(requires, options, result) + result.incl pkgInfo.processFreeDependencies(requires, options) proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, url: string, first: bool, fromLockFile: bool, @@ -389,7 +388,7 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, if first and pkgInfo.hasLockedDeps(): result.deps = pkgInfo.processLockedDependencies(depsOptions) elif not fromLockFile: - pkgInfo.processFreeDependencies(pkgInfo.requires, depsOptions, result.deps) + result.deps = pkgInfo.processFreeDependencies(pkgInfo.requires, depsOptions) if options.depsOnly: result.pkg = pkgInfo @@ -1630,8 +1629,8 @@ proc lock(options: Options) = # We need to process free dependencies for all tasks. # Then we can store each task as a seperate sub graph. - var deps = initHashSet[PackageInfo]() # Base deps - pkgInfo.processFreeDependencies(pkgInfo.requires, options, deps) + var requirements = pkgInfo.requires + var deps = pkgInfo.processFreeDependencies(pkgInfo.requires, options) var fullDeps = deps # Deps shared by base and tasks # We need to seperate the graph into seperate tasks later @@ -1645,21 +1644,18 @@ proc lock(options: Options) = # Add each individual tasks as partial sub graphs for task, requires in pkgInfo.taskRequires: - pkgInfo.processFreeDependencies(requires, options, deps) - let fullInfo = deps.toSeq().map(pkg => pkg.toFullInfo(options)) + let newDeps = pkgInfo.processFreeDependencies(requires, options) + let fullInfo = newDeps.toSeq().map(pkg => pkg.toFullInfo(options)) pkgInfo.validateDevelopDependenciesVersionRanges(fullInfo, options) - var origBaseDeps = initHashSet[PackageInfo](baseDepNames.len) # Add in the dependencies that are in this task but not in base taskDepNames[task] = initHashSet[string]() - for dep in deps: + for dep in newDeps: fullDeps.incl dep if dep.name notin baseDepNames: taskDepNames[task].incl dep.name - else: - origBaseDeps.incl dep # Reset the deps to what they were before hand. # Stops dependencies in this task overflowing into the next - deps = origBaseDeps + fullDeps.incl newDeps let fullInfo = fullDeps.toSeq().map(pkg => pkg.toFullInfo(options)) pkgInfo.validateDevelopDependenciesVersionRanges(fullInfo, options) From aae85613857e6560281242c21de3d8c84df25231 Mon Sep 17 00:00:00 2001 From: Jake Leahy Date: Sun, 18 Dec 2022 23:14:05 +1100 Subject: [PATCH 07/14] Check satisisfaction --- src/nimble.nim | 2 +- src/nimblepkg/lockfile.nim | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/nimble.nim b/src/nimble.nim index 19a329e2..36a9a135 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -1656,7 +1656,7 @@ proc lock(options: Options) = # Reset the deps to what they were before hand. # Stops dependencies in this task overflowing into the next fullDeps.incl newDeps - + options.checkSatisfied(fullDeps) let fullInfo = fullDeps.toSeq().map(pkg => pkg.toFullInfo(options)) pkgInfo.validateDevelopDependenciesVersionRanges(fullInfo, options) var graph = buildDependencyGraph(fullInfo, options) diff --git a/src/nimblepkg/lockfile.nim b/src/nimblepkg/lockfile.nim index bf36288e..40f61253 100644 --- a/src/nimblepkg/lockfile.nim +++ b/src/nimblepkg/lockfile.nim @@ -48,7 +48,6 @@ proc readLockFile*(filePath: string): AllLockFileDeps = result[noTask] = data[$lfjkPackages].to(LockFileDeps) if $lfjkTasks in data: for task, deps in data[$lfjkTasks]: - echo "Reading in ", task result[task] = deps.to(LockFileDeps) {.warning[ProveInit]: on.} {.warning[UnsafeDefault]: on.} From 8354713bf3689e0a14739327e6690a240a5c26ad Mon Sep 17 00:00:00 2001 From: Jake Leahy Date: Sun, 18 Dec 2022 23:35:59 +1100 Subject: [PATCH 08/14] Remove unused variable --- src/nimble.nim | 1 - 1 file changed, 1 deletion(-) diff --git a/src/nimble.nim b/src/nimble.nim index 36a9a135..cd3a4340 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -1629,7 +1629,6 @@ proc lock(options: Options) = # We need to process free dependencies for all tasks. # Then we can store each task as a seperate sub graph. - var requirements = pkgInfo.requires var deps = pkgInfo.processFreeDependencies(pkgInfo.requires, options) var fullDeps = deps # Deps shared by base and tasks From 0f6656e89c16abdd93b91cd23805ce7b084dea50 Mon Sep 17 00:00:00 2001 From: Jake Leahy Date: Mon, 19 Dec 2022 10:21:57 +1100 Subject: [PATCH 09/14] Turn off ProveInit for line Error says it is due to stdlib but it works a few lines later --- src/nimble.nim | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/nimble.nim b/src/nimble.nim index cd3a4340..56462d32 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -1644,7 +1644,10 @@ proc lock(options: Options) = # Add each individual tasks as partial sub graphs for task, requires in pkgInfo.taskRequires: let newDeps = pkgInfo.processFreeDependencies(requires, options) + {.push warning[ProveInit]: off.} + # Don't know why this isn't considered proved let fullInfo = newDeps.toSeq().map(pkg => pkg.toFullInfo(options)) + {.push warning[ProveInit]: on.} pkgInfo.validateDevelopDependenciesVersionRanges(fullInfo, options) # Add in the dependencies that are in this task but not in base taskDepNames[task] = initHashSet[string]() @@ -1655,6 +1658,7 @@ proc lock(options: Options) = # Reset the deps to what they were before hand. # Stops dependencies in this task overflowing into the next fullDeps.incl newDeps + # Now build graph for all dependencies options.checkSatisfied(fullDeps) let fullInfo = fullDeps.toSeq().map(pkg => pkg.toFullInfo(options)) pkgInfo.validateDevelopDependenciesVersionRanges(fullInfo, options) From b1f4e76bd9bcc9c8827fe6ba7058ebcfcc57de6f Mon Sep 17 00:00:00 2001 From: Ivan Yonchovski Date: Wed, 4 Jan 2023 11:30:29 +0200 Subject: [PATCH 10/14] Allow locking nim version in nimble.lock - Fixes #953 Allow having nim as locked dependency. - I will add unit tests once we agree on the approach and once nimble related changes in nim are merged (I will link the PR in comment). Ditto for the documentation. In order that change to work we have to add nim package in nimble packages repo and also add alias compiler -> nim to avoid breaking backward compatibility. Here it is the flow: ``` bash nimble develop nim nimble lock ``` After that `nimble install` and `nimble build` commands will use the locked `nim` version --- src/nimble.nim | 54 ++++++++++++++++++++++++------- src/nimblepkg/options.nim | 14 ++++++++ src/nimblepkg/packageinfo.nim | 5 +-- src/nimblepkg/packageparser.nim | 6 ++-- src/nimblepkg/tools.nim | 5 --- src/nimblepkg/topologicalsort.nim | 2 +- tests/nimdep/nimdep.nimble | 12 +++++++ tests/nimdep/src/demo.nim | 0 tests/tinitcommand.nim | 1 + tests/tlockfile.nim | 21 ++++++++++++ 10 files changed, 99 insertions(+), 21 deletions(-) create mode 100644 tests/nimdep/nimdep.nimble create mode 100644 tests/nimdep/src/demo.nim diff --git a/src/nimble.nim b/src/nimble.nim index 56462d32..4b600e5c 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -81,7 +81,8 @@ proc checkSatisfied(options: Options, dependencies: HashSet[PackageInfo]) = pkgsInPath[pkgInfo.basicInfo.name] = currentVer proc processFreeDependencies(pkgInfo: PackageInfo, requirements: seq[PkgTuple], - options: Options): HashSet[PackageInfo] = + options: Options, nimAsDependency = false): + HashSet[PackageInfo] = ## Verifies and installs dependencies. ## ## Returns set of PackageInfo (for paths) to pass to the compiler @@ -98,7 +99,7 @@ proc processFreeDependencies(pkgInfo: PackageInfo, requirements: seq[PkgTuple], var reverseDependencies: seq[PackageBasicInfo] = @[] for dep in requirements: - if dep.name == "nimrod" or dep.name == "nim": + if not nimAsDependency and dep.name.isNim: let nimVer = getNimrodVersion(options) if not withinRange(nimVer, dep.ver): let msg = "Unsatisfied dependency: " & dep.name & " (" & $dep.ver & ")" @@ -218,7 +219,7 @@ proc buildFromDir(pkgInfo: PackageInfo, paths: HashSet[string], # `quoteShell` would be more robust than `\"` (and avoid quoting when # un-necessary) but would require changing `extractBin` let cmd = "$# $# --colors:on --noNimblePath $# $# $#" % [ - getNimBin(options).quoteShell, pkgInfo.backend, join(args, " "), + pkgInfo.getNimBin(options).quoteShell, pkgInfo.backend, join(args, " "), outputOpt, input.quoteShell] try: doCmd(cmd) @@ -324,7 +325,7 @@ proc packageExists(pkgInfo: PackageInfo, options: Options): fillMetaData(oldPkgInfo, pkgDestDir, true) return some(oldPkgInfo) -proc processLockedDependencies(pkgInfo: PackageInfo, options: Options): +proc processLockedDependencies(pkgInfo: PackageInfo, options: Options, onlyNim = false): HashSet[PackageInfo] proc processAllDependencies(pkgInfo: PackageInfo, options: Options): @@ -342,6 +343,22 @@ proc allDependencies(pkgInfo: PackageInfo, options: Options): HashSet[PackageInf for requires in pkgInfo.taskRequires.values: result.incl pkgInfo.processFreeDependencies(requires, options) +proc useLockedNimIfNeeded(pkgInfo: PackageInfo, options: var Options) = + if pkgInfo.lockedDeps.len > 0: + var deps = pkgInfo.processLockedDependencies(options, true) + if deps.len != 0: + # process the first entry (hash.pop is triggering warnings) + for nimDep in deps: + const binaryName = when defined(windows): "nim.exe" else: "nim" + let nim = nimDep.getRealDir() / "bin" / binaryName + + if not fileExists(nim): + raise nimbleError("Trying to use nim from $1 " % nimDep.getRealDir(), + "If you are using develop mode nim make sure to compile it.") + + options.nim = nim + display("Info:", "using $1 for compilation" % options.nim, priority = HighPriority) + proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, url: string, first: bool, fromLockFile: bool, vcsRevision = notSetSha1Hash): @@ -413,10 +430,14 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, result.pkg = oldPkg return + # nim is intended only for local project local usage, so avoid installing it + # in .nimble/bin + let isNimPackage = pkgInfo.basicInfo.name.isNim + # Build before removing an existing package (if one exists). This way # if the build fails then the old package will still be installed. - if pkgInfo.bin.len > 0: + if pkgInfo.bin.len > 0 and not isNimPackage: let paths = result.deps.map(dep => dep.getRealDir()) let flags = if options.action.typ in {actionInstall, actionPath, actionUninstall, actionDevelop}: options.action.passNimFlags @@ -454,7 +475,7 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, filesInstalled.incl copyFileD(pkgInfo.myPath, dest) var binariesInstalled: HashSet[string] - if pkgInfo.bin.len > 0: + if pkgInfo.bin.len > 0 and not pkgInfo.basicInfo.name.isNim: # Make sure ~/.nimble/bin directory is created. createDir(binDir) # Set file permissions to +x for all binaries built, @@ -614,7 +635,7 @@ proc installDependency(pkgInfo: PackageInfo, downloadInfo: DownloadInfo, return newlyInstalledPkgInfo -proc processLockedDependencies(pkgInfo: PackageInfo, options: Options): +proc processLockedDependencies(pkgInfo: PackageInfo, options: Options, onlyNim = false): HashSet[PackageInfo] = # Returns a hash set with `PackageInfo` of all packages from the lock file of # the package `pkgInfo` by getting the info for develop mode dependencies from @@ -625,6 +646,8 @@ proc processLockedDependencies(pkgInfo: PackageInfo, options: Options): let developModeDeps = getDevelopDependencies(pkgInfo, options) for name, dep in pkgInfo.lockedDepsFor(options): + if onlyNim and not name.isNim: + continue if developModeDeps.hasKey(name): result.incl developModeDeps[name][] elif isInstalled(name, dep, options): @@ -723,9 +746,10 @@ proc build(pkgInfo: PackageInfo, options: Options) = var args = options.getCompilationFlags() buildFromDir(pkgInfo, paths, args, options) -proc build(options: Options) = +proc build(options: var Options) = let dir = getCurrentDir() let pkgInfo = getPkgInfo(dir, options) + useLockedNimIfNeeded(pkgInfo, options) pkgInfo.build(options) proc clean(options: Options) = @@ -780,7 +804,7 @@ proc execBackend(pkgInfo: PackageInfo, options: Options) = "backend") % [bin, pkgInfo.basicInfo.name, backend], priority = HighPriority) doCmd("$# $# --noNimblePath $# $# $#" % - [getNimBin(options).quoteShell, + [pkgInfo.getNimBin(options).quoteShell, backend, join(args, " "), bin.quoteShell, @@ -1312,7 +1336,7 @@ proc developFreeDependencies(pkgInfo: PackageInfo, "developFreeDependencies needs pkgInfo.requires" for dep in pkgInfo.requires: - if dep.name == "nimrod" or dep.name == "nim": + if dep.name.isNim: continue let resolvedDep = dep.resolveAlias(options) @@ -1629,7 +1653,11 @@ proc lock(options: Options) = # We need to process free dependencies for all tasks. # Then we can store each task as a seperate sub graph. - var deps = pkgInfo.processFreeDependencies(pkgInfo.requires, options) + let + includeNim = + pkgInfo.lockedDeps.contains("compiler") or + pkgInfo.getDevelopDependencies(options).contains("nim") + deps = pkgInfo.processFreeDependencies(pkgInfo.requires, options, includeNim) var fullDeps = deps # Deps shared by base and tasks # We need to seperate the graph into seperate tasks later @@ -2028,6 +2056,10 @@ proc doAction(options: var Options) = of actionRefresh: refresh(options) of actionInstall: + if options.action.packages.len == 0: + let pkgInfo = getPkgInfo(getCurrentDir(), options) + useLockedNimIfNeeded(pkgInfo, options) + let (_, pkgInfo) = install(options.action.packages, options, doPrompt = true, first = true, diff --git a/src/nimblepkg/options.nim b/src/nimblepkg/options.nim index 7b05e56e..b659b6e9 100644 --- a/src/nimblepkg/options.nim +++ b/src/nimblepkg/options.nim @@ -435,6 +435,20 @@ proc setNimBin*(options: var Options) = raise nimbleError( "Unable to find `nim` binary - add to $PATH or use `--nim`") +proc getNimbleFileDir*(pkgInfo: PackageInfo): string = + pkgInfo.myPath.splitFile.dir + +proc getNimBin*(pkgInfo: PackageInfo, options: Options): string = + if pkgInfo.basicInfo.name == "nim": + let binaryPath = when defined(windows): + "bin\nim.exe" + else: + "bin/nim" + result = pkgInfo.getNimbleFileDir() / binaryPath + display("Info:", "compiling nim package using $1" % result, priority = HighPriority) + else: + result = options.nim + proc getNimBin*(options: Options): string = return options.nim diff --git a/src/nimblepkg/packageinfo.nim b/src/nimblepkg/packageinfo.nim index ea0ed191..68574cc4 100644 --- a/src/nimblepkg/packageinfo.nim +++ b/src/nimblepkg/packageinfo.nim @@ -360,8 +360,6 @@ proc findAllPkgs*(pkglist: seq[PackageInfo], dep: PkgTuple): seq[PackageInfo] = if withinRange(pkg, dep.ver): result.add pkg -proc getNimbleFileDir*(pkgInfo: PackageInfo): string = - pkgInfo.myPath.splitFile.dir proc getRealDir*(pkgInfo: PackageInfo): string = ## Returns the directory containing the package source files. @@ -560,6 +558,9 @@ proc hash*(x: PackageInfo): Hash = proc getNameAndVersion*(pkgInfo: PackageInfo): string = &"{pkgInfo.basicInfo.name}@{pkgInfo.basicInfo.version}" +proc isNim*(name: string): bool = + result = name == "nim" or name == "nimrod" or name == "compiler" + when isMainModule: import unittest diff --git a/src/nimblepkg/packageparser.nim b/src/nimblepkg/packageparser.nim index 039eb7e0..9ee75c2c 100644 --- a/src/nimblepkg/packageparser.nim +++ b/src/nimblepkg/packageparser.nim @@ -184,7 +184,9 @@ proc validatePackageInfo(pkgInfo: PackageInfo, options: Options) = raise validationError("'" & pkgInfo.backend & "' is an invalid backend.", false) - validatePackageStructure(pkginfo, options) + # nim is used for building the project, thus no need to validate its structure. + if not pkgInfo.basicInfo.name.isNim: + validatePackageStructure(pkginfo, options) proc nimScriptHint*(pkgInfo: PackageInfo) = if not pkgInfo.isNimScript: @@ -323,7 +325,7 @@ proc inferInstallRules(pkgInfo: var PackageInfo, options: Options) = # installed.) let installInstructions = pkgInfo.installDirs.len + pkgInfo.installExt.len + pkgInfo.installFiles.len - if installInstructions == 0 and pkgInfo.bin.len > 0: + if installInstructions == 0 and pkgInfo.bin.len > 0 and pkgInfo.basicInfo.name != "nim": pkgInfo.skipExt.add("nim") # When a package doesn't specify a `srcDir` it's fair to assume that diff --git a/src/nimblepkg/tools.nim b/src/nimblepkg/tools.nim index 29b7027b..e3688102 100644 --- a/src/nimblepkg/tools.nim +++ b/src/nimblepkg/tools.nim @@ -62,11 +62,6 @@ proc tryDoCmdEx*(cmd: string): string {.discardable.} = raise nimbleError(tryDoCmdExErrorMessage(cmd, output, exitCode)) return output -proc getNimBin*: string = - result = "nim" - if findExe("nim") != "": result = findExe("nim") - elif findExe("nimrod") != "": result = findExe("nimrod") - proc getNimrodVersion*(options: Options): Version = let vOutput = doCmdEx(getNimBin(options).quoteShell & " -v").output var matches: array[0..MaxSubpatterns, string] diff --git a/src/nimblepkg/topologicalsort.nim b/src/nimblepkg/topologicalsort.nim index 38dead23..8e0c1fef 100644 --- a/src/nimblepkg/topologicalsort.nim +++ b/src/nimblepkg/topologicalsort.nim @@ -11,7 +11,7 @@ proc getDependencies(packages: seq[PackageInfo], package: PackageInfo, ## package. It is needed because some of the names of the packages in the ## `requires` clause of a package could be URLs. for dep in package.requires: - if dep.name == "nim": + if dep.name.isNim: continue var depPkgInfo = initPackageInfo() var found = findPkg(packages, dep, depPkgInfo) diff --git a/tests/nimdep/nimdep.nimble b/tests/nimdep/nimdep.nimble new file mode 100644 index 00000000..2046ede9 --- /dev/null +++ b/tests/nimdep/nimdep.nimble @@ -0,0 +1,12 @@ +# Package + +version = "0.1.0" +author = "Ivan Yonchovski" +description = "A new awesome nimble package" +license = "MIT" +srcDir = "src" +bin = @["demo"] + +# Dependencies + +requires "nim == 1.7.1" diff --git a/tests/nimdep/src/demo.nim b/tests/nimdep/src/demo.nim new file mode 100644 index 00000000..e69de29b diff --git a/tests/tinitcommand.nim b/tests/tinitcommand.nim index 8972427f..d6d4240f 100644 --- a/tests/tinitcommand.nim +++ b/tests/tinitcommand.nim @@ -9,6 +9,7 @@ suite "init": ## https://github.com/nim-lang/nimble/pull/983 test "init within directory that is invalid package name will not create new directory": let tempdir = getTempDir() / "a-b" + if dirExists tempdir: removeDir(tempDir) createDir(tempdir) cd(tempdir): let args = ["init"] diff --git a/tests/tlockfile.nim b/tests/tlockfile.nim index 9c382534..06ae3bac 100644 --- a/tests/tlockfile.nim +++ b/tests/tlockfile.nim @@ -591,3 +591,24 @@ requires "nim >= 1.5.1" writeDevelopFile(developFileName, @[], @[dep1PkgRepoPath, mainPkgOriginRepoPath]) let (_, exitCode) = execNimbleYes("--debug", "--verbose", "sync") check exitCode == QuitSuccess + + test "can generate lock file for nim as dep": + cleanUp() + cd "nimdep": + removeFile "nimble.develop" + removeFile "nimble.lock" + removeDir "Nim" + + check execNimbleYes("develop", "nim").exitCode == QuitSuccess + cd "Nim": + let (_, exitCode) = execNimbleYes("-y", "install") + check exitCode == QuitSuccess + + # check if the compiler version will be used when doing build + testLockFile(@[("nim", "Nim")], isNew = true) + removeFile "nimble.develop" + removeDir "Nim" + + let (output, exitCodeInstall) = execNimbleYes("-y", "build") + check exitCodeInstall == QuitSuccess + check output.contains("bin/nim for compilation") From dc7c3dba4b101611087d2a531b9f65515455d393 Mon Sep 17 00:00:00 2001 From: Ivan Yonchovski Date: Mon, 9 Jan 2023 16:29:09 +0200 Subject: [PATCH 11/14] Force using nim defined in the lock file for tasks --- .gitignore | 1 + src/nimble.nim | 4 ++++ tests/nimdep/nimdep.nimble | 5 ++++- tests/tlockfile.nim | 6 ++++++ 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ec6622fd..99d2cbd4 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,4 @@ src/nimblepkg/version # Test procedure artifacts *.nims /buildTests +/tests/nimdep/nimble.lock diff --git a/src/nimble.nim b/src/nimble.nim index 4b600e5c..d109bcec 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -357,6 +357,9 @@ proc useLockedNimIfNeeded(pkgInfo: PackageInfo, options: var Options) = "If you are using develop mode nim make sure to compile it.") options.nim = nim + let separator = when defined(windows): ";" else: ":" + + putEnv("PATH", nimDep.getRealDir() / "bin" & separator & getEnv("PATH")) display("Info:", "using $1 for compilation" % options.nim, priority = HighPriority) proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, @@ -2128,6 +2131,7 @@ proc doAction(options: var Options) = discard pkgInfo.processAllDependencies(optsCopy) # If valid task defined in nimscript, run it var execResult: ExecutionResult[bool] + useLockedNimIfNeeded(pkgInfo, optsCopy) if execCustom(nimbleFile, optsCopy, execResult): if execResult.hasTaskRequestedCommand(): var options = execResult.getOptionsForCommand(optsCopy) diff --git a/tests/nimdep/nimdep.nimble b/tests/nimdep/nimdep.nimble index 2046ede9..ca236877 100644 --- a/tests/nimdep/nimdep.nimble +++ b/tests/nimdep/nimdep.nimble @@ -9,4 +9,7 @@ bin = @["demo"] # Dependencies -requires "nim == 1.7.1" +requires "nim == 1.7.1" + +task version, "Test nim version": + exec "nim --version" diff --git a/tests/tlockfile.nim b/tests/tlockfile.nim index 06ae3bac..2b7bfbb2 100644 --- a/tests/tlockfile.nim +++ b/tests/tlockfile.nim @@ -591,6 +591,8 @@ requires "nim >= 1.5.1" writeDevelopFile(developFileName, @[], @[dep1PkgRepoPath, mainPkgOriginRepoPath]) let (_, exitCode) = execNimbleYes("--debug", "--verbose", "sync") check exitCode == QuitSuccess + proc getRevision(dep: string, lockFileName = defaultLockFileName): string = + result = lockFileName.readFile.parseJson{$lfjkPackages}{dep}{$lfjkPkgVcsRevision}.str test "can generate lock file for nim as dep": cleanUp() @@ -612,3 +614,7 @@ requires "nim >= 1.5.1" let (output, exitCodeInstall) = execNimbleYes("-y", "build") check exitCodeInstall == QuitSuccess check output.contains("bin/nim for compilation") + + # check the nim version + let (outputVersion, _) = execNimble("version") + check outputVersion.contains(getRevision("nim")) From b5fa8092fda3c30fd95d66bfb974dae0e7c61454 Mon Sep 17 00:00:00 2001 From: Ivan Yonchovski Date: Thu, 19 Jan 2023 12:40:30 +0200 Subject: [PATCH 12/14] Add --use-system-nim setting --- src/nimble.nim | 2 +- src/nimblepkg/options.nim | 6 +++++- tests/nimdep/nimdep.nimble | 2 +- tests/tlockfile.nim | 4 ++++ 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/nimble.nim b/src/nimble.nim index d109bcec..87b388dd 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -344,7 +344,7 @@ proc allDependencies(pkgInfo: PackageInfo, options: Options): HashSet[PackageInf result.incl pkgInfo.processFreeDependencies(requires, options) proc useLockedNimIfNeeded(pkgInfo: PackageInfo, options: var Options) = - if pkgInfo.lockedDeps.len > 0: + if pkgInfo.lockedDeps.len > 0 and not options.useSystemNim: var deps = pkgInfo.processLockedDependencies(options, true) if deps.len != 0: # process the first entry (hash.pop is triggering warnings) diff --git a/src/nimblepkg/options.nim b/src/nimblepkg/options.nim index b659b6e9..a442084a 100644 --- a/src/nimblepkg/options.nim +++ b/src/nimblepkg/options.nim @@ -28,6 +28,7 @@ type pkgInfoCache*: TableRef[string, PackageInfo] showHelp*: bool lockFileName*: string + useSystemNim*: bool showVersion*: bool offline*: bool noColor*: bool @@ -112,7 +113,7 @@ Usage: nimble [nimbleopts] COMMAND [cmdopts] Commands: install [pkgname, ...] Installs a list of packages. [-d, --depsOnly] Only install dependencies. Leave out pkgname - to install deps for a local nimble package. + to install deps for a local nimble package. [-p, --passNim] Forward specified flag to compiler. [--noRebuild] Don't rebuild binaries if they're up-to-date. develop [pkgname, ...] Clones a list of packages for development. @@ -228,6 +229,8 @@ Nimble Options: --noColor Don't colorise output. --noSSLCheck Don't check SSL certificates. --lock-file Override the lock file name. + --use-system-nim Use system nim and ignore nim from the lock + file if any For more information read the Github readme: https://github.com/nim-lang/nimble#readme @@ -532,6 +535,7 @@ proc parseFlag*(flag, val: string, result: var Options, kind = cmdLongOption) = of "tarballs", "t": result.enableTarballs = true of "package", "p": result.package = val of "lock-file": result.lockFileName = val + of "use-system-nim": result.useSystemNim = true else: isGlobalFlag = false var wasFlagHandled = true diff --git a/tests/nimdep/nimdep.nimble b/tests/nimdep/nimdep.nimble index ca236877..7da37d48 100644 --- a/tests/nimdep/nimdep.nimble +++ b/tests/nimdep/nimdep.nimble @@ -9,7 +9,7 @@ bin = @["demo"] # Dependencies -requires "nim == 1.7.1" +requires "nim" task version, "Test nim version": exec "nim --version" diff --git a/tests/tlockfile.nim b/tests/tlockfile.nim index 2b7bfbb2..99b4b7a9 100644 --- a/tests/tlockfile.nim +++ b/tests/tlockfile.nim @@ -618,3 +618,7 @@ requires "nim >= 1.5.1" # check the nim version let (outputVersion, _) = execNimble("version") check outputVersion.contains(getRevision("nim")) + + let (outputGlobalNim, exitCodeGlobalNim) = execNimbleYes("-y", "--use-system-nim", "build") + check exitCodeGlobalNim == QuitSuccess + check not outputGlobalNim.contains("bin/nim for compilation") From 5dc23bc530f4c8c02f5f8024f2adfb0e14626e15 Mon Sep 17 00:00:00 2001 From: Ivan Yonchovski Date: Wed, 1 Feb 2023 13:03:48 +0200 Subject: [PATCH 13/14] Fix the tests on Windows --- tests/tlockfile.nim | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/tlockfile.nim b/tests/tlockfile.nim index 99b4b7a9..dcd8ff7d 100644 --- a/tests/tlockfile.nim +++ b/tests/tlockfile.nim @@ -613,7 +613,8 @@ requires "nim >= 1.5.1" let (output, exitCodeInstall) = execNimbleYes("-y", "build") check exitCodeInstall == QuitSuccess - check output.contains("bin/nim for compilation") + let usingNim = when defined(Windows): "nim.exe for compilation" else: "bin/nim for compilation" + check output.contains(usingNim) # check the nim version let (outputVersion, _) = execNimble("version") @@ -621,4 +622,4 @@ requires "nim >= 1.5.1" let (outputGlobalNim, exitCodeGlobalNim) = execNimbleYes("-y", "--use-system-nim", "build") check exitCodeGlobalNim == QuitSuccess - check not outputGlobalNim.contains("bin/nim for compilation") + check not outputGlobalNim.contains(usingNim) From 8e5501b81ff953dd15c16d2f0e82d47cb27f2bfc Mon Sep 17 00:00:00 2001 From: Ivan Yonchovski Date: Mon, 6 Feb 2023 14:59:34 +0200 Subject: [PATCH 14/14] Implement support for getPaths()/getPathsClause --- src/nimble.nim | 23 ++++++++++++++++++----- src/nimblepkg/nimscriptapi.nim | 11 ++++++++++- src/nimblepkg/options.nim | 3 +++ src/nimblepkg/packageinfo.nim | 3 ++- tests/tasks/getpaths/getpaths.nimble | 17 +++++++++++++++++ tests/tgetpaths.nim | 17 +++++++++++++++++ 6 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 tests/tasks/getpaths/getpaths.nimble create mode 100644 tests/tgetpaths.nim diff --git a/src/nimble.nim b/src/nimble.nim index 87b388dd..673411c9 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -27,6 +27,7 @@ const nimbleConfigFileName* = "config.nims" gitIgnoreFileName = ".gitignore" hgIgnoreFileName = ".hgignore" + nimblePathsEnv = "__NIMBLE_PATHS" proc refresh(options: Options) = ## Downloads the package list from the specified URL. @@ -328,6 +329,9 @@ proc packageExists(pkgInfo: PackageInfo, options: Options): proc processLockedDependencies(pkgInfo: PackageInfo, options: Options, onlyNim = false): HashSet[PackageInfo] +proc getDependenciesPaths(pkgInfo: PackageInfo, options: Options): + HashSet[string] + proc processAllDependencies(pkgInfo: PackageInfo, options: Options): HashSet[PackageInfo] = if pkgInfo.hasLockedDeps(): @@ -337,6 +341,8 @@ proc processAllDependencies(pkgInfo: PackageInfo, options: Options): if options.task in pkgInfo.taskRequires: result.incl pkgInfo.processFreeDependencies(pkgInfo.taskRequires[options.task], options) + putEnv(nimblePathsEnv, result.map(dep => dep.getRealDir()).toSeq().join("|")) + proc allDependencies(pkgInfo: PackageInfo, options: Options): HashSet[PackageInfo] = ## Returns all dependencies for a package (Including tasks) result.incl pkgInfo.processFreeDependencies(pkgInfo.requires, options) @@ -364,7 +370,8 @@ proc useLockedNimIfNeeded(pkgInfo: PackageInfo, options: var Options) = proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, url: string, first: bool, fromLockFile: bool, - vcsRevision = notSetSha1Hash): + vcsRevision = notSetSha1Hash, + deps: seq[PackageInfo] = @[]): PackageDependenciesInfo = ## Returns where package has been installed to, together with paths ## to the packages this package depends on. @@ -409,6 +416,8 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, result.deps = pkgInfo.processLockedDependencies(depsOptions) elif not fromLockFile: result.deps = pkgInfo.processFreeDependencies(pkgInfo.requires, depsOptions) + else: + result.deps = deps.toHashSet if options.depsOnly: result.pkg = pkgInfo @@ -617,7 +626,8 @@ proc downloadDependency(name: string, dep: LockFileDep, options: Options): vcsRevision: vcsRevision) proc installDependency(pkgInfo: PackageInfo, downloadInfo: DownloadInfo, - options: Options): PackageInfo = + options: Options, + deps: seq[PackageInfo]): PackageInfo = ## Installs an already downloaded dependency of the package `pkgInfo`. let (_, newlyInstalledPkgInfo) = installFromDir( downloadInfo.downloadDir, @@ -626,7 +636,8 @@ proc installDependency(pkgInfo: PackageInfo, downloadInfo: DownloadInfo, downloadInfo.url, first = false, fromLockFile = true, - downloadInfo.vcsRevision) + downloadInfo.vcsRevision, + deps = deps) downloadInfo.downloadDir.removeDir let deps = pkgInfo.lockedDeps[noTask] @@ -656,8 +667,10 @@ proc processLockedDependencies(pkgInfo: PackageInfo, options: Options, onlyNim = elif isInstalled(name, dep, options): result.incl getDependency(name, dep, options) elif not options.offline: - let downloadResult = downloadDependency(name, dep, options) - result.incl installDependency(pkgInfo, downloadResult, options) + let + downloadResult = downloadDependency(name, dep, options) + dependencies = result.toSeq.filterIt(dep.dependencies.contains(it.name)) + result.incl installDependency(pkgInfo, downloadResult, options, dependencies) else: raise nimbleError("Unsatisfied dependency: " & pkgInfo.basicInfo.name) diff --git a/src/nimblepkg/nimscriptapi.nim b/src/nimblepkg/nimscriptapi.nim index 2bd34b2e..bca23450 100644 --- a/src/nimblepkg/nimscriptapi.nim +++ b/src/nimblepkg/nimscriptapi.nim @@ -42,6 +42,7 @@ var project = "" success = false retVal = true + nimblePathsEnv = "__NIMBLE_PATHS" proc requires*(deps: varargs[string]) = ## Call this to set the list of requirements of your Nimble @@ -201,7 +202,7 @@ template task*(name: untyped; description: string; body: untyped): untyped = proc `name Task`*() = body nimbleTasks.add (astToStr(name), description) - + if actionName.len == 0 or actionName == "help": success = true elif actionName == astToStr(name).normalize: @@ -238,3 +239,11 @@ proc getPkgDir*(): string = result = projectFile.rsplit(seps={'/', '\\', ':'}, maxsplit=1)[0] proc thisDir*(): string = getPkgDir() + +proc getPaths*(): seq[string] = + ## Returns the paths to the dependencies + return getEnv(nimblePathsEnv).split("|") + +proc getPathsClause*(): string = + ## Returns the paths to the dependencies as consumed by the nim compiler. + return getPaths().mapIt("--path:" & it.quoteShell).join(" ") diff --git a/src/nimblepkg/options.nim b/src/nimblepkg/options.nim index a442084a..5baec4d8 100644 --- a/src/nimblepkg/options.nim +++ b/src/nimblepkg/options.nim @@ -45,6 +45,7 @@ type localdeps*: bool # True if project local deps mode developLocaldeps*: bool # True if local deps + nimble develop pkg1 ... disableSslCertCheck*: bool + disableLockFile*: bool enableTarballs*: bool # Enable downloading of packages as tarballs from GitHub. task*: string # Name of the task that is getting ran package*: string @@ -229,6 +230,7 @@ Nimble Options: --noColor Don't colorise output. --noSSLCheck Don't check SSL certificates. --lock-file Override the lock file name. + --noLockFile Ignore the lock file if present. --use-system-nim Use system nim and ignore nim from the lock file if any @@ -532,6 +534,7 @@ proc parseFlag*(flag, val: string, result: var Options, kind = cmdLongOption) = of "nim": result.nim = val of "localdeps", "l": result.localdeps = true of "nosslcheck": result.disableSslCertCheck = true + of "nolockfile": result.disableLockFile = true of "tarballs", "t": result.enableTarballs = true of "package", "p": result.package = val of "lock-file": result.lockFileName = val diff --git a/src/nimblepkg/packageinfo.nim b/src/nimblepkg/packageinfo.nim index 68574cc4..0e3add69 100644 --- a/src/nimblepkg/packageinfo.nim +++ b/src/nimblepkg/packageinfo.nim @@ -37,7 +37,8 @@ proc initPackageInfo*(options: Options, filePath: string): PackageInfo = result.myPath = filePath result.basicInfo.name = fileName result.backend = "c" - result.lockedDeps = options.lockFile(fileDir).getLockedDependencies() + if not options.disableLockFile: + result.lockedDeps = options.lockFile(fileDir).getLockedDependencies() proc toValidPackageName*(name: string): string = for c in name: diff --git a/tests/tasks/getpaths/getpaths.nimble b/tests/tasks/getpaths/getpaths.nimble new file mode 100644 index 00000000..7b56d241 --- /dev/null +++ b/tests/tasks/getpaths/getpaths.nimble @@ -0,0 +1,17 @@ +# Package + +version = "0.1.0" +author = "Ivan Yonchovski" +description = "A new awesome nimble package" +license = "MIT" +srcDir = "src" +bin = @["run"] + + +# Dependencies + +requires "benchy", "unittest2" + +task echoPaths, "": + echo getPaths() + echo getPathsClause() diff --git a/tests/tgetpaths.nim b/tests/tgetpaths.nim new file mode 100644 index 00000000..9c3a66d6 --- /dev/null +++ b/tests/tgetpaths.nim @@ -0,0 +1,17 @@ +# Copyright (C) Dominik Picheta. All rights reserved. +# BSD License. Look at license.txt for more info. + +{.used.} + +import unittest, strutils, os +import testscommon +from nimblepkg/common import cd + +suite "nimble getPaths/getPathsClause": + test "check getPaths result": + cd "tasks/getpaths": + let (output, exitCode) = execNimble("echoPaths") + check output.contains("--path:") + check output.contains("benchy") + check output.contains("unittest2") + check exitCode == QuitSuccess