From 5f7f4f2d713025f05cbc240323b6fe47bb6dbe24 Mon Sep 17 00:00:00 2001 From: kitsunyan Date: Mon, 30 Apr 2018 00:01:25 +0300 Subject: Add PKGBUILD retrieving --- doc/pakku.8.txt | 4 + src/common.nim | 216 ++++++++++++++++++++++++++++++-------------- src/config.nim | 17 ++-- src/feature/syncinfo.nim | 2 +- src/feature/syncinstall.nim | 61 +++---------- src/feature/syncsource.nim | 173 +++++++++++++++++++++++++++++++++++ src/format.nim | 6 ++ src/main.nim | 6 +- src/pacman.nim | 7 +- 9 files changed, 367 insertions(+), 125 deletions(-) create mode 100644 src/feature/syncsource.nim diff --git a/doc/pakku.8.txt b/doc/pakku.8.txt index d9a0e00..8fb140b 100644 --- a/doc/pakku.8.txt +++ b/doc/pakku.8.txt @@ -39,6 +39,10 @@ Sync Options (apply to '-S')[[SO]] root path, skip dependency checking using '-d', or run the program from root. +*-z, \--source*:: + Retrieve PKGBUILD source. You can specify destination directory after + package name: pakku -Sz bash::/tmp/bash. + Configuration ------------- See linkman:pacman.conf[5] and linkman:pakku.conf[5] for details on configuring diff --git a/src/common.nim b/src/common.nim index b66237a..351dd73 100644 --- a/src/common.nim +++ b/src/common.nim @@ -1,6 +1,6 @@ import future, options, os, osproc, posix, sequtils, sets, strutils, tables, - args, config, lists, package, pacman, utils, + args, config, format, lists, package, pacman, utils, "wrapper/alpm" type @@ -18,6 +18,7 @@ type PackageTarget* = object of RootObj reference*: PackageReference repo*: Option[string] + destination*: Option[string] SyncPackageTarget* = object of PackageTarget foundInfos*: seq[SyncFoundInfo] @@ -51,15 +52,25 @@ proc checkAndRefresh*(color: bool, args: seq[Argument]): tuple[code: int, args: else: (0, args) -proc packageTargets*(args: seq[Argument]): seq[PackageTarget] = +proc packageTargets*(args: seq[Argument], parseDestination: bool): seq[PackageTarget] = args.targets.map(target => (block: - let splitTarget = target.split('/', 2) - let (repo, nameConstraint) = if splitTarget.len == 2: - (some(splitTarget[0]), splitTarget[1]) + let (noDestinationTarget, destination) = if parseDestination: (block: + let split = target.split("::", 2) + if split.len == 2: + (split[0], some(split[1])) + else: + (target, none(string))) + else: + (target, none(string)) + + let splitRepoTarget = noDestinationTarget.split('/', 2) + let (repo, nameConstraint) = if splitRepoTarget.len == 2: + (some(splitRepoTarget[0]), splitRepoTarget[1]) else: - (none(string), target) + (none(string), noDestinationTarget) + let reference = parsePackageReference(nameConstraint, false) - PackageTarget(reference: reference, repo: repo))) + PackageTarget(reference: reference, repo: repo, destination: destination))) proc isAurTargetSync*(target: SyncPackageTarget): bool = target.foundInfos.len == 0 and (target.repo.isNone or target.repo == some("aur")) @@ -67,6 +78,29 @@ proc isAurTargetSync*(target: SyncPackageTarget): bool = proc isAurTargetFull*[T: RpcPackageInfo](target: FullPackageTarget[T]): bool = target.foundInfos.len > 0 and target.foundInfos[0].repo == "aur" +proc filterNotFoundSyncTargetsInternal(syncTargets: seq[SyncPackageTarget], + pkgInfoReferencesTable: Table[string, PackageReference], + upToDateNeededTable: Table[string, PackageReference]): seq[SyncPackageTarget] = + # collect packages which were found neither in sync DB nor in AUR + syncTargets.filter(t => not (upToDateNeededTable.opt(t.reference.name) + .map(r => t.reference.isProvidedBy(r)).get(false)) and t.foundInfos.len == 0 and + not (t.isAurTargetSync and pkgInfoReferencesTable.opt(t.reference.name) + .map(r => t.reference.isProvidedBy(r)).get(false))) + +proc filterNotFoundSyncTargets*[T: RpcPackageInfo](syncTargets: seq[SyncPackageTarget], + pkgInfos: seq[T], upToDateNeededTable: Table[string, PackageReference]): seq[SyncPackageTarget] = + let pkgInfoReferencesTable = pkgInfos.map(i => (i.name, i.toPackageReference)).toTable + filterNotFoundSyncTargetsInternal(syncTargets, pkgInfoReferencesTable, upToDateNeededTable) + +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.reference]) + else: + printError(config.color, trp("database not found: %s\n") % [target.repo.unsafeGet]) + proc findSyncTargets*(handle: ptr AlpmHandle, dbs: seq[ptr AlpmDatabase], targets: seq[PackageTarget], allowGroups: bool, checkProvides: bool): (seq[SyncPackageTarget], seq[string]) = @@ -125,7 +159,7 @@ proc findSyncTargets*(handle: ptr AlpmHandle, dbs: seq[ptr AlpmDatabase], return @[] let syncTargets = targets.map(t => SyncPackageTarget(reference: t.reference, - repo: t.repo, foundInfos: findSync(t))) + repo: t.repo, destination: t.destination, foundInfos: findSync(t))) let checkAurNames = syncTargets.filter(isAurTargetSync).map(t => t.reference.name) (syncTargets, checkAurNames) @@ -146,10 +180,10 @@ proc mapAurTargets*[T: RpcPackageInfo](targets: seq[SyncPackageTarget], if res.isSome: let (syncInfo, pkgInfo) = res.get FullPackageTarget[T](reference: target.reference, repo: target.repo, - foundInfos: @[syncInfo], pkgInfo: some(pkgInfo)) + destination: target.destination, foundInfos: @[syncInfo], pkgInfo: some(pkgInfo)) else: FullPackageTarget[T](reference: target.reference, repo: target.repo, - foundInfos: target.foundInfos, pkgInfo: none(T))) + destination: target.destination, foundInfos: target.foundInfos, pkgInfo: none(T))) proc queryUnrequired*(handle: ptr AlpmHandle, withOptional: bool, withoutOptional: bool, assumeExplicit: HashSet[string]): (HashSet[string], HashSet[string], HashSet[string]) = @@ -213,22 +247,46 @@ proc queryUnrequired*(handle: ptr AlpmHandle, withOptional: bool, withoutOptiona proc `$`*[T: PackageTarget](target: T): string = target.repo.map(proc (r: string): string = r & "/" & $target.reference).get($target.reference) -proc ensureTmpOrError*(config: Config): Option[string] = +template tmpRoot(config: Config, dropPrivileges: bool): string = + if dropPrivileges: config.tmpRootInitial else: config.tmpRootCurrent + +proc ensureTmpOrError*(config: Config, dropPrivileges: bool): Option[string] = let tmpRootExists = try: - let user = initialUser.get(currentUser) - discard config.tmpRoot.existsOrCreateDir() - discard chown(config.tmpRoot, (Uid) user.uid, (Gid) user.gid) + discard config.tmpRoot(dropPrivileges).existsOrCreateDir() + if dropPrivileges: + let user = initialUser.get(currentUser) + discard chown(config.tmpRoot(dropPrivileges), (Uid) user.uid, (Gid) user.gid) true except: false if not tmpRootExists: - some(tr"failed to create tmp directory '$#'" % [config.tmpRoot]) + some(tr"failed to create tmp directory '$#'" % [config.tmpRoot(dropPrivileges)]) else: none(string) +proc getGitFiles*(repoPath: string, gitSubdir: Option[string], + dropPrivileges: bool): seq[string] = + if gitSubdir.isSome: + forkWaitRedirect(() => (block: + if not dropPrivileges or dropPrivileges(): + execResult(gitCmd, "-C", repoPath, "ls-tree", "-r", "--name-only", "@", + gitSubdir.unsafeGet & "/") + else: + quit(1))) + .output + .map(s => s[gitSubdir.unsafeGet.len + 1 .. ^1]) + else: + forkWaitRedirect(() => (block: + if not dropPrivileges or dropPrivileges(): + execResult(gitCmd, "-C", repoPath, "ls-tree", "-r", "--name-only", "@") + else: + quit(1))) + .output + proc bisectVersion(repoPath: string, debug: bool, firstCommit: Option[string], - compareMethod: string, gitSubdir: string, version: string): Option[string] = + compareMethod: string, gitSubdir: string, version: string, + dropPrivileges: bool): Option[string] = template forkExecWithoutOutput(args: varargs[string]): int = forkWait(() => (block: discard close(0) @@ -236,7 +294,7 @@ proc bisectVersion(repoPath: string, debug: bool, firstCommit: Option[string], discard close(1) discard close(2) - if dropPrivileges(): + if not dropPrivileges or dropPrivileges(): execResult(args) else: quit(1))) @@ -245,7 +303,7 @@ proc bisectVersion(repoPath: string, debug: bool, firstCommit: Option[string], (firstCommit, false) else: (forkWaitRedirect(() => (block: - if dropPrivileges(): + if not dropPrivileges or dropPrivileges(): execResult(gitCmd, "-C", repoPath, "rev-list", "--max-parents=0", "@") else: @@ -253,7 +311,7 @@ proc bisectVersion(repoPath: string, debug: bool, firstCommit: Option[string], .output.optLast, true) let (realLastThreeCommits, _) = forkWaitRedirect(() => (block: - if dropPrivileges(): + if not dropPrivileges or dropPrivileges(): execResult(gitCmd, "-C", repoPath, "rev-list", "--max-count=3", "@") else: @@ -273,7 +331,7 @@ proc bisectVersion(repoPath: string, debug: bool, firstCommit: Option[string], none(string) else: let foundVersion = forkWaitRedirect(() => (block: - if dropPrivileges(): + if not dropPrivileges or dropPrivileges(): execResult(pkgLibDir & "/bisect", compareMethod, repoPath & "/" & gitSubdir, version) else: @@ -317,7 +375,7 @@ proc bisectVersion(repoPath: string, debug: bool, firstCommit: Option[string], "bisect", "run", pkgLibDir & "/bisect", compareMethod, gitSubdir, version) let commit = forkWaitRedirect(() => (block: - if dropPrivileges(): + if not dropPrivileges or dropPrivileges(): execResult(gitCmd, "-C", repoPath, "rev-list", "--max-count=1", "refs/bisect/bad") else: @@ -333,7 +391,8 @@ proc bisectVersion(repoPath: string, debug: bool, firstCommit: Option[string], checkedCommit else: # non-incremental git history (e.g. downgrade without epoch change), bisect again - bisectVersion(repoPath, debug, commit, compareMethod, gitSubdir, version) + bisectVersion(repoPath, debug, commit, compareMethod, gitSubdir, + version, dropPrivileges) elif checkFirst and workFirstCommit.isSome: checkCommit(workFirstCommit.unsafeGet) else: @@ -360,8 +419,52 @@ proc reloadPkgInfos*(config: Config, path: string, pkgInfos: seq[PackageInfo]): else: pkgInfos +proc clonePackageRepoInternal(config: Config, base: string, version: string, + git: GitRepo, dropPrivileges: bool): Option[string] = + let tmpRoot = config.tmpRoot(dropPrivileges) + let repoPath = repoPath(tmpRoot, base) + removeDirQuiet(repoPath) + + if forkWait(() => (block: + if not dropPrivileges or dropPrivileges(): + execResult(gitCmd, "-C", tmpRoot, + "clone", "-q", git.url, "-b", git.branch, + "--single-branch", base) + else: + quit(1))) == 0: + let commit = bisectVersion(repoPath, config.debug, none(string), + "source", git.path, version, dropPrivileges) + + if commit.isNone: + removeDirQuiet(repoPath) + none(string) + else: + discard forkWait(() => (block: + if not dropPrivileges or dropPrivileges(): + execResult(gitCmd, "-C", repoPath, + "checkout", "-q", commit.unsafeGet) + else: + quit(1))) + + some(repoPath) + else: + removeDirQuiet(repoPath) + none(string) + +proc clonePackageRepo*(config: Config, base: string, version: string, + git: GitRepo, dropPrivileges: bool): Option[string] = + let message = ensureTmpOrError(config, dropPrivileges) + if message.isSome: + message + else: + let repoPath = clonePackageRepoInternal(config, base, version, git, dropPrivileges) + if repoPath.isNone: + some(tr"$#: failed to clone git repository" % [base]) + else: + none(string) + proc obtainBuildPkgInfosInternal(config: Config, bases: seq[LookupBaseGroup], - pacmanTargetNames: seq[string], progressCallback: (int, int) -> void): + pacmanTargetNames: seq[string], progressCallback: (int, int) -> void, dropPrivileges: bool): (seq[PackageInfo], seq[string], seq[string]) = let lookupResults: seq[LookupGitResult] = bases .map(b => (b, lookupGitRepo(b.repo, b.base, b.arch))) @@ -371,43 +474,21 @@ proc obtainBuildPkgInfosInternal(config: Config, bases: seq[LookupBaseGroup], let messages = notFoundRepos.map(r => tr"$#: repository not found" % [r.group.base]) (newSeq[PackageInfo](), newSeq[string](), messages) else: - let message = ensureTmpOrError(config) + let message = ensureTmpOrError(config, dropPrivileges) if message.isSome: (@[], @[], @[message.unsafeGet]) else: proc findCommitAndGetSrcInfo(base: string, version: string, repo: string, git: GitRepo): tuple[pkgInfos: seq[PackageInfo], path: Option[string]] = - let repoPath = repoPath(config.tmpRoot, base) - removeDirQuiet(repoPath) - - if forkWait(() => (block: - if dropPrivileges(): - execResult(gitCmd, "-C", config.tmpRoot, - "clone", "-q", git.url, "-b", git.branch, - "--single-branch", base) - else: - quit(1))) == 0: - let commit = bisectVersion(repoPath, config.debug, none(string), - "source", git.path, version) - - if commit.isNone: - removeDirQuiet(repoPath) - (newSeq[PackageInfo](), none(string)) - else: - discard forkWait(() => (block: - if dropPrivileges(): - execResult(gitCmd, "-C", repoPath, - "checkout", "-q", commit.unsafeGet) - else: - quit(1))) - - let srcInfo = obtainSrcInfo(repoPath & "/" & git.path) - let pkgInfos = parseSrcInfo(repo, srcInfo, config.arch, - git.url, some(git.path)) - .filter(i => i.version == version) - (pkgInfos, some(repoPath)) + let repoPath = clonePackageRepoInternal(config, base, version, git, dropPrivileges) + + if repoPath.isSome: + let srcInfo = obtainSrcInfo(repoPath.unsafeGet & "/" & git.path) + let pkgInfos = parseSrcInfo(repo, srcInfo, config.arch, + git.url, some(git.path)) + .filter(i => i.version == version) + (pkgInfos, repoPath) else: - removeDirQuiet(repoPath) (newSeq[PackageInfo](), none(string)) progressCallback(0, lookupResults.len) @@ -434,12 +515,12 @@ proc obtainBuildPkgInfosInternal(config: Config, bases: seq[LookupBaseGroup], if errorMessages.len > 0: for path in paths: removeDirQuiet(path) - discard rmdir(config.tmpRoot) + discard rmdir(config.tmpRoot(dropPrivileges)) (foundPkgInfos, paths, errorMessages) proc obtainBuildPkgInfos*[T: RpcPackageInfo](config: Config, - pacmanTargets: seq[FullPackageTarget[T]], progressCallback: (int, int) -> void): - (seq[PackageInfo], seq[string], seq[string]) = + pacmanTargets: seq[FullPackageTarget[T]], progressCallback: (int, int) -> void, + dropPrivileges: bool): (seq[PackageInfo], seq[string], seq[string]) = let bases = pacmanTargets .map(proc (target: FullPackageTarget[T]): LookupBaseGroup = let info = target.foundInfos[0] @@ -448,20 +529,21 @@ proc obtainBuildPkgInfos*[T: RpcPackageInfo](config: Config, .deduplicate let pacmanTargetNames = pacmanTargets.map(t => t.reference.name) - obtainBuildPkgInfosInternal(config, bases, pacmanTargetNames, progressCallback) + obtainBuildPkgInfosInternal(config, bases, pacmanTargetNames, progressCallback, dropPrivileges) -proc cloneAurRepo*(config: Config, base: string, gitUrl: string): (int, Option[string]) = - let repoPath = repoPath(config.tmpRoot, base) +proc cloneAurRepo*(config: Config, base: string, gitUrl: string, + dropPrivileges: bool): (int, Option[string]) = + let repoPath = repoPath(config.tmpRoot(dropPrivileges), base) - let message = ensureTmpOrError(config) + let message = ensureTmpOrError(config, dropPrivileges) if message.isSome: (1, message) elif repoPath.existsDir(): (0, none(string)) else: let cloneCode = forkWait(() => (block: - if dropPrivileges(): - execResult(gitCmd, "-C", config.tmpRoot, + if not dropPrivileges or dropPrivileges(): + execResult(gitCmd, "-C", config.tmpRoot(dropPrivileges), "clone", "-q", gitUrl, "--single-branch", base) else: quit(1))) @@ -472,7 +554,7 @@ proc cloneAurRepo*(config: Config, base: string, gitUrl: string): (int, Option[s (0, none(string)) proc cloneAurReposWithPackageInfos*(config: Config, rpcInfos: seq[RpcPackageInfo], - keepRepos: bool, progressCallback: (int, int) -> void): + keepRepos: bool, progressCallback: (int, int) -> void, dropPrivileges: bool): (seq[PackageInfo], seq[PackageInfo], seq[string], seq[string]) = let bases: seq[tuple[base: string, gitUrl: string]] = rpcInfos .map(i => (i.base, i.gitUrl)).deduplicate @@ -484,11 +566,11 @@ proc cloneAurReposWithPackageInfos*(config: Config, rpcInfos: seq[RpcPackageInfo if index >= bases.len: (toSeq(pkgInfos.items), toSeq(paths.items), toSeq(errors.items)) else: - let repoPath = repoPath(config.tmpRoot, bases[index].base) + let repoPath = repoPath(config.tmpRoot(dropPrivileges), bases[index].base) removeDirQuiet(repoPath) let (cloneCode, cloneErrorMessage) = cloneAurRepo(config, - bases[index].base, bases[index].gitUrl) + bases[index].base, bases[index].gitUrl, dropPrivileges) progressCallback(index + 1, bases.len) @@ -516,5 +598,5 @@ proc cloneAurReposWithPackageInfos*(config: Config, rpcInfos: seq[RpcPackageInfo let names = rpcInfos.map(i => i.name).toSet let additionalPkgInfos = fullPkgInfos.filter(i => not (i.name in names)) - discard rmdir(config.tmpRoot) + discard rmdir(config.tmpRoot(dropPrivileges)) (resultPkgInfos, additionalPkgInfos, paths, errors) diff --git a/src/config.nim b/src/config.nim index a6f65b9..ad57550 100644 --- a/src/config.nim +++ b/src/config.nim @@ -27,7 +27,8 @@ type Config* = object of CommonConfig root*: string db*: string - tmpRoot*: string + tmpRootInitial*: string + tmpRootCurrent*: string color*: bool aurComments*: bool checkIgnored*: bool @@ -112,10 +113,13 @@ proc obtainConfig*(config: PacmanConfig): Config = let db = config.db let color = config.colorMode.get - let user = initialUser.get(currentUser) - let tmpRoot = options.opt("TmpDir").get("/tmp/pakku-${USER}") - .replace("${UID}", $user.uid) - .replace("${USER}", user.name) + proc obtainTmpDir(user: User): string = + options.opt("TmpDir").get("/tmp/pakku-${USER}") + .replace("${UID}", $user.uid) + .replace("${USER}", user.name) + + let tmpRootInitial = obtainTmpDir(initialUser.get(currentUser)) + let tmpRootCurrent = obtainTmpDir(currentUser) let aurComments = options.hasKey("AurComments") let checkIgnored = options.hasKey("CheckIgnored") let printAurNotFound = options.hasKey("PrintAurNotFound") @@ -123,7 +127,8 @@ proc obtainConfig*(config: PacmanConfig): Config = let viewNoDefault = options.hasKey("ViewNoDefault") let preBuildCommand = options.opt("PreBuildCommand") - Config(root: root, db: db, tmpRoot: tmpRoot, color: color, + Config(root: root, db: db, + tmpRootInitial: tmpRootInitial, tmpRootCurrent: tmpRootCurrent, color: color, dbs: config.dbs, arch: config.arch, debug: config.debug, progressBar: config.progressBar, verbosePkgList: config.verbosePkgList, pgpKeyserver: config.pgpKeyserver, ignorePkgs: config.ignorePkgs, ignoreGroups: config.ignoreGroups, diff --git a/src/feature/syncinfo.nim b/src/feature/syncinfo.nim index 42d7e91..b0765f4 100644 --- a/src/feature/syncinfo.nim +++ b/src/feature/syncinfo.nim @@ -101,7 +101,7 @@ proc handleTarget(config: Config, padding: int, args: seq[Argument], proc handleSyncInfo*(args: seq[Argument], config: Config): int = let (_, callArgs) = checkAndRefresh(config.color, args) - let targets = args.packageTargets + let targets = args.packageTargets(false) let (syncTargets, checkAurNames) = withAlpm(config.root, config.db, config.dbs, config.arch, handle, dbs, errors): diff --git a/src/feature/syncinstall.nim b/src/feature/syncinstall.nim index c214198..ab01df1 100644 --- a/src/feature/syncinstall.nim +++ b/src/feature/syncinstall.nim @@ -197,7 +197,7 @@ proc findDependencies(config: Config, handle: ptr AlpmHandle, dbs: seq[ptr AlpmD let (rpcInfos, aerrors) = getRpcPackageInfos(aurCheck.map(r => r.name)) for e in aerrors: printError(config.color, e) let (pkgInfos, additionalPkgInfos, paths, cerrors) = - cloneAurReposWithPackageInfos(config, rpcInfos, not printMode, update) + cloneAurReposWithPackageInfos(config, rpcInfos, not printMode, update, true) for e in cerrors: printError(config.color, e) (pkgInfos, additionalPkgInfos, paths)) @@ -247,30 +247,7 @@ proc findDependencies(config: Config, handle: ptr AlpmHandle, template clearPaths(paths: untyped) = for path in paths: removeDirQuiet(path) - discard rmdir(config.tmpRoot) - -proc filterNotFoundSyncTargetsInternal(syncTargets: seq[SyncPackageTarget], - pkgInfoReferencesTable: Table[string, PackageReference], - upToDateNeededTable: Table[string, PackageReference]): seq[SyncPackageTarget] = - # collect packages which were found neither in sync DB nor in AUR - syncTargets.filter(t => not (upToDateNeededTable.opt(t.reference.name) - .map(r => t.reference.isProvidedBy(r)).get(false)) and t.foundInfos.len == 0 and - not (t.isAurTargetSync and pkgInfoReferencesTable.opt(t.reference.name) - .map(r => t.reference.isProvidedBy(r)).get(false))) - -proc filterNotFoundSyncTargets[T: RpcPackageInfo](syncTargets: seq[SyncPackageTarget], - pkgInfos: seq[T], upToDateNeededTable: Table[string, PackageReference]): seq[SyncPackageTarget] = - let pkgInfoReferencesTable = pkgInfos.map(i => (i.name, i.toPackageReference)).toTable - filterNotFoundSyncTargetsInternal(syncTargets, pkgInfoReferencesTable, upToDateNeededTable) - -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.reference]) - else: - printError(config.color, trp("database not found: %s\n") % [target.repo.unsafeGet]) + discard rmdir(config.tmpRootInitial) proc printUnsatisfied(config: Config, satisfied: Table[PackageReference, SatisfyResult], unsatisfied: seq[PackageReference]) = @@ -328,19 +305,7 @@ proc editLoop(config: Config, base: string, repoPath: string, gitSubdir: Option[ else: res - let rawFiles = if gitSubdir.isSome: - forkWaitRedirect(() => (block: - dropPrivilegesAndChdir(none(string)): - execResult(gitCmd, "-C", repoPath, "ls-tree", "-r", "--name-only", "@", - gitSubdir.unsafeGet & "/"))) - .output - .map(s => s[gitSubdir.unsafeGet.len + 1 .. ^1]) - else: - forkWaitRedirect(() => (block: - dropPrivilegesAndChdir(none(string)): - execResult(gitCmd, "-C", repoPath, "ls-tree", "-r", "--name-only", "@"))) - .output - + let rawFiles = getGitFiles(repoPath, gitSubdir, true) let files = ("PKGBUILD" & rawFiles.filter(x => x != ".SRCINFO")).deduplicate proc editFileLoopAll(index: int): char = @@ -355,7 +320,7 @@ proc editLoop(config: Config, base: string, repoPath: string, gitSubdir: Option[ proc buildLoop(config: Config, pkgInfos: seq[PackageInfo], noconfirm: bool, noextract: bool): (Option[BuildResult], int, bool) = let base = pkgInfos[0].base - let repoPath = repoPath(config.tmpRoot, base) + let repoPath = repoPath(config.tmpRootInitial, base) let gitSubdir = pkgInfos[0].gitSubdir let buildPath = buildPath(repoPath, gitSubdir) @@ -365,7 +330,7 @@ proc buildLoop(config: Config, pkgInfos: seq[PackageInfo], noconfirm: bool, else: confFileEnv - let workConfFile = config.tmpRoot & "/makepkg.conf" + let workConfFile = config.tmpRootInitial & "/makepkg.conf" let workConfFileCopySuccess = try: copyFile(confFile, workConfFile) @@ -377,7 +342,7 @@ proc buildLoop(config: Config, pkgInfos: seq[PackageInfo], noconfirm: bool, file.writeLine("# PAKKU OVERRIDES") file.writeLine('#'.repeat(73)) file.writeLine("CARCH=" & config.arch.bashEscape) - file.writeLine("PKGDEST=" & config.tmpRoot.bashEscape) + file.writeLine("PKGDEST=" & config.tmpRootInitial.bashEscape) finally: file.close() true @@ -458,7 +423,7 @@ proc buildLoop(config: Config, pkgInfos: seq[PackageInfo], noconfirm: bool, proc buildFromSources(config: Config, commonArgs: seq[Argument], pkgInfos: seq[PackageInfo], noconfirm: bool): (Option[BuildResult], int) = let base = pkgInfos[0].base - let repoPath = repoPath(config.tmpRoot, base) + let repoPath = repoPath(config.tmpRootInitial, base) let gitSubdir = pkgInfos[0].gitSubdir proc loop(noextract: bool, showEditLoop: bool): (Option[BuildResult], int) = @@ -536,7 +501,7 @@ proc installGroupFromSources(config: Config, commonArgs: seq[Argument], proc formatArchiveFile(pkgInfo: PackageInfo, ext: string): string = let arch = if config.arch in pkgInfo.archs: config.arch else: "any" - config.tmpRoot & "/" & pkgInfo.name & "-" & pkgInfo.version & "-" & arch & ext + config.tmpRootInitial & "/" & pkgInfo.name & "-" & pkgInfo.version & "-" & arch & ext let allFiles = lc[(r.name, formatArchiveFile(r.pkgInfo, br.ext)) | (br <- buildResults, r <- br.replacePkgInfos), tuple[name: Option[string], file: string]] @@ -554,7 +519,7 @@ proc installGroupFromSources(config: Config, commonArgs: seq[Argument], discard if not clear: - printWarning(config.color, tr"packages are saved to '$#'" % [config.tmpRoot]) + printWarning(config.color, tr"packages are saved to '$#'" % [config.tmpRootInitial]) if buildCode != 0: handleTmpRoot(true) @@ -654,7 +619,7 @@ proc confirmViewAndImportKeys(config: Config, basePackages: seq[seq[seq[PackageI if index < flatBasePackages.len: let pkgInfos = flatBasePackages[index] let base = pkgInfos[0].base - let repoPath = repoPath(config.tmpRoot, base) + let repoPath = repoPath(config.tmpRootInitial, base) let aur = pkgInfos[0].repo == "aur" @@ -1072,7 +1037,7 @@ proc obtainAurPackageInfos(config: Config, rpcInfos: seq[RpcPackageInfo], else: (block: let (rpcInfos, aerrors) = getRpcPackageInfos(fullRpcInfos.map(i => i.name)) let (pkgInfos, additionalPkgInfos, paths, cerrors) = - cloneAurReposWithPackageInfos(config, rpcInfos, not printMode, update) + cloneAurReposWithPackageInfos(config, rpcInfos, not printMode, update, true) (pkgInfos, additionalPkgInfos, paths, (toSeq(aerrors.items) & cerrors).deduplicate)) terminate() @@ -1113,7 +1078,7 @@ proc obtainPacmanBuildTargets(config: Config, pacmanTargets: seq[FullPackageTarg let (buildPkgInfos, buildPaths, obtainErrorMessages) = if checkPacmanBuildPkgInfos: (block: echo(tr"checking official repositories...") let (update, terminate) = createCloneProgress(config, pacmanTargets.len, printMode) - let res = obtainBuildPkgInfos[PackageInfo](config, pacmanTargets, update) + let res = obtainBuildPkgInfos[PackageInfo](config, pacmanTargets, update, true) terminate() res) else: @@ -1259,7 +1224,7 @@ proc handleSyncInstall*(args: seq[Argument], config: Config): int = arg.matchOption(%%%"noconfirm")).optLast .map(arg => arg.key == "noconfirm").get(false) - let targets = args.packageTargets + let targets = args.packageTargets(false) withAur(): let (code, installed, foreignUpgrade, targetNamesSet, pacmanTargets, diff --git a/src/feature/syncsource.nim b/src/feature/syncsource.nim new file mode 100644 index 0000000..fba7062 --- /dev/null +++ b/src/feature/syncsource.nim @@ -0,0 +1,173 @@ +import + future, options, os, posix, sequtils, strutils, tables, + "../args", "../aur", "../common", "../config", "../format", "../lists", + "../package", "../pacman", "../utils", + "../wrapper/alpm" + +type + BaseTarget = tuple[ + base: string, + version: string, + destination: string, + aurGitUrl: Option[string], + gitRepo: Option[GitRepo] + ] + + CloneResult = tuple[ + base: string, + path: string, + files: seq[string], + destination: string + ] + +proc getFilesOrClear(base: string, repoPath: string, gitSubdir: Option[string]): + (seq[string], Option[string]) = + let rawFiles = getGitFiles(repoPath, gitSubdir, false) + .filter(f => f != ".gitignore" and f != ".SRCINFO" and f.find('/') < 0) + .map(f => gitSubdir.map(s => s & "/" & f).get(f)) + + if rawFiles.len > 0: + (rawFiles, none(string)) + else: + removeDirQuiet(repoPath) + (newSeq[string](), some(tr"$#: failed to clone git repository" % [base])) + +proc cloneRepositories(config: Config, targets: seq[BaseTarget], + update: (int, int) -> void): (List[CloneResult], List[string]) = + proc cloneNext(index: int, results: List[CloneResult], messages: List[string]): + (List[CloneResult], List[string]) = + update(index, targets.len) + + if index >= targets.len: + (results.reversed, messages.reversed) + else: + let target = targets[index] + let repoPath = repoPath(config.tmpRootCurrent, target.base) + removeDirQuiet(repoPath) + + if target.aurGitUrl.isSome: + let (cloneCode, cerror) = cloneAurRepo(config, + target.base, target.aurGitUrl.unsafeGet, false) + + if cloneCode != 0: + cloneNext(index + 1, results, toSeq(cerror.items) ^& messages) + else: + let (files, ferror) = getFilesOrClear(target.base, repoPath, none(string)) + if ferror.isSome: + cloneNext(index + 1, results, ferror.unsafeGet ^& messages) + else: + cloneNext(index + 1, (target.base, repoPath, files, + target.destination) ^& results, messages) + elif target.gitRepo.isSome: + let gitRepo = target.gitRepo.unsafeGet + let cerror = clonePackageRepo(config, target.base, + target.version, gitRepo, false) + + if cerror.isSome: + cloneNext(index + 1, results, cerror.unsafeGet ^& messages) + else: + let (files, ferror) = getFilesOrClear(target.base, repoPath, some(gitRepo.path)) + if ferror.isSome: + cloneNext(index + 1, results, ferror.unsafeGet ^& messages) + else: + cloneNext(index + 1, (target.base, repoPath, files, + target.destination) ^& results, messages) + else: + let message = tr"$#: repository not found" % [target.base] + cloneNext(index + 1, results, message ^& messages) + + cloneNext(0, nil, nil) + +proc copyFiles(config: Config, quiet: bool, results: seq[CloneResult]): List[string] = + proc copyNext(index: int, messages: List[string]): List[string] = + if index >= results.len: + messages.reversed + else: + let res = results[index] + discard mkdir(res.destination, 0o755) + + let error = try: + for f in res.files: + let index = f.rfind('/') + let name = if index >= 0: f[index + 1 .. ^1] else: f + let dest = if res.destination == ".": name else: res.destination & "/" & name + moveFile(res.path & "/" & f, dest) + printFile(config.color, quiet, res.base, dest) + none(string) + except OSError: + some(tr"$#: failed to move files" % [res.base]) + + copyNext(index + 1, toSeq(error.items) ^& messages) + + copyNext(0, nil) + +proc cloneAndCopy(config: Config, quiet: bool, + fullTargets: seq[FullPackageTarget[RpcPackageInfo]]): int = + let baseTargets = fullTargets.foldl(block: + let bases = a.map(x => x.base) + if b.isAurTargetFull: + let rpcInfo = b.pkgInfo.get + if rpcInfo.base in bases: + a + else: + a & (rpcInfo.base, rpcInfo.version, b.destination.get(rpcInfo.base), + some(rpcInfo.gitUrl), none(GitRepo)) + else: + let foundInfo = b.foundInfos[0] + let pkg = foundInfo.pkg.get + if pkg.base in bases: + a + else: + let git = lookupGitRepo(foundInfo.repo, pkg.base, pkg.arch.get) + a & (pkg.base, pkg.version, b.destination.get(pkg.base), + none(string), git), + newSeq[BaseTarget]()) + + let (update, terminate) = if quiet: + (proc (a: int, b: int) {.closure.} = discard, proc () {.closure.} = discard) + else: + printProgressShare(config.progressBar, tr"cloning repositories") + + let (results, rerrors) = cloneRepositories(config, baseTargets, update) + terminate() + for e in rerrors: printError(config.color, e) + + let cerrors = copyFiles(config, quiet, toSeq(results.items)) + for e in cerrors: printError(config.color, e) + + for result in results: + removeDirQuiet(result.path) + discard rmdir(config.tmpRootCurrent) + + if rerrors != nil and cerrors != nil: + 1 + else: + 0 + +proc handleSyncSource*(args: seq[Argument], config: Config): int = + discard checkAndRefresh(config.color, args) + + let quiet = args.check(%%%"quiet") + let targets = args.packageTargets(true) + + if targets.len == 0: + printError(config.color, trp("no targets specified (use -h for help)\n")) + 1 + else: + let (syncTargets, checkAurNames) = 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 (rpcInfos, aerrors) = getRpcPackageInfos(checkAurNames) + for e in aerrors: printError(config.color, e) + + let notFoundTargets = filterNotFoundSyncTargets(syncTargets, + rpcInfos, initTable[string, PackageReference]()) + + if notFoundTargets.len > 0: + printSyncNotFound(config, notFoundTargets) + 1 + else: + let fullTargets = mapAurTargets[RpcPackageInfo](syncTargets, rpcInfos) + cloneAndCopy(config, quiet, fullTargets) diff --git a/src/format.nim b/src/format.nim index c3a7e5e..bb5ae1f 100644 --- a/src/format.nim +++ b/src/format.nim @@ -65,6 +65,12 @@ proc printError*(color: bool, s: string) = proc printWarning*(color: bool, s: string) = stderr.writeLine(^Color.yellow, trp"warning: ", ^Color.normal, s) +proc printFile*(color: bool, quiet: bool, name: string, file: string) = + if quiet: + echo(file) + else: + echo(^Color.bold, name, ^Color.normal, ' ', file) + proc formatPkgRating*(votes: int, popularity: float): string = $votes & " / " & formatFloat(popularity, format = ffDecimal, precision = 6) diff --git a/src/main.nim b/src/main.nim index fbf3364..bf32cdd 100644 --- a/src/main.nim +++ b/src/main.nim @@ -4,8 +4,9 @@ import import "feature/syncinfo", - "feature/syncsearch", "feature/syncinstall", + "feature/syncsearch", + "feature/syncsource", "feature/localquery" proc passValidation(args: seq[Argument], config: Config, @@ -82,6 +83,8 @@ proc handleSync(args: seq[Argument], config: Config): int = elif syncArgs.check(%%%"search") and syncArgs.checkOpGroup(OpGroup.syncSearch): handleSyncSearch(args, config) + elif syncArgs.check(%%%"source"): + handleSyncSource(args, config) elif syncArgs.checkOpGroup(OpGroup.syncInstall) and (args.check(%%%"sysupgrade") or args.targets.len > 0): let printMode = args.check(%%%"print") or args.check(%%%"print-format") @@ -185,6 +188,7 @@ proc handleHelp(operation: OperationType) = printHelp(%%%"build", none(string), tr"build targets from source") printHelp(%%%"keyserver", some("name"), tr"use name as keyserver to receive keys from") printHelp(%%%"noaur", none(string), tr"disable all AUR operations") + printHelp(%%%"source", none(string), tr"retrieve PKGBUILD source") else: discard diff --git a/src/pacman.nim b/src/pacman.nim index f5df4e4..f9e921c 100644 --- a/src/pacman.nim +++ b/src/pacman.nim @@ -142,7 +142,8 @@ const o("y", "refresh") + g(syncInstall, syncSearch, syncQuery), $o("n", "build") + g(syncInstall), $(^o("keyserver")) + g(syncInstall), - $o("noaur") + g(syncInstall) + $o("noaur") + g(syncInstall), + $o("z", "source") + g() ] databaseOptions*: seq[CommandOption] = @[ @@ -177,7 +178,9 @@ const ("asdeps", @["asexplicit"]), ("build", @["nodeps", "assume-installed", "dbonly", "clean", "groups", "info", "list", "search", "sysupgrade", "downloadonly"]), - ("keyserver", @["clean", "groups", "info", "list", "search"]) + ("keyserver", @["clean", "groups", "info", "list", "search"]), + ("source", @["clean", "groups", "info", "list", "search", "sysupgrade", + "downloadonly", "build", "keyserver", "noaur"]) ] allConflictingOptions = syncConflictingOptions -- cgit v1.2.3-70-g09d2