summaryrefslogtreecommitdiffstats
path: root/build/moz.configure/util.configure
diff options
context:
space:
mode:
Diffstat (limited to 'build/moz.configure/util.configure')
-rw-r--r--build/moz.configure/util.configure440
1 files changed, 440 insertions, 0 deletions
diff --git a/build/moz.configure/util.configure b/build/moz.configure/util.configure
new file mode 100644
index 000000000..33ed17de6
--- /dev/null
+++ b/build/moz.configure/util.configure
@@ -0,0 +1,440 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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)