diff options
Diffstat (limited to 'src/checkenv_resolver')
-rwxr-xr-x | src/checkenv_resolver | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/src/checkenv_resolver b/src/checkenv_resolver new file mode 100755 index 0000000..6ebabaf --- /dev/null +++ b/src/checkenv_resolver @@ -0,0 +1,300 @@ +#!/usr/bin/env python +# Copyright (c) 2015, Joseph Hunkeler <jhunk at stsci.edu> +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +try: + import argparse +except ImportError: + print("Please install argparse.") + exit(1) + +import os +import sys +from string import Template + + +class PackageException(Exception): + pass + +class Package(object): + def __init__(self, filename, load=True): + self.valid_keywords = [ + 'Type', + 'Requires', + 'Precedes', + 'Synopsis', + 'Description', + 'Environment', + 'LdLibrary', + 'IncPath', + 'Root', + 'Path', + 'ManPath', + 'Default', + 'Source' + ] + self.filename = os.path.join(ENVCONFIG_PATH, filename) + self.exists = False + self.name = '' + self.name_internal = '' + self.description = '' + self.dependencies = [] + self.precedence = [] + self.priority = 0 # 0 - 99 + self.shell = '' + self.env = {} + self.script = '' + self.invisible = False + self.mtime = 0 + self.data = {} + self.verbose = False + + if not load: + return + + self.preload() + + def __repr__(self): + return self.name + + def preload(self): + if os.path.exists(self.filename): + self.exists = True + + self.name = os.path.basename(self.filename.replace(' ', '_')) + + if self.filename is not None \ + and self.exists: + self.data = self.load() + self.get_requirements() + self.get_precedence() + + def get_requirements(self): + if not self.data: + PackageException('{0}: Package data not loaded.'.format(self.name)) + + if 'Requires' in self.data: + for next_req in self.data['Requires']: + try: + req = Package(next_req) + except: + continue + self.dependencies.append(req) + if not req.exists and self.verbose: + print("Requirement warning, {0}: {1} does not exist".format(self.name, os.path.basename(req.filename))) + + def get_precedence(self): + if not self.data: + raise PackageException('{0}: Package data not loaded.'.format(self.name)) + + if 'Precedes' in self.data: + for next_req in self.data['Precedes']: + req = Package(next_req, load=False) + self.precedence.append(req) + if not req.exists and self.verbose: + print("Precedence warning, {0}: {1} does not exist".format(self.name, os.path.basename(req.filename))) + + + def load(self): + pairs = [] + comment = '#' + delimiter = ':' + keyword = '' + value = '' + + with open(self.filename, 'r') as f: + for line in f.readlines(): + line = line.strip() + if not line: + continue + if not line.find(delimiter): + continue + if line.startswith(comment): + continue + + keyword = line[0:line.find(delimiter)] + if keyword not in self.valid_keywords: + continue + if keyword == 'Source': + continue + + value = line[line.find(delimiter) + 1:].strip() + pairs.append([keyword, value]) + + # Do source block + with open(self.filename, 'r') as f: + value = '' + shell_type = '' + data_block = False + + for line in f.readlines(): + if line.startswith('Source:'): + data_block = True + shell_type = line[line.find(delimiter) + 1:line.rfind(delimiter)].strip() + line = line[line.rfind(delimiter) + 1:-1] + if data_block: + value += line + if line.startswith('"') and line.endswith('\\'): + data_block = False + + pairs.append(['Source', value]) + pairs.append(['Shell', shell_type]) + + pairs = dict(pairs) + + # Alias the filename to be the same as "Root" value + if 'Root' in pairs: + pairs[self.name] = pairs['Root'] + + # Substitute configuration values + for key, value in pairs.items(): + s = Template(value) + pairs[key] = s.safe_substitute(pairs) + + if 'Requires' in pairs: + pairs['Requires'] = str(pairs['Requires']).split() + + if 'Precedes' in pairs: + pairs['Precedes'] = str(pairs['Precedes']).split() + + return dict(pairs) + + +spaces = '' +missing = ' Missing'.rjust(25, '.') +def solver(p, style='dependency'): + ''' Recursive package dependency resolver + ''' + flags = '' + tree = ' \\_' + resolver = p.dependencies + + if args.no_graphics: + tree = ' ' + + if style != 'dependency': + resolver = p.precedence + + # Wasn't planning on using a global here, but... recursion. + global spaces + global missing + for dep in resolver: + if not dep.exists: + flags = missing + print('{0} {1}{2:20s} {3}'.format(spaces, tree, dep.name, flags)) + flags = '' + if dep.dependencies: + spaces += ' ' + # Fall back in on yourself if there are more dependencies + # beyond the current 'dep' + solver(dep, style) + spaces = '' + +def show_package_map(packages, style='dependency'): + tree = '*--' + global missing + + if args.no_graphics: + tree = '' + + for pkg in pkgs: + flags = '' + if not pkg.exists: + flags = missing + print('{0}{1:22s} {2}'.format(tree, pkg.name, flags)) + solver(pkg, style) + print('') + + +if __name__ == '__main__': + args = None + ENVCONFIG_PATH = os.path.abspath('/usr/local/envconfig') + HOME = os.environ['HOME'] + ENVRC = os.path.join(HOME, '.envrc') + pkgs = [] + + parser = argparse.ArgumentParser() + parser.add_argument('-e', '--every', action='store_true', help='Generate map for all packages') + parser.add_argument('-r', '--requires', action='store_true', help='Omit package dependencies') + parser.add_argument('-p', '--precedence', action='store_true', help='Omit package precedence') + parser.add_argument('-w', '--warnings', action='store_true', help='No tree, only warnings') + parser.add_argument('-g', '--no-graphics', action='store_true', help='No tree characters') + + args = parser.parse_args() + + + if args.every: + for r, d, fs in os.walk(ENVCONFIG_PATH): + for f in fs: + pkgs.append(Package(f)) + else: + try: + with open(ENVRC, 'r') as fp: + for record in fp: + if record.startswith('#'): + continue + + if record.startswith('Package:'): + _, _, record = record.partition(':') + else: + continue + + record = record.strip() + pkgs.append(Package(record)) + except OSError as e: + print(e.message) + + if not args.requires and not args.precedence: + print('To see a summary of available options, issue -h or --help') + exit(0) + + if args.warnings: + for pkg in pkgs: + pkg.verbose = True + + if not pkg.exists: + # Normally the object performs its own checks to make sure + # it can read precedence, but we're overriding that functionality. + print('General failure, {0}: {1} does not exist'.format(pkg.name)) + + if args.requires: + pkg.get_requirements() + if args.precedence: + pkg.get_precedence() + + exit(0) + + if args.requires: + print("#### ####") + print("### Dependency Map ###") + print("#### ####") + show_package_map(pkgs) + print('') + + if args.precedence: + print("#### ####") + print("### Precedence Map ###") + print("#### ####") + show_package_map(pkgs, style='precedence') + print('') + + exit(0) |