mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-30 16:22:00 +00:00
e5ded960e9
Differential Revision: https://phabricator.services.mozilla.com/D214087
576 lines
19 KiB
Python
576 lines
19 KiB
Python
# -*- 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:
|
|
with LineIO(lambda l: log.debug("| %s", l)) as o:
|
|
o.write(stderr)
|
|
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()
|
|
|
|
|
|
@template
|
|
@imports(_from="tempfile", _import="mkstemp")
|
|
@imports("os")
|
|
@imports(_from="contextlib", _import="contextmanager")
|
|
@imports(_from="__builtin__", _import="FileNotFoundError")
|
|
def make_create_temporary_file():
|
|
@contextmanager
|
|
def create_temporary_file(suffix):
|
|
fd, path = mkstemp(prefix="conftest", suffix=suffix)
|
|
os.close(fd)
|
|
yield path
|
|
try:
|
|
os.remove(path)
|
|
except FileNotFoundError:
|
|
pass
|
|
|
|
return create_temporary_file
|
|
|
|
|
|
create_temporary_file = make_create_temporary_file()
|
|
|
|
|
|
# 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="__builtin__", _import="open")
|
|
@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 = configure_cache is not None
|
|
|
|
if use_cache and 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]
|
|
|
|
with create_temporary_file(suffix=suffix) as path:
|
|
bsource = source.encode("ascii", "replace")
|
|
|
|
log.debug("Creating `%s` with content:", path)
|
|
with LineIO(lambda l: log.debug("| %s", l)) as out:
|
|
out.write(bsource)
|
|
|
|
with open(path, "w") as fd:
|
|
fd.write(source)
|
|
|
|
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
|
|
|
|
|
|
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)
|
|
|
|
|
|
# A template providing a shorthand for setting a variable. The created
|
|
# option will only be settable with imply_option.
|
|
# It is expected that a project-specific moz.configure will call imply_option
|
|
# to set a value other than the default.
|
|
# If required, the set_as_define argument will additionally cause the variable
|
|
# to be set using set_define.
|
|
@template
|
|
def project_flag(env=None, set_as_define=False, **kwargs):
|
|
if not env:
|
|
configure_error("A project_flag must be passed a variable name to set.")
|
|
|
|
if kwargs.get("nargs", 0) not in (0, 1):
|
|
configure_error("A project_flag must be passed nargs={0,1}.")
|
|
|
|
opt = option(env=env, possible_origins=("implied",), **kwargs)
|
|
|
|
@depends(opt.option)
|
|
def option_implementation(value):
|
|
if value:
|
|
if len(value) == 1:
|
|
return value[0]
|
|
elif len(value):
|
|
return value
|
|
return bool(value)
|
|
|
|
set_config(env, option_implementation)
|
|
if set_as_define:
|
|
set_define(env, option_implementation)
|
|
|
|
|
|
@template
|
|
@imports(_from="mozbuild.configure.constants", _import="RaiseErrorOnUse")
|
|
def obsolete_config(name, *, replacement):
|
|
set_config(name, RaiseErrorOnUse(f"{name} is obsolete. Use {replacement} instead."))
|
|
|
|
|
|
# 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)))
|