зеркало из https://github.com/mozilla/gecko-dev.git
489 строки
16 KiB
Python
489 строки
16 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("six")
|
|
@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",
|
|
**kwargs
|
|
)
|
|
stdout, stderr = proc.communicate()
|
|
# Normally we would set the `encoding` and `errors` arguments in the
|
|
# constructor to subprocess.Popen, but those arguments were added in 3.6
|
|
# and we need to support back to 3.5, so instead we need to do this
|
|
# nonsense.
|
|
stdout = six.ensure_text(
|
|
stdout, encoding=system_encoding, errors="replace"
|
|
).replace("\r\n", "\n")
|
|
stderr = six.ensure_text(
|
|
stderr, encoding=system_encoding, errors="replace"
|
|
).replace("\r\n", "\n")
|
|
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):
|
|
# 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):
|
|
path = which(os.path.basename(file), path=os.path.dirname(file), exts=exts)
|
|
return normalize_path(path) if path else None
|
|
|
|
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)
|
|
|
|
path = which(file, path=paths, exts=exts)
|
|
return normalize_path(path) if path else None
|
|
|
|
|
|
@imports("os")
|
|
@imports(_from="mozbuild.configure.util", _import="LineIO")
|
|
@imports(_from="six", _import="ensure_binary")
|
|
@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", 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, ensure_binary(source))
|
|
os.close(fd)
|
|
cmd = compiler + [path] + list(flags)
|
|
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.
|
|
#
|
|
# 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)
|
|
|
|
def decorator(func):
|
|
@depends(opt.option)
|
|
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="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)
|
|
# Depend on --help to make lint happy if the dependable is used as an input
|
|
# to an option().
|
|
return depends("--help", when=True)(lambda _: 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)))
|