diff --git a/src/nimble.nim b/src/nimble.nim index a5d5cd07..0df36653 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -34,7 +34,17 @@ const hgIgnoreFileName = ".hgignore" separator = when defined(windows): ";" else: ":" -proc initPkgList(pkgInfo: PackageInfo, options: Options, nimBin: string): seq[PackageInfo] = +template withNimBinFallback*(nimBin: var Option[string], options: Options, body: untyped) = + ## Catch NeedsNimBinError, resolve bootstrap nim lazily, and retry. + ## Use around any call that may trigger VM-parser fallback with nimBin=none. + try: + body + except NeedsNimBinError: + if nimBin.isNone: + nimBin = some(ensureBootstrapNim(options)) + body + +proc initPkgList(pkgInfo: PackageInfo, options: Options, nimBin: Option[string]): seq[PackageInfo] = let installedPkgs = getInstalledPkgsMin(options.getPkgsDir(), options) developPkgs = processDevelopDependencies(pkgInfo, options, nimBin) @@ -52,18 +62,18 @@ proc initPkgList(pkgInfo: PackageInfo, options: Options, nimBin: string): seq[Pa proc install(packages: seq[PkgTuple], options: Options, doPrompt, first, fromLockFile: bool, - nimBin: string = "", + nimBin: Option[string] = none(string), preferredPackages: seq[PackageInfo] = @[]): PackageDependenciesInfo -proc getNimDir(options: var Options, nimBin: string): string +proc getNimDir(options: var Options, nimBin: var Option[string]): string -proc solvePkgs(rootPackage: PackageInfo, options: var Options, nimBin: string) {.instrument.} +proc solvePkgs(rootPackage: PackageInfo, options: var Options, nimBin: var Option[string]) {.instrument.} -proc setup(options: Options, nimBin: string) +proc setup(options: Options, nimBin: Option[string]) -proc develop(options: var Options, nimBin: string) +proc develop(options: var Options, nimBinParam: Option[string]) -proc cleanFromDir(pkgInfo: PackageInfo, nimBin: string, options: Options) = +proc cleanFromDir(pkgInfo: PackageInfo, nimBin: Option[string], options: Options) = ## Clean up build files. # Handle pre-`clean` hook. let pkgDir = pkgInfo.myPath.parentDir() @@ -105,7 +115,7 @@ proc removeBinariesSymlinks(pkgInfo: PackageInfo, binDir: string) = removeFile(binDir / bin.changeFileExt("cmd")) removeFile(binDir / bin) -proc reinstallSymlinksForOlderVersion(pkgDir: string, options: Options, nimBin: string) = +proc reinstallSymlinksForOlderVersion(pkgDir: string, options: Options, nimBin: Option[string]) = let (pkgName, _, _) = getNameVersionChecksum(pkgDir) let pkgList = getInstalledPkgsMin(options.getPkgsDir(), options) var newPkgInfo = initPackageInfo() @@ -116,7 +126,7 @@ proc reinstallSymlinksForOlderVersion(pkgDir: string, options: Options, nimBin: let symlinkFilename = options.getBinDir() / bin.extractFilename discard setupBinSymlink(symlinkDest, symlinkFilename, options) -proc removePackage(pkgInfo: PackageInfo, options: Options, nimBin: string) = +proc removePackage(pkgInfo: PackageInfo, options: Options, nimBin: Option[string]) = var pkgInfo = pkgInfo let pkgDestDir = pkgInfo.getPkgDest(options) @@ -132,7 +142,7 @@ proc removePackage(pkgInfo: PackageInfo, options: Options, nimBin: string) = reinstallSymlinksForOlderVersion(pkgDestDir, options, nimBin) options.nimbleData.removeRevDep(pkgInfo) -proc packageExists(pkgInfo: PackageInfo, options: Options, nimBin: string): +proc packageExists(pkgInfo: PackageInfo, options: Options, nimBin: Option[string]): Option[PackageInfo] = ## Checks whether a package `pkgInfo` already exists in the Nimble cache. If a ## package already exists returns the `PackageInfo` of the package in the @@ -153,7 +163,7 @@ proc packageExists(pkgInfo: PackageInfo, options: Options, nimBin: string): proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, url: string, first: bool, fromLockFile: bool, - nimBin: string, + nimBin: Option[string], vcsRevision = notSetSha1Hash, deps: seq[PackageInfo] = @[], preferredPackages: seq[PackageInfo] = @[]): @@ -283,7 +293,7 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, cd pkgInfo.myPath.splitFile.dir: discard execHook(nimBin, options, actionInstall, false) -proc installNimToPkgs2*(nimPkgInfo: PackageInfo, options: Options, nimBin: string): PackageInfo = +proc installNimToPkgs2*(nimPkgInfo: PackageInfo, options: Options, nimBin: Option[string]): PackageInfo = ## Installs nim to pkgs2 directory by copying from nimbinaries. ## This ensures nim is available like other packages for dependency resolution. ## Only applies to nim installed via nimbinaries, not system nim. @@ -357,7 +367,7 @@ proc raiseCannotCloneInExistingDirException(downloadDir: string) = proc install(packages: seq[PkgTuple], options: Options, doPrompt, first, fromLockFile: bool, - nimBin: string = "", + nimBin: Option[string] = none(string), preferredPackages: seq[PackageInfo] = @[]): PackageDependenciesInfo = ## ``first`` ## True if this is the first level of the indirect recursion. @@ -426,7 +436,7 @@ proc install(packages: seq[PkgTuple], options: Options, else: raise -proc addPackages(packages: seq[PkgTuple], options: var Options, nimBin: string) = +proc addPackages(packages: seq[PkgTuple], options: var Options, nimBin: Option[string]) = if packages.len == 0: raise nimbleError( "Expected packages to add to dependencies, got none." @@ -510,13 +520,13 @@ proc addPackages(packages: seq[PkgTuple], options: var Options, nimBin: string) priority = HighPriority ) -proc clean(options: Options, nimBin: string) = +proc clean(options: Options, nimBin: Option[string]) = let dir = getCurrentDir() let pkgInfo = getPkgInfo(dir, options, nimBin = nimBin) nimScriptHint(pkgInfo) cleanFromDir(pkgInfo, nimBin, options) -proc execBackend(pkgInfo: PackageInfo, options: Options, nimBin: string) = +proc execBackend(pkgInfo: PackageInfo, options: Options, nimBin: Option[string]) = let bin = options.getCompilationBinary(pkgInfo).get("") binDotNim = bin.addFileExt("nim") @@ -560,7 +570,7 @@ proc execBackend(pkgInfo: PackageInfo, options: Options, nimBin: string) = "backend") % [bin, pkgInfo.basicInfo.name, backend], priority = HighPriority) doCmd("$# $# --noNimblePath $# $# $#" % - [nimBin.quoteShell, + [nimBin.get.quoteShell, backend, join(args, " "), bin.quoteShell, @@ -716,7 +726,7 @@ proc join(x: seq[PkgTuple]; y: string): string = result.add y result.add x[i][0] & " " & $x[i][1] -proc getPackageByPattern(pattern: string, options: Options, nimBin: string): PackageInfo = +proc getPackageByPattern(pattern: string, options: Options, nimBin: Option[string]): PackageInfo = ## Search for a package file using multiple strategies. if pattern == "": # Not specified - using current directory @@ -748,7 +758,7 @@ proc getEntryPoints(pkgInfo: PackageInfo, options: Options): seq[string] = for entry in entries: result.add if entry.endsWith(".nim"): entry else: entry & ".nim" -proc dump(options: var Options, nimBin: string) = +proc dump(options: var Options, nimBin: var Option[string]) = var p = getPackageByPattern(options.action.projName, options, nimBin) if options.action.collect or options.action.solve: p.requires &= options.extraRequires @@ -814,7 +824,7 @@ proc dump(options: var Options, nimBin: string) = s = j.pretty echo s -proc init(options: Options, nimBin: string) = +proc init(options: Options, nimBin: Option[string]) = # Check whether the vcs is installed. let vcsBin = options.action.vcsOption if vcsBin != "" and findExe(vcsBin, true) == "": @@ -972,7 +982,7 @@ Please specify a valid SPDX identifier.""", display("Success:", "Package $# created successfully" % [pkgName], Success, HighPriority) -proc removePackages(pkgs: HashSet[ReverseDependency], options: var Options, nimBin: string) = +proc removePackages(pkgs: HashSet[ReverseDependency], options: var Options, nimBin: Option[string]) = for pkg in pkgs: let pkgInfo = pkg.toPkgInfo(options, nimBin = nimBin) case pkg.kind @@ -988,7 +998,7 @@ proc collectNames(pkgs: HashSet[ReverseDependency], if pkg.kind != rdkDevelop or includeDevelopRevDeps: result.add $pkg -proc uninstall(options: var Options, nimBin: string) = +proc uninstall(options: var Options, nimBin: Option[string]) = if options.action.packages.len == 0: raise nimbleError( "Please specify the package(s) to uninstall.") @@ -1026,7 +1036,7 @@ proc uninstall(options: var Options, nimBin: string) = removePackages(pkgsToDelete, options, nimBin) -proc listTasks(options: Options, nimBin: string) = +proc listTasks(options: Options, nimBin: Option[string]) = let nimbleFile = findNimbleFile(getCurrentDir(), true, options) nimscriptwrapper.listTasks(nimBin, nimbleFile, options) @@ -1045,7 +1055,7 @@ proc saveLinkFile(pkgInfo: PackageInfo, options: Options) = writeFile(pkgLinkFilePath, pkgLinkFileContent) displaySuccess(pkgLinkFileSavedMsg(pkgLinkFilePath)) -proc developFromDir(pkgInfo: PackageInfo, options: var Options, topLevel = false, nimBin: string) = +proc developFromDir(pkgInfo: PackageInfo, options: var Options, topLevel = false, nimBin: Option[string]) = assert options.action.typ == actionDevelop, "This procedure should be called only when executing develop sub-command." @@ -1074,8 +1084,9 @@ proc developFromDir(pkgInfo: PackageInfo, options: var Options, topLevel = false cd dir: discard execHook(nimBin, options, actionDevelop, false) -proc installDevelopPackage(pkgTup: PkgTuple, options: var Options, nimBin: string): +proc installDevelopPackage(pkgTup: PkgTuple, options: var Options, nimBinParam: Option[string]): PackageInfo = + var nimBin = nimBinParam let (meth, url, metadata) = getDownloadInfo(pkgTup, options, true) let subdir = metadata.getOrDefault("subdir") let downloadDir = getDevelopDownloadDir(url, subdir, options) @@ -1084,7 +1095,9 @@ proc installDevelopPackage(pkgTup: PkgTuple, options: var Options, nimBin: strin if options.developWithDependencies: displayWarning(skipDownloadingInAlreadyExistingDirectoryMsg( downloadDir, pkgTup.name)) - let pkgInfo = getPkgInfo(downloadDir, options, nimBin = nimBin) + var pkgInfo: PackageInfo + withNimBinFallback(nimBin, options): + pkgInfo = getPkgInfo(downloadDir, options, nimBin = nimBin) developFromDir(pkgInfo, options, nimBin = nimBin) options.action.devActions.add( (datAdd, pkgInfo.getNimbleFileDir.normalizedPath)) @@ -1104,7 +1117,9 @@ proc installDevelopPackage(pkgTup: PkgTuple, options: var Options, nimBin: strin vcsRevision = notSetSha1Hash, nimBin = nimBin) let pkgDir = downloadDir / subdir - var pkgInfo = getPkgInfo(pkgDir, options, nimBin = nimBin) + var pkgInfo: PackageInfo + withNimBinFallback(nimBin, options): + pkgInfo = getPkgInfo(pkgDir, options, nimBin = nimBin) developFromDir(pkgInfo, options, nimBin = nimBin) options.action.devActions.add( @@ -1116,9 +1131,9 @@ proc installDevelopPackage(pkgTup: PkgTuple, options: var Options, nimBin: strin return pkgInfo -proc updateSyncFile(dependentPkg: PackageInfo, options: Options, nimBin: string) +proc updateSyncFile(dependentPkg: PackageInfo, options: Options, nimBin: Option[string]) -proc updatePathsFile(pkgInfo: PackageInfo, options: Options, nimBin: string) = +proc updatePathsFile(pkgInfo: PackageInfo, options: Options, nimBin: Option[string]) = var paths = initHashSet[seq[string]]() for path in options.getPathsAllPkgs(nimBin): paths.incl @[path] @@ -1131,7 +1146,8 @@ proc updatePathsFile(pkgInfo: PackageInfo, options: Options, nimBin: string) = var msgPriority = if options.action.typ == actionSetup: HighPriority else: LowPriority displayInfo(&"\"{nimblePathsFileName}\" is {action}.", msgPriority) -proc develop(options: var Options, nimBin: string) = +proc develop(options: var Options, nimBinParam: Option[string]) = + var nimBin = nimBinParam if options.action.path.len == 0: # If no path is provided, use the vendor folder as default options.action.path = defaultDevelopPath @@ -1141,7 +1157,7 @@ proc develop(options: var Options, nimBin: string) = isDefaultPath = options.action.path == defaultDevelopPath hasDevActions = options.action.devActions.len > 0 hasDevFile = options.developFile.len > 0 - withDependencies = options.action.withDependencies + withDependencies = options.action.withDependencies var currentDirPkgInfo = initPackageInfo() @@ -1149,6 +1165,10 @@ proc develop(options: var Options, nimBin: string) = try: # Check whether the current directory is a package directory. + # getPkgInfo with pikFull needs a nim binary for VM parsing, so ensure + # bootstrap is resolved before calling it. + if nimBin.isNone: + nimBin = some(ensureBootstrapNim(options)) currentDirPkgInfo = getPkgInfo(getCurrentDir(), options, nimBin = nimBin) except CatchableError as error: if hasDevActions and not hasDevFile: @@ -1193,7 +1213,7 @@ proc develop(options: var Options, nimBin: string) = "There are some errors while executing the operation.", "See the log above for more details.") -proc test(options: Options, nimBin: string) = +proc test(options: Options, nimBin: Option[string]) = ## Executes all tests starting with 't' in the ``tests`` directory. ## Subdirectories are not walked. var pkgInfo = getPkgInfo(getCurrentDir(), options, nimBin = nimBin) @@ -1270,7 +1290,7 @@ proc notInRequiredRangeMsg*(dependentPkg, dependencyPkg: PackageInfo, $versionRange) proc validateDevelopDependenciesVersionRanges(dependentPkg: PackageInfo, - dependencies: seq[PackageInfo], options: Options, nimBin: string) = + dependencies: seq[PackageInfo], options: Options, nimBin: Option[string]) = let allPackages = concat(@[dependentPkg], dependencies) let developDependencies = processDevelopDependencies(dependentPkg, options, nimBin) var errors: seq[string] @@ -1292,7 +1312,7 @@ proc validateDevelopDependenciesVersionRanges(dependentPkg: PackageInfo, if errors.len > 0: raise nimbleError(invalidDevelopDependenciesVersionsMsg(errors)) -proc validateParsedDependencies(pkgInfo: PackageInfo, options: Options, nimBin: string) = +proc validateParsedDependencies(pkgInfo: PackageInfo, options: Options, nimBin: Option[string]) = displayInfo(&"Validating dependencies for pkgInfo {pkgInfo.infoKind}", HighPriority) var options = options options.useDeclarativeParser = true @@ -1305,7 +1325,7 @@ proc validateParsedDependencies(pkgInfo: PackageInfo, options: Options, nimBin: if declDeps != vmDeps: raise nimbleError(&"Parsed declarative and VM dependencies are not the same: {declDeps} != {vmDeps}") -proc check(options: Options, nimBin: string) = +proc check(options: Options, nimBin: Option[string]) = try: let currentDir = getCurrentDir() let pkgInfo = getPkgInfo(currentDir, options, nimBin = nimBin, forValidation = true) @@ -1319,7 +1339,7 @@ proc check(options: Options, nimBin: string) = display("Failure:", validationFailedMsg, Error, HighPriority) raise nimbleQuit(QuitFailure) -proc updateSyncFile(dependentPkg: PackageInfo, options: Options, nimBin: string) = +proc updateSyncFile(dependentPkg: PackageInfo, options: Options, nimBin: Option[string]) = # Updates the sync file with the current VCS revisions of develop mode # dependencies of the package `dependentPkg`. @@ -1336,7 +1356,7 @@ proc updateSyncFile(dependentPkg: PackageInfo, options: Options, nimBin: string) syncFile.save proc validateDevModeDepsWorkingCopiesBeforeLock( - pkgInfo: PackageInfo, options: Options, nimBin: string): ValidationErrors = + pkgInfo: PackageInfo, options: Options, nimBin: Option[string]): ValidationErrors = ## Validates that the develop mode dependencies states are suitable for ## locking. They must be under version control, their working copies must be ## in a clean state and their current VCS revision must be present on some of @@ -1390,7 +1410,7 @@ proc check(errors: ValidationErrors, graph: LockFileDeps) = if err.len > 0: raise validationErrors(err) -proc lock(options: var Options, nimBin: string) = +proc lock(options: var Options, nimBin: Option[string]) = ## Generates a lock file for the package in the current directory or updates ## it if it already exists. let currentDir = getCurrentDir() @@ -1518,7 +1538,7 @@ proc depsPrint(options: Options, else: printDepsHumanReadable(pkgInfo, dependencies, errors, true) -proc deps(options: Options, nimBin: string) = +proc deps(options: Options, nimBin: Option[string]) = ## handles deps actions let pkgInfo = getPkgInfo(getCurrentDir(), options, nimBin = nimBin) @@ -1647,7 +1667,7 @@ proc syncWorkingCopy(name: string, path: Path, dependentPkg: PackageInfo, "cannot be synced.") displayDetails(error.msg) -proc sync(options: Options, nimBin: string) = +proc sync(options: Options, nimBin: Option[string]) = # Syncs working copies of the develop mode dependencies of the current # directory package with the revision data from the lock file. @@ -1692,7 +1712,7 @@ proc append(existingContent: var string; newContent: string) = existingContent &= "\n" existingContent &= newContent -proc setupNimbleConfig(options: Options, nimBin: string) = +proc setupNimbleConfig(options: Options, nimBin: Option[string]) = ## Creates `nimble.paths` file containing file system paths to the ## dependencies. Includes it in `config.nims` file to make them available ## for the compiler. @@ -1787,11 +1807,11 @@ proc setupVcsIgnoreFile = if writeFile: writeFile(vcsIgnoreFileName, fileContent & "\n") -proc setup(options: Options, nimBin: string) = +proc setup(options: Options, nimBin: Option[string]) = setupNimbleConfig(options, nimBin) setupVcsIgnoreFile() -proc getAlteredPath(options: Options, nimBin: string): string = +proc getAlteredPath(options: Options, nimBin: Option[string]): string = let pkgInfo = options.satResult.rootPackage var pkgs = options.satResult.pkgs.toSeq.toOrderedSet pkgs.incl(pkgInfo) @@ -1803,16 +1823,16 @@ proc getAlteredPath(options: Options, nimBin: string): string = let folder = fullInfo.getOutputDir(bin).parentDir.quoteShell paths.add folder paths.reverse - let parentDir = nimBin.parentDir + let parentDir = nimBin.get.parentDir result = fmt "{getAppDir()}{separator}{paths.join(separator)}{separator}{parentDir}{separator}{getEnv(\"PATH\")}" -proc shellenv(options: var Options, nimBin: string) = +proc shellenv(options: var Options, nimBin: Option[string]) = setVerbosity(SilentPriority) options.verbosity = SilentPriority const prefix = when defined(windows): "set PATH=" else: "export PATH=" echo prefix & getAlteredPath(options, nimBin) -proc shell(options: Options, nimBin: string) = +proc shell(options: Options, nimBin: Option[string]) = putEnv("PATH", getAlteredPath(options, nimBin)) when defined windows: @@ -1824,7 +1844,7 @@ proc shell(options: Options, nimBin: string) = discard waitForExit startProcess(shell, options = {poParentStreams, poUsePath}) -proc getPackageForAction(pkgInfo: PackageInfo, options: Options, nimBin: string): PackageInfo = +proc getPackageForAction(pkgInfo: PackageInfo, options: Options, nimBin: Option[string]): PackageInfo = ## Returns the `PackageInfo` for the package in `pkgInfo`'s dependencies tree ## with the name specified in `options.package`. If `options.package` is empty ## or it matches the name of the `pkgInfo` then `pkgInfo` is returned. Raises @@ -1846,7 +1866,7 @@ proc getPackageForAction(pkgInfo: PackageInfo, options: Options, nimBin: string) raise nimbleError(notFoundPkgWithNameInPkgDepTree(options.package)) -proc runAction(options: Options, nimBin: string) = +proc runAction(options: Options, nimBin: Option[string]) = ## Handles `actionRun`: runs a compiled binary from the current package. var pkgInfo: PackageInfo pkgInfo = options.satResult.rootPackage @@ -1883,7 +1903,7 @@ proc openNimbleManual = displayInfo("If it did not open, you can try going to the link manually: " & NimbleGuideURL) openDefaultBrowser(NimbleGuideURL) -proc loadFilePathPkgs*(entryPkg: PackageInfo, options: var Options, nimBin: string) = +proc loadFilePathPkgs*(entryPkg: PackageInfo, options: var Options, nimBin: Option[string]) = addUnique(options.filePathPkgs, entryPkg) for require in entryPkg.requires: if require.name.isFileURL: @@ -1891,12 +1911,12 @@ proc loadFilePathPkgs*(entryPkg: PackageInfo, options: var Options, nimBin: stri let pkg = getPkgInfo(path, options, nimBin = nimBin, level = pikRequires) pkg.loadFilePathPkgs(options, nimBin) -proc loadFilePathPkgs(options: var Options, nimBin: string) = +proc loadFilePathPkgs(options: var Options, nimBin: Option[string]) = options.isFilePathDiscovering = true options.satResult.rootPackage.loadFilePathPkgs(options, nimBin) options.isFilePathDiscovering = false -proc solvePkgs(rootPackage: PackageInfo, options: var Options, nimBin: string) {.instrument.} = +proc solvePkgs(rootPackage: PackageInfo, options: var Options, nimBin: var Option[string]) {.instrument.} = options.satResult.rootPackage = rootPackage options.satResult.rootPackage.requires &= options.extraRequires # Add task-specific requirements if a task is being executed @@ -1907,16 +1927,20 @@ proc solvePkgs(rootPackage: PackageInfo, options: var Options, nimBin: string) { if options.action.typ == actionLock: for task in rootPackage.taskRequires.keys: options.satResult.rootPackage.requires &= rootPackage.taskRequires[task] - loadFilePathPkgs(options, nimBin) - var pkgList = initPkgList(options.satResult.rootPackage, options, nimBin = nimBin) + withNimBinFallback(nimBin, options): + loadFilePathPkgs(options, nimBin) + var pkgList: seq[PackageInfo] + withNimBinFallback(nimBin, options): + pkgList = initPkgList(options.satResult.rootPackage, options, nimBin = nimBin) options.satResult.rootPackage.enableFeatures(options) - - var resolvedNim = resolveAndConfigureNim(options.satResult.rootPackage, pkgList, options, nimBin) + var resolvedNim: NimResolved + withNimBinFallback(nimBin, options): + resolvedNim = resolveAndConfigureNim(options.satResult.rootPackage, pkgList, options, nimBin) if resolvedNim.pkg.isNone: let nimInstalled = installNimFromBinariesDir(("nim", resolvedNim.version.toVersionRange()), options) if nimInstalled.isSome: - resolvedNim.pkg = some getPkgInfo(nimInstalled.get.dir, options, nimBin = "", level = pikRequires) #Can be empty as the code path for nim doesnt need it. + resolvedNim.pkg = some getPkgInfo(nimInstalled.get.dir, options, nimBin = none(string), level = pikRequires) #Can be empty as the code path for nim doesnt need it. resolvedNim.version = nimInstalled.get.ver else: raise nimbleError("Failed to install nim") #What to do here? Is this ever possible? @@ -1933,6 +1957,7 @@ proc solvePkgs(rootPackage: PackageInfo, options: var Options, nimBin: string) { nimPkgInfo.nimBinPath.get # System nim with non-standard layout (#1609) else: nimPkgInfo.getNimPath() # Standard layout + nimBin = some(resolvedNimBin) options.nimBin = some makeNimBin(options, resolvedNimBin) # Add nim to PATH for subprocesses and handle compilation (only for non-system nim in standard layout) if nimPkgInfo.nimBinPath.isNone: @@ -1968,7 +1993,7 @@ proc solvePkgs(rootPackage: PackageInfo, options: var Options, nimBin: string) { options.satResult.pass = satDone -proc developFromSolution(rootPkgName: string, options: var Options, nimBin: string) = +proc developFromSolution(rootPkgName: string, options: var Options, nimBin: Option[string]) = ## Clones solved packages into vendor/ based on SAT solver results. if options.action.path.len == 0: options.action.path = defaultDevelopPath @@ -1999,7 +2024,7 @@ proc developFromSolution(rootPkgName: string, options: var Options, nimBin: stri if options.developFile.len > 0: discard updateDevelopFile(currentDirPkgInfo, options, nimBin) -proc runDevelopAction(options: var Options, nimBin: string): bool = +proc runDevelopAction(options: var Options, nimBin: Option[string]): bool = ## Handles `actionDevelop`. Returns `true` if fully handled (caller should ## `return`), `false` if the caller should fall through to the normal ## solve/install flow (the `--with-dependencies` project-dir case). @@ -2024,17 +2049,20 @@ proc runDevelopAction(options: var Options, nimBin: string): bool = # For non-project-dir develops, solve from each cloned package as root if not options.thereIsNimbleFile and developedPkgs.len > 0: for rootPkg in developedPkgs: + var nimBinLocal = nimBin options.satResult = initSATResult(satSolving) - solvePkgs(rootPkg, options, nimBin) - developFromSolution(rootPkg.basicInfo.name, options, nimBin) + solvePkgs(rootPkg, options, nimBinLocal) + developFromSolution(rootPkg.basicInfo.name, options, nimBinLocal) return true return false -proc runInstallRootGloballyAction(options: var Options, nimBin: string) = +proc runInstallRootGloballyAction(options: var Options, nimBin: var Option[string]) = ## `nimble install -g` inside a project directory: install the current ## project globally. options.satResult = initSATResult(satSolving) - let rootPackage = getPkgInfo(getCurrentDir(), options, nimBin = nimBin, level = pikRequires) + var rootPackage: PackageInfo + withNimBinFallback(nimBin, options): + rootPackage = getPkgInfo(getCurrentDir(), options, nimBin = nimBin, level = pikRequires) solvePkgs(rootPackage, options, nimBin) let rootSolvedPkg = SolvedPackage( pkgName: rootPackage.basicInfo.name, @@ -2046,7 +2074,7 @@ proc runInstallRootGloballyAction(options: var Options, nimBin: string) = options.satResult.installPkgs(options, nimBin) options.satResult.addReverseDeps(options) -proc runInstallPackagesAction(options: var Options, nimBin: string) = +proc runInstallPackagesAction(options: var Options, nimBin: var Option[string]) = ## Global install of named packages: `nimble install foo bar`. for pkg in options.action.packages: options.satResult = initSATResult(satSolving) @@ -2056,7 +2084,9 @@ proc runInstallPackagesAction(options: var Options, nimBin: string) = var dlOptions = options dlOptions.ignoreSubmodules = true dlOptions.enableTarballs = false - var rootPackage = downloadPkInfoForPv(pkg, dlOptions, doPrompt = true, nimBin = nimBin) + var rootPackage: PackageInfo + withNimBinFallback(nimBin, options): + rootPackage = downloadPkInfoForPv(pkg, dlOptions, doPrompt = true, nimBin = nimBin) solvePkgs(rootPackage, options, nimBin) let rootSolvedPkg = SolvedPackage( @@ -2069,13 +2099,14 @@ proc runInstallPackagesAction(options: var Options, nimBin: string) = options.satResult.installPkgs(options, nimBin) options.satResult.addReverseDeps(options) -proc runLocalProjectAction(options: var Options, nimBin: string): PackageInfo = +proc runLocalProjectAction(options: var Options, nimBin: var Option[string]): PackageInfo = ## Loads the local project root package and runs the SAT solver. Returns ## the root package so the caller can feed it into the post-solve tail. options.satResult = initSATResult(satSolving) options.isFilePathDiscovering = true #we need to skip validation for root - result = getPkgInfo(getCurrentDir(), options, nimBin = nimBin, level = pikRequires) + withNimBinFallback(nimBin, options): + result = getPkgInfo(getCurrentDir(), options, nimBin = nimBin, level = pikRequires) options.isFilePathDiscovering = false if options.action.typ in {actionInstall, actionAdd}: result.requires.add(options.action.packages) @@ -2094,7 +2125,7 @@ proc warnVersionMismatches(options: Options) = except CatchableError: discard -proc run*(options: var Options, nimBin: string) {.instrument.} = +proc run*(options: var Options, nimBin: var Option[string]) {.instrument.} = ## Main pipeline entry. Dispatches to the appropriate branch helper and ## runs the shared post-solve install/reverse-deps tail for branches that ## fall through. @@ -2143,7 +2174,7 @@ proc run*(options: var Options, nimBin: string) {.instrument.} = warnVersionMismatches(options) -proc getNimDir(options: var Options, nimBin: string): string = +proc getNimDir(options: var Options, nimBin: var Option[string]): string = ## returns the nim directory prioritizing the nimBin one if it satisfais the requirement of the project ## otherwise it returns the major version of the nim installed packages that satisfies the requirement of the project ## if no nim package satisfies the requirement of the project it returns the nimBin parent directory @@ -2168,15 +2199,24 @@ proc getNimDir(options: var Options, nimBin: string): string = if options.action.typ == actionInstall: rootPackage.requires.add(options.action.packages) solvePkgs(rootPackage, options, nimBin) - return nimBin.parentDir + return nimBin.get.parentDir -proc doAction(options: var Options, nimBin: string) {.instrument.} = +proc doAction(options: var Options, nimBinParam: Option[string]) {.instrument.} = if options.showHelp: writeHelp() if options.showVersion: writeVersion() + # Lazily resolve bootstrap Nim on demand for non-resolving branches that + # need it (dump, tasks, check, publish). Resolving branches already get + # nimBin from the SAT solver (populated by run() → satResult.nimResolved). + var nimBin = nimBinParam + template needNim() = + if nimBin.isNone: + nimBin = some(ensureBootstrapNim(options)) + if options.nimBin.isNone: + options.nimBin = some makeNimBin(options, nimBin.get) case options.action.typ of actionRefresh: refresh(options) @@ -2207,17 +2247,22 @@ proc doAction(options: var Options, nimBin: string) {.instrument.} = var pkgInfo = getPkgInfo(getCurrentDir(), options, nimBin = nimBin) execBackend(pkgInfo, options, nimBin) of actionInit: + needNim() init(options, nimBin) of actionPublish: + needNim() var pkgInfo = getPkgInfo(getCurrentDir(), options, nimBin = nimBin) publish(pkgInfo, options) of actionDump: + needNim() dump(options, nimBin) of actionTasks: + needNim() listTasks(options, nimBin) of actionDevelop: develop(options, nimBin) of actionCheck: + needNim() check(options, nimBin) of actionLock: lock(options, nimBin) @@ -2295,27 +2340,20 @@ when isMainModule: "Could not find a .nimble file in the current directory. " & "This command requires a Nimble package file.") - # Actions that don't need a Nim binary should not trigger downloading Nim. - # This avoids e.g. `nimble --version` or `nimble list -i` fetching Nim binaries. - const actionsNotNeedingNim = {actionRefresh, actionSearch, actionList, - actionPath, actionUninstall, actionClean, actionManual, - actionNil} - let needsNim = not opt.showVersion and not opt.showHelp and - opt.action.typ notin actionsNotNeedingNim - var nimBin = "" - if needsNim: - let bootstrapNimRes = getBootstrapNimResolved(opt) - nimBin = bootstrapNimRes.getNimBin() + # Resolving actions (build/install/run/etc.) get their Nim from the SAT solver inside run(); + # non-resolving actions call ensureBootstrapNim at their own point-of-use. + # Actions that never need a Nim binary (refresh, search, list, path, + # uninstall, clean, manual, init, nil — and --version/--help) stay free. + var nimBin = none(string) if shouldRun: # For actionCustom, set the task name before calling run if opt.action.typ == actionCustom: opt.task = opt.action.command.normalize run(opt, nimBin) - elif not opt.showVersion and not opt.showHelp: - # Non-vnext actions (publish, tasks, check, dump) just need a nim binary - # to parse nimble files. nimBin is already resolved by getBootstrapNimResolved above. - if nimBin != "": - opt.nimBin = some makeNimBin(opt, nimBin) + # After run(), the SAT solver has resolved nim for this project. + # Read it out so subsequent setup()/doAction() can reuse the same binary. + if opt.satResult.nimResolved.pkg.isSome: + nimBin = some(opt.satResult.nimResolved.getNimBin()) #if the action is different than setup and in vnext we run setup #when not doing a global install (no nimble file in the current directory) diff --git a/src/nimblepkg/build.nim b/src/nimblepkg/build.nim index 30655ff3..43ec3db4 100644 --- a/src/nimblepkg/build.nim +++ b/src/nimblepkg/build.nim @@ -48,7 +48,7 @@ proc getDepsPkgInfo*(satResult: SATResult, pkgInfo: PackageInfo, options: Option let depInfo = getPkgInfoFromSolution(satResult, solvedPkg, options) result.add(depInfo) -proc expandPaths*(pkgInfo: PackageInfo, nimBin: string, options: Options): seq[string] = +proc expandPaths*(pkgInfo: PackageInfo, nimBin: Option[string], options: Options): seq[string] = var pkgInfo = pkgInfo.toFullInfo(options, nimBin = nimBin) #TODO is this needed in VNEXT? I dont think so pkgInfo = pkgInfo.toRequiresInfo(options, nimBin = nimBin) let baseDir = pkgInfo.getRealDir() @@ -65,7 +65,7 @@ proc expandPaths*(pkgInfo: PackageInfo, nimBin: string, options: Options): seq[s result.add path proc getPathsToBuildFor*(satResult: SATResult, pkgInfo: PackageInfo, recursive: bool, options: Options): HashSet[string] = - let nimBin = satResult.nimResolved.getNimBin() + let nimBin = some(satResult.nimResolved.getNimBin()) for depInfo in getDepsPkgInfo(satResult, pkgInfo, options): for path in depInfo.expandPaths(nimBin, options): result.incl(path) @@ -74,7 +74,7 @@ proc getPathsToBuildFor*(satResult: SATResult, pkgInfo: PackageInfo, recursive: result.incl(path) result.incl(pkgInfo.expandPaths(nimBin, options)) -proc getPathsAllPkgs*(options: Options, nimBin: string): HashSet[string] = +proc getPathsAllPkgs*(options: Options, nimBin: Option[string]): HashSet[string] = let satResult = options.satResult for pkg in satResult.pkgs: if pkg.basicInfo.name.isNim: @@ -83,7 +83,7 @@ proc getPathsAllPkgs*(options: Options, nimBin: string): HashSet[string] = result.incl(path) proc buildFromDir*(pkgInfo: PackageInfo, paths: HashSet[string], - args: seq[string], options: Options, nimBin: string) = + args: seq[string], options: Options, nimBin: Option[string]) = ## Builds a package as specified by ``pkgInfo``. # Handle pre-`build` hook. let @@ -184,7 +184,7 @@ proc buildFromDir*(pkgInfo: PackageInfo, paths: HashSet[string], realDir / src.changeFileExt("nim") let cmd = "$# $# --colors:$# --noNimblePath $# $# $#" % [ - nimBin.quoteShell, pkgInfo.backend, if options.noColor: "off" else: "on", join(args, " "), + nimBin.get.quoteShell, pkgInfo.backend, if options.noColor: "off" else: "on", join(args, " "), outputOpt, input.quoteShell] try: doCmd(cmd) @@ -247,7 +247,7 @@ proc createBinSymlink*(pkgInfo: PackageInfo, options: Options) = binariesInstalled.incl( setupBinSymlink(symlinkDest, symlinkFilename, options)) -proc buildPkg*(nimBin: string, pkgToBuild: PackageInfo, isRootInRootDir: bool, options: Options) {.instrument.} = +proc buildPkg*(nimBin: Option[string], pkgToBuild: PackageInfo, isRootInRootDir: bool, options: Options) {.instrument.} = # let paths = getPathsToBuildFor(options.satResult, pkgToBuild, recursive = true, options) let paths = getPathsAllPkgs(options, nimBin) # echo "Paths ", paths diff --git a/src/nimblepkg/common.nim b/src/nimblepkg/common.nim index 92e6b79c..65ce72b5 100644 --- a/src/nimblepkg/common.nim +++ b/src/nimblepkg/common.nim @@ -19,6 +19,10 @@ type PackageNotFoundError* = object of NimbleError ## Raised when a package cannot be found in any repository + NeedsNimBinError* = object of NimbleError + ## Raised by the declarative parser when VM fallback is needed but nimBin is + ## not yet resolved. Callers catch this and retry after bootstrap resolution. + ## Same as quit(QuitSuccess) or quit(QuitFailure), but allows cleanup. ## Inheriting from `Defect` is workaround to avoid accidental catching of ## `NimbleQuit` by `CatchableError` handlers. diff --git a/src/nimblepkg/declarativeparser.nim b/src/nimblepkg/declarativeparser.nim index 8f4e6cd7..528880bb 100644 --- a/src/nimblepkg/declarativeparser.nim +++ b/src/nimblepkg/declarativeparser.nim @@ -642,14 +642,15 @@ proc getPkgInfoMaybeInTempDir(pkgDir: string, options: Options, nimBin: string, ## Runs getPkgInfo, potentially in a temp directory copy. ## Only copies to temp dir when the package is in pkgcache AND has state-modifying ops. ## This prevents the VM parser from creating files/directories in pkgcache. + let nimBinOpt = some(nimBin) if not isInPkgCache(pkgDir, options): - return getPkgInfoVm(pkgDir, options, nimBin) + return getPkgInfoVm(pkgDir, options, nimBinOpt) if not fileHasStateModifyingOps(nimbleFile): - return getPkgInfoVm(pkgDir, options, nimBin) + return getPkgInfoVm(pkgDir, options, nimBinOpt) let tempDir = getNimbleTempDir() / "vmparse_" & $epochTime().int try: copyDirRec(pkgDir, tempDir) - result = getPkgInfoVm(tempDir, options, nimBin) + result = getPkgInfoVm(tempDir, options, nimBinOpt) # Update myPath to point back to original location let nimbleFileName = result.myPath.extractFilename result.myPath = pkgDir / nimbleFileName @@ -661,16 +662,23 @@ proc getPkgInfoMaybeInTempDir(pkgDir: string, options: Options, nimBin: string, except CatchableError: discard # Ignore cleanup errors -proc toRequiresInfo*(pkgInfo: PackageInfo, options: Options, nimBin: string, nimbleFileInfo: Option[NimbleFileInfo] = none(NimbleFileInfo)): PackageInfo = +proc toRequiresInfo*(pkgInfo: PackageInfo, options: Options, nimBin: Option[string], nimbleFileInfo: Option[NimbleFileInfo] = none(NimbleFileInfo)): PackageInfo = #For nim we only need the version. Since version is usually in the form of `version = $NimMajor & "." & $NimMinor & "." & $NimPatch #we need to use the vm to get the version. Another option could be to use the binary and ask for the version # echo "toRequiresInfo: ", $pkgInfo.basicInfo, $pkgInfo.requires result = pkgInfo + # Helper: resolve nimBin from parameter, memoized bootstrap, or raise NeedsNimBinError + proc resolveNimBin(nimBin: Option[string], options: Options): string = + if nimBin.isSome: return nimBin.get + if options.satResult.bootstrapNim.nimResolved.pkg.isSome: + return options.satResult.bootstrapNim.nimResolved.getNimBin() + raise newNimbleError[NeedsNimBinError]("VM parser needed but no Nim binary available yet") + if pkgInfo.myPath.splitFile.ext == ".babel": let babelWarning = &"Package {pkgInfo.basicInfo.name} is a babel package, skipping declarative parser" if options.verbosity <= LowPriority: displayWarning babelWarning - result = getPkgInfoMaybeInTempDir(pkgInfo.myPath.parentDir, options, nimBin, pkgInfo.myPath) + result = getPkgInfoMaybeInTempDir(pkgInfo.myPath.parentDir, options, resolveNimBin(nimBin, options), pkgInfo.myPath) fillMetaData(result, result.getRealDir(), false, options) if babelWarning notin result.declarativeParserErrors: result.declarativeParserErrors.add(babelWarning) @@ -684,13 +692,13 @@ proc toRequiresInfo*(pkgInfo: PackageInfo, options: Options, nimBin: string, nim result.infoKind = pikRequires if nimbleFileInfo.nestedRequires and options.action.typ != actionCheck: #When checking we want to fail on porpuse - assert nimBin != "", "Cant fallback to the vm parser as there is no nim bin." + let resolvedBin = resolveNimBin(nimBin, options) if options.verbosity <= LowPriority: for line in nimbleFileInfo.declarativeParserErrorLines: displayWarning line - result = getPkgInfoMaybeInTempDir(result.myPath.parentDir, options, nimBin, result.myPath) + result = getPkgInfoMaybeInTempDir(result.myPath.parentDir, options, resolvedBin, result.myPath) for line in nimbleFileInfo.declarativeParserErrorLines: if line notin result.declarativeParserErrors: result.declarativeParserErrors.add(line) @@ -725,7 +733,7 @@ proc fillPkgBasicInfo(pkgInfo: var PackageInfo, nimbleFileInfo: NimbleFileInfo) pkgInfo.basicInfo.version = newVersion nimbleFileInfo.version pkgInfo.srcDir = nimbleFileInfo.srcDir -proc getNimPkgInfo*(dir: string, options: Options, nimBin: string): PackageInfo = +proc getNimPkgInfo*(dir: string, options: Options, nimBin: Option[string]): PackageInfo = let nimbleFile = dir / "nim.nimble" assert fileExists(nimbleFile), "Nim.nimble file not found in " & dir let nimbleFileInfo = extractRequiresInfo(nimbleFile, options) @@ -733,7 +741,7 @@ proc getNimPkgInfo*(dir: string, options: Options, nimBin: string): PackageInfo fillPkgBasicInfo(result, nimbleFileInfo) result = toRequiresInfo(result, options, nimBin, some nimbleFileInfo) -proc getPkgInfoFromDirWithDeclarativeParser(dir: string, options: Options, nimBin: string, shouldError: bool = true): PackageInfo = +proc getPkgInfoFromDirWithDeclarativeParser(dir: string, options: Options, nimBin: Option[string], shouldError: bool = true): PackageInfo = let nimbleFile = findNimbleFile(dir, shouldError, options) let nimbleFileInfo = extractRequiresInfo(nimbleFile, options) result = initPackageInfo() @@ -775,7 +783,7 @@ proc getMinimalInfoFromContent*(content: string, name: string, version: Version, return some(pkgInfo) -proc getPkgInfo*(dir: string, options: Options, nimBin: string, +proc getPkgInfo*(dir: string, options: Options, nimBin: Option[string], level: PackageInfoKind = pikFull, forValidation = false, shouldError = true): PackageInfo = ## Unified entry point for package parsing. Tries declarative parser first, @@ -788,7 +796,7 @@ proc getPkgInfo*(dir: string, options: Options, nimBin: string, # which the declarative parser can't provide return getPkgInfoVm(dir, options, nimBin, forValidation) -proc getMinimalInfo*(nimbleFile: string, options: Options, nimBin: string): PackageMinimalInfo = +proc getMinimalInfo*(nimbleFile: string, options: Options, nimBin: Option[string]): PackageMinimalInfo = #TODO we can use the new getPkgInfo to get the minimal info and add the features to the packageinfo type so this whole function can be removed #TODO we need to handle the url here as well. # declarative parser is always used diff --git a/src/nimblepkg/developfile.nim b/src/nimblepkg/developfile.nim index 533ee3b1..d186b48d 100644 --- a/src/nimblepkg/developfile.nim +++ b/src/nimblepkg/developfile.nim @@ -5,7 +5,7 @@ ## files. import sets, sequtils, os, strformat, tables, hashes, strutils, math, - std/jsonutils + std/jsonutils, std/options import typetraits except distinctBase import compat/json @@ -159,7 +159,7 @@ proc developFileExists*(pkg: PackageInfo): bool = ## the directory of the package's `pkg` `.nimble` file or `false` otherwise. pkg.getNimbleFilePath.developFileExists -proc validatePackage(pkgPath: Path, options: Options, nimBin: string): +proc validatePackage(pkgPath: Path, options: Options, nimBin: Option[string]): tuple[pkgInfo: PackageInfo, error: ref CatchableError] = ## By given file system path `pkgPath`, determines whether it points to a ## valid Nimble package. @@ -314,7 +314,7 @@ proc mergeFollowedDevFileData(lhs: var DevelopFileData, rhs: DevelopFileData, errors.collidingNames) proc load(cache: var DevelopCache, path: Path, dependentPkg: PackageInfo, options: Options, - silentIfFileNotExists, raiseOnValidationErrors, loadGlobalDeps: bool, nimBin: string): + silentIfFileNotExists, raiseOnValidationErrors, loadGlobalDeps: bool, nimBin: Option[string]): DevelopFileData template load(cache: var DevelopCache, dependentPkg: PackageInfo, args: varargs[untyped]): @@ -328,7 +328,7 @@ template load(cache: var DevelopCache, dependentPkg: PackageInfo, args: varargs[ proc loadGlobalDependencies(result: var DevelopFileData, collidingNames: var CollidingNames, options: Options, - nimBin: string) = + nimBin: Option[string]) = ## Loads data from the `links` subdirectory in the Nimble cache. The links ## in the cache are treated as paths in a global develop file used when a ## local one does not exist. @@ -356,7 +356,7 @@ proc loadGlobalDependencies(result: var DevelopFileData, displayDetails(error.msg) proc load(cache: var DevelopCache, path: Path, dependentPkg: PackageInfo, options: Options, - silentIfFileNotExists, raiseOnValidationErrors, loadGlobalDeps: bool, nimBin: string): + silentIfFileNotExists, raiseOnValidationErrors, loadGlobalDeps: bool, nimBin: Option[string]): DevelopFileData = ## Loads data from a develop file at path `path`. ## @@ -485,7 +485,7 @@ proc addDevelopPackage(data: var DevelopFileData, pkg: PackageInfo): bool = return true proc addDevelopPackage(data: var DevelopFileData, path: Path, - options: Options, nimBin: string): bool = + options: Options, nimBin: Option[string]): bool = ## Adds path `path` to some package directory to the develop file. ## ## Returns `true` if: @@ -604,7 +604,7 @@ proc removeDevelopPackageByName(data: var DevelopFileData, name: string): bool = return success proc includeDevelopFile(cache: var DevelopCache, data: var DevelopFileData, path: Path, - options: Options, nimBin: string): bool = + options: Options, nimBin: Option[string]): bool = ## Includes a develop file at path `path` to the current project's develop ## file. ## @@ -681,7 +681,7 @@ proc assertDevelopActionIsSet(options: Options) = assert options.action.typ == actionDevelop, "This procedure must be called only on develop command." -proc updateDevelopFile*(dependentPkg: PackageInfo, options: Options, nimBin: string): bool = +proc updateDevelopFile*(dependentPkg: PackageInfo, options: Options, nimBin: Option[string]): bool = ## Updates a dependent package `dependentPkg`'s develop file with an ## information from the Nimble's command line. ## - Adds newly installed develop packages. @@ -728,7 +728,7 @@ proc updateDevelopFile*(dependentPkg: PackageInfo, options: Options, nimBin: str return not hasError -proc processDevelopDependencies*(dependentPkg: PackageInfo, options: Options, nimBin: string): +proc processDevelopDependencies*(dependentPkg: PackageInfo, options: Options, nimBin: Option[string]): seq[PackageInfo] = ## Returns a sequence with the develop mode dependencies of the `dependentPkg` ## and recursively all of their develop mode dependencies. @@ -743,7 +743,7 @@ proc processDevelopDependencies*(dependentPkg: PackageInfo, options: Options, ni # echo "SAT RESULT ", options.satResult.pkgs.mapIt(it.basicInfo.name) # options.debugSATResult() -proc getDevelopDependencies*(dependentPkg: PackageInfo, options: Options, raiseOnValidationErrors = true, nimBin: string): +proc getDevelopDependencies*(dependentPkg: PackageInfo, options: Options, raiseOnValidationErrors = true, nimBin: Option[string]): Table[string, ref PackageInfo] = ## Returns a table with a mapping between names and `PackageInfo`s of develop ## mode dependencies of package `dependentPkg` and recursively all of their @@ -924,7 +924,7 @@ template addError(error: ValidationErrorKind) = proc findValidationErrorsOfDevDepsWithLockFile*( dependentPkg: PackageInfo, options: Options, - errors: var ValidationErrors, nimBin: string) = + errors: var ValidationErrors, nimBin: Option[string]) = ## Collects validation errors for the develop mode dependencies with the ## content of the lock file by getting in consideration the information from ## the sync file. In the case of discrepancy, gives a useful advice what have @@ -961,7 +961,7 @@ proc validationErrors*(errors: ValidationErrors): ref NimbleError = hint = errors.getValidationsErrorsHint) proc validateDevelopFileAgainstLockFile( - dependentPkg: PackageInfo, options: Options, nimBin: string) = + dependentPkg: PackageInfo, options: Options, nimBin: Option[string]) = ## Does validation of the develop file dependencies against the data written ## in the lock file. @@ -971,7 +971,7 @@ proc validateDevelopFileAgainstLockFile( if errors.len > 0: raise validationErrors(errors) -proc validateDevelopFile*(dependentPkg: PackageInfo, options: Options, nimBin: string) = +proc validateDevelopFile*(dependentPkg: PackageInfo, options: Options, nimBin: Option[string]) = ## The procedure is used in the Nimble's `check` command to transitively ## validate the contents of the develop files. diff --git a/src/nimblepkg/download.nim b/src/nimblepkg/download.nim index c7994a94..de56877b 100644 --- a/src/nimblepkg/download.nim +++ b/src/nimblepkg/download.nim @@ -2,7 +2,7 @@ # BSD License. Look at license.txt for more info. import parseutils, os, strutils, tables, uri, strformat, - httpclient, sequtils, urls, chronos + httpclient, sequtils, urls, chronos, std/options import compat/[json, osproc] from algorithm import SortOrder, sorted @@ -871,7 +871,7 @@ proc downloadPkg*(url: string, verRange: VersionRange, options: Options, downloadPath: string, vcsRevision: Sha1Hash, - nimBin: string, + nimBin: Option[string], validateRange = true): DownloadPkgResult = ## Downloads the repository as specified by ``url`` and ``verRange`` using ## the download method specified. @@ -947,7 +947,7 @@ proc downloadPkgAsync*(url: string, verRange: VersionRange, options: Options, downloadPath: string, vcsRevision: Sha1Hash, - nimBin: string, + nimBin: Option[string], validateRange = true): Future[DownloadPkgResult] {.async.} = ## Async version of downloadPkg that uses async operations for cloning and downloading. ## Downloads the repository as specified by ``url`` and ``verRange`` using diff --git a/src/nimblepkg/install.nim b/src/nimblepkg/install.nim index 4aae576b..845deda6 100644 --- a/src/nimblepkg/install.nim +++ b/src/nimblepkg/install.nim @@ -57,7 +57,7 @@ proc addReverseDeps*(satResult: SATResult, options: Options) = # This can happen when packages are installed recursively during hooks displayInfo("Skipping reverse dependency for package not found in solution: " & $dep, MediumPriority) -proc executeHook(nimBin: string, dir: string, options: var Options, action: ActionType, before: bool) = +proc executeHook(nimBin: Option[string], dir: string, options: var Options, action: ActionType, before: bool) = let nimbleFile = findNimbleFile(dir, false, options).splitFile.name let hook = VisitedHook(pkgName: nimbleFile, action: action, before: before) if hook in options.visitedHooks: @@ -71,7 +71,7 @@ proc executeHook(nimBin: string, dir: string, options: var Options, action: Acti else: raise nimbleError("Post-hook prevented further execution.") -proc packageExists(nimBin: string, pkgInfo: PackageInfo, options: Options): +proc packageExists(nimBin: Option[string], pkgInfo: PackageInfo, options: Options): Option[PackageInfo] = ## Checks whether a package `pkgInfo` already exists in the Nimble cache. If a ## package already exists returns the `PackageInfo` of the package in the @@ -136,7 +136,7 @@ proc copyInstallFiles(srcDir, destDir: string, pkgInfo: PackageInfo, copied -proc installFromDirDownloadInfo(nimBin: string, downloadDir: string, url: string, pv: PkgTuple, options: var Options): PackageInfo {.instrument.} = +proc installFromDirDownloadInfo(nimBin: Option[string], downloadDir: string, url: string, pv: PkgTuple, options: var Options): PackageInfo {.instrument.} = ## Installs a package from a download directory (pkgcache). ## flow: pkgcache -> buildtemp (build) -> pkgs2 (install minimum) @@ -337,7 +337,7 @@ proc getVersionRangeFoPkgToInstall(satResult: SATResult, name: string, ver: Vers return parseVersionRange(specialVersion) return ver.toVersionRange() -proc installPkgs*(satResult: var SATResult, options: var Options, nimBin: string) {.instrument.} = +proc installPkgs*(satResult: var SATResult, options: var Options, nimBin: Option[string]) {.instrument.} = # options.debugSATResult("installPkgs") #At this point the packages are already downloaded. #We still need to install them aka copy them from the cache to the nimbleDir + run preInstall and postInstall scripts diff --git a/src/nimblepkg/nimblesat.nim b/src/nimblepkg/nimblesat.nim index b644ce77..586e7bd1 100644 --- a/src/nimblepkg/nimblesat.nim +++ b/src/nimblepkg/nimblesat.nim @@ -51,7 +51,7 @@ type # var urlToName: Table[string, string] = initTable[string, string]() -proc dumpPackageVersionTable*(pkg: PackageInfo, pkgVersionTable: Table[string, PackageVersions], options: Options, nimBin: string) +proc dumpPackageVersionTable*(pkg: PackageInfo, pkgVersionTable: Table[string, PackageVersions], options: Options, nimBin: Option[string]) proc hasKey(packageToDependency: Table[string, int], dep: string): bool = @@ -679,7 +679,7 @@ proc normalizeSpecialVersions*(pkgVersionTable: var Table[string, PackageVersion let reqName = req.name.toLower if reqName in winners and req.ver.kind == verSpecial and req.ver.spe != winners[reqName]: req.ver = VersionRange(kind: verSpecial, spe: winners[reqName]) -proc postProcessSolvedPkgs*(solvedPkgs: var seq[SolvedPackage], options: Options, nimBin: string) {.instrument.} = +proc postProcessSolvedPkgs*(solvedPkgs: var seq[SolvedPackage], options: Options, nimBin: Option[string]) {.instrument.} = #Prioritizes fileUrl packages over the regular packages defined in the requirements var fileUrlPkgs: seq[PackageInfo] = @[] for solved in solvedPkgs: @@ -694,7 +694,7 @@ proc postProcessSolvedPkgs*(solvedPkgs: var seq[SolvedPackage], options: Options break solvedPkgs = solvedPkgs.filterIt(it notin toReplace) -proc solveLocalPackages(root: PackageMinimalInfo, pkgList: seq[PackageInfo], options: Options, output: var string, solvedPkgs: var seq[SolvedPackage], nimBin: string): HashSet[PackageInfo] = +proc solveLocalPackages(root: PackageMinimalInfo, pkgList: seq[PackageInfo], options: Options, output: var string, solvedPkgs: var seq[SolvedPackage], nimBin: Option[string]): HashSet[PackageInfo] = ## Try to solve using only installed packages (no cache, no downloads). ## Returns the solved packages if successful, or an empty set if local ## packages don't satisfy all constraints. See #1648. @@ -720,7 +720,7 @@ proc solveLocalPackages(root: PackageMinimalInfo, pkgList: seq[PackageInfo], opt break return pkgs -proc solvePackages*(rootPkg: PackageInfo, pkgList: seq[PackageInfo], pkgsToInstall: var seq[(string, Version)], options: Options, output: var string, solvedPkgs: var seq[SolvedPackage], nimBin: string): HashSet[PackageInfo] {.instrument.} = +proc solvePackages*(rootPkg: PackageInfo, pkgList: seq[PackageInfo], pkgsToInstall: var seq[(string, Version)], options: Options, output: var string, solvedPkgs: var seq[SolvedPackage], nimBin: Option[string]): HashSet[PackageInfo] {.instrument.} = var root: PackageMinimalInfo = rootPkg.getMinimalInfo(options) root.isRoot = true @@ -758,7 +758,7 @@ proc solvePackages*(rootPkg: PackageInfo, pkgList: seq[PackageInfo], pkgsToInsta solvedPkgs = pkgVersionTable.getSolvedPackages(output, options).topologicalSort() solvedPkgs.postProcessSolvedPkgs(options, nimBin) - let systemNimCompatible = solvedPkgs.isSystemNimCompatible(options, getNimVersionFromBin(nimBin)) + let systemNimCompatible = solvedPkgs.isSystemNimCompatible(options, getNimVersionFromBin(nimBin.get)) # echo "DEBUG: SolvedPkgs after post processing: ", solvedPkgs.mapIt(it.pkgName & " " & $it.version).join(", ") # echo "ACTION IS ", options.action.typ for solvedPkg in solvedPkgs: @@ -796,7 +796,7 @@ proc getPackageInfo*(name: string, pkgs: seq[PackageInfo], version: Option[Versi else: #No version passed over first match return some pkg -proc getPkgVersionTable*(pkgInfo: PackageInfo, pkgList: seq[PackageInfo], options: Options, nimBin: string): Table[string, PackageVersions] = +proc getPkgVersionTable*(pkgInfo: PackageInfo, pkgList: seq[PackageInfo], options: Options, nimBin: Option[string]): Table[string, PackageVersions] = # Load cached package versions to skip re-fetching known packages result = cacheToPackageVersionTable(options) var root = pkgInfo.getMinimalInfo(options) @@ -833,7 +833,7 @@ proc formatPkgName(pkgName: string, maxWidth = maxPkgNameDisplayWidth): string = if result.len > maxWidth - 3: result = result[0..<(maxWidth - 3)] & "..." -proc dumpSolvedPackages*(pkgInfo: PackageInfo, pkgList: seq[PackageInfo], options: Options, nimBin: string) = +proc dumpSolvedPackages*(pkgInfo: PackageInfo, pkgList: seq[PackageInfo], options: Options, nimBin: Option[string]) = var pkgToInstall: seq[(string, Version)] = @[] var output = "" var solvedPkgs: seq[SolvedPackage] = @[] @@ -951,7 +951,7 @@ proc dumpSolvedPackages*(pkgInfo: PackageInfo, pkgList: seq[PackageInfo], option echo " ".repeat(maxPkgNameDisplayWidth + maxVersionDisplayWidth + 3), currentLine -proc dumpPackageVersionTable*(pkg: PackageInfo, pkgVersionTable: Table[string, PackageVersions], options: Options, nimBin: string) = +proc dumpPackageVersionTable*(pkg: PackageInfo, pkgVersionTable: Table[string, PackageVersions], options: Options, nimBin: Option[string]) = # Display header echo "PACKAGE".alignLeft(maxPkgNameDisplayWidth), "VERSION".alignLeft(maxVersionDisplayWidth), "REQUIREMENTS" echo "-".repeat(maxPkgNameDisplayWidth + maxVersionDisplayWidth + 4) @@ -1020,7 +1020,7 @@ proc dumpPackageVersionTable*(pkg: PackageInfo, pkgVersionTable: Table[string, P isFirstVersion = false -proc dumpPackageVersionTable*(pkg: PackageInfo, pkgList: seq[PackageInfo], options: Options, nimBin: string) = +proc dumpPackageVersionTable*(pkg: PackageInfo, pkgList: seq[PackageInfo], options: Options, nimBin: Option[string]) = let pkgVersionTable = getPkgVersionTable(pkg, pkgList, options, nimBin) dumpPackageVersionTable(pkg, pkgVersionTable, options, nimBin) @@ -1115,7 +1115,7 @@ proc getSolvedPkgFromInstalledPkgs*(satResult: SATResult, solvedPkg: SolvedPacka return some(pkg) return none(PackageInfo) -proc solveLockFileDeps*(satResult: var SATResult, pkgList: seq[PackageInfo], options: Options, nimBin: string) = +proc solveLockFileDeps*(satResult: var SATResult, pkgList: seq[PackageInfo], options: Options, nimBin: Option[string]) = let lockFile = options.lockFile(satResult.rootPackage.myPath.parentDir()) let currentRequires = satResult.rootPackage.requires var existingRequires = newSeq[(string, Version)]() @@ -1252,7 +1252,7 @@ proc solveLockFileDeps*(satResult: var SATResult, pkgList: seq[PackageInfo], opt else: satResult.pkgsToInstall.add((name, dep.version)) -proc solutionToFullInfo*(satResult: SATResult, options: var Options, nimBin: string) {.instrument.} = +proc solutionToFullInfo*(satResult: SATResult, options: var Options, nimBin: Option[string]) {.instrument.} = if satResult.rootPackage.infoKind != pikFull and not satResult.rootPackage.basicInfo.name.isNim: satResult.rootPackage = getPkgInfo(satResult.rootPackage.getNimbleFileDir, options, nimBin = nimBin).toRequiresInfo(options, nimBin = nimBin) satResult.rootPackage.enableFeatures(options) diff --git a/src/nimblepkg/nimresolution.nim b/src/nimblepkg/nimresolution.nim index e98fec0b..f22a2424 100644 --- a/src/nimblepkg/nimresolution.nim +++ b/src/nimblepkg/nimresolution.nim @@ -41,7 +41,7 @@ proc getNimFromSystem*(options: Options): Option[PackageInfo] = if exitCode == 0: dir = output.strip().parentDir try: - var pkgInfo = getPkgInfo(dir, options, nimBin = "", level = pikRequires) #Can be empty as the code path for nim doesnt need it. + var pkgInfo = getPkgInfo(dir, options, nimBin = none(string), level = pikRequires) #Can be empty as the code path for nim doesnt need it. pkgInfo.nimBinPath = some pnim # preserve the PATH-resolved binary for later use return some pkgInfo except CatchableError: @@ -59,7 +59,7 @@ proc solvePackagesWithSystemNimFallback*( rootPackage: PackageInfo, pkgList: seq[PackageInfo], options: var Options, - resolvedNim: Option[NimResolved], nimBin: string): HashSet[PackageInfo] {.instrument.} = + resolvedNim: Option[NimResolved], nimBin: Option[string]): HashSet[PackageInfo] {.instrument.} = ## Solves packages with system Nim as a hard requirement, falling back to ## solving without it if the first attempt fails due to unsatisfiable dependencies. @@ -130,18 +130,18 @@ proc resolveNim*(rootPackage: PackageInfo, pkgListDecl: seq[PackageInfo], system var resolvedNim: Option[NimResolved] if systemNimPkg.isSome: resolvedNim = some(NimResolved(pkg: systemNimPkg, version: systemNimPkg.get.basicInfo.version)) - var nimBin: string + var nimBin: Option[string] if resolvedNim.isSome: - nimBin = resolvedNim.get.getNimBin() + nimBin = some(resolvedNim.get.getNimBin()) else: if options.satResult.bootstrapNim.nimResolved.pkg.isNone: let nimPkg = (name: "nim", ver: parseVersionRange(options.satResult.bootstrapNim.nimResolved.version)) let nimInstalled = installNimFromBinariesDir(nimPkg, options) if nimInstalled.isSome: - options.satResult.bootstrapNim.nimResolved.pkg = some getPkgInfo(nimInstalled.get.dir, options, nimBin = "", level = pikRequires) #Can be empty as the code path for nim doesnt need it. + options.satResult.bootstrapNim.nimResolved.pkg = some getPkgInfo(nimInstalled.get.dir, options, nimBin = none(string), level = pikRequires) #Can be empty as the code path for nim doesnt need it. else: raise newNimbleError[NimbleError]("Failed to install nim") - nimBin = options.satResult.bootstrapNim.nimResolved.getNimBin() + nimBin = some(options.satResult.bootstrapNim.nimResolved.getNimBin()) options.satResult.pkgs = solvePackagesWithSystemNimFallback( rootPackage, pkgListDecl, options, resolvedNim, nimBin) @@ -190,7 +190,7 @@ proc resolveNim*(rootPackage: PackageInfo, pkgListDecl: seq[PackageInfo], system result.pkg = some(nims[0]) result.version = nims[0].basicInfo.version -proc setBootstrapNim*(systemNimPkg: Option[PackageInfo], pkgList: seq[PackageInfo], options: var Options) = +proc setBootstrapNim*(systemNimPkg: Option[PackageInfo], pkgList: seq[PackageInfo], options: Options) = var bootstrapNim: NimResolved let nimPkgList = pkgList.filterIt(it.basicInfo.name.isNim) #we want to use actual systemNimPkg as bootstrap nim. @@ -222,7 +222,7 @@ proc getNimBinariesPackages*(options: Options): seq[PackageInfo] = if kind == pcDir: let nimbleFile = path / "nim.nimble" if fileExists(nimbleFile): - var pkgInfo = getNimPkgInfo(nimbleFile.parentDir, options, nimBin = "") #Can be empty as the code path for nim doesnt need it. + var pkgInfo = getNimPkgInfo(nimbleFile.parentDir, options, nimBin = none(string)) #Can be empty as the code path for nim doesnt need it. # Check if directory name indicates a special version (e.g., nim-#devel) # The directory name format is "nim-" let dirName = path.extractFilename @@ -236,7 +236,12 @@ proc getNimBinariesPackages*(options: Options): seq[PackageInfo] = pkgInfo.basicInfo.version = specialVer result.add pkgInfo -proc getBootstrapNimResolved*(options: var Options): NimResolved = +proc getBootstrapNimResolved*(options: Options): NimResolved = + # Instrumentation hook for tests: setting NIMBLE_TRACE_BOOTSTRAP=1 prints a + # marker to stderr every time bootstrap resolution actually runs. Tests can + # count occurrences to prove laziness. + if existsEnv("NIMBLE_TRACE_BOOTSTRAP"): + stderr.writeLine("NIMBLE_BOOTSTRAP_RESOLVED") var pkgList: seq[PackageInfo] = @[] #Should we use the install nim pkgs? In most cases they should already be in the nim binaries dir let nimBinariesPackages = getNimBinariesPackages(options).sortedByIt(it.basicInfo.version).reversed() pkgList.add(nimBinariesPackages) @@ -245,13 +250,19 @@ proc getBootstrapNimResolved*(options: var Options): NimResolved = if bootstrapNim.nimResolved.pkg.isNone: let nimInstalled = installNimFromBinariesDir(("nim", bootstrapNim.nimResolved.version.toVersionRange()), options) if nimInstalled.isSome: - bootstrapNim.nimResolved.pkg = some getPkgInfo(nimInstalled.get.dir, options, nimBin = "", level = pikRequires) #Can be empty as the code path for nim doesnt need it. + bootstrapNim.nimResolved.pkg = some getPkgInfo(nimInstalled.get.dir, options, nimBin = none(string), level = pikRequires) #Can be empty as the code path for nim doesnt need it. else: raise nimbleError("Failed to install nim") #What to do here? Is this ever possible? options.satResult.bootstrapNim = bootstrapNim return bootstrapNim.nimResolved -proc resolveAndConfigureNim*(rootPackage: PackageInfo, pkgList: seq[PackageInfo], options: var Options, nimBin: string): NimResolved {.instrument.} = +proc ensureBootstrapNim*(options: Options): string = + let existing = options.satResult.bootstrapNim.nimResolved + if existing.pkg.isSome: + return existing.getNimBin() + return getBootstrapNimResolved(options).getNimBin() + +proc resolveAndConfigureNim*(rootPackage: PackageInfo, pkgList: seq[PackageInfo], options: var Options, nimBin: Option[string]): NimResolved {.instrument.} = #Before resolving nim, we bootstrap it, so if we fail resolving it when can use the bootstrapped version. #Notice when implemented it would make the second sat pass obsolete. let systemNimPkg = getNimFromSystem(options) @@ -287,11 +298,11 @@ proc resolveAndConfigureNim*(rootPackage: PackageInfo, pkgList: seq[PackageInfo] let nimInstalled = installNimFromBinariesDir(nimPkg, options) if nimInstalled.isSome: let resolvedNim = NimResolved( - pkg: some getPkgInfo(nimInstalled.get.dir, options, nimBin = "", level = pikRequires), #Can be empty as the code path for nim doesnt need it. + pkg: some getPkgInfo(nimInstalled.get.dir, options, nimBin = none(string), level = pikRequires), #Can be empty as the code path for nim doesnt need it. version: nimInstalled.get.ver ) # Still need to set bootstrap nim and configure it - var pkgListDecl = pkgList.mapIt(it.toRequiresInfo(options, resolvedNim.getNimBin())) + var pkgListDecl = pkgList.mapIt(it.toRequiresInfo(options, some(resolvedNim.getNimBin()))) if systemNimPkg.isSome: pkgListDecl.add(systemNimPkg.get) pkgListDecl.sort(compPkgListByVersion) @@ -323,7 +334,7 @@ proc resolveAndConfigureNim*(rootPackage: PackageInfo, pkgList: seq[PackageInfo] #forcing a recompilation of nim. let nimInstalled = installNimFromBinariesDir(nimPkg, options) if nimInstalled.isSome: - resolvedNim.pkg = some getPkgInfo(nimInstalled.get.dir, options, nimBin = "", level = pikRequires) #Can be empty as the code path for nim doesnt need it. + resolvedNim.pkg = some getPkgInfo(nimInstalled.get.dir, options, nimBin = none(string), level = pikRequires) #Can be empty as the code path for nim doesnt need it. resolvedNim.version = nimInstalled.get.ver elif rootPackage.basicInfo.name.isNim: #special version/not in releases nim binaries resolvedNim.pkg = some rootPackage diff --git a/src/nimblepkg/nimscriptexecutor.nim b/src/nimblepkg/nimscriptexecutor.nim index 293d1018..7b2173fd 100644 --- a/src/nimblepkg/nimscriptexecutor.nim +++ b/src/nimblepkg/nimscriptexecutor.nim @@ -1,11 +1,11 @@ # Copyright (C) Dominik Picheta. All rights reserved. # BSD License. Look at license.txt for more info. -import os, strutils, sets +import os, strutils, sets, std/options import packageparser, common, options, nimscriptwrapper, cli -proc execHook*(nimBin: string, options: Options, hookAction: ActionType, before: bool): bool = +proc execHook*(nimBin: Option[string], options: Options, hookAction: ActionType, before: bool): bool = ## Returns whether to continue. result = true @@ -30,7 +30,7 @@ proc execHook*(nimBin: string, options: Options, hookAction: ActionType, before: if res.success: result = res.retVal -proc execCustom*(nimBin: string, nimbleFile: string, options: Options, +proc execCustom*(nimBin: Option[string], nimbleFile: string, options: Options, execResult: var ExecutionResult[bool]): bool = ## Executes the custom command using the nimscript backend. diff --git a/src/nimblepkg/nimscriptwrapper.nim b/src/nimblepkg/nimscriptwrapper.nim index e457ad89..81ed8bab 100644 --- a/src/nimblepkg/nimscriptwrapper.nim +++ b/src/nimblepkg/nimscriptwrapper.nim @@ -4,6 +4,7 @@ ## Implements the new configuration system for Nimble. Uses Nim as a ## scripting language. +import std/options as stdoptions import hashes, os, strutils, tables, times import compat/json import common, options, cli, tools @@ -40,9 +41,11 @@ proc writeExecutionOutput(data: string) = proc getNimblecache(): string = getTempDir() / "nimblecache-" & $getEnv("USER").hash().abs() -proc execNimscript(nimBin: string, +proc execNimscript(nimBin: Option[string], nimbleFile, nimsFile, actionName: string, options: Options, isHook: bool ): tuple[output: string, exitCode: int, stdout: string] = + if nimBin.isNone: + raise newNimbleError[NeedsNimBinError]("VM evaluation requires a Nim binary but none is available yet") let outFile = getNimbleTempDir() & ".out" isCustomTask = isCustomTask(actionName, options) @@ -57,7 +60,7 @@ proc execNimscript(nimBin: string, let nimbleVersion = common.nimbleVersion.split(".") var cmd = ( "$# e $# $# --colors:on $# $# $# $# $# $# $# $# $#" % [ - nimBin, + nimBin.get, "--hints:off --verbosity:0", "--define:nimbleExe=" & getAppFilename().quoteShell, "--define:NimbleVersion=" & common.nimbleVersion, @@ -132,7 +135,7 @@ onExit() result = nimsFile -proc getIniFile*(scriptName: string, options: Options, nimBin: string): string = +proc getIniFile*(scriptName: string, options: Options, nimBin: Option[string]): string = let nimsFile = getNimsFile(scriptName, options) @@ -155,7 +158,7 @@ proc getIniFile*(scriptName: string, options: Options, nimBin: string): string = raise nimbleError(stdout & "\nprintPkgInfo() failed") proc execScript( - nimBin: string, scriptName, actionName: string, options: Options, isHook: bool + nimBin: Option[string], scriptName, actionName: string, options: Options, isHook: bool ): ExecutionResult[bool] = let nimsFile = getNimsFile(scriptName, options) @@ -193,7 +196,7 @@ proc execScript( stdout.writeExecutionOutput() -proc execTask*(nimBin: string, scriptName, taskName: string, +proc execTask*(nimBin: Option[string], scriptName, taskName: string, options: Options): ExecutionResult[bool] = ## Executes the specified task in the specified script. ## @@ -203,7 +206,7 @@ proc execTask*(nimBin: string, scriptName, taskName: string, result = execScript(nimBin, scriptName, taskName, options, isHook=false) -proc execHook*(nimBin: string, scriptName, actionName: string, before: bool, +proc execHook*(nimBin: Option[string], scriptName, actionName: string, before: bool, options: Options): ExecutionResult[bool] = ## Executes the specified action's hook. Depending on ``before``, either ## the "before" or the "after" hook. @@ -221,5 +224,5 @@ proc hasTaskRequestedCommand*(execResult: ExecutionResult): bool = ## Determines whether the last executed task used ``setCommand`` return execResult.command != internalCmd -proc listTasks*(nimBin: string, scriptName: string, options: Options) = +proc listTasks*(nimBin: Option[string], scriptName: string, options: Options) = discard execScript(nimBin, scriptName, "", options, isHook=false) diff --git a/src/nimblepkg/packageparser.nim b/src/nimblepkg/packageparser.nim index 07622ad8..e386e0ec 100644 --- a/src/nimblepkg/packageparser.nim +++ b/src/nimblepkg/packageparser.nim @@ -1,6 +1,6 @@ # Copyright (C) Dominik Picheta. All rights reserved. # BSD License. Look at license.txt for more info. -import sets, streams, strutils, os, tables, sugar, strformat +import sets, streams, strutils, os, tables, sugar, strformat, std/options from sequtils import apply, map, toSeq import compat/parsecfg @@ -248,7 +248,7 @@ proc readPackageInfoFromNimble(path: string; result: var PackageInfo) = else: raise nimbleError("Cannot open package info: " & path) -proc readPackageInfoFromNims(nimBin: string, scriptName: string, options: Options, +proc readPackageInfoFromNims(nimBin: Option[string], scriptName: string, options: Options, result: var PackageInfo) = let iniFile = getIniFile(scriptName, options, nimBin) @@ -267,7 +267,7 @@ proc inferInstallRules(pkgInfo: var PackageInfo, options: Options) = if fileExists(pkgInfo.getRealDir() / pkgInfo.basicInfo.name.addFileExt("nim")): pkgInfo.installFiles.add(pkgInfo.basicInfo.name.addFileExt("nim")) -proc readPackageInfo(pkgInfo: var PackageInfo, nf: NimbleFile, options: Options, nimBin: string, onlyMinimalInfo=false, useCache=true) = +proc readPackageInfo(pkgInfo: var PackageInfo, nf: NimbleFile, options: Options, nimBin: Option[string], onlyMinimalInfo=false, useCache=true) = ## Reads package info from the specified Nimble file. ## ## Attempts to read it using the "old" Nimble ini format first, if that @@ -363,7 +363,7 @@ proc readPackageInfo(pkgInfo: var PackageInfo, nf: NimbleFile, options: Options, validateVersion($pkgInfo.basicInfo.version) validatePackageInfo(pkgInfo, options) -proc getPkgInfoFromFile*(nimBin: string,file: NimbleFile, options: Options, +proc getPkgInfoFromFile*(nimBin: Option[string],file: NimbleFile, options: Options, forValidation = false, useCache = true, onlyMinimalInfo = false): PackageInfo = ## Reads the specified .nimble file and returns its data as a PackageInfo ## object. Any validation errors are handled and displayed as warnings. @@ -377,13 +377,13 @@ proc getPkgInfoFromFile*(nimBin: string,file: NimbleFile, options: Options, else: raise exc -proc getPkgInfoVm*(dir: string, options: Options, nimBin: string, forValidation = false, onlyMinimalInfo = false): +proc getPkgInfoVm*(dir: string, options: Options, nimBin: Option[string], forValidation = false, onlyMinimalInfo = false): PackageInfo = ## Find the .nimble file in ``dir`` and parses it via VM, returning a PackageInfo. let nimbleFile = findNimbleFile(dir, true, options) result = getPkgInfoFromFile(nimBin, nimbleFile, options, forValidation, onlyMinimalInfo = onlyMinimalInfo) -proc getInstalledPkgs*(nimBin: string, libsDir: string, options: Options): seq[PackageInfo] = +proc getInstalledPkgs*(nimBin: Option[string], libsDir: string, options: Options): seq[PackageInfo] = ## Gets a list of installed packages. ## ## ``libsDir`` is in most cases: ~/.nimble/pkgs/ @@ -433,12 +433,12 @@ proc getInstalledPkgs*(nimBin: string, libsDir: string, options: Options): seq[P pkg.source = psInstalled result.add pkg -proc isNimScript*(nimBin: string, nf: string, options: Options): bool = +proc isNimScript*(nimBin: Option[string], nf: string, options: Options): bool = var pkg = initPackageInfo() readPackageInfo(pkg, nf, options, nimBin) result = pkg.isNimScript -proc toFullInfo*(pkg: PackageInfo, options: Options, nimBin: string): PackageInfo = +proc toFullInfo*(pkg: PackageInfo, options: Options, nimBin: Option[string]): PackageInfo = if pkg.isMinimal or pkg.infoKind == pikRequires: result = getPkgInfoFromFile(nimBin, pkg.mypath, options) result.source = pkg.source @@ -454,7 +454,7 @@ proc toFullInfo*(pkg: PackageInfo, options: Options, nimBin: string): PackageInf else: return pkg -proc getConcreteVersion*(pkgInfo: PackageInfo, options: Options, nimBin: string): Version = +proc getConcreteVersion*(pkgInfo: PackageInfo, options: Options, nimBin: Option[string]): Version = ## Returns a non-special version from the specified ``pkgInfo``. If the ## ``pkgInfo`` is minimal it looks it up and retrieves the concrete version. result = pkgInfo.basicInfo.version diff --git a/src/nimblepkg/reversedeps.nim b/src/nimblepkg/reversedeps.nim index 237ac930..2feac7ff 100644 --- a/src/nimblepkg/reversedeps.nim +++ b/src/nimblepkg/reversedeps.nim @@ -1,7 +1,7 @@ # Copyright (C) Dominik Picheta. All rights reserved. # BSD License. Look at license.txt for more info. -import sets, os, hashes +import sets, os, hashes, std/options import compat/json import options, version, download, jsonhelpers, nimbledatafile, sha1hashes, packageinfotypes, packageinfo, declarativeparser @@ -132,7 +132,7 @@ proc getRevDeps*(nimbleData: JsonNode, pkg: ReverseDependency): checksum: revDep[$ndjkRevDepChecksum].str.initSha1Hash) result.incl ReverseDependency(kind: rdkInstalled, pkgInfo: pkgBasicInfo) -proc toPkgInfo*(revDep: ReverseDependency, options: Options, nimBin: string): PackageInfo = +proc toPkgInfo*(revDep: ReverseDependency, options: Options, nimBin: Option[string]): PackageInfo = case revDep.kind of rdkInstalled: let pkgDir = revDep.pkgInfo.getPkgDest(options) diff --git a/src/nimblepkg/tools.nim b/src/nimblepkg/tools.nim index 923ce3cc..754e8573 100644 --- a/src/nimblepkg/tools.nim +++ b/src/nimblepkg/tools.nim @@ -2,6 +2,7 @@ # BSD License. Look at license.txt for more info. # # Various miscellaneous utility functions reside here. +import std/options as stdoptions import compat/pegs, strutils, os, uri, sets, json, parseutils, strformat, sequtils, macros, times @@ -135,8 +136,10 @@ proc tryDoCmdEx*(cmd: string): string {.discardable.} = raise nimbleError(errorMsg) return output -proc getNimrodVersion*(options: Options, nimBin: string): Version = - let vOutput = doCmdEx(nimBin.quoteShell & " -v").output +proc getNimrodVersion*(options: Options, nimBin: Option[string]): Version = + if nimBin.isNone: + raise newNimbleError[NeedsNimBinError]("Nim version query requires a Nim binary but none is available yet") + let vOutput = doCmdEx(nimBin.get.quoteShell & " -v").output var matches: array[0..MaxSubpatterns, string] if vOutput.find(peg"'Version'\s{(\d+\.)+\d+}", matches) == -1: if vOutput.find(peg"'Nim:'\s{(\d+\.)+\d+}", matches) == -1: diff --git a/src/nimblepkg/versiondiscovery.nim b/src/nimblepkg/versiondiscovery.nim index 471d3bfe..96a61d28 100644 --- a/src/nimblepkg/versiondiscovery.nim +++ b/src/nimblepkg/versiondiscovery.nim @@ -9,8 +9,8 @@ import compat/[sequtils] export declarativeparser type - GetPackageMinimal* = proc (pv: PkgTuple, options: Options, nimBin: string): seq[PackageMinimalInfo] - GetPackageMinimalAsync* = proc (pv: PkgTuple, options: Options, nimBin: string): Future[seq[PackageMinimalInfo]] {.async.} + GetPackageMinimal* = proc (pv: PkgTuple, options: Options, nimBin: Option[string]): seq[PackageMinimalInfo] + GetPackageMinimalAsync* = proc (pv: PkgTuple, options: Options, nimBin: Option[string]): Future[seq[PackageMinimalInfo]] {.async.} TaggedVersionsCache* = Table[string, seq[PackageMinimalInfo]] ## Central cache for all package tagged versions, keyed by normalized package name @@ -71,11 +71,11 @@ proc getPackageDownloadInfo*(pv: PkgTuple, options: Options, doPrompt = false, v let downloadDir = getCacheDownloadDir(url, pv.ver, options, vcsRevision) PackageDownloadInfo(meth: some meth, url: url, subdir: subdir, downloadDir: downloadDir, pv: pv, vcsRevision: vcsRevision) -proc getPackageFromFileUrl*(fileUrl: string, options: Options, nimBin: string): PackageInfo = +proc getPackageFromFileUrl*(fileUrl: string, options: Options, nimBin: Option[string]): PackageInfo = let absPath = extractFilePathFromURL(fileUrl) getPkgInfo(absPath, options, nimBin, pikRequires) -proc downloadFromDownloadInfo*(dlInfo: PackageDownloadInfo, options: Options, nimBin: string): (DownloadPkgResult, Option[DownloadMethod]) = +proc downloadFromDownloadInfo*(dlInfo: PackageDownloadInfo, options: Options, nimBin: Option[string]): (DownloadPkgResult, Option[DownloadMethod]) = if dlInfo.isFileUrl: let pkgInfo = getPackageFromFileUrl(dlInfo.url, options, nimBin) let downloadRes = (dir: pkgInfo.getNimbleFileDir(), version: pkgInfo.basicInfo.version, vcsRevision: notSetSha1Hash) @@ -85,11 +85,11 @@ proc downloadFromDownloadInfo*(dlInfo: PackageDownloadInfo, options: Options, ni dlInfo.downloadDir, vcsRevision = dlInfo.vcsRevision, nimBin = nimBin) (downloadRes, dlInfo.meth) -proc downloadPkgFromUrl*(pv: PkgTuple, options: Options, doPrompt = false, nimBin: string): (DownloadPkgResult, Option[DownloadMethod]) = +proc downloadPkgFromUrl*(pv: PkgTuple, options: Options, doPrompt = false, nimBin: Option[string]): (DownloadPkgResult, Option[DownloadMethod]) = let dlInfo = getPackageDownloadInfo(pv, options, doPrompt) downloadFromDownloadInfo(dlInfo, options, nimBin) -proc downloadPkInfoForPv*(pv: PkgTuple, options: Options, doPrompt = false, nimBin: string): PackageInfo = +proc downloadPkInfoForPv*(pv: PkgTuple, options: Options, doPrompt = false, nimBin: Option[string]): PackageInfo = let downloadRes = downloadPkgFromUrl(pv, options, doPrompt, nimBin) result = getPkgInfo(downloadRes[0].dir, options, nimBin, pikRequires) @@ -174,7 +174,7 @@ proc cacheToPackageVersionTable*(options: Options): Table[string, PackageVersion if validVersions.len > 0: result[pkgName] = PackageVersions(pkgName: pkgName, versions: validVersions) -proc getPackageMinimalVersionsFromRepo*(repoDir: string, pkg: PkgTuple, version: Version, downloadMethod: DownloadMethod, options: Options, nimBin: string): seq[PackageMinimalInfo] = +proc getPackageMinimalVersionsFromRepo*(repoDir: string, pkg: PkgTuple, version: Version, downloadMethod: DownloadMethod, options: Options, nimBin: Option[string]): seq[PackageMinimalInfo] = result = newSeq[PackageMinimalInfo]() let name = pkg[0] @@ -269,7 +269,7 @@ proc getPackageMinimalVersionsFromRepo*(repoDir: string, pkg: PkgTuple, version: except CatchableError as e: displayWarning(&"Error cleaning up temporary directory {tempDir}: {e.msg}", LowPriority) -proc getPackageMinimalVersionsFromRepoAsync*(repoDir: string, pkg: PkgTuple, version: Version, downloadMethod: DownloadMethod, options: Options, nimBin: string): Future[seq[PackageMinimalInfo]] {.async.} = +proc getPackageMinimalVersionsFromRepoAsync*(repoDir: string, pkg: PkgTuple, version: Version, downloadMethod: DownloadMethod, options: Options, nimBin: Option[string]): Future[seq[PackageMinimalInfo]] {.async.} = ## Async version of getPackageMinimalVersionsFromRepo that uses async operations for VCS commands. result = newSeq[PackageMinimalInfo]() @@ -361,7 +361,7 @@ proc getPackageMinimalVersionsFromRepoAsyncFast*( pkg: PkgTuple, downloadMethod: DownloadMethod, options: Options, - nimBin: string + nimBin: Option[string] ): Future[seq[PackageMinimalInfo]] {.async.} = ## Fast version that reads nimble files directly from git tags without checkout. ## Uses git ls-tree and git show to avoid expensive checkout + copyDir operations. @@ -474,7 +474,7 @@ proc getPackageMinimalVersionsFromRepoAsyncFast*( except CatchableError as e: displayWarning(&"Error saving tagged versions for {name}: {e.msg}", LowPriority) -proc downloadMinimalPackage*(pv: PkgTuple, options: Options, nimBin: string): seq[PackageMinimalInfo] = +proc downloadMinimalPackage*(pv: PkgTuple, options: Options, nimBin: Option[string]): seq[PackageMinimalInfo] = if pv.name == "": return newSeq[PackageMinimalInfo]() if pv.isNim and not options.disableNimBinaries: if pv.ver.kind == verSpecial: @@ -488,7 +488,7 @@ proc downloadMinimalPackage*(pv: PkgTuple, options: Options, nimBin: string): se if nimVersion != "": ver.speSemanticVersion = some(nimVersion) return @[PackageMinimalInfo(name: "nim", version: ver)] - return getAllNimReleases(options, getNimVersionFromBin(nimBin)) + return getAllNimReleases(options, getNimVersionFromBin(nimBin.get)) # During version discovery, we only need to read .nimble files, not compile code # So we ignore submodules to speed up cloning and avoid failures from broken submodules var versionDiscoveryOptions = options @@ -507,7 +507,7 @@ proc downloadMinimalPackage*(pv: PkgTuple, options: Options, nimBin: string): se if r.url == "": r.url = pv.name -proc downloadFromDownloadInfoAsync*(dlInfo: PackageDownloadInfo, options: Options, nimBin: string): Future[(DownloadPkgResult, Option[DownloadMethod])] {.async.} = +proc downloadFromDownloadInfoAsync*(dlInfo: PackageDownloadInfo, options: Options, nimBin: Option[string]): Future[(DownloadPkgResult, Option[DownloadMethod])] {.async.} = ## Async version of downloadFromDownloadInfo that uses async download operations. if dlInfo.isFileUrl: let pkgInfo = getPackageFromFileUrl(dlInfo.url, options, nimBin) @@ -518,19 +518,19 @@ proc downloadFromDownloadInfoAsync*(dlInfo: PackageDownloadInfo, options: Option dlInfo.downloadDir, vcsRevision = dlInfo.vcsRevision, nimBin = nimBin) return (downloadRes, dlInfo.meth) -proc downloadPkgFromUrlAsync*(pv: PkgTuple, options: Options, doPrompt = false, nimBin: string): Future[(DownloadPkgResult, Option[DownloadMethod])] {.async.} = +proc downloadPkgFromUrlAsync*(pv: PkgTuple, options: Options, doPrompt = false, nimBin: Option[string]): Future[(DownloadPkgResult, Option[DownloadMethod])] {.async.} = ## Async version of downloadPkgFromUrl that downloads from a package URL. let dlInfo = getPackageDownloadInfo(pv, options, doPrompt) return await downloadFromDownloadInfoAsync(dlInfo, options, nimBin) -proc downloadPkInfoForPvAsync*(pv: PkgTuple, options: Options, doPrompt = false, nimBin: string): Future[PackageInfo] {.async.} = +proc downloadPkInfoForPvAsync*(pv: PkgTuple, options: Options, doPrompt = false, nimBin: Option[string]): Future[PackageInfo] {.async.} = ## Async version of downloadPkInfoForPv that downloads and gets package info. let downloadRes = await downloadPkgFromUrlAsync(pv, options, doPrompt, nimBin) return getPkgInfo(downloadRes[0].dir, options, nimBin, pikRequires) var downloadCache {.threadvar.}: Table[string, Future[seq[PackageMinimalInfo]]] -proc downloadMinimalPackageAsyncImpl(pv: PkgTuple, options: Options, nimBin: string): Future[seq[PackageMinimalInfo]] {.async.} = +proc downloadMinimalPackageAsyncImpl(pv: PkgTuple, options: Options, nimBin: Option[string]): Future[seq[PackageMinimalInfo]] {.async.} = ## Internal implementation of async download without caching. if pv.name == "": return newSeq[PackageMinimalInfo]() if pv.isNim and not options.disableNimBinaries: @@ -538,7 +538,7 @@ proc downloadMinimalPackageAsyncImpl(pv: PkgTuple, options: Options, nimBin: str # For special versions, delegate to the sync version which handles downloading {.gcsafe.}: return downloadMinimalPackage(pv, options, nimBin) - return getAllNimReleases(options, getNimVersionFromBin(nimBin)) + return getAllNimReleases(options, getNimVersionFromBin(nimBin.get)) # During version discovery, we only need to read .nimble files, not compile code # So we ignore submodules to speed up cloning and avoid failures from broken submodules @@ -561,7 +561,7 @@ proc downloadMinimalPackageAsyncImpl(pv: PkgTuple, options: Options, nimBin: str # Always set URL for URL-based packages to ensure subdirectories have correct URL r.url = pv.name -proc downloadMinimalPackageAsync*(pv: PkgTuple, options: Options, nimBin: string): Future[seq[PackageMinimalInfo]] {.async.} = +proc downloadMinimalPackageAsync*(pv: PkgTuple, options: Options, nimBin: Option[string]): Future[seq[PackageMinimalInfo]] {.async.} = ## Async version of downloadMinimalPackage with deduplication. ## If multiple calls request the same package concurrently, they share the same download. ## Cache key uses canonical package URL (not version) since we download all versions anyway. @@ -614,7 +614,7 @@ proc fillPackageTableFromPreferred*(packages: var Table[string, PackageVersions] proc getInstalledMinimalPackages*(options: Options): seq[PackageMinimalInfo] = getInstalledPkgsMin(options.getPkgsDir(), options).mapIt(it.getMinimalInfo(options)) -proc getMinimalFromPreferred(pv: PkgTuple, getMinimalPackage: GetPackageMinimal, preferredPackages: seq[PackageMinimalInfo], options: Options, nimBin: string): seq[PackageMinimalInfo] = +proc getMinimalFromPreferred(pv: PkgTuple, getMinimalPackage: GetPackageMinimal, preferredPackages: seq[PackageMinimalInfo], options: Options, nimBin: Option[string]): seq[PackageMinimalInfo] = # Check if we have a preferred package first for pp in preferredPackages: if (pp.name == pv.name or pp.url == pv.name) and pp.version.withinRange(pv.ver): @@ -630,7 +630,7 @@ proc getMinimalFromPreferred(pv: PkgTuple, getMinimalPackage: GetPackageMinimal if result.len == 0: raise -proc getMinimalFromPreferredAsync*(pv: PkgTuple, getMinimalPackage: GetPackageMinimalAsync, preferredPackages: seq[PackageMinimalInfo], options: Options, nimBin: string): Future[seq[PackageMinimalInfo]] {.async.} = +proc getMinimalFromPreferredAsync*(pv: PkgTuple, getMinimalPackage: GetPackageMinimalAsync, preferredPackages: seq[PackageMinimalInfo], options: Options, nimBin: Option[string]): Future[seq[PackageMinimalInfo]] {.async.} = ## Async version of getMinimalFromPreferred that uses async package fetching. # Check if we have a preferred package first for pp in preferredPackages: @@ -658,7 +658,7 @@ proc expandActiveFeatures(pkgMin: var PackageMinimalInfo, versions: Table[string for req in pkgMin.features[featureName]: pkgMin.requires.addUnique(convertNimAliasToNim(req)) -proc processRequirements(versions: var Table[string, PackageVersions], pv: PkgTuple, visited: var HashSet[PkgTuple], getMinimalPackage: GetPackageMinimal, preferredPackages: seq[PackageMinimalInfo] = newSeq[PackageMinimalInfo](), options: Options, nimBin: string) = +proc processRequirements(versions: var Table[string, PackageVersions], pv: PkgTuple, visited: var HashSet[PkgTuple], getMinimalPackage: GetPackageMinimal, preferredPackages: seq[PackageMinimalInfo] = newSeq[PackageMinimalInfo](), options: Options, nimBin: Option[string]) = if pv in visited: return @@ -747,7 +747,7 @@ proc processRequirements(versions: var Table[string, PackageVersions], pv: PkgTu # we need to avoid adding it to the package table as this will cause the solver to fail displayWarning(&"Error processing requirements for {pv.name}: {e.msg}", HighPriority) -proc processRequirementsAsync*(pv: PkgTuple, visitedParam: HashSet[PkgTuple], getMinimalPackage: GetPackageMinimalAsync, preferredPackages: seq[PackageMinimalInfo] = newSeq[PackageMinimalInfo](), options: Options, nimBin: string): Future[Table[string, PackageVersions]] {.async.} = +proc processRequirementsAsync*(pv: PkgTuple, visitedParam: HashSet[PkgTuple], getMinimalPackage: GetPackageMinimalAsync, preferredPackages: seq[PackageMinimalInfo] = newSeq[PackageMinimalInfo](), options: Options, nimBin: Option[string]): Future[Table[string, PackageVersions]] {.async.} = ## Async version of processRequirements that returns computed versions instead of mutating shared state. ## This allows for safe parallel execution since there's no shared mutable state. ## Takes visited by value since we pass separate copies to each top-level dependency branch. @@ -856,7 +856,7 @@ proc processRequirementsAsync*(pv: PkgTuple, visitedParam: HashSet[PkgTuple], ge # we need to avoid adding it to the package table as this will cause the solver to fail displayWarning(&"Error processing requirements for {pv.name}: {e.msg}", HighPriority) -proc collectAllVersions*(versions: var Table[string, PackageVersions], package: PackageMinimalInfo, options: Options, getMinimalPackage: GetPackageMinimal, preferredPackages: seq[PackageMinimalInfo] = newSeq[PackageMinimalInfo](), nimBin: string) = +proc collectAllVersions*(versions: var Table[string, PackageVersions], package: PackageMinimalInfo, options: Options, getMinimalPackage: GetPackageMinimal, preferredPackages: seq[PackageMinimalInfo] = newSeq[PackageMinimalInfo](), nimBin: Option[string]) = var visited = initHashSet[PkgTuple]() for pv in package.requires: processRequirements(versions, pv, visited, getMinimalPackage, preferredPackages, options, nimBin) @@ -872,7 +872,7 @@ proc mergeVersionTables(dest: var Table[string, PackageVersions], source: Table[ for ver in pkgVersions.versions: dest[pkgName].versions.addUnique ver -proc collectAllVersionsAsync*(package: PackageMinimalInfo, options: Options, getMinimalPackage: GetPackageMinimalAsync, preferredPackages: seq[PackageMinimalInfo] = newSeq[PackageMinimalInfo](), nimBin: string): Future[Table[string, PackageVersions]] {.async.} = +proc collectAllVersionsAsync*(package: PackageMinimalInfo, options: Options, getMinimalPackage: GetPackageMinimalAsync, preferredPackages: seq[PackageMinimalInfo] = newSeq[PackageMinimalInfo](), nimBin: Option[string]): Future[Table[string, PackageVersions]] {.async.} = ## Async version of collectAllVersions that processes top-level dependencies in parallel. ## Uses return-based approach: each branch returns its computed versions, then we merge them. ## This allows for safe parallel execution with no shared mutable state during processing. diff --git a/tests/tasynctools.nim b/tests/tasynctools.nim index 502596cd..87cdd9e1 100644 --- a/tests/tasynctools.nim +++ b/tests/tasynctools.nim @@ -1,4 +1,5 @@ import unittest, chronos, strutils, os, tables +import std/options import nimblepkg/[tools, download, options, packageinfotypes, sha1hashes, version, versiondiscovery] {.used.} @@ -162,8 +163,8 @@ suite "Async Tools": let verRange = parseVersionRange("#v0.4.0") # Find nim binary for validation - let nimBin = findExe("nim") - if nimBin == "": + let nimBin = some(findExe("nim")) + if nimBin.get == "": skip() let result = waitFor downloadPkgAsync( @@ -201,8 +202,8 @@ suite "Async Tools": onlyTip = false, options = options) # Find nim binary - let nimBin = findExe("nim") - if nimBin == "": + let nimBin = some(findExe("nim")) + if nimBin.get == "": skip() # Get minimal versions for a version range @@ -264,8 +265,8 @@ suite "Async Tools": createDir(tmpDir) # Find nim binary - let nimBin = findExe("nim") - if nimBin == "": + let nimBin = some(findExe("nim")) + if nimBin.get == "": skip() # Create options @@ -312,8 +313,8 @@ suite "Async Tools": createDir(tmpDir) # Find nim binary - let nimBin = findExe("nim") - if nimBin == "": + let nimBin = some(findExe("nim")) + if nimBin.get == "": skip() # Create options @@ -365,8 +366,8 @@ suite "Async Tools": createDir(tmpDir) # Find nim binary - let nimBin = findExe("nim") - if nimBin == "": + let nimBin = some(findExe("nim")) + if nimBin.get == "": skip() # Create options with async enabled (not legacy) @@ -477,8 +478,8 @@ suite "Async Tools": onlyTip = false, options = options) # Find nim binary - let nimBin = findExe("nim") - if nimBin == "": + let nimBin = some(findExe("nim")) + if nimBin.get == "": skip() # Get minimal versions using the FAST method (no checkout) diff --git a/tests/tdeclarativeparser.nim b/tests/tdeclarativeparser.nim index e3839622..9a56c8eb 100644 --- a/tests/tdeclarativeparser.nim +++ b/tests/tdeclarativeparser.nim @@ -18,7 +18,7 @@ proc getNimbleFileFromPkgNameHelper(pkgName: string, ver = VersionRange(kind: ve ], ) options.pkgCachePath = "./nimbleDir/pkgcache" - let pkgInfo = downloadPkInfoForPv(pv, options, nimBin = "nim") + let pkgInfo = downloadPkInfoForPv(pv, options, nimBin = some("nim")) pkgInfo.myPath suite "Declarative parsing": @@ -74,12 +74,12 @@ suite "Declarative parsing": setSuppressMessages(false) removeDir(options.pkgCachePath) let pv = parseRequires("nimfp >= 0.3.4") - let downloadRes = pv.downloadPkgFromUrl(options, nimBin = "nim")[0] + let downloadRes = pv.downloadPkgFromUrl(options, nimBin = some("nim"))[0] #This is just to setup the test. We need a git dir to work on let repoDir = downloadRes.dir let downloadMethod = DownloadMethod git let packageVersions = getPackageMinimalVersionsFromRepo( - repoDir, pv, downloadRes.version, downloadMethod, options, nimBin = "nim" + repoDir, pv, downloadRes.version, downloadMethod, options, nimBin = some("nim") ) #we know these versions are available diff --git a/tests/tfilepathrequires.nim b/tests/tfilepathrequires.nim index a945474c..b29ac4ac 100644 --- a/tests/tfilepathrequires.nim +++ b/tests/tfilepathrequires.nim @@ -1,4 +1,5 @@ {.used.} +import std/options as stdoptions import unittest, os, sequtils, strutils import testscommon import nimblepkg/[common, options, declarativeparser, packageinfotypes] @@ -68,8 +69,8 @@ suite "file path requires": var options = initOptions() options.isFilePathDiscovering = true cd "filepathrequires/dep3file": - let entryPkg = getPkgInfo(getCurrentDir(), options, nimBin = "nim", level = pikRequires) - loadFilePathPkgs(entryPkg, options, nimBin = "nim") + let entryPkg = getPkgInfo(getCurrentDir(), options, nimBin = some("nim"), level = pikRequires) + loadFilePathPkgs(entryPkg, options, nimBin = some("nim")) check options.filePathPkgs.len == 2 test "should not allow filepath requires in other deps if they werent openened through a filepath require": diff --git a/tests/tlazybootstrap.nim b/tests/tlazybootstrap.nim new file mode 100644 index 00000000..8eb166dd --- /dev/null +++ b/tests/tlazybootstrap.nim @@ -0,0 +1,59 @@ +## Verifies that bootstrap Nim resolution is lazy: it only fires in branches +## that actually need a Nim binary, not eagerly at dispatch entry. +## +## Uses the NIMBLE_TRACE_BOOTSTRAP instrumentation hook in +## src/nimblepkg/nimresolution.nim, which prints "NIMBLE_BOOTSTRAP_RESOLVED" +## to stderr on every call to getBootstrapNimResolved. + +{.used.} + +import unittest, os, osproc, strutils, strtabs, sequtils, streams +import testscommon + +proc countBootstrap(args: varargs[string], cwd = ""): tuple[count: int, exitCode: int, output: string] = + var quotedArgs = @args + quotedArgs.insert("--noColor") + var envCopy = newStringTable() + for k, v in envPairs(): + envCopy[k] = v + envCopy["NIMBLE_TRACE_BOOTSTRAP"] = "1" + let workdir = if cwd.len > 0: cwd else: getCurrentDir() + let p = startProcess( + command = nimblePath, + workingDir = workdir, + args = quotedArgs, + env = envCopy, + options = {poStdErrToStdOut, poUsePath}) + let outp = p.outputStream.readAll() + let code = p.waitForExit() + p.close() + var n = 0 + for line in outp.splitLines(): + if line == "NIMBLE_BOOTSTRAP_RESOLVED": + inc n + (n, code, outp) + +suite "lazy bootstrap nim resolution": + test "--version never resolves bootstrap": + # --version short-circuits in dispatch before any action handler runs. + let r = countBootstrap("--version") + checkpoint r.output + check r.count == 0 + + test "dump on declarative package resolves bootstrap at most once (memoized)": + let r = countBootstrap("dump", cwd = getCurrentDir() / "testdump") + checkpoint r.output + check r.exitCode == 0 + check r.count <= 1 + + test "tasks on declarative package resolves bootstrap at most once": + let r = countBootstrap("tasks", cwd = getCurrentDir() / "testdump") + checkpoint r.output + check r.exitCode == 0 + check r.count <= 1 + + test "check on declarative package resolves bootstrap at most once": + let r = countBootstrap("check", cwd = getCurrentDir() / "testdump") + checkpoint r.output + check r.exitCode == 0 + check r.count <= 1 diff --git a/tests/tnimbinaries.nim b/tests/tnimbinaries.nim index db8ba010..c4584799 100644 --- a/tests/tnimbinaries.nim +++ b/tests/tnimbinaries.nim @@ -28,7 +28,7 @@ suite "Nim binaries": var options = initOptions() let pv = ("nim", VersionRange(kind: verAny)) let releases: seq[Version] = getOfficialReleases(options) - let nimBin = "nim" + let nimBin = some("nim") let minimalPgks = downloadMinimalPackage(pv, options, nimBin) check minimalPgks.len > 0 @@ -50,7 +50,7 @@ suite "Nim binaries": check nimInstalled.isSome check nimInstalled.get().ver == newVersion("2.0.4") options.nimBin = some options.makeNimBin("nim") - let pkgInfo = getPkgInfo(nimInstalled.get().dir, options, nimBin = "nim") + let pkgInfo = getPkgInfo(nimInstalled.get().dir, options, nimBin = some("nim")) check pkgInfo.basicInfo.name == "nim" test "Should be able to reuse -without compiling- a Nim version": diff --git a/tests/tsat.nim b/tests/tsat.nim index bab9ff55..4e321c1e 100644 --- a/tests/tsat.nim +++ b/tests/tsat.nim @@ -6,7 +6,7 @@ import std/[tables, json, jsonutils, strutils, times, options] import nimblepkg/[version, nimblesat, options, config, packageinfotypes, versiondiscovery] from nimblepkg/common import cd, NimbleError -let nimBin = "nim" +let nimBin = some("nim") #Test utils: proc downloadAndStorePackageVersionTableFor(pkgName: string, options: Options) = #Downloads all the dependencies for a given package and store the minimal version of the deps in a json file. diff --git a/tests/tversiondiscovery.nim b/tests/tversiondiscovery.nim index 0a622d1c..6c99c1a2 100644 --- a/tests/tversiondiscovery.nim +++ b/tests/tversiondiscovery.nim @@ -5,7 +5,7 @@ import std/[tables, sequtils, strutils, options, strformat] import nimblepkg/[version, nimblesat, options, config, download, packageinfotypes, versiondiscovery] from nimblepkg/common import cd, NimbleError -let nimBin = "nim" +let nimBin = some("nim") suite "Version Discovery": test "should be able to download a package and select its deps": @@ -175,7 +175,7 @@ suite "Version Discovery": # and another requires a normal version, both should be kept in the version table. # Mock getMinimalPackage that returns controlled versions - proc mockGetMinimalPackage(pv: PkgTuple, options: Options, nimBin: string): seq[PackageMinimalInfo] = + proc mockGetMinimalPackage(pv: PkgTuple, options: Options, nimBin: Option[string]): seq[PackageMinimalInfo] = case pv.name of "dep": if pv.ver.kind == verSpecial: @@ -220,7 +220,7 @@ suite "Version Discovery": pkgVersionTable["root"] = PackageVersions(pkgName: "root", versions: @[root]) # Collect all versions - this triggers processRequirements - collectAllVersions(pkgVersionTable, root, options, mockGetMinimalPackage, nimBin = "nim") + collectAllVersions(pkgVersionTable, root, options, mockGetMinimalPackage, nimBin = some("nim")) check pkgVersionTable.hasKey("dep") let depVersions = pkgVersionTable["dep"].versions.mapIt($it.version)