Skip to content

Commit

Permalink
SAT implementation (#1200)
Browse files Browse the repository at this point in the history
* Removes noise from SAT implementation

disabled tests will be added in a future PR

* removes import

* adds nimble install to tests

* Update .github/workflows/test.yml

Co-authored-by: ringabout <[email protected]>

---------

Co-authored-by: Andreas Rumpf <[email protected]>
Co-authored-by: ringabout <[email protected]>
  • Loading branch information
3 people committed Apr 6, 2024
1 parent a6443cd commit 5958f92
Show file tree
Hide file tree
Showing 8 changed files with 729 additions and 65 deletions.
2 changes: 1 addition & 1 deletion nimble.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ installExt = @["nim"]

# Dependencies

requires "nim >= 0.13.0"
requires "nim >= 0.13.0", "sat"
requires "checksums"

when defined(nimdistros):
Expand Down
112 changes: 53 additions & 59 deletions src/nimble.nim
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ import std/options as std_opt

import strutils except toLower
from unicode import toLower

import sat/sat
import nimblepkg/packageinfotypes, nimblepkg/packageinfo, nimblepkg/version,
nimblepkg/tools, nimblepkg/download, nimblepkg/config, nimblepkg/common,
nimblepkg/tools, nimblepkg/download, nimblepkg/common,
nimblepkg/publish, nimblepkg/options, nimblepkg/packageparser,
nimblepkg/cli, nimblepkg/packageinstaller, nimblepkg/reversedeps,
nimblepkg/nimscriptexecutor, nimblepkg/init, nimblepkg/vcstools,
nimblepkg/checksums, nimblepkg/topologicalsort, nimblepkg/lockfile,
nimblepkg/nimscriptwrapper, nimblepkg/developfile, nimblepkg/paths,
nimblepkg/nimbledatafile, nimblepkg/packagemetadatafile,
nimblepkg/displaymessages, nimblepkg/sha1hashes, nimblepkg/syncfile,
nimblepkg/deps
nimblepkg/deps, nimblepkg/nimblesat

const
nimblePathsFileName* = "nimble.paths"
Expand All @@ -28,34 +28,6 @@ const
nimblePathsEnv = "__NIMBLE_PATHS"
separator = when defined(windows): ";" else: ":"

proc refresh(options: Options) =
## Downloads the package list from the specified URL.
##
## If the download is not successful, an exception is raised.
if options.offline:
raise nimbleError("Cannot refresh package list in offline mode.")

let parameter =
if options.action.typ == actionRefresh:
options.action.optionalURL
else:
""

if parameter.len > 0:
if parameter.isUrl:
let cmdLine = PackageList(name: "commandline", urls: @[parameter])
fetchList(cmdLine, options)
else:
if parameter notin options.config.packageLists:
let msg = "Package list with the specified name not found."
raise nimbleError(msg)

fetchList(options.config.packageLists[parameter], options)
else:
# Try each package list in config
for name, list in options.config.packageLists:
fetchList(list, options)

proc initPkgList(pkgInfo: PackageInfo, options: Options): seq[PackageInfo] =
let
installedPkgs = getInstalledPkgsMin(options.getPkgsDir(), options)
Expand All @@ -79,6 +51,50 @@ proc checkSatisfied(options: Options, dependencies: seq[PackageInfo]) =
[pkgInfo.basicInfo.name, $currentVer, $pkgsInPath[pkgInfo.basicInfo.name]])
pkgsInPath[pkgInfo.basicInfo.name] = currentVer

proc processFreeDependenciesSAT(rootPkgInfo: PackageInfo, pkgList: seq[PackageInfo], options: Options): HashSet[PackageInfo] =
result = solveLocalPackages(rootPkgInfo, pkgList)
if result.len > 0: return result

var reverseDependencies: seq[PackageBasicInfo] = @[]
var pkgsToInstall: seq[(string, Version)] = @[]
var output = ""
result = solvePackages(rootPkgInfo, pkgList, pkgsToInstall, options, output)
if pkgsToInstall.len > 0:
for pkg in pkgsToInstall:
let dep = (name: pkg[0], ver: pkg[1].toVersionRange)
let resolvedDep = dep.resolveAlias(options)
display("Installing", $resolvedDep, priority = HighPriority)
let toInstall = @[(resolvedDep.name, resolvedDep.ver)]
#TODO install here will download the package again. We could use the already downloaded package
#from the cache
let (packages, _) = install(toInstall, options,
doPrompt = false, first = false, fromLockFile = false, preferredPackages = @[])

for pkg in packages:
if result.contains pkg:
# If the result already contains the newly tried to install package
# we had to merge its special versions set into the set of the old
# one.
result[pkg].metaData.specialVersions.incl(
pkg.metaData.specialVersions)
else:
result.incl pkg

if not pkg.isLink:
reverseDependencies.add(pkg.basicInfo)
if result.len > 0:
# We add the reverse deps to the JSON file here because we don't want
# them added if the above errorenous condition occurs
# (unsatisfiable dependendencies).
# N.B. NimbleData is saved in installFromDir.
for i in reverseDependencies:
addRevDep(options.nimbleData, i, rootPkgInfo)
return result
else:
display("Error", output, Error, priority = HighPriority)
raise nimbleError("Unsatisfiable dependencies")


