| 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] |
| |
| MAX_RC = 25 |
| 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) - (Req.MAX_RC + 1) |
| 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<VERSION>\d+)\.+" |
| "(?P<PATCHLEVEL>\d+)[.]*" |
| "(?P<SUBLEVEL>\d*)" |
| "(?P<EXTRAVERSION>[-rc]+\w*)\-*" |
| "(?P<RELMOD_UPDATE>\d*)[-]*", |
| rel) |
| else: |
| m = re.match(r"v*(?P<VERSION>\d+)\.+" |
| "(?P<PATCHLEVEL>\d+)[.]*" |
| "(?P<SUBLEVEL>\d*)[.]*" |
| "(?P<EXTRAVERSION>\w*)\-*" |
| "(?P<RELMOD_UPDATE>\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) |