#!/usr/bin/python -O import re, os, sys, pacman, getopt ############################################################ # Define some classes we need class Version: def __init__(self): self.version = None self.file = None class Package: def __init__(self): self.name = None self.old = None self.new = None ############################################################ # Functions for walking the file trees ############################################################ def filesForRegexp(topdir, regexp): retval = [] def matchfile(regexp, dirpath, namelist): for name in namelist: if (regexp.match(name)): retval.append(os.path.join(dirpath, name)) os.path.walk(topdir, matchfile, regexp) return retval def packagesInTree(topdir): return filesForRegexp(topdir, re.compile("^.*\.pkg\.tar\.gz$")) def pkgbuildsInTree(topdir): return filesForRegexp(topdir, re.compile("^PKGBUILD$")) ############################################################ # Function for testing if two files are identical ############################################################ def areFilesIdentical(file_a, file_b): command = "cmp '" + file_a + "' '" + file_b + "' >/dev/null" retval = os.system(command) if (retval == 0): return True return False ############################################################ # Function for fetching info from PKGBUILDs and packages ############################################################ def infoFromPackageFile(filename): pkg = pacman.load(filename) return pkg.name, pkg.version + "-" + pkg.release def infoFromPkgbuildFile(filename): # open and source the file pf_stdin, pf_stdout = os.popen2("/bin/bash", 't', 0) print >>pf_stdin, ". " + filename # get pkgname print >>pf_stdin, 'echo $pkgname' pkgname = pf_stdout.readline().strip() # get pkgver print >>pf_stdin, 'echo $pkgver' pkgver = pf_stdout.readline().strip() # get pkgrel print >>pf_stdin, 'echo $pkgrel' pkgrel = pf_stdout.readline().strip() # clean up pf_stdin.close() pf_stdout.close() return pkgname, pkgver + "-" + pkgrel ############################################################ # Functions for doing the final steps of execution ############################################################ def execute(command): global switches print(command) if not (switches.get("-n") == True): os.system(command) def copyFileToRepo(filename, repodir): destfile = os.path.join(repodir, os.path.basename(filename)) command = "cp -p '" + filename + "' '" + destfile + "'" execute(command) def deleteFile(filename): command = "rm '" + filename + "'" execute(command) def runGensync(repo, pkgbuild): target = os.path.join(repo, os.path.basename(repo) + ".db.tar.gz") command = "gensync '" + pkgbuild_dir + "' '" + target + "'" execute(command) ############################################################ # Functions for error handling ############################################################ def warning(string): print >>sys.stderr, string + "\n" had_error = 0 def error(string): global had_error warning(string) had_error = 1 ############################################################ # MAIN ############################################################ # ARGUMENTS # # tupkgupdate [-n] [--delete] [--paranoid] # First call getopt switch_list,args_proper = getopt.getopt(sys.argv[1:], 'n', [ "delete", "paranoid" ]) switches = {} for switch in switch_list: switches[switch[0]] = 1 # Then handle the remaining arguments if (len(args_proper) < 3): print >>sys.stderr, "syntax: tupkgupdate [-n] [--delete] [--paranoid] " sys.exit(-1) repo_dir, pkgbuild_dir, build_dir = args_proper # Set up the lists and tables packages = dict() copy = list() delete = list() # PASS 1: PARSING/LOCATING # # A) Go through the PKGBUILD tree # For each PKGBUILD, create a Package with new Version containing # parsed version and and None for file a_files = pkgbuildsInTree(pkgbuild_dir) for a_file in a_files: pkgname, ver = infoFromPkgbuildFile(a_file) # Error (and skip) if we encounter any invalid PKGBUILD files if (pkgname == None or ver == None): error("Pkgbuild '" + a_file + "' is invalid!") continue # Error (and skip) if we encounter any duplicate package names # in the PKGBUILDs if (packages.get(pkgname)): error("Pkgbuild '" + a_file + "' is a duplicate!") continue version = Version() version.version = ver version.file = None package = Package() package.name = pkgname package.new = version packages[pkgname] = package # B) Go through the old repo dir # For each package file we encounter, create a Package with old # Version containing parsed version and filepath b_files = packagesInTree(repo_dir) for b_file in b_files: pkgname, ver = infoFromPackageFile(b_file) version = Version() version.version = ver version.file = b_file package = packages.get(pkgname) if (package == None): package = Package() package.name = pkgname packages[pkgname] = package package.old = version # C) Go through the build tree # For each package file we encounter: # 1 - look up the package name; if it fails, ignore the file (no error) # 2 - if package.new == None, ignore the package (no error) # 3 - if package.new.version doesn't match, then skip (no error) # 4 - if package.new.file == None, point it to this file # otherwise, log an error (and skip) c_files = packagesInTree(build_dir) for c_file in c_files: pkgname, ver = infoFromPackageFile(c_file) # 1 package = packages.get(pkgname) if (package == None): continue # 2 if (package.new == None): continue # 3 if (package.new.version != ver): continue # 4 if (package.new.file == None): package.new.file = c_file continue else: error("Duplicate new file '" + c_file + "'") continue # PASS 2: CHECKING # # Go through the package collection # 1 - if package has no new, place its old file on the "delete" list # 2 - if package has a new but no new.file, error and skip # 3 - if package has no old, add new file to "copy" list into repo dir # 4 - if old > new, error and skip # 5 - if new == old, compare them and error and skip if files not the same # 6 - add entry to "delete" list for old file and "copy" list for # new file into repo dir for package in packages.values(): # 1 if (package.new == None): delete.append(package.old.file) continue # 2 if (package.new.file == None): error("No new package supplied for " + package.name + " " + package.new.version + "!") continue # 3 if (package.old == None): copy.append(package.new.file) continue # 4 if (package.old.version < package.new.version): delete.append(package.old.file) copy.append(package.new.file) continue # 5 if (package.old.version == package.new.version): if (switches.get("--paranoid") == True): if not (areFilesIdentical(package.old.file, package.new.file)): warning("New package file with identical version '" + package.new.file + "' is different than the old one:") if (switches.get("--delete") == True): warning(" Deleting the new file.") else: warning(" Ignoring the new file.") continue # 6 delete.append(package.old.file) copy.append(package.new.file) continue ## IF WE HAVE HAD ANY ERRORS AT THIS POINT, ABORT! ## if (had_error == 1): error("Aborting due to errors.") sys.exit(-1) # PASS 3: EXECUTION # # Copy for file in copy: copyFileToRepo(file, repo_dir) # Delete (second, for safety's sake) for file in delete: deleteFile(file) # Run gensync to build the repo index runGensync(repo_dir, pkgbuild_dir)