from waflib.Errors import ConfigurationError, WafError from waflib.Configure import conf from waflib.Build import BuildContext from waflib.Logs import pprint import deps_parser import inflector class DependencyError(Exception): pass class Dependency(object): def __init__(self, ctx, known_deps, satisfied_deps, dependency): self.ctx = ctx self.known_deps = known_deps self.satisfied_deps = satisfied_deps self.identifier, self.desc = dependency['name'], dependency['desc'] self.attributes = self.__parse_attributes__(dependency) known_deps.add(self.identifier) if 'deps' in self.attributes: self.ctx.ensure_dependency_is_known(self.attributes['deps']) def __parse_attributes__(self, dependency): if 'os_specific_checks' in dependency: all_chks = dependency['os_specific_checks'] chks = [check for check in all_chks if check in self.satisfied_deps] if any(chks): return all_chks[chks[0]] return dependency def check(self): self.ctx.start_msg('Checking for {0}'.format(self.desc)) try: self.check_group_disabled() self.check_disabled() self.check_dependencies() except DependencyError: # No check was run, since the prerequisites of the dependency are # not satisfied. Make sure the define is 'undefined' so that we # get a `#define YYY 0` in `config.h`. self.ctx.undefine(inflector.define_key(self.identifier)) self.fatal_if_needed() return self.check_autodetect_func() def check_group_disabled(self): if 'groups' in self.attributes: groups = self.attributes['groups'] disabled = (self.enabled_option(g) == False for g in groups) if any(disabled): self.skip() raise DependencyError def check_disabled(self): if self.enabled_option() == False: self.skip() raise DependencyError if self.enabled_option() == True: self.attributes['req'] = True self.attributes['fmsg'] = "You manually enabled the feature '{0}', but \ the autodetection check failed.".format(self.identifier) def check_dependencies(self): if 'deps' in self.attributes: ok, why = deps_parser.check_dependency_expr(self.attributes['deps'], self.satisfied_deps) if not ok: self.skip(why) raise DependencyError def check_autodetect_func(self): if self.attributes['func'](self.ctx, self.identifier): self.success(self.identifier) else: self.fail() self.ctx.undefine(inflector.define_key(self.identifier)) self.fatal_if_needed() def enabled_option(self, identifier=None): try: return getattr(self.ctx.options, self.enabled_option_repr(identifier)) except AttributeError: pass return None def enabled_option_repr(self, identifier): return "enable_{0}".format(identifier or self.identifier) def success(self, depname): self.ctx.mark_satisfied(depname) self.ctx.end_msg(self.__message__('yes')) def fail(self, reason='no'): self.ctx.end_msg(self.__message__(reason), 'RED') def fatal_if_needed(self): if self.enabled_option() == False: return if self.attributes.get('req', False): raise ConfigurationError(self.attributes.get('fmsg', 'Unsatisfied requirement')) def skip(self, reason='disabled', color='YELLOW'): self.ctx.end_msg(self.__message__(reason), color) def __message__(self, message): optional_message = self.ctx.deps_msg.get(self.identifier) if optional_message: return "{0} ({1})".format(message, optional_message) else: return message def configure(ctx): def __detect_target_os_dependency__(ctx): target = "os-{0}".format(ctx.env.DEST_OS) ctx.start_msg('Detected target OS:') ctx.end_msg(target) ctx.known_deps.add(target) ctx.satisfied_deps.add(target) ctx.deps_msg = {} ctx.known_deps = set() ctx.satisfied_deps = set() __detect_target_os_dependency__(ctx) @conf def ensure_dependency_is_known(ctx, depnames): def check(ast): if isinstance(ast, deps_parser.AstSym): if (not ast.name.startswith('os-')) and ast.name not in ctx.known_deps: raise ConfigurationError( "error in dependencies definition: dependency {0} in" " {1} is unknown.".format(ast.name, depnames)) elif isinstance(ast, deps_parser.AstOp): for sub in ast.sub: check(sub) else: assert False check(deps_parser.parse_expr(depnames)) @conf def mark_satisfied(ctx, dependency_identifier): ctx.satisfied_deps.add(dependency_identifier) @conf def add_optional_message(ctx, dependency_identifier, message): ctx.deps_msg[dependency_identifier] = message @conf def parse_dependencies(ctx, dependencies): def __check_dependency__(ctx, dependency): Dependency(ctx, ctx.known_deps, ctx.satisfied_deps, dependency).check() [__check_dependency__(ctx, dependency) for dependency in dependencies] @conf def dependency_satisfied(ctx, dependency_identifier): ctx.ensure_dependency_is_known(dependency_identifier) ok, _ = deps_parser.check_dependency_expr(dependency_identifier, ctx.satisfied_deps) return ok @conf def store_dependencies_lists(ctx): ctx.env.known_deps = list(ctx.known_deps) ctx.env.satisfied_deps = list(ctx.satisfied_deps) @conf def unpack_dependencies_lists(ctx): ctx.known_deps = set(ctx.env.known_deps) ctx.satisfied_deps = set(ctx.env.satisfied_deps) def filtered_sources(ctx, sources): def __source_file__(source): if isinstance(source, tuple): return source[0] else: return source def __check_filter__(dependency): return dependency_satisfied(ctx, dependency) def __unpack_and_check_filter__(source): try: _, dependency = source return __check_filter__(dependency) except ValueError: return True return [__source_file__(source) for source in sources \ if __unpack_and_check_filter__(source)] """ Like filtered_sources(), but pick only the first entry that matches, and return its filename. """ def pick_first_matching_dep(ctx, deps): files = filtered_sources(ctx, deps) if len(files) > 0: return files[0] else: raise DependencyError def env_fetch(tx): def fn(ctx): deps = ctx.env.satisfied_deps lists = [ctx.env[tx(dep)] for dep in deps if (tx(dep) in ctx.env)] return [item for sublist in lists for item in sublist] return fn def dependencies_use(ctx): return [inflector.storage_key(dep) for dep in ctx.env.satisfied_deps] BuildContext.filtered_sources = filtered_sources BuildContext.pick_first_matching_dep = pick_first_matching_dep BuildContext.dependencies_use = dependencies_use BuildContext.dependencies_includes = env_fetch(lambda x: "INCLUDES_{0}".format(x)) BuildContext.dependency_satisfied = dependency_satisfied