import subprocess, os, sys, re """ Often enough Python code can grow to depend on binaries on a system, you may also require only specific versions of these. This small library helps with this. It also has helpers for packages which we know to handle already. """ class ReqError(Exception): pass class ExecutionError(ReqError): def __init__(self, errcode): self.error_code = errcode class Req: "To be used for verifying binay package dependencies on Python code" def __init__(self): self.all_reqs_ok = True self.debug = False def enable_debug(self): self.debug = True def reqs_match(self): if self.all_reqs_ok: return True sys.stdout.write("You have unfulfilled binary requirements\n") return False def req_missing(self, program): self.all_reqs_ok = False sys.stdout.write("You need to have installed: %s\n" % program) def req_old_program(self, program, version_req): self.all_reqs_ok = False sys.stdout.write("You need to have installed: %s >= %s\n" % (program, version_req)) def which(self, program): cmd = ['which', program] process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True, universal_newlines=True) stdout = process.communicate()[0] process.wait() if process.returncode != 0: raise ExecutionError(process.returncode) return stdout def req_exists(self, program): cmd = ['which', program] process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True, universal_newlines=True) stdout = process.communicate()[0] process.wait() if process.returncode == 0: return True return False def req_get_prog_version(self, program, version_query, version_pos): ''' Suppose you have a binary that outputs: $ spatch --version spatch version 1.0.0-rc21 with Python support and with PCRE support Every program veries what it wants you to query it for a version string, prog_version() is designed so that you pass what the program expects for its version query, and the position you expect the version string to be on using python list. ''' cmd = [program, version_query] process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True, universal_newlines=True) stdout = process.communicate()[0] process.wait() if process.returncode != 0: raise ExecutionError(process.returncode) if self.debug: sys.stdout.write("Running '%s' got us this break down:\n%s\n" % ( ' '.join(cmd), "\n".join(map(str, [[i, x] for i, x in enumerate(stdout.split())])), )) sys.stdout.write("You are using for version: %s\n" % stdout.split()[version_pos]) sys.stdout.write("Specifically your idx, element: %s\n" % ([[i, x] for i, x in enumerate(stdout.split())][version_pos])) return stdout.split()[version_pos] def __compute_rel_weight(self, rel_specs): weight = 0 extra = 0 sublevel = 0 relmod = 0 if self.debug: sys.stdout.write("VERSION = %s\n" % rel_specs['VERSION']) sys.stdout.write("PATCHLEVEL = %s\n" % rel_specs['PATCHLEVEL']) sys.stdout.write("SUBLEVEL = %s\n" % rel_specs['SUBLEVEL']) sys.stdout.write("EXTRAVERSION = %s\n" % rel_specs['EXTRAVERSION']) sys.stdout.write("RELMOD_UPDATE = %s\n" % rel_specs['RELMOD_UPDATE']) if rel_specs['EXTRAVERSION'] != '': if ("." in rel_specs['EXTRAVERSION'] or "rc" in rel_specs['EXTRAVERSION']): rc = rel_specs['EXTRAVERSION'].lstrip("-rc") if (rc == ""): rc = 0 else: rc = int(rc) - 20 extra = int(rc) else: extra = int(rel_specs['EXTRAVERSION']) + 10 if rel_specs['SUBLEVEL'] != '': sublevel = int(rel_specs['SUBLEVEL'].lstrip(".")) * 20 else: sublevel = 5 if rel_specs['RELMOD_UPDATE'] != '': mod = rel_specs['RELMOD_UPDATE'] if (mod == ""): mod = 0 else: mod = int(mod) relmod = int(mod) weight = (int(rel_specs['VERSION']) << 32) + \ (int(rel_specs['PATCHLEVEL']) << 16) + \ (sublevel << 8 ) + \ (extra * 60) + (relmod * 2) return weight def req_get_rel_spec(self, rel): if "rc" in rel: m = re.match(r"v*(?P\d+)\.+" "(?P\d+)[.]*" "(?P\d*)" "(?P[-rc]+\w*)\-*" "(?P\d*)[-]*", rel) else: m = re.match(r"v*(?P\d+)\.+" "(?P\d+)[.]*" "(?P\d*)[.]*" "(?P\w*)\-*" "(?P\d*)[-]*", rel) if not m: return m rel_specs = m.groupdict() return rel_specs def compute_rel_weight(self, rel): rel_specs = self.req_get_rel_spec(rel) if not rel_specs: return 0 return self.__compute_rel_weight(rel_specs) def linux_version_cmp(self, version_req, version): ''' If the program follows the linux version style scheme you can use this to compare versions. ''' weight_has = self.compute_rel_weight(version) weight_req = self.compute_rel_weight(version_req) if self.debug: sys.stdout.write("You have program weight: %s\n" % weight_has) sys.stdout.write("Required program weight: %s\n" % weight_req) if weight_has < weight_req: return -1 return 0 def require_version(self, program, version_query, version_req, version_pos, version_cmp): ''' If you have a program version requirement you can specify it here, as for the other flags refer to prog_version. ''' if not self.require(program): return False version = self.req_get_prog_version(program, version_query, version_pos) if self.debug: sys.stdout.write("Checking release specs and weight: for: %s\n" % program) sys.stdout.write("You have version: %s\n" % version) sys.stdout.write("Required version: %s\n" % version_req) if version_cmp(version_req, version) != 0: self.req_old_program(program, version_req) return False return True def require(self, program): if self.req_exists(program): return True self.req_missing(program) return False def require_hint(self, program, package_hint): if self.require(program): return True sys.stdout.write("Try installing the package: %s\n" % package_hint) return False def coccinelle(self, version): if self.require_version('spatch', '--version', version, 2, self.linux_version_cmp): return True sys.stdout.write("Try installing the package: coccinelle\n") sys.stdout.write("If that is too old go grab the code from source:\n\n") sys.stdout.write("git clone https://github.com/coccinelle/coccinelle.git\n\n") sys.stdout.write("To build you will need: ocaml ncurses-devel\n\n") sys.stdout.write("If on SUSE / OpenSUSE you will also need: ocaml-ocamldoc\n\n") return False def kup(self): if self.require('kup'): return True sys.stdout.write("Try installing the package: kup\n") sys.stdout.write("If your distribution lacks that go get from source:\n\n") sys.stdout.write("git clone git://git.kernel.org/pub/scm/utils/kup/kup.git\n\n") return False def make(self, version): return self.require_version('make', '--version', version, 2, self.linux_version_cmp) def gcc(self, version): return self.require_version('gcc', '--version', version, 3, self.linux_version_cmp)