#!/usr/bin/env python import os import sys from PythonUtils import getGoboVariable, colorCyan, colorNormal, colorGray goboSettings = getGoboVariable('goboSettings') goboPrograms = getGoboVariable('goboPrograms') goboUserSettings = getGoboVariable('goboUserSettings') class UseFlagEngine(object): program = None depth = 0 execute_depth = 0 block_stack = [] on_dict = {} capture_text = None _potentialflags = frozenset() _generic_flags = {'DEFINED_GENERICS': [], 'INSTALLED_PROGRAMS': []} _flags = set(['GENERIC_FLAGS', 'MUTUALLY_EXCLUSIVES', 'INSTALLED']) _changes = [] _changedata = {} def __init__(self, program, potentialflags=frozenset()): self.program = program self._potentialflags = potentialflags self._generic_flags['INSTALLED_PROGRAMS'] = list(installed_programs()) self.on_dict['-INSTALLED'] = [(None, (lambda: self._flags.difference_update(self._changedata['installed'][2]), "self._flags.difference_update(self._changedata['installed'][2])" ))] self._initialiseCommands() def _initialiseCommands(self): self.commands = { '+': self.setflag, '-': self.unsetflag, '#': lambda *x: None, 'define_generic': self.define_generic, 'if': self.if_handler, 'generic_prepend': self.generic_prepend, 'generic_remove': self.generic_remove, 'generic_pop': self.generic_pop, 'on': self.on_handler, 'loop': self.loop_handler, 'break': self.break_handler, 'include': self.include_handler, } def __getstate__(self): d = dict(self.__dict__) d['commands'] = None od = dict(self.on_dict) d['on_dict'] = od for flag in od: for index in range(0, len(od[flag])): depth, text = od[flag][index] if depth is None and isinstance(text, tuple): od[flag][index] = (depth, text[1]) return d def __setstate__(self, d): for k in d: setattr(self, k, d[k]) self._initialiseCommands def setflag(self, flag, *programs): if not programs or self.program in programs: self._flags.add(flag) if flag in self.on_dict: for depth, text in self.on_dict[flag]: self.execute_at_depth(text, depth) def unsetflag(self, flag, *programs): if not programs or self.program in programs: self._flags.discard(flag) if '*' == flag: for flag in frozenset(self._flags): self.unsetflag(flag) if '-' + flag in self.on_dict: for depth, text in self.on_dict['-' + flag]: self.execute_at_depth(text, depth) if 'SPOOKY_ACTION_AT_A_DISTANCE' in self._flags: for gen in self._generic_flags['DEFINED_GENERICS']: if flag in self._generic_flags[gen]: self._generic_flags[gen].remove(flag) def define_generic(self, generic, *components): self._generic_flags[generic] = list(components) if generic.islower(): self._generic_flags['DEFINED_GENERICS'].append(generic) def if_handler(self, *args): flag, query = args[0], args[1:] if query: if ('is', 'available') == query: if flag in self._potentialflags: self.execute_depth += 1 elif 'program' == flag and ('is', self.program) == query: self.execute_depth += 1 elif args[0] in self._flags: self.execute_depth += 1 self.block_stack.append(None) def loop_handler(self, *args): self.block_stack.append((self.loop_done, self.depth + 1, args)) self.capture_text = '' def loop_done(self, depth, args, text): generic, variable = args stack = self._generic_flags[generic] for value in stack: to_execute = text.replace('$' + variable, value) ret = self.execute_at_depth(to_execute, depth) if ret: ret -= 1 break def break_handler(self, *args): if args: return int(args[0]) else: return 1 def include_handler(self, *args): for path in (goboUserSettings + '/Scripts/UseFlags/', goboSettings + '/Scripts/UseFlags/', goboPrograms + '/Scripts/Current/Data/UseFlags/'): if os.path.exists(path + args[0] + '.useflags'): self.execute_file(path + args[0] + '.useflags', 'include "' + args[0] + '"') break def on_handler(self, *args): self.block_stack.append((self.on_done, self.depth + 1, args)) self.capture_text = '' def on_done(self, depth, flags, text): for flag in flags: if flag not in self.on_dict: self.on_dict[flag] = [] self.on_dict[flag].append((depth, text)) def generic_prepend(self, generic, *args): if not generic in self._generic_flags: self.define_generic(generic) self._generic_flags[generic][0:0] = args def generic_remove(self, generic, *args): if generic in self._generic_flags: self._generic_flags[generic] = list(filter(lambda x: x not in args, self._generic_flags[generic])) def generic_pop(self, generic, *args): if generic in self._generic_flags: popped = self._generic_flags[generic][0] self._generic_flags[generic] = self._generic_flags[generic][1:] if args: self.generic_prepend(args[0], popped) def execute_at_depth(self, text, depth): if depth is None: if isinstance(text, str): return eval(text) elif isinstance(text, tuple): return text[0]() else: return text() d, ed = self.depth, self.execute_depth self.depth = depth self.execute_depth = depth ret = self.execute_string(text) self.depth, self.execute_depth = d, ed return ret def _scope_handle(self, line): depth = 0 for char in line: if ' ' == char: depth += 1 else: break if depth < self.depth and depth <= self.execute_depth: self._leave_scope() self.depth = depth if depth < self.execute_depth: self.execute_depth = depth if self.capture_text is not None: self.capture_text += line + '\n' if self.depth > self.execute_depth: return True def _leave_scope(self, final_end = False): if final_end and not (self.depth > self.execute_depth): return if self.block_stack: b = self.block_stack.pop() if b: b[0](b[1], b[2], self.capture_text) self.capture_text = None def _begin_changeset(self, name): self._changes.append(name) self._changedata[name] = (frozenset(self._flags),) def _end_changeset(self, name): startflags, = self._changedata[name] endflags = frozenset(self._flags) self._changedata[name] = (startflags, endflags, endflags-startflags, startflags-endflags) def execute_string(self, text): for line in text.splitlines(): ret = self.execute_line(line) if ret: return ret self._leave_scope(True) def execute_line(self, line): self.debug(line) if not line: return if self._scope_handle(line): return tokens = line.split() if not tokens: return cmd = tokens[0] args = tokens[1:] if cmd.startswith(('+', '-', '#')): args[0:0] = [cmd[1:]] cmd = cmd[0] elif cmd.endswith(':'): args[0:0] = [cmd[0:-1]] cmd = 'define_generic' elif cmd.endswith(':+'): args[0:0] = [cmd[0:-2]] cmd = 'generic_prepend' elif cmd.endswith(':-'): args[0:0] = [cmd[0:-2]] cmd = 'generic_remove' if cmd in self.commands: ret = self.commands[cmd](*args) if ret: self.debug('returned ' + str(ret)) self._leave_scope(True) return ret else: print "Error: invalid command", cmd def execute_file(self, path, name=None): f = None try: self._begin_changeset(name) f = open(path) for line in f: if self.execute_line(line): break self._leave_scope(True) self._end_changeset(name) except IOError: pass finally: if f: f.close() def add_installed_programs(self): self._begin_changeset('installed') self._flags.update(installed_programs()) self._end_changeset('installed') def parse_environ(self): if 'USE' in os.environ: self._begin_changeset('environ') for spec in os.environ['USE'].split(): comps = spec.split('@') mode, flag = comps[0][0], comps[0][1:] progs = comps[1:] if '+' == mode: self.setflag(flag, *progs) elif '-' == mode: self.unsetflag(flag, *progs) self._end_changeset('environ') def finalise(self): if 'GENERIC_FLAGS' in self._flags and self._potentialflags: self._begin_changeset('generictospecific') for generic in self._generic_flags: if generic not in self._flags: continue found = None for flag in self._generic_flags[generic]: if flag not in self._potentialflags: continue if not found and flag in self._potentialflags: found = flag if flag in self._flags: found = None break if found: self.setflag(found) self._end_changeset('generictospecific') if 'MUTUALLY_EXCLUSIVES' in self._flags: self._begin_changeset('mutuallyexclusive') for generic in self._generic_flags: if '*' + generic in self._potentialflags: found = False for flag in self._generic_flags[generic]: if flag not in self._potentialflags: continue if found: self._flags.discard(flag) elif flag in self._flags: found = True self._end_changeset('mutuallyexclusive') def standard_run(self): files = ( (goboPrograms + '/Scripts/Current/Data/DistUseFlags.conf', 'distflags'), (goboSettings + '/GenericFlags.conf', 'genericflags'), (goboUserSettings+'/Scripts/GenericFlags.conf','usergenerics'), (goboSettings + '/UseFlags.conf', 'systemflags'), (goboUserSettings + '/Scripts/UseFlags.conf', 'userflags') ) for path, name in files: if 'systemflags' == name: if ('USE' not in os.environ or '-INSTALLED' not in os.environ['USE'].split()): self.add_installed_programs() else: self._begin_changeset('installed') self._end_changeset('installed') if os.path.exists(path): self.execute_file(path, name=name) if 'USE' in os.environ: self.parse_environ() self.finalise() def flags(self): if self._potentialflags is not None: return frozenset(self._potentialflags & self._flags) return frozenset(self._flags) def allflags(self): return frozenset(self._flags) def potentialflags(self): if self._potentialflags: return frozenset(self._potentialflags) return frozenset() def disabledflags(self): return frozenset(self._potentialflags - self._flags) debug_on = False def debug(self, line): if self.debug_on: print 'with line:', line print ' flags:', ', '.join(sorted(self._flags)) print ' generics:', self._generic_flags def UseFlags(program=None, return_engine=False): """Return the set of flags enabled for program""" potentialflags = frozenset() if program and program.startswith('/'): catch = False recipedir = str(program) for comp in program.split('/'): if catch: program = comp break elif comp.endswith('Recipes'): catch = True potentialflags = potentialFlags(recipedir) if not potentialFlags: # There can't possibly be any flags enabled, so just return. return frozenset() e = UseFlagEngine(program, potentialflags=potentialflags) e.standard_run() if return_engine: return e elif program: return e.flags() else: return e.allflags() global_program_potential_flags_cache = None def potentialFlags(recipedir): global global_program_potential_flags_cache if not global_program_potential_flags_cache: global_program_potential_flags_cache = dict() if recipedir in global_program_potential_flags_cache: return global_program_potential_flags_cache[recipedir] import re potentialflags = set() mutuallyexclusives = set() flagre = re.compile('.*\[([^]]+)\].*') for depfile in (recipedir+'/Resources/Dependencies', recipedir+'/Resources/BuildDependencies'): if os.path.exists(depfile): for dep in open(depfile): m = flagre.match(dep) if m: potentialflags.update(m.group(1).split(',')) potentialflags.discard('cross') potentialflags.discard('!cross') potentialflags = frozenset(potentialflags) global_program_potential_flags_cache[recipedir] = potentialflags return potentialflags def _change_report(e): potentials = e.potentialflags() generics = dict((gen, e._generic_flags[gen]) for gen in e._generic_flags['DEFINED_GENERICS']) potentialgenerics = set() specific_to_generic = {} for generic in generics: for flag in generics[generic]: if flag in potentials: potentialgenerics.add(generic) specific_to_generic[flag] = generic pwg = potentials | potentialgenerics for name in e._changes: if 'distflags' == name: desc = 'distribution flags' elif 'installed' == name: desc = 'installed programs' elif 'genericflags' == name: desc = 'system GenericFlags' elif 'usergenerics' == name: desc = 'user-specific GenericFlags' elif 'systemflags' == name: desc = 'system UseFlags' elif 'userflags' == name: desc = 'user-specific UseFlags' elif 'environ' == name: desc = 'USE environment variable' elif 'mutuallyexclusive' == name: desc = 'mutually-exclusive flags' else: desc = 'somewhere unknown (' + name + ')' startflags,endflags,setflags,unsetflags = e._changedata[name] setflags = setflags & pwg unsetflags = unsetflags & pwg if name == 'generictospecific': for gen in generics: if gen not in e._flags: continue for flag in generics[gen]: if flag in startflags and flag in pwg: avail = set(generics[gen]) & pwg avail.discard(flag) sys.stderr.write(colorGray+'UseFlags: '+colorCyan) sys.stderr.write('Transformed generic flag ' + gen + ' into already-enabled flag ' + flag) if avail: sys.stderr.write('; also available ' + ', '.join(avail)) sys.stderr.write(colorNormal + '\n') break for flag in setflags: gen = specific_to_generic[flag] avail = set(generics[gen]) & pwg avail.discard(flag) sys.stderr.write(colorGray + 'UseFlags: ' + colorCyan) sys.stderr.write('Transformed generic flag ' + gen + ' into ' + flag) if avail: sys.stderr.write('; also available ' + ', '.join(avail)) sys.stderr.write(colorNormal + '\n') continue if setflags: sys.stderr.write(colorGray + 'UseFlags: ' + colorCyan) sys.stderr.write('Added from ' + desc + ': ' + ', '.join(setflags) + colorNormal + '\n') if unsetflags: sys.stderr.write(colorGray + 'UseFlags: ' + colorCyan) sys.stderr.write('Unset from ' + desc + ': ' + ', '.join(unsetflags) + colorNormal + '\n') def installed_programs(): if not hasattr(installed_programs, 'cache'): def cacheadd(flag): installed_programs.cache.add(flag) installed_programs.cache = set() for oprog in os.listdir(goboPrograms): prog = oprog.lower().replace('-', '_') prog = filter(lambda p: p.isalpha() or p.isdigit() or p=='_', prog) if prog in ('qt', 'gtk'): for ver in os.listdir(os.path.join( goboPrograms, oprog)): if '.' in ver: major, _ = ver.split('.', 1) if major.isdigit(): cacheadd(prog + major) else: cacheadd(prog) return installed_programs.cache # Executable code when run from the command line. if __name__ == '__main__': args = sys.argv[1:] # "verbose" mode applies when a program AND flag are given: it will # output a message as well as setting the return code if args and '-v' == args[0]: verbose = True args = args[1:] elif args and '--help' == args[0]: sys.stdout.write(""" UseFlags Usage: UseFlags [ []] When program and flag both specified, the return code is true if the flag is enabled, and false otherwise. If only program is specified, or `UseFlags` is called alone, output the set of flags enabled for that program or overall. program may be the path to a recipe directory to include only flags actually used by prog. """) sys.exit() program = None # If args[0] is a path, just pass it straight through if args and '/' in args[0]: program = args[0] elif args: # Otherwise, call GetRecipe to fetch/find the local copy f = None if len(args) >= 2: f = os.popen('GetRecipe %s %s 2> /dev/null' % (args[0], args[1])) elif len(args) == 1: f = os.popen('GetRecipe %s 2>/dev/null'%(args[0])) program = f.read().strip() f.close() e = UseFlags(program, return_engine=True) report = False if 'USE' in os.environ: report = True if os.path.exists(goboSettings + '/UseFlags.conf'): f = open(goboSettings + '/UseFlags.conf') for line in f: if not line.startswith('#'): report = True f.close() break if report: _change_report(e) if os.path.exists(goboSettings + '/GenericFlags.conf'): sys.stderr.write(colorGray + 'UseFlags: ' + colorCyan) sys.stderr.write("GenericFlags.conf is deprecated and should " "be merged into UseFlags.conf. " "`UpdateSettings Scripts` will perform the " "merge automatically if you have not modified " "GenericFlags.conf, after which you can " "delete the file.") sys.stderr.write(colorNormal + '\n') flags = e.flags() if not program: flags = e.allflags() # If no flag is specified, output all the enabled flags for this program if len(args) < 2: for flag in flags: sys.stdout.write(flag + '\n') sys.exit(0) # Otherwise, test whether the flag is enabled and set the return code # accordingly. If -v was given, also output " on|off" else: if (len(args) == 3 and args[2] in flags) or ( len(args) == 2 and args[1] in flags): sys.exit(0) else: sys.exit(1)