From fd397b356e9be1d3572ace965ead57120803a0ac Mon Sep 17 00:00:00 2001 From: kitsunyan Date: Sun, 11 Mar 2018 00:10:43 +0300 Subject: Initial commit --- src/common.nim | 392 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 392 insertions(+) create mode 100644 src/common.nim (limited to 'src/common.nim') diff --git a/src/common.nim b/src/common.nim new file mode 100644 index 0000000..e17c802 --- /dev/null +++ b/src/common.nim @@ -0,0 +1,392 @@ +import + future, options, os, osproc, posix, sequtils, sets, strutils, tables, + args, config, package, pacman, utils, + "wrapper/alpm" + +type + SyncFoundPackageInfo* = tuple[ + base: string, + version: string, + arch: Option[string] + ] + + SyncFoundInfo* = tuple[ + repo: string, + pkg: Option[SyncFoundPackageInfo] + ] + + PackageTarget* = object of RootObj + name*: string + repo*: Option[string] + + SyncPackageTarget* = object of PackageTarget + foundInfo*: Option[SyncFoundInfo] + + FullPackageTarget*[T] = object of SyncPackageTarget + pkgInfo*: Option[T] + +proc toPackageReference*(dependency: ptr AlpmDependency): PackageReference = + let op = case dependency.depmod: + of AlpmDepMod.eq: some(ConstraintOperation.eq) + of AlpmDepMod.ge: some(ConstraintOperation.ge) + of AlpmDepMod.le: some(ConstraintOperation.le) + of AlpmDepMod.gt: some(ConstraintOperation.gt) + of AlpmDepMod.lt: some(ConstraintOperation.lt) + else: none(ConstraintOperation) + + let description = if dependency.desc != nil: some($dependency.desc) else: none(string) + ($dependency.name, description, op.map(o => (o, $dependency.version))) + +proc checkConstraints(lop: ConstraintOperation, rop: ConstraintOperation, cmp: int): bool = + let (x1, x2) = if cmp > 0: + (1, -1) + elif cmp < 0: + (-1, 1) + else: + (0, 0) + + proc c(op: ConstraintOperation, x1: int, x2: int): bool = + case op: + of ConstraintOperation.eq: x1 == x2 + of ConstraintOperation.ge: x1 >= x2 + of ConstraintOperation.le: x1 <= x2 + of ConstraintOperation.gt: x1 > x2 + of ConstraintOperation.lt: x1 < x2 + + template a(x: int): bool = lop.c(x, x1) and rop.c(x, x2) + + a(2) or a(1) or a(0) or a(-1) or a(-2) + +proc isProvidedBy*(package: PackageReference, by: PackageReference): bool = + if package.name == by.name: + if package.constraint.isNone or by.constraint.isNone: + true + else: + let lcon = package.constraint.unsafeGet + let rcon = package.constraint.unsafeGet + let cmp = vercmp(lcon.version, rcon.version) + checkConstraints(lcon.operation, rcon.operation, cmp) + else: + false + +proc checkAndRefresh*(color: bool, args: seq[Argument]): tuple[code: int, args: seq[Argument]] = + let refreshCount = args.count((some("y"), "refresh")) + if refreshCount > 0: + let code = pacmanRun(true, color, args + .keepOnlyOptions(commonOptions, upgradeCommonOptions) & + ("S", none(string), ArgumentType.short) & + ("y", none(string), ArgumentType.short).repeat(refreshCount)) + + let callArgs = args + .filter(arg => not arg.matchOption((some("y"), "refresh"))) + (code, callArgs) + else: + (0, args) + +proc packageTargets*(args: seq[Argument]): seq[PackageTarget] = + args.targets.map(proc (target: string): PackageTarget = + let splitTarget = target.split('/', 2) + if splitTarget.len == 2: + PackageTarget(name: splitTarget[1], repo: some(splitTarget[0])) + else: + PackageTarget(name: target, repo: none(string))) + +proc isAurTargetSync*(target: SyncPackageTarget): bool = + target.foundInfo.isNone and (target.repo.isNone or target.repo == some("aur")) + +proc isAurTargetFull*[T: RpcPackageInfo](target: FullPackageTarget[T]): bool = + target.foundInfo.isSome and target.foundInfo.unsafeGet.repo == "aur" + +proc findSyncTargets*(handle: ptr AlpmHandle, dbs: seq[ptr AlpmDatabase], + targets: seq[PackageTarget], allowGroups: bool, checkProvides: bool): + (seq[SyncPackageTarget], seq[string]) = + let dbTable = dbs.map(d => ($d.name, d)).toTable + + proc checkProvided(name: string, db: ptr AlpmDatabase): bool = + for pkg in db.packages: + for provides in pkg.provides: + if $provides.name == name: + return true + return false + + proc findSync(target: PackageTarget): Option[SyncFoundInfo] = + if target.repo.isSome: + let repo = target.repo.unsafeGet + + if dbTable.hasKey(repo): + let db = dbTable[repo] + let pkg = db[target.name] + + if pkg != nil: + let base = if pkg.base == nil: target.name else: $pkg.base + return some((repo, some((base, $pkg.version, some($pkg.arch))))) + elif checkProvides and target.name.checkProvided(db): + return some((repo, none(SyncFoundPackageInfo))) + else: + return none(SyncFoundInfo) + else: + return none(SyncFoundInfo) + else: + let directResult = dbs + .map(db => (block: + let pkg = db[target.name] + if pkg != nil: + let base = if pkg.base == nil: target.name else: $pkg.base + some(($db.name, some((base, $pkg.version, some($pkg.arch))))) + else: + none(SyncFoundInfo))) + .filter(i => i.isSome) + .optFirst + .flatten + + if directResult.isSome: + return directResult + else: + if allowGroups: + let groupRepo = lc[d | (d <- dbs, g <- d.groups, $g.name == target.name), + ptr AlpmDatabase].optFirst + if groupRepo.isSome: + return groupRepo.map(d => ($d.name, none(SyncFoundPackageInfo))) + + if checkProvides: + for db in dbs: + if target.name.checkProvided(db): + return some(($db.name, none(SyncFoundPackageInfo))) + return none(SyncFoundInfo) + else: + return none(SyncFoundInfo) + + let syncTargets = targets.map(t => SyncPackageTarget(name: t.name, + repo: t.repo, foundInfo: findSync(t))) + let checkAur = syncTargets.filter(isAurTargetSync).map(t => t.name) + (syncTargets, checkAur) + +proc mapAurTargets*[T: RpcPackageInfo](targets: seq[SyncPackageTarget], + pkgInfos: seq[T]): seq[FullPackageTarget[T]] = + let aurTable = pkgInfos.map(i => (i.name, i)).toTable + + targets.map(proc (target: SyncPackageTarget): FullPackageTarget[T] = + if target.foundInfo.isNone and aurTable.hasKey(target.name): + let pkgInfo = aurTable[target.name] + let syncInfo = ("aur", some((pkgInfo.base, pkgInfo.version, none(string)))) + FullPackageTarget[T](name: target.name, repo: target.repo, + foundInfo: some(syncInfo), pkgInfo: some(pkgInfo)) + else: + FullPackageTarget[T](name: target.name, repo: target.repo, + foundInfo: target.foundInfo, pkgInfo: none(T))) + +proc formatArgument*(target: PackageTarget): string = + target.repo.map(r => r & "/" & target.name).get(target.name) + +proc ensureTmpOrError*(config: Config): Option[string] = + let tmpRootExists = try: + discard config.tmpRoot.existsOrCreateDir() + true + except: + false + + if not tmpRootExists: + some(tr"failed to create tmp directory '$#'" % [config.tmpRoot]) + else: + none(string) + +proc bisectVersion(repoPath: string, debug: bool, firstCommit: Option[string], + compareMethod: string, relativePath: string, version: string): Option[string] = + template forkExecWithoutOutput(args: varargs[string]): int = + forkWait(() => (block: + discard close(0) + if not debug: + discard close(1) + discard close(2) + + execResult(args))) + + let (workFirstCommit, checkFirst) = if firstCommit.isSome: + (firstCommit, false) + else: + (runProgram(gitCmd, "-C", repoPath, + "rev-list", "--max-parents=0", "@").optLast, true) + let realLastThreeCommits = runProgram(gitCmd, "-C", repoPath, + "rev-list", "--max-count=3", "@") + let index = workFirstCommit.map(c => realLastThreeCommits.find(c)).get(-1) + let lastThreeCommits = if index >= 0: + realLastThreeCommits[0 .. index] + else: + realLastThreeCommits + + proc checkCommit(commit: string): Option[string] = + let checkout1Code = forkExecWithoutOutput(gitCmd, "-C", repoPath, + "checkout", commit) + + if checkout1Code != 0: + none(string) + else: + let foundVersion = runProgram(pkgLibDir & "/bisect", + compareMethod, repoPath & "/" & relativePath, version).optFirst + let checkout2Code = forkExecWithoutOutput(gitCmd, "-C", repoPath, + "checkout", lastThreeCommits[0]) + + if checkout2Code != 0: + none(string) + elif foundVersion == some(version): + some(commit) + else: + none(string) + + if lastThreeCommits.len == 0: + none(string) + elif lastThreeCommits.len == 1: + if checkFirst: + checkCommit(lastThreeCommits[0]) + else: + none(string) + elif lastThreeCommits.len == 2: + let checkedCommit = checkCommit(lastThreeCommits[0]) + if checkedCommit.isSome: + checkedCommit + elif checkFirst: + checkCommit(lastThreeCommits[1]) + else: + none(string) + else: + # find the commit with specific package version using git bisect + let bisectStartCode = forkExecWithoutOutput(gitCmd, "-C", repoPath, + "bisect", "start", "@", workFirstCommit.get("")) + + if bisectStartCode != 0: + none(string) + else: + discard forkExecWithoutOutput(gitCmd, "-C", repoPath, + "bisect", "run", pkgLibDir & "/bisect", compareMethod, relativePath, version) + + let commit = runProgram(gitCmd, "-C", repoPath, + "rev-list", "--max-count=1", "refs/bisect/bad").optFirst + + discard forkExecWithoutOutput(gitCmd, "-C", repoPath, + "bisect", "reset") + + if commit.isSome: + let checkedCommit = commit.map(checkCommit).flatten + if checkedCommit.isSome: + checkedCommit + else: + # non-incremental git history (e.g. downgrade without epoch change), bisect again + bisectVersion(repoPath, debug, commit, compareMethod, relativePath, version) + elif checkFirst and workFirstCommit.isSome: + checkCommit(workFirstCommit.unsafeGet) + else: + none(string) + +proc obtainBuildPkgInfos*(config: Config, + pacmanTargets: seq[FullPackageTarget[RpcPackageInfo]]): (seq[PackageInfo], seq[string]) = + type + LookupBaseGroup = tuple[ + base: string, + version: string, + arch: string, + repo: string + ] + + LookupGitResult = tuple[ + group: LookupBaseGroup, + git: Option[GitRepo] + ] + + let bases: seq[LookupBaseGroup] = pacmanTargets + .map(target => (block: + let info = target.foundInfo.get + let pkg = info.pkg.get + (pkg.base, pkg.version, pkg.arch.get, info.repo))) + .deduplicate + + let lookupResults: seq[LookupGitResult] = bases + .map(b => (b, lookupGitRepo(b.repo, b.base, b.arch))) + let notFoundRepos = lookupResults.filter(r => r.git.isNone) + + if notFoundRepos.len > 0: + let messages = notFoundRepos.map(r => tr"$#: repository not found" % [r.group.base]) + (newSeq[PackageInfo](), messages) + else: + let message = ensureTmpOrError(config) + if message.isSome: + (@[], @[message.unsafeGet]) + else: + proc findCommitAndGetSrcInfo(base: string, version: string, + repo: string, git: GitRepo): seq[PackageInfo] = + let repoPath = repoPath(config.tmpRoot, base) + removeDirQuiet(repoPath) + + try: + if forkWait(() => execResult(gitCmd, "-C", config.tmpRoot, + "clone", "-q", git.url, "-b", git.branch, + "--single-branch", base)) == 0: + let commit = bisectVersion(repoPath, config.debug, none(string), + "source", git.path, version) + + if commit.isNone: + @[] + else: + discard forkWait(() => execResult(gitCmd, "-C", repoPath, + "checkout", "-q", commit.unsafeGet)) + let output = execProcess(bashCmd, ["-c", + """cd "$2/$3" && "$1" --printsrcinfo""", + "bash", makePkgCmd, repoPath, git.path], options = {}) + parseSrcInfo(repo, output, git.url, some(git.branch), commit, some(git.path)) + .filter(i => i.version == version) + else: + @[] + finally: + removeDirQuiet(repoPath) + + let pkgInfos = lc[x | (r <- lookupResults, x <- findCommitAndGetSrcInfo(r.group.base, + r.group.version, r.group.repo, r.git.get)), PackageInfo] + + let pkgInfosTable = pkgInfos.map(i => (i.name, i)).toTable + + let foundPkgInfos = lc[x | (y <- pacmanTargets, + x <- pkgInfosTable.opt(y.name)), PackageInfo] + let messages = pacmanTargets + .filter(t => not pkgInfosTable.hasKey(t.name)) + .map(t => tr"$#: failed to get package info" % [t.name]) + + discard rmdir(config.tmpRoot) + (foundPkgInfos, messages) + +proc cloneRepo*(config: Config, basePackages: seq[PackageInfo]): (int, Option[string]) = + let base = basePackages[0].base + let repoPath = repoPath(config.tmpRoot, base) + + let message = ensureTmpOrError(config) + if message.isSome: + (1, message) + elif repoPath.existsDir(): + (0, none(string)) + else: + let gitUrl = basePackages[0].gitUrl + let gitBranch = basePackages[0].gitBranch + let gitCommit = basePackages[0].gitCommit + let aur = basePackages[0].repo == "aur" + let branch = gitBranch.get("master") + + let cloneCode = forkWait(() => execResult(gitCmd, "-C", config.tmpRoot, + "clone", "-q", gitUrl, "-b", branch, "--single-branch", base)) + + if cloneCode == 0: + if gitCommit.isSome: + let code = forkWait(() => execResult(gitCmd, "-C", repoPath, + "reset", "-q", "--hard", gitCommit.unsafeGet)) + (code, none(string)) + elif aur: (block: + let commit = bisectVersion(repoPath, config.debug, none(string), + "srcinfo", basePackages[0].gitPath.get("."), basePackages[0].version) + + if commit.isSome: + let code = forkWait(() => execResult(gitCmd, "-C", repoPath, + "reset", "-q", "--hard", commit.unsafeGet)) + (code, none(string)) + else: + (1, none(string))) + else: + (1, none(string)) + else: + (cloneCode, none(string)) -- cgit v1.2.3-70-g09d2