#!/usr/bin/env python # Copyright (C) 2008 Michael Homer <=mwh> # Gives information about the state of use flags # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. from PythonUtils import getGoboVariable import sys, os.path # A frozen set of the global set of flags (program=None, or any program # with no special flags specified) global_use_flags_cache = None # A dict of program=>set of flags for each program that has been evaluated global_program_use_flags_cache = None # A dict of recipe dir=>set of potential flags global_program_potential_flags_cache = None # A dict of generic flag=>(specific flag,...) generic_to_specific = None # A dict of specific flag=>parent generic flag specific_to_generic = None # A set of /Programs entries, normalised into flag form installed_programs = set() def UseFlags(program=None, postchangehook=None): """Return the set of flags enabled for program""" global global_use_flags_cache, global_program_use_flags_cache global generic_to_specific, specific_to_generic, installed_programs if generic_to_specific is None: _parseGenerics() potentialflags = None if program and program.startswith('/'): catch = False recipedir = str(program) components = os.path.split(recipedir) for comp in components: 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() # When program is specified and a cached set is available, use it. if (program and global_program_use_flags_cache and program in global_program_use_flags_cache): return global_program_use_flags_cache[program] # When a program is *not* specified and a cached global set exists, # use it elif global_use_flags_cache is not None and not program: return global_use_flags_cache # Otherwise, generate the set of flags now. else: goboSettings = getGoboVariable('goboSettings') goboPrograms = getGoboVariable('goboPrograms') flags = set() # Import default use flags if os.path.exists(goboPrograms + '/Scripts/Current/Data/DistUseFlags.conf'): f = open(goboPrograms + '/Scripts/Current/Data/DistUseFlags.conf') _mergeFlags(flags, f, program) f.close() _callhook(postchangehook, 'distflags', flags, potentialflags) # Include all /Programs entries, appropriately normalised, # where they can be overridden in UseFlags.conf. if not installed_programs: for prog in os.listdir(goboPrograms): prog = prog.lower().replace('-', '_') prog = filter(lambda p: p.isalpha() or p.isdigit() or p=='_', prog) installed_programs.add(prog) flags.update(installed_programs) _callhook(postchangehook, 'installed', flags, potentialflags) # Import site-specific use flags if os.path.exists(goboSettings + '/UseFlags.conf'): f = open(goboSettings + '/UseFlags.conf') _mergeFlags(flags, f, program) f.close() _callhook(postchangehook, 'useflags', flags, potentialflags) # Flags set in the environment for a specific run of Compile are # merged, and overwrite any previous specifications for the same flag. if 'USE' in os.environ: _mergeFlags(flags, os.environ['USE'].split(), program) _callhook(postchangehook, 'environ', flags, potentialflags) if potentialflags: for generic in generic_to_specific: if generic in flags: child = False first = None for specific in generic_to_specific[generic]: if specific not in potentialflags: continue # Save the first available flag, but continue # looping in case a later one is enabled specifically. if not first: first = specific if specific in flags: child = True break # When there is no component flag enabled but one of them # is available, enable it now. if not child and first: flags.add(first) _callhook(postchangehook, 'generictospecific', flags, potentialflags) # These should be handled *after* generic-to-specific mapping # occurs, so that there's no e.g. +sqlite->+sql->+mysql for specific in specific_to_generic: if specific in flags: flags.add(specific_to_generic[specific]) _callhook(postchangehook, 'specifictogeneric', flags, potentialflags) # If program is specified, the flags should be saved in the program # cache. Otherwise they are the global flags and can be saved there. if program: if not global_program_use_flags_cache: global_program_use_flags_cache = dict() if potentialflags is not None: flags.intersection_update(potentialflags) flags = frozenset(flags) global_program_use_flags_cache[program] = flags return flags else: global_use_flags_cache = frozenset(flags) return global_use_flags_cache 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() 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 _mergeFlags(flagSet, iterable, program=None): """Merges all the flag specifications from iterable into the set flagSet, relative to program if specified. Calls _modifyFlagSetFromSpec for each element. """ for spec in iterable: _modifyFlagSetFromSpec(spec.strip(), flagSet, program) def _modifyFlagSetFromSpec(specStr, flagSet, program=None): """Update the set flagSet with the flag specification in specStr. Flag specifications are of the form [-+][a-z0-9]+|-* Each flag specification may have a list of programs after it to which it applies. This list of programs can be separated by spaces or @ signs (space is preferred, @ is available for the USE environment variable's sake). """ # specComponents[0] wil be initialised with the flag itself, and [1:] will # contain the list of programs it applies to. specComponents = [] # Split by strudels if present, to allow program-specific specifications # in USE: USE="-bar@baz +foo@baz" if '@' in specStr: specComponents = specStr.split('@') # Otherwise, split by any whitespace. else: specComponents = specStr.split() flagSpec = specComponents[0] # Apply this specification iff there is no program list, or there is and # the given program is in it. if len(specComponents) == 1 or (program and program in specComponents[1:]): if flagSpec == '-*': flagSet.clear() elif flagSpec[0] == '-': flagSet.discard(flagSpec[1:]) elif flagSpec[0] == '+': flagSet.add(flagSpec[1:]) elif flagSpec[0] == '#': pass else: flagSet.add(flagSpec) def _parseGenerics(): """Parse GenericFlags.conf and initialise its module variables. GenericFlags.conf is in the format generic: specific1 specific2... generic_to_specific and specific_to_generic in the global scope will be initialised as dicts in the corresponding direction; generic_to_specific will be string=>iterable, specific_to_generic string=>string """ global generic_to_specific, specific_to_generic fn = os.path.join(getGoboVariable('goboSettings'), 'GenericFlags.conf') generic_to_specific = dict() specific_to_generic = dict() if not os.path.exists(fn): return for line in open(fn): components = line.split() generic = components[0][:-1] specifics = components[1:] generic_to_specific[generic] = specifics for specific in specifics: specific_to_generic[specific] = generic def _callhook(hook, *args): if hook: hook(*args) def _vchook(desc, flags, potentials = None): if not hasattr(_vchook, 'lastflags'): _vchook.lastflags = frozenset() if potentials is not None: flags = flags.intersection(potentials) sys.stdout.write('Added at ' + desc + ': {' + ', '.join(flags-_vchook.lastflags) + '}\n') sys.stdout.write('Removed at ' + desc + ': {' +\ ', '.join(_vchook.lastflags-flags) + '}\n') _vchook.lastflags = flags # Executable code when run from the command line. if __name__ == '__main__': verbose = False args = sys.argv[1:] changehook = None # "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 '--debug' == args[0]: changehook = _vchook args = args[1:] elif args and '--help' == args[0]: sys.stdout.write(""" UseFlags Usage: UseFlags [-v] [ []] When program and flag both specified, the return code is true if the flag is enabled, and false otherwise. -v will output a message as well. 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 len(args): program = args[0] flags = UseFlags(program, postchangehook=changehook) # 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 args[1] in flags: if verbose: sys.stdout.write(args[1] + ' on\n') sys.exit(0) else: if verbose: sys.stdout.write(args[1] + ' off\n') sys.exit(1)