From fd397b356e9be1d3572ace965ead57120803a0ac Mon Sep 17 00:00:00 2001 From: kitsunyan Date: Sun, 11 Mar 2018 00:10:43 +0300 Subject: Initial commit --- src/feature/localquery.nim | 81 +++++ src/feature/syncinfo.nim | 130 +++++++ src/feature/syncinstall.nim | 827 ++++++++++++++++++++++++++++++++++++++++++++ src/feature/syncsearch.nim | 53 +++ 4 files changed, 1091 insertions(+) create mode 100644 src/feature/localquery.nim create mode 100644 src/feature/syncinfo.nim create mode 100644 src/feature/syncinstall.nim create mode 100644 src/feature/syncsearch.nim (limited to 'src/feature') diff --git a/src/feature/localquery.nim b/src/feature/localquery.nim new file mode 100644 index 0000000..b07ec98 --- /dev/null +++ b/src/feature/localquery.nim @@ -0,0 +1,81 @@ +import + algorithm, future, options, sequtils, sets, strutils, tables, + "../args", "../common", "../config", "../format", "../package", "../pacman", "../utils", + "../wrapper/alpm" + +proc handleQueryOrphans*(args: seq[Argument], config: Config): int = + type Package = tuple[name: string, explicit: bool] + + let (installed, alternatives) = withAlpm(config.root, config.db, + newSeq[string](), config.arch, handle, dbs, errors): + for e in errors: printError(config.color, e) + + var installed = initTable[Package, HashSet[PackageReference]]() + var alternatives = initTable[string, HashSet[PackageReference]]() + + for pkg in handle.local.packages: + proc fixProvides(reference: PackageReference): PackageReference = + if reference.constraint.isNone: + (reference.name, reference.description, + some((ConstraintOperation.eq, $pkg.version))) + else: + reference + + let depends = toSeq(pkg.depends.items) + .map(toPackageReference).toSet + let optional = toSeq(pkg.optional.items) + .map(toPackageReference).toSet + let provides = toSeq(pkg.provides.items) + .map(toPackageReference).map(fixProvides).toSet + + installed.add(($pkg.name, pkg.reason == AlpmReason.explicit), + depends + optional) + if provides.len > 0: + alternatives.add($pkg.name, provides) + + (installed, alternatives) + + let providedBy = lc[(y, x.key) | (x <- alternatives.namedPairs, y <- x.value), + tuple[reference: PackageReference, name: string]] + + let installedSeq = lc[x | (x <- installed.pairs), + tuple[package: Package, dependencies: HashSet[PackageReference]]] + let explicit = installedSeq + .filter(t => t.package.explicit) + .map(t => t.package.name) + .toSet + + proc findRequired(results: HashSet[string], check: HashSet[string]): HashSet[string] = + let full = results + check + + let direct = lc[x | (y <- installedSeq, y.package.name in check, + x <- y.dependencies), PackageReference] + + let indirect = lc[x.name | (y <- direct, x <- providedBy, + y.isProvidedBy(x.reference)), string].toSet + + let checkNext = (direct.map(p => p.name).toSet + indirect) - full + if checkNext.len > 0: findRequired(full, checkNext) else: full + + let required = findRequired(initSet[string](), explicit) + let orphans = installedSeq.map(t => t.package.name).toSet - required + + let targets = args.targets.map(t => (if t[0 .. 5] == "local/": t[6 .. ^1] else: t)) + + # Provide similar output for not installed packages + let unknownTargets = targets.toSet - toSeq(installed.keys).map(p => p.name).toSet + let results = if targets.len > 0: + targets.filter(t => t in orphans or t in unknownTargets) + else: + toSeq(orphans.items).sorted(cmp) + + if results.len > 0: + let newArgs = args.filter(arg => not arg.isTarget and + not arg.matchOption((some("t"), "unrequired")) and + not arg.matchOption((some("d"), "deps"))) & + results.map(r => (r, none(string), ArgumentType.target)) + pacmanExec(false, config.color, newArgs) + elif targets.len == 0: + 0 + else: + 1 diff --git a/src/feature/syncinfo.nim b/src/feature/syncinfo.nim new file mode 100644 index 0000000..3c64282 --- /dev/null +++ b/src/feature/syncinfo.nim @@ -0,0 +1,130 @@ +import + future, options, posix, sequtils, strutils, times, + "../args", "../aur", "../common", "../config", "../format", "../package", + "../pacman", "../utils", + "../wrapper/alpm" + +const + pacmanInfoStrings = [ + "Architecture", + "Backup Files", + "Build Date", + "Compressed Size", + "Conflicts With", + "Depends On", + "Description", + "Download Size", + "Groups", + "Install Date", + "Install Reason", + "Install Script", + "Installed Size", + "Licenses", + "MD5 Sum", + "Name", + "Optional Deps", + "Optional For", + "Packager", + "Provides", + "Replaces", + "Repository", + "Required By", + "SHA-256 Sum", + "Signatures", + "URL", + "Validated By", + "Version" + ] + +proc formatDeps(title: string, config: Config, + refs: seq[ArchPackageReference]): PackageLineFormat = + proc formatDep(reference: ArchPackageReference): (string, bool) = + reference.reference.description + .map(d => ($reference.reference & ": " & d, true)) + .get(($reference.reference, false)) + + let values: seq[tuple[title: string, hasDesc: bool]] = refs + .filter(r => r.arch.isNone or r.arch == some(config.arch)) + .map(formatDep) + + if values.len > 0: + (title, values.map(v => v.title), values.map(v => v.hasDesc).foldl(a or b)) + else: + (title, @[], false) + +proc formatDate(date: Option[int64]): seq[string] = + if date.isSome: + var time = (posix.Time) date.unsafeGet + var ltime: Tm + discard localtime_r(time, ltime) + var buffer: array[100, char] + let res = strftime(addr(buffer), buffer.len, "%c", ltime) + if res > 0: @[buffer.toString(none(int))] else: @[] + else: + @[] + +proc handleTarget(config: Config, padding: int, args: seq[Argument], + target: FullPackageTarget[PackageInfo]): int = + if target.foundInfo.isSome: + if isAurTargetFull[PackageInfo](target): + let pkgInfo = target.pkgInfo.unsafeGet + + printPackageInfo(padding, config.color, + (trp"Repository", @["aur"], false), + (trp"Name", @[pkgInfo.name], false), + (trp"Version", @[pkgInfo.version], false), + (trp"Description", toSeq(pkgInfo.description.items), false), + (trp"Architecture", pkgInfo.archs, false), + (trp"URL", toSeq(pkgInfo.url.items), false), + (trp"Licenses", pkgInfo.licenses, false), + (trp"Groups", pkgInfo.groups, false), + formatDeps(trp"Provides", config, pkgInfo.provides), + formatDeps(trp"Depends On", config, pkgInfo.depends), + formatDeps(trp"Optional Deps", config, pkgInfo.optional), + formatDeps(trp"Conflicts With", config, pkgInfo.conflicts), + formatDeps(trp"Replaces", config, pkgInfo.replaces), + (tr"Maintainer", toSeq(pkgInfo.maintainer.items()), false), + (tr"First Submitted", pkgInfo.firstSubmitted.formatDate, false), + (tr"Last Modified", pkgInfo.lastModified.formatDate, false), + (tr"Rating", @[formatPkgRating(pkgInfo.votes, pkgInfo.popularity)], false)) + + 0 + else: + pacmanRun(false, config.color, args & + (target.formatArgument, none(string), ArgumentType.target)) + else: + if target.repo == some("aur"): + printError(config.color, trp("package '%s' was not found\n") % [target.formatArgument]) + 1 + else: + pacmanRun(false, config.color, args & + (target.formatArgument, none(string), ArgumentType.target)) + +proc handleSyncInfo*(args: seq[Argument], config: Config): int = + let (_, callArgs) = checkAndRefresh(config.color, args) + let targets = args.packageTargets + + let (syncTargets, checkAur) = withAlpm(config.root, config.db, + config.dbs, config.arch, handle, dbs, errors): + for e in errors: printError(config.color, e) + findSyncTargets(handle, dbs, targets, false, false) + + let (pkgInfos, aerrors) = getAurPackageInfo(checkAur, none(seq[RpcPackageInfo]), + proc (a: int, b: int) = discard) + for e in aerrors: printError(config.color, e) + + let fullTargets = mapAurTargets[PackageInfo](syncTargets, pkgInfos) + + let code = min(aerrors.len, 1) + if fullTargets.filter(isAurTargetFull[PackageInfo]).len == 0: + if code == 0: + pacmanExec(false, config.color, callArgs) + else: + discard pacmanRun(false, config.color, callArgs) + code + else: + let finalArgs = callArgs.filter(arg => not arg.isTarget) + let padding = pacmanInfoStrings.map(s => s.trp).computeMaxLength + + let codes = code & lc[handleTarget(config, padding, finalArgs, x) | (x <- fullTargets), int] + codes.filter(c => c != 0).optFirst.get(0) diff --git a/src/feature/syncinstall.nim b/src/feature/syncinstall.nim new file mode 100644 index 0000000..5f6e082 --- /dev/null +++ b/src/feature/syncinstall.nim @@ -0,0 +1,827 @@ +import + algorithm, future, options, os, posix, sequtils, sets, strutils, tables, + "../args", "../aur", "../config", "../common", "../format", "../package", + "../pacman", "../utils", + "../wrapper/alpm" + +type + Installed = tuple[ + name: string, + version: string, + groups: seq[string], + foreign: bool + ] + + SatisfyResult = tuple[ + installed: bool, + name: string, + buildPkgInfo: Option[PackageInfo] + ] + + BuildResult = tuple[ + version: string, + arch: string, + ext: string, + names: seq[string] + ] + +proc groupsSeq(pkg: ptr AlpmPackage): seq[string] = + toSeq(pkg.groups.items).map(s => $s) + +proc orderInstallation(ordered: seq[seq[seq[PackageInfo]]], grouped: seq[seq[PackageInfo]], + dependencies: Table[PackageReference, SatisfyResult]): seq[seq[seq[PackageInfo]]] = + let orderedNamesSet = lc[c.name | (a <- ordered, b <- a, c <- b), string].toSet + + proc hasBuildDependency(pkgInfos: seq[PackageInfo]): bool = + for pkgInfo in pkgInfos: + for reference in pkgInfo.allDepends: + let satres = dependencies[reference.reference] + if satres.buildPkgInfo.isSome and + not (satres.buildPkgInfo.unsafeGet in pkgInfos) and + not (satres.buildPkgInfo.unsafeGet.name in orderedNamesSet): + return true + return false + + let split: seq[tuple[pkgInfos: seq[PackageInfo], dependent: bool]] = + grouped.map(i => (i, i.hasBuildDependency)) + + let newOrdered = ordered & split.filter(s => not s.dependent).map(s => s.pkgInfos) + let unordered = split.filter(s => s.dependent).map(s => s.pkgInfos) + + if unordered.len > 0: + if unordered.len == grouped.len: + newOrdered & unordered + else: + orderInstallation(newOrdered, unordered, dependencies) + else: + newOrdered + +proc orderInstallation(pkgInfos: seq[PackageInfo], + dependencies: Table[PackageReference, SatisfyResult]): seq[seq[seq[PackageInfo]]] = + let grouped = pkgInfos.groupBy(i => i.base).map(p => p.values) + + orderInstallation(@[], grouped, dependencies) + .map(x => x.filter(s => s.len > 0)) + .filter(x => x.len > 0) + +proc findDependencies(config: Config, handle: ptr AlpmHandle, dbs: seq[ptr AlpmDatabase], + satisfied: Table[PackageReference, SatisfyResult], unsatisfied: seq[PackageReference], + printMode: bool, noaur: bool): (Table[PackageReference, SatisfyResult], seq[PackageReference]) = + proc findInSatisfied(reference: PackageReference): Option[PackageInfo] = + for satref, res in satisfied.pairs: + if res.buildPkgInfo.isSome: + let pkgInfo = res.buildPkgInfo.unsafeGet + if satref == reference or reference.isProvidedBy((pkgInfo.name, none(string), + some((ConstraintOperation.eq, pkgInfo.version)))): + return some(pkgInfo) + for provides in pkgInfo.provides: + if provides.arch.isNone or provides.arch == some(config.arch): + if reference.isProvidedBy(provides.reference): + return some(pkgInfo) + return none(PackageInfo) + + proc findInDatabaseWithGroups(db: ptr AlpmDatabase, reference: PackageReference, + directName: bool): Option[tuple[name: string, groups: seq[string]]] = + for pkg in db.packages: + if reference.isProvidedBy(($pkg.name, none(string), + some((ConstraintOperation.eq, $pkg.version)))): + return some(($pkg.name, pkg.groupsSeq)) + for provides in pkg.provides: + if reference.isProvidedBy(provides.toPackageReference): + if directName: + return some(($pkg.name, pkg.groupsSeq)) + else: + return some(($provides.name, pkg.groupsSeq)) + return none((string, seq[string])) + + proc findInDatabase(db: ptr AlpmDatabase, reference: PackageReference, + directName: bool, checkIgnored: bool): Option[string] = + let res = findInDatabaseWithGroups(db, reference, directName) + if res.isSome: + let r = res.unsafeGet + if checkIgnored and config.ignored(r.name, r.groups): + none(string) + else: + some(r.name) + else: + none(string) + + proc findInDatabases(reference: PackageReference, + directName: bool, checkIgnored: bool): Option[string] = + for db in dbs: + let name = findInDatabase(db, reference, directName, checkIgnored) + if name.isSome: + return name + return none(string) + + proc find(reference: PackageReference): Option[SatisfyResult] = + let localName = findInDatabase(handle.local, reference, true, false) + if localName.isSome: + some((true, localName.unsafeGet, none(PackageInfo))) + else: + let pkgInfo = findInSatisfied(reference) + if pkgInfo.isSome: + some((false, pkgInfo.unsafeGet.name, pkgInfo)) + else: + let syncName = findInDatabases(reference, false, true) + if syncName.isSome: + some((false, syncName.unsafeGet, none(PackageInfo))) + else: + none(SatisfyResult) + + type ReferenceResult = tuple[reference: PackageReference, result: Option[SatisfyResult]] + + let findResult: seq[ReferenceResult] = unsatisfied.map(r => (r, r.find)) + let success = findResult.filter(r => r.result.isSome) + let aurCheck = findResult.filter(r => r.result.isNone).map(r => r.reference) + + let (aurSuccess, aurFail) = if not noaur and aurCheck.len > 0: (block: + let (update, terminate) = if aurCheck.len >= 4: + printProgressShare(config.progressBar, tr"checking build dependencies") + else: + (proc (a: int, b: int) {.closure.} = discard, proc {.closure.} = discard) + try: + withAur(): + let (pkgInfos, aerrors) = getAurPackageInfo(aurCheck.map(r => r.name), + none(seq[RpcPackageInfo]), update) + for e in aerrors: printError(config.color, e) + + let acceptedPkgInfos = pkgInfos.filter(i => not config.ignored(i.name, i.groups)) + let aurTable = acceptedPkgInfos.map(i => (i.name, i)).toTable + let aurResult = aurCheck.map(proc (reference: PackageReference): ReferenceResult = + if aurTable.hasKey(reference.name): + (reference, some((false, reference.name, some(aurTable[reference.name])))) + else: + (reference, none(SatisfyResult))) + + let aurSuccess = aurResult.filter(r => r.result.isSome) + let aurFail = aurResult.filter(r => r.result.isNone).map(r => r.reference) + (aurSuccess, aurFail) + finally: + terminate()) + else: + (@[], aurCheck) + + let newSatisfied = (toSeq(satisfied.pairs) & + success.map(r => (r.reference, r.result.unsafeGet)) & + aurSuccess.map(r => (r.reference, r.result.unsafeGet))).toTable + + let newUnsatisfied = lc[x.reference | (y <- aurSuccess, + r <- y.result, i <- r.buildPkgInfo, x <- i.allDepends, + x.arch.isNone or x.arch == some(config.arch)), PackageReference].deduplicate + + if aurFail.len > 0: + (newSatisfied, aurFail) + elif newUnsatisfied.len > 0: + findDependencies(config, handle, dbs, newSatisfied, newUnsatisfied, printMode, noaur) + else: + (newSatisfied, @[]) + +proc findDependencies(config: Config, handle: ptr AlpmHandle, + dbs: seq[ptr AlpmDatabase], pkgInfos: seq[PackageInfo], printMode: bool, noaur: bool): + (Table[PackageReference, SatisfyResult], seq[PackageReference]) = + let satisfied = pkgInfos.map(p => ((p.name, none(string), none(VersionConstraint)), + (false, p.name, some(p)))).toTable + let unsatisfied = lc[x.reference | (i <- pkgInfos, x <- i.allDepends, + x.arch.isNone or x.arch == some(config.arch)), PackageReference].deduplicate + findDependencies(config, handle, dbs, satisfied, unsatisfied, printMode, noaur) + +proc filterNotFoundSyncTargets[T: RpcPackageInfo](syncTargets: seq[SyncPackageTarget], + pkgInfos: seq[T]): (Table[string, T], seq[SyncPackageTarget]) = + let rpcInfoTable = pkgInfos.map(d => (d.name, d)).toTable + + proc notFoundOrFoundInAur(target: SyncPackageTarget): bool = + target.foundInfo.isNone and + not (target.isAurTargetSync and rpcInfoTable.hasKey(target.name)) + + # collect packages which were found neither in sync DB nor in AUR + let notFoundTargets = syncTargets.filter(notFoundOrFoundInAur) + + (rpcInfoTable, notFoundTargets) + +proc printSyncNotFound(config: Config, notFoundTargets: seq[SyncPackageTarget]) = + let dbs = config.dbs.toSet + + for target in notFoundTargets: + if target.repo.isNone or target.repo == some("aur") or target.repo.unsafeGet in dbs: + printError(config.color, trp("target not found: %s\n") % [target.name]) + else: + printError(config.color, trp("database not found: %s\n") % [target.repo.unsafeGet]) + +proc printUnsatisfied(config: Config, + satisfied: Table[PackageReference, SatisfyResult], unsatisfied: seq[PackageReference]) = + if unsatisfied.len > 0: + for _, satres in satisfied.pairs: + for pkgInfo in satres.buildPkgInfo: + for reference in pkgInfo.allDepends: + let pref = reference.reference + if pref in unsatisfied: + printError(config.color, + trp("unable to satisfy dependency '%s' required by %s\n") % + [$pref, pkgInfo.name]) + +proc editLoop(config: Config, base: string, repoPath: string, gitPath: Option[string], + defaultYes: bool, noconfirm: bool): char = + proc editFileLoop(file: string): char = + let default = if defaultYes: 'y' else: 'n' + let res = printColonUserChoice(config.color, + tr"View and edit $#?" % [base & "/" & file], ['y', 'n', 's', 'a', '?'], + default, '?', noconfirm, 'n') + + if res == '?': + printUserInputHelp(('s', tr"skip all files"), + ('a', tr"abort operation")) + editFileLoop(file) + elif res == 'y': + let visualEnv = getenv("VISUAL") + let editorEnv = getenv("EDITOR") + let editor = if visualEnv != nil and visualEnv.len > 0: + $visualEnv + elif editorEnv != nil and editorEnv.len > 0: + $editorEnv + else: + printColonUserInput(config.color, tr"Enter editor executable name" & ":", + noconfirm, "", "") + + if editor.strip.len == 0: + 'n' + else: + discard forkWait(proc: int = + discard chdir(buildPath(repoPath, gitPath)) + execResult(bashCmd, "-c", """$1 "$2"""", "bash", editor, file)) + editFileLoop(file) + else: + res + + let rawFiles = if gitPath.isSome: + runProgram(gitCmd, "-C", repoPath, "ls-tree", "-r", "--name-only", "@", + gitPath.unsafeGet & "/").map(s => s[gitPath.unsafeGet.len + 1 .. ^1]) + else: + runProgram(gitCmd, "-C", repoPath, "ls-tree", "-r", "--name-only", "@") + + let files = ("PKGBUILD" & rawFiles.filter(x => x != ".SRCINFO")).deduplicate + + proc editFileLoopAll(index: int): char = + if index < files.len: + let res = editFileLoop(files[index]) + if res == 'n': editFileLoopAll(index + 1) else: res + else: + 'n' + + editFileLoopAll(0) + +proc buildLoop(config: Config, pkgInfos: seq[PackageInfo], noconfirm: bool, + noextract: bool): (Option[BuildResult], int) = + let base = pkgInfos[0].base + let repoPath = repoPath(config.tmpRoot, base) + let gitPath = pkgInfos[0].gitPath + let buildPath = buildPath(repoPath, gitPath) + + let buildCode = forkWait(proc: int = + if chdir(buildPath) == 0: + discard setenv("PKGDEST", config.tmpRoot, 1) + discard setenv("CARCH", config.arch, 1) + + if not noextract: + removeDirQuiet(buildPath & "src") + + let optional: seq[tuple[arg: string, cond: bool]] = @[ + ("-e", noextract), + ("-m", not config.color) + ] + + execResult(@[makepkgCmd, "-f"] & + optional.filter(o => o.cond).map(o => o.arg)) + else: + quit(1)) + + if buildCode != 0: + printError(config.color, tr"failed to build '$#'" % [base]) + (none(BuildResult), buildCode) + else: + let confFileEnv = getenv("MAKEPKG_CONF") + let confFile = if confFileEnv == nil or confFileEnv.len == 0: + sysConfDir & "/makepkg.conf" + else: + $confFileEnv + + let envExt = getenv("PKGEXT") + let confExt = if envExt == nil or envExt.len == 0: + runProgram(bashCmd, "-c", + "source \"$@\" && echo \"$PKGEXT\"", + "bash", confFile).optFirst.get("") + else: + $envExt + + let extracted = runProgram(bashCmd, "-c", + """source "$@" && echo "$epoch" && echo "$pkgver" && """ & + """echo "$pkgrel" && echo "${arch[@]}" && echo "${pkgname[@]}"""", + "bash", buildPath & "/PKGBUILD") + if extracted.len != 5: + printError(config.color, tr"failed to extract package info '$#'" % [base]) + (none(BuildResult), 1) + else: + let epoch = extracted[0] + let versionShort = extracted[1] & "-" & extracted[2] + let version = if epoch.len > 0: epoch & ":" & versionShort else: versionShort + let archs = extracted[3].split(" ").toSet + let arch = if config.arch in archs: config.arch else: "any" + let names = extracted[4].split(" ") + + (some((version, arch, $confExt, names)), 0) + +proc buildFromSources(config: Config, commonArgs: seq[Argument], + pkgInfos: seq[PackageInfo], noconfirm: bool): (Option[BuildResult], int) = + let base = pkgInfos[0].base + let (cloneCode, cloneErrorMessage) = cloneRepo(config, pkgInfos) + + if cloneCode != 0: + for e in cloneErrorMessage: printError(config.color, e) + printError(config.color, tr"$#: failed to clone git repository" % [base]) + (none(BuildResult), cloneCode) + else: + proc loop(noextract: bool, showEditLoop: bool): (Option[BuildResult], int) = + let res = if showEditLoop: + editLoop(config, base, repoPath(config.tmpRoot, base), pkgInfos[0].gitPath, + false, noconfirm) + else: + 'n' + + if res == 'a': + (none(BuildResult), 1) + else: + let (buildResult, code) = buildLoop(config, pkgInfos, + noconfirm, noextract) + + if code != 0: + proc ask(): char = + let res = printColonUserChoice(config.color, + tr"Build failed, retry?", ['y', 'e', 'n', '?'], 'n', '?', + noconfirm, 'n') + if res == '?': + printUserInputHelp(('e', tr"retry with --noextract option")) + ask() + else: + res + + let res = ask() + if res == 'e': + loop(true, true) + elif res == 'y': + loop(false, true) + else: + (buildResult, code) + else: + (buildResult, code) + + loop(false, false) + +proc installGroupFromSources(config: Config, commonArgs: seq[Argument], + basePackages: seq[seq[PackageInfo]], explicits: HashSet[string], noconfirm: bool): int = + proc buildNext(index: int, buildResults: seq[BuildResult]): (seq[BuildResult], int) = + if index < basePackages.len: + let (buildResult, code) = buildFromSources(config, commonArgs, + basePackages[index], noconfirm) + + if code != 0: + (buildResults, code) + else: + buildNext(index + 1, buildResults & buildResult.unsafeGet) + else: + (buildResults, 0) + + let (buildResults, buildCode) = buildNext(0, @[]) + + proc formatArchiveFile(br: BuildResult, name: string): string = + config.tmpRoot & "/" & name & "-" & br.version & "-" & br.arch & br.ext + + let files = lc[(name, formatArchiveFile(br, name)) | + (br <- buildResults, name <- br.names), (string, string)].toTable + let install = lc[x | (g <- basePackages, i <- g, x <- files.opt(i.name)), string] + + proc handleTmpRoot(clear: bool) = + for _, file in files: + if clear or not (file in install): + try: + removeFile(file) + except: + discard + + if not clear: + printWarning(config.color, tr"packages are saved to '$#'" % [config.tmpRoot]) + + if buildCode != 0: + handleTmpRoot(true) + buildCode + else: + let res = printColonUserChoice(config.color, + tr"Continue installing?", ['y', 'n'], 'y', 'n', + noconfirm, 'y') + + if res != 'y': + handleTmpRoot(false) + 1 + else: + let explicit = basePackages.filter(p => p.filter(i => i.name in explicits).len > 0).len > 0 + let asdepsSeq = if not explicit: @[("asdeps", none(string), ArgumentType.long)] else: @[] + + let installCode = pacmanRun(true, config.color, commonArgs & + ("U", none(string), ArgumentType.short) & asdepsSeq & + install.map(i => (i, none(string), ArgumentType.target))) + + if installCode != 0: + handleTmpRoot(false) + installCode + else: + handleTmpRoot(true) + 0 + +proc handleInstall(args: seq[Argument], config: Config, upgradeCount: int, + noconfirm: bool, explicits: HashSet[string], installed: seq[Installed], + dependencies: Table[PackageReference, SatisfyResult], + directPacmanTargets: seq[string], additionalPacmanTargets: seq[string], + basePackages: seq[seq[seq[PackageInfo]]]): int = + let (directCode, directSome) = if directPacmanTargets.len > 0 or upgradeCount > 0: + (pacmanRun(true, config.color, args.filter(arg => not arg.isTarget) & + directPacmanTargets.map(t => (t, none(string), ArgumentType.target))), true) + else: + (0, false) + + if directCode != 0: + directCode + else: + let commonArgs = args.keepOnlyOptions(commonOptions, upgradeCommonOptions) + + let (paths, confirmAndCloneCode) = if basePackages.len > 0: (block: + let installedVersions = installed.map(i => (i.name, i.version)).toTable + + printPackages(config.color, config.verbosePkgList, + lc[(i.name, i.repo, installedVersions.opt(i.name), i.version) | + (g <- basePackages, b <- g, i <- b), PackageInstallFormat] + .sorted((a, b) => cmp(a.name, b.name))) + let input = printColonUserChoice(config.color, + tr"Proceed with building?", ['y', 'n'], 'y', 'n', noconfirm, 'y') + + if input == 'y': + let (update, terminate) = if config.debug: + (proc (a: int, b: int) {.closure.} = discard, proc {.closure.} = discard) + else: + printProgressShare(config.progressBar, tr"cloning repositories") + + let flatBasePackages = lc[x | (a <- basePackages, x <- a), seq[PackageInfo]] + update(0, flatBasePackages.len) + + proc cloneNext(index: int, paths: seq[string]): (seq[string], int) = + if index < flatBasePackages.len: + let pkgInfos = flatBasePackages[index] + let base = pkgInfos[0].base + let repoPath = repoPath(config.tmpRoot, base) + let (cloneCode, cloneErrorMessage) = cloneRepo(config, flatBasePackages[index]) + + if cloneCode == 0: + update(index + 1, flatBasePackages.len) + cloneNext(index + 1, paths & repoPath) + else: + terminate() + for e in cloneErrorMessage: printError(config.color, e) + printError(config.color, tr"$#: failed to clone git repository" % + [pkgInfos[0].base]) + (paths & repoPath, cloneCode) + else: + terminate() + (paths, 0) + + let (paths, cloneCode) = cloneNext(0, @[]) + if cloneCode != 0: + (paths, cloneCode) + else: + proc checkNext(index: int, skipEdit: bool): int = + if index < flatBasePackages.len: + let pkgInfos = flatBasePackages[index] + let base = pkgInfos[0].base + let repoPath = repoPath(config.tmpRoot, base) + + let aur = pkgInfos[0].repo == "aur" + + if not skipEdit and aur and config.aurComments: + echo(tr"downloading comments from AUR...") + let (comments, error) = downloadAurComments(base) + for e in error: printError(config.color, e) + if comments.len > 0: + let commentsReversed = toSeq(comments.reversed) + printComments(config.color, pkgInfos[0].maintainer, commentsReversed) + + let res = if skipEdit: + 'n' + else: (block: + let defaultYes = aur and not config.viewNoDefault + editLoop(config, base, repoPath, pkgInfos[0].gitPath, defaultYes, noconfirm)) + + if res == 'a': + 1 + else: + checkNext(index + 1, skipEdit or res == 's') + else: + 0 + + (paths, checkNext(0, false)) + else: + (@[], 1)) + else: + (@[], 0) + + proc removeTmp() = + for path in paths: + removeDirQuiet(path) + discard rmdir(config.tmpRoot) + + if confirmAndCloneCode != 0: + removeTmp() + confirmAndCloneCode + else: + let (additionalCode, additionalSome) = if additionalPacmanTargets.len > 0: (block: + printColon(config.color, tr"Installing build dependencies...") + + (pacmanRun(true, config.color, commonArgs & + ("S", none(string), ArgumentType.short) & + ("needed", none(string), ArgumentType.long) & + ("asdeps", none(string), ArgumentType.long) & + additionalPacmanTargets.map(t => (t, none(string), ArgumentType.target))), true)) + else: + (0, false) + + if additionalCode != 0: + removeTmp() + additionalCode + else: + if basePackages.len > 0: + # check all pacman dependencies were installed + let unsatisfied = withAlpm(config.root, config.db, + config.dbs, config.arch, handle, dbs, errors): + for e in errors: printError(config.color, e) + + proc checkSatisfied(reference: PackageReference): bool = + for pkg in handle.local.packages: + if reference.isProvidedBy(($pkg.name, none(string), + some((ConstraintOperation.eq, $pkg.version)))): + return true + for provides in pkg.provides: + if reference.isProvidedBy(provides.toPackageReference): + return true + return false + + lc[x.key | (x <- dependencies.namedPairs, not x.value.installed and + x.value.buildPkgInfo.isNone and not x.key.checkSatisfied), PackageReference] + + if unsatisfied.len > 0: + removeTmp() + printUnsatisfied(config, dependencies, unsatisfied) + 1 + else: + proc installNext(index: int, lastCode: int): (int, int) = + if index < basePackages.len and lastCode == 0: + let code = installGroupFromSources(config, commonArgs, + basePackages[index], explicits, noconfirm) + installNext(index + 1, code) + else: + (lastCode, index - 1) + + let (code, index) = installNext(0, 0) + if code != 0 and index < basePackages.len - 1: + printWarning(config.color, tr"installation aborted") + removeTmp() + code + elif not directSome and not additionalSome: + echo(trp(" there is nothing to do\n")) + 0 + else: + 0 + +proc handlePrint(args: seq[Argument], config: Config, printFormat: string, + upgradeCount: int, directPacmanTargets: seq[string], additionalPacmanTargets: seq[string], + basePackages: seq[seq[seq[PackageInfo]]]): int = + + let code = if directPacmanTargets.len > 0 or + additionalPacmanTargets.len > 0 or upgradeCount > 0: + pacmanRun(false, config.color, args.filter(arg => not arg.isTarget) & + (directPacmanTargets & additionalPacmanTargets) + .map(t => (t, none(string), ArgumentType.target))) + else: + 0 + + if code == 0: + proc printWithFormat(pkgInfo: PackageInfo) = + echo(printFormat + .replace("%n", pkgInfo.name) + .replace("%v", pkgInfo.version) + .replace("%r", "aur") + .replace("%s", "0") + .replace("%l", pkgInfo.gitUrl)) + + for installGroup in basePackages: + for pkgInfos in installGroup: + for pkgInfo in pkgInfos: + printWithFormat(pkgInfo) + 0 + else: + code + +proc handleSyncInstall*(args: seq[Argument], config: Config): int = + let (_, callArgs) = checkAndRefresh(config.color, args) + + let upgradeCount = args.count((some("u"), "sysupgrade")) + let needed = args.check((none(string), "needed")) + let noaur = args.check((none(string), "noaur")) + let build = args.check((none(string), "build")) + + let printModeArg = args.check((some("p"), "print")) + let printModeFormat = args.filter(arg => arg + .matchOption((none(string), "print-format"))).optLast + let printFormat = if printModeArg or printModeFormat.isSome: + some(printModeFormat.map(arg => arg.value.get).get("%l")) + else: + none(string) + + let noconfirm = args + .filter(arg => arg.matchOption((none(string), "confirm")) or + arg.matchOption((none(string), "noconfirm"))).optLast + .map(arg => arg.key == "noconfirm").get(false) + + let targets = args.packageTargets + + let (syncTargets, checkAur, installed) = withAlpm(config.root, config.db, + config.dbs, config.arch, handle, dbs, errors): + for e in errors: printError(config.color, e) + + let (syncTargets, checkAur) = findSyncTargets(handle, dbs, targets, + not build, not build) + + let installed = lc[($p.name, $p.version, p.groupsSeq, + dbs.filter(d => d[p.name] != nil).len == 0) | + (p <- handle.local.packages), Installed] + + (syncTargets, checkAur, installed) + + let realCheckAur = if noaur: + @[] + elif upgradeCount > 0: + installed + .filter(i => i.foreign and + (config.checkIgnored or not config.ignored(i.name, i.groups))) + .map(i => i.name) & checkAur + else: + checkAur + + withAur(): + if realCheckAur.len > 0 and printFormat.isNone: + printColon(config.color, tr"Checking AUR database...") + let (rpcInfos, aerrors) = getRpcPackageInfo(realCheckAur) + for e in aerrors: printError(config.color, e) + + let (rpcInfoTable, notFoundTargets) = filterNotFoundSyncTargets(syncTargets, rpcInfos) + + if notFoundTargets.len > 0: + printSyncNotFound(config, notFoundTargets) + 1 + else: + let fullTargets = mapAurTargets(syncTargets, rpcInfos) + let pacmanTargets = fullTargets.filter(t => not isAurTargetFull(t)) + let aurTargets = fullTargets.filter(isAurTargetFull) + + if upgradeCount > 0 and not noaur and printFormat.isNone and config.printAurNotFound: + for inst in installed: + if inst.foreign and not config.ignored(inst.name, inst.groups) and + not rpcInfoTable.hasKey(inst.name): + printWarning(config.color, tr"$# was not found in AUR" % [inst.name]) + + let installedTable = installed.map(i => (i.name, i)).toTable + + proc checkNeeded(name: string, version: string): bool = + if installedTable.hasKey(name): + let i = installedTable[name] + vercmp(version, i.version) > 0 + else: + true + + let targetRpcInfos: seq[tuple[rpcInfo: RpcPackageInfo, upgradeable: bool]] = + aurTargets.map(t => t.pkgInfo.get).map(i => (i, checkNeeded(i.name, i.version))) + + if printFormat.isNone and needed: + for rpcInfo in targetRpcInfos: + if not rpcInfo.upgradeable: + # not upgradeable assumes that package is installed + let inst = installedTable[rpcInfo.rpcInfo.name] + printWarning(config.color, tra("%s-%s is up to date -- skipping\n") % + [rpcInfo.rpcInfo.name, inst.version]) + + let aurTargetsSet = aurTargets.map(t => t.name).toSet + let fullRpcInfos = (targetRpcInfos + .filter(i => not needed or i.upgradeable).map(i => i.rpcInfo) & + rpcInfos.filter(i => upgradeCount > 0 and not (i.name in aurTargetsSet) and + checkNeeded(i.name, i.version))).deduplicate + + if fullRpcInfos.len > 0 and printFormat.isNone: + echo(tr"downloading full package descriptions...") + let (aurPkgInfos, faerrors) = getAurPackageInfo(fullRpcInfos + .map(i => i.name), some(fullRpcInfos), proc (a: int, b: int) = discard) + + if faerrors.len > 0: + for e in faerrors: printError(config.color, e) + 1 + else: + let neededPacmanTargets = if printFormat.isNone and build and needed: + pacmanTargets.filter(target => (block: + let version = target.foundInfo.get.pkg.get.version + if checkNeeded(target.name, version): + true + else: + printWarning(config.color, tra("%s-%s is up to date -- skipping\n") % + [target.name, version]) + false)) + else: + pacmanTargets + + let checkPacmanPkgInfos = printFormat.isNone and build and + neededPacmanTargets.len > 0 + + let (buildPkgInfos, obtainErrorMessages) = if checkPacmanPkgInfos: (block: + printColon(config.color, tr"Checking repositories...") + obtainBuildPkgInfos(config, pacmanTargets)) + else: + (@[], @[]) + + if checkPacmanPkgInfos and buildPkgInfos.len < pacmanTargets.len: + for e in obtainErrorMessages: printError(config.color, e) + 1 + else: + let pkgInfos = buildPkgInfos & aurPkgInfos + let targetsSet = fullTargets.map(t => t.name).toSet + + let acceptedPkgInfos = pkgInfos.filter(pkgInfo => (block: + let instGroups = lc[x | (i <- installedTable.opt(pkgInfo.name), + x <- i.groups), string] + + if config.ignored(pkgInfo.name, (instGroups & pkgInfo.groups).deduplicate): + if pkgInfo.name in targetsSet: + if printFormat.isNone: + let input = printColonUserChoice(config.color, + trp"%s is in IgnorePkg/IgnoreGroup. Install anyway?" % [pkgInfo.name], + ['y', 'n'], 'y', 'n', noconfirm, 'y') + input != 'n' + else: + true + else: + false + else: + true)) + + if acceptedPkgInfos.len > 0 and printFormat.isNone: + echo(trp("resolving dependencies...\n")) + let (satisfied, unsatisfied) = withAlpm(config.root, config.db, + config.dbs, config.arch, handle, dbs, errors): + findDependencies(config, handle, dbs, acceptedPkgInfos, + printFormat.isSome, noaur) + + if unsatisfied.len > 0: + printUnsatisfied(config, satisfied, unsatisfied) + 1 + else: + if printFormat.isNone: + let acceptedSet = acceptedPkgInfos.map(i => i.name).toSet + + for pkgInfo in pkgInfos: + if not (pkgInfo.name in acceptedSet): + if not (pkgInfo.name in targetsSet) and upgradeCount > 0 and + installedTable.hasKey(pkgInfo.name): + printWarning(config.color, tra("%s: ignoring package upgrade (%s => %s)\n") % + [pkgInfo.name, installedTable[pkgInfo.name].version, pkgInfo.version]) + else: + printWarning(config.color, trp("skipping target: %s\n") % [pkgInfo.name]) + elif pkgInfo.repo == "aur" and pkgInfo.maintainer.isNone: + printWarning(config.color, tr"$# is orphaned" % [pkgInfo.name]) + + let aurPrintSet = acceptedPkgInfos.map(i => i.name).toSet + let fullPkgInfos = acceptedPkgInfos & lc[i | (s <- satisfied.values, + i <- s.buildPkgInfo, not (i.name in aurPrintSet)), PackageInfo].deduplicate + + let directPacmanTargets = pacmanTargets.map(t => t.formatArgument) + let additionalPacmanTargets = lc[x.name | (x <- satisfied.values, + not x.installed and x.buildPkgInfo.isNone), string] + let orderedPkgInfos = orderInstallation(fullPkgInfos, satisfied) + + let pacmanArgs = callArgs.filterExtensions(true, true) + + if printFormat.isSome: + handlePrint(pacmanArgs, config, printFormat.unsafeGet, upgradeCount, + directPacmanTargets, additionalPacmanTargets, orderedPkgInfos) + else: + let explicits = if not args.check((none(string), "asdeps")): + targets.map(t => t.name) + else: + @[] + + let passDirectPacmanTargets = if build: @[] else: directPacmanTargets + + handleInstall(pacmanArgs, config, upgradeCount, noconfirm, + explicits.toSet, installed, satisfied, passDirectPacmanTargets, + additionalPacmanTargets, orderedPkgInfos) diff --git a/src/feature/syncsearch.nim b/src/feature/syncsearch.nim new file mode 100644 index 0000000..7b2e77c --- /dev/null +++ b/src/feature/syncsearch.nim @@ -0,0 +1,53 @@ +import + algorithm, future, options, sequtils, strutils, + "../args", "../aur", "../config", "../common", "../format", "../package", + "../pacman", "../utils", + "../wrapper/alpm" + +proc handleSyncSearch*(args: seq[Argument], config: Config): int = + let (_, callArgs) = checkAndRefresh(config.color, args) + + let quiet = args.check((some("q"), "quiet")) + + let (aurPackages, aerrors) = findAurPackages(args.targets) + for e in aerrors: printError(config.color, e) + + type Package = tuple[rpcInfo: RpcPackageInfo, installedVersion: Option[string]] + + proc checkLocalPackages: seq[Package] = + if quiet: + aurPackages.map(pkg => (pkg, none(string))) + elif aurPackages.len > 0: + withAlpm(config.root, config.db, newSeq[string](), config.arch, handle, dbs, errors): + for e in errors: printError(config.color, e) + + aurPackages.map(proc (rpcInfo: RpcPackageInfo): Package = + let pkg = handle.local[rpcInfo.name] + if pkg != nil: + (rpcInfo, some($pkg.version)) + else: + (rpcInfo, none(string))) + else: + @[] + + let pkgs = checkLocalPackages() + .sorted((a, b) => cmp(a.rpcInfo.name, b.rpcInfo.name)) + + var code = min(aerrors.len, 1) + if pkgs.len == 0: + if code == 0: + pacmanExec(false, config.color, callArgs) + else: + discard pacmanRun(false, config.color, callArgs) + code + else: + discard pacmanRun(false, config.color, callArgs) + + for pkg in pkgs: + if quiet: + echo(pkg.rpcInfo.name) + else: + printPackageSearch(config.color, "aur", pkg.rpcInfo.name, + pkg.rpcInfo.version, pkg.installedVersion, pkg.rpcInfo.description, + some(formatPkgRating(pkg.rpcInfo.votes, pkg.rpcInfo.popularity))) + 0 -- cgit v1.2.3-70-g09d2