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.configure517
1 files changed, 517 insertions, 0 deletions
diff --git a/build/moz.configure/util.configure b/build/moz.configure/util.configure
new file mode 100644
index 0000000000..f5ff3acd79
--- /dev/null
+++ b/build/moz.configure/util.configure
@@ -0,0 +1,517 @@
+# -*- 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 and return code.
+# Returns a tuple (retcode, stdout, stderr).
+@imports("os")
+@imports("subprocess")
+@imports(_from="mozbuild.shellutil", _import="quote")
+@imports(_from="mozbuild.util", _import="system_encoding")
+def get_cmd_output(*args, **kwargs):
+ log.debug("Executing: `%s`", quote(*args))
+ proc = subprocess.Popen(
+ args,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ # On Python 2 on Windows, close_fds prevents the process from inheriting
+ # stdout/stderr. Elsewhere, it simply prevents it from inheriting extra
+ # file descriptors, which is what we want.
+ close_fds=os.name != "nt",
+ encoding=system_encoding,
+ errors="replace",
+ **kwargs
+ )
+ stdout, stderr = proc.communicate()
+ return proc.wait(), stdout, stderr
+
+
+# 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(_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():
+ retcode, stdout, stderr = get_cmd_output(*args, **kwargs)
+ 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, ctypes.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: `%s`. "
+ "Use `fsutil file setshortname' "
+ "to create a short name "
+ "for any components of this path "
+ "that have spaces.",
+ out.value,
+ )
+ 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("sys")
+@imports(_from="os", _import="pathsep")
+@imports(_from="os", _import="environ")
+@imports(_from="mozfile", _import="which")
+def find_program(file, paths=None, allow_spaces=False):
+ def which_normalize(file, path, exts):
+ path = which(file, path=path, exts=exts)
+ if not path:
+ return None
+ if not allow_spaces:
+ return normalize_path(path)
+ return normsep(path)
+
+ # The following snippet comes from `which` itself, with a slight
+ # modification to use lowercase extensions, because it's confusing rustup
+ # (on top of making results not really appealing to the eye).
+
+ # Windows has the concept of a list of extensions (PATHEXT env var).
+ if sys.platform.startswith("win"):
+ exts = [e.lower() for e in environ.get("PATHEXT", "").split(pathsep)]
+ # If '.exe' is not in exts then obviously this is Win9x and
+ # or a bogus PATHEXT, then use a reasonable default.
+ if ".exe" not in exts:
+ exts = [".com", ".exe", ".bat"]
+ else:
+ exts = None
+
+ if is_absolute_or_relative(file):
+ return which_normalize(
+ os.path.basename(file), path=os.path.dirname(file), exts=exts
+ )
+
+ if paths:
+ if not isinstance(paths, (list, tuple)):
+ die(
+ "Paths provided to find_program must be a list of strings, " "not %r",
+ paths,
+ )
+ paths = pathsep.join(paths)
+
+ return which_normalize(file, path=paths, exts=exts)
+
+
+@imports("os")
+@imports(_from="mozbuild.configure.util", _import="LineIO")
+@imports(_from="tempfile", _import="mkstemp")
+@imports(_import="subprocess")
+def try_invoke_compiler(
+ configure_cache, compiler, language, source, flags=None, onerror=None, wrapper=[]
+):
+ compiler_path = compiler[0]
+ compiler = wrapper + compiler
+ use_cache = True
+
+ if compiler_path not in configure_cache.version_checked_compilers:
+ try:
+ version_info = subprocess.check_output(
+ [compiler_path, "--version"],
+ encoding="UTF-8",
+ ).strip()
+ except subprocess.CalledProcessError:
+ # There's no sane way to use the cache without the version details, so
+ # we need to avoid both reads from and writes to the cache.
+ use_cache = False
+ pass
+
+ if use_cache:
+ if version_info != configure_cache.setdefault(compiler_path, {}).get(
+ "version"
+ ):
+ configure_cache[compiler_path].clear()
+
+ configure_cache[compiler_path]["version"] = version_info
+ configure_cache.version_checked_compilers.add(compiler_path)
+
+ flags = flags or []
+
+ if use_cache:
+ key = " ".join(compiler) + language + source + (" ".join(flags) or "")
+
+ if key in configure_cache[compiler_path]:
+ return configure_cache[compiler_path][key]
+
+ if not isinstance(flags, (list, tuple)):
+ die("Flags provided to try_compile must be a list of strings, " "not %r", flags)
+
+ suffix = {
+ "C": ".c",
+ "C++": ".cpp",
+ }[language]
+
+ fd, path = mkstemp(prefix="conftest.", suffix=suffix, text=True)
+ 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 + [path] + list(flags)
+ kwargs = {"onerror": onerror}
+ val = check_cmd_output(*cmd, **kwargs)
+ if use_cache:
+ configure_cache[compiler_path][key] = val
+ return val
+ 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.
+#
+# The `get_32_and_64_bit` argument is a boolean, if True then it will return the
+# values from the 32-bit and 64-bit registry views. This defaults to False,
+# which will return the view depending on the bitness of python.
+#
+# 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\Installed Roots\KitsRoot8.1',
+# get_32_and_64_bit=True)
+# yields e.g.:
+# (r'C:\Program Files (x86)\Windows Kits\8.1\',)
+# (r'C:\Program Files\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")
+@imports(_from="__builtin__", _import="WindowsError")
+@imports(_from="fnmatch", _import="fnmatch")
+def get_registry_values(pattern, get_32_and_64_bit=False):
+ def enum_helper(func, key):
+ i = 0
+ while True:
+ try:
+ yield func(key, i)
+ except WindowsError:
+ break
+ i += 1
+
+ def get_keys(key, pattern, access_mask):
+ try:
+ s = winreg.OpenKey(key, "\\".join(pattern[:-1]), 0, access_mask)
+ except WindowsError:
+ return
+ for k in enum_helper(winreg.EnumKey, s):
+ if fnmatch(k, pattern[-1]):
+ try:
+ yield k, winreg.OpenKey(s, k, 0, access_mask)
+ except WindowsError:
+ pass
+
+ def get_values(key, pattern, access_mask):
+ try:
+ s = winreg.OpenKey(key, "\\".join(pattern[:-1]), 0, access_mask)
+ 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
+
+ def get_all_values(keys, pattern, access_mask):
+ 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, access_mask):
+ yield matches + ((name, value) if want_name else (value,))
+ else:
+ for name, k in get_keys(base_key, p, access_mask):
+ next_keys.append(matches + (name, k))
+ keys = next_keys
+
+ pattern = pattern.split("\\")
+ assert pattern[0].startswith("HKEY_")
+ keys = [(getattr(winreg, pattern[0]),)]
+ pattern = list(split_pattern(pattern[1:]))
+ if get_32_and_64_bit:
+ for match in get_all_values(
+ keys, pattern, winreg.KEY_READ | winreg.KEY_WOW64_32KEY
+ ):
+ yield match
+ for match in get_all_values(
+ keys, pattern, winreg.KEY_READ | winreg.KEY_WOW64_64KEY
+ ):
+ yield match
+ else:
+ for match in get_all_values(keys, pattern, winreg.KEY_READ):
+ yield match
+
+
+@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)
+ kwargs = {k: v for k, v in kwargs.items() if k == "when"}
+
+ def decorator(func):
+ @depends(opt.option, **kwargs)
+ def deprecated(value):
+ if value.origin != "default":
+ return func(value)
+
+ return deprecated
+
+ return decorator
+
+
+# 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="mozbuild.configure", _import="SandboxDependsFunction")
+def dependable(obj):
+ if isinstance(obj, SandboxDependsFunction):
+ return obj
+ return depends(when=True)(obj)
+
+
+always = dependable(True)
+never = dependable(False)
+
+
+# Create a decorator that will only execute the body of a function
+# if the passed function returns True when passed all positional
+# arguments.
+@template
+def depends_tmpl(eval_args_fn, *args, **kwargs):
+ if kwargs:
+ assert len(kwargs) == 1
+ when = kwargs["when"]
+ else:
+ when = None
+
+ def decorator(func):
+ @depends(*args, when=when)
+ def wrapper(*args):
+ if eval_args_fn(args):
+ return func(*args)
+
+ return wrapper
+
+ return decorator
+
+
+# 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, **kwargs):
+ return depends_tmpl(any, *args, **kwargs)
+
+
+# Like @depends, but the decorated function is only called if all of the
+# arguments it would be called with have a positive value.
+@template
+def depends_all(*args, **kwargs):
+ return depends_tmpl(all, *args, **kwargs)
+
+
+# Hacks related to old-configure
+# ==============================
+
+
+@dependable
+def old_configure_assignments():
+ return []
+
+
+@template
+def add_old_configure_assignment(var, value, when=None):
+ var = dependable(var)
+ value = dependable(value)
+
+ @depends(old_configure_assignments, var, value, when=when)
+ @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((var, "1"))
+ elif value is False:
+ assignments.append((var, ""))
+ else:
+ if isinstance(value, (list, tuple)):
+ value = quote(*value)
+ assignments.append((var, str(value)))