proc processFreeDependencies(pkgInfo: PackageInfo,
requirements: seq[PkgTuple],
options: Options,
Expand All @@ -92,7 +108,12 @@ proc processFreeDependencies(pkgInfo: PackageInfo,
"processFreeDependencies needs pkgInfo.requires"

var pkgList {.global.}: seq[PackageInfo]
once: pkgList = initPkgList(pkgInfo, options)

once:
pkgList = initPkgList(pkgInfo, options)
if options.useSatSolver:
return processFreeDependenciesSAT(pkgInfo, pkgList, options)

display("Verifying", "dependencies for $1@$2" %
[pkgInfo.basicInfo.name, $pkgInfo.basicInfo.version],
priority = HighPriority)
Expand Down Expand Up @@ -686,33 +707,6 @@ proc processLockedDependencies(pkgInfo: PackageInfo, options: Options):

return res.toHashSet

proc getDownloadInfo*(pv: PkgTuple, options: Options,
doPrompt: bool, ignorePackageCache = false): (DownloadMethod, string,
Table[string, string]) =
if pv.name.isURL:
let (url, metadata) = getUrlData(pv.name)
return (checkUrlType(url), url, metadata)
else:
var pkg = initPackage()
if getPackage(pv.name, options, pkg, ignorePackageCache):
let (url, metadata) = getUrlData(pkg.url)
return (pkg.downloadMethod, url, metadata)
else:
# If package is not found give the user a chance to refresh
# package.json
if doPrompt and not options.offline and
options.prompt(pv.name & " not found in any local packages.json, " &
"check internet for updated packages?"):
refresh(options)

# Once we've refreshed, try again, but don't prompt if not found
# (as we've already refreshed and a failure means it really
# isn't there)
# Also ignore the package cache so the old info isn't used
return getDownloadInfo(pv, options, false, true)
else:
raise nimbleError(pkgNotFoundMsg(pv))

proc compileNim(realDir: string) =
let command = when defined(windows): "build_all.bat" else: "./build_all.sh"
cd realDir:
Expand Down
68 changes: 65 additions & 3 deletions src/nimblepkg/download.nim
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import parseutils, os, osproc, strutils, tables, pegs, uri, strformat,
from algorithm import SortOrder, sorted

import packageinfotypes, packageparser, version, tools, common, options, cli,
sha1hashes, vcstools
sha1hashes, vcstools, displaymessages, packageinfo, config

type
DownloadPkgResult* = tuple
Expand Down Expand Up @@ -461,13 +461,20 @@ proc downloadPkg*(url: string, verRange: VersionRange,

if options.offline:
raise nimbleError("Cannot download in offline mode.")

let downloadDir =
if downloadPath == "":
(getNimbleTempDir() / getDownloadDirName(url, verRange, vcsRevision))
let dir = if options.pkgCachePath != "":
options.pkgCachePath
else:
getNimbleTempDir()
(dir / getDownloadDirName(url, verRange, vcsRevision))
else:
downloadPath

if options.pkgCachePath != "" and dirExists(downloadDir):
#TODO test integrity of the package
return (dir: downloadDir, version: newVersion getSimpleString(verRange), vcsRevision: notSetSha1Hash)

createDir(downloadDir)
var modUrl =
if url.startsWith("git:https://") and options.config.cloneUsingHttps:
Expand Down Expand Up @@ -544,6 +551,61 @@ proc getDevelopDownloadDir*(url, subdir: string, options: Options): string =
else:
getCurrentDir() / options.action.path / downloadDirName

proc refresh*(options: Options) =
## Downloads the package list from the specified URL.
##
## If the download is not successful, an exception is raised.
if options.offline:
raise nimbleError("Cannot refresh package list in offline mode.")

let parameter =
if options.action.typ == actionRefresh:
options.action.optionalURL
else:
""

if parameter.len > 0:
if parameter.isUrl:
let cmdLine = PackageList(name: "commandline", urls: @[parameter])
fetchList(cmdLine, options)
else:
if parameter notin options.config.packageLists:
let msg = "Package list with the specified name not found."
raise nimbleError(msg)

fetchList(options.config.packageLists[parameter], options)
else:
# Try each package list in config
for name, list in options.config.packageLists:
fetchList(list, options)

proc getDownloadInfo*(pv: PkgTuple, options: Options,
doPrompt: bool, ignorePackageCache = false): (DownloadMethod, string,
Table[string, string]) =
if pv.name.isURL:
let (url, metadata) = getUrlData(pv.name)
return (checkUrlType(url), url, metadata)
else:
var pkg = initPackage()
if getPackage(pv.name, options, pkg, ignorePackageCache):
let (url, metadata) = getUrlData(pkg.url)
return (pkg.downloadMethod, url, metadata)
else:
# If package is not found give the user a chance to refresh
# package.json
if doPrompt and not options.offline and
options.prompt(pv.name & " not found in any local packages.json, " &
"check internet for updated packages?"):
refresh(options)

# Once we've refreshed, try again, but don't prompt if not found
# (as we've already refreshed and a failure means it really
# isn't there)
# Also ignore the package cache so the old info isn't used
return getDownloadInfo(pv, options, false, true)
else:
raise nimbleError(pkgNotFoundMsg(pv))

when isMainModule:
import unittest

Expand Down
Loading

0 comments on commit 5958f92

Please sign in to comment.