mirror of
https://github.com/mpv-player/mpv
synced 2024-12-21 22:30:22 +00:00
7eca787571
In a bunch of cases, we emulate highly platform specific APIs on a higher level across all OSes, such as IPC, terminal, subprocess handling, and more. We have source files for each OS, and they implement all the same mpv internal API. Selecting which source file to use on an OS can be tricky, because there is partially overlapping and emulated APIs (consider Cygwin on Windows). Add a pick_first_matching_dep() function to make this slightly easier and more structured. Also add dummy backends in some cases, to deal with APIs not being available. Clarify the Windows dependency identifiers, as these are the most confusing.
241 lines
8.1 KiB
Python
241 lines
8.1 KiB
Python
from waflib.Errors import ConfigurationError, WafError
|
|
from waflib.Configure import conf
|
|
from waflib.Build import BuildContext
|
|
from waflib.Logs import pprint
|
|
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)
|
|
for dep_key in ['deps', 'deps_any', 'deps_neg']:
|
|
if dep_key in self.attributes:
|
|
deps = self.attributes[dep_key]
|
|
self.ctx.ensure_dependency_is_known(*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_any_dependencies()
|
|
self.check_dependencies()
|
|
self.check_negative_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_any_dependencies(self):
|
|
if 'deps_any' in self.attributes:
|
|
deps = set(self.attributes['deps_any'])
|
|
if len(deps & self.satisfied_deps) == 0:
|
|
self.skip("not found any of {0}".format(", ".join(deps)))
|
|
raise DependencyError
|
|
|
|
def check_dependencies(self):
|
|
if 'deps' in self.attributes:
|
|
deps = set(self.attributes['deps'])
|
|
if not deps <= self.satisfied_deps:
|
|
missing_deps = deps - self.satisfied_deps
|
|
self.skip("{0} not found".format(", ".join(missing_deps)))
|
|
raise DependencyError
|
|
|
|
def check_negative_dependencies(self):
|
|
if 'deps_neg' in self.attributes:
|
|
deps = set(self.attributes['deps_neg'])
|
|
conflicting_deps = deps & self.satisfied_deps
|
|
if len(conflicting_deps) > 0:
|
|
self.skip("{0} found".format(", ".join(conflicting_deps)), 'CYAN')
|
|
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):
|
|
deps = set([d for d in depnames if not d.startswith('os-')])
|
|
if not deps <= ctx.known_deps:
|
|
raise ConfigurationError(
|
|
"error in dependencies definition: some dependencies in"
|
|
" {0} are unknown.".format(deps))
|
|
|
|
|
|
@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)
|
|
return dependency_identifier in ctx.satisfied_deps
|
|
|
|
@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):
|
|
if dependency.find('!') == 0:
|
|
dependency = dependency.lstrip('!')
|
|
ctx.ensure_dependency_is_known(dependency)
|
|
return dependency not in ctx.satisfied_deps
|
|
else:
|
|
ctx.ensure_dependency_is_known(dependency)
|
|
return dependency in ctx.satisfied_deps
|
|
|
|
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
|