123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440 |
- # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
- # This Source Code Form is subject to the terms of the Mozilla Public
- # License, v. 2.0. If a copy of the MPL was not distributed with this
- # file, You can obtain one at http://mozilla.org/MPL/2.0/.
- @imports('sys')
- def die(*args):
- 'Print an error and terminate configure.'
- log.error(*args)
- sys.exit(1)
- @imports(_from='mozbuild.configure', _import='ConfigureError')
- def configure_error(message):
- '''Raise a programming error and terminate configure.
- Primarily for use in moz.configure templates to sanity check
- their inputs from moz.configure usage.'''
- raise ConfigureError(message)
- # A wrapper to obtain a process' output that returns the output generated
- # by running the given command if it exits normally, and streams that
- # output to log.debug and calls die or the given error callback if it
- # does not.
- @imports('subprocess')
- @imports('sys')
- @imports(_from='mozbuild.configure.util', _import='LineIO')
- @imports(_from='mozbuild.shellutil', _import='quote')
- def check_cmd_output(*args, **kwargs):
- onerror = kwargs.pop('onerror', None)
- with log.queue_debug():
- log.debug('Executing: `%s`', quote(*args))
- proc = subprocess.Popen(args, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- stdout, stderr = proc.communicate()
- retcode = proc.wait()
- if retcode == 0:
- return stdout
- log.debug('The command returned non-zero exit status %d.',
- retcode)
- for out, desc in ((stdout, 'output'), (stderr, 'error output')):
- if out:
- log.debug('Its %s was:', desc)
- with LineIO(lambda l: log.debug('| %s', l)) as o:
- o.write(out)
- if onerror:
- return onerror()
- die('Command `%s` failed with exit status %d.' %
- (quote(*args), retcode))
- @imports('os')
- def is_absolute_or_relative(path):
- if os.altsep and os.altsep in path:
- return True
- return os.sep in path
- @imports(_import='mozpack.path', _as='mozpath')
- def normsep(path):
- return mozpath.normsep(path)
- @imports('ctypes')
- @imports(_from='ctypes', _import='wintypes')
- @imports(_from='mozbuild.configure.constants', _import='WindowsBinaryType')
- def windows_binary_type(path):
- """Obtain the type of a binary on Windows.
- Returns WindowsBinaryType constant.
- """
- GetBinaryTypeW = ctypes.windll.kernel32.GetBinaryTypeW
- GetBinaryTypeW.argtypes = [wintypes.LPWSTR, wintypes.POINTER(wintypes.DWORD)]
- GetBinaryTypeW.restype = wintypes.BOOL
- bin_type = wintypes.DWORD()
- res = GetBinaryTypeW(path, ctypes.byref(bin_type))
- if not res:
- die('could not obtain binary type of %s' % path)
- if bin_type.value == 0:
- return WindowsBinaryType('win32')
- elif bin_type.value == 6:
- return WindowsBinaryType('win64')
- # If we see another binary type, something is likely horribly wrong.
- else:
- die('unsupported binary type on %s: %s' % (path, bin_type))
- @imports('ctypes')
- @imports(_from='ctypes', _import='wintypes')
- def get_GetShortPathNameW():
- GetShortPathNameW = ctypes.windll.kernel32.GetShortPathNameW
- GetShortPathNameW.argtypes = [wintypes.LPCWSTR, wintypes.LPWSTR,
- wintypes.DWORD]
- GetShortPathNameW.restype = wintypes.DWORD
- return GetShortPathNameW
- @template
- @imports('ctypes')
- @imports('platform')
- @imports(_from='mozbuild.shellutil', _import='quote')
- def normalize_path():
- # Until the build system can properly handle programs that need quoting,
- # transform those paths into their short version on Windows (e.g.
- # c:\PROGRA~1...).
- if platform.system() == 'Windows':
- GetShortPathNameW = get_GetShortPathNameW()
- def normalize_path(path):
- path = normsep(path)
- if quote(path) == path:
- return path
- size = 0
- while True:
- out = ctypes.create_unicode_buffer(size)
- needed = GetShortPathNameW(path, out, size)
- if size >= needed:
- if ' ' in out.value:
- die("GetShortPathName returned a long path name. "
- "Are 8dot3 filenames disabled?")
- return normsep(out.value)
- size = needed
- else:
- def normalize_path(path):
- return normsep(path)
- return normalize_path
- normalize_path = normalize_path()
- # Locates the given program using which, or returns the given path if it
- # exists.
- # The `paths` parameter may be passed to search the given paths instead of
- # $PATH.
- @imports(_from='which', _import='which')
- @imports(_from='which', _import='WhichError')
- @imports('itertools')
- @imports(_from='os', _import='pathsep')
- def find_program(file, paths=None):
- try:
- if is_absolute_or_relative(file):
- return normalize_path(which(os.path.basename(file),
- [os.path.dirname(file)]))
- if paths:
- if not isinstance(paths, (list, tuple)):
- die("Paths provided to find_program must be a list of strings, "
- "not %r", paths)
- paths = list(itertools.chain(
- *(p.split(pathsep) for p in paths if p)))
- return normalize_path(which(file, path=paths))
- except WhichError:
- return None
- @imports('os')
- @imports('subprocess')
- @imports(_from='mozbuild.configure.util', _import='LineIO')
- @imports(_from='tempfile', _import='mkstemp')
- def try_invoke_compiler(compiler, language, source, flags=None, onerror=None):
- flags = flags or []
- if not isinstance(flags, (list, tuple)):
- die("Flags provided to try_compile must be a list of strings, "
- "not %r", paths)
- suffix = {
- 'C': '.c',
- 'C++': '.cpp',
- }[language]
- fd, path = mkstemp(prefix='conftest.', suffix=suffix)
- try:
- source = source.encode('ascii', 'replace')
- log.debug('Creating `%s` with content:', path)
- with LineIO(lambda l: log.debug('| %s', l)) as out:
- out.write(source)
- os.write(fd, source)
- os.close(fd)
- cmd = compiler + list(flags) + [path]
- kwargs = {'onerror': onerror}
- return check_cmd_output(*cmd, **kwargs)
- finally:
- os.remove(path)
- def unique_list(l):
- result = []
- for i in l:
- if l not in result:
- result.append(i)
- return result
- # Get values out of the Windows registry. This function can only be called on
- # Windows.
- # The `pattern` argument is a string starting with HKEY_ and giving the full
- # "path" of the registry key to get the value for, with backslash separators.
- # The string can contains wildcards ('*').
- # The result of this functions is an enumerator yielding tuples for each
- # match. Each of these tuples contains the key name matching wildcards
- # followed by the value.
- #
- # Examples:
- # get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
- # r'Windows Kits\Installed Roots\KitsRoot*')
- # yields e.g.:
- # ('KitsRoot81', r'C:\Program Files (x86)\Windows Kits\8.1\')
- # ('KitsRoot10', r'C:\Program Files (x86)\Windows Kits\10\')
- #
- # get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
- # r'Windows Kits\Installed Roots\KitsRoot8.1')
- # yields e.g.:
- # (r'C:\Program Files (x86)\Windows Kits\8.1\',)
- #
- # get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
- # r'Windows Kits\*\KitsRoot*')
- # yields e.g.:
- # ('Installed Roots', 'KitsRoot81',
- # r'C:\Program Files (x86)\Windows Kits\8.1\')
- # ('Installed Roots', 'KitsRoot10',
- # r'C:\Program Files (x86)\Windows Kits\10\')
- #
- # get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
- # r'VisualStudio\VC\*\x86\*\Compiler')
- # yields e.g.:
- # ('19.0', 'arm', r'C:\...\amd64_arm\cl.exe')
- # ('19.0', 'x64', r'C:\...\amd64\cl.exe')
- # ('19.0', 'x86', r'C:\...\amd64_x86\cl.exe')
- @imports(_import='_winreg', _as='winreg')
- @imports(_from='__builtin__', _import='WindowsError')
- @imports(_from='fnmatch', _import='fnmatch')
- def get_registry_values(pattern):
- def enum_helper(func, key):
- i = 0
- while True:
- try:
- yield func(key, i)
- except WindowsError:
- break
- i += 1
- def get_keys(key, pattern):
- try:
- s = winreg.OpenKey(key, '\\'.join(pattern[:-1]))
- except WindowsError:
- return
- for k in enum_helper(winreg.EnumKey, s):
- if fnmatch(k, pattern[-1]):
- try:
- yield k, winreg.OpenKey(s, k)
- except WindowsError:
- pass
- def get_values(key, pattern):
- try:
- s = winreg.OpenKey(key, '\\'.join(pattern[:-1]))
- except WindowsError:
- return
- for k, v, t in enum_helper(winreg.EnumValue, s):
- if fnmatch(k, pattern[-1]):
- yield k, v
- def split_pattern(pattern):
- subpattern = []
- for p in pattern:
- subpattern.append(p)
- if '*' in p:
- yield subpattern
- subpattern = []
- if subpattern:
- yield subpattern
- pattern = pattern.split('\\')
- assert pattern[0].startswith('HKEY_')
- keys = [(getattr(winreg, pattern[0]),)]
- pattern = list(split_pattern(pattern[1:]))
- for i, p in enumerate(pattern):
- next_keys = []
- for base_key in keys:
- matches = base_key[:-1]
- base_key = base_key[-1]
- if i == len(pattern) - 1:
- want_name = '*' in p[-1]
- for name, value in get_values(base_key, p):
- yield matches + ((name, value) if want_name else (value,))
- else:
- for name, k in get_keys(base_key, p):
- next_keys.append(matches + (name, k))
- keys = next_keys
- @imports(_from='mozbuild.configure.util', _import='Version', _as='_Version')
- def Version(v):
- 'A version number that can be compared usefully.'
- return _Version(v)
- # Denotes a deprecated option. Combines option() and @depends:
- # @deprecated_option('--option')
- # def option(value):
- # ...
- # @deprecated_option() takes the same arguments as option(), except `help`.
- # The function may handle the option like a typical @depends function would,
- # but it is recommended it emits a deprecation error message suggesting an
- # alternative option to use if there is one.
- @template
- def deprecated_option(*args, **kwargs):
- assert 'help' not in kwargs
- kwargs['help'] = 'Deprecated'
- opt = option(*args, **kwargs)
- def decorator(func):
- @depends(opt.option)
- def deprecated(value):
- if value.origin != 'default':
- return func(value)
- return deprecated
- return decorator
- # from mozbuild.util import ReadOnlyNamespace as namespace
- @imports(_from='mozbuild.util', _import='ReadOnlyNamespace')
- def namespace(**kwargs):
- return ReadOnlyNamespace(**kwargs)
- # Turn an object into an object that can be used as an argument to @depends.
- # The given object can be a literal value, a function that takes no argument,
- # or, for convenience, a @depends function.
- @template
- @imports(_from='inspect', _import='isfunction')
- @imports(_from='mozbuild.configure', _import='SandboxDependsFunction')
- def dependable(obj):
- if isinstance(obj, SandboxDependsFunction):
- return obj
- if isfunction(obj):
- return depends(when=True)(obj)
- return depends(when=True)(lambda: obj)
- always = dependable(True)
- never = dependable(False)
- # Some @depends function return namespaces, and one could want to use one
- # specific attribute from such a namespace as a "value" given to functions
- # such as `set_config`. But those functions do not take immediate values.
- # The `delayed_getattr` function allows access to attributes from the result
- # of a @depends function in a non-immediate manner.
- # @depends('--option')
- # def option(value)
- # return namespace(foo=value)
- # set_config('FOO', delayed_getattr(option, 'foo')
- @template
- def delayed_getattr(func, key):
- @depends(func)
- def result(value):
- # The @depends function we're being passed may have returned
- # None, or an object that simply doesn't have the wanted key.
- # In that case, just return None.
- return getattr(value, key, None)
- return result
- # Like @depends, but the decorated function is only called if one of the
- # arguments it would be called with has a positive value (bool(value) is True)
- @template
- def depends_if(*args):
- def decorator(func):
- @depends(*args)
- def wrapper(*args):
- if any(arg for arg in args):
- return func(*args)
- return wrapper
- return decorator
- # Like @depends_if, but a distinguished value passed as a keyword argument
- # "when" is truth tested instead of every argument. This value is not passed
- # to the function if it is called.
- @template
- def depends_when(*args, **kwargs):
- if not len(kwargs) == 1 and kwargs.get('when'):
- die('depends_when requires a single keyword argument, "when"')
- when = kwargs['when']
- if not when:
- return depends(*args)
- def decorator(fn):
- @depends(when, *args)
- def wrapper(val, *args):
- if val:
- return fn(*args)
- return wrapper
- return decorator
- # Hacks related to old-configure
- # ==============================
- @dependable
- def old_configure_assignments():
- return []
- @dependable
- def extra_old_configure_args():
- return []
- @template
- def add_old_configure_assignment(var, value):
- var = dependable(var)
- value = dependable(value)
- @depends(old_configure_assignments, var, value)
- @imports(_from='mozbuild.shellutil', _import='quote')
- def add_assignment(assignments, var, value):
- if var is None or value is None:
- return
- if value is True:
- assignments.append('%s=1' % var)
- elif value is False:
- assignments.append('%s=' % var)
- else:
- if isinstance(value, (list, tuple)):
- value = quote(*value)
- assignments.append('%s=%s' % (var, quote(str(value))))
- @template
- def add_old_configure_arg(arg):
- @depends(extra_old_configure_args, arg)
- def add_arg(args, arg):
- if arg:
- args.append(arg)
|