#!/usr/bin/env python # -*- coding: utf-8 -*- # # # pkgdepcheck.py # # Script to find missing dependencies in current based on stable # #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++# # # # pkgdepcheck - a script to find subdependencies # # # # Copyright Tim Beech . # # # # partly based on Frédéric Galusik's code from pkgtxt2db # This program is free software; you can redistribute it and/or # # modify it under the terms of the GNU General Public License # # as published by the Free Software Foundation; either version 2 # # of the License, or (at your option) any later version. # # # # This program is distributed in the hope that it will be useful, # # but WITHOUT ANY WARRANTY; without even the implied warranty of # # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # # GNU General Public License for more details. # # # # You should have received a copy of the GNU General Public License # # along with this program; if not, write to the Free Software # # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # # #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++# import os import sys import urllib2 import gzip import argparse import string from ParsePkgtxt import Package import cPickle as pickle # Parse the CLI options parser = argparse.ArgumentParser( prog='pkgdepcheck', description='Download and interrogate PACKAGES.TXTs', epilog= 'e.g. pkgdepcheck -p midori, check for midori and its dependencies without refreshing package data') parser.add_argument('-u', '--update', action="store_true", default=False, help='Download fresh PACKAGES.TXT files') parser.add_argument('-P', '--packager', action="store", dest='packager', default='', help='Search for packages by a particular packager') parser.add_argument('-e', '--expa', action="store", dest='expa', default='/', help='Choose the slackware extra/patches repository') parser.add_argument('-p', '--package', action="store", dest='package', default='', help = 'Detailed package status info') parser.add_argument('-a', '--arch', action="store", dest='arch', default=os.uname()[4], help = 'Specify repo, defaults to local arch') parser.add_argument('-d', '--download', action="store", dest='download', default='', help = 'Download package source and previous package') parser.add_argument('-ns', '--nosource', action="store_true", default=False, help='Do not download package source files') parser.add_argument('-np', '--nopackage', action="store_true", default=False, help='Do not download package files (just get source)') parser.add_argument('-D', '--depcheck', action="store", dest='Package', default='', help='Check unpackaged package dependencies') parser.add_argument('-s', '--stable', action="store", dest='stable', default='14.0', help = 'Specify stable release, default 14.0') parser.add_argument('-c', '--current', action="store", dest='current', default='14.1', help = 'Specify release for current, default 14.1') parser.add_argument('-m', '--mirror', action="store", dest='mirror', default='http://download.salixos.org/', help = 'Choose a different mirror (default http://download.salixos.org)') args = parser.parse_args() #vars pkgtxtz = 'PACKAGES.TXT.gz' pkgtxt = 'PACKAGES.TXT' update = args.update packager = args.packager pkg = args.package expa = args.expa download = args.download download_source = not args.nosource download_package = not args.nopackage depcheck= args.Package if depcheck: pkg = depcheck stable = args.stable current = args.current mirror = args.mirror slash = '/' #dirs wd = os.getcwd() + slash # we were called from here configdir = os.path.expanduser('~/.config/pkgdepcheck/') os.system('mkdir -p ' + configdir) for hdir in (".", configdir, "/usr/libexec/pkgdepcheck"): if os.path.exists(os.path.join(hdir, "src.sh")): srcparse = os.path.join(hdir, "src.sh") #repo index vars slxstable, slxcurrent, slkstable, slkcurrent = 0, 1, 2, 3 #arch def guessArch(arg): """ If the user input contains the string '64', choose that repo, otherwise i486. guessArch(arg) """ if arg.find('64') != -1: return 'x86_64' else: return 'i486' arch = args.arch repo = guessArch(arch) ##### The following function pkgtxturl downloads PACKAGES.TXT def pkgtxturl(repo='i486', target='salix', release='14.0', expa='/'): """ Download the slackware/salix PACKAGES.TXT.gz from a built URL and unzip it. Ensure corresponding old dictionary is removed as well. pkgtxturl(repo, target, release, |extra|patches) """ # adjust the target if target == 'slackware': target = 'slackware-' if target[0:5] == 'salix': target = '' # if expa == 'extra': expa = '/extra/' elif expa == 'patches': expa = '/patches/' # Build the URL to fetch PACKAGES.TXT url = mirror + repo + slash + target + release + expa + pkgtxtz # remove old files clean([pkgtxtz, pkgtxt], configdir) # fetch downloadURL(configdir, url) # unzip it fout = open(configdir + pkgtxt, 'w') with gzip.open(configdir + pkgtxtz, 'rb') as f: for line in f: fout.write(line) fout.close() # rename and clean up if expa != "" and expa[0] == '/' and expa[-1] == '/': expa = expa[1:-1] if target == '': target = 'salix-' filename = target + repo + '_' + release + expa os.rename(configdir + pkgtxt, configdir + filename) dictname = filename + '.' + 'pickle' clean([pkgtxtz,pkgtxt,dictname], configdir) # remove old pickle too ##### The following function listPackages is for the --packager option def listPackages(nick): """ List packages packaged by someone before but not yet this time listPackages(nick) """ #open dictionaries dbStable = openDict('salix-', stable) dbCurrent = openDict('salix-', current) p = [] for package in dbStable: pname = stripNum(dbStable[package][2]) if pname == nick: # this is one he did if not elem(package,dbCurrent): p.append(package) p = sorted(p) for pkg in p: print pkg ##### The following functions getSource and getPkgFiles relate to --download option def getSource(pkg): """ Download source and package files for a given package from previous Slaix repo; check first. getSource(pkg) """ db = openDict('salix-', stable) pkgtxtFilename = 'salix-' + repo + '_' + stable print pkgtxtFilename, ' checking for ', pkg if elem(pkg, db): subdir = db[pkg][6] + slash urlbase = mirror + repo + slash + stable + '/source' + subdir[6:] + pkg + slash downloadURL(wd, urlbase + 'SLKBUILD') os.system(srcparse + ' > ' + configdir + 'sourcelist') f = open(configdir + "sourcelist", "r") sources = f.read() f.close() clean(['sourcelist'], configdir) for filename in sources.split(): downloadURL(wd, urlbase + filename) else: print "Package not available in Salix ", stable def getPkgFiles(pkg): version, parch, pkgrel, subdir = 0, 1, 2, 6 destdir = pkg + '-packagefiles' db = openDict('salix-', stable) urlbase = mirror + repo + slash + stable + db[pkg][subdir] + slash pkgnamebase = string.join([pkg, db[pkg][version], db[pkg][parch], db[pkg][pkgrel]], '-') for suffix in ['.dep', '.md5', '.meta', '.txt', '.txz']: url = urlbase+pkgnamebase+suffix downloadURL(wd, url) ##### These functions correspond to the --package and --depcheck options def overview(repos): """ Returns a dictionary associating each package name with four boolean variables to indicate whether it is present in each repo """ #create list of packages from all repos pkglist = [] for repo in repos: for pkg in repo: pkglist.append(pkg) #remove duplicates pkglist = list(set(pkglist)) #create dictionary from list using dictionary comprehension return {pkg:query(pkg, repos) for pkg in pkglist} def query(pkg, repos): """ Returns a list of boolean values for presence of single package per repo query(pkg, repos) """ profile = [] for repo in repos: profile.append(elem (pkg, repo)) return profile def display(isPresent, repos): """ Output messages about package status per repo I think all bases are covered - 2 ^ 3 = 8 options if not already in current Print version """ versionfield = 0 stableversion = '' if isPresent[slxstable]: stableversion = repos[slxstable][pkg][versionfield] if isPresent[slxcurrent]: # it has been packaged for Salix current currentversion = repos[slxcurrent][pkg][versionfield] packager = stripNum(repos[slxcurrent][pkg][2]) print pkg + '-' + currentversion, "has already been packaged for ", current, "by", packager if stableversion: print "The previous version was", stableversion elif not isPresent[slxstable]: # no Salix package this time or last, tell about Slack print pkg, "was not packaged for Salix", stable if isPresent[slkstable]: print "However, there was a package for it in Slackware", stable if isPresent[slkcurrent]: print "It has also been packaged for Slackware", current else: print "There does not appear to be a package for it in Slackware", current elif isPresent[slkcurrent]: print "Although it was not packaged for Slackware", stable, "either, there is a package for Slackware", current else: print "There is no record of any such package." else: # it was packaged for Salix last time, tell about Slack print pkg, "has not been packaged yet. The previous version was:", pkg + '-' + stableversion if isPresent[slkcurrent]: print "However, there is a Slackware package for it." if not isPresent[slkstable]: print "There was no Slackware package for", stable elif isPresent[slkstable]: print "It was packaged for Slackware", stable, "but has not yet been packaged for", current print def listDeps(package, repos): """ List unpackaged dependencies, recursively listDeps(package, repos) """ deps = [] depfield = 3 s = repos[slxstable] c = repos[slxcurrent] S = repos[slkstable] C = repos[slkcurrent] for dep in s[package][depfield].split(","): if not elem(dep, c) and elem(dep,s): deps.append(dep) if not elem(dep, S) and not elem('|', dep): for subdep in listDeps(dep, repos): deps.append(subdep) return sorted(list(set(deps))) # turn list to set to remove duplicates ##### The following are general helper functions def stripNum(pkgrel): """ Strip digits from left recursively stripNum(pjgrel) """ if pkgrel == '': return '' if pkgrel[0].isdigit(): return stripNum(pkgrel[1:]) else: return pkgrel def downloadURL(location, url): """ Download file from url and place it at local location downloadURL(location, URL) """ try: f = urllib2.urlopen(url) print "Fetching ", url print "" # Open local file for writing with open(location + os.path.basename(url), "wb") as local_file: local_file.write(f.read()) except urllib2.HTTPError, e: print "HTTP Error:", e.code, url return False except urllib2.URLError, e: print "URL Error:", e.reason, url return False def clean(files, directory): """ Remove any stray file or archive clean(files, directory) """ for f in files: if os.path.isfile(directory + f): os.remove(directory + f) def repoToDict(target, release): """ Convert PACKAGES.TXT file to a dictionary repoToDict(target, release), save file, return filename; if present and not updating, just name """ pkgtxtname = target + repo + '_' + release dictname = pkgtxtname + '.' + 'pickle' if update: clean(dictname, configdir) if not os.path.isfile(configdir + dictname): db = Package.parse(Package(), configdir + pkgtxtname) #save db as dictname with open(configdir + dictname, 'wb') as handle: pickle.dump(db, handle) return dictname def parseRepos(): """ Convert each repo to dict, return list of all four parseRepos() """ repos = [] for target in ['salix-', 'slackware-']: for repo in [stable, current]: db = openDict(target, repo) repos.append(db) return repos def openDict(target, release): pdict = repoToDict(target, release) with open(configdir + pdict, 'rb') as handle: return pickle.load(handle) def elem(item, listobj): """ There must be a library function that does this elem(item, listobj) """ for i in listobj: if i == item: return True return False ###### main ... def main(): #download databases if absent or if update requested, rename for target in ['slackware-', 'salix-']: for release in [stable, current]: filename = target + repo + '_' + release if expa != slash: filename = filename + expa if update or not os.path.isfile(configdir + filename): pkgtxturl(repo, target, release, expa) #carry out specified actions if download and download_source: getSource(download) if download and download_package: getPkgFiles(download) if packager: listPackages(packager) if pkg: #first make dictionaries repos = parseRepos() if not depcheck: #just check one package display(query(pkg, repos), repos) #show detail if elem(pkg, repos[slxstable]): pkgrelfield = 2 pkgrel = repos[slxstable][pkg][pkgrelfield] print pkg, "was packaged by", stripNum(pkgrel), "for Salix", stable else: #check for unpackaged dependencies pkgdict = overview(repos) if not elem(pkg,pkgdict): print "There is no package for", pkg elif pkgdict[pkg][slxstable]: deplist = sorted(listDeps(pkg, repos)) pipe =[] for dep in deplist: print dep if elem('|', dep): pipe.append(dep) if pipe: print "Note that alternative dependencies (like jdk|jre) aren't processed further, but you can check them independently." if not deplist: print pkg, "appears to have no unpackaged Salix dependencies." else: print pkg, "was not packaged for Salix", stable if __name__ == '__main__': main()