summaryrefslogtreecommitdiffstats
path: root/python/mozbuild/mozbuild/test/configure
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /python/mozbuild/mozbuild/test/configure
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'python/mozbuild/mozbuild/test/configure')
-rw-r--r--python/mozbuild/mozbuild/test/configure/common.py307
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/decorators.configure53
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/empty_mozconfig0
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/extra.configure15
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/imply_option/imm.configure37
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/imply_option/infer.configure28
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/imply_option/infer_ko.configure36
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/imply_option/negative.configure40
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/imply_option/simple.configure28
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/imply_option/values.configure28
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/included.configure68
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/moz.configure205
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/set_config.configure51
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/set_define.configure51
-rw-r--r--python/mozbuild/mozbuild/test/configure/data/subprocess.configure24
-rw-r--r--python/mozbuild/mozbuild/test/configure/lint.py62
-rw-r--r--python/mozbuild/mozbuild/test/configure/macos_fake_sdk/SDKSettings.plist8
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_bootstrap.py43
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_checks_configure.py1169
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_compile_checks.py599
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_configure.py1986
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_lint.py487
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_moz_configure.py185
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_options.py905
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_toolchain_configure.py2056
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_toolchain_helpers.py433
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_toolkit_moz_configure.py102
-rw-r--r--python/mozbuild/mozbuild/test/configure/test_util.py539
28 files changed, 9545 insertions, 0 deletions
diff --git a/python/mozbuild/mozbuild/test/configure/common.py b/python/mozbuild/mozbuild/test/configure/common.py
new file mode 100644
index 0000000000..7dc1b85b22
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/common.py
@@ -0,0 +1,307 @@
+# 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/.
+
+import copy
+import errno
+import os
+import subprocess
+import sys
+import tempfile
+import unittest
+
+import six
+from buildconfig import topobjdir, topsrcdir
+from mozpack import path as mozpath
+from six import StringIO, string_types
+
+from mozbuild.configure import ConfigureSandbox
+from mozbuild.util import ReadOnlyNamespace, memoized_property
+
+
+def fake_short_path(path):
+ if sys.platform.startswith("win"):
+ return "/".join(
+ p.split(" ", 1)[0] + "~1" if " " in p else p for p in mozpath.split(path)
+ )
+ return path
+
+
+def ensure_exe_extension(path):
+ if sys.platform.startswith("win"):
+ return path + ".exe"
+ return path
+
+
+class ConfigureTestVFS(object):
+ def __init__(self, paths):
+ self._paths = set(mozpath.abspath(p) for p in paths)
+
+ def _real_file(self, path):
+ return mozpath.basedir(path, [topsrcdir, topobjdir, tempfile.gettempdir()])
+
+ def exists(self, path):
+ if path in self._paths:
+ return True
+ if self._real_file(path):
+ return os.path.exists(path)
+ return False
+
+ def isfile(self, path):
+ path = mozpath.abspath(path)
+ if path in self._paths:
+ return True
+ if self._real_file(path):
+ return os.path.isfile(path)
+ return False
+
+ def expanduser(self, path):
+ return os.path.expanduser(path)
+
+ def isdir(self, path):
+ path = mozpath.abspath(path)
+ if any(mozpath.basedir(mozpath.dirname(p), [path]) for p in self._paths):
+ return True
+ if self._real_file(path):
+ return os.path.isdir(path)
+ return False
+
+ def getsize(self, path):
+ if not self._real_file(path):
+ raise FileNotFoundError(path)
+ return os.path.getsize(path)
+
+
+class ConfigureTestSandbox(ConfigureSandbox):
+ """Wrapper around the ConfigureSandbox for testing purposes.
+
+ Its arguments are the same as ConfigureSandbox, except for the additional
+ `paths` argument, which is a dict where the keys are file paths and the
+ values are either None or a function that will be called when the sandbox
+ calls an implemented function from subprocess with the key as command.
+ When the command is CONFIG_SHELL, the function for the path of the script
+ that follows will be called.
+
+ The API for those functions is:
+ retcode, stdout, stderr = func(stdin, args)
+
+ This class is only meant to implement the minimal things to make
+ moz.configure testing possible. As such, it takes shortcuts.
+ """
+
+ def __init__(self, paths, config, environ, *args, **kwargs):
+ self._search_path = environ.get("PATH", "").split(os.pathsep)
+
+ self._subprocess_paths = {
+ mozpath.abspath(k): v for k, v in six.iteritems(paths) if v
+ }
+
+ paths = list(paths)
+
+ environ = copy.copy(environ)
+ if "CONFIG_SHELL" not in environ:
+ environ["CONFIG_SHELL"] = mozpath.abspath("/bin/sh")
+ self._subprocess_paths[environ["CONFIG_SHELL"]] = self.shell
+ paths.append(environ["CONFIG_SHELL"])
+ self._subprocess_paths[
+ mozpath.join(topsrcdir, "build/win32/vswhere.exe")
+ ] = self.vswhere
+
+ vfs = ConfigureTestVFS(paths)
+
+ os_path = {k: getattr(vfs, k) for k in dir(vfs) if not k.startswith("_")}
+
+ os_path.update(self.OS.path.__dict__)
+
+ os_contents = {}
+ exec("from os import *", {}, os_contents)
+ os_contents["path"] = ReadOnlyNamespace(**os_path)
+ os_contents["environ"] = dict(environ)
+ self.imported_os = ReadOnlyNamespace(**os_contents)
+
+ super(ConfigureTestSandbox, self).__init__(config, environ, *args, **kwargs)
+
+ @memoized_property
+ def _wrapped_mozfile(self):
+ return ReadOnlyNamespace(which=self.which)
+
+ @memoized_property
+ def _wrapped_os(self):
+ return self.imported_os
+
+ @memoized_property
+ def _wrapped_subprocess(self):
+ return ReadOnlyNamespace(
+ CalledProcessError=subprocess.CalledProcessError,
+ check_output=self.check_output,
+ PIPE=subprocess.PIPE,
+ STDOUT=subprocess.STDOUT,
+ Popen=self.Popen,
+ )
+
+ @memoized_property
+ def _wrapped_ctypes(self):
+ class CTypesFunc(object):
+ def __init__(self, func):
+ self._func = func
+
+ def __call__(self, *args, **kwargs):
+ return self._func(*args, **kwargs)
+
+ return ReadOnlyNamespace(
+ create_unicode_buffer=self.create_unicode_buffer,
+ windll=ReadOnlyNamespace(
+ kernel32=ReadOnlyNamespace(
+ GetShortPathNameW=CTypesFunc(self.GetShortPathNameW)
+ )
+ ),
+ wintypes=ReadOnlyNamespace(LPCWSTR=0, LPWSTR=1, DWORD=2),
+ )
+
+ @memoized_property
+ def _wrapped__winreg(self):
+ def OpenKey(*args, **kwargs):
+ raise WindowsError()
+
+ return ReadOnlyNamespace(HKEY_LOCAL_MACHINE=0, OpenKey=OpenKey)
+
+ def create_unicode_buffer(self, *args, **kwargs):
+ class Buffer(object):
+ def __init__(self):
+ self.value = ""
+
+ return Buffer()
+
+ def GetShortPathNameW(self, path_in, path_out, length):
+ path_out.value = fake_short_path(path_in)
+ return length
+
+ def which(self, command, mode=None, path=None, exts=None):
+ if isinstance(path, string_types):
+ path = path.split(os.pathsep)
+
+ for parent in path or self._search_path:
+ c = mozpath.abspath(mozpath.join(parent, command))
+ for candidate in (c, ensure_exe_extension(c)):
+ if self.imported_os.path.exists(candidate):
+ return candidate
+ return None
+
+ def Popen(self, args, stdin=None, stdout=None, stderr=None, **kargs):
+ program = self.which(args[0])
+ if not program:
+ raise OSError(errno.ENOENT, "File not found")
+
+ func = self._subprocess_paths.get(program)
+ retcode, stdout, stderr = func(stdin, args[1:])
+
+ class Process(object):
+ def communicate(self, stdin=None):
+ return stdout, stderr
+
+ def wait(self):
+ return retcode
+
+ return Process()
+
+ def check_output(self, args, **kwargs):
+ proc = self.Popen(args, **kwargs)
+ stdout, stderr = proc.communicate()
+ retcode = proc.wait()
+ if retcode:
+ raise subprocess.CalledProcessError(retcode, args, stdout)
+ return stdout
+
+ def shell(self, stdin, args):
+ script = mozpath.abspath(args[0])
+ if script in self._subprocess_paths:
+ return self._subprocess_paths[script](stdin, args[1:])
+ return 127, "", "File not found"
+
+ def vswhere(self, stdin, args):
+ return 0, "[]", ""
+
+ def get_config(self, name):
+ # Like the loop in ConfigureSandbox.run, but only execute the code
+ # associated with the given config item.
+ for func, args in self._execution_queue:
+ if (
+ func == self._resolve_and_set
+ and args[0] is self._config
+ and args[1] == name
+ ):
+ func(*args)
+ return self._config.get(name)
+
+
+class BaseConfigureTest(unittest.TestCase):
+ HOST = "x86_64-pc-linux-gnu"
+
+ def setUp(self):
+ self._cwd = os.getcwd()
+ os.chdir(topobjdir)
+
+ def tearDown(self):
+ os.chdir(self._cwd)
+
+ def config_guess(self, stdin, args):
+ return 0, self.HOST, ""
+
+ def config_sub(self, stdin, args):
+ return 0, args[0], ""
+
+ def get_sandbox(
+ self,
+ paths,
+ config,
+ args=[],
+ environ={},
+ mozconfig="",
+ out=None,
+ logger=None,
+ cls=ConfigureTestSandbox,
+ ):
+ kwargs = {}
+ if logger:
+ kwargs["logger"] = logger
+ else:
+ if not out:
+ out = StringIO()
+ kwargs["stdout"] = out
+ kwargs["stderr"] = out
+
+ if hasattr(self, "TARGET"):
+ target = ["--target=%s" % self.TARGET]
+ else:
+ target = []
+
+ if mozconfig:
+ fh, mozconfig_path = tempfile.mkstemp(text=True)
+ os.write(fh, six.ensure_binary(mozconfig))
+ os.close(fh)
+ else:
+ mozconfig_path = os.path.join(
+ os.path.dirname(__file__), "data", "empty_mozconfig"
+ )
+
+ try:
+ environ = dict(
+ environ,
+ OLD_CONFIGURE=os.path.join(topsrcdir, "old-configure"),
+ MOZCONFIG=mozconfig_path,
+ )
+
+ paths = dict(paths)
+ autoconf_dir = mozpath.join(topsrcdir, "build", "autoconf")
+ paths[mozpath.join(autoconf_dir, "config.guess")] = self.config_guess
+ paths[mozpath.join(autoconf_dir, "config.sub")] = self.config_sub
+
+ sandbox = cls(
+ paths, config, environ, ["configure"] + target + args, **kwargs
+ )
+ sandbox.include_file(os.path.join(topsrcdir, "moz.configure"))
+
+ return sandbox
+ finally:
+ if mozconfig:
+ os.remove(mozconfig_path)
diff --git a/python/mozbuild/mozbuild/test/configure/data/decorators.configure b/python/mozbuild/mozbuild/test/configure/data/decorators.configure
new file mode 100644
index 0000000000..b98eb26f3f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/decorators.configure
@@ -0,0 +1,53 @@
+# -*- 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/.
+
+
+@template
+def simple_decorator(func):
+ return func
+
+
+@template
+def wrapper_decorator(func):
+ def wrapper(*args, **kwargs):
+ return func(*args, **kwargs)
+
+ return wrapper
+
+
+@template
+def function_decorator(*args, **kwargs):
+ # We could return wrapper_decorator from above here, but then we wouldn't
+ # know if this works as expected because wrapper_decorator itself was
+ # modified or because the right thing happened here.
+ def wrapper_decorator(func):
+ def wrapper(*args, **kwargs):
+ return func(*args, **kwargs)
+
+ return wrapper
+
+ return wrapper_decorator
+
+
+@depends("--help")
+@simple_decorator
+def foo(help):
+ global FOO
+ FOO = 1
+
+
+@depends("--help")
+@wrapper_decorator
+def bar(help):
+ global BAR
+ BAR = 1
+
+
+@depends("--help")
+@function_decorator("a", "b", "c")
+def qux(help):
+ global QUX
+ QUX = 1
diff --git a/python/mozbuild/mozbuild/test/configure/data/empty_mozconfig b/python/mozbuild/mozbuild/test/configure/data/empty_mozconfig
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/empty_mozconfig
diff --git a/python/mozbuild/mozbuild/test/configure/data/extra.configure b/python/mozbuild/mozbuild/test/configure/data/extra.configure
new file mode 100644
index 0000000000..e54a93dbc3
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/extra.configure
@@ -0,0 +1,15 @@
+# -*- 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/.
+
+option("--extra", help="Extra")
+
+
+@depends("--extra")
+def extra(extra):
+ return extra
+
+
+set_config("EXTRA", extra)
diff --git a/python/mozbuild/mozbuild/test/configure/data/imply_option/imm.configure b/python/mozbuild/mozbuild/test/configure/data/imply_option/imm.configure
new file mode 100644
index 0000000000..f20a4a7149
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/imply_option/imm.configure
@@ -0,0 +1,37 @@
+# -*- 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/.
+
+imply_option("--enable-foo", True)
+
+option("--enable-foo", help="enable foo")
+
+
+@depends("--enable-foo", "--help")
+def foo(value, help):
+ if value:
+ return True
+
+
+imply_option("--enable-bar", ("foo", "bar"))
+
+option("--enable-bar", nargs="*", help="enable bar")
+
+
+@depends("--enable-bar")
+def bar(value):
+ if value:
+ return value
+
+
+imply_option("--enable-baz", "BAZ")
+
+option("--enable-baz", nargs=1, help="enable baz")
+
+
+@depends("--enable-baz")
+def bar(value):
+ if value:
+ return value
diff --git a/python/mozbuild/mozbuild/test/configure/data/imply_option/infer.configure b/python/mozbuild/mozbuild/test/configure/data/imply_option/infer.configure
new file mode 100644
index 0000000000..b73be9a720
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/imply_option/infer.configure
@@ -0,0 +1,28 @@
+# -*- 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/.
+
+option("--enable-foo", help="enable foo")
+
+
+@depends("--enable-foo", "--help")
+def foo(value, help):
+ if value:
+ return True
+
+
+imply_option("--enable-bar", foo)
+
+
+option("--enable-bar", help="enable bar")
+
+
+@depends("--enable-bar")
+def bar(value):
+ if value:
+ return value
+
+
+set_config("BAR", bar)
diff --git a/python/mozbuild/mozbuild/test/configure/data/imply_option/infer_ko.configure b/python/mozbuild/mozbuild/test/configure/data/imply_option/infer_ko.configure
new file mode 100644
index 0000000000..9b3761c3c3
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/imply_option/infer_ko.configure
@@ -0,0 +1,36 @@
+# -*- 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/.
+
+option("--enable-hoge", help="enable hoge")
+
+
+@depends("--enable-hoge")
+def hoge(value):
+ return value
+
+
+option("--enable-foo", help="enable foo")
+
+
+@depends("--enable-foo", hoge)
+def foo(value, hoge):
+ if value:
+ return True
+
+
+imply_option("--enable-bar", foo)
+
+
+option("--enable-bar", help="enable bar")
+
+
+@depends("--enable-bar")
+def bar(value):
+ if value:
+ return value
+
+
+set_config("BAR", bar)
diff --git a/python/mozbuild/mozbuild/test/configure/data/imply_option/negative.configure b/python/mozbuild/mozbuild/test/configure/data/imply_option/negative.configure
new file mode 100644
index 0000000000..e953231f5e
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/imply_option/negative.configure
@@ -0,0 +1,40 @@
+# -*- 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/.
+
+option("--enable-foo", help="enable foo")
+
+
+@depends("--enable-foo")
+def foo(value):
+ if value:
+ return False
+
+
+imply_option("--enable-bar", foo)
+
+
+option("--disable-hoge", help="enable hoge")
+
+
+@depends("--disable-hoge")
+def hoge(value):
+ if not value:
+ return False
+
+
+imply_option("--enable-bar", hoge)
+
+
+option("--enable-bar", default=True, help="enable bar")
+
+
+@depends("--enable-bar")
+def bar(value):
+ if not value:
+ return value
+
+
+set_config("BAR", bar)
diff --git a/python/mozbuild/mozbuild/test/configure/data/imply_option/simple.configure b/python/mozbuild/mozbuild/test/configure/data/imply_option/simple.configure
new file mode 100644
index 0000000000..6aa225cc45
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/imply_option/simple.configure
@@ -0,0 +1,28 @@
+# -*- 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/.
+
+option("--enable-foo", help="enable foo")
+
+
+@depends("--enable-foo")
+def foo(value):
+ if value:
+ return True
+
+
+imply_option("--enable-bar", foo)
+
+
+option("--enable-bar", help="enable bar")
+
+
+@depends("--enable-bar")
+def bar(value):
+ if value:
+ return value
+
+
+set_config("BAR", bar)
diff --git a/python/mozbuild/mozbuild/test/configure/data/imply_option/values.configure b/python/mozbuild/mozbuild/test/configure/data/imply_option/values.configure
new file mode 100644
index 0000000000..93198a8295
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/imply_option/values.configure
@@ -0,0 +1,28 @@
+# -*- 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/.
+
+option("--enable-foo", nargs="*", help="enable foo")
+
+
+@depends("--enable-foo")
+def foo(value):
+ if value:
+ return value
+
+
+imply_option("--enable-bar", foo)
+
+
+option("--enable-bar", nargs="*", help="enable bar")
+
+
+@depends("--enable-bar")
+def bar(value):
+ if value:
+ return value
+
+
+set_config("BAR", bar)
diff --git a/python/mozbuild/mozbuild/test/configure/data/included.configure b/python/mozbuild/mozbuild/test/configure/data/included.configure
new file mode 100644
index 0000000000..97166618ec
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/included.configure
@@ -0,0 +1,68 @@
+# -*- 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/.
+
+# For more complex and repetitive things, we can create templates
+@template
+def check_compiler_flag(flag):
+ @depends(is_gcc)
+ def check(value):
+ if value:
+ return [flag]
+
+ set_config("CFLAGS", check)
+ return check
+
+
+check_compiler_flag("-Werror=foobar")
+
+# Normal functions can be used in @depends functions.
+def fortytwo():
+ return 42
+
+
+def twentyone():
+ yield 21
+
+
+@depends(is_gcc)
+def check(value):
+ if value:
+ return fortytwo()
+
+
+set_config("TEMPLATE_VALUE", check)
+
+
+@depends(is_gcc)
+def check(value):
+ if value:
+ for val in twentyone():
+ return val
+
+
+set_config("TEMPLATE_VALUE_2", check)
+
+# Normal functions can use @imports too to import modules.
+@imports("sys")
+def platform():
+ return sys.platform
+
+
+option("--enable-imports-in-template", help="Imports in template")
+
+
+@depends("--enable-imports-in-template")
+def check(value):
+ if value:
+ return platform()
+
+
+set_config("PLATFORM", check)
+
+
+@template
+def indirectly_define_option(*args, **kwargs):
+ option(*args, **kwargs)
diff --git a/python/mozbuild/mozbuild/test/configure/data/moz.configure b/python/mozbuild/mozbuild/test/configure/data/moz.configure
new file mode 100644
index 0000000000..4d57eabbb9
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/moz.configure
@@ -0,0 +1,205 @@
+# -*- 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/.
+
+option("--enable-simple", help="Enable simple")
+
+# Setting MOZ_WITH_ENV in the environment has the same effect as passing
+# --enable-with-env.
+option("--enable-with-env", env="MOZ_WITH_ENV", help="Enable with env")
+
+# Optional values
+option("--enable-values", nargs="*", help="Enable values")
+
+# Everything supported in the Option class is supported in option(). Assume
+# the tests of the Option class are extensive about this.
+
+# Alternatively to --enable/--disable, there also is --with/--without. The
+# difference is semantic only. Behavior is the same as --enable/--disable.
+
+# When the option name starts with --disable/--without, the default is for
+# the option to be enabled.
+option("--without-thing", help="Build without thing")
+
+# A --enable/--with option with a default of False is equivalent to a
+# --disable/--without option. This can be used to change the defaults
+# depending on e.g. the target or the built application.
+option("--with-stuff", default=False, help="Build with stuff")
+
+# Other kinds of arbitrary options are also allowed. This is effectively
+# equivalent to --enable/--with, with no possibility of --disable/--without.
+option("--option", env="MOZ_OPTION", help="Option")
+
+# It is also possible to pass options through the environment only.
+option(env="CC", nargs=1, help="C Compiler")
+
+# Call the function when the --enable-simple option is processed, with its
+# OptionValue as argument.
+@depends("--enable-simple")
+def simple(simple):
+ if simple:
+ return simple
+
+
+set_config("ENABLED_SIMPLE", simple)
+
+# There can be multiple functions depending on the same option.
+@depends("--enable-simple")
+def simple(simple):
+ return simple
+
+
+set_config("SIMPLE", simple)
+
+
+@depends("--enable-with-env")
+def with_env(with_env):
+ return with_env
+
+
+set_config("WITH_ENV", with_env)
+
+# It doesn't matter if the dependency is on --enable or --disable
+@depends("--disable-values")
+def with_env2(values):
+ return values
+
+
+set_config("VALUES", with_env2)
+
+# It is possible to @depends on environment-only options.
+@depends("CC")
+def is_gcc(cc):
+ return cc and "gcc" in cc[0]
+
+
+set_config("IS_GCC", is_gcc)
+
+# It is possible to depend on the result from another function.
+@depends(with_env2)
+def with_env3(values):
+ return values
+
+
+set_config("VALUES2", with_env3)
+
+# @depends functions can also return results for use as input to another
+# @depends.
+@depends(with_env3)
+def with_env4(values):
+ return values
+
+
+@depends(with_env4)
+def with_env5(values):
+ return values
+
+
+set_config("VALUES3", with_env5)
+
+# The result from @depends functions can also be used as input to options.
+# The result must be returned, not implied.
+@depends("--enable-simple")
+def simple(simple):
+ return "simple" if simple else "not-simple"
+
+
+option("--with-returned-default", default=simple, help="Returned default")
+
+
+@depends("--with-returned-default")
+def default(value):
+ return value
+
+
+set_config("DEFAULTED", default)
+
+
+@depends("--enable-values")
+def choices(values):
+ if len(values):
+ return {
+ "alpha": ("a", "b", "c"),
+ "numeric": ("0", "1", "2"),
+ }.get(values[0])
+
+
+option("--returned-choices", choices=choices, help="Choices")
+
+
+@depends("--returned-choices")
+def returned_choices(values):
+ return values
+
+
+set_config("CHOICES", returned_choices)
+
+# All options must be referenced by some @depends function.
+# It is possible to depend on multiple options/functions
+@depends("--without-thing", "--with-stuff", with_env4, "--option")
+def remainder(*args):
+ return args
+
+
+set_config("REMAINDER", remainder)
+
+# It is possible to include other files to extend the configuration script.
+include("included.configure")
+
+# It is also possible for the include file path to come from the result of a
+# @depends function.
+option("--enable-include", nargs=1, help="Include")
+
+
+@depends("--enable-include")
+def include_path(path):
+ return path[0] if path else None
+
+
+include(include_path)
+
+# Sandboxed functions can import from modules through the use of the @imports
+# decorator.
+# The order of the decorators matter: @imports needs to appear after other
+# decorators.
+option("--with-imports", nargs="?", help="Imports")
+
+# A limited set of functions from os.path are exposed by default.
+@depends("--with-imports")
+def with_imports(value):
+ if len(value):
+ return hasattr(os.path, "abspath")
+
+
+set_config("HAS_ABSPATH", with_imports)
+
+# It is still possible to import the full set from os.path.
+# It is also possible to cherry-pick builtins.
+@depends("--with-imports")
+@imports("os.path")
+def with_imports(value):
+ if len(value):
+ return hasattr(os.path, "getatime")
+
+
+set_config("HAS_GETATIME", with_imports)
+
+
+@depends("--with-imports")
+def with_imports(value):
+ if len(value):
+ return hasattr(os.path, "getatime")
+
+
+set_config("HAS_GETATIME2", with_imports)
+
+# This option should be attributed to this file in the --help output even though
+# included.configure is the actual file that defines the option.
+indirectly_define_option("--indirect-option", help="Indirectly defined option")
+
+
+@depends("--indirect-option")
+def indirect_option(option):
+ return option
diff --git a/python/mozbuild/mozbuild/test/configure/data/set_config.configure b/python/mozbuild/mozbuild/test/configure/data/set_config.configure
new file mode 100644
index 0000000000..0ae5fef6d6
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/set_config.configure
@@ -0,0 +1,51 @@
+# -*- 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/.
+
+option("--set-foo", help="set foo")
+
+
+@depends("--set-foo")
+def foo(value):
+ if value:
+ return True
+
+
+set_config("FOO", foo)
+
+
+option("--set-bar", help="set bar")
+
+
+@depends("--set-bar")
+def bar(value):
+ return bool(value)
+
+
+set_config("BAR", bar)
+
+
+option("--set-value", nargs=1, help="set value")
+
+
+@depends("--set-value")
+def set_value(value):
+ if value:
+ return value[0]
+
+
+set_config("VALUE", set_value)
+
+
+option("--set-name", nargs=1, help="set name")
+
+
+@depends("--set-name")
+def set_name(value):
+ if value:
+ return value[0]
+
+
+set_config(set_name, True)
diff --git a/python/mozbuild/mozbuild/test/configure/data/set_define.configure b/python/mozbuild/mozbuild/test/configure/data/set_define.configure
new file mode 100644
index 0000000000..ce9a60d7f1
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/set_define.configure
@@ -0,0 +1,51 @@
+# -*- 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/.
+
+option("--set-foo", help="set foo")
+
+
+@depends("--set-foo")
+def foo(value):
+ if value:
+ return True
+
+
+set_define("FOO", foo)
+
+
+option("--set-bar", help="set bar")
+
+
+@depends("--set-bar")
+def bar(value):
+ return bool(value)
+
+
+set_define("BAR", bar)
+
+
+option("--set-value", nargs=1, help="set value")
+
+
+@depends("--set-value")
+def set_value(value):
+ if value:
+ return value[0]
+
+
+set_define("VALUE", set_value)
+
+
+option("--set-name", nargs=1, help="set name")
+
+
+@depends("--set-name")
+def set_name(value):
+ if value:
+ return value[0]
+
+
+set_define(set_name, True)
diff --git a/python/mozbuild/mozbuild/test/configure/data/subprocess.configure b/python/mozbuild/mozbuild/test/configure/data/subprocess.configure
new file mode 100644
index 0000000000..3316fee087
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/subprocess.configure
@@ -0,0 +1,24 @@
+# -*- 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/.
+
+
+@depends("--help")
+@imports("codecs")
+@imports(_from="mozbuild.configure.util", _import="getpreferredencoding")
+@imports("os")
+@imports(_from="__builtin__", _import="open")
+def dies_when_logging(_):
+ test_file = "test.txt"
+ quote_char = "'"
+ if getpreferredencoding().lower() == "utf-8":
+ quote_char = "\u00B4"
+ try:
+ with open(test_file, "w+") as fh:
+ fh.write(quote_char)
+ out = check_cmd_output("cat", "test.txt")
+ log.info(out)
+ finally:
+ os.remove(test_file)
diff --git a/python/mozbuild/mozbuild/test/configure/lint.py b/python/mozbuild/mozbuild/test/configure/lint.py
new file mode 100644
index 0000000000..59d41da264
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/lint.py
@@ -0,0 +1,62 @@
+# 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/.
+
+import os
+import unittest
+
+import six
+from buildconfig import topobjdir, topsrcdir
+from mozunit import main
+
+from mozbuild.configure.lint import LintSandbox
+
+test_path = os.path.abspath(__file__)
+
+
+class LintMeta(type):
+ def __new__(mcs, name, bases, attrs):
+ def create_test(project, func):
+ def test(self):
+ return func(self, project)
+
+ return test
+
+ for project in (
+ "browser",
+ "js",
+ "memory",
+ "mobile/android",
+ ):
+ attrs["test_%s" % project.replace("/", "_")] = create_test(
+ project, attrs["lint"]
+ )
+
+ return type.__new__(mcs, name, bases, attrs)
+
+
+# We don't actually need python2 compat, but this makes flake8 happy.
+@six.add_metaclass(LintMeta)
+class Lint(unittest.TestCase):
+ def setUp(self):
+ self._curdir = os.getcwd()
+ os.chdir(topobjdir)
+
+ def tearDown(self):
+ os.chdir(self._curdir)
+
+ def lint(self, project):
+ sandbox = LintSandbox(
+ {
+ "OLD_CONFIGURE": os.path.join(topsrcdir, "old-configure"),
+ "MOZCONFIG": os.path.join(
+ os.path.dirname(test_path), "data", "empty_mozconfig"
+ ),
+ },
+ ["configure", "--enable-project=%s" % project, "--help"],
+ )
+ sandbox.run(os.path.join(topsrcdir, "moz.configure"))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/configure/macos_fake_sdk/SDKSettings.plist b/python/mozbuild/mozbuild/test/configure/macos_fake_sdk/SDKSettings.plist
new file mode 100644
index 0000000000..f0d6e1949f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/macos_fake_sdk/SDKSettings.plist
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>Version</key>
+ <string>13.3</string>
+</dict>
+</plist>
diff --git a/python/mozbuild/mozbuild/test/configure/test_bootstrap.py b/python/mozbuild/mozbuild/test/configure/test_bootstrap.py
new file mode 100644
index 0000000000..eaa417d566
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_bootstrap.py
@@ -0,0 +1,43 @@
+# 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/.
+
+from mozunit import main
+
+from common import BaseConfigureTest
+
+
+class TestBootstrap(BaseConfigureTest):
+ def test_bootstrap(self):
+ def get_value_for(arg):
+ sandbox = self.get_sandbox({}, {}, [arg], {})
+ return sandbox._value_for(sandbox["enable_bootstrap"])
+
+ self.assertEqual(None, get_value_for("--disable-bootstrap"))
+
+ # With `--enable-bootstrap`, anything is bootstrappable
+ bootstrap = get_value_for("--enable-bootstrap")
+ self.assertTrue(bootstrap("foo"))
+ self.assertTrue(bootstrap("bar"))
+
+ # With `--enable-bootstrap=foo,bar`, only foo and bar are bootstrappable
+ bootstrap = get_value_for("--enable-bootstrap=foo,bar")
+ self.assertTrue(bootstrap("foo"))
+ self.assertTrue(bootstrap("bar"))
+ self.assertFalse(bootstrap("qux"))
+
+ # With `--enable-bootstrap=-foo`, anything is bootstrappable, except foo
+ bootstrap = get_value_for("--enable-bootstrap=-foo")
+ self.assertFalse(bootstrap("foo"))
+ self.assertTrue(bootstrap("bar"))
+ self.assertTrue(bootstrap("qux"))
+
+ # Corner case.
+ bootstrap = get_value_for("--enable-bootstrap=-foo,foo,bar")
+ self.assertFalse(bootstrap("foo"))
+ self.assertTrue(bootstrap("bar"))
+ self.assertFalse(bootstrap("qux"))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/configure/test_checks_configure.py b/python/mozbuild/mozbuild/test/configure/test_checks_configure.py
new file mode 100644
index 0000000000..53361ff199
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_checks_configure.py
@@ -0,0 +1,1169 @@
+# 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/.
+
+import os
+import sys
+import textwrap
+import unittest
+
+from buildconfig import topsrcdir
+from mozpack import path as mozpath
+from mozunit import MockedOpen, main
+from six import StringIO
+
+from common import ConfigureTestSandbox, ensure_exe_extension, fake_short_path
+from mozbuild.configure import ConfigureError, ConfigureSandbox
+from mozbuild.shellutil import quote as shell_quote
+from mozbuild.util import exec_
+
+
+class TestChecksConfigure(unittest.TestCase):
+ def test_checking(self):
+ def make_test(to_exec):
+ def test(val, msg):
+ out = StringIO()
+ sandbox = ConfigureSandbox({}, stdout=out, stderr=out)
+ base_dir = os.path.join(topsrcdir, "build", "moz.configure")
+ sandbox.include_file(os.path.join(base_dir, "checks.configure"))
+ exec_(to_exec, sandbox)
+ sandbox["foo"](val)
+ self.assertEqual(out.getvalue(), msg)
+
+ return test
+
+ test = make_test(
+ textwrap.dedent(
+ """
+ @checking('for a thing')
+ def foo(value):
+ return value
+ """
+ )
+ )
+ test(True, "checking for a thing... yes\n")
+ test(False, "checking for a thing... no\n")
+ test(42, "checking for a thing... 42\n")
+ test("foo", "checking for a thing... foo\n")
+ data = ["foo", "bar"]
+ test(data, "checking for a thing... %r\n" % data)
+
+ # When the function given to checking does nothing interesting, the
+ # behavior is not altered
+ test = make_test(
+ textwrap.dedent(
+ """
+ @checking('for a thing', lambda x: x)
+ def foo(value):
+ return value
+ """
+ )
+ )
+ test(True, "checking for a thing... yes\n")
+ test(False, "checking for a thing... no\n")
+ test(42, "checking for a thing... 42\n")
+ test("foo", "checking for a thing... foo\n")
+ data = ["foo", "bar"]
+ test(data, "checking for a thing... %r\n" % data)
+
+ test = make_test(
+ textwrap.dedent(
+ """
+ def munge(x):
+ if not x:
+ return 'not found'
+ if isinstance(x, (str, bool, int)):
+ return x
+ return ' '.join(x)
+
+ @checking('for a thing', munge)
+ def foo(value):
+ return value
+ """
+ )
+ )
+ test(True, "checking for a thing... yes\n")
+ test(False, "checking for a thing... not found\n")
+ test(42, "checking for a thing... 42\n")
+ test("foo", "checking for a thing... foo\n")
+ data = ["foo", "bar"]
+ test(data, "checking for a thing... foo bar\n")
+
+ KNOWN_A = ensure_exe_extension(mozpath.abspath("/usr/bin/known-a"))
+ KNOWN_B = ensure_exe_extension(mozpath.abspath("/usr/local/bin/known-b"))
+ KNOWN_C = ensure_exe_extension(mozpath.abspath("/home/user/bin/known c"))
+ OTHER_A = ensure_exe_extension(mozpath.abspath("/lib/other/known-a"))
+
+ def get_result(
+ self,
+ command="",
+ args=[],
+ environ={},
+ prog="/bin/configure",
+ extra_paths=None,
+ includes=("util.configure", "checks.configure"),
+ ):
+ config = {}
+ out = StringIO()
+ paths = {self.KNOWN_A: None, self.KNOWN_B: None, self.KNOWN_C: None}
+ if extra_paths:
+ paths.update(extra_paths)
+ environ = dict(environ)
+ if "PATH" not in environ:
+ environ["PATH"] = os.pathsep.join(os.path.dirname(p) for p in paths)
+ paths[self.OTHER_A] = None
+ sandbox = ConfigureTestSandbox(paths, config, environ, [prog] + args, out, out)
+ base_dir = os.path.join(topsrcdir, "build", "moz.configure")
+ for f in includes:
+ sandbox.include_file(os.path.join(base_dir, f))
+
+ status = 0
+ try:
+ exec_(command, sandbox)
+ sandbox.run()
+ except SystemExit as e:
+ status = e.code
+
+ return config, out.getvalue(), status
+
+ def test_check_prog(self):
+ config, out, status = self.get_result('check_prog("FOO", ("known-a",))')
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"FOO": self.KNOWN_A})
+ self.assertEqual(out, "checking for foo... %s\n" % self.KNOWN_A)
+
+ config, out, status = self.get_result(
+ 'check_prog("FOO", ("unknown", "known-b", "known c"))'
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"FOO": self.KNOWN_B})
+ self.assertEqual(out, "checking for foo... %s\n" % self.KNOWN_B)
+
+ config, out, status = self.get_result(
+ 'check_prog("FOO", ("unknown", "unknown-2", "known c"))'
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"FOO": fake_short_path(self.KNOWN_C)})
+ self.assertEqual(
+ out, "checking for foo... %s\n" % shell_quote(fake_short_path(self.KNOWN_C))
+ )
+
+ config, out, status = self.get_result('check_prog("FOO", ("unknown",))')
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for foo... not found
+ DEBUG: foo: Looking for unknown
+ ERROR: Cannot find foo
+ """
+ ),
+ )
+
+ config, out, status = self.get_result(
+ 'check_prog("FOO", ("unknown", "unknown-2", "unknown 3"))'
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for foo... not found
+ DEBUG: foo: Looking for unknown
+ DEBUG: foo: Looking for unknown-2
+ DEBUG: foo: Looking for 'unknown 3'
+ ERROR: Cannot find foo
+ """
+ ),
+ )
+
+ config, out, status = self.get_result(
+ 'check_prog("FOO", ("unknown", "unknown-2", "unknown 3"), '
+ "allow_missing=True)"
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {})
+ self.assertEqual(out, "checking for foo... not found\n")
+
+ @unittest.skipIf(not sys.platform.startswith("win"), "Windows-only test")
+ def test_check_prog_exe(self):
+ config, out, status = self.get_result(
+ 'check_prog("FOO", ("unknown", "known-b", "known c"))', ["FOO=known-a.exe"]
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"FOO": self.KNOWN_A})
+ self.assertEqual(out, "checking for foo... %s\n" % self.KNOWN_A)
+
+ config, out, status = self.get_result(
+ 'check_prog("FOO", ("unknown", "known-b", "known c"))',
+ ["FOO=%s" % os.path.splitext(self.KNOWN_A)[0]],
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"FOO": self.KNOWN_A})
+ self.assertEqual(out, "checking for foo... %s\n" % self.KNOWN_A)
+
+ def test_check_prog_with_args(self):
+ config, out, status = self.get_result(
+ 'check_prog("FOO", ("unknown", "known-b", "known c"))', ["FOO=known-a"]
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"FOO": self.KNOWN_A})
+ self.assertEqual(out, "checking for foo... %s\n" % self.KNOWN_A)
+
+ config, out, status = self.get_result(
+ 'check_prog("FOO", ("unknown", "known-b", "known c"))',
+ ["FOO=%s" % self.KNOWN_A],
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"FOO": self.KNOWN_A})
+ self.assertEqual(out, "checking for foo... %s\n" % self.KNOWN_A)
+
+ path = self.KNOWN_B.replace("known-b", "known-a")
+ config, out, status = self.get_result(
+ 'check_prog("FOO", ("unknown", "known-b", "known c"))', ["FOO=%s" % path]
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for foo... not found
+ DEBUG: foo: Looking for %s
+ ERROR: Cannot find foo
+ """
+ )
+ % path,
+ )
+
+ config, out, status = self.get_result(
+ 'check_prog("FOO", ("unknown",))', ["FOO=known c"]
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"FOO": fake_short_path(self.KNOWN_C)})
+ self.assertEqual(
+ out, "checking for foo... %s\n" % shell_quote(fake_short_path(self.KNOWN_C))
+ )
+
+ config, out, status = self.get_result(
+ 'check_prog("FOO", ("unknown", "unknown-2", "unknown 3"), '
+ "allow_missing=True)",
+ ["FOO=unknown"],
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for foo... not found
+ DEBUG: foo: Looking for unknown
+ ERROR: Cannot find foo
+ """
+ ),
+ )
+
+ def test_check_prog_what(self):
+ config, out, status = self.get_result(
+ 'check_prog("CC", ("known-a",), what="the target C compiler")'
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"CC": self.KNOWN_A})
+ self.assertEqual(
+ out, "checking for the target C compiler... %s\n" % self.KNOWN_A
+ )
+
+ config, out, status = self.get_result(
+ 'check_prog("CC", ("unknown", "unknown-2", "unknown 3"),'
+ ' what="the target C compiler")'
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for the target C compiler... not found
+ DEBUG: cc: Looking for unknown
+ DEBUG: cc: Looking for unknown-2
+ DEBUG: cc: Looking for 'unknown 3'
+ ERROR: Cannot find the target C compiler
+ """
+ ),
+ )
+
+ def test_check_prog_input(self):
+ config, out, status = self.get_result(
+ textwrap.dedent(
+ """
+ option("--with-ccache", nargs=1, help="ccache")
+ check_prog("CCACHE", ("known-a",), input="--with-ccache")
+ """
+ ),
+ ["--with-ccache=known-b"],
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"CCACHE": self.KNOWN_B})
+ self.assertEqual(out, "checking for ccache... %s\n" % self.KNOWN_B)
+
+ script = textwrap.dedent(
+ """
+ option(env="CC", nargs=1, help="compiler")
+ @depends("CC")
+ def compiler(value):
+ return value[0].split()[0] if value else None
+ check_prog("CC", ("known-a",), input=compiler)
+ """
+ )
+ config, out, status = self.get_result(script)
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"CC": self.KNOWN_A})
+ self.assertEqual(out, "checking for cc... %s\n" % self.KNOWN_A)
+
+ config, out, status = self.get_result(script, ["CC=known-b"])
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"CC": self.KNOWN_B})
+ self.assertEqual(out, "checking for cc... %s\n" % self.KNOWN_B)
+
+ config, out, status = self.get_result(script, ["CC=known-b -m32"])
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"CC": self.KNOWN_B})
+ self.assertEqual(out, "checking for cc... %s\n" % self.KNOWN_B)
+
+ def test_check_prog_progs(self):
+ config, out, status = self.get_result('check_prog("FOO", ())')
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {})
+ self.assertEqual(out, "")
+
+ config, out, status = self.get_result('check_prog("FOO", ())', ["FOO=known-a"])
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"FOO": self.KNOWN_A})
+ self.assertEqual(out, "checking for foo... %s\n" % self.KNOWN_A)
+
+ script = textwrap.dedent(
+ """
+ option(env="TARGET", nargs=1, default="linux", help="target")
+ @depends("TARGET")
+ def compiler(value):
+ if value:
+ if value[0] == "linux":
+ return ("gcc", "clang")
+ if value[0] == "winnt":
+ return ("cl", "clang-cl")
+ check_prog("CC", compiler)
+ """
+ )
+ config, out, status = self.get_result(script)
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for cc... not found
+ DEBUG: cc: Looking for gcc
+ DEBUG: cc: Looking for clang
+ ERROR: Cannot find cc
+ """
+ ),
+ )
+
+ config, out, status = self.get_result(script, ["TARGET=linux"])
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for cc... not found
+ DEBUG: cc: Looking for gcc
+ DEBUG: cc: Looking for clang
+ ERROR: Cannot find cc
+ """
+ ),
+ )
+
+ config, out, status = self.get_result(script, ["TARGET=winnt"])
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for cc... not found
+ DEBUG: cc: Looking for cl
+ DEBUG: cc: Looking for clang-cl
+ ERROR: Cannot find cc
+ """
+ ),
+ )
+
+ config, out, status = self.get_result(script, ["TARGET=none"])
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {})
+ self.assertEqual(out, "")
+
+ config, out, status = self.get_result(script, ["TARGET=winnt", "CC=known-a"])
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"CC": self.KNOWN_A})
+ self.assertEqual(out, "checking for cc... %s\n" % self.KNOWN_A)
+
+ config, out, status = self.get_result(script, ["TARGET=none", "CC=known-a"])
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"CC": self.KNOWN_A})
+ self.assertEqual(out, "checking for cc... %s\n" % self.KNOWN_A)
+
+ def test_check_prog_configure_error(self):
+ with self.assertRaises(ConfigureError) as e:
+ self.get_result('check_prog("FOO", "foo")')
+
+ self.assertEqual(str(e.exception), "progs must resolve to a list or tuple!")
+
+ with self.assertRaises(ConfigureError) as e:
+ self.get_result(
+ 'foo = depends(when=True)(lambda: ("a", "b"))\n'
+ 'check_prog("FOO", ("known-a",), input=foo)'
+ )
+
+ self.assertEqual(
+ str(e.exception),
+ "input must resolve to a tuple or a list with a "
+ "single element, or a string",
+ )
+
+ with self.assertRaises(ConfigureError) as e:
+ self.get_result(
+ 'foo = depends(when=True)(lambda: {"a": "b"})\n'
+ 'check_prog("FOO", ("known-a",), input=foo)'
+ )
+
+ self.assertEqual(
+ str(e.exception),
+ "input must resolve to a tuple or a list with a "
+ "single element, or a string",
+ )
+
+ def test_check_prog_with_path(self):
+ config, out, status = self.get_result(
+ 'check_prog("A", ("known-a",), paths=["/some/path"])'
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for a... not found
+ DEBUG: a: Looking for known-a
+ ERROR: Cannot find a
+ """
+ ),
+ )
+
+ config, out, status = self.get_result(
+ 'check_prog("A", ("known-a",), paths=["%s"])'
+ % os.path.dirname(self.OTHER_A)
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"A": self.OTHER_A})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for a... %s
+ """
+ % self.OTHER_A
+ ),
+ )
+
+ dirs = map(mozpath.dirname, (self.OTHER_A, self.KNOWN_A))
+ config, out, status = self.get_result(
+ textwrap.dedent(
+ """\
+ check_prog("A", ("known-a",), paths=["%s"])
+ """
+ % os.pathsep.join(dirs)
+ )
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"A": self.OTHER_A})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for a... %s
+ """
+ % self.OTHER_A
+ ),
+ )
+
+ dirs = map(mozpath.dirname, (self.KNOWN_A, self.KNOWN_B))
+ config, out, status = self.get_result(
+ textwrap.dedent(
+ """\
+ check_prog("A", ("known-a",), paths=["%s", "%s"])
+ """
+ % (os.pathsep.join(dirs), self.OTHER_A)
+ )
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"A": self.KNOWN_A})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for a... %s
+ """
+ % self.KNOWN_A
+ ),
+ )
+
+ config, out, status = self.get_result(
+ 'check_prog("A", ("known-a",), paths="%s")' % os.path.dirname(self.OTHER_A)
+ )
+
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for a... """ # noqa # trailing whitespace...
+ """
+ DEBUG: a: Looking for known-a
+ ERROR: Paths provided to find_program must be a list of strings, not %r
+ """
+ % mozpath.dirname(self.OTHER_A)
+ ),
+ )
+
+ @unittest.skipIf(
+ not sys.platform.startswith("linux"),
+ "Linux-only test, assumes Java is located from a $PATH",
+ )
+ def test_java_tool_checks_linux(self):
+ def run_configure_java(
+ mock_fs_paths, mock_java_home=None, mock_path=None, args=[]
+ ):
+ script = textwrap.dedent(
+ """\
+ @depends('--help')
+ def host(_):
+ return namespace(os='unknown', kernel='unknown')
+ toolchains_base_dir = depends(when=True)(lambda: '/mozbuild')
+ include('%(topsrcdir)s/build/moz.configure/java.configure')
+ """
+ % {"topsrcdir": topsrcdir}
+ )
+
+ # Don't let system JAVA_HOME influence the test
+ original_java_home = os.environ.pop("JAVA_HOME", None)
+ configure_environ = {}
+
+ if mock_java_home:
+ os.environ["JAVA_HOME"] = mock_java_home
+ configure_environ["JAVA_HOME"] = mock_java_home
+
+ if mock_path:
+ configure_environ["PATH"] = mock_path
+
+ # * Even if the real file sysphabtem has a symlink at the mocked path, don't let
+ # realpath follow it, as it may influence the test.
+ # * When finding a binary, check the mock paths rather than the real filesystem.
+ # Note: Python doesn't allow the different "with" bits to be put in parenthesis,
+ # because then it thinks it's an un-with-able tuple. Additionally, if this is cleanly
+ # lined up with "\", black removes them and autoformats them to the block that is
+ # below.
+ result = self.get_result(
+ args=args,
+ command=script,
+ extra_paths=paths,
+ environ=configure_environ,
+ )
+
+ if original_java_home:
+ os.environ["JAVA_HOME"] = original_java_home
+ return result
+
+ java = mozpath.abspath("/usr/bin/java")
+ javac = mozpath.abspath("/usr/bin/javac")
+ paths = {java: None, javac: None}
+ expected_error_message = (
+ "ERROR: Could not locate Java at /mozbuild/jdk/jdk-17.0.7+7/bin, "
+ "please run ./mach bootstrap --no-system-changes\n"
+ )
+
+ config, out, status = run_configure_java(paths)
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(out, expected_error_message)
+
+ # An alternative valid set of tools referred to by JAVA_HOME.
+ alt_java = mozpath.abspath("/usr/local/bin/java")
+ alt_javac = mozpath.abspath("/usr/local/bin/javac")
+ alt_java_home = mozpath.dirname(mozpath.dirname(alt_java))
+ paths = {alt_java: None, alt_javac: None, java: None, javac: None}
+
+ alt_path = mozpath.dirname(java)
+ config, out, status = run_configure_java(paths, alt_java_home, alt_path)
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(out, expected_error_message)
+
+ # We can use --with-java-bin-path instead of JAVA_HOME to similar
+ # effect.
+ config, out, status = run_configure_java(
+ paths,
+ mock_path=mozpath.dirname(java),
+ args=["--with-java-bin-path=%s" % mozpath.dirname(alt_java)],
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"JAVA": alt_java, "MOZ_JAVA_CODE_COVERAGE": False})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for java... %s
+ """
+ % alt_java
+ ),
+ )
+
+ # If --with-java-bin-path and JAVA_HOME are both set,
+ # --with-java-bin-path takes precedence.
+ config, out, status = run_configure_java(
+ paths,
+ mock_java_home=mozpath.dirname(mozpath.dirname(java)),
+ mock_path=mozpath.dirname(java),
+ args=["--with-java-bin-path=%s" % mozpath.dirname(alt_java)],
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"JAVA": alt_java, "MOZ_JAVA_CODE_COVERAGE": False})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for java... %s
+ """
+ % alt_java
+ ),
+ )
+
+ # --enable-java-coverage should set MOZ_JAVA_CODE_COVERAGE.
+ alt_java_home = mozpath.dirname(mozpath.dirname(java))
+ config, out, status = run_configure_java(
+ paths,
+ mock_java_home=alt_java_home,
+ mock_path=mozpath.dirname(java),
+ args=["--enable-java-coverage"],
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+
+ # Any missing tool is fatal when these checks run.
+ paths = {}
+ config, out, status = run_configure_java(
+ mock_fs_paths={},
+ mock_path=mozpath.dirname(java),
+ args=["--enable-java-coverage"],
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(config, {})
+ self.assertEqual(out, expected_error_message)
+
+ def test_pkg_check_modules(self):
+ mock_pkg_config_version = "0.10.0"
+ mock_pkg_config_path = mozpath.abspath("/usr/bin/pkg-config")
+
+ seen_flags = set()
+
+ def mock_pkg_config(_, args):
+ if "--dont-define-prefix" in args:
+ args = list(args)
+ seen_flags.add(args.pop(args.index("--dont-define-prefix")))
+ args = tuple(args)
+ if args[0:2] == ("--errors-to-stdout", "--print-errors"):
+ assert len(args) == 3
+ package = args[2]
+ if package == "unknown":
+ return (
+ 1,
+ "Package unknown was not found in the pkg-config search path.\n"
+ "Perhaps you should add the directory containing `unknown.pc'\n"
+ "to the PKG_CONFIG_PATH environment variable\n"
+ "No package 'unknown' found",
+ "",
+ )
+ if package == "valid":
+ return 0, "", ""
+ if package == "new > 1.1":
+ return 1, "Requested 'new > 1.1' but version of new is 1.1", ""
+ if args[0] == "--cflags":
+ assert len(args) == 2
+ return 0, "-I/usr/include/%s" % args[1], ""
+ if args[0] == "--libs":
+ assert len(args) == 2
+ return 0, "-l%s" % args[1], ""
+ if args[0] == "--version":
+ return 0, mock_pkg_config_version, ""
+ if args[0] == "--about":
+ return 1, "Unknown option --about", ""
+ self.fail("Unexpected arguments to mock_pkg_config: %s" % (args,))
+
+ def mock_pkgconf(_, args):
+ if args[0] == "--shared":
+ seen_flags.add(args[0])
+ args = args[1:]
+ if args[0] == "--about":
+ return 0, "pkgconf {}".format(mock_pkg_config_version), ""
+ return mock_pkg_config(_, args)
+
+ def get_result(cmd, args=[], bootstrapped_sysroot=False, extra_paths=None):
+ return self.get_result(
+ textwrap.dedent(
+ """\
+ option('--disable-compile-environment', help='compile env')
+ compile_environment = depends(when='--enable-compile-environment')(lambda: True)
+ toolchain_prefix = depends(when=True)(lambda: None)
+ target_multiarch_dir = depends(when=True)(lambda: None)
+ target_sysroot = depends(when=True)(lambda: %(sysroot)s)
+ target = depends(when=True)(lambda: None)
+ include('%(topsrcdir)s/build/moz.configure/util.configure')
+ include('%(topsrcdir)s/build/moz.configure/checks.configure')
+ # Skip bootstrapping.
+ @template
+ def check_prog(*args, **kwargs):
+ del kwargs["bootstrap"]
+ return check_prog(*args, **kwargs)
+ include('%(topsrcdir)s/build/moz.configure/pkg.configure')
+ """
+ % {
+ "topsrcdir": topsrcdir,
+ "sysroot": "namespace(bootstrapped=True)"
+ if bootstrapped_sysroot
+ else "None",
+ }
+ )
+ + cmd,
+ args=args,
+ extra_paths=extra_paths,
+ includes=(),
+ )
+
+ extra_paths = {mock_pkg_config_path: mock_pkg_config}
+
+ config, output, status = get_result("pkg_check_modules('MOZ_VALID', 'valid')")
+ self.assertEqual(status, 1)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for pkg_config... not found
+ ERROR: *** The pkg-config script could not be found. Make sure it is
+ *** in your path, or set the PKG_CONFIG environment variable
+ *** to the full path to pkg-config.
+ """
+ ),
+ )
+
+ for pkg_config, version, bootstrapped_sysroot, is_pkgconf in (
+ (mock_pkg_config, "0.10.0", False, False),
+ (mock_pkg_config, "0.30.0", False, False),
+ (mock_pkg_config, "0.30.0", True, False),
+ (mock_pkgconf, "1.1.0", True, True),
+ (mock_pkgconf, "1.6.0", False, True),
+ (mock_pkgconf, "1.8.0", False, True),
+ (mock_pkgconf, "1.8.0", True, True),
+ ):
+ seen_flags = set()
+ mock_pkg_config_version = version
+ config, output, status = get_result(
+ "pkg_check_modules('MOZ_VALID', 'valid')",
+ bootstrapped_sysroot=bootstrapped_sysroot,
+ extra_paths={mock_pkg_config_path: pkg_config},
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for pkg_config... %s
+ checking for pkg-config version... %s
+ checking whether pkg-config is pkgconf... %s
+ checking for valid... yes
+ checking MOZ_VALID_CFLAGS... -I/usr/include/valid
+ checking MOZ_VALID_LIBS... -lvalid
+ """
+ % (
+ mock_pkg_config_path,
+ mock_pkg_config_version,
+ "yes" if is_pkgconf else "no",
+ )
+ ),
+ )
+ self.assertEqual(
+ config,
+ {
+ "PKG_CONFIG": mock_pkg_config_path,
+ "MOZ_VALID_CFLAGS": ("-I/usr/include/valid",),
+ "MOZ_VALID_LIBS": ("-lvalid",),
+ },
+ )
+ if version == "1.8.0" and bootstrapped_sysroot:
+ self.assertEqual(seen_flags, set(["--shared", "--dont-define-prefix"]))
+ elif version == "1.8.0":
+ self.assertEqual(seen_flags, set(["--shared"]))
+ elif version in ("1.6.0", "0.30.0") and bootstrapped_sysroot:
+ self.assertEqual(seen_flags, set(["--dont-define-prefix"]))
+ else:
+ self.assertEqual(seen_flags, set())
+
+ config, output, status = get_result(
+ "pkg_check_modules('MOZ_UKNOWN', 'unknown')", extra_paths=extra_paths
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for pkg_config... %s
+ checking for pkg-config version... %s
+ checking whether pkg-config is pkgconf... no
+ checking for unknown... no
+ ERROR: Package unknown was not found in the pkg-config search path.
+ ERROR: Perhaps you should add the directory containing `unknown.pc'
+ ERROR: to the PKG_CONFIG_PATH environment variable
+ ERROR: No package 'unknown' found
+ """
+ % (mock_pkg_config_path, mock_pkg_config_version)
+ ),
+ )
+ self.assertEqual(config, {"PKG_CONFIG": mock_pkg_config_path})
+
+ config, output, status = get_result(
+ "pkg_check_modules('MOZ_NEW', 'new > 1.1')", extra_paths=extra_paths
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for pkg_config... %s
+ checking for pkg-config version... %s
+ checking whether pkg-config is pkgconf... no
+ checking for new > 1.1... no
+ ERROR: Requested 'new > 1.1' but version of new is 1.1
+ """
+ % (mock_pkg_config_path, mock_pkg_config_version)
+ ),
+ )
+ self.assertEqual(config, {"PKG_CONFIG": mock_pkg_config_path})
+
+ # allow_missing makes missing packages non-fatal.
+ cmd = textwrap.dedent(
+ """\
+ have_new_module = pkg_check_modules('MOZ_NEW', 'new > 1.1', allow_missing=True)
+ @depends(have_new_module)
+ def log_new_module_error(mod):
+ if mod is not True:
+ log.info('Module not found.')
+ """
+ )
+
+ config, output, status = get_result(cmd, extra_paths=extra_paths)
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for pkg_config... %s
+ checking for pkg-config version... %s
+ checking whether pkg-config is pkgconf... no
+ checking for new > 1.1... no
+ WARNING: Requested 'new > 1.1' but version of new is 1.1
+ Module not found.
+ """
+ % (mock_pkg_config_path, mock_pkg_config_version)
+ ),
+ )
+ self.assertEqual(config, {"PKG_CONFIG": mock_pkg_config_path})
+
+ config, output, status = get_result(
+ cmd, args=["--disable-compile-environment"], extra_paths=extra_paths
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(output, "Module not found.\n")
+ self.assertEqual(config, {})
+
+ def mock_old_pkg_config(_, args):
+ if args[0] == "--version":
+ return 0, "0.8.10", ""
+ if args[0] == "--about":
+ return 1, "Unknown option --about", ""
+ self.fail("Unexpected arguments to mock_old_pkg_config: %s" % args)
+
+ extra_paths = {mock_pkg_config_path: mock_old_pkg_config}
+
+ config, output, status = get_result(
+ "pkg_check_modules('MOZ_VALID', 'valid')", extra_paths=extra_paths
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for pkg_config... %s
+ checking for pkg-config version... 0.8.10
+ checking whether pkg-config is pkgconf... no
+ ERROR: *** Your version of pkg-config is too old. You need version 0.9.0 or newer.
+ """
+ % mock_pkg_config_path
+ ),
+ )
+
+ def test_simple_keyfile(self):
+ includes = ("util.configure", "checks.configure", "keyfiles.configure")
+
+ config, output, status = self.get_result(
+ "simple_keyfile('Mozilla API')", includes=includes
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Mozilla API key... no
+ """
+ ),
+ )
+ self.assertEqual(config, {"MOZ_MOZILLA_API_KEY": "no-mozilla-api-key"})
+
+ config, output, status = self.get_result(
+ "simple_keyfile('Mozilla API')",
+ args=["--with-mozilla-api-keyfile=/foo/bar/does/not/exist"],
+ includes=includes,
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Mozilla API key... no
+ ERROR: '/foo/bar/does/not/exist': No such file or directory.
+ """
+ ),
+ )
+ self.assertEqual(config, {})
+
+ with MockedOpen({"key": ""}):
+ config, output, status = self.get_result(
+ "simple_keyfile('Mozilla API')",
+ args=["--with-mozilla-api-keyfile=key"],
+ includes=includes,
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Mozilla API key... no
+ ERROR: 'key' is empty.
+ """
+ ),
+ )
+ self.assertEqual(config, {})
+
+ with MockedOpen({"key": "fake-key\n"}):
+ config, output, status = self.get_result(
+ "simple_keyfile('Mozilla API')",
+ args=["--with-mozilla-api-keyfile=key"],
+ includes=includes,
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Mozilla API key... yes
+ """
+ ),
+ )
+ self.assertEqual(config, {"MOZ_MOZILLA_API_KEY": "fake-key"})
+
+ with MockedOpen({"default": "default-key\n"}):
+ config, output, status = self.get_result(
+ "simple_keyfile('Mozilla API', default='default')", includes=includes
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Mozilla API key... yes
+ """
+ ),
+ )
+ self.assertEqual(config, {"MOZ_MOZILLA_API_KEY": "default-key"})
+
+ with MockedOpen({"default": "default-key\n", "key": "fake-key\n"}):
+ config, output, status = self.get_result(
+ "simple_keyfile('Mozilla API', default='key')", includes=includes
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Mozilla API key... yes
+ """
+ ),
+ )
+ self.assertEqual(config, {"MOZ_MOZILLA_API_KEY": "fake-key"})
+
+ def test_id_and_secret_keyfile(self):
+ includes = ("util.configure", "checks.configure", "keyfiles.configure")
+
+ config, output, status = self.get_result(
+ "id_and_secret_keyfile('Bing API')", includes=includes
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Bing API key... no
+ """
+ ),
+ )
+ self.assertEqual(
+ config,
+ {
+ "MOZ_BING_API_CLIENTID": "no-bing-api-clientid",
+ "MOZ_BING_API_KEY": "no-bing-api-key",
+ },
+ )
+
+ config, output, status = self.get_result(
+ "id_and_secret_keyfile('Bing API')",
+ args=["--with-bing-api-keyfile=/foo/bar/does/not/exist"],
+ includes=includes,
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Bing API key... no
+ ERROR: '/foo/bar/does/not/exist': No such file or directory.
+ """
+ ),
+ )
+ self.assertEqual(config, {})
+
+ with MockedOpen({"key": ""}):
+ config, output, status = self.get_result(
+ "id_and_secret_keyfile('Bing API')",
+ args=["--with-bing-api-keyfile=key"],
+ includes=includes,
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Bing API key... no
+ ERROR: 'key' is empty.
+ """
+ ),
+ )
+ self.assertEqual(config, {})
+
+ with MockedOpen({"key": "fake-id fake-key\n"}):
+ config, output, status = self.get_result(
+ "id_and_secret_keyfile('Bing API')",
+ args=["--with-bing-api-keyfile=key"],
+ includes=includes,
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Bing API key... yes
+ """
+ ),
+ )
+ self.assertEqual(
+ config,
+ {"MOZ_BING_API_CLIENTID": "fake-id", "MOZ_BING_API_KEY": "fake-key"},
+ )
+
+ with MockedOpen({"key": "fake-key\n"}):
+ config, output, status = self.get_result(
+ "id_and_secret_keyfile('Bing API')",
+ args=["--with-bing-api-keyfile=key"],
+ includes=includes,
+ )
+ self.assertEqual(status, 1)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Bing API key... no
+ ERROR: Bing API key file has an invalid format.
+ """
+ ),
+ )
+ self.assertEqual(config, {})
+
+ with MockedOpen({"default-key": "default-id default-key\n"}):
+ config, output, status = self.get_result(
+ "id_and_secret_keyfile('Bing API', default='default-key')",
+ includes=includes,
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Bing API key... yes
+ """
+ ),
+ )
+ self.assertEqual(
+ config,
+ {
+ "MOZ_BING_API_CLIENTID": "default-id",
+ "MOZ_BING_API_KEY": "default-key",
+ },
+ )
+
+ with MockedOpen(
+ {"default-key": "default-id default-key\n", "key": "fake-id fake-key\n"}
+ ):
+ config, output, status = self.get_result(
+ "id_and_secret_keyfile('Bing API', default='default-key')",
+ args=["--with-bing-api-keyfile=key"],
+ includes=includes,
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ output,
+ textwrap.dedent(
+ """\
+ checking for the Bing API key... yes
+ """
+ ),
+ )
+ self.assertEqual(
+ config,
+ {"MOZ_BING_API_CLIENTID": "fake-id", "MOZ_BING_API_KEY": "fake-key"},
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/configure/test_compile_checks.py b/python/mozbuild/mozbuild/test/configure/test_compile_checks.py
new file mode 100644
index 0000000000..37988d535f
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_compile_checks.py
@@ -0,0 +1,599 @@
+# 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/.
+
+import os
+import textwrap
+import unittest
+
+import mozpack.path as mozpath
+from buildconfig import topsrcdir
+from mozunit import main
+from six import StringIO
+from test_toolchain_helpers import FakeCompiler
+
+from common import ConfigureTestSandbox
+from mozbuild.util import exec_
+
+
+class BaseCompileChecks(unittest.TestCase):
+ def get_mock_compiler(self, expected_test_content=None, expected_flags=None):
+ expected_flags = expected_flags or []
+
+ def mock_compiler(stdin, args):
+ if args != ["--version"]:
+ test_file = [a for a in args if not a.startswith("-")]
+ self.assertEqual(len(test_file), 1)
+ test_file = test_file[0]
+ args = [a for a in args if a.startswith("-")]
+ self.assertIn("-c", args)
+ for flag in expected_flags:
+ self.assertIn(flag, args)
+
+ if expected_test_content:
+ with open(test_file) as fh:
+ test_content = fh.read()
+ self.assertEqual(test_content, expected_test_content)
+
+ return FakeCompiler()(None, args)
+
+ return mock_compiler
+
+ def do_compile_test(self, command, expected_test_content=None, expected_flags=None):
+
+ paths = {
+ os.path.abspath("/usr/bin/mockcc"): self.get_mock_compiler(
+ expected_test_content=expected_test_content,
+ expected_flags=expected_flags,
+ ),
+ }
+
+ base_dir = os.path.join(topsrcdir, "build", "moz.configure")
+
+ mock_compiler_defs = textwrap.dedent(
+ """\
+ @depends(when=True)
+ def extra_toolchain_flags():
+ return []
+
+ @depends(when=True)
+ def linker_ldflags():
+ return []
+
+ target = depends(when=True)(lambda: True)
+
+ @depends(when=True)
+ def configure_cache():
+
+ class ConfigureCache(dict):
+ pass
+
+ cache_data = {}
+
+ cache = ConfigureCache(cache_data)
+ cache.version_checked_compilers = set()
+
+ return cache
+
+ include('%s/compilers-util.configure')
+
+ @template
+ def wrap_compiler(compiler):
+ return compiler_class(compiler, False)
+
+ @wrap_compiler
+ @depends(when=True)
+ def c_compiler():
+ return namespace(
+ flags=[],
+ type='gcc',
+ compiler=os.path.abspath('/usr/bin/mockcc'),
+ wrapper=[],
+ language='C',
+ )
+
+ @wrap_compiler
+ @depends(when=True)
+ def host_c_compiler():
+ return namespace(
+ flags=[],
+ type='gcc',
+ compiler=os.path.abspath('/usr/bin/mockcc'),
+ wrapper=[],
+ language='C',
+ )
+
+ @wrap_compiler
+ @depends(when=True)
+ def cxx_compiler():
+ return namespace(
+ flags=[],
+ type='gcc',
+ compiler=os.path.abspath('/usr/bin/mockcc'),
+ wrapper=[],
+ language='C++',
+ )
+
+ @wrap_compiler
+ @depends(when=True)
+ def host_cxx_compiler():
+ return namespace(
+ flags=[],
+ type='gcc',
+ compiler=os.path.abspath('/usr/bin/mockcc'),
+ wrapper=[],
+ language='C++',
+ )
+ """
+ % mozpath.normsep(base_dir)
+ )
+
+ config = {}
+ out = StringIO()
+ sandbox = ConfigureTestSandbox(paths, config, {}, ["/bin/configure"], out, out)
+ sandbox.include_file(os.path.join(base_dir, "util.configure"))
+ sandbox.include_file(os.path.join(base_dir, "checks.configure"))
+ exec_(mock_compiler_defs, sandbox)
+ sandbox.include_file(os.path.join(base_dir, "compile-checks.configure"))
+
+ status = 0
+ try:
+ exec_(command, sandbox)
+ sandbox.run()
+ except SystemExit as e:
+ status = e.code
+
+ return config, out.getvalue(), status
+
+
+class TestHeaderChecks(BaseCompileChecks):
+ def test_try_compile_include(self):
+ expected_test_content = textwrap.dedent(
+ """\
+ #include <foo.h>
+ #include <bar.h>
+ int
+ main(void)
+ {
+
+ ;
+ return 0;
+ }
+ """
+ )
+
+ cmd = textwrap.dedent(
+ """\
+ try_compile(['foo.h', 'bar.h'], language='C')
+ """
+ )
+
+ config, out, status = self.do_compile_test(cmd, expected_test_content)
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {})
+
+ def test_try_compile_flags(self):
+ expected_flags = ["--extra", "--flags"]
+
+ cmd = textwrap.dedent(
+ """\
+ try_compile(language='C++', flags=['--flags', '--extra'])
+ """
+ )
+
+ config, out, status = self.do_compile_test(cmd, expected_flags=expected_flags)
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {})
+
+ def test_try_compile_failure(self):
+ cmd = textwrap.dedent(
+ """\
+ have_fn = try_compile(body='somefn();', flags=['-funknown-flag'])
+ set_config('HAVE_SOMEFN', have_fn)
+
+ have_another = try_compile(body='anotherfn();', language='C')
+ set_config('HAVE_ANOTHERFN', have_another)
+ """
+ )
+
+ config, out, status = self.do_compile_test(cmd)
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ config,
+ {
+ "HAVE_ANOTHERFN": True,
+ },
+ )
+
+ def test_try_compile_msg(self):
+ cmd = textwrap.dedent(
+ """\
+ known_flag = try_compile(language='C++', flags=['-fknown-flag'],
+ check_msg='whether -fknown-flag works')
+ set_config('HAVE_KNOWN_FLAG', known_flag)
+ """
+ )
+ config, out, status = self.do_compile_test(cmd)
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"HAVE_KNOWN_FLAG": True})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking whether -fknown-flag works... yes
+ """
+ ),
+ )
+
+ def test_check_header(self):
+ expected_test_content = textwrap.dedent(
+ """\
+ #include <foo.h>
+ int
+ main(void)
+ {
+
+ ;
+ return 0;
+ }
+ """
+ )
+
+ cmd = textwrap.dedent(
+ """\
+ check_header('foo.h')
+ """
+ )
+
+ config, out, status = self.do_compile_test(
+ cmd, expected_test_content=expected_test_content
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"DEFINES": {"HAVE_FOO_H": True}})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for foo.h... yes
+ """
+ ),
+ )
+
+ def test_check_header_conditional(self):
+ cmd = textwrap.dedent(
+ """\
+ check_headers('foo.h', 'bar.h', when=never)
+ """
+ )
+
+ config, out, status = self.do_compile_test(cmd)
+ self.assertEqual(status, 0)
+ self.assertEqual(out, "")
+ self.assertEqual(config, {"DEFINES": {}})
+
+ def test_check_header_include(self):
+ expected_test_content = textwrap.dedent(
+ """\
+ #include <std.h>
+ #include <bar.h>
+ #include <foo.h>
+ int
+ main(void)
+ {
+
+ ;
+ return 0;
+ }
+ """
+ )
+
+ cmd = textwrap.dedent(
+ """\
+ have_foo = check_header('foo.h', includes=['std.h', 'bar.h'])
+ set_config('HAVE_FOO_H', have_foo)
+ """
+ )
+
+ config, out, status = self.do_compile_test(
+ cmd, expected_test_content=expected_test_content
+ )
+
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ config,
+ {
+ "HAVE_FOO_H": True,
+ "DEFINES": {
+ "HAVE_FOO_H": True,
+ },
+ },
+ )
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for foo.h... yes
+ """
+ ),
+ )
+
+ def test_check_headers_multiple(self):
+ cmd = textwrap.dedent(
+ """\
+ baz_bar, quux_bar = check_headers('baz/foo-bar.h', 'baz-quux/foo-bar.h')
+ set_config('HAVE_BAZ_BAR', baz_bar)
+ set_config('HAVE_QUUX_BAR', quux_bar)
+ """
+ )
+
+ config, out, status = self.do_compile_test(cmd)
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ config,
+ {
+ "HAVE_BAZ_BAR": True,
+ "HAVE_QUUX_BAR": True,
+ "DEFINES": {
+ "HAVE_BAZ_FOO_BAR_H": True,
+ "HAVE_BAZ_QUUX_FOO_BAR_H": True,
+ },
+ },
+ )
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for baz/foo-bar.h... yes
+ checking for baz-quux/foo-bar.h... yes
+ """
+ ),
+ )
+
+ def test_check_headers_not_found(self):
+
+ cmd = textwrap.dedent(
+ """\
+ baz_bar, quux_bar = check_headers('baz/foo-bar.h', 'baz-quux/foo-bar.h',
+ flags=['-funknown-flag'])
+ set_config('HAVE_BAZ_BAR', baz_bar)
+ set_config('HAVE_QUUX_BAR', quux_bar)
+ """
+ )
+
+ config, out, status = self.do_compile_test(cmd)
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {"DEFINES": {}})
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking for baz/foo-bar.h... no
+ checking for baz-quux/foo-bar.h... no
+ """
+ ),
+ )
+
+
+class TestWarningChecks(BaseCompileChecks):
+ def get_warnings(self):
+ return textwrap.dedent(
+ """\
+ set_config('_WARNINGS_CFLAGS', warnings_flags.cflags)
+ set_config('_WARNINGS_CXXFLAGS', warnings_flags.cxxflags)
+ """
+ )
+
+ def test_check_and_add_warning(self):
+ for flag, expected_flags in (
+ ("-Wfoo", ["-Werror", "-Wfoo"]),
+ ("-Wno-foo", ["-Werror", "-Wfoo"]),
+ ("-Werror=foo", ["-Werror=foo"]),
+ ("-Wno-error=foo", ["-Wno-error=foo"]),
+ ):
+ cmd = (
+ textwrap.dedent(
+ """\
+ check_and_add_warning('%s')
+ """
+ % flag
+ )
+ + self.get_warnings()
+ )
+
+ config, out, status = self.do_compile_test(
+ cmd, expected_flags=expected_flags
+ )
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ config,
+ {
+ "_WARNINGS_CFLAGS": [flag],
+ "_WARNINGS_CXXFLAGS": [flag],
+ },
+ )
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking whether the C compiler supports {flag}... yes
+ checking whether the C++ compiler supports {flag}... yes
+ """.format(
+ flag=flag
+ )
+ ),
+ )
+
+ def test_check_and_add_warning_one(self):
+ cmd = (
+ textwrap.dedent(
+ """\
+ check_and_add_warning('-Wfoo', cxx_compiler)
+ """
+ )
+ + self.get_warnings()
+ )
+
+ config, out, status = self.do_compile_test(cmd)
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ config,
+ {
+ "_WARNINGS_CFLAGS": [],
+ "_WARNINGS_CXXFLAGS": ["-Wfoo"],
+ },
+ )
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking whether the C++ compiler supports -Wfoo... yes
+ """
+ ),
+ )
+
+ def test_check_and_add_warning_when(self):
+ cmd = (
+ textwrap.dedent(
+ """\
+ @depends(when=True)
+ def never():
+ return False
+ check_and_add_warning('-Wfoo', cxx_compiler, when=never)
+ """
+ )
+ + self.get_warnings()
+ )
+
+ config, out, status = self.do_compile_test(cmd)
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ config,
+ {
+ "_WARNINGS_CFLAGS": [],
+ "_WARNINGS_CXXFLAGS": [],
+ },
+ )
+ self.assertEqual(out, "")
+
+ cmd = (
+ textwrap.dedent(
+ """\
+ @depends(when=True)
+ def always():
+ return True
+ check_and_add_warning('-Wfoo', cxx_compiler, when=always)
+ """
+ )
+ + self.get_warnings()
+ )
+
+ config, out, status = self.do_compile_test(cmd)
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ config,
+ {
+ "_WARNINGS_CFLAGS": [],
+ "_WARNINGS_CXXFLAGS": ["-Wfoo"],
+ },
+ )
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ checking whether the C++ compiler supports -Wfoo... yes
+ """
+ ),
+ )
+
+ def test_add_warning(self):
+ cmd = (
+ textwrap.dedent(
+ """\
+ add_warning('-Wfoo')
+ """
+ )
+ + self.get_warnings()
+ )
+
+ config, out, status = self.do_compile_test(cmd)
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ config,
+ {
+ "_WARNINGS_CFLAGS": ["-Wfoo"],
+ "_WARNINGS_CXXFLAGS": ["-Wfoo"],
+ },
+ )
+ self.assertEqual(out, "")
+
+ def test_add_warning_one(self):
+ cmd = (
+ textwrap.dedent(
+ """\
+ add_warning('-Wfoo', c_compiler)
+ """
+ )
+ + self.get_warnings()
+ )
+
+ config, out, status = self.do_compile_test(cmd)
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ config,
+ {
+ "_WARNINGS_CFLAGS": ["-Wfoo"],
+ "_WARNINGS_CXXFLAGS": [],
+ },
+ )
+ self.assertEqual(out, "")
+
+ def test_add_warning_when(self):
+ cmd = (
+ textwrap.dedent(
+ """\
+ @depends(when=True)
+ def never():
+ return False
+ add_warning('-Wfoo', c_compiler, when=never)
+ """
+ )
+ + self.get_warnings()
+ )
+
+ config, out, status = self.do_compile_test(cmd)
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ config,
+ {
+ "_WARNINGS_CFLAGS": [],
+ "_WARNINGS_CXXFLAGS": [],
+ },
+ )
+ self.assertEqual(out, "")
+
+ cmd = (
+ textwrap.dedent(
+ """\
+ @depends(when=True)
+ def always():
+ return True
+ add_warning('-Wfoo', c_compiler, when=always)
+ """
+ )
+ + self.get_warnings()
+ )
+
+ config, out, status = self.do_compile_test(cmd)
+ self.assertEqual(status, 0)
+ self.assertEqual(
+ config,
+ {
+ "_WARNINGS_CFLAGS": ["-Wfoo"],
+ "_WARNINGS_CXXFLAGS": [],
+ },
+ )
+ self.assertEqual(out, "")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/configure/test_configure.py b/python/mozbuild/mozbuild/test/configure/test_configure.py
new file mode 100644
index 0000000000..a5e42faae3
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_configure.py
@@ -0,0 +1,1986 @@
+# 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/.
+
+import os
+import sys
+import textwrap
+import unittest
+
+import mozpack.path as mozpath
+import six
+from mozunit import MockedOpen, main
+from six import StringIO
+
+from mozbuild.configure import ConfigureError, ConfigureSandbox
+from mozbuild.configure.options import (
+ InvalidOptionError,
+ NegativeOptionValue,
+ PositiveOptionValue,
+)
+from mozbuild.util import ReadOnlyNamespace, exec_, memoized_property
+
+test_data_path = mozpath.abspath(mozpath.dirname(__file__))
+test_data_path = mozpath.join(test_data_path, "data")
+
+
+class TestConfigure(unittest.TestCase):
+ def get_config(
+ self, options=[], env={}, configure="moz.configure", prog="/bin/configure"
+ ):
+ config = {}
+ out = StringIO()
+ sandbox = ConfigureSandbox(config, env, [prog] + options, out, out)
+
+ sandbox.run(mozpath.join(test_data_path, configure))
+
+ if "--help" in options:
+ return six.ensure_text(out.getvalue()), config
+ self.assertEqual("", out.getvalue())
+ return config
+
+ def moz_configure(self, source):
+ return MockedOpen(
+ {os.path.join(test_data_path, "moz.configure"): textwrap.dedent(source)}
+ )
+
+ def test_defaults(self):
+ config = self.get_config()
+ self.maxDiff = None
+ self.assertEqual(
+ {
+ "CHOICES": NegativeOptionValue(),
+ "DEFAULTED": PositiveOptionValue(("not-simple",)),
+ "IS_GCC": NegativeOptionValue(),
+ "REMAINDER": (
+ PositiveOptionValue(),
+ NegativeOptionValue(),
+ NegativeOptionValue(),
+ NegativeOptionValue(),
+ ),
+ "SIMPLE": NegativeOptionValue(),
+ "VALUES": NegativeOptionValue(),
+ "VALUES2": NegativeOptionValue(),
+ "VALUES3": NegativeOptionValue(),
+ "WITH_ENV": NegativeOptionValue(),
+ },
+ config,
+ )
+
+ def test_help(self):
+ help, config = self.get_config(["--help"], prog="configure")
+
+ self.assertEqual({}, config)
+ self.maxDiff = None
+ self.assertEqual(
+ "Usage: configure [options]\n"
+ "\n"
+ "Options: [defaults in brackets after descriptions]\n"
+ " Help options:\n"
+ " --help print this message\n"
+ "\n"
+ " Options from python/mozbuild/mozbuild/test/configure/data/included.configure:\n"
+ " --enable-imports-in-template\n Imports in template\n"
+ "\n"
+ " Options from python/mozbuild/mozbuild/test/configure/data/moz.configure:\n"
+ " --enable-include Include\n"
+ " --enable-simple Enable simple\n"
+ " --enable-values Enable values\n"
+ " --enable-with-env Enable with env\n"
+ " --indirect-option Indirectly defined option\n"
+ " --option Option\n"
+ " --returned-choices Choices\n"
+ " --with-imports Imports\n"
+ " --with-returned-default Returned default [not-simple]\n"
+ " --with-stuff Build with stuff\n"
+ " --without-thing Build without thing\n"
+ "\n"
+ "\n"
+ "Environment variables:\n"
+ " Options from python/mozbuild/mozbuild/test/configure/data/moz.configure:\n"
+ " CC C Compiler\n"
+ "\n",
+ help.replace("\\", "/"),
+ )
+
+ def test_unknown(self):
+ with self.assertRaises(InvalidOptionError):
+ self.get_config(["--unknown"])
+
+ def test_simple(self):
+ for config in (
+ self.get_config(),
+ self.get_config(["--disable-simple"]),
+ # Last option wins.
+ self.get_config(["--enable-simple", "--disable-simple"]),
+ ):
+ self.assertNotIn("ENABLED_SIMPLE", config)
+ self.assertIn("SIMPLE", config)
+ self.assertEqual(NegativeOptionValue(), config["SIMPLE"])
+
+ for config in (
+ self.get_config(["--enable-simple"]),
+ self.get_config(["--disable-simple", "--enable-simple"]),
+ ):
+ self.assertIn("ENABLED_SIMPLE", config)
+ self.assertIn("SIMPLE", config)
+ self.assertEqual(PositiveOptionValue(), config["SIMPLE"])
+ self.assertIs(config["SIMPLE"], config["ENABLED_SIMPLE"])
+
+ # --enable-simple doesn't take values.
+ with self.assertRaises(InvalidOptionError):
+ self.get_config(["--enable-simple=value"])
+
+ def test_with_env(self):
+ for config in (
+ self.get_config(),
+ self.get_config(["--disable-with-env"]),
+ self.get_config(["--enable-with-env", "--disable-with-env"]),
+ self.get_config(env={"MOZ_WITH_ENV": ""}),
+ # Options win over environment
+ self.get_config(["--disable-with-env"], env={"MOZ_WITH_ENV": "1"}),
+ ):
+ self.assertIn("WITH_ENV", config)
+ self.assertEqual(NegativeOptionValue(), config["WITH_ENV"])
+
+ for config in (
+ self.get_config(["--enable-with-env"]),
+ self.get_config(["--disable-with-env", "--enable-with-env"]),
+ self.get_config(env={"MOZ_WITH_ENV": "1"}),
+ self.get_config(["--enable-with-env"], env={"MOZ_WITH_ENV": ""}),
+ ):
+ self.assertIn("WITH_ENV", config)
+ self.assertEqual(PositiveOptionValue(), config["WITH_ENV"])
+
+ with self.assertRaises(InvalidOptionError):
+ self.get_config(["--enable-with-env=value"])
+
+ with self.assertRaises(InvalidOptionError):
+ self.get_config(env={"MOZ_WITH_ENV": "value"})
+
+ def test_values(self, name="VALUES"):
+ for config in (
+ self.get_config(),
+ self.get_config(["--disable-values"]),
+ self.get_config(["--enable-values", "--disable-values"]),
+ ):
+ self.assertIn(name, config)
+ self.assertEqual(NegativeOptionValue(), config[name])
+
+ for config in (
+ self.get_config(["--enable-values"]),
+ self.get_config(["--disable-values", "--enable-values"]),
+ ):
+ self.assertIn(name, config)
+ self.assertEqual(PositiveOptionValue(), config[name])
+
+ config = self.get_config(["--enable-values=foo"])
+ self.assertIn(name, config)
+ self.assertEqual(PositiveOptionValue(("foo",)), config[name])
+
+ config = self.get_config(["--enable-values=foo,bar"])
+ self.assertIn(name, config)
+ self.assertTrue(config[name])
+ self.assertEqual(PositiveOptionValue(("foo", "bar")), config[name])
+
+ def test_values2(self):
+ self.test_values("VALUES2")
+
+ def test_values3(self):
+ self.test_values("VALUES3")
+
+ def test_returned_default(self):
+ config = self.get_config(["--enable-simple"])
+ self.assertIn("DEFAULTED", config)
+ self.assertEqual(PositiveOptionValue(("simple",)), config["DEFAULTED"])
+
+ config = self.get_config(["--disable-simple"])
+ self.assertIn("DEFAULTED", config)
+ self.assertEqual(PositiveOptionValue(("not-simple",)), config["DEFAULTED"])
+
+ def test_returned_choices(self):
+ for val in ("a", "b", "c"):
+ config = self.get_config(
+ ["--enable-values=alpha", "--returned-choices=%s" % val]
+ )
+ self.assertIn("CHOICES", config)
+ self.assertEqual(PositiveOptionValue((val,)), config["CHOICES"])
+
+ for val in ("0", "1", "2"):
+ config = self.get_config(
+ ["--enable-values=numeric", "--returned-choices=%s" % val]
+ )
+ self.assertIn("CHOICES", config)
+ self.assertEqual(PositiveOptionValue((val,)), config["CHOICES"])
+
+ with self.assertRaises(InvalidOptionError):
+ self.get_config(["--enable-values=numeric", "--returned-choices=a"])
+
+ with self.assertRaises(InvalidOptionError):
+ self.get_config(["--enable-values=alpha", "--returned-choices=0"])
+
+ def test_included(self):
+ config = self.get_config(env={"CC": "gcc"})
+ self.assertIn("IS_GCC", config)
+ self.assertEqual(config["IS_GCC"], True)
+
+ config = self.get_config(["--enable-include=extra.configure", "--extra"])
+ self.assertIn("EXTRA", config)
+ self.assertEqual(PositiveOptionValue(), config["EXTRA"])
+
+ with self.assertRaises(InvalidOptionError):
+ self.get_config(["--extra"])
+
+ def test_template(self):
+ config = self.get_config(env={"CC": "gcc"})
+ self.assertIn("CFLAGS", config)
+ self.assertEqual(config["CFLAGS"], ["-Werror=foobar"])
+
+ config = self.get_config(env={"CC": "clang"})
+ self.assertNotIn("CFLAGS", config)
+
+ def test_imports(self):
+ config = {}
+ out = StringIO()
+ sandbox = ConfigureSandbox(config, {}, ["configure"], out, out)
+
+ with self.assertRaises(ImportError):
+ exec_(
+ textwrap.dedent(
+ """
+ @template
+ def foo():
+ import sys
+ foo()"""
+ ),
+ sandbox,
+ )
+
+ exec_(
+ textwrap.dedent(
+ """
+ @template
+ @imports('sys')
+ def foo():
+ return sys"""
+ ),
+ sandbox,
+ )
+
+ self.assertIs(sandbox["foo"](), sys)
+
+ # os.path after an import is a mix of vanilla os.path and sandbox os.path.
+ os_path = {}
+ exec_("from os.path import *", {}, os_path)
+ os_path.update(sandbox.OS.path.__dict__)
+ os_path = ReadOnlyNamespace(**os_path)
+
+ exec_(
+ textwrap.dedent(
+ """
+ @template
+ @imports(_from='os', _import='path')
+ def foo():
+ return path"""
+ ),
+ sandbox,
+ )
+
+ self.assertEqual(sandbox["foo"](), os_path)
+
+ exec_(
+ textwrap.dedent(
+ """
+ @template
+ @imports(_from='os', _import='path', _as='os_path')
+ def foo():
+ return os_path"""
+ ),
+ sandbox,
+ )
+
+ self.assertEqual(sandbox["foo"](), os_path)
+
+ exec_(
+ textwrap.dedent(
+ """
+ @template
+ @imports('__builtin__')
+ def foo():
+ return __builtin__"""
+ ),
+ sandbox,
+ )
+
+ self.assertIs(sandbox["foo"](), six.moves.builtins)
+
+ exec_(
+ textwrap.dedent(
+ """
+ @template
+ @imports(_from='__builtin__', _import='open')
+ def foo():
+ return open('%s')"""
+ % os.devnull
+ ),
+ sandbox,
+ )
+
+ f = sandbox["foo"]()
+ self.assertEqual(f.name, os.devnull)
+ f.close()
+
+ # This unlocks the sandbox
+ exec_(
+ textwrap.dedent(
+ """
+ @template
+ @imports(_import='__builtin__', _as='__builtins__')
+ def foo():
+ import sys
+ return sys"""
+ ),
+ sandbox,
+ )
+
+ self.assertIs(sandbox["foo"](), sys)
+
+ exec_(
+ textwrap.dedent(
+ """
+ @template
+ @imports('__sandbox__')
+ def foo():
+ return __sandbox__"""
+ ),
+ sandbox,
+ )
+
+ self.assertIs(sandbox["foo"](), sandbox)
+
+ exec_(
+ textwrap.dedent(
+ """
+ @template
+ @imports(_import='__sandbox__', _as='s')
+ def foo():
+ return s"""
+ ),
+ sandbox,
+ )
+
+ self.assertIs(sandbox["foo"](), sandbox)
+
+ # Nothing leaked from the function being executed
+ self.assertEqual(list(sandbox), ["__builtins__", "foo"])
+ self.assertEqual(sandbox["__builtins__"], ConfigureSandbox.BUILTINS)
+
+ exec_(
+ textwrap.dedent(
+ """
+ @template
+ @imports('sys')
+ def foo():
+ @depends(when=True)
+ def bar():
+ return sys
+ return bar
+ bar = foo()"""
+ ),
+ sandbox,
+ )
+
+ with self.assertRaises(NameError) as e:
+ sandbox._depends[sandbox["bar"]].result()
+
+ self.assertIn("name 'sys' is not defined", str(e.exception))
+
+ def test_apply_imports(self):
+ imports = []
+
+ class CountApplyImportsSandbox(ConfigureSandbox):
+ def _apply_imports(self, *args, **kwargs):
+ imports.append((args, kwargs))
+ super(CountApplyImportsSandbox, self)._apply_imports(*args, **kwargs)
+
+ config = {}
+ out = StringIO()
+ sandbox = CountApplyImportsSandbox(config, {}, ["configure"], out, out)
+
+ exec_(
+ textwrap.dedent(
+ """
+ @template
+ @imports('sys')
+ def foo():
+ return sys
+ foo()
+ foo()"""
+ ),
+ sandbox,
+ )
+
+ self.assertEqual(len(imports), 1)
+
+ def test_import_wrapping(self):
+ bar = object()
+ foo = ReadOnlyNamespace(bar=bar)
+
+ class BasicWrappingSandbox(ConfigureSandbox):
+ @memoized_property
+ def _wrapped_foo(self):
+ return foo
+
+ config = {}
+ out = StringIO()
+ sandbox = BasicWrappingSandbox(config, {}, ["configure"], out, out)
+
+ exec_(
+ textwrap.dedent(
+ """
+ @template
+ @imports('foo')
+ def toplevel():
+ return foo
+ @template
+ @imports('foo.bar')
+ def bar():
+ return foo.bar
+ @template
+ @imports('foo.bar')
+ def bar_upper():
+ return foo
+ @template
+ @imports(_from='foo', _import='bar')
+ def from_import():
+ return bar
+ @template
+ @imports(_from='foo', _import='bar', _as='custom_name')
+ def from_import_as():
+ return custom_name
+ @template
+ @imports(_import='foo', _as='custom_name')
+ def import_as():
+ return custom_name
+ """
+ ),
+ sandbox,
+ )
+ self.assertIs(sandbox["toplevel"](), foo)
+ self.assertIs(sandbox["bar"](), bar)
+ self.assertIs(sandbox["bar_upper"](), foo)
+ self.assertIs(sandbox["from_import"](), bar)
+ self.assertIs(sandbox["from_import_as"](), bar)
+ self.assertIs(sandbox["import_as"](), foo)
+
+ def test_os_path(self):
+ config = self.get_config(["--with-imports=%s" % __file__])
+ self.assertIn("HAS_ABSPATH", config)
+ self.assertEqual(config["HAS_ABSPATH"], True)
+ self.assertIn("HAS_GETATIME", config)
+ self.assertEqual(config["HAS_GETATIME"], True)
+ self.assertIn("HAS_GETATIME2", config)
+ self.assertEqual(config["HAS_GETATIME2"], False)
+
+ def test_template_call(self):
+ config = self.get_config(env={"CC": "gcc"})
+ self.assertIn("TEMPLATE_VALUE", config)
+ self.assertEqual(config["TEMPLATE_VALUE"], 42)
+ self.assertIn("TEMPLATE_VALUE_2", config)
+ self.assertEqual(config["TEMPLATE_VALUE_2"], 21)
+
+ def test_template_imports(self):
+ config = self.get_config(["--enable-imports-in-template"])
+ self.assertIn("PLATFORM", config)
+ self.assertEqual(config["PLATFORM"], sys.platform)
+
+ def test_decorators(self):
+ config = {}
+ out = StringIO()
+ sandbox = ConfigureSandbox(config, {}, ["configure"], out, out)
+
+ sandbox.include_file(mozpath.join(test_data_path, "decorators.configure"))
+
+ self.assertNotIn("FOO", sandbox)
+ self.assertNotIn("BAR", sandbox)
+ self.assertNotIn("QUX", sandbox)
+
+ def test_set_config(self):
+ def get_config(*args):
+ return self.get_config(*args, configure="set_config.configure")
+
+ help, config = get_config(["--help"])
+ self.assertEqual(config, {})
+
+ config = get_config(["--set-foo"])
+ self.assertIn("FOO", config)
+ self.assertEqual(config["FOO"], True)
+
+ config = get_config(["--set-bar"])
+ self.assertNotIn("FOO", config)
+ self.assertIn("BAR", config)
+ self.assertEqual(config["BAR"], True)
+
+ config = get_config(["--set-value=qux"])
+ self.assertIn("VALUE", config)
+ self.assertEqual(config["VALUE"], "qux")
+
+ config = get_config(["--set-name=hoge"])
+ self.assertIn("hoge", config)
+ self.assertEqual(config["hoge"], True)
+
+ config = get_config([])
+ self.assertEqual(config, {"BAR": False})
+
+ with self.assertRaises(ConfigureError):
+ # Both --set-foo and --set-name=FOO are going to try to
+ # set_config('FOO'...)
+ get_config(["--set-foo", "--set-name=FOO"])
+
+ def test_set_config_when(self):
+ with self.moz_configure(
+ """
+ option('--with-qux', help='qux')
+ set_config('FOO', 'foo', when=True)
+ set_config('BAR', 'bar', when=False)
+ set_config('QUX', 'qux', when='--with-qux')
+ """
+ ):
+ config = self.get_config()
+ self.assertEqual(
+ config,
+ {
+ "FOO": "foo",
+ },
+ )
+ config = self.get_config(["--with-qux"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": "foo",
+ "QUX": "qux",
+ },
+ )
+
+ def test_set_config_when_disable(self):
+ with self.moz_configure(
+ """
+ option('--disable-baz', help='Disable baz')
+ set_config('BAZ', True, when='--enable-baz')
+ """
+ ):
+ config = self.get_config()
+ self.assertEqual(config["BAZ"], True)
+ config = self.get_config(["--enable-baz"])
+ self.assertEqual(config["BAZ"], True)
+ config = self.get_config(["--disable-baz"])
+ self.assertEqual(config, {})
+
+ def test_set_define(self):
+ def get_config(*args):
+ return self.get_config(*args, configure="set_define.configure")
+
+ help, config = get_config(["--help"])
+ self.assertEqual(config, {"DEFINES": {}})
+
+ config = get_config(["--set-foo"])
+ self.assertIn("FOO", config["DEFINES"])
+ self.assertEqual(config["DEFINES"]["FOO"], True)
+
+ config = get_config(["--set-bar"])
+ self.assertNotIn("FOO", config["DEFINES"])
+ self.assertIn("BAR", config["DEFINES"])
+ self.assertEqual(config["DEFINES"]["BAR"], True)
+
+ config = get_config(["--set-value=qux"])
+ self.assertIn("VALUE", config["DEFINES"])
+ self.assertEqual(config["DEFINES"]["VALUE"], "qux")
+
+ config = get_config(["--set-name=hoge"])
+ self.assertIn("hoge", config["DEFINES"])
+ self.assertEqual(config["DEFINES"]["hoge"], True)
+
+ config = get_config([])
+ self.assertEqual(config["DEFINES"], {"BAR": False})
+
+ with self.assertRaises(ConfigureError):
+ # Both --set-foo and --set-name=FOO are going to try to
+ # set_define('FOO'...)
+ get_config(["--set-foo", "--set-name=FOO"])
+
+ def test_set_define_when(self):
+ with self.moz_configure(
+ """
+ option('--with-qux', help='qux')
+ set_define('FOO', 'foo', when=True)
+ set_define('BAR', 'bar', when=False)
+ set_define('QUX', 'qux', when='--with-qux')
+ """
+ ):
+ config = self.get_config()
+ self.assertEqual(
+ config["DEFINES"],
+ {
+ "FOO": "foo",
+ },
+ )
+ config = self.get_config(["--with-qux"])
+ self.assertEqual(
+ config["DEFINES"],
+ {
+ "FOO": "foo",
+ "QUX": "qux",
+ },
+ )
+
+ def test_set_define_when_disable(self):
+ with self.moz_configure(
+ """
+ option('--disable-baz', help='Disable baz')
+ set_define('BAZ', True, when='--enable-baz')
+ """
+ ):
+ config = self.get_config()
+ self.assertEqual(config["DEFINES"]["BAZ"], True)
+ config = self.get_config(["--enable-baz"])
+ self.assertEqual(config["DEFINES"]["BAZ"], True)
+ config = self.get_config(["--disable-baz"])
+ self.assertEqual(config["DEFINES"], {})
+
+ def test_imply_option_simple(self):
+ def get_config(*args):
+ return self.get_config(*args, configure="imply_option/simple.configure")
+
+ help, config = get_config(["--help"])
+ self.assertEqual(config, {})
+
+ config = get_config([])
+ self.assertEqual(config, {})
+
+ config = get_config(["--enable-foo"])
+ self.assertIn("BAR", config)
+ self.assertEqual(config["BAR"], PositiveOptionValue())
+
+ with self.assertRaises(InvalidOptionError) as e:
+ get_config(["--enable-foo", "--disable-bar"])
+
+ self.assertEqual(
+ str(e.exception),
+ "'--enable-bar' implied by '--enable-foo' conflicts with "
+ "'--disable-bar' from the command-line",
+ )
+
+ def test_imply_option_negative(self):
+ def get_config(*args):
+ return self.get_config(*args, configure="imply_option/negative.configure")
+
+ help, config = get_config(["--help"])
+ self.assertEqual(config, {})
+
+ config = get_config([])
+ self.assertEqual(config, {})
+
+ config = get_config(["--enable-foo"])
+ self.assertIn("BAR", config)
+ self.assertEqual(config["BAR"], NegativeOptionValue())
+
+ with self.assertRaises(InvalidOptionError) as e:
+ get_config(["--enable-foo", "--enable-bar"])
+
+ self.assertEqual(
+ str(e.exception),
+ "'--disable-bar' implied by '--enable-foo' conflicts with "
+ "'--enable-bar' from the command-line",
+ )
+
+ config = get_config(["--disable-hoge"])
+ self.assertIn("BAR", config)
+ self.assertEqual(config["BAR"], NegativeOptionValue())
+
+ with self.assertRaises(InvalidOptionError) as e:
+ get_config(["--disable-hoge", "--enable-bar"])
+
+ self.assertEqual(
+ str(e.exception),
+ "'--disable-bar' implied by '--disable-hoge' conflicts with "
+ "'--enable-bar' from the command-line",
+ )
+
+ def test_imply_option_values(self):
+ def get_config(*args):
+ return self.get_config(*args, configure="imply_option/values.configure")
+
+ help, config = get_config(["--help"])
+ self.assertEqual(config, {})
+
+ config = get_config([])
+ self.assertEqual(config, {})
+
+ config = get_config(["--enable-foo=a"])
+ self.assertIn("BAR", config)
+ self.assertEqual(config["BAR"], PositiveOptionValue(("a",)))
+
+ config = get_config(["--enable-foo=a,b"])
+ self.assertIn("BAR", config)
+ self.assertEqual(config["BAR"], PositiveOptionValue(("a", "b")))
+
+ with self.assertRaises(InvalidOptionError) as e:
+ get_config(["--enable-foo=a,b", "--disable-bar"])
+
+ self.assertEqual(
+ str(e.exception),
+ "'--enable-bar=a,b' implied by '--enable-foo' conflicts with "
+ "'--disable-bar' from the command-line",
+ )
+
+ def test_imply_option_infer(self):
+ def get_config(*args):
+ return self.get_config(*args, configure="imply_option/infer.configure")
+
+ help, config = get_config(["--help"])
+ self.assertEqual(config, {})
+
+ config = get_config([])
+ self.assertEqual(config, {})
+
+ with self.assertRaises(InvalidOptionError) as e:
+ get_config(["--enable-foo", "--disable-bar"])
+
+ self.assertEqual(
+ str(e.exception),
+ "'--enable-bar' implied by '--enable-foo' conflicts with "
+ "'--disable-bar' from the command-line",
+ )
+
+ with self.assertRaises(ConfigureError) as e:
+ self.get_config([], configure="imply_option/infer_ko.configure")
+
+ self.assertEqual(
+ str(e.exception),
+ "Cannot infer what implies '--enable-bar'. Please add a `reason` "
+ "to the `imply_option` call.",
+ )
+
+ def test_imply_option_immediate_value(self):
+ def get_config(*args):
+ return self.get_config(*args, configure="imply_option/imm.configure")
+
+ help, config = get_config(["--help"])
+ self.assertEqual(config, {})
+
+ config = get_config([])
+ self.assertEqual(config, {})
+
+ config_path = mozpath.abspath(
+ mozpath.join(test_data_path, "imply_option", "imm.configure")
+ )
+
+ with self.assertRaisesRegexp(
+ InvalidOptionError,
+ "--enable-foo' implied by 'imply_option at %s:7' conflicts "
+ "with '--disable-foo' from the command-line" % config_path,
+ ):
+ get_config(["--disable-foo"])
+
+ with self.assertRaisesRegexp(
+ InvalidOptionError,
+ "--enable-bar=foo,bar' implied by 'imply_option at %s:18' "
+ "conflicts with '--enable-bar=a,b,c' from the command-line" % config_path,
+ ):
+ get_config(["--enable-bar=a,b,c"])
+
+ with self.assertRaisesRegexp(
+ InvalidOptionError,
+ "--enable-baz=BAZ' implied by 'imply_option at %s:29' "
+ "conflicts with '--enable-baz=QUUX' from the command-line" % config_path,
+ ):
+ get_config(["--enable-baz=QUUX"])
+
+ def test_imply_option_failures(self):
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ imply_option('--with-foo', ('a',), 'bar')
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception),
+ "`--with-foo`, emitted from `%s` line 2, is unknown."
+ % mozpath.join(test_data_path, "moz.configure"),
+ )
+
+ with self.assertRaises(TypeError) as e:
+ with self.moz_configure(
+ """
+ imply_option('--with-foo', 42, 'bar')
+
+ option('--with-foo', help='foo')
+ @depends('--with-foo')
+ def foo(value):
+ return value
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Unexpected type: 'int'")
+
+ def test_imply_option_when(self):
+ with self.moz_configure(
+ """
+ option('--with-foo', help='foo')
+ imply_option('--with-qux', True, when='--with-foo')
+ option('--with-qux', help='qux')
+ set_config('QUX', depends('--with-qux')(lambda x: x))
+ """
+ ):
+ config = self.get_config()
+ self.assertEqual(
+ config,
+ {
+ "QUX": NegativeOptionValue(),
+ },
+ )
+
+ config = self.get_config(["--with-foo"])
+ self.assertEqual(
+ config,
+ {
+ "QUX": PositiveOptionValue(),
+ },
+ )
+
+ def test_imply_option_dependency_loop(self):
+ with self.moz_configure(
+ """
+ option('--without-foo', help='foo')
+
+ @depends('--with-foo')
+ def qux_default(foo):
+ return bool(foo)
+
+ option('--with-qux', default=qux_default, help='qux')
+
+ imply_option('--with-foo', depends('--with-qux')(lambda x: x or None))
+
+ set_config('FOO', depends('--with-foo')(lambda x: x))
+ set_config('QUX', depends('--with-qux')(lambda x: x))
+ """
+ ):
+ config = self.get_config()
+ self.assertEqual(
+ config,
+ {
+ "FOO": PositiveOptionValue(),
+ "QUX": PositiveOptionValue(),
+ },
+ )
+
+ config = self.get_config(["--without-foo"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": NegativeOptionValue(),
+ "QUX": NegativeOptionValue(),
+ },
+ )
+
+ config = self.get_config(["--with-qux"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": PositiveOptionValue(),
+ "QUX": PositiveOptionValue(),
+ },
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ config = self.get_config(["--without-foo", "--with-qux"])
+
+ self.assertEqual(
+ str(e.exception),
+ "'--with-foo' implied by '--with-qux' conflicts "
+ "with '--without-foo' from the command-line",
+ )
+
+ config = self.get_config(["--without-qux"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": PositiveOptionValue(),
+ "QUX": NegativeOptionValue(),
+ },
+ )
+
+ with self.moz_configure(
+ """
+ option('--with-foo', help='foo')
+
+ @depends('--with-foo')
+ def qux_default(foo):
+ return bool(foo)
+
+ option('--with-qux', default=qux_default, help='qux')
+
+ imply_option('--with-foo', depends('--with-qux')(lambda x: x or None))
+
+ set_config('FOO', depends('--with-foo')(lambda x: x))
+ set_config('QUX', depends('--with-qux')(lambda x: x))
+ """
+ ):
+ config = self.get_config()
+ self.assertEqual(
+ config,
+ {
+ "FOO": NegativeOptionValue(),
+ "QUX": NegativeOptionValue(),
+ },
+ )
+
+ config = self.get_config(["--with-foo"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": PositiveOptionValue(),
+ "QUX": PositiveOptionValue(),
+ },
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ config = self.get_config(["--with-qux"])
+
+ self.assertEqual(
+ str(e.exception),
+ "'--with-foo' implied by '--with-qux' conflicts "
+ "with '--without-foo' from the default",
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ config = self.get_config(["--without-foo", "--with-qux"])
+
+ self.assertEqual(
+ str(e.exception),
+ "'--with-foo' implied by '--with-qux' conflicts "
+ "with '--without-foo' from the command-line",
+ )
+
+ config = self.get_config(["--without-qux"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": NegativeOptionValue(),
+ "QUX": NegativeOptionValue(),
+ },
+ )
+
+ config_path = mozpath.abspath(mozpath.join(test_data_path, "moz.configure"))
+
+ # Same test as above, but using `when` in the `imply_option`.
+ with self.moz_configure(
+ """
+ option('--with-foo', help='foo')
+
+ @depends('--with-foo')
+ def qux_default(foo):
+ return bool(foo)
+
+ option('--with-qux', default=qux_default, help='qux')
+
+ imply_option('--with-foo', True, when='--with-qux')
+
+ set_config('FOO', depends('--with-foo')(lambda x: x))
+ set_config('QUX', depends('--with-qux')(lambda x: x))
+ """
+ ):
+ config = self.get_config()
+ self.assertEqual(
+ config,
+ {
+ "FOO": NegativeOptionValue(),
+ "QUX": NegativeOptionValue(),
+ },
+ )
+
+ config = self.get_config(["--with-foo"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": PositiveOptionValue(),
+ "QUX": PositiveOptionValue(),
+ },
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ config = self.get_config(["--with-qux"])
+
+ self.assertEqual(
+ str(e.exception),
+ "'--with-foo' implied by 'imply_option at %s:10' conflicts "
+ "with '--without-foo' from the default" % config_path,
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ config = self.get_config(["--without-foo", "--with-qux"])
+
+ self.assertEqual(
+ str(e.exception),
+ "'--with-foo' implied by 'imply_option at %s:10' conflicts "
+ "with '--without-foo' from the command-line" % config_path,
+ )
+
+ config = self.get_config(["--without-qux"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": NegativeOptionValue(),
+ "QUX": NegativeOptionValue(),
+ },
+ )
+
+ def test_imply_option_recursion(self):
+ config_path = mozpath.abspath(mozpath.join(test_data_path, "moz.configure"))
+
+ message = (
+ "'--without-foo' appears somewhere in the direct or indirect dependencies "
+ "when resolving imply_option at %s:8" % config_path
+ )
+
+ with self.moz_configure(
+ """
+ option('--without-foo', help='foo')
+
+ imply_option('--with-qux', depends('--with-foo')(lambda x: x or None))
+
+ option('--with-qux', help='qux')
+
+ imply_option('--with-foo', depends('--with-qux')(lambda x: x or None))
+
+ set_config('FOO', depends('--with-foo')(lambda x: x))
+ set_config('QUX', depends('--with-qux')(lambda x: x))
+ """
+ ):
+ # Note: no error is detected when the depends function in the
+ # imply_options resolve to None, which disables the imply_option.
+
+ with self.assertRaises(ConfigureError) as e:
+ self.get_config()
+
+ self.assertEqual(str(e.exception), message)
+
+ with self.assertRaises(ConfigureError) as e:
+ self.get_config(["--with-qux"])
+
+ self.assertEqual(str(e.exception), message)
+
+ with self.assertRaises(ConfigureError) as e:
+ self.get_config(["--without-foo", "--with-qux"])
+
+ self.assertEqual(str(e.exception), message)
+
+ def test_option_failures(self):
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure('option("--with-foo", help="foo")'):
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception),
+ "Option `--with-foo` is not handled ; reference it with a @depends",
+ )
+
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ option("--with-foo", help="foo")
+ option("--with-foo", help="foo")
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Option `--with-foo` already defined")
+
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ option(env="MOZ_FOO", help="foo")
+ option(env="MOZ_FOO", help="foo")
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Option `MOZ_FOO` already defined")
+
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ option('--with-foo', env="MOZ_FOO", help="foo")
+ option(env="MOZ_FOO", help="foo")
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Option `MOZ_FOO` already defined")
+
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ option(env="MOZ_FOO", help="foo")
+ option('--with-foo', env="MOZ_FOO", help="foo")
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Option `MOZ_FOO` already defined")
+
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ option('--with-foo', env="MOZ_FOO", help="foo")
+ option('--with-foo', help="foo")
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Option `--with-foo` already defined")
+
+ def test_option_when(self):
+ with self.moz_configure(
+ """
+ option('--with-foo', help='foo', when=True)
+ option('--with-bar', help='bar', when=False)
+ option('--with-qux', env="QUX", help='qux', when='--with-foo')
+
+ set_config('FOO', depends('--with-foo', when=True)(lambda x: x))
+ set_config('BAR', depends('--with-bar', when=False)(lambda x: x))
+ set_config('QUX', depends('--with-qux', when='--with-foo')(lambda x: x))
+ """
+ ):
+ config = self.get_config()
+ self.assertEqual(
+ config,
+ {
+ "FOO": NegativeOptionValue(),
+ },
+ )
+
+ config = self.get_config(["--with-foo"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": PositiveOptionValue(),
+ "QUX": NegativeOptionValue(),
+ },
+ )
+
+ config = self.get_config(["--with-foo", "--with-qux"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": PositiveOptionValue(),
+ "QUX": PositiveOptionValue(),
+ },
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(["--with-bar"])
+
+ self.assertEqual(
+ str(e.exception), "--with-bar is not available in this configuration"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(["--with-qux"])
+
+ self.assertEqual(
+ str(e.exception), "--with-qux is not available in this configuration"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(["QUX=1"])
+
+ self.assertEqual(
+ str(e.exception), "QUX is not available in this configuration"
+ )
+
+ config = self.get_config(env={"QUX": "1"})
+ self.assertEqual(
+ config,
+ {
+ "FOO": NegativeOptionValue(),
+ },
+ )
+
+ help, config = self.get_config(["--help"])
+ self.assertEqual(
+ help.replace("\\", "/"),
+ textwrap.dedent(
+ """\
+ Usage: configure [options]
+
+ Options: [defaults in brackets after descriptions]
+ Help options:
+ --help print this message
+
+ Options from python/mozbuild/mozbuild/test/configure/data/moz.configure:
+ --with-foo foo
+
+
+ Environment variables:
+ """
+ ),
+ )
+
+ help, config = self.get_config(["--help", "--with-foo"])
+ self.assertEqual(
+ help.replace("\\", "/"),
+ textwrap.dedent(
+ """\
+ Usage: configure [options]
+
+ Options: [defaults in brackets after descriptions]
+ Help options:
+ --help print this message
+
+ Options from python/mozbuild/mozbuild/test/configure/data/moz.configure:
+ --with-foo foo
+ --with-qux qux
+
+
+ Environment variables:
+ """
+ ),
+ )
+
+ with self.moz_configure(
+ """
+ option('--with-foo', help='foo', when=True)
+ set_config('FOO', depends('--with-foo')(lambda x: x))
+ """
+ ):
+ with self.assertRaises(ConfigureError) as e:
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception),
+ "@depends function needs the same `when` as " "options it depends on",
+ )
+
+ with self.moz_configure(
+ """
+ @depends(when=True)
+ def always():
+ return True
+ @depends(when=True)
+ def always2():
+ return True
+ option('--with-foo', help='foo', when=always)
+ set_config('FOO', depends('--with-foo', when=always2)(lambda x: x))
+ """
+ ):
+ with self.assertRaises(ConfigureError) as e:
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception),
+ "@depends function needs the same `when` as " "options it depends on",
+ )
+
+ with self.moz_configure(
+ """
+ @depends(when=True)
+ def always():
+ return True
+ @depends(when=True)
+ def always2():
+ return True
+ with only_when(always2):
+ option('--with-foo', help='foo', when=always)
+ # include() triggers resolution of its dependencies, and their
+ # side effects.
+ include(depends('--with-foo', when=always)(lambda x: x))
+ # The sandbox should figure that the `when` here is
+ # appropriate. Bad behavior in CombinedDependsFunction.__eq__
+ # made this fail in the past.
+ set_config('FOO', depends('--with-foo', when=always)(lambda x: x))
+ """
+ ):
+ self.get_config()
+
+ with self.moz_configure(
+ """
+ option('--with-foo', help='foo')
+ option('--without-bar', help='bar', when='--with-foo')
+ option('--with-qux', help='qux', when='--with-bar')
+ set_config('QUX', True, when='--with-qux')
+ """
+ ):
+ # These are valid:
+ self.get_config(["--with-foo"])
+ self.get_config(["--with-foo", "--with-bar"])
+ self.get_config(["--with-foo", "--without-bar"])
+ self.get_config(["--with-foo", "--with-bar", "--with-qux"])
+ self.get_config(["--with-foo", "--with-bar", "--without-qux"])
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(["--with-bar"])
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(["--without-bar"])
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(["--with-qux"])
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(["--without-qux"])
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(["--with-foo", "--without-bar", "--with-qux"])
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(["--with-foo", "--without-bar", "--without-qux"])
+
+ def test_include_failures(self):
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure('include("../foo.configure")'):
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception),
+ "Cannot include `%s` because it is not in a subdirectory of `%s`"
+ % (
+ mozpath.normpath(mozpath.join(test_data_path, "..", "foo.configure")),
+ mozpath.normsep(test_data_path),
+ ),
+ )
+
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ include('extra.configure')
+ include('extra.configure')
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception),
+ "Cannot include `%s` because it was included already."
+ % mozpath.normpath(mozpath.join(test_data_path, "extra.configure")),
+ )
+
+ with self.assertRaises(TypeError) as e:
+ with self.moz_configure(
+ """
+ include(42)
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Unexpected type: 'int'")
+
+ def test_include_when(self):
+ with MockedOpen(
+ {
+ os.path.join(test_data_path, "moz.configure"): textwrap.dedent(
+ """
+ option('--with-foo', help='foo')
+
+ include('always.configure', when=True)
+ include('never.configure', when=False)
+ include('foo.configure', when='--with-foo')
+
+ set_config('FOO', foo)
+ set_config('BAR', bar)
+ set_config('QUX', qux)
+ """
+ ),
+ os.path.join(test_data_path, "always.configure"): textwrap.dedent(
+ """
+ option('--with-bar', help='bar')
+ @depends('--with-bar')
+ def bar(x):
+ if x:
+ return 'bar'
+ """
+ ),
+ os.path.join(test_data_path, "never.configure"): textwrap.dedent(
+ """
+ option('--with-qux', help='qux')
+ @depends('--with-qux')
+ def qux(x):
+ if x:
+ return 'qux'
+ """
+ ),
+ os.path.join(test_data_path, "foo.configure"): textwrap.dedent(
+ """
+ option('--with-foo-really', help='really foo')
+ @depends('--with-foo-really')
+ def foo(x):
+ if x:
+ return 'foo'
+
+ include('foo2.configure', when='--with-foo-really')
+ """
+ ),
+ os.path.join(test_data_path, "foo2.configure"): textwrap.dedent(
+ """
+ set_config('FOO2', True)
+ """
+ ),
+ }
+ ):
+ config = self.get_config()
+ self.assertEqual(config, {})
+
+ config = self.get_config(["--with-foo"])
+ self.assertEqual(config, {})
+
+ config = self.get_config(["--with-bar"])
+ self.assertEqual(
+ config,
+ {
+ "BAR": "bar",
+ },
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(["--with-qux"])
+
+ self.assertEqual(
+ str(e.exception), "--with-qux is not available in this configuration"
+ )
+
+ config = self.get_config(["--with-foo", "--with-foo-really"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": "foo",
+ "FOO2": True,
+ },
+ )
+
+ def test_sandbox_failures(self):
+ with self.assertRaises(KeyError) as e:
+ with self.moz_configure(
+ """
+ include = 42
+ """
+ ):
+ self.get_config()
+
+ self.assertIn("Cannot reassign builtins", str(e.exception))
+
+ with self.assertRaises(KeyError) as e:
+ with self.moz_configure(
+ """
+ foo = 42
+ """
+ ):
+ self.get_config()
+
+ self.assertIn(
+ "Cannot assign `foo` because it is neither a @depends nor a " "@template",
+ str(e.exception),
+ )
+
+ def test_depends_failures(self):
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ @depends()
+ def foo():
+ return
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "@depends needs at least one argument")
+
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ @depends('--with-foo')
+ def foo(value):
+ return value
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception),
+ "'--with-foo' is not a known option. Maybe it's " "declared too late?",
+ )
+
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ @depends('--with-foo=42')
+ def foo(value):
+ return value
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Option must not contain an '='")
+
+ with self.assertRaises(TypeError) as e:
+ with self.moz_configure(
+ """
+ @depends(42)
+ def foo(value):
+ return value
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception),
+ "Cannot use object of type 'int' as argument " "to @depends",
+ )
+
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ @depends('--help')
+ def foo(value):
+ yield
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception), "Cannot decorate generator functions with @depends"
+ )
+
+ with self.assertRaises(TypeError) as e:
+ with self.moz_configure(
+ """
+ @depends('--help')
+ def foo(value):
+ return value
+
+ depends('--help')(foo)
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Cannot nest @depends functions")
+
+ with self.assertRaises(TypeError) as e:
+ with self.moz_configure(
+ """
+ @template
+ def foo(f):
+ pass
+
+ depends('--help')(foo)
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Cannot use a @template function here")
+
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+ @depends('--foo')
+ def foo(value):
+ return value
+
+ foo()
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "The `foo` function may not be called")
+
+ with self.assertRaises(TypeError) as e:
+ with self.moz_configure(
+ """
+ @depends('--help', foo=42)
+ def foo(_):
+ return
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception), "depends_impl() got an unexpected keyword argument 'foo'"
+ )
+
+ def test_depends_when(self):
+ with self.moz_configure(
+ """
+ @depends(when=True)
+ def foo():
+ return 'foo'
+
+ set_config('FOO', foo)
+
+ @depends(when=False)
+ def bar():
+ return 'bar'
+
+ set_config('BAR', bar)
+
+ option('--with-qux', help='qux')
+ @depends(when='--with-qux')
+ def qux():
+ return 'qux'
+
+ set_config('QUX', qux)
+ """
+ ):
+ config = self.get_config()
+ self.assertEqual(
+ config,
+ {
+ "FOO": "foo",
+ },
+ )
+
+ config = self.get_config(["--with-qux"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": "foo",
+ "QUX": "qux",
+ },
+ )
+
+ def test_depends_value(self):
+ with self.moz_configure(
+ """
+ foo = depends(when=True)('foo')
+
+ set_config('FOO', foo)
+
+ bar = depends(when=False)('bar')
+
+ set_config('BAR', bar)
+
+ option('--with-qux', help='qux')
+ @depends(when='--with-qux')
+ def qux():
+ return 'qux'
+
+ set_config('QUX', qux)
+ """
+ ):
+ config = self.get_config()
+ self.assertEqual(
+ config,
+ {
+ "FOO": "foo",
+ },
+ )
+
+ with self.assertRaises(TypeError) as e:
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+
+ depends('--foo')('foo')
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception), "Cannot wrap literal values in @depends with dependencies"
+ )
+
+ def test_imports_failures(self):
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ @imports('os')
+ @template
+ def foo(value):
+ return value
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "@imports must appear after @template")
+
+ with self.assertRaises(ConfigureError) as e:
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+ @imports('os')
+ @depends('--foo')
+ def foo(value):
+ return value
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "@imports must appear after @depends")
+
+ for import_ in (
+ "42",
+ "_from=42, _import='os'",
+ "_from='os', _import='path', _as=42",
+ ):
+ with self.assertRaises(TypeError) as e:
+ with self.moz_configure(
+ """
+ @imports(%s)
+ @template
+ def foo(value):
+ return value
+ """
+ % import_
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Unexpected type: 'int'")
+
+ with self.assertRaises(TypeError) as e:
+ with self.moz_configure(
+ """
+ @imports('os', 42)
+ @template
+ def foo(value):
+ return value
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Unexpected type: 'int'")
+
+ with self.assertRaises(ValueError) as e:
+ with self.moz_configure(
+ """
+ @imports('os*')
+ def foo(value):
+ return value
+ """
+ ):
+ self.get_config()
+
+ self.assertEqual(str(e.exception), "Invalid argument to @imports: 'os*'")
+
+ def test_only_when(self):
+ moz_configure = """
+ option('--enable-when', help='when')
+ @depends('--enable-when', '--help')
+ def when(value, _):
+ return bool(value)
+
+ with only_when(when):
+ option('--foo', nargs='*', help='foo')
+ @depends('--foo')
+ def foo(value):
+ return value
+
+ set_config('FOO', foo)
+ set_define('FOO', foo)
+
+ # It is possible to depend on a function defined in a only_when
+ # block. It then resolves to `None`.
+ set_config('BAR', depends(foo)(lambda x: x))
+ set_define('BAR', depends(foo)(lambda x: x))
+ """
+
+ with self.moz_configure(moz_configure):
+ config = self.get_config()
+ self.assertEqual(
+ config,
+ {
+ "DEFINES": {},
+ },
+ )
+
+ config = self.get_config(["--enable-when"])
+ self.assertEqual(
+ config,
+ {
+ "BAR": NegativeOptionValue(),
+ "FOO": NegativeOptionValue(),
+ "DEFINES": {
+ "BAR": NegativeOptionValue(),
+ "FOO": NegativeOptionValue(),
+ },
+ },
+ )
+
+ config = self.get_config(["--enable-when", "--foo=bar"])
+ self.assertEqual(
+ config,
+ {
+ "BAR": PositiveOptionValue(["bar"]),
+ "FOO": PositiveOptionValue(["bar"]),
+ "DEFINES": {
+ "BAR": PositiveOptionValue(["bar"]),
+ "FOO": PositiveOptionValue(["bar"]),
+ },
+ },
+ )
+
+ # The --foo option doesn't exist when --enable-when is not given.
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config(["--foo"])
+
+ self.assertEqual(
+ str(e.exception), "--foo is not available in this configuration"
+ )
+
+ # Cannot depend on an option defined in a only_when block, because we
+ # don't know what OptionValue would make sense.
+ with self.moz_configure(
+ moz_configure
+ + """
+ set_config('QUX', depends('--foo')(lambda x: x))
+ """
+ ):
+ with self.assertRaises(ConfigureError) as e:
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception),
+ "@depends function needs the same `when` as " "options it depends on",
+ )
+
+ with self.moz_configure(
+ moz_configure
+ + """
+ set_config('QUX', depends('--foo', when=when)(lambda x: x))
+ """
+ ):
+ self.get_config(["--enable-when"])
+
+ # Using imply_option for an option defined in a only_when block fails
+ # similarly if the imply_option happens outside the block.
+ with self.moz_configure(
+ """
+ imply_option('--foo', True)
+ """
+ + moz_configure
+ ):
+ with self.assertRaises(InvalidOptionError) as e:
+ self.get_config()
+
+ self.assertEqual(
+ str(e.exception), "--foo is not available in this configuration"
+ )
+
+ # And similarly doesn't fail when the condition is true.
+ with self.moz_configure(
+ """
+ imply_option('--foo', True)
+ """
+ + moz_configure
+ ):
+ self.get_config(["--enable-when"])
+
+ def test_depends_binary_ops(self):
+ with self.moz_configure(
+ """
+ option('--foo', nargs=1, help='foo')
+ @depends('--foo')
+ def foo(value):
+ return value or 0
+
+ option('--bar', nargs=1, help='bar')
+ @depends('--bar')
+ def bar(value):
+ return value or ''
+
+ option('--baz', nargs=1, help='baz')
+ @depends('--baz')
+ def baz(value):
+ return value
+
+ set_config('FOOorBAR', foo | bar)
+ set_config('FOOorBARorBAZ', foo | bar | baz)
+ set_config('FOOandBAR', foo & bar)
+ set_config('FOOandBARandBAZ', foo & bar & baz)
+ """
+ ):
+ for foo_opt, foo_value in (
+ ("", 0),
+ ("--foo=foo", PositiveOptionValue(("foo",))),
+ ):
+ for bar_opt, bar_value in (
+ ("", ""),
+ ("--bar=bar", PositiveOptionValue(("bar",))),
+ ):
+ for baz_opt, baz_value in (
+ ("", NegativeOptionValue()),
+ ("--baz=baz", PositiveOptionValue(("baz",))),
+ ):
+ config = self.get_config(
+ [x for x in (foo_opt, bar_opt, baz_opt) if x]
+ )
+ self.assertEqual(
+ config,
+ {
+ "FOOorBAR": foo_value or bar_value,
+ "FOOorBARorBAZ": foo_value or bar_value or baz_value,
+ "FOOandBAR": foo_value and bar_value,
+ "FOOandBARandBAZ": foo_value
+ and bar_value
+ and baz_value,
+ },
+ )
+
+ def test_depends_getattr(self):
+ with self.moz_configure(
+ """
+ @imports(_from='mozbuild.util', _import='ReadOnlyNamespace')
+ def namespace(**kwargs):
+ return ReadOnlyNamespace(**kwargs)
+
+ option('--foo', nargs=1, help='foo')
+ @depends('--foo')
+ def foo(value):
+ return value
+
+ option('--bar', nargs=1, help='bar')
+ @depends('--bar')
+ def bar(value):
+ return value or None
+
+ @depends(foo, bar)
+ def foobar(foo, bar):
+ return namespace(foo=foo, bar=bar)
+
+ set_config('FOO', foobar.foo)
+ set_config('BAR', foobar.bar)
+ set_config('BAZ', foobar.baz)
+ """
+ ):
+ config = self.get_config()
+ self.assertEqual(
+ config,
+ {
+ "FOO": NegativeOptionValue(),
+ },
+ )
+
+ config = self.get_config(["--foo=foo"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": PositiveOptionValue(("foo",)),
+ },
+ )
+
+ config = self.get_config(["--bar=bar"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": NegativeOptionValue(),
+ "BAR": PositiveOptionValue(("bar",)),
+ },
+ )
+
+ config = self.get_config(["--foo=foo", "--bar=bar"])
+ self.assertEqual(
+ config,
+ {
+ "FOO": PositiveOptionValue(("foo",)),
+ "BAR": PositiveOptionValue(("bar",)),
+ },
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/configure/test_lint.py b/python/mozbuild/mozbuild/test/configure/test_lint.py
new file mode 100644
index 0000000000..7ecac769c3
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_lint.py
@@ -0,0 +1,487 @@
+# 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/.
+
+import contextlib
+import os
+import sys
+import textwrap
+import traceback
+import unittest
+
+import mozpack.path as mozpath
+from mozunit import MockedOpen, main
+
+from mozbuild.configure import ConfigureError
+from mozbuild.configure.lint import LintSandbox
+
+test_data_path = mozpath.abspath(mozpath.dirname(__file__))
+test_data_path = mozpath.join(test_data_path, "data")
+
+
+class TestLint(unittest.TestCase):
+ def lint_test(self, options=[], env={}):
+ sandbox = LintSandbox(env, ["configure"] + options)
+
+ sandbox.run(mozpath.join(test_data_path, "moz.configure"))
+
+ def moz_configure(self, source):
+ return MockedOpen(
+ {os.path.join(test_data_path, "moz.configure"): textwrap.dedent(source)}
+ )
+
+ @contextlib.contextmanager
+ def assertRaisesFromLine(self, exc_type, line):
+ with self.assertRaises(exc_type) as e:
+ yield e
+
+ _, _, tb = sys.exc_info()
+ self.assertEqual(
+ traceback.extract_tb(tb)[-1][:2],
+ (mozpath.join(test_data_path, "moz.configure"), line),
+ )
+
+ def test_configure_testcase(self):
+ # Lint python/mozbuild/mozbuild/test/configure/data/moz.configure
+ self.lint_test()
+
+ def test_depends_failures(self):
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+ @depends('--foo')
+ def foo(value):
+ return value
+
+ @depends('--help', foo)
+ @imports('os')
+ def bar(help, foo):
+ return foo
+ """
+ ):
+ self.lint_test()
+
+ with self.assertRaisesFromLine(ConfigureError, 7) as e:
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+ @depends('--foo')
+ def foo(value):
+ return value
+
+ @depends('--help', foo)
+ def bar(help, foo):
+ return foo
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(str(e.exception), "The dependency on `--help` is unused")
+
+ with self.assertRaisesFromLine(ConfigureError, 3) as e:
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+ @depends('--foo')
+ @imports('os')
+ def foo(value):
+ return value
+
+ @depends('--help', foo)
+ @imports('os')
+ def bar(help, foo):
+ return foo
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(
+ str(e.exception),
+ "Missing '--help' dependency because `bar` depends on '--help' and `foo`",
+ )
+
+ with self.assertRaisesFromLine(ConfigureError, 7) as e:
+ with self.moz_configure(
+ """
+ @template
+ def tmpl():
+ qux = 42
+
+ option('--foo', help='foo')
+ @depends('--foo')
+ def foo(value):
+ qux
+ return value
+
+ @depends('--help', foo)
+ @imports('os')
+ def bar(help, foo):
+ return foo
+ tmpl()
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(
+ str(e.exception),
+ "Missing '--help' dependency because `bar` depends on '--help' and `foo`",
+ )
+
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+ @depends('--foo')
+ def foo(value):
+ return value
+
+ include(foo)
+ """
+ ):
+ self.lint_test()
+
+ with self.assertRaisesFromLine(ConfigureError, 3) as e:
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+ @depends('--foo')
+ @imports('os')
+ def foo(value):
+ return value
+
+ include(foo)
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(str(e.exception), "Missing '--help' dependency")
+
+ with self.assertRaisesFromLine(ConfigureError, 3) as e:
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+ @depends('--foo')
+ @imports('os')
+ def foo(value):
+ return value
+
+ @depends(foo)
+ def bar(value):
+ return value
+
+ include(bar)
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(str(e.exception), "Missing '--help' dependency")
+
+ with self.assertRaisesFromLine(ConfigureError, 3) as e:
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+ @depends('--foo')
+ @imports('os')
+ def foo(value):
+ return value
+
+ option('--bar', help='bar', when=foo)
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(str(e.exception), "Missing '--help' dependency")
+
+ # This would have failed with "Missing '--help' dependency"
+ # in the past, because of the reference to the builtin False.
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+ @depends('--foo')
+ def foo(value):
+ return False or value
+
+ option('--bar', help='bar', when=foo)
+ """
+ ):
+ self.lint_test()
+
+ # However, when something that is normally a builtin is overridden,
+ # we should still want the dependency on --help.
+ with self.assertRaisesFromLine(ConfigureError, 7) as e:
+ with self.moz_configure(
+ """
+ @template
+ def tmpl():
+ sorted = 42
+
+ option('--foo', help='foo')
+ @depends('--foo')
+ def foo(value):
+ return sorted
+
+ option('--bar', help='bar', when=foo)
+ tmpl()
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(str(e.exception), "Missing '--help' dependency")
+
+ # There is a default restricted `os` module when there is no explicit
+ # @imports, and it's fine to use it without a dependency on --help.
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+ @depends('--foo')
+ def foo(value):
+ os
+ return value
+
+ include(foo)
+ """
+ ):
+ self.lint_test()
+
+ with self.assertRaisesFromLine(ConfigureError, 3) as e:
+ with self.moz_configure(
+ """
+ option('--foo', help='foo')
+ @depends('--foo')
+ def foo(value):
+ return
+
+ include(foo)
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(str(e.exception), "The dependency on `--foo` is unused")
+
+ with self.assertRaisesFromLine(ConfigureError, 5) as e:
+ with self.moz_configure(
+ """
+ @depends(when=True)
+ def bar():
+ return
+ @depends(bar)
+ def foo(value):
+ return
+
+ include(foo)
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(str(e.exception), "The dependency on `bar` is unused")
+
+ with self.assertRaisesFromLine(ConfigureError, 2) as e:
+ with self.moz_configure(
+ """
+ @depends(depends(when=True)(lambda: None))
+ def foo(value):
+ return
+
+ include(foo)
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(str(e.exception), "The dependency on `<lambda>` is unused")
+
+ with self.assertRaisesFromLine(ConfigureError, 9) as e:
+ with self.moz_configure(
+ """
+ @template
+ def tmpl():
+ @depends(when=True)
+ def bar():
+ return
+ return bar
+ qux = tmpl()
+ @depends(qux)
+ def foo(value):
+ return
+
+ include(foo)
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(str(e.exception), "The dependency on `qux` is unused")
+
+ def test_default_enable(self):
+ # --enable-* with default=True is not allowed.
+ with self.moz_configure(
+ """
+ option('--enable-foo', default=False, help='foo')
+ """
+ ):
+ self.lint_test()
+ with self.assertRaisesFromLine(ConfigureError, 2) as e:
+ with self.moz_configure(
+ """
+ option('--enable-foo', default=True, help='foo')
+ """
+ ):
+ self.lint_test()
+ self.assertEqual(
+ str(e.exception),
+ "--disable-foo should be used instead of " "--enable-foo with default=True",
+ )
+
+ def test_default_disable(self):
+ # --disable-* with default=False is not allowed.
+ with self.moz_configure(
+ """
+ option('--disable-foo', default=True, help='foo')
+ """
+ ):
+ self.lint_test()
+ with self.assertRaisesFromLine(ConfigureError, 2) as e:
+ with self.moz_configure(
+ """
+ option('--disable-foo', default=False, help='foo')
+ """
+ ):
+ self.lint_test()
+ self.assertEqual(
+ str(e.exception),
+ "--enable-foo should be used instead of "
+ "--disable-foo with default=False",
+ )
+
+ def test_default_with(self):
+ # --with-* with default=True is not allowed.
+ with self.moz_configure(
+ """
+ option('--with-foo', default=False, help='foo')
+ """
+ ):
+ self.lint_test()
+ with self.assertRaisesFromLine(ConfigureError, 2) as e:
+ with self.moz_configure(
+ """
+ option('--with-foo', default=True, help='foo')
+ """
+ ):
+ self.lint_test()
+ self.assertEqual(
+ str(e.exception),
+ "--without-foo should be used instead of " "--with-foo with default=True",
+ )
+
+ def test_default_without(self):
+ # --without-* with default=False is not allowed.
+ with self.moz_configure(
+ """
+ option('--without-foo', default=True, help='foo')
+ """
+ ):
+ self.lint_test()
+ with self.assertRaisesFromLine(ConfigureError, 2) as e:
+ with self.moz_configure(
+ """
+ option('--without-foo', default=False, help='foo')
+ """
+ ):
+ self.lint_test()
+ self.assertEqual(
+ str(e.exception),
+ "--with-foo should be used instead of " "--without-foo with default=False",
+ )
+
+ def test_default_func(self):
+ # Help text for an option with variable default should contain
+ # {enable|disable} rule.
+ with self.moz_configure(
+ """
+ option(env='FOO', help='foo')
+ option('--enable-bar', default=depends('FOO')(lambda x: bool(x)),
+ help='{Enable|Disable} bar')
+ """
+ ):
+ self.lint_test()
+ with self.assertRaisesFromLine(ConfigureError, 3) as e:
+ with self.moz_configure(
+ """
+ option(env='FOO', help='foo')
+ option('--enable-bar', default=depends('FOO')(lambda x: bool(x)),\
+ help='Enable bar')
+ """
+ ):
+ self.lint_test()
+ self.assertEqual(
+ str(e.exception),
+ '`help` should contain "{Enable|Disable}" because of '
+ "non-constant default",
+ )
+
+ def test_large_offset(self):
+ with self.assertRaisesFromLine(ConfigureError, 375):
+ with self.moz_configure(
+ """
+ option(env='FOO', help='foo')
+ """
+ + "\n" * 371
+ + """
+ option('--enable-bar', default=depends('FOO')(lambda x: bool(x)),\
+ help='Enable bar')
+ """
+ ):
+ self.lint_test()
+
+ def test_undefined_global(self):
+ with self.assertRaisesFromLine(NameError, 6) as e:
+ with self.moz_configure(
+ """
+ option(env='FOO', help='foo')
+ @depends('FOO')
+ def foo(value):
+ if value:
+ return unknown
+ return value
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(str(e.exception), "global name 'unknown' is not defined")
+
+ # Ideally, this would raise on line 4, where `unknown` is used, but
+ # python disassembly doesn't give use the information.
+ with self.assertRaisesFromLine(NameError, 2) as e:
+ with self.moz_configure(
+ """
+ @template
+ def tmpl():
+ @depends(unknown)
+ def foo(value):
+ if value:
+ return True
+ return foo
+ tmpl()
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(str(e.exception), "global name 'unknown' is not defined")
+
+ def test_unnecessary_imports(self):
+ with self.assertRaisesFromLine(NameError, 3) as e:
+ with self.moz_configure(
+ """
+ option(env='FOO', help='foo')
+ @depends('FOO')
+ @imports(_from='__builtin__', _import='list')
+ def foo(value):
+ if value:
+ return list()
+ return value
+ """
+ ):
+ self.lint_test()
+
+ self.assertEqual(str(e.exception), "builtin 'list' doesn't need to be imported")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/configure/test_moz_configure.py b/python/mozbuild/mozbuild/test/configure/test_moz_configure.py
new file mode 100644
index 0000000000..22129a3970
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_moz_configure.py
@@ -0,0 +1,185 @@
+# 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/.
+
+from mozunit import main
+
+from common import BaseConfigureTest, ConfigureTestSandbox
+from mozbuild.util import ReadOnlyNamespace, exec_, memoized_property
+
+
+def sandbox_class(platform):
+ class ConfigureTestSandboxOverridingPlatform(ConfigureTestSandbox):
+ @memoized_property
+ def _wrapped_sys(self):
+ sys = {}
+ exec_("from sys import *", sys)
+ sys["platform"] = platform
+ return ReadOnlyNamespace(**sys)
+
+ return ConfigureTestSandboxOverridingPlatform
+
+
+class TargetTest(BaseConfigureTest):
+ def get_target(self, args, env={}):
+ if "linux" in self.HOST:
+ platform = "linux2"
+ elif "mingw" in self.HOST or "windows" in self.HOST:
+ platform = "win32"
+ elif "openbsd6" in self.HOST:
+ platform = "openbsd6"
+ else:
+ raise Exception("Missing platform for HOST {}".format(self.HOST))
+ sandbox = self.get_sandbox({}, {}, args, env, cls=sandbox_class(platform))
+ return sandbox._value_for(sandbox["target"]).alias
+
+
+class TestTargetLinux(TargetTest):
+ def test_target(self):
+ self.assertEqual(self.get_target([]), self.HOST)
+ self.assertEqual(self.get_target(["--target=i686"]), "i686-pc-linux-gnu")
+ self.assertEqual(
+ self.get_target(["--target=i686-unknown-linux-gnu"]),
+ "i686-unknown-linux-gnu",
+ )
+ self.assertEqual(
+ self.get_target(["--target=i686-pc-windows-msvc"]), "i686-pc-windows-msvc"
+ )
+
+
+class TestTargetWindows(TargetTest):
+ # BaseConfigureTest uses this as the return value for config.guess
+ HOST = "i686-pc-windows-msvc"
+
+ def test_target(self):
+ self.assertEqual(self.get_target([]), self.HOST)
+ self.assertEqual(
+ self.get_target(["--target=x86_64-pc-windows-msvc"]),
+ "x86_64-pc-windows-msvc",
+ )
+ self.assertEqual(self.get_target(["--target=x86_64"]), "x86_64-pc-windows-msvc")
+
+ # The tests above are actually not realistic, because most Windows
+ # machines will have a few environment variables that make us not
+ # use config.guess.
+
+ # 32-bits process on x86_64 host.
+ env = {
+ "PROCESSOR_ARCHITECTURE": "x86",
+ "PROCESSOR_ARCHITEW6432": "AMD64",
+ }
+ self.assertEqual(self.get_target([], env), "x86_64-pc-windows-msvc")
+ self.assertEqual(
+ self.get_target(["--target=i686-pc-windows-msvc"]), "i686-pc-windows-msvc"
+ )
+ self.assertEqual(self.get_target(["--target=i686"]), "i686-pc-windows-msvc")
+
+ # 64-bits process on x86_64 host.
+ env = {
+ "PROCESSOR_ARCHITECTURE": "AMD64",
+ }
+ self.assertEqual(self.get_target([], env), "x86_64-pc-windows-msvc")
+ self.assertEqual(
+ self.get_target(["--target=i686-pc-windows-msvc"]), "i686-pc-windows-msvc"
+ )
+ self.assertEqual(self.get_target(["--target=i686"]), "i686-pc-windows-msvc")
+
+ # 32-bits process on x86 host.
+ env = {
+ "PROCESSOR_ARCHITECTURE": "x86",
+ }
+ self.assertEqual(self.get_target([], env), "i686-pc-windows-msvc")
+ self.assertEqual(
+ self.get_target(["--target=x86_64-pc-windows-msvc"]),
+ "x86_64-pc-windows-msvc",
+ )
+ self.assertEqual(self.get_target(["--target=x86_64"]), "x86_64-pc-windows-msvc")
+
+ # While host autodection will give us a -windows-msvc triplet, setting host
+ # is expecting to implicitly set the target.
+ self.assertEqual(
+ self.get_target(["--host=x86_64-pc-windows-gnu"]), "x86_64-pc-windows-gnu"
+ )
+ self.assertEqual(
+ self.get_target(["--host=x86_64-pc-mingw32"]), "x86_64-pc-mingw32"
+ )
+
+
+class TestTargetAndroid(TargetTest):
+ HOST = "x86_64-pc-linux-gnu"
+
+ def test_target(self):
+ self.assertEqual(
+ self.get_target(["--enable-project=mobile/android"]),
+ "arm-unknown-linux-androideabi",
+ )
+ self.assertEqual(
+ self.get_target(["--enable-project=mobile/android", "--target=i686"]),
+ "i686-unknown-linux-android",
+ )
+ self.assertEqual(
+ self.get_target(["--enable-project=mobile/android", "--target=x86_64"]),
+ "x86_64-unknown-linux-android",
+ )
+ self.assertEqual(
+ self.get_target(["--enable-project=mobile/android", "--target=aarch64"]),
+ "aarch64-unknown-linux-android",
+ )
+ self.assertEqual(
+ self.get_target(["--enable-project=mobile/android", "--target=arm"]),
+ "arm-unknown-linux-androideabi",
+ )
+
+
+class TestTargetOpenBSD(TargetTest):
+ # config.guess returns amd64 on OpenBSD, which we need to pass through to
+ # config.sub so that it canonicalizes to x86_64.
+ HOST = "amd64-unknown-openbsd6.4"
+
+ def test_target(self):
+ self.assertEqual(self.get_target([]), "x86_64-unknown-openbsd6.4")
+
+ def config_sub(self, stdin, args):
+ if args[0] == "amd64-unknown-openbsd6.4":
+ return 0, "x86_64-unknown-openbsd6.4", ""
+ return super(TestTargetOpenBSD, self).config_sub(stdin, args)
+
+
+class TestMozConfigure(BaseConfigureTest):
+ def test_nsis_version(self):
+ this = self
+
+ class FakeNSIS(object):
+ def __init__(self, version):
+ self.version = version
+
+ def __call__(self, stdin, args):
+ this.assertEqual(args, ("-version",))
+ return 0, self.version, ""
+
+ def check_nsis_version(version):
+ sandbox = self.get_sandbox(
+ {"/usr/bin/makensis": FakeNSIS(version)},
+ {},
+ ["--target=x86_64-pc-windows-msvc", "--disable-bootstrap"],
+ {"PATH": "/usr/bin", "MAKENSISU": "/usr/bin/makensis"},
+ )
+ return sandbox._value_for(sandbox["nsis_version"])
+
+ with self.assertRaises(SystemExit):
+ check_nsis_version("v2.5")
+
+ with self.assertRaises(SystemExit):
+ check_nsis_version("v3.0a2")
+
+ self.assertEqual(check_nsis_version("v3.0b1"), "3.0b1")
+ self.assertEqual(check_nsis_version("v3.0b2"), "3.0b2")
+ self.assertEqual(check_nsis_version("v3.0rc1"), "3.0rc1")
+ self.assertEqual(check_nsis_version("v3.0"), "3.0")
+ self.assertEqual(check_nsis_version("v3.0-2"), "3.0")
+ self.assertEqual(check_nsis_version("v3.0.1"), "3.0")
+ self.assertEqual(check_nsis_version("v3.1"), "3.1")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/configure/test_options.py b/python/mozbuild/mozbuild/test/configure/test_options.py
new file mode 100644
index 0000000000..59ba616355
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_options.py
@@ -0,0 +1,905 @@
+# 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/.
+
+import unittest
+
+from mozunit import main
+
+from mozbuild.configure.options import (
+ CommandLineHelper,
+ ConflictingOptionError,
+ InvalidOptionError,
+ NegativeOptionValue,
+ Option,
+ OptionValue,
+ PositiveOptionValue,
+)
+
+
+class Option(Option):
+ def __init__(self, *args, **kwargs):
+ kwargs["help"] = "Dummy help"
+ super(Option, self).__init__(*args, **kwargs)
+
+
+class TestOption(unittest.TestCase):
+ def test_option(self):
+ option = Option("--option")
+ self.assertEqual(option.prefix, "")
+ self.assertEqual(option.name, "option")
+ self.assertEqual(option.env, None)
+ self.assertFalse(option.default)
+
+ option = Option("--enable-option")
+ self.assertEqual(option.prefix, "enable")
+ self.assertEqual(option.name, "option")
+ self.assertEqual(option.env, None)
+ self.assertFalse(option.default)
+
+ option = Option("--disable-option")
+ self.assertEqual(option.prefix, "disable")
+ self.assertEqual(option.name, "option")
+ self.assertEqual(option.env, None)
+ self.assertTrue(option.default)
+
+ option = Option("--with-option")
+ self.assertEqual(option.prefix, "with")
+ self.assertEqual(option.name, "option")
+ self.assertEqual(option.env, None)
+ self.assertFalse(option.default)
+
+ option = Option("--without-option")
+ self.assertEqual(option.prefix, "without")
+ self.assertEqual(option.name, "option")
+ self.assertEqual(option.env, None)
+ self.assertTrue(option.default)
+
+ option = Option("--without-option-foo", env="MOZ_OPTION")
+ self.assertEqual(option.env, "MOZ_OPTION")
+
+ option = Option(env="MOZ_OPTION")
+ self.assertEqual(option.prefix, "")
+ self.assertEqual(option.name, None)
+ self.assertEqual(option.env, "MOZ_OPTION")
+ self.assertFalse(option.default)
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs=0, default=("a",))
+ self.assertEqual(
+ str(e.exception), "The given `default` doesn't satisfy `nargs`"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs=1, default=())
+ self.assertEqual(
+ str(e.exception), "default must be a bool, a string or a tuple of strings"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs=1, default=True)
+ self.assertEqual(
+ str(e.exception), "The given `default` doesn't satisfy `nargs`"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs=1, default=("a", "b"))
+ self.assertEqual(
+ str(e.exception), "The given `default` doesn't satisfy `nargs`"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs=2, default=())
+ self.assertEqual(
+ str(e.exception), "default must be a bool, a string or a tuple of strings"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs=2, default=True)
+ self.assertEqual(
+ str(e.exception), "The given `default` doesn't satisfy `nargs`"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs=2, default=("a",))
+ self.assertEqual(
+ str(e.exception), "The given `default` doesn't satisfy `nargs`"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs="?", default=("a", "b"))
+ self.assertEqual(
+ str(e.exception), "The given `default` doesn't satisfy `nargs`"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs="+", default=())
+ self.assertEqual(
+ str(e.exception), "default must be a bool, a string or a tuple of strings"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs="+", default=True)
+ self.assertEqual(
+ str(e.exception), "The given `default` doesn't satisfy `nargs`"
+ )
+
+ # --disable options with a nargs value that requires at least one
+ # argument need to be given a default.
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--disable-option", nargs=1)
+ self.assertEqual(
+ str(e.exception), "The given `default` doesn't satisfy `nargs`"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--disable-option", nargs="+")
+ self.assertEqual(
+ str(e.exception), "The given `default` doesn't satisfy `nargs`"
+ )
+
+ # Test nargs inference from default value
+ option = Option("--with-foo", default=True)
+ self.assertEqual(option.nargs, 0)
+
+ option = Option("--with-foo", default=False)
+ self.assertEqual(option.nargs, 0)
+
+ option = Option("--with-foo", default="a")
+ self.assertEqual(option.nargs, "?")
+
+ option = Option("--with-foo", default=("a",))
+ self.assertEqual(option.nargs, "?")
+
+ option = Option("--with-foo", default=("a", "b"))
+ self.assertEqual(option.nargs, "*")
+
+ option = Option(env="FOO", default=True)
+ self.assertEqual(option.nargs, 0)
+
+ option = Option(env="FOO", default=False)
+ self.assertEqual(option.nargs, 0)
+
+ option = Option(env="FOO", default="a")
+ self.assertEqual(option.nargs, "?")
+
+ option = Option(env="FOO", default=("a",))
+ self.assertEqual(option.nargs, "?")
+
+ option = Option(env="FOO", default=("a", "b"))
+ self.assertEqual(option.nargs, "*")
+
+ def test_option_option(self):
+ for option in (
+ "--option",
+ "--enable-option",
+ "--disable-option",
+ "--with-option",
+ "--without-option",
+ ):
+ self.assertEqual(Option(option).option, option)
+ self.assertEqual(Option(option, env="FOO").option, option)
+
+ opt = Option(option, default=False)
+ self.assertEqual(
+ opt.option,
+ option.replace("-disable-", "-enable-").replace("-without-", "-with-"),
+ )
+
+ opt = Option(option, default=True)
+ self.assertEqual(
+ opt.option,
+ option.replace("-enable-", "-disable-").replace("-with-", "-without-"),
+ )
+
+ self.assertEqual(Option(env="FOO").option, "FOO")
+
+ def test_option_choices(self):
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs=3, choices=("a", "b"))
+ self.assertEqual(str(e.exception), "Not enough `choices` for `nargs`")
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--without-option", nargs=1, choices=("a", "b"))
+ self.assertEqual(
+ str(e.exception), "A `default` must be given along with `choices`"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--without-option", nargs="+", choices=("a", "b"))
+ self.assertEqual(
+ str(e.exception), "A `default` must be given along with `choices`"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--without-option", default="c", choices=("a", "b"))
+ self.assertEqual(
+ str(e.exception), "The `default` value must be one of 'a', 'b'"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option(
+ "--without-option",
+ default=(
+ "a",
+ "c",
+ ),
+ choices=("a", "b"),
+ )
+ self.assertEqual(
+ str(e.exception), "The `default` value must be one of 'a', 'b'"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--without-option", default=("c",), choices=("a", "b"))
+ self.assertEqual(
+ str(e.exception), "The `default` value must be one of 'a', 'b'"
+ )
+
+ option = Option("--with-option", nargs="+", choices=("a", "b"))
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("--with-option=c")
+ self.assertEqual(str(e.exception), "'c' is not one of 'a', 'b'")
+
+ value = option.get_value("--with-option=b,a")
+ self.assertTrue(value)
+ self.assertEqual(PositiveOptionValue(("b", "a")), value)
+
+ option = Option("--without-option", nargs="*", default="a", choices=("a", "b"))
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("--with-option=c")
+ self.assertEqual(str(e.exception), "'c' is not one of 'a', 'b'")
+
+ value = option.get_value("--with-option=b,a")
+ self.assertTrue(value)
+ self.assertEqual(PositiveOptionValue(("b", "a")), value)
+
+ # Test nargs inference from choices
+ option = Option("--with-option", choices=("a", "b"))
+ self.assertEqual(option.nargs, 1)
+
+ # Test "relative" values
+ option = Option(
+ "--with-option", nargs="*", default=("b", "c"), choices=("a", "b", "c", "d")
+ )
+
+ value = option.get_value("--with-option=+d")
+ self.assertEqual(PositiveOptionValue(("b", "c", "d")), value)
+
+ value = option.get_value("--with-option=-b")
+ self.assertEqual(PositiveOptionValue(("c",)), value)
+
+ value = option.get_value("--with-option=-b,+d")
+ self.assertEqual(PositiveOptionValue(("c", "d")), value)
+
+ # Adding something that is in the default is fine
+ value = option.get_value("--with-option=+b")
+ self.assertEqual(PositiveOptionValue(("b", "c")), value)
+
+ # Removing something that is not in the default is fine, as long as it
+ # is one of the choices
+ value = option.get_value("--with-option=-a")
+ self.assertEqual(PositiveOptionValue(("b", "c")), value)
+
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("--with-option=-e")
+ self.assertEqual(str(e.exception), "'e' is not one of 'a', 'b', 'c', 'd'")
+
+ # Other "not a choice" errors.
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("--with-option=+e")
+ self.assertEqual(str(e.exception), "'e' is not one of 'a', 'b', 'c', 'd'")
+
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("--with-option=e")
+ self.assertEqual(str(e.exception), "'e' is not one of 'a', 'b', 'c', 'd'")
+
+ def test_option_value_compare(self):
+ # OptionValue are tuple and equivalence should compare as tuples.
+ val = PositiveOptionValue(("foo",))
+
+ self.assertEqual(val[0], "foo")
+ self.assertEqual(val, PositiveOptionValue(("foo",)))
+ self.assertNotEqual(val, PositiveOptionValue(("foo", "bar")))
+
+ # Can compare a tuple to an OptionValue.
+ self.assertEqual(val, ("foo",))
+ self.assertNotEqual(val, ("foo", "bar"))
+
+ # Different OptionValue types are never equal.
+ self.assertNotEqual(val, OptionValue(("foo",)))
+
+ # For usability reasons, we raise TypeError when attempting to compare
+ # against a non-tuple.
+ with self.assertRaisesRegexp(TypeError, "cannot compare a"):
+ val == "foo"
+
+ # But we allow empty option values to compare otherwise we can't
+ # easily compare value-less types like PositiveOptionValue and
+ # NegativeOptionValue.
+ empty_positive = PositiveOptionValue()
+ empty_negative = NegativeOptionValue()
+ self.assertEqual(empty_positive, ())
+ self.assertEqual(empty_positive, PositiveOptionValue())
+ self.assertEqual(empty_negative, ())
+ self.assertEqual(empty_negative, NegativeOptionValue())
+ self.assertNotEqual(empty_positive, "foo")
+ self.assertNotEqual(empty_positive, ("foo",))
+ self.assertNotEqual(empty_negative, "foo")
+ self.assertNotEqual(empty_negative, ("foo",))
+
+ def test_option_value_format(self):
+ val = PositiveOptionValue()
+ self.assertEqual("--with-value", val.format("--with-value"))
+ self.assertEqual("--with-value", val.format("--without-value"))
+ self.assertEqual("--enable-value", val.format("--enable-value"))
+ self.assertEqual("--enable-value", val.format("--disable-value"))
+ self.assertEqual("--value", val.format("--value"))
+ self.assertEqual("VALUE=1", val.format("VALUE"))
+
+ val = PositiveOptionValue(("a",))
+ self.assertEqual("--with-value=a", val.format("--with-value"))
+ self.assertEqual("--with-value=a", val.format("--without-value"))
+ self.assertEqual("--enable-value=a", val.format("--enable-value"))
+ self.assertEqual("--enable-value=a", val.format("--disable-value"))
+ self.assertEqual("--value=a", val.format("--value"))
+ self.assertEqual("VALUE=a", val.format("VALUE"))
+
+ val = PositiveOptionValue(("a", "b"))
+ self.assertEqual("--with-value=a,b", val.format("--with-value"))
+ self.assertEqual("--with-value=a,b", val.format("--without-value"))
+ self.assertEqual("--enable-value=a,b", val.format("--enable-value"))
+ self.assertEqual("--enable-value=a,b", val.format("--disable-value"))
+ self.assertEqual("--value=a,b", val.format("--value"))
+ self.assertEqual("VALUE=a,b", val.format("VALUE"))
+
+ val = NegativeOptionValue()
+ self.assertEqual("--without-value", val.format("--with-value"))
+ self.assertEqual("--without-value", val.format("--without-value"))
+ self.assertEqual("--disable-value", val.format("--enable-value"))
+ self.assertEqual("--disable-value", val.format("--disable-value"))
+ self.assertEqual("", val.format("--value"))
+ self.assertEqual("VALUE=", val.format("VALUE"))
+
+ def test_option_value(self, name="option", nargs=0, default=None):
+ disabled = name.startswith(("disable-", "without-"))
+ if disabled:
+ negOptionValue = PositiveOptionValue
+ posOptionValue = NegativeOptionValue
+ else:
+ posOptionValue = PositiveOptionValue
+ negOptionValue = NegativeOptionValue
+ defaultValue = PositiveOptionValue(default) if default else negOptionValue()
+
+ option = Option("--%s" % name, nargs=nargs, default=default)
+
+ if nargs in (0, "?", "*") or disabled:
+ value = option.get_value("--%s" % name, "option")
+ self.assertEqual(value, posOptionValue())
+ self.assertEqual(value.origin, "option")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("--%s" % name)
+ if nargs == 1:
+ self.assertEqual(str(e.exception), "--%s takes 1 value" % name)
+ elif nargs == "+":
+ self.assertEqual(str(e.exception), "--%s takes 1 or more values" % name)
+ else:
+ self.assertEqual(str(e.exception), "--%s takes 2 values" % name)
+
+ value = option.get_value("")
+ self.assertEqual(value, defaultValue)
+ self.assertEqual(value.origin, "default")
+
+ value = option.get_value(None)
+ self.assertEqual(value, defaultValue)
+ self.assertEqual(value.origin, "default")
+
+ with self.assertRaises(AssertionError):
+ value = option.get_value("MOZ_OPTION=", "environment")
+
+ with self.assertRaises(AssertionError):
+ value = option.get_value("MOZ_OPTION=1", "environment")
+
+ with self.assertRaises(AssertionError):
+ value = option.get_value("--foo")
+
+ if nargs in (1, "?", "*", "+") and not disabled:
+ value = option.get_value("--%s=" % name, "option")
+ self.assertEqual(value, PositiveOptionValue(("",)))
+ self.assertEqual(value.origin, "option")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("--%s=" % name)
+ if disabled:
+ self.assertEqual(str(e.exception), "Cannot pass a value to --%s" % name)
+ else:
+ self.assertEqual(
+ str(e.exception), "--%s takes %d values" % (name, nargs)
+ )
+
+ if nargs in (1, "?", "*", "+") and not disabled:
+ value = option.get_value("--%s=foo" % name, "option")
+ self.assertEqual(value, PositiveOptionValue(("foo",)))
+ self.assertEqual(value.origin, "option")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("--%s=foo" % name)
+ if disabled:
+ self.assertEqual(str(e.exception), "Cannot pass a value to --%s" % name)
+ else:
+ self.assertEqual(
+ str(e.exception), "--%s takes %d values" % (name, nargs)
+ )
+
+ if nargs in (2, "*", "+") and not disabled:
+ value = option.get_value("--%s=foo,bar" % name, "option")
+ self.assertEqual(value, PositiveOptionValue(("foo", "bar")))
+ self.assertEqual(value.origin, "option")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("--%s=foo,bar" % name, "option")
+ if disabled:
+ self.assertEqual(str(e.exception), "Cannot pass a value to --%s" % name)
+ elif nargs == "?":
+ self.assertEqual(str(e.exception), "--%s takes 0 or 1 values" % name)
+ else:
+ self.assertEqual(
+ str(e.exception),
+ "--%s takes %d value%s" % (name, nargs, "s" if nargs != 1 else ""),
+ )
+
+ option = Option("--%s" % name, env="MOZ_OPTION", nargs=nargs, default=default)
+ if nargs in (0, "?", "*") or disabled:
+ value = option.get_value("--%s" % name, "option")
+ self.assertEqual(value, posOptionValue())
+ self.assertEqual(value.origin, "option")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("--%s" % name)
+ if disabled:
+ self.assertEqual(str(e.exception), "Cannot pass a value to --%s" % name)
+ elif nargs == "+":
+ self.assertEqual(str(e.exception), "--%s takes 1 or more values" % name)
+ else:
+ self.assertEqual(
+ str(e.exception),
+ "--%s takes %d value%s" % (name, nargs, "s" if nargs != 1 else ""),
+ )
+
+ value = option.get_value("")
+ self.assertEqual(value, defaultValue)
+ self.assertEqual(value.origin, "default")
+
+ value = option.get_value(None)
+ self.assertEqual(value, defaultValue)
+ self.assertEqual(value.origin, "default")
+
+ value = option.get_value("MOZ_OPTION=", "environment")
+ self.assertEqual(value, NegativeOptionValue())
+ self.assertEqual(value.origin, "environment")
+
+ if nargs in (0, "?", "*"):
+ value = option.get_value("MOZ_OPTION=1", "environment")
+ self.assertEqual(value, PositiveOptionValue())
+ self.assertEqual(value.origin, "environment")
+ elif nargs in (1, "+"):
+ value = option.get_value("MOZ_OPTION=1", "environment")
+ self.assertEqual(value, PositiveOptionValue(("1",)))
+ self.assertEqual(value.origin, "environment")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("MOZ_OPTION=1", "environment")
+ self.assertEqual(str(e.exception), "MOZ_OPTION takes 2 values")
+
+ if nargs in (1, "?", "*", "+") and not disabled:
+ value = option.get_value("--%s=" % name, "option")
+ self.assertEqual(value, PositiveOptionValue(("",)))
+ self.assertEqual(value.origin, "option")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("--%s=" % name, "option")
+ if disabled:
+ self.assertEqual(str(e.exception), "Cannot pass a value to --%s" % name)
+ else:
+ self.assertEqual(
+ str(e.exception), "--%s takes %d values" % (name, nargs)
+ )
+
+ with self.assertRaises(AssertionError):
+ value = option.get_value("--foo", "option")
+
+ if nargs in (1, "?", "*", "+"):
+ value = option.get_value("MOZ_OPTION=foo", "environment")
+ self.assertEqual(value, PositiveOptionValue(("foo",)))
+ self.assertEqual(value.origin, "environment")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("MOZ_OPTION=foo", "environment")
+ self.assertEqual(str(e.exception), "MOZ_OPTION takes %d values" % nargs)
+
+ if nargs in (2, "*", "+"):
+ value = option.get_value("MOZ_OPTION=foo,bar", "environment")
+ self.assertEqual(value, PositiveOptionValue(("foo", "bar")))
+ self.assertEqual(value.origin, "environment")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("MOZ_OPTION=foo,bar", "environment")
+ if nargs == "?":
+ self.assertEqual(str(e.exception), "MOZ_OPTION takes 0 or 1 values")
+ else:
+ self.assertEqual(
+ str(e.exception),
+ "MOZ_OPTION takes %d value%s" % (nargs, "s" if nargs != 1 else ""),
+ )
+
+ if disabled:
+ return option
+
+ env_option = Option(env="MOZ_OPTION", nargs=nargs, default=default)
+ with self.assertRaises(AssertionError):
+ env_option.get_value("--%s" % name)
+
+ value = env_option.get_value("")
+ self.assertEqual(value, defaultValue)
+ self.assertEqual(value.origin, "default")
+
+ value = env_option.get_value("MOZ_OPTION=", "environment")
+ self.assertEqual(value, negOptionValue())
+ self.assertEqual(value.origin, "environment")
+
+ if nargs in (0, "?", "*"):
+ value = env_option.get_value("MOZ_OPTION=1", "environment")
+ self.assertEqual(value, posOptionValue())
+ self.assertTrue(value)
+ self.assertEqual(value.origin, "environment")
+ elif nargs in (1, "+"):
+ value = env_option.get_value("MOZ_OPTION=1", "environment")
+ self.assertEqual(value, PositiveOptionValue(("1",)))
+ self.assertEqual(value.origin, "environment")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ env_option.get_value("MOZ_OPTION=1", "environment")
+ self.assertEqual(str(e.exception), "MOZ_OPTION takes 2 values")
+
+ with self.assertRaises(AssertionError) as e:
+ env_option.get_value("--%s" % name)
+
+ with self.assertRaises(AssertionError) as e:
+ env_option.get_value("--foo")
+
+ if nargs in (1, "?", "*", "+"):
+ value = env_option.get_value("MOZ_OPTION=foo", "environment")
+ self.assertEqual(value, PositiveOptionValue(("foo",)))
+ self.assertEqual(value.origin, "environment")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ env_option.get_value("MOZ_OPTION=foo", "environment")
+ self.assertEqual(str(e.exception), "MOZ_OPTION takes %d values" % nargs)
+
+ if nargs in (2, "*", "+"):
+ value = env_option.get_value("MOZ_OPTION=foo,bar", "environment")
+ self.assertEqual(value, PositiveOptionValue(("foo", "bar")))
+ self.assertEqual(value.origin, "environment")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ env_option.get_value("MOZ_OPTION=foo,bar", "environment")
+ if nargs == "?":
+ self.assertEqual(str(e.exception), "MOZ_OPTION takes 0 or 1 values")
+ else:
+ self.assertEqual(
+ str(e.exception),
+ "MOZ_OPTION takes %d value%s" % (nargs, "s" if nargs != 1 else ""),
+ )
+
+ return option
+
+ def test_option_value_enable(
+ self, enable="enable", disable="disable", nargs=0, default=None
+ ):
+ option = self.test_option_value(
+ "%s-option" % enable, nargs=nargs, default=default
+ )
+
+ value = option.get_value("--%s-option" % disable, "option")
+ self.assertEqual(value, NegativeOptionValue())
+ self.assertEqual(value.origin, "option")
+
+ option = self.test_option_value(
+ "%s-option" % disable, nargs=nargs, default=default
+ )
+
+ if nargs in (0, "?", "*"):
+ value = option.get_value("--%s-option" % enable, "option")
+ self.assertEqual(value, PositiveOptionValue())
+ self.assertEqual(value.origin, "option")
+ else:
+ with self.assertRaises(InvalidOptionError) as e:
+ option.get_value("--%s-option" % enable, "option")
+ if nargs == 1:
+ self.assertEqual(str(e.exception), "--%s-option takes 1 value" % enable)
+ elif nargs == "+":
+ self.assertEqual(
+ str(e.exception), "--%s-option takes 1 or more values" % enable
+ )
+ else:
+ self.assertEqual(
+ str(e.exception), "--%s-option takes 2 values" % enable
+ )
+
+ def test_option_value_with(self):
+ self.test_option_value_enable("with", "without")
+
+ def test_option_value_invalid_nargs(self):
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs="foo")
+ self.assertEqual(
+ str(e.exception), "nargs must be a positive integer, '?', '*' or '+'"
+ )
+
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--option", nargs=-2)
+ self.assertEqual(
+ str(e.exception), "nargs must be a positive integer, '?', '*' or '+'"
+ )
+
+ def test_option_value_nargs_1(self):
+ self.test_option_value(nargs=1)
+ self.test_option_value(nargs=1, default=("a",))
+ self.test_option_value_enable(nargs=1, default=("a",))
+
+ # A default is required
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--disable-option", nargs=1)
+ self.assertEqual(
+ str(e.exception), "The given `default` doesn't satisfy `nargs`"
+ )
+
+ def test_option_value_nargs_2(self):
+ self.test_option_value(nargs=2)
+ self.test_option_value(nargs=2, default=("a", "b"))
+ self.test_option_value_enable(nargs=2, default=("a", "b"))
+
+ # A default is required
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--disable-option", nargs=2)
+ self.assertEqual(
+ str(e.exception), "The given `default` doesn't satisfy `nargs`"
+ )
+
+ def test_option_value_nargs_0_or_1(self):
+ self.test_option_value(nargs="?")
+ self.test_option_value(nargs="?", default=("a",))
+ self.test_option_value_enable(nargs="?")
+ self.test_option_value_enable(nargs="?", default=("a",))
+
+ def test_option_value_nargs_0_or_more(self):
+ self.test_option_value(nargs="*")
+ self.test_option_value(nargs="*", default=("a",))
+ self.test_option_value(nargs="*", default=("a", "b"))
+ self.test_option_value_enable(nargs="*")
+ self.test_option_value_enable(nargs="*", default=("a",))
+ self.test_option_value_enable(nargs="*", default=("a", "b"))
+
+ def test_option_value_nargs_1_or_more(self):
+ self.test_option_value(nargs="+")
+ self.test_option_value(nargs="+", default=("a",))
+ self.test_option_value(nargs="+", default=("a", "b"))
+ self.test_option_value_enable(nargs="+", default=("a",))
+ self.test_option_value_enable(nargs="+", default=("a", "b"))
+
+ # A default is required
+ with self.assertRaises(InvalidOptionError) as e:
+ Option("--disable-option", nargs="+")
+ self.assertEqual(
+ str(e.exception), "The given `default` doesn't satisfy `nargs`"
+ )
+
+
+class TestCommandLineHelper(unittest.TestCase):
+ def test_basic(self):
+ helper = CommandLineHelper({}, ["cmd", "--foo", "--bar"])
+
+ self.assertEqual(["--foo", "--bar"], list(helper))
+
+ helper.add("--enable-qux")
+
+ self.assertEqual(["--foo", "--bar", "--enable-qux"], list(helper))
+
+ value, option = helper.handle(Option("--bar"))
+ self.assertEqual(["--foo", "--enable-qux"], list(helper))
+ self.assertEqual(PositiveOptionValue(), value)
+ self.assertEqual("--bar", option)
+
+ value, option = helper.handle(Option("--baz"))
+ self.assertEqual(["--foo", "--enable-qux"], list(helper))
+ self.assertEqual(NegativeOptionValue(), value)
+ self.assertEqual(None, option)
+
+ with self.assertRaises(AssertionError):
+ CommandLineHelper({}, ["--foo", "--bar"])
+
+ def test_precedence(self):
+ foo = Option("--with-foo", nargs="*")
+ helper = CommandLineHelper({}, ["cmd", "--with-foo=a,b"])
+ value, option = helper.handle(foo)
+ self.assertEqual(PositiveOptionValue(("a", "b")), value)
+ self.assertEqual("command-line", value.origin)
+ self.assertEqual("--with-foo=a,b", option)
+
+ helper = CommandLineHelper({}, ["cmd", "--with-foo=a,b", "--without-foo"])
+ value, option = helper.handle(foo)
+ self.assertEqual(NegativeOptionValue(), value)
+ self.assertEqual("command-line", value.origin)
+ self.assertEqual("--without-foo", option)
+
+ helper = CommandLineHelper({}, ["cmd", "--without-foo", "--with-foo=a,b"])
+ value, option = helper.handle(foo)
+ self.assertEqual(PositiveOptionValue(("a", "b")), value)
+ self.assertEqual("command-line", value.origin)
+ self.assertEqual("--with-foo=a,b", option)
+
+ foo = Option("--with-foo", env="FOO", nargs="*")
+ helper = CommandLineHelper({"FOO": ""}, ["cmd", "--with-foo=a,b"])
+ value, option = helper.handle(foo)
+ self.assertEqual(PositiveOptionValue(("a", "b")), value)
+ self.assertEqual("command-line", value.origin)
+ self.assertEqual("--with-foo=a,b", option)
+
+ helper = CommandLineHelper({"FOO": "a,b"}, ["cmd", "--without-foo"])
+ value, option = helper.handle(foo)
+ self.assertEqual(NegativeOptionValue(), value)
+ self.assertEqual("command-line", value.origin)
+ self.assertEqual("--without-foo", option)
+
+ helper = CommandLineHelper({"FOO": ""}, ["cmd", "--with-bar=a,b"])
+ value, option = helper.handle(foo)
+ self.assertEqual(NegativeOptionValue(), value)
+ self.assertEqual("environment", value.origin)
+ self.assertEqual("FOO=", option)
+
+ helper = CommandLineHelper({"FOO": "a,b"}, ["cmd", "--without-bar"])
+ value, option = helper.handle(foo)
+ self.assertEqual(PositiveOptionValue(("a", "b")), value)
+ self.assertEqual("environment", value.origin)
+ self.assertEqual("FOO=a,b", option)
+
+ helper = CommandLineHelper({}, ["cmd", "--with-foo=a,b", "FOO="])
+ value, option = helper.handle(foo)
+ self.assertEqual(NegativeOptionValue(), value)
+ self.assertEqual("command-line", value.origin)
+ self.assertEqual("FOO=", option)
+
+ helper = CommandLineHelper({}, ["cmd", "--without-foo", "FOO=a,b"])
+ value, option = helper.handle(foo)
+ self.assertEqual(PositiveOptionValue(("a", "b")), value)
+ self.assertEqual("command-line", value.origin)
+ self.assertEqual("FOO=a,b", option)
+
+ helper = CommandLineHelper({}, ["cmd", "FOO=", "--with-foo=a,b"])
+ value, option = helper.handle(foo)
+ self.assertEqual(PositiveOptionValue(("a", "b")), value)
+ self.assertEqual("command-line", value.origin)
+ self.assertEqual("--with-foo=a,b", option)
+
+ helper = CommandLineHelper({}, ["cmd", "FOO=a,b", "--without-foo"])
+ value, option = helper.handle(foo)
+ self.assertEqual(NegativeOptionValue(), value)
+ self.assertEqual("command-line", value.origin)
+ self.assertEqual("--without-foo", option)
+
+ def test_extra_args(self):
+ foo = Option("--with-foo", env="FOO", nargs="*")
+ helper = CommandLineHelper({}, ["cmd"])
+ helper.add("FOO=a,b,c", "other-origin")
+ value, option = helper.handle(foo)
+ self.assertEqual(PositiveOptionValue(("a", "b", "c")), value)
+ self.assertEqual("other-origin", value.origin)
+ self.assertEqual("FOO=a,b,c", option)
+
+ helper = CommandLineHelper({}, ["cmd"])
+ helper.add("FOO=a,b,c", "other-origin")
+ helper.add("--with-foo=a,b,c", "other-origin")
+ value, option = helper.handle(foo)
+ self.assertEqual(PositiveOptionValue(("a", "b", "c")), value)
+ self.assertEqual("other-origin", value.origin)
+ self.assertEqual("--with-foo=a,b,c", option)
+
+ # Adding conflicting options is not allowed.
+ helper = CommandLineHelper({}, ["cmd"])
+ helper.add("FOO=a,b,c", "other-origin")
+ with self.assertRaises(ConflictingOptionError) as cm:
+ helper.add("FOO=", "other-origin")
+ self.assertEqual("FOO=", cm.exception.arg)
+ self.assertEqual("other-origin", cm.exception.origin)
+ self.assertEqual("FOO=a,b,c", cm.exception.old_arg)
+ self.assertEqual("other-origin", cm.exception.old_origin)
+ with self.assertRaises(ConflictingOptionError) as cm:
+ helper.add("FOO=a,b", "other-origin")
+ self.assertEqual("FOO=a,b", cm.exception.arg)
+ self.assertEqual("other-origin", cm.exception.origin)
+ self.assertEqual("FOO=a,b,c", cm.exception.old_arg)
+ self.assertEqual("other-origin", cm.exception.old_origin)
+ # But adding the same is allowed.
+ helper.add("FOO=a,b,c", "other-origin")
+ value, option = helper.handle(foo)
+ self.assertEqual(PositiveOptionValue(("a", "b", "c")), value)
+ self.assertEqual("other-origin", value.origin)
+ self.assertEqual("FOO=a,b,c", option)
+
+ # The same rule as above applies when using the option form vs. the
+ # variable form. But we can't detect it when .add is called.
+ helper = CommandLineHelper({}, ["cmd"])
+ helper.add("FOO=a,b,c", "other-origin")
+ helper.add("--without-foo", "other-origin")
+ with self.assertRaises(ConflictingOptionError) as cm:
+ helper.handle(foo)
+ self.assertEqual("--without-foo", cm.exception.arg)
+ self.assertEqual("other-origin", cm.exception.origin)
+ self.assertEqual("FOO=a,b,c", cm.exception.old_arg)
+ self.assertEqual("other-origin", cm.exception.old_origin)
+ helper = CommandLineHelper({}, ["cmd"])
+ helper.add("FOO=a,b,c", "other-origin")
+ helper.add("--with-foo=a,b", "other-origin")
+ with self.assertRaises(ConflictingOptionError) as cm:
+ helper.handle(foo)
+ self.assertEqual("--with-foo=a,b", cm.exception.arg)
+ self.assertEqual("other-origin", cm.exception.origin)
+ self.assertEqual("FOO=a,b,c", cm.exception.old_arg)
+ self.assertEqual("other-origin", cm.exception.old_origin)
+ helper = CommandLineHelper({}, ["cmd"])
+ helper.add("FOO=a,b,c", "other-origin")
+ helper.add("--with-foo=a,b,c", "other-origin")
+ value, option = helper.handle(foo)
+ self.assertEqual(PositiveOptionValue(("a", "b", "c")), value)
+ self.assertEqual("other-origin", value.origin)
+ self.assertEqual("--with-foo=a,b,c", option)
+
+ # Conflicts are also not allowed against what is in the
+ # environment/on the command line.
+ helper = CommandLineHelper({}, ["cmd", "--with-foo=a,b"])
+ helper.add("FOO=a,b,c", "other-origin")
+ with self.assertRaises(ConflictingOptionError) as cm:
+ helper.handle(foo)
+ self.assertEqual("FOO=a,b,c", cm.exception.arg)
+ self.assertEqual("other-origin", cm.exception.origin)
+ self.assertEqual("--with-foo=a,b", cm.exception.old_arg)
+ self.assertEqual("command-line", cm.exception.old_origin)
+
+ helper = CommandLineHelper({}, ["cmd", "--with-foo=a,b"])
+ helper.add("--without-foo", "other-origin")
+ with self.assertRaises(ConflictingOptionError) as cm:
+ helper.handle(foo)
+ self.assertEqual("--without-foo", cm.exception.arg)
+ self.assertEqual("other-origin", cm.exception.origin)
+ self.assertEqual("--with-foo=a,b", cm.exception.old_arg)
+ self.assertEqual("command-line", cm.exception.old_origin)
+
+ def test_possible_origins(self):
+ with self.assertRaises(InvalidOptionError):
+ Option("--foo", possible_origins="command-line")
+
+ helper = CommandLineHelper({"BAZ": "1"}, ["cmd", "--foo", "--bar"])
+ foo = Option("--foo", possible_origins=("command-line",))
+ value, option = helper.handle(foo)
+ self.assertEqual(PositiveOptionValue(), value)
+ self.assertEqual("command-line", value.origin)
+ self.assertEqual("--foo", option)
+
+ bar = Option("--bar", possible_origins=("mozconfig",))
+ with self.assertRaisesRegexp(
+ InvalidOptionError,
+ "--bar can not be set by command-line. Values are accepted from: mozconfig",
+ ):
+ helper.handle(bar)
+
+ baz = Option(env="BAZ", possible_origins=("implied",))
+ with self.assertRaisesRegexp(
+ InvalidOptionError,
+ "BAZ=1 can not be set by environment. Values are accepted from: implied",
+ ):
+ helper.handle(baz)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/configure/test_toolchain_configure.py b/python/mozbuild/mozbuild/test/configure/test_toolchain_configure.py
new file mode 100644
index 0000000000..c6af3d99d4
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_toolchain_configure.py
@@ -0,0 +1,2056 @@
+# 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/.
+
+import logging
+import os
+
+import six
+from mozboot.util import MINIMUM_RUST_VERSION
+from mozpack import path as mozpath
+from mozunit import main
+from six import StringIO
+from test_toolchain_helpers import CompilerResult, FakeCompiler, PrependFlags
+
+from common import BaseConfigureTest
+from mozbuild.configure.util import Version
+from mozbuild.util import ReadOnlyNamespace, memoize
+
+DEFAULT_C99 = {"__STDC_VERSION__": "199901L"}
+
+DEFAULT_C11 = {"__STDC_VERSION__": "201112L"}
+
+DEFAULT_C17 = {"__STDC_VERSION__": "201710L"}
+
+DEFAULT_CXX_97 = {"__cplusplus": "199711L"}
+
+DEFAULT_CXX_11 = {"__cplusplus": "201103L"}
+
+DRAFT_CXX_14 = {"__cplusplus": "201300L"}
+
+DEFAULT_CXX_14 = {"__cplusplus": "201402L"}
+
+DRAFT_CXX17_201500 = {"__cplusplus": "201500L"}
+
+DRAFT_CXX17_201406 = {"__cplusplus": "201406L"}
+
+DEFAULT_CXX_17 = {"__cplusplus": "201703L"}
+
+SUPPORTS_GNU99 = {"-std=gnu99": DEFAULT_C99}
+
+SUPPORTS_GNUXX11 = {"-std=gnu++11": DEFAULT_CXX_11}
+
+SUPPORTS_GNUXX14 = {"-std=gnu++14": DEFAULT_CXX_14}
+
+SUPPORTS_CXX14 = {"-std=c++14": DEFAULT_CXX_14}
+
+SUPPORTS_GNUXX17 = {"-std=gnu++17": DEFAULT_CXX_17}
+
+SUPPORTS_CXX17 = {"-std=c++17": DEFAULT_CXX_17}
+
+
+@memoize
+def GCC_BASE(version):
+ version = Version(version)
+ return FakeCompiler(
+ {
+ "__GNUC__": version.major,
+ "__GNUC_MINOR__": version.minor,
+ "__GNUC_PATCHLEVEL__": version.patch,
+ "__STDC__": 1,
+ }
+ )
+
+
+@memoize
+def GCC(version):
+ return GCC_BASE(version) + SUPPORTS_GNU99
+
+
+@memoize
+def GXX(version):
+ return GCC_BASE(version) + DEFAULT_CXX_97 + SUPPORTS_GNUXX11
+
+
+SUPPORTS_DRAFT_CXX14_VERSION = {"-std=gnu++14": DRAFT_CXX_14}
+
+SUPPORTS_GNUXX1Z = {"-std=gnu++1z": DRAFT_CXX17_201406}
+
+SUPPORTS_DRAFT_CXX17_201500_VERSION = {"-std=gnu++17": DRAFT_CXX17_201500}
+
+GCC_4_9 = GCC("4.9.3")
+GXX_4_9 = GXX("4.9.3") + SUPPORTS_DRAFT_CXX14_VERSION
+GCC_5 = GCC("5.2.1") + DEFAULT_C11
+GXX_5 = GXX("5.2.1") + SUPPORTS_GNUXX14
+GCC_6 = GCC("6.4.0") + DEFAULT_C11
+GXX_6 = (
+ GXX("6.4.0")
+ + DEFAULT_CXX_14
+ + SUPPORTS_GNUXX17
+ + SUPPORTS_DRAFT_CXX17_201500_VERSION
+)
+GCC_7 = GCC("7.3.0") + DEFAULT_C11
+GXX_7 = GXX("7.3.0") + DEFAULT_CXX_14 + SUPPORTS_GNUXX17 + SUPPORTS_CXX17
+GCC_8 = GCC("8.3.0") + DEFAULT_C11
+GXX_8 = GXX("8.3.0") + DEFAULT_CXX_14 + SUPPORTS_GNUXX17 + SUPPORTS_CXX17
+GCC_10 = GCC("10.2.1") + DEFAULT_C17
+GXX_10 = GXX("10.2.1") + DEFAULT_CXX_14 + SUPPORTS_GNUXX17 + SUPPORTS_CXX17
+
+DEFAULT_GCC = GCC_8
+DEFAULT_GXX = GXX_8
+
+GCC_PLATFORM_LITTLE_ENDIAN = {
+ "__ORDER_LITTLE_ENDIAN__": 1234,
+ "__ORDER_BIG_ENDIAN__": 4321,
+ "__BYTE_ORDER__": 1234,
+}
+
+GCC_PLATFORM_BIG_ENDIAN = {
+ "__ORDER_LITTLE_ENDIAN__": 1234,
+ "__ORDER_BIG_ENDIAN__": 4321,
+ "__BYTE_ORDER__": 4321,
+}
+
+GCC_PLATFORM_X86 = FakeCompiler(GCC_PLATFORM_LITTLE_ENDIAN) + {
+ None: {"__i386__": 1},
+ "-m64": {"__i386__": False, "__x86_64__": 1},
+}
+
+GCC_PLATFORM_X86_64 = FakeCompiler(GCC_PLATFORM_LITTLE_ENDIAN) + {
+ None: {"__x86_64__": 1},
+ "-m32": {"__x86_64__": False, "__i386__": 1},
+}
+
+GCC_PLATFORM_ARM = FakeCompiler(GCC_PLATFORM_LITTLE_ENDIAN) + {"__arm__": 1}
+
+GCC_PLATFORM_LINUX = {"__linux__": 1}
+
+GCC_PLATFORM_DARWIN = {"__APPLE__": 1}
+
+GCC_PLATFORM_WIN = {"_WIN32": 1, "WINNT": 1}
+
+GCC_PLATFORM_OPENBSD = {"__OpenBSD__": 1}
+
+GCC_PLATFORM_X86_LINUX = FakeCompiler(GCC_PLATFORM_X86, GCC_PLATFORM_LINUX)
+GCC_PLATFORM_X86_64_LINUX = FakeCompiler(GCC_PLATFORM_X86_64, GCC_PLATFORM_LINUX)
+GCC_PLATFORM_ARM_LINUX = FakeCompiler(GCC_PLATFORM_ARM, GCC_PLATFORM_LINUX)
+GCC_PLATFORM_X86_OSX = FakeCompiler(GCC_PLATFORM_X86, GCC_PLATFORM_DARWIN)
+GCC_PLATFORM_X86_64_OSX = FakeCompiler(GCC_PLATFORM_X86_64, GCC_PLATFORM_DARWIN)
+GCC_PLATFORM_X86_WIN = FakeCompiler(GCC_PLATFORM_X86, GCC_PLATFORM_WIN)
+GCC_PLATFORM_X86_64_WIN = FakeCompiler(GCC_PLATFORM_X86_64, GCC_PLATFORM_WIN)
+
+
+@memoize
+def CLANG_BASE(version):
+ version = Version(version)
+ return FakeCompiler(
+ {
+ "__clang__": 1,
+ "__clang_major__": version.major,
+ "__clang_minor__": version.minor,
+ "__clang_patchlevel__": version.patch,
+ }
+ )
+
+
+@memoize
+def CLANG(version):
+ return GCC_BASE("4.2.1") + CLANG_BASE(version) + SUPPORTS_GNU99
+
+
+@memoize
+def CLANGXX(version):
+ return (
+ GCC_BASE("4.2.1")
+ + CLANG_BASE(version)
+ + DEFAULT_CXX_97
+ + SUPPORTS_GNUXX11
+ + SUPPORTS_GNUXX14
+ )
+
+
+CLANG_3_3 = CLANG("3.3.0") + DEFAULT_C99
+CLANGXX_3_3 = CLANGXX("3.3.0")
+CLANG_4_0 = CLANG("4.0.2") + DEFAULT_C11
+CLANGXX_4_0 = CLANGXX("4.0.2") + SUPPORTS_GNUXX1Z
+CLANG_7_0 = CLANG("7.0.0") + DEFAULT_C11
+CLANGXX_7_0 = CLANGXX("7.0.0") + DEFAULT_CXX_14 + SUPPORTS_GNUXX17
+XCODE_CLANG_3_3 = (
+ CLANG("5.0")
+ + DEFAULT_C99
+ + {
+ # Real Xcode clang has a full version here, but we don't care about it.
+ "__apple_build_version__": "1"
+ }
+)
+XCODE_CLANGXX_3_3 = CLANGXX("5.0") + {"__apple_build_version__": "1"}
+XCODE_CLANG_4_0 = CLANG("9.0.0") + DEFAULT_C11 + {"__apple_build_version__": "1"}
+XCODE_CLANGXX_4_0 = (
+ CLANGXX("9.0.0") + SUPPORTS_GNUXX1Z + {"__apple_build_version__": "1"}
+)
+XCODE_CLANG_7_0 = CLANG("10.0.1") + DEFAULT_C11 + {"__apple_build_version__": "1"}
+XCODE_CLANGXX_7_0 = (
+ CLANGXX("10.0.1") + SUPPORTS_GNUXX17 + {"__apple_build_version__": "1"}
+)
+DEFAULT_CLANG = CLANG_7_0
+DEFAULT_CLANGXX = CLANGXX_7_0
+
+
+def CLANG_PLATFORM(gcc_platform):
+ base = {
+ "--target=x86_64-linux-gnu": GCC_PLATFORM_X86_64_LINUX[None],
+ "--target=x86_64-apple-darwin11.2.0": GCC_PLATFORM_X86_64_OSX[None],
+ "--target=i686-linux-gnu": GCC_PLATFORM_X86_LINUX[None],
+ "--target=i686-apple-darwin11.2.0": GCC_PLATFORM_X86_OSX[None],
+ "--target=arm-linux-gnu": GCC_PLATFORM_ARM_LINUX[None],
+ }
+ undo_gcc_platform = {
+ k: {symbol: False for symbol in gcc_platform[None]} for k in base
+ }
+ return FakeCompiler(gcc_platform, undo_gcc_platform, base)
+
+
+CLANG_PLATFORM_X86_LINUX = CLANG_PLATFORM(GCC_PLATFORM_X86_LINUX)
+CLANG_PLATFORM_X86_64_LINUX = CLANG_PLATFORM(GCC_PLATFORM_X86_64_LINUX)
+CLANG_PLATFORM_X86_OSX = CLANG_PLATFORM(GCC_PLATFORM_X86_OSX)
+CLANG_PLATFORM_X86_64_OSX = CLANG_PLATFORM(GCC_PLATFORM_X86_64_OSX)
+CLANG_PLATFORM_X86_WIN = CLANG_PLATFORM(GCC_PLATFORM_X86_WIN)
+CLANG_PLATFORM_X86_64_WIN = CLANG_PLATFORM(GCC_PLATFORM_X86_64_WIN)
+
+
+@memoize
+def VS(version):
+ version = Version(version)
+ return FakeCompiler(
+ {
+ None: {
+ "_MSC_VER": "%02d%02d" % (version.major, version.minor),
+ "_MSC_FULL_VER": "%02d%02d%05d"
+ % (version.major, version.minor, version.patch),
+ "_MT": "1",
+ },
+ "*.cpp": DEFAULT_CXX_97,
+ }
+ )
+
+
+VS_2017u8 = VS("19.15.26726")
+
+VS_PLATFORM_X86 = {"_M_IX86": 600, "_WIN32": 1}
+
+VS_PLATFORM_X86_64 = {"_M_X64": 100, "_WIN32": 1, "_WIN64": 1}
+
+# Despite the 32 in the name, this macro is defined for 32- and 64-bit.
+MINGW32 = {"__MINGW32__": True}
+
+# Note: In reality, the -std=gnu* options are only supported when preceded by
+# -Xclang.
+CLANG_CL_3_9 = (
+ CLANG_BASE("3.9.0")
+ + VS("18.00.00000")
+ + DEFAULT_C11
+ + SUPPORTS_GNU99
+ + SUPPORTS_GNUXX11
+ + SUPPORTS_CXX14
+) + {"*.cpp": {"__STDC_VERSION__": False, "__cplusplus": "201103L"}}
+CLANG_CL_9_0 = (
+ CLANG_BASE("9.0.0")
+ + VS("18.00.00000")
+ + DEFAULT_C11
+ + SUPPORTS_GNU99
+ + SUPPORTS_GNUXX11
+ + SUPPORTS_CXX14
+ + SUPPORTS_CXX17
+) + {"*.cpp": {"__STDC_VERSION__": False, "__cplusplus": "201103L"}}
+
+CLANG_CL_PLATFORM_X86 = FakeCompiler(
+ VS_PLATFORM_X86, GCC_PLATFORM_X86[None], GCC_PLATFORM_LITTLE_ENDIAN
+)
+CLANG_CL_PLATFORM_X86_64 = FakeCompiler(
+ VS_PLATFORM_X86_64, GCC_PLATFORM_X86_64[None], GCC_PLATFORM_LITTLE_ENDIAN
+)
+
+LIBRARY_NAME_INFOS = {
+ "linux-gnu": {
+ "DLL_PREFIX": "lib",
+ "DLL_SUFFIX": ".so",
+ "LIB_PREFIX": "lib",
+ "LIB_SUFFIX": "a",
+ "IMPORT_LIB_SUFFIX": "",
+ "OBJ_SUFFIX": "o",
+ },
+ "darwin11.2.0": {
+ "DLL_PREFIX": "lib",
+ "DLL_SUFFIX": ".dylib",
+ "LIB_PREFIX": "lib",
+ "LIB_SUFFIX": "a",
+ "IMPORT_LIB_SUFFIX": "",
+ "OBJ_SUFFIX": "o",
+ },
+ "mingw32": {
+ "DLL_PREFIX": "",
+ "DLL_SUFFIX": ".dll",
+ "LIB_PREFIX": "lib",
+ "LIB_SUFFIX": "a",
+ "IMPORT_LIB_SUFFIX": "a",
+ "OBJ_SUFFIX": "o",
+ },
+ "windows-msvc": {
+ "DLL_PREFIX": "",
+ "DLL_SUFFIX": ".dll",
+ "LIB_PREFIX": "",
+ "LIB_SUFFIX": "lib",
+ "IMPORT_LIB_SUFFIX": "lib",
+ "OBJ_SUFFIX": "obj",
+ },
+ "windows-gnu": {
+ "DLL_PREFIX": "",
+ "DLL_SUFFIX": ".dll",
+ "LIB_PREFIX": "lib",
+ "LIB_SUFFIX": "a",
+ "IMPORT_LIB_SUFFIX": "a",
+ "OBJ_SUFFIX": "o",
+ },
+ "openbsd6.1": {
+ "DLL_PREFIX": "lib",
+ "DLL_SUFFIX": ".so.1.0",
+ "LIB_PREFIX": "lib",
+ "LIB_SUFFIX": "a",
+ "IMPORT_LIB_SUFFIX": "",
+ "OBJ_SUFFIX": "o",
+ },
+}
+
+
+class BaseToolchainTest(BaseConfigureTest):
+ def setUp(self):
+ super(BaseToolchainTest, self).setUp()
+ self.out = StringIO()
+ self.logger = logging.getLogger("BaseToolchainTest")
+ self.logger.setLevel(logging.ERROR)
+ self.handler = logging.StreamHandler(self.out)
+ self.logger.addHandler(self.handler)
+
+ def tearDown(self):
+ self.logger.removeHandler(self.handler)
+ del self.handler
+ del self.out
+ super(BaseToolchainTest, self).tearDown()
+
+ def do_toolchain_test(self, paths, results, args=[], environ={}):
+ """Helper to test the toolchain checks from toolchain.configure.
+
+ - `paths` is a dict associating compiler paths to FakeCompiler
+ definitions from above.
+ - `results` is a dict associating result variable names from
+ toolchain.configure (c_compiler, cxx_compiler, host_c_compiler,
+ host_cxx_compiler) with a result.
+ The result can either be an error string, or a CompilerResult
+ corresponding to the object returned by toolchain.configure checks.
+ When the results for host_c_compiler are identical to c_compiler,
+ they can be omitted. Likewise for host_cxx_compiler vs.
+ cxx_compiler.
+ """
+ environ = dict(environ)
+ if "PATH" not in environ:
+ environ["PATH"] = os.pathsep.join(
+ mozpath.abspath(p) for p in ("/bin", "/usr/bin")
+ )
+
+ args = args + ["--enable-release", "--disable-bootstrap"]
+
+ sandbox = self.get_sandbox(paths, {}, args, environ, logger=self.logger)
+
+ for var in (
+ "c_compiler",
+ "cxx_compiler",
+ "host_c_compiler",
+ "host_cxx_compiler",
+ ):
+ if var in results:
+ result = results[var]
+ elif var.startswith("host_"):
+ result = results.get(var[5:], {})
+ else:
+ result = {}
+ try:
+ self.out.truncate(0)
+ self.out.seek(0)
+ compiler = sandbox._value_for(sandbox[var])
+ # Add var on both ends to make it clear which of the
+ # variables is failing the test when that happens.
+ self.assertEqual((var, compiler), (var, result))
+ except SystemExit:
+ self.assertEqual((var, result), (var, self.out.getvalue().strip()))
+ return
+
+ # Normalize the target os to match what we have as keys in
+ # LIBRARY_NAME_INFOS.
+ target_os = getattr(self, "TARGET", self.HOST).split("-", 2)[2]
+ if target_os == "mingw32":
+ compiler_type = sandbox._value_for(sandbox["c_compiler"]).type
+ if compiler_type == "clang-cl":
+ target_os = "windows-msvc"
+ elif target_os == "linux-gnuabi64":
+ target_os = "linux-gnu"
+
+ self.do_library_name_info_test(target_os, sandbox)
+
+ # Try again on artifact builds. In that case, we always get library
+ # name info for msvc on Windows
+ if target_os == "mingw32":
+ target_os = "windows-msvc"
+
+ sandbox = self.get_sandbox(
+ paths, {}, args + ["--enable-artifact-builds"], environ, logger=self.logger
+ )
+
+ self.do_library_name_info_test(target_os, sandbox)
+
+ def do_library_name_info_test(self, target_os, sandbox):
+ library_name_info = LIBRARY_NAME_INFOS[target_os]
+ for k in (
+ "DLL_PREFIX",
+ "DLL_SUFFIX",
+ "LIB_PREFIX",
+ "LIB_SUFFIX",
+ "IMPORT_LIB_SUFFIX",
+ "OBJ_SUFFIX",
+ ):
+ self.assertEqual(
+ "%s=%s" % (k, sandbox.get_config(k)),
+ "%s=%s" % (k, library_name_info[k]),
+ )
+
+
+def old_gcc_message(old_ver):
+ return "Only GCC 8.1 or newer is supported (found version {}).".format(old_ver)
+
+
+class LinuxToolchainTest(BaseToolchainTest):
+ PATHS = {
+ "/usr/bin/gcc": DEFAULT_GCC + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/g++": DEFAULT_GXX + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/gcc-4.9": GCC_4_9 + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/g++-4.9": GXX_4_9 + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/gcc-5": GCC_5 + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/g++-5": GXX_5 + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/gcc-6": GCC_6 + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/g++-6": GXX_6 + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/gcc-7": GCC_7 + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/g++-7": GXX_7 + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/gcc-8": GCC_8 + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/g++-8": GXX_8 + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/gcc-10": GCC_10 + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/g++-10": GXX_10 + GCC_PLATFORM_X86_64_LINUX,
+ "/usr/bin/clang": DEFAULT_CLANG + CLANG_PLATFORM_X86_64_LINUX,
+ "/usr/bin/clang++": DEFAULT_CLANGXX + CLANG_PLATFORM_X86_64_LINUX,
+ "/usr/bin/clang-7.0": CLANG_7_0 + CLANG_PLATFORM_X86_64_LINUX,
+ "/usr/bin/clang++-7.0": CLANGXX_7_0 + CLANG_PLATFORM_X86_64_LINUX,
+ "/usr/bin/clang-4.0": CLANG_4_0 + CLANG_PLATFORM_X86_64_LINUX,
+ "/usr/bin/clang++-4.0": CLANGXX_4_0 + CLANG_PLATFORM_X86_64_LINUX,
+ "/usr/bin/clang-3.3": CLANG_3_3 + CLANG_PLATFORM_X86_64_LINUX,
+ "/usr/bin/clang++-3.3": CLANGXX_3_3 + CLANG_PLATFORM_X86_64_LINUX,
+ }
+
+ GCC_4_7_RESULT = old_gcc_message("4.7.3")
+ GXX_4_7_RESULT = GCC_4_7_RESULT
+ GCC_4_9_RESULT = old_gcc_message("4.9.3")
+ GXX_4_9_RESULT = GCC_4_9_RESULT
+ GCC_5_RESULT = old_gcc_message("5.2.1")
+ GXX_5_RESULT = GCC_5_RESULT
+ GCC_6_RESULT = old_gcc_message("6.4.0")
+ GXX_6_RESULT = GCC_6_RESULT
+ GCC_7_RESULT = old_gcc_message("7.3.0")
+ GXX_7_RESULT = GCC_7_RESULT
+ GCC_8_RESULT = CompilerResult(
+ flags=["-std=gnu99"],
+ version="8.3.0",
+ type="gcc",
+ compiler="/usr/bin/gcc-8",
+ language="C",
+ )
+ GXX_8_RESULT = CompilerResult(
+ flags=["-std=gnu++17"],
+ version="8.3.0",
+ type="gcc",
+ compiler="/usr/bin/g++-8",
+ language="C++",
+ )
+ DEFAULT_GCC_RESULT = GCC_8_RESULT + {"compiler": "/usr/bin/gcc"}
+ DEFAULT_GXX_RESULT = GXX_8_RESULT + {"compiler": "/usr/bin/g++"}
+
+ CLANG_3_3_RESULT = (
+ "Only clang/llvm 7.0 or newer is supported (found version 3.3.0)."
+ )
+ CLANGXX_3_3_RESULT = (
+ "Only clang/llvm 7.0 or newer is supported (found version 3.3.0)."
+ )
+ CLANG_4_0_RESULT = (
+ "Only clang/llvm 7.0 or newer is supported (found version 4.0.2)."
+ )
+ CLANGXX_4_0_RESULT = (
+ "Only clang/llvm 7.0 or newer is supported (found version 4.0.2)."
+ )
+ CLANG_7_0_RESULT = CompilerResult(
+ flags=["-std=gnu99"],
+ version="7.0.0",
+ type="clang",
+ compiler="/usr/bin/clang-7.0",
+ language="C",
+ )
+ CLANGXX_7_0_RESULT = CompilerResult(
+ flags=["-std=gnu++17"],
+ version="7.0.0",
+ type="clang",
+ compiler="/usr/bin/clang++-7.0",
+ language="C++",
+ )
+ DEFAULT_CLANG_RESULT = CLANG_7_0_RESULT + {"compiler": "/usr/bin/clang"}
+ DEFAULT_CLANGXX_RESULT = CLANGXX_7_0_RESULT + {"compiler": "/usr/bin/clang++"}
+
+ def test_default(self):
+ # We'll try clang and gcc, and find clang first.
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT,
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ },
+ )
+
+ def test_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_GCC_RESULT,
+ "cxx_compiler": self.DEFAULT_GXX_RESULT,
+ },
+ environ={"CC": "gcc", "CXX": "g++"},
+ )
+
+ def test_unsupported_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": self.GCC_4_9_RESULT},
+ environ={"CC": "gcc-4.9", "CXX": "g++-4.9"},
+ )
+
+ # Maybe this should be reporting the mismatched version instead.
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_GCC_RESULT,
+ "cxx_compiler": self.GXX_4_9_RESULT,
+ },
+ environ={"CC": "gcc", "CXX": "g++-4.9"},
+ )
+
+ def test_overridden_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": self.GCC_7_RESULT, "cxx_compiler": self.GXX_7_RESULT},
+ environ={"CC": "gcc-7", "CXX": "g++-7"},
+ )
+
+ def test_guess_cxx(self):
+ # When CXX is not set, we guess it from CC.
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": self.GCC_7_RESULT, "cxx_compiler": self.GXX_7_RESULT},
+ environ={"CC": "gcc-7"},
+ )
+
+ def test_mismatched_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_GCC_RESULT,
+ "cxx_compiler": (
+ "The target C compiler is version 8.3.0, while the target "
+ "C++ compiler is version 10.2.1. Need to use the same compiler "
+ "version."
+ ),
+ },
+ environ={"CC": "gcc", "CXX": "g++-10"},
+ )
+
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_GCC_RESULT,
+ "cxx_compiler": self.DEFAULT_GXX_RESULT,
+ "host_c_compiler": self.DEFAULT_GCC_RESULT,
+ "host_cxx_compiler": (
+ "The host C compiler is version 8.3.0, while the host "
+ "C++ compiler is version 10.2.1. Need to use the same compiler "
+ "version."
+ ),
+ },
+ environ={"CC": "gcc", "HOST_CXX": "g++-10"},
+ )
+
+ def test_mismatched_compiler(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT,
+ "cxx_compiler": (
+ "The target C compiler is clang, while the target C++ compiler "
+ "is gcc. Need to use the same compiler suite."
+ ),
+ },
+ environ={"CXX": "g++"},
+ )
+
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT,
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ "host_c_compiler": self.DEFAULT_CLANG_RESULT,
+ "host_cxx_compiler": (
+ "The host C compiler is clang, while the host C++ compiler "
+ "is gcc. Need to use the same compiler suite."
+ ),
+ },
+ environ={"HOST_CXX": "g++"},
+ )
+
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": "`%s` is not a C compiler."
+ % mozpath.abspath("/usr/bin/g++")
+ },
+ environ={"CC": "g++"},
+ )
+
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT,
+ "cxx_compiler": "`%s` is not a C++ compiler."
+ % mozpath.abspath("/usr/bin/clang"),
+ },
+ environ={"CXX": "clang"},
+ )
+
+ def test_clang(self):
+ # We'll try gcc and clang, but since there is no gcc (gcc-x.y doesn't
+ # count), find clang.
+ paths = {
+ k: v
+ for k, v in six.iteritems(self.PATHS)
+ if os.path.basename(k) not in ("gcc", "g++")
+ }
+ self.do_toolchain_test(
+ paths,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT,
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ },
+ )
+
+ def test_guess_cxx_clang(self):
+ # When CXX is not set, we guess it from CC.
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.CLANG_7_0_RESULT,
+ "cxx_compiler": self.CLANGXX_7_0_RESULT,
+ },
+ environ={"CC": "clang-7.0"},
+ )
+
+ def test_unsupported_clang(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.CLANG_3_3_RESULT,
+ "cxx_compiler": self.CLANGXX_3_3_RESULT,
+ },
+ environ={"CC": "clang-3.3", "CXX": "clang++-3.3"},
+ )
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.CLANG_4_0_RESULT,
+ "cxx_compiler": self.CLANGXX_4_0_RESULT,
+ },
+ environ={"CC": "clang-4.0", "CXX": "clang++-4.0"},
+ )
+
+ def test_no_supported_compiler(self):
+ # Even if there are gcc-x.y or clang-x.y compilers available, we
+ # don't try them. This could be considered something to improve.
+ paths = {
+ k: v
+ for k, v in six.iteritems(self.PATHS)
+ if os.path.basename(k) not in ("gcc", "g++", "clang", "clang++")
+ }
+ self.do_toolchain_test(
+ paths, {"c_compiler": "Cannot find the target C compiler"}
+ )
+
+ def test_absolute_path(self):
+ paths = dict(self.PATHS)
+ paths.update(
+ {
+ "/opt/clang/bin/clang": paths["/usr/bin/clang"],
+ "/opt/clang/bin/clang++": paths["/usr/bin/clang++"],
+ }
+ )
+ result = {
+ "c_compiler": self.DEFAULT_CLANG_RESULT
+ + {"compiler": "/opt/clang/bin/clang"},
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT
+ + {"compiler": "/opt/clang/bin/clang++"},
+ }
+ self.do_toolchain_test(
+ paths,
+ result,
+ environ={"CC": "/opt/clang/bin/clang", "CXX": "/opt/clang/bin/clang++"},
+ )
+ # With CXX guess too.
+ self.do_toolchain_test(paths, result, environ={"CC": "/opt/clang/bin/clang"})
+
+ def test_atypical_name(self):
+ paths = dict(self.PATHS)
+ paths.update(
+ {
+ "/usr/bin/afl-clang-fast": paths["/usr/bin/clang"],
+ "/usr/bin/afl-clang-fast++": paths["/usr/bin/clang++"],
+ }
+ )
+ self.do_toolchain_test(
+ paths,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT
+ + {"compiler": "/usr/bin/afl-clang-fast"},
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT
+ + {"compiler": "/usr/bin/afl-clang-fast++"},
+ },
+ environ={"CC": "afl-clang-fast", "CXX": "afl-clang-fast++"},
+ )
+
+ def test_mixed_compilers(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT,
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ "host_c_compiler": self.DEFAULT_GCC_RESULT,
+ "host_cxx_compiler": self.DEFAULT_GXX_RESULT,
+ },
+ environ={"CC": "clang", "HOST_CC": "gcc"},
+ )
+
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT,
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ "host_c_compiler": self.DEFAULT_GCC_RESULT,
+ "host_cxx_compiler": self.DEFAULT_GXX_RESULT,
+ },
+ environ={"CC": "clang", "CXX": "clang++", "HOST_CC": "gcc"},
+ )
+
+
+class LinuxSimpleCrossToolchainTest(BaseToolchainTest):
+ TARGET = "i686-pc-linux-gnu"
+ PATHS = LinuxToolchainTest.PATHS
+ DEFAULT_GCC_RESULT = LinuxToolchainTest.DEFAULT_GCC_RESULT
+ DEFAULT_GXX_RESULT = LinuxToolchainTest.DEFAULT_GXX_RESULT
+ DEFAULT_CLANG_RESULT = LinuxToolchainTest.DEFAULT_CLANG_RESULT
+ DEFAULT_CLANGXX_RESULT = LinuxToolchainTest.DEFAULT_CLANGXX_RESULT
+
+ def test_cross_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_GCC_RESULT + {"flags": ["-m32"]},
+ "cxx_compiler": self.DEFAULT_GXX_RESULT + {"flags": ["-m32"]},
+ "host_c_compiler": self.DEFAULT_GCC_RESULT,
+ "host_cxx_compiler": self.DEFAULT_GXX_RESULT,
+ },
+ environ={"CC": "gcc"},
+ )
+
+ def test_cross_clang(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT + {"flags": ["-m32"]},
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT + {"flags": ["-m32"]},
+ "host_c_compiler": self.DEFAULT_CLANG_RESULT,
+ "host_cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ },
+ )
+
+
+class LinuxX86_64CrossToolchainTest(BaseToolchainTest):
+ HOST = "i686-pc-linux-gnu"
+ TARGET = "x86_64-pc-linux-gnu"
+ PATHS = {
+ "/usr/bin/gcc": DEFAULT_GCC + GCC_PLATFORM_X86_LINUX,
+ "/usr/bin/g++": DEFAULT_GXX + GCC_PLATFORM_X86_LINUX,
+ "/usr/bin/clang": DEFAULT_CLANG + CLANG_PLATFORM_X86_LINUX,
+ "/usr/bin/clang++": DEFAULT_CLANGXX + CLANG_PLATFORM_X86_LINUX,
+ }
+ DEFAULT_GCC_RESULT = LinuxToolchainTest.DEFAULT_GCC_RESULT
+ DEFAULT_GXX_RESULT = LinuxToolchainTest.DEFAULT_GXX_RESULT
+ DEFAULT_CLANG_RESULT = LinuxToolchainTest.DEFAULT_CLANG_RESULT
+ DEFAULT_CLANGXX_RESULT = LinuxToolchainTest.DEFAULT_CLANGXX_RESULT
+
+ def test_cross_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_GCC_RESULT + {"flags": ["-m64"]},
+ "cxx_compiler": self.DEFAULT_GXX_RESULT + {"flags": ["-m64"]},
+ "host_c_compiler": self.DEFAULT_GCC_RESULT,
+ "host_cxx_compiler": self.DEFAULT_GXX_RESULT,
+ },
+ environ={"CC": "gcc"},
+ )
+
+ def test_cross_clang(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT + {"flags": ["-m64"]},
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT + {"flags": ["-m64"]},
+ "host_c_compiler": self.DEFAULT_CLANG_RESULT,
+ "host_cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ },
+ )
+
+
+def xcrun(stdin, args):
+ if args == ("--show-sdk-path",):
+ return (
+ 0,
+ mozpath.join(os.path.abspath(os.path.dirname(__file__)), "macos_fake_sdk"),
+ "",
+ )
+ raise NotImplementedError()
+
+
+class OSXToolchainTest(BaseToolchainTest):
+ HOST = "x86_64-apple-darwin11.2.0"
+ PATHS = {
+ "/usr/bin/gcc-5": GCC_5 + GCC_PLATFORM_X86_64_OSX,
+ "/usr/bin/g++-5": GXX_5 + GCC_PLATFORM_X86_64_OSX,
+ "/usr/bin/gcc-8": GCC_8 + GCC_PLATFORM_X86_64_OSX,
+ "/usr/bin/g++-8": GXX_8 + GCC_PLATFORM_X86_64_OSX,
+ "/usr/bin/clang": XCODE_CLANG_7_0 + CLANG_PLATFORM_X86_64_OSX,
+ "/usr/bin/clang++": XCODE_CLANGXX_7_0 + CLANG_PLATFORM_X86_64_OSX,
+ "/usr/bin/clang-4.0": XCODE_CLANG_4_0 + CLANG_PLATFORM_X86_64_OSX,
+ "/usr/bin/clang++-4.0": XCODE_CLANGXX_4_0 + CLANG_PLATFORM_X86_64_OSX,
+ "/usr/bin/clang-3.3": XCODE_CLANG_3_3 + CLANG_PLATFORM_X86_64_OSX,
+ "/usr/bin/clang++-3.3": XCODE_CLANGXX_3_3 + CLANG_PLATFORM_X86_64_OSX,
+ "/usr/bin/xcrun": xcrun,
+ }
+ CLANG_3_3_RESULT = (
+ "Only clang/llvm 7.0 or newer is supported (found version 4.0.0.or.less)."
+ )
+ CLANGXX_3_3_RESULT = (
+ "Only clang/llvm 7.0 or newer is supported (found version 4.0.0.or.less)."
+ )
+ CLANG_4_0_RESULT = (
+ "Only clang/llvm 7.0 or newer is supported (found version 4.0.0.or.less)."
+ )
+ CLANGXX_4_0_RESULT = (
+ "Only clang/llvm 7.0 or newer is supported (found version 4.0.0.or.less)."
+ )
+ DEFAULT_CLANG_RESULT = CompilerResult(
+ flags=["-std=gnu99"],
+ version="7.0.0",
+ type="clang",
+ compiler="/usr/bin/clang",
+ language="C",
+ )
+ DEFAULT_CLANGXX_RESULT = CompilerResult(
+ flags=["-stdlib=libc++", "-std=gnu++17"],
+ version="7.0.0",
+ type="clang",
+ compiler="/usr/bin/clang++",
+ language="C++",
+ )
+ GCC_5_RESULT = LinuxToolchainTest.GCC_5_RESULT
+ GXX_5_RESULT = LinuxToolchainTest.GXX_5_RESULT
+ GCC_8_RESULT = LinuxToolchainTest.GCC_8_RESULT
+ GXX_8_RESULT = LinuxToolchainTest.GXX_8_RESULT
+ SYSROOT_FLAGS = {
+ "flags": PrependFlags(
+ [
+ "-isysroot",
+ xcrun("", ("--show-sdk-path",))[1],
+ "-mmacosx-version-min=10.12",
+ ]
+ )
+ }
+
+ def test_clang(self):
+ # We only try clang because gcc is known not to work.
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT + self.SYSROOT_FLAGS,
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT + self.SYSROOT_FLAGS,
+ },
+ )
+
+ def test_not_gcc(self):
+ # We won't pick GCC if it's the only thing available.
+ paths = {
+ k: v
+ for k, v in six.iteritems(self.PATHS)
+ if os.path.basename(k) not in ("clang", "clang++")
+ }
+ self.do_toolchain_test(
+ paths, {"c_compiler": "Cannot find the target C compiler"}
+ )
+
+ def test_unsupported_clang(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.CLANG_3_3_RESULT,
+ "cxx_compiler": self.CLANGXX_3_3_RESULT,
+ },
+ environ={"CC": "clang-3.3", "CXX": "clang++-3.3"},
+ )
+ # When targeting mac, we require at least version 5.
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.CLANG_4_0_RESULT,
+ "cxx_compiler": self.CLANGXX_4_0_RESULT,
+ },
+ environ={"CC": "clang-4.0", "CXX": "clang++-4.0"},
+ )
+
+ def test_forced_gcc(self):
+ # GCC can still be forced if the user really wants it.
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.GCC_8_RESULT + self.SYSROOT_FLAGS,
+ "cxx_compiler": self.GXX_8_RESULT + self.SYSROOT_FLAGS,
+ },
+ environ={"CC": "gcc-8", "CXX": "g++-8"},
+ )
+
+ def test_forced_unsupported_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": self.GCC_5_RESULT},
+ environ={"CC": "gcc-5", "CXX": "g++-5"},
+ )
+
+
+class MingwToolchainTest(BaseToolchainTest):
+ HOST = "i686-pc-mingw32"
+
+ # For the purpose of this test, it doesn't matter that the paths are not
+ # real Windows paths.
+ PATHS = {
+ "/usr/bin/cl": VS_2017u8 + VS_PLATFORM_X86,
+ "/usr/bin/clang-cl-3.9": CLANG_CL_3_9 + CLANG_CL_PLATFORM_X86,
+ "/usr/bin/clang-cl": CLANG_CL_9_0 + CLANG_CL_PLATFORM_X86,
+ "/usr/bin/gcc": DEFAULT_GCC + GCC_PLATFORM_X86_WIN + MINGW32,
+ "/usr/bin/g++": DEFAULT_GXX + GCC_PLATFORM_X86_WIN + MINGW32,
+ "/usr/bin/gcc-4.9": GCC_4_9 + GCC_PLATFORM_X86_WIN + MINGW32,
+ "/usr/bin/g++-4.9": GXX_4_9 + GCC_PLATFORM_X86_WIN + MINGW32,
+ "/usr/bin/gcc-5": GCC_5 + GCC_PLATFORM_X86_WIN + MINGW32,
+ "/usr/bin/g++-5": GXX_5 + GCC_PLATFORM_X86_WIN + MINGW32,
+ "/usr/bin/gcc-6": GCC_6 + GCC_PLATFORM_X86_WIN + MINGW32,
+ "/usr/bin/g++-6": GXX_6 + GCC_PLATFORM_X86_WIN + MINGW32,
+ "/usr/bin/gcc-7": GCC_7 + GCC_PLATFORM_X86_WIN + MINGW32,
+ "/usr/bin/g++-7": GXX_7 + GCC_PLATFORM_X86_WIN + MINGW32,
+ "/usr/bin/clang": DEFAULT_CLANG + CLANG_PLATFORM_X86_WIN,
+ "/usr/bin/clang++": DEFAULT_CLANGXX + CLANG_PLATFORM_X86_WIN,
+ "/usr/bin/clang-7.0": CLANG_7_0 + CLANG_PLATFORM_X86_WIN,
+ "/usr/bin/clang++-7.0": CLANGXX_7_0 + CLANG_PLATFORM_X86_WIN,
+ "/usr/bin/clang-4.0": CLANG_4_0 + CLANG_PLATFORM_X86_WIN,
+ "/usr/bin/clang++-4.0": CLANGXX_4_0 + CLANG_PLATFORM_X86_WIN,
+ "/usr/bin/clang-3.3": CLANG_3_3 + CLANG_PLATFORM_X86_WIN,
+ "/usr/bin/clang++-3.3": CLANGXX_3_3 + CLANG_PLATFORM_X86_WIN,
+ }
+
+ CLANG_CL_3_9_RESULT = (
+ "Only clang-cl 9.0 or newer is supported (found version 3.9.0)"
+ )
+ CLANG_CL_9_0_RESULT = CompilerResult(
+ version="9.0.0",
+ flags=["-Xclang", "-std=gnu99"],
+ type="clang-cl",
+ compiler="/usr/bin/clang-cl",
+ language="C",
+ )
+ CLANGXX_CL_3_9_RESULT = (
+ "Only clang-cl 9.0 or newer is supported (found version 3.9.0)"
+ )
+ CLANGXX_CL_9_0_RESULT = CompilerResult(
+ version="9.0.0",
+ flags=["-Xclang", "-std=c++17"],
+ type="clang-cl",
+ compiler="/usr/bin/clang-cl",
+ language="C++",
+ )
+ CLANG_3_3_RESULT = LinuxToolchainTest.CLANG_3_3_RESULT
+ CLANGXX_3_3_RESULT = LinuxToolchainTest.CLANGXX_3_3_RESULT
+ CLANG_4_0_RESULT = LinuxToolchainTest.CLANG_4_0_RESULT
+ CLANGXX_4_0_RESULT = LinuxToolchainTest.CLANGXX_4_0_RESULT
+ DEFAULT_CLANG_RESULT = LinuxToolchainTest.DEFAULT_CLANG_RESULT
+ DEFAULT_CLANGXX_RESULT = LinuxToolchainTest.DEFAULT_CLANGXX_RESULT
+
+ def test_unsupported_msvc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": "Unknown compiler or compiler not supported."},
+ environ={"CC": "/usr/bin/cl"},
+ )
+
+ def test_unsupported_clang_cl(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": self.CLANG_CL_3_9_RESULT},
+ environ={"CC": "/usr/bin/clang-cl-3.9"},
+ )
+
+ def test_clang_cl(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.CLANG_CL_9_0_RESULT,
+ "cxx_compiler": self.CLANGXX_CL_9_0_RESULT,
+ },
+ )
+
+ def test_gcc(self):
+ # GCC is unsupported, if you try it should find clang.
+ paths = {
+ k: v
+ for k, v in six.iteritems(self.PATHS)
+ if os.path.basename(k) != "clang-cl"
+ }
+ self.do_toolchain_test(
+ paths,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT,
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ },
+ )
+
+ # This test is not perfect, as the GCC version needs to be updated when we
+ # bump the minimum GCC version, but the idea is that even supported GCC
+ # on other platforms should not be supported on Windows.
+ def test_overridden_supported_elsewhere_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": "Unknown compiler or compiler not supported."},
+ environ={"CC": "gcc-7", "CXX": "g++-7"},
+ )
+
+ def test_overridden_unsupported_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": "Unknown compiler or compiler not supported."},
+ environ={"CC": "gcc-5", "CXX": "g++-5"},
+ )
+
+ def test_clang(self):
+ # We'll pick clang if nothing else is found.
+ paths = {
+ k: v
+ for k, v in six.iteritems(self.PATHS)
+ if os.path.basename(k) not in ("clang-cl", "gcc")
+ }
+ self.do_toolchain_test(
+ paths,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT,
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ },
+ )
+
+ def test_overridden_unsupported_clang(self):
+ # clang 3.3 C compiler is perfectly fine, but we need more for C++.
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.CLANG_3_3_RESULT,
+ "cxx_compiler": self.CLANGXX_3_3_RESULT,
+ },
+ environ={"CC": "clang-3.3", "CXX": "clang++-3.3"},
+ )
+
+
+class Mingw64ToolchainTest(MingwToolchainTest):
+ HOST = "x86_64-pc-mingw32"
+
+ # For the purpose of this test, it doesn't matter that the paths are not
+ # real Windows paths.
+ PATHS = {
+ "/usr/bin/cl": VS_2017u8 + VS_PLATFORM_X86_64,
+ "/usr/bin/clang-cl": CLANG_CL_9_0 + CLANG_CL_PLATFORM_X86_64,
+ "/usr/bin/clang-cl-3.9": CLANG_CL_3_9 + CLANG_CL_PLATFORM_X86_64,
+ "/usr/bin/gcc": DEFAULT_GCC + GCC_PLATFORM_X86_64_WIN + MINGW32,
+ "/usr/bin/g++": DEFAULT_GXX + GCC_PLATFORM_X86_64_WIN + MINGW32,
+ "/usr/bin/gcc-4.9": GCC_4_9 + GCC_PLATFORM_X86_64_WIN + MINGW32,
+ "/usr/bin/g++-4.9": GXX_4_9 + GCC_PLATFORM_X86_64_WIN + MINGW32,
+ "/usr/bin/gcc-5": GCC_5 + GCC_PLATFORM_X86_64_WIN + MINGW32,
+ "/usr/bin/g++-5": GXX_5 + GCC_PLATFORM_X86_64_WIN + MINGW32,
+ "/usr/bin/gcc-6": GCC_6 + GCC_PLATFORM_X86_64_WIN + MINGW32,
+ "/usr/bin/g++-6": GXX_6 + GCC_PLATFORM_X86_64_WIN + MINGW32,
+ "/usr/bin/gcc-7": GCC_7 + GCC_PLATFORM_X86_64_WIN + MINGW32,
+ "/usr/bin/g++-7": GXX_7 + GCC_PLATFORM_X86_64_WIN + MINGW32,
+ "/usr/bin/clang": DEFAULT_CLANG + CLANG_PLATFORM_X86_64_WIN,
+ "/usr/bin/clang++": DEFAULT_CLANGXX + CLANG_PLATFORM_X86_64_WIN,
+ "/usr/bin/clang-7.0": CLANG_7_0 + CLANG_PLATFORM_X86_64_WIN,
+ "/usr/bin/clang++-7.0": CLANGXX_7_0 + CLANG_PLATFORM_X86_64_WIN,
+ "/usr/bin/clang-4.0": CLANG_4_0 + CLANG_PLATFORM_X86_64_WIN,
+ "/usr/bin/clang++-4.0": CLANGXX_4_0 + CLANG_PLATFORM_X86_64_WIN,
+ "/usr/bin/clang-3.3": CLANG_3_3 + CLANG_PLATFORM_X86_64_WIN,
+ "/usr/bin/clang++-3.3": CLANGXX_3_3 + CLANG_PLATFORM_X86_64_WIN,
+ }
+
+
+class WindowsToolchainTest(BaseToolchainTest):
+ HOST = "i686-pc-windows-msvc"
+
+ PATHS = MingwToolchainTest.PATHS
+
+ def test_unsupported_msvc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": "Unknown compiler or compiler not supported."},
+ environ={"CC": "/usr/bin/cl"},
+ )
+
+ def test_unsupported_clang_cl(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": MingwToolchainTest.CLANG_CL_3_9_RESULT},
+ environ={"CC": "/usr/bin/clang-cl-3.9"},
+ )
+
+ def test_clang_cl(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": MingwToolchainTest.CLANG_CL_9_0_RESULT,
+ "cxx_compiler": MingwToolchainTest.CLANGXX_CL_9_0_RESULT,
+ },
+ )
+
+ def test_unsupported_gcc(self):
+ paths = {
+ k: v
+ for k, v in six.iteritems(self.PATHS)
+ if os.path.basename(k) != "clang-cl"
+ }
+ self.do_toolchain_test(
+ paths,
+ {"c_compiler": "Cannot find the target C compiler"},
+ )
+
+ def test_overridden_unsupported_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": "Unknown compiler or compiler not supported."},
+ environ={"CC": "gcc-5", "CXX": "g++-5"},
+ )
+
+ def test_unsupported_clang(self):
+ paths = {
+ k: v
+ for k, v in six.iteritems(self.PATHS)
+ if os.path.basename(k) not in ("clang-cl", "gcc")
+ }
+ self.do_toolchain_test(
+ paths,
+ {"c_compiler": "Cannot find the target C compiler"},
+ )
+
+ def test_overridden_unsupported_clang(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": "Unknown compiler or compiler not supported."},
+ environ={"CC": "clang-3.3", "CXX": "clang++-3.3"},
+ )
+
+
+class Windows64ToolchainTest(WindowsToolchainTest):
+ HOST = "x86_64-pc-windows-msvc"
+
+ PATHS = Mingw64ToolchainTest.PATHS
+
+
+class WindowsGnuToolchainTest(BaseToolchainTest):
+ HOST = "i686-pc-windows-gnu"
+
+ PATHS = MingwToolchainTest.PATHS
+
+ def test_unsupported_msvc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": "Unknown compiler or compiler not supported."},
+ environ={"CC": "/usr/bin/cl"},
+ )
+
+ def test_unsupported_clang_cl(self):
+ paths = {
+ k: v
+ for k, v in six.iteritems(self.PATHS)
+ if os.path.basename(k) == "clang-cl"
+ }
+ self.do_toolchain_test(
+ paths,
+ {"c_compiler": "Cannot find the target C compiler"},
+ )
+
+ def test_overridden_unsupported_clang_cl(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": "Unknown compiler or compiler not supported."},
+ environ={"CC": "clang-cl", "CXX": "clang-cl"},
+ )
+
+ def test_unsupported_gcc(self):
+ paths = {
+ k: v for k, v in six.iteritems(self.PATHS) if os.path.basename(k) == "gcc"
+ }
+ self.do_toolchain_test(
+ paths,
+ {"c_compiler": "Cannot find the target C compiler"},
+ )
+
+ def test_overridden_unsupported_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": "Unknown compiler or compiler not supported."},
+ environ={"CC": "gcc-5", "CXX": "g++-5"},
+ )
+
+ def test_clang(self):
+ paths = {
+ k: v
+ for k, v in six.iteritems(self.PATHS)
+ if os.path.basename(k) not in ("clang-cl", "gcc")
+ }
+ self.do_toolchain_test(
+ paths,
+ {
+ "c_compiler": MingwToolchainTest.DEFAULT_CLANG_RESULT,
+ "cxx_compiler": MingwToolchainTest.DEFAULT_CLANGXX_RESULT,
+ },
+ )
+
+ def test_overridden_unsupported_clang(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": MingwToolchainTest.CLANG_3_3_RESULT,
+ "cxx_compiler": MingwToolchainTest.CLANGXX_3_3_RESULT,
+ },
+ environ={"CC": "clang-3.3", "CXX": "clang++-3.3"},
+ )
+
+
+class WindowsGnu64ToolchainTest(WindowsGnuToolchainTest):
+ HOST = "x86_64-pc-windows-gnu"
+
+ PATHS = Mingw64ToolchainTest.PATHS
+
+
+class LinuxCrossCompileToolchainTest(BaseToolchainTest):
+ TARGET = "arm-unknown-linux-gnu"
+ PATHS = {
+ "/usr/bin/arm-linux-gnu-gcc-4.9": GCC_4_9 + GCC_PLATFORM_ARM_LINUX,
+ "/usr/bin/arm-linux-gnu-g++-4.9": GXX_4_9 + GCC_PLATFORM_ARM_LINUX,
+ "/usr/bin/arm-linux-gnu-gcc-5": GCC_5 + GCC_PLATFORM_ARM_LINUX,
+ "/usr/bin/arm-linux-gnu-g++-5": GXX_5 + GCC_PLATFORM_ARM_LINUX,
+ "/usr/bin/arm-linux-gnu-gcc": DEFAULT_GCC + GCC_PLATFORM_ARM_LINUX,
+ "/usr/bin/arm-linux-gnu-g++": DEFAULT_GXX + GCC_PLATFORM_ARM_LINUX,
+ "/usr/bin/arm-linux-gnu-gcc-7": GCC_7 + GCC_PLATFORM_ARM_LINUX,
+ "/usr/bin/arm-linux-gnu-g++-7": GXX_7 + GCC_PLATFORM_ARM_LINUX,
+ }
+ PATHS.update(LinuxToolchainTest.PATHS)
+ ARM_GCC_4_9_RESULT = LinuxToolchainTest.GCC_4_9_RESULT
+ ARM_GXX_4_9_RESULT = LinuxToolchainTest.GXX_4_9_RESULT
+ ARM_GCC_5_RESULT = LinuxToolchainTest.GCC_5_RESULT
+ ARM_GXX_5_RESULT = LinuxToolchainTest.GXX_5_RESULT
+ ARM_DEFAULT_GCC_RESULT = LinuxToolchainTest.DEFAULT_GCC_RESULT + {
+ "compiler": "/usr/bin/arm-linux-gnu-gcc"
+ }
+ ARM_DEFAULT_GXX_RESULT = LinuxToolchainTest.DEFAULT_GXX_RESULT + {
+ "compiler": "/usr/bin/arm-linux-gnu-g++"
+ }
+ ARM_GCC_7_RESULT = LinuxToolchainTest.GCC_7_RESULT
+ ARM_GXX_7_RESULT = LinuxToolchainTest.GXX_7_RESULT
+ DEFAULT_CLANG_RESULT = LinuxToolchainTest.DEFAULT_CLANG_RESULT
+ DEFAULT_CLANGXX_RESULT = LinuxToolchainTest.DEFAULT_CLANGXX_RESULT
+ DEFAULT_GCC_RESULT = LinuxToolchainTest.DEFAULT_GCC_RESULT
+ DEFAULT_GXX_RESULT = LinuxToolchainTest.DEFAULT_GXX_RESULT
+
+ little_endian = FakeCompiler(GCC_PLATFORM_LINUX, GCC_PLATFORM_LITTLE_ENDIAN)
+ big_endian = FakeCompiler(GCC_PLATFORM_LINUX, GCC_PLATFORM_BIG_ENDIAN)
+
+ PLATFORMS = {
+ "i686-pc-linux-gnu": GCC_PLATFORM_X86_LINUX,
+ "x86_64-pc-linux-gnu": GCC_PLATFORM_X86_64_LINUX,
+ "arm-unknown-linux-gnu": GCC_PLATFORM_ARM_LINUX,
+ "aarch64-unknown-linux-gnu": little_endian + {"__aarch64__": 1},
+ "ia64-unknown-linux-gnu": little_endian + {"__ia64__": 1},
+ "s390x-unknown-linux-gnu": big_endian + {"__s390x__": 1, "__s390__": 1},
+ "s390-unknown-linux-gnu": big_endian + {"__s390__": 1},
+ "powerpc64-unknown-linux-gnu": big_endian
+ + {
+ None: {"__powerpc64__": 1, "__powerpc__": 1},
+ "-m32": {"__powerpc64__": False},
+ },
+ "powerpc-unknown-linux-gnu": big_endian
+ + {None: {"__powerpc__": 1}, "-m64": {"__powerpc64__": 1}},
+ "alpha-unknown-linux-gnu": little_endian + {"__alpha__": 1},
+ "hppa-unknown-linux-gnu": big_endian + {"__hppa__": 1},
+ "sparc64-unknown-linux-gnu": big_endian
+ + {None: {"__arch64__": 1, "__sparc__": 1}, "-m32": {"__arch64__": False}},
+ "sparc-unknown-linux-gnu": big_endian
+ + {None: {"__sparc__": 1}, "-m64": {"__arch64__": 1}},
+ "m68k-unknown-linux-gnu": big_endian + {"__m68k__": 1},
+ "mips64-unknown-linux-gnuabi64": big_endian + {"__mips64": 1, "__mips__": 1},
+ "mips-unknown-linux-gnu": big_endian + {"__mips__": 1},
+ "riscv64-unknown-linux-gnu": little_endian + {"__riscv": 1, "__riscv_xlen": 64},
+ "sh4-unknown-linux-gnu": little_endian + {"__sh__": 1},
+ }
+
+ PLATFORMS["powerpc64le-unknown-linux-gnu"] = (
+ PLATFORMS["powerpc64-unknown-linux-gnu"] + GCC_PLATFORM_LITTLE_ENDIAN
+ )
+ PLATFORMS["mips64el-unknown-linux-gnuabi64"] = (
+ PLATFORMS["mips64-unknown-linux-gnuabi64"] + GCC_PLATFORM_LITTLE_ENDIAN
+ )
+ PLATFORMS["mipsel-unknown-linux-gnu"] = (
+ PLATFORMS["mips-unknown-linux-gnu"] + GCC_PLATFORM_LITTLE_ENDIAN
+ )
+
+ def do_test_cross_gcc_32_64(self, host, target):
+ self.HOST = host
+ self.TARGET = target
+ paths = {
+ "/usr/bin/gcc": DEFAULT_GCC + self.PLATFORMS[host],
+ "/usr/bin/g++": DEFAULT_GXX + self.PLATFORMS[host],
+ }
+ cross_flags = {"flags": ["-m64" if "64" in target else "-m32"]}
+ self.do_toolchain_test(
+ paths,
+ {
+ "c_compiler": self.DEFAULT_GCC_RESULT + cross_flags,
+ "cxx_compiler": self.DEFAULT_GXX_RESULT + cross_flags,
+ "host_c_compiler": self.DEFAULT_GCC_RESULT,
+ "host_cxx_compiler": self.DEFAULT_GXX_RESULT,
+ },
+ )
+ self.HOST = LinuxCrossCompileToolchainTest.HOST
+ self.TARGET = LinuxCrossCompileToolchainTest.TARGET
+
+ def test_cross_x86_x64(self):
+ self.do_test_cross_gcc_32_64("i686-pc-linux-gnu", "x86_64-pc-linux-gnu")
+ self.do_test_cross_gcc_32_64("x86_64-pc-linux-gnu", "i686-pc-linux-gnu")
+
+ def test_cross_sparc_sparc64(self):
+ self.do_test_cross_gcc_32_64(
+ "sparc-unknown-linux-gnu", "sparc64-unknown-linux-gnu"
+ )
+ self.do_test_cross_gcc_32_64(
+ "sparc64-unknown-linux-gnu", "sparc-unknown-linux-gnu"
+ )
+
+ def test_cross_ppc_ppc64(self):
+ self.do_test_cross_gcc_32_64(
+ "powerpc-unknown-linux-gnu", "powerpc64-unknown-linux-gnu"
+ )
+ self.do_test_cross_gcc_32_64(
+ "powerpc64-unknown-linux-gnu", "powerpc-unknown-linux-gnu"
+ )
+
+ def do_test_cross_gcc(self, host, target):
+ self.HOST = host
+ self.TARGET = target
+ host_cpu = host.split("-")[0]
+ cpu, manufacturer, os = target.split("-", 2)
+ toolchain_prefix = "/usr/bin/%s-%s" % (cpu, os)
+ paths = {
+ "/usr/bin/gcc": DEFAULT_GCC + self.PLATFORMS[host],
+ "/usr/bin/g++": DEFAULT_GXX + self.PLATFORMS[host],
+ }
+ self.do_toolchain_test(
+ paths,
+ {
+ "c_compiler": (
+ "Target C compiler target CPU (%s) "
+ "does not match --target CPU (%s)" % (host_cpu, cpu)
+ )
+ },
+ )
+
+ paths.update(
+ {
+ "%s-gcc" % toolchain_prefix: DEFAULT_GCC + self.PLATFORMS[target],
+ "%s-g++" % toolchain_prefix: DEFAULT_GXX + self.PLATFORMS[target],
+ }
+ )
+ self.do_toolchain_test(
+ paths,
+ {
+ "c_compiler": self.DEFAULT_GCC_RESULT
+ + {"compiler": "%s-gcc" % toolchain_prefix},
+ "cxx_compiler": self.DEFAULT_GXX_RESULT
+ + {"compiler": "%s-g++" % toolchain_prefix},
+ "host_c_compiler": self.DEFAULT_GCC_RESULT,
+ "host_cxx_compiler": self.DEFAULT_GXX_RESULT,
+ },
+ )
+ self.HOST = LinuxCrossCompileToolchainTest.HOST
+ self.TARGET = LinuxCrossCompileToolchainTest.TARGET
+
+ def test_cross_gcc_misc(self):
+ for target in self.PLATFORMS:
+ if not target.endswith("-pc-linux-gnu"):
+ self.do_test_cross_gcc("x86_64-pc-linux-gnu", target)
+
+ def test_cannot_cross(self):
+ self.TARGET = "mipsel-unknown-linux-gnu"
+
+ paths = {
+ "/usr/bin/gcc": DEFAULT_GCC + self.PLATFORMS["mips-unknown-linux-gnu"],
+ "/usr/bin/g++": DEFAULT_GXX + self.PLATFORMS["mips-unknown-linux-gnu"],
+ }
+ self.do_toolchain_test(
+ paths,
+ {
+ "c_compiler": (
+ "Target C compiler target endianness (big) "
+ "does not match --target endianness (little)"
+ )
+ },
+ )
+ self.TARGET = LinuxCrossCompileToolchainTest.TARGET
+
+ def test_overridden_cross_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.ARM_GCC_7_RESULT,
+ "cxx_compiler": self.ARM_GXX_7_RESULT,
+ "host_c_compiler": self.DEFAULT_GCC_RESULT,
+ "host_cxx_compiler": self.DEFAULT_GXX_RESULT,
+ },
+ environ={"CC": "arm-linux-gnu-gcc-7", "CXX": "arm-linux-gnu-g++-7"},
+ )
+
+ def test_overridden_unsupported_cross_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {"c_compiler": self.ARM_GCC_4_9_RESULT},
+ environ={"CC": "arm-linux-gnu-gcc-4.9", "CXX": "arm-linux-gnu-g++-4.9"},
+ )
+
+ def test_guess_cross_cxx(self):
+ # When CXX is not set, we guess it from CC.
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.ARM_GCC_7_RESULT,
+ "cxx_compiler": self.ARM_GXX_7_RESULT,
+ "host_c_compiler": self.DEFAULT_GCC_RESULT,
+ "host_cxx_compiler": self.DEFAULT_GXX_RESULT,
+ },
+ environ={"CC": "arm-linux-gnu-gcc-7"},
+ )
+
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.ARM_DEFAULT_GCC_RESULT,
+ "cxx_compiler": self.ARM_DEFAULT_GXX_RESULT,
+ "host_c_compiler": self.DEFAULT_CLANG_RESULT,
+ "host_cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ },
+ environ={"CC": "arm-linux-gnu-gcc", "HOST_CC": "clang"},
+ )
+
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.ARM_DEFAULT_GCC_RESULT,
+ "cxx_compiler": self.ARM_DEFAULT_GXX_RESULT,
+ "host_c_compiler": self.DEFAULT_CLANG_RESULT,
+ "host_cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ },
+ environ={
+ "CC": "arm-linux-gnu-gcc",
+ "CXX": "arm-linux-gnu-g++",
+ "HOST_CC": "clang",
+ },
+ )
+
+ def test_cross_clang(self):
+ cross_clang_result = self.DEFAULT_CLANG_RESULT + {
+ "flags": ["--target=arm-linux-gnu"]
+ }
+ cross_clangxx_result = self.DEFAULT_CLANGXX_RESULT + {
+ "flags": ["--target=arm-linux-gnu"]
+ }
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": cross_clang_result,
+ "cxx_compiler": cross_clangxx_result,
+ "host_c_compiler": self.DEFAULT_CLANG_RESULT,
+ "host_cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ },
+ environ={"CC": "clang", "HOST_CC": "clang"},
+ )
+
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": cross_clang_result,
+ "cxx_compiler": cross_clangxx_result,
+ "host_c_compiler": self.DEFAULT_CLANG_RESULT,
+ "host_cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ },
+ environ={"CC": "clang"},
+ )
+
+ def test_cross_atypical_clang(self):
+ paths = dict(self.PATHS)
+ paths.update(
+ {
+ "/usr/bin/afl-clang-fast": paths["/usr/bin/clang"],
+ "/usr/bin/afl-clang-fast++": paths["/usr/bin/clang++"],
+ }
+ )
+ afl_clang_result = self.DEFAULT_CLANG_RESULT + {
+ "compiler": "/usr/bin/afl-clang-fast"
+ }
+ afl_clangxx_result = self.DEFAULT_CLANGXX_RESULT + {
+ "compiler": "/usr/bin/afl-clang-fast++"
+ }
+ self.do_toolchain_test(
+ paths,
+ {
+ "c_compiler": afl_clang_result + {"flags": ["--target=arm-linux-gnu"]},
+ "cxx_compiler": afl_clangxx_result
+ + {"flags": ["--target=arm-linux-gnu"]},
+ "host_c_compiler": afl_clang_result,
+ "host_cxx_compiler": afl_clangxx_result,
+ },
+ environ={"CC": "afl-clang-fast", "CXX": "afl-clang-fast++"},
+ )
+
+
+class OSXCrossToolchainTest(BaseToolchainTest):
+ TARGET = "i686-apple-darwin11.2.0"
+ PATHS = dict(LinuxToolchainTest.PATHS)
+ PATHS.update(
+ {
+ "/usr/bin/clang": CLANG_7_0 + CLANG_PLATFORM_X86_64_LINUX,
+ "/usr/bin/clang++": CLANGXX_7_0 + CLANG_PLATFORM_X86_64_LINUX,
+ }
+ )
+ DEFAULT_CLANG_RESULT = CompilerResult(
+ flags=["-std=gnu99"],
+ version="7.0.0",
+ type="clang",
+ compiler="/usr/bin/clang",
+ language="C",
+ )
+ DEFAULT_CLANGXX_RESULT = CompilerResult(
+ flags=["-std=gnu++17"],
+ version="7.0.0",
+ type="clang",
+ compiler="/usr/bin/clang++",
+ language="C++",
+ )
+
+ def test_osx_cross(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_CLANG_RESULT
+ + OSXToolchainTest.SYSROOT_FLAGS
+ + {"flags": ["--target=i686-apple-darwin11.2.0"]},
+ "cxx_compiler": self.DEFAULT_CLANGXX_RESULT
+ + {"flags": PrependFlags(["-stdlib=libc++"])}
+ + OSXToolchainTest.SYSROOT_FLAGS
+ + {"flags": ["--target=i686-apple-darwin11.2.0"]},
+ "host_c_compiler": self.DEFAULT_CLANG_RESULT,
+ "host_cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ },
+ environ={"CC": "clang"},
+ args=["--with-macos-sdk=%s" % OSXToolchainTest.SYSROOT_FLAGS["flags"][1]],
+ )
+
+ def test_cannot_osx_cross(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": "Target C compiler target kernel (Linux) does not "
+ "match --target kernel (Darwin)"
+ },
+ environ={"CC": "gcc"},
+ args=["--with-macos-sdk=%s" % OSXToolchainTest.SYSROOT_FLAGS["flags"][1]],
+ )
+
+
+class WindowsCrossToolchainTest(BaseToolchainTest):
+ TARGET = "x86_64-pc-windows-msvc"
+ DEFAULT_CLANG_RESULT = LinuxToolchainTest.DEFAULT_CLANG_RESULT
+ DEFAULT_CLANGXX_RESULT = LinuxToolchainTest.DEFAULT_CLANGXX_RESULT
+
+ def test_clang_cl_cross(self):
+ paths = {"/usr/bin/clang-cl": CLANG_CL_9_0 + CLANG_CL_PLATFORM_X86_64}
+ paths.update(LinuxToolchainTest.PATHS)
+ self.do_toolchain_test(
+ paths,
+ {
+ "c_compiler": MingwToolchainTest.CLANG_CL_9_0_RESULT,
+ "cxx_compiler": MingwToolchainTest.CLANGXX_CL_9_0_RESULT,
+ "host_c_compiler": self.DEFAULT_CLANG_RESULT,
+ "host_cxx_compiler": self.DEFAULT_CLANGXX_RESULT,
+ },
+ )
+
+
+class OpenBSDToolchainTest(BaseToolchainTest):
+ HOST = "x86_64-unknown-openbsd6.1"
+ TARGET = "x86_64-unknown-openbsd6.1"
+ PATHS = {
+ "/usr/bin/gcc": DEFAULT_GCC + GCC_PLATFORM_X86_64 + GCC_PLATFORM_OPENBSD,
+ "/usr/bin/g++": DEFAULT_GXX + GCC_PLATFORM_X86_64 + GCC_PLATFORM_OPENBSD,
+ }
+ DEFAULT_GCC_RESULT = LinuxToolchainTest.DEFAULT_GCC_RESULT
+ DEFAULT_GXX_RESULT = LinuxToolchainTest.DEFAULT_GXX_RESULT
+
+ def test_gcc(self):
+ self.do_toolchain_test(
+ self.PATHS,
+ {
+ "c_compiler": self.DEFAULT_GCC_RESULT,
+ "cxx_compiler": self.DEFAULT_GXX_RESULT,
+ },
+ )
+
+
+@memoize
+def gen_invoke_cargo(version, rustup_wrapper=False):
+ def invoke_cargo(stdin, args):
+ args = tuple(args)
+ if not rustup_wrapper and args == ("+stable",):
+ return (101, "", "we are the real thing")
+ if args == ("--version", "--verbose"):
+ return 0, "cargo %s\nrelease: %s" % (version, version), ""
+ raise NotImplementedError("unsupported arguments")
+
+ return invoke_cargo
+
+
+@memoize
+def gen_invoke_rustc(version, rustup_wrapper=False):
+ def invoke_rustc(stdin, args):
+ args = tuple(args)
+ # TODO: we don't have enough machinery set up to test the `rustup which`
+ # fallback yet.
+ if not rustup_wrapper and args == ("+stable",):
+ return (1, "", "error: couldn't read +stable: No such file or directory")
+ if args == ("--version", "--verbose"):
+ return (
+ 0,
+ "rustc %s\nrelease: %s\nhost: x86_64-unknown-linux-gnu"
+ % (version, version),
+ "",
+ )
+ if args == ("--print", "target-list"):
+ # Raw list returned by rustc version 1.32, + ios, which somehow
+ # don't appear in the default list.
+ # https://github.com/rust-lang/rust/issues/36156
+ rust_targets = [
+ "aarch64-apple-ios",
+ "aarch64-fuchsia",
+ "aarch64-linux-android",
+ "aarch64-pc-windows-msvc",
+ "aarch64-unknown-cloudabi",
+ "aarch64-unknown-freebsd",
+ "aarch64-unknown-hermit",
+ "aarch64-unknown-linux-gnu",
+ "aarch64-unknown-linux-musl",
+ "aarch64-unknown-netbsd",
+ "aarch64-unknown-none",
+ "aarch64-unknown-openbsd",
+ "arm-linux-androideabi",
+ "arm-unknown-linux-gnueabi",
+ "arm-unknown-linux-gnueabihf",
+ "arm-unknown-linux-musleabi",
+ "arm-unknown-linux-musleabihf",
+ "armebv7r-none-eabi",
+ "armebv7r-none-eabihf",
+ "armv4t-unknown-linux-gnueabi",
+ "armv5te-unknown-linux-gnueabi",
+ "armv5te-unknown-linux-musleabi",
+ "armv6-unknown-netbsd-eabihf",
+ "armv7-linux-androideabi",
+ "armv7-unknown-cloudabi-eabihf",
+ "armv7-unknown-linux-gnueabihf",
+ "armv7-unknown-linux-musleabihf",
+ "armv7-unknown-netbsd-eabihf",
+ "armv7r-none-eabi",
+ "armv7r-none-eabihf",
+ "armv7s-apple-ios",
+ "asmjs-unknown-emscripten",
+ "i386-apple-ios",
+ "i586-pc-windows-msvc",
+ "i586-unknown-linux-gnu",
+ "i586-unknown-linux-musl",
+ "i686-apple-darwin",
+ "i686-linux-android",
+ "i686-pc-windows-gnu",
+ "i686-pc-windows-msvc",
+ "i686-unknown-cloudabi",
+ "i686-unknown-dragonfly",
+ "i686-unknown-freebsd",
+ "i686-unknown-haiku",
+ "i686-unknown-linux-gnu",
+ "i686-unknown-linux-musl",
+ "i686-unknown-netbsd",
+ "i686-unknown-openbsd",
+ "mips-unknown-linux-gnu",
+ "mips-unknown-linux-musl",
+ "mips-unknown-linux-uclibc",
+ "mips64-unknown-linux-gnuabi64",
+ "mips64el-unknown-linux-gnuabi64",
+ "mipsel-unknown-linux-gnu",
+ "mipsel-unknown-linux-musl",
+ "mipsel-unknown-linux-uclibc",
+ "msp430-none-elf",
+ "powerpc-unknown-linux-gnu",
+ "powerpc-unknown-linux-gnuspe",
+ "powerpc-unknown-linux-musl",
+ "powerpc-unknown-netbsd",
+ "powerpc64-unknown-linux-gnu",
+ "powerpc64-unknown-linux-musl",
+ "powerpc64le-unknown-linux-gnu",
+ "powerpc64le-unknown-linux-musl",
+ "riscv32imac-unknown-none-elf",
+ "riscv32imc-unknown-none-elf",
+ "s390x-unknown-linux-gnu",
+ "sparc-unknown-linux-gnu",
+ "sparc64-unknown-linux-gnu",
+ "sparc64-unknown-netbsd",
+ "sparcv9-sun-solaris",
+ "thumbv6m-none-eabi",
+ "thumbv7a-pc-windows-msvc",
+ "thumbv7em-none-eabi",
+ "thumbv7em-none-eabihf",
+ "thumbv7m-none-eabi",
+ "thumbv8m.base-none-eabi",
+ "wasm32-experimental-emscripten",
+ "wasm32-unknown-emscripten",
+ "wasm32-unknown-unknown",
+ "x86_64-apple-darwin",
+ "x86_64-apple-ios",
+ "x86_64-fortanix-unknown-sgx",
+ "x86_64-fuchsia",
+ "x86_64-linux-android",
+ "x86_64-pc-windows-gnu",
+ "x86_64-pc-windows-msvc",
+ "x86_64-rumprun-netbsd",
+ "x86_64-sun-solaris",
+ "x86_64-unknown-bitrig",
+ "x86_64-unknown-cloudabi",
+ "x86_64-unknown-dragonfly",
+ "x86_64-unknown-freebsd",
+ "x86_64-unknown-haiku",
+ "x86_64-unknown-hermit",
+ "x86_64-unknown-l4re-uclibc",
+ "x86_64-unknown-linux-gnu",
+ "x86_64-unknown-linux-gnux32",
+ "x86_64-unknown-linux-musl",
+ "x86_64-unknown-netbsd",
+ "x86_64-unknown-openbsd",
+ "x86_64-unknown-redox",
+ ]
+ # Additional targets from 1.33
+ if Version(version) >= "1.33.0":
+ rust_targets += [
+ "thumbv7neon-linux-androideabi",
+ "thumbv7neon-unknown-linux-gnueabihf",
+ "x86_64-unknown-uefi",
+ "thumbv8m.main-none-eabi",
+ "thumbv8m.main-none-eabihf",
+ ]
+ # Additional targets from 1.34
+ if Version(version) >= "1.34.0":
+ rust_targets += [
+ "nvptx64-nvidia-cuda",
+ "powerpc64-unknown-freebsd",
+ "riscv64gc-unknown-none-elf",
+ "riscv64imac-unknown-none-elf",
+ ]
+ # Additional targets from 1.35
+ if Version(version) >= "1.35.0":
+ rust_targets += [
+ "armv6-unknown-freebsd",
+ "armv7-unknown-freebsd",
+ "mipsisa32r6-unknown-linux-gnu",
+ "mipsisa32r6el-unknown-linux-gnu",
+ "mipsisa64r6-unknown-linux-gnuabi64",
+ "mipsisa64r6el-unknown-linux-gnuabi64",
+ "wasm32-unknown-wasi",
+ ]
+ # Additional targets from 1.36
+ if Version(version) >= "1.36.0":
+ rust_targets += ["wasm32-wasi"]
+ rust_targets.remove("wasm32-unknown-wasi")
+ rust_targets.remove("x86_64-unknown-bitrig")
+ # Additional targets from 1.37
+ if Version(version) >= "1.37.0":
+ rust_targets += ["x86_64-pc-solaris"]
+ # Additional targets from 1.38
+ if Version(version) >= "1.38.0":
+ rust_targets += [
+ "aarch64-unknown-redox",
+ "aarch64-wrs-vxworks",
+ "armv7-unknown-linux-gnueabi",
+ "armv7-unknown-linux-musleabi",
+ "armv7-wrs-vxworks",
+ "hexagon-unknown-linux-musl",
+ "i586-wrs-vxworks",
+ "i686-uwp-windows-gnu",
+ "i686-wrs-vxworks",
+ "powerpc-wrs-vxworks",
+ "powerpc-wrs-vxworks-spe",
+ "powerpc64-wrs-vxworks",
+ "riscv32i-unknown-none-elf",
+ "x86_64-uwp-windows-gnu",
+ "x86_64-wrs-vxworks",
+ ]
+ # Additional targets from 1.38
+ if Version(version) >= "1.39.0":
+ rust_targets += [
+ "aarch64-uwp-windows-msvc",
+ "armv7-wrs-vxworks-eabihf",
+ "i686-unknown-uefi",
+ "i686-uwp-windows-msvc",
+ "mips64-unknown-linux-muslabi64",
+ "mips64el-unknown-linux-muslabi64",
+ "sparc64-unknown-openbsd",
+ "x86_64-linux-kernel",
+ "x86_64-uwp-windows-msvc",
+ ]
+ rust_targets.remove("armv7-wrs-vxworks")
+ rust_targets.remove("i586-wrs-vxworks")
+
+ return 0, "\n".join(sorted(rust_targets)), ""
+ if (
+ len(args) == 6
+ and args[:2] == ("--crate-type", "staticlib")
+ and args[2].startswith("--target=")
+ and args[3] == "-o"
+ ):
+ with open(args[4], "w") as fh:
+ fh.write("foo")
+ return 0, "", ""
+ raise NotImplementedError("unsupported arguments")
+
+ return invoke_rustc
+
+
+class RustTest(BaseConfigureTest):
+ def get_rust_target(
+ self, target, compiler_type="gcc", version=MINIMUM_RUST_VERSION, arm_target=None
+ ):
+ environ = {
+ "PATH": os.pathsep.join(mozpath.abspath(p) for p in ("/bin", "/usr/bin"))
+ }
+
+ paths = {
+ mozpath.abspath("/usr/bin/cargo"): gen_invoke_cargo(version),
+ mozpath.abspath("/usr/bin/rustc"): gen_invoke_rustc(version),
+ }
+
+ self.TARGET = target
+ sandbox = self.get_sandbox(paths, {}, [], environ)
+
+ # Trick the sandbox into not running the target compiler check
+ dep = sandbox._depends[sandbox["c_compiler"]]
+ getattr(sandbox, "__value_for_depends")[(dep,)] = CompilerResult(
+ type=compiler_type
+ )
+ # Same for the arm_target checks.
+ dep = sandbox._depends[sandbox["arm_target"]]
+ getattr(sandbox, "__value_for_depends")[
+ (dep,)
+ ] = arm_target or ReadOnlyNamespace(
+ arm_arch=7, thumb2=False, fpu="vfpv2", float_abi="softfp"
+ )
+ return sandbox._value_for(sandbox["rust_target_triple"])
+
+ def test_rust_target(self):
+ # Cases where the output of config.sub matches a rust target
+ for straightforward in (
+ "x86_64-unknown-dragonfly",
+ "aarch64-unknown-freebsd",
+ "i686-unknown-freebsd",
+ "x86_64-unknown-freebsd",
+ "sparc64-unknown-netbsd",
+ "i686-unknown-netbsd",
+ "x86_64-unknown-netbsd",
+ "i686-unknown-openbsd",
+ "x86_64-unknown-openbsd",
+ "aarch64-unknown-linux-gnu",
+ "sparc64-unknown-linux-gnu",
+ "i686-unknown-linux-gnu",
+ "i686-apple-darwin",
+ "x86_64-apple-darwin",
+ "mips-unknown-linux-gnu",
+ "mipsel-unknown-linux-gnu",
+ "mips64-unknown-linux-gnuabi64",
+ "mips64el-unknown-linux-gnuabi64",
+ "powerpc64-unknown-linux-gnu",
+ "powerpc64le-unknown-linux-gnu",
+ "i686-pc-windows-msvc",
+ "x86_64-pc-windows-msvc",
+ "aarch64-pc-windows-msvc",
+ "i686-pc-windows-gnu",
+ "x86_64-pc-windows-gnu",
+ ):
+ self.assertEqual(self.get_rust_target(straightforward), straightforward)
+
+ # Cases where the output of config.sub is different
+ for autoconf, rust in (
+ ("aarch64-unknown-linux-android", "aarch64-linux-android"),
+ ("arm-unknown-linux-androideabi", "armv7-linux-androideabi"),
+ ("armv7-unknown-linux-androideabi", "armv7-linux-androideabi"),
+ ("i386-unknown-linux-android", "i686-linux-android"),
+ ("i686-unknown-linux-android", "i686-linux-android"),
+ ("i686-pc-linux-gnu", "i686-unknown-linux-gnu"),
+ ("x86_64-unknown-linux-android", "x86_64-linux-android"),
+ ("x86_64-pc-linux-gnu", "x86_64-unknown-linux-gnu"),
+ ("sparcv9-sun-solaris2", "sparcv9-sun-solaris"),
+ ("x86_64-sun-solaris2", "x86_64-sun-solaris"),
+ ):
+ self.assertEqual(self.get_rust_target(autoconf), rust)
+
+ # Windows
+ for autoconf, building_with_gcc, rust in (
+ ("i686-pc-mingw32", "clang-cl", "i686-pc-windows-msvc"),
+ ("x86_64-pc-mingw32", "clang-cl", "x86_64-pc-windows-msvc"),
+ ("i686-pc-mingw32", "clang", "i686-pc-windows-gnu"),
+ ("x86_64-pc-mingw32", "clang", "x86_64-pc-windows-gnu"),
+ ("i686-w64-mingw32", "clang", "i686-pc-windows-gnu"),
+ ("x86_64-w64-mingw32", "clang", "x86_64-pc-windows-gnu"),
+ ("aarch64-windows-mingw32", "clang-cl", "aarch64-pc-windows-msvc"),
+ ):
+ self.assertEqual(self.get_rust_target(autoconf, building_with_gcc), rust)
+
+ # Arm special cases
+ self.assertEqual(
+ self.get_rust_target(
+ "arm-unknown-linux-androideabi",
+ arm_target=ReadOnlyNamespace(
+ arm_arch=7, fpu="neon", thumb2=True, float_abi="softfp"
+ ),
+ ),
+ "thumbv7neon-linux-androideabi",
+ )
+
+ self.assertEqual(
+ self.get_rust_target(
+ "arm-unknown-linux-androideabi",
+ arm_target=ReadOnlyNamespace(
+ arm_arch=7, fpu="neon", thumb2=False, float_abi="softfp"
+ ),
+ ),
+ "armv7-linux-androideabi",
+ )
+
+ self.assertEqual(
+ self.get_rust_target(
+ "arm-unknown-linux-androideabi",
+ arm_target=ReadOnlyNamespace(
+ arm_arch=7, fpu="vfpv2", thumb2=True, float_abi="softfp"
+ ),
+ ),
+ "armv7-linux-androideabi",
+ )
+
+ self.assertEqual(
+ self.get_rust_target(
+ "armv7-unknown-linux-gnueabihf",
+ arm_target=ReadOnlyNamespace(
+ arm_arch=7, fpu="neon", thumb2=True, float_abi="hard"
+ ),
+ ),
+ "thumbv7neon-unknown-linux-gnueabihf",
+ )
+
+ self.assertEqual(
+ self.get_rust_target(
+ "armv7-unknown-linux-gnueabihf",
+ arm_target=ReadOnlyNamespace(
+ arm_arch=7, fpu="neon", thumb2=False, float_abi="hard"
+ ),
+ ),
+ "armv7-unknown-linux-gnueabihf",
+ )
+
+ self.assertEqual(
+ self.get_rust_target(
+ "armv7-unknown-linux-gnueabihf",
+ arm_target=ReadOnlyNamespace(
+ arm_arch=7, fpu="vfpv2", thumb2=True, float_abi="hard"
+ ),
+ ),
+ "armv7-unknown-linux-gnueabihf",
+ )
+
+ self.assertEqual(
+ self.get_rust_target(
+ "arm-unknown-freebsd13.0-gnueabihf",
+ arm_target=ReadOnlyNamespace(
+ arm_arch=7, fpu="vfpv2", thumb2=True, float_abi="hard"
+ ),
+ ),
+ "armv7-unknown-freebsd",
+ )
+
+ self.assertEqual(
+ self.get_rust_target(
+ "arm-unknown-freebsd13.0-gnueabihf",
+ arm_target=ReadOnlyNamespace(
+ arm_arch=6, fpu=None, thumb2=False, float_abi="hard"
+ ),
+ ),
+ "armv6-unknown-freebsd",
+ )
+
+ self.assertEqual(
+ self.get_rust_target(
+ "arm-unknown-linux-gnueabi",
+ arm_target=ReadOnlyNamespace(
+ arm_arch=4, fpu=None, thumb2=False, float_abi="softfp"
+ ),
+ ),
+ "armv4t-unknown-linux-gnueabi",
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/configure/test_toolchain_helpers.py b/python/mozbuild/mozbuild/test/configure/test_toolchain_helpers.py
new file mode 100644
index 0000000000..f42778215b
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_toolchain_helpers.py
@@ -0,0 +1,433 @@
+# 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/.
+
+import copy
+import re
+import unittest
+from fnmatch import fnmatch
+from textwrap import dedent
+
+import six
+from mozpack import path as mozpath
+from mozunit import MockedOpen, main
+from six import StringIO
+
+from mozbuild.preprocessor import Preprocessor
+from mozbuild.util import ReadOnlyNamespace
+
+
+class CompilerPreprocessor(Preprocessor):
+ # The C preprocessor only expands macros when they are not in C strings.
+ # For now, we don't look very hard for C strings because they don't matter
+ # that much for our unit tests, but we at least avoid expanding in the
+ # simple "FOO" case.
+ VARSUBST = re.compile('(?<!")(?P<VAR>\w+)(?!")', re.U)
+ NON_WHITESPACE = re.compile("\S")
+ HAS_FEATURE_OR_BUILTIN = re.compile(
+ '(__has_(?:feature|builtin|attribute|warning))\("?([^"\)]*)"?\)'
+ )
+
+ def __init__(self, *args, **kwargs):
+ Preprocessor.__init__(self, *args, **kwargs)
+ self.do_filter("c_substitution")
+ self.setMarker("#\s*")
+
+ def do_if(self, expression, **kwargs):
+ # The C preprocessor handles numbers following C rules, which is a
+ # different handling than what our Preprocessor does out of the box.
+ # Hack around it enough that the configure tests work properly.
+ context = self.context
+
+ def normalize_numbers(value):
+ if isinstance(value, six.string_types):
+ if value[-1:] == "L" and value[:-1].isdigit():
+ value = int(value[:-1])
+ return value
+
+ # Our Preprocessor doesn't handle macros with parameters, so we hack
+ # around that for __has_feature()-like things.
+
+ def normalize_has_feature_or_builtin(expr):
+ return (
+ self.HAS_FEATURE_OR_BUILTIN.sub(r"\1\2", expr)
+ .replace("-", "_")
+ .replace("+", "_")
+ )
+
+ self.context = self.Context(
+ (normalize_has_feature_or_builtin(k), normalize_numbers(v))
+ for k, v in six.iteritems(context)
+ )
+ try:
+ return Preprocessor.do_if(
+ self, normalize_has_feature_or_builtin(expression), **kwargs
+ )
+ finally:
+ self.context = context
+
+ class Context(dict):
+ def __missing__(self, key):
+ return None
+
+ def filter_c_substitution(self, line):
+ def repl(matchobj):
+ varname = matchobj.group("VAR")
+ if varname in self.context:
+ result = six.text_type(self.context[varname])
+ # The C preprocessor inserts whitespaces around expanded
+ # symbols.
+ start, end = matchobj.span("VAR")
+ if self.NON_WHITESPACE.match(line[start - 1 : start]):
+ result = " " + result
+ if self.NON_WHITESPACE.match(line[end : end + 1]):
+ result = result + " "
+ return result
+ return matchobj.group(0)
+
+ return self.VARSUBST.sub(repl, line)
+
+
+class TestCompilerPreprocessor(unittest.TestCase):
+ def test_expansion(self):
+ pp = CompilerPreprocessor({"A": 1, "B": "2", "C": "c", "D": "d"})
+ pp.out = StringIO()
+ input = StringIO('A.B.C "D"')
+ input.name = "foo"
+ pp.do_include(input)
+
+ self.assertEqual(pp.out.getvalue(), '1 . 2 . c "D"')
+
+ def test_normalization(self):
+ pp = CompilerPreprocessor(
+ {"__has_attribute(bar)": 1, '__has_warning("-Wc++98-foo")': 1}
+ )
+ pp.out = StringIO()
+ input = StringIO(
+ dedent(
+ """\
+ #if __has_warning("-Wbar")
+ WBAR
+ #endif
+ #if __has_warning("-Wc++98-foo")
+ WFOO
+ #endif
+ #if !__has_warning("-Wc++98-foo")
+ NO_WFOO
+ #endif
+ #if __has_attribute(bar)
+ BAR
+ #else
+ NO_BAR
+ #endif
+ #if !__has_attribute(foo)
+ NO_FOO
+ #endif
+ """
+ )
+ )
+
+ input.name = "foo"
+ pp.do_include(input)
+
+ self.assertEqual(pp.out.getvalue(), "WFOO\nBAR\nNO_FOO\n")
+
+ def test_condition(self):
+ pp = CompilerPreprocessor({"A": 1, "B": "2", "C": "0L"})
+ pp.out = StringIO()
+ input = StringIO(
+ dedent(
+ """\
+ #ifdef A
+ IFDEF_A
+ #endif
+ #if A
+ IF_A
+ #endif
+ # if B
+ IF_B
+ # else
+ IF_NOT_B
+ # endif
+ #if !C
+ IF_NOT_C
+ #else
+ IF_C
+ #endif
+ """
+ )
+ )
+ input.name = "foo"
+ pp.do_include(input)
+
+ self.assertEqual("IFDEF_A\nIF_A\nIF_NOT_B\nIF_NOT_C\n", pp.out.getvalue())
+
+
+class FakeCompiler(dict):
+ """Defines a fake compiler for use in toolchain tests below.
+
+ The definitions given when creating an instance can have one of two
+ forms:
+ - a dict giving preprocessor symbols and their respective value, e.g.
+ { '__GNUC__': 4, '__STDC__': 1 }
+ - a dict associating flags to preprocessor symbols. An entry for `None`
+ is required in this case. Those are the baseline preprocessor symbols.
+ Additional entries describe additional flags to set or existing flags
+ to unset (with a value of `False`).
+ {
+ None: { '__GNUC__': 4, '__STDC__': 1, '__STRICT_ANSI__': 1 },
+ '-std=gnu99': { '__STDC_VERSION__': '199901L',
+ '__STRICT_ANSI__': False },
+ }
+ With the dict above, invoking the preprocessor with no additional flags
+ would define __GNUC__, __STDC__ and __STRICT_ANSI__, and with -std=gnu99,
+ __GNUC__, __STDC__, and __STDC_VERSION__ (__STRICT_ANSI__ would be
+ unset).
+ It is also possible to have different symbols depending on the source
+ file extension. In this case, the key is '*.ext'. e.g.
+ {
+ '*.c': { '__STDC__': 1 },
+ '*.cpp': { '__cplusplus': '199711L' },
+ }
+
+ All the given definitions are merged together.
+
+ A FakeCompiler instance itself can be used as a definition to create
+ another FakeCompiler.
+
+ For convenience, FakeCompiler instances can be added (+) to one another.
+ """
+
+ def __init__(self, *definitions):
+ for definition in definitions:
+ if all(not isinstance(d, dict) for d in six.itervalues(definition)):
+ definition = {None: definition}
+ for key, value in six.iteritems(definition):
+ self.setdefault(key, {}).update(value)
+
+ def __call__(self, stdin, args):
+ files = []
+ flags = []
+ args = iter(args)
+ while True:
+ arg = next(args, None)
+ if arg is None:
+ break
+ if arg.startswith("-"):
+ # Ignore -isysroot/--sysroot and the argument that follows it.
+ if arg in ("-isysroot", "--sysroot"):
+ next(args, None)
+ else:
+ flags.append(arg)
+ else:
+ files.append(arg)
+
+ if "-E" in flags:
+ assert len(files) == 1
+ file = files[0]
+ pp = CompilerPreprocessor(self[None])
+
+ def apply_defn(defn):
+ for k, v in six.iteritems(defn):
+ if v is False:
+ if k in pp.context:
+ del pp.context[k]
+ else:
+ pp.context[k] = v
+
+ for glob, defn in six.iteritems(self):
+ if glob and not glob.startswith("-") and fnmatch(file, glob):
+ apply_defn(defn)
+
+ for flag in flags:
+ apply_defn(self.get(flag, {}))
+
+ pp.out = StringIO()
+ pp.do_include(file)
+ return 0, pp.out.getvalue(), ""
+ elif "-c" in flags:
+ if "-funknown-flag" in flags:
+ return 1, "", ""
+ return 0, "", ""
+
+ return 1, "", ""
+
+ def __add__(self, other):
+ return FakeCompiler(self, other)
+
+
+class TestFakeCompiler(unittest.TestCase):
+ def test_fake_compiler(self):
+ with MockedOpen({"file": "A B C", "file.c": "A B C"}):
+ compiler = FakeCompiler({"A": "1", "B": "2"})
+ self.assertEqual(compiler(None, ["-E", "file"]), (0, "1 2 C", ""))
+
+ compiler = FakeCompiler(
+ {
+ None: {"A": "1", "B": "2"},
+ "-foo": {"C": "foo"},
+ "-bar": {"B": "bar", "C": "bar"},
+ "-qux": {"B": False},
+ "*.c": {"B": "42"},
+ }
+ )
+ self.assertEqual(compiler(None, ["-E", "file"]), (0, "1 2 C", ""))
+ self.assertEqual(compiler(None, ["-E", "-foo", "file"]), (0, "1 2 foo", ""))
+ self.assertEqual(
+ compiler(None, ["-E", "-bar", "file"]), (0, "1 bar bar", "")
+ )
+ self.assertEqual(compiler(None, ["-E", "-qux", "file"]), (0, "1 B C", ""))
+ self.assertEqual(
+ compiler(None, ["-E", "-foo", "-bar", "file"]), (0, "1 bar bar", "")
+ )
+ self.assertEqual(
+ compiler(None, ["-E", "-bar", "-foo", "file"]), (0, "1 bar foo", "")
+ )
+ self.assertEqual(
+ compiler(None, ["-E", "-bar", "-qux", "file"]), (0, "1 B bar", "")
+ )
+ self.assertEqual(
+ compiler(None, ["-E", "-qux", "-bar", "file"]), (0, "1 bar bar", "")
+ )
+ self.assertEqual(compiler(None, ["-E", "file.c"]), (0, "1 42 C", ""))
+ self.assertEqual(
+ compiler(None, ["-E", "-bar", "file.c"]), (0, "1 bar bar", "")
+ )
+
+ def test_multiple_definitions(self):
+ compiler = FakeCompiler({"A": 1, "B": 2}, {"C": 3})
+
+ self.assertEqual(compiler, {None: {"A": 1, "B": 2, "C": 3}})
+ compiler = FakeCompiler({"A": 1, "B": 2}, {"B": 4, "C": 3})
+
+ self.assertEqual(compiler, {None: {"A": 1, "B": 4, "C": 3}})
+ compiler = FakeCompiler(
+ {"A": 1, "B": 2}, {None: {"B": 4, "C": 3}, "-foo": {"D": 5}}
+ )
+
+ self.assertEqual(compiler, {None: {"A": 1, "B": 4, "C": 3}, "-foo": {"D": 5}})
+
+ compiler = FakeCompiler(
+ {None: {"A": 1, "B": 2}, "-foo": {"D": 5}},
+ {"-foo": {"D": 5}, "-bar": {"E": 6}},
+ )
+
+ self.assertEqual(
+ compiler, {None: {"A": 1, "B": 2}, "-foo": {"D": 5}, "-bar": {"E": 6}}
+ )
+
+
+class PrependFlags(list):
+ """Wrapper to allow to Prepend to flags instead of appending, in
+ CompilerResult.
+ """
+
+
+class CompilerResult(ReadOnlyNamespace):
+ """Helper of convenience to manipulate toolchain results in unit tests
+
+ When adding a dict, the result is a new CompilerResult with the values
+ from the dict replacing those from the CompilerResult, except for `flags`,
+ where the value from the dict extends the `flags` in `self`.
+ """
+
+ def __init__(
+ self, wrapper=None, compiler="", version="", type="", language="", flags=None
+ ):
+ if flags is None:
+ flags = []
+ if wrapper is None:
+ wrapper = []
+ super(CompilerResult, self).__init__(
+ flags=flags,
+ version=version,
+ type=type,
+ compiler=mozpath.abspath(compiler),
+ wrapper=wrapper,
+ language=language,
+ )
+
+ def __add__(self, other):
+ assert isinstance(other, dict)
+ result = copy.deepcopy(self.__dict__)
+ for k, v in six.iteritems(other):
+ if k == "flags":
+ flags = result.setdefault(k, [])
+ if isinstance(v, PrependFlags):
+ flags[:0] = v
+ else:
+ flags.extend(v)
+ else:
+ result[k] = v
+ return CompilerResult(**result)
+
+
+class TestCompilerResult(unittest.TestCase):
+ def test_compiler_result(self):
+ result = CompilerResult()
+ self.assertEqual(
+ result.__dict__,
+ {
+ "wrapper": [],
+ "compiler": mozpath.abspath(""),
+ "version": "",
+ "type": "",
+ "language": "",
+ "flags": [],
+ },
+ )
+
+ result = CompilerResult(
+ compiler="/usr/bin/gcc",
+ version="4.2.1",
+ type="gcc",
+ language="C",
+ flags=["-std=gnu99"],
+ )
+ self.assertEqual(
+ result.__dict__,
+ {
+ "wrapper": [],
+ "compiler": mozpath.abspath("/usr/bin/gcc"),
+ "version": "4.2.1",
+ "type": "gcc",
+ "language": "C",
+ "flags": ["-std=gnu99"],
+ },
+ )
+
+ result2 = result + {"flags": ["-m32"]}
+ self.assertEqual(
+ result2.__dict__,
+ {
+ "wrapper": [],
+ "compiler": mozpath.abspath("/usr/bin/gcc"),
+ "version": "4.2.1",
+ "type": "gcc",
+ "language": "C",
+ "flags": ["-std=gnu99", "-m32"],
+ },
+ )
+ # Original flags are untouched.
+ self.assertEqual(result.flags, ["-std=gnu99"])
+
+ result3 = result + {
+ "compiler": "/usr/bin/gcc-4.7",
+ "version": "4.7.3",
+ "flags": ["-m32"],
+ }
+ self.assertEqual(
+ result3.__dict__,
+ {
+ "wrapper": [],
+ "compiler": mozpath.abspath("/usr/bin/gcc-4.7"),
+ "version": "4.7.3",
+ "type": "gcc",
+ "language": "C",
+ "flags": ["-std=gnu99", "-m32"],
+ },
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/configure/test_toolkit_moz_configure.py b/python/mozbuild/mozbuild/test/configure/test_toolkit_moz_configure.py
new file mode 100644
index 0000000000..e6b96b3627
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_toolkit_moz_configure.py
@@ -0,0 +1,102 @@
+# 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/.
+
+import os
+
+from buildconfig import topsrcdir
+from mozpack import path as mozpath
+from mozunit import MockedOpen, main
+
+from common import BaseConfigureTest
+from mozbuild.configure.options import InvalidOptionError
+
+
+class TestToolkitMozConfigure(BaseConfigureTest):
+ def test_moz_configure_options(self):
+ def get_value_for(args=[], environ={}, mozconfig=""):
+ sandbox = self.get_sandbox({}, {}, args, environ, mozconfig)
+
+ # Add a fake old-configure option
+ sandbox.option_impl(
+ "--with-foo", nargs="*", help="Help missing for old configure options"
+ )
+
+ # Remove all implied options, otherwise, getting
+ # all_configure_options below triggers them, and that triggers
+ # configure parts that aren't expected to run during this test.
+ del sandbox._implied_options[:]
+ result = sandbox._value_for(sandbox["all_configure_options"])
+ shell = mozpath.abspath("/bin/sh")
+ return result.replace("CONFIG_SHELL=%s " % shell, "")
+
+ self.assertEqual(
+ "--enable-application=browser",
+ get_value_for(["--enable-application=browser"]),
+ )
+
+ self.assertEqual(
+ "--enable-application=browser " "MOZ_VTUNE=1",
+ get_value_for(["--enable-application=browser", "MOZ_VTUNE=1"]),
+ )
+
+ value = get_value_for(
+ environ={"MOZ_VTUNE": "1"},
+ mozconfig="ac_add_options --enable-application=browser",
+ )
+
+ self.assertEqual("--enable-application=browser MOZ_VTUNE=1", value)
+
+ # --disable-js-shell is the default, so it's filtered out.
+ self.assertEqual(
+ "--enable-application=browser",
+ get_value_for(["--enable-application=browser", "--disable-js-shell"]),
+ )
+
+ # Normally, --without-foo would be filtered out because that's the
+ # default, but since it is a (fake) old-configure option, it always
+ # appears.
+ self.assertEqual(
+ "--enable-application=browser --without-foo",
+ get_value_for(["--enable-application=browser", "--without-foo"]),
+ )
+ self.assertEqual(
+ "--enable-application=browser --with-foo",
+ get_value_for(["--enable-application=browser", "--with-foo"]),
+ )
+
+ self.assertEqual(
+ "--enable-application=browser '--with-foo=foo bar'",
+ get_value_for(["--enable-application=browser", "--with-foo=foo bar"]),
+ )
+
+ def test_developer_options(self, milestone="42.0a1"):
+ def get_value(args=[], environ={}):
+ sandbox = self.get_sandbox({}, {}, args, environ)
+ return sandbox._value_for(sandbox["developer_options"])
+
+ milestone_path = os.path.join(topsrcdir, "config", "milestone.txt")
+ with MockedOpen({milestone_path: milestone}):
+ # developer options are enabled by default on "nightly" milestone
+ # only
+ self.assertEqual(get_value(), "a" in milestone or None)
+
+ self.assertEqual(get_value(["--enable-release"]), None)
+
+ self.assertEqual(get_value(environ={"MOZILLA_OFFICIAL": 1}), None)
+
+ self.assertEqual(
+ get_value(["--enable-release"], environ={"MOZILLA_OFFICIAL": 1}), None
+ )
+
+ with self.assertRaises(InvalidOptionError):
+ get_value(["--disable-release"], environ={"MOZILLA_OFFICIAL": 1})
+
+ self.assertEqual(get_value(environ={"MOZ_AUTOMATION": 1}), None)
+
+ def test_developer_options_release(self):
+ self.test_developer_options("42.0")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/mozbuild/mozbuild/test/configure/test_util.py b/python/mozbuild/mozbuild/test/configure/test_util.py
new file mode 100644
index 0000000000..81c2e2a8bf
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/test_util.py
@@ -0,0 +1,539 @@
+# 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/.
+
+import logging
+import os
+import sys
+import tempfile
+import textwrap
+import unittest
+
+import six
+from buildconfig import topsrcdir
+from mozpack import path as mozpath
+from mozunit import main
+from six import StringIO
+
+from common import ConfigureTestSandbox
+from mozbuild.configure import ConfigureSandbox
+from mozbuild.configure.util import (
+ ConfigureOutputHandler,
+ LineIO,
+ Version,
+ getpreferredencoding,
+)
+from mozbuild.util import exec_
+
+
+class TestConfigureOutputHandler(unittest.TestCase):
+ def test_separation(self):
+ out = StringIO()
+ err = StringIO()
+ name = "%s.test_separation" % self.__class__.__name__
+ logger = logging.getLogger(name)
+ logger.setLevel(logging.DEBUG)
+ logger.addHandler(ConfigureOutputHandler(out, err))
+
+ logger.error("foo")
+ logger.warning("bar")
+ logger.info("baz")
+ # DEBUG level is not printed out by this handler
+ logger.debug("qux")
+
+ self.assertEqual(out.getvalue(), "baz\n")
+ self.assertEqual(err.getvalue(), "foo\nbar\n")
+
+ def test_format(self):
+ out = StringIO()
+ err = StringIO()
+ name = "%s.test_format" % self.__class__.__name__
+ logger = logging.getLogger(name)
+ logger.setLevel(logging.DEBUG)
+ handler = ConfigureOutputHandler(out, err)
+ handler.setFormatter(logging.Formatter("%(levelname)s:%(message)s"))
+ logger.addHandler(handler)
+
+ logger.error("foo")
+ logger.warning("bar")
+ logger.info("baz")
+ # DEBUG level is not printed out by this handler
+ logger.debug("qux")
+
+ self.assertEqual(out.getvalue(), "baz\n")
+ self.assertEqual(err.getvalue(), "ERROR:foo\n" "WARNING:bar\n")
+
+ def test_continuation(self):
+ out = StringIO()
+ name = "%s.test_continuation" % self.__class__.__name__
+ logger = logging.getLogger(name)
+ logger.setLevel(logging.DEBUG)
+ handler = ConfigureOutputHandler(out, out)
+ handler.setFormatter(logging.Formatter("%(levelname)s:%(message)s"))
+ logger.addHandler(handler)
+
+ logger.info("foo")
+ logger.info("checking bar... ")
+ logger.info("yes")
+ logger.info("qux")
+
+ self.assertEqual(out.getvalue(), "foo\n" "checking bar... yes\n" "qux\n")
+
+ out.seek(0)
+ out.truncate()
+
+ logger.info("foo")
+ logger.info("checking bar... ")
+ logger.warning("hoge")
+ logger.info("no")
+ logger.info("qux")
+
+ self.assertEqual(
+ out.getvalue(),
+ "foo\n" "checking bar... \n" "WARNING:hoge\n" " ... no\n" "qux\n",
+ )
+
+ out.seek(0)
+ out.truncate()
+
+ logger.info("foo")
+ logger.info("checking bar... ")
+ logger.warning("hoge")
+ logger.warning("fuga")
+ logger.info("no")
+ logger.info("qux")
+
+ self.assertEqual(
+ out.getvalue(),
+ "foo\n"
+ "checking bar... \n"
+ "WARNING:hoge\n"
+ "WARNING:fuga\n"
+ " ... no\n"
+ "qux\n",
+ )
+
+ out.seek(0)
+ out.truncate()
+ err = StringIO()
+
+ logger.removeHandler(handler)
+ handler = ConfigureOutputHandler(out, err)
+ handler.setFormatter(logging.Formatter("%(levelname)s:%(message)s"))
+ logger.addHandler(handler)
+
+ logger.info("foo")
+ logger.info("checking bar... ")
+ logger.warning("hoge")
+ logger.warning("fuga")
+ logger.info("no")
+ logger.info("qux")
+
+ self.assertEqual(out.getvalue(), "foo\n" "checking bar... no\n" "qux\n")
+
+ self.assertEqual(err.getvalue(), "WARNING:hoge\n" "WARNING:fuga\n")
+
+ def test_queue_debug(self):
+ out = StringIO()
+ name = "%s.test_queue_debug" % self.__class__.__name__
+ logger = logging.getLogger(name)
+ logger.setLevel(logging.DEBUG)
+ handler = ConfigureOutputHandler(out, out, maxlen=3)
+ handler.setFormatter(logging.Formatter("%(levelname)s:%(message)s"))
+ logger.addHandler(handler)
+
+ with handler.queue_debug():
+ logger.info("checking bar... ")
+ logger.debug("do foo")
+ logger.info("yes")
+ logger.info("qux")
+
+ self.assertEqual(out.getvalue(), "checking bar... yes\n" "qux\n")
+
+ out.seek(0)
+ out.truncate()
+
+ with handler.queue_debug():
+ logger.info("checking bar... ")
+ logger.debug("do foo")
+ logger.info("no")
+ logger.error("fail")
+
+ self.assertEqual(
+ out.getvalue(), "checking bar... no\n" "DEBUG:do foo\n" "ERROR:fail\n"
+ )
+
+ out.seek(0)
+ out.truncate()
+
+ with handler.queue_debug():
+ logger.info("checking bar... ")
+ logger.debug("do foo")
+ logger.debug("do bar")
+ logger.debug("do baz")
+ logger.info("no")
+ logger.error("fail")
+
+ self.assertEqual(
+ out.getvalue(),
+ "checking bar... no\n"
+ "DEBUG:do foo\n"
+ "DEBUG:do bar\n"
+ "DEBUG:do baz\n"
+ "ERROR:fail\n",
+ )
+
+ out.seek(0)
+ out.truncate()
+
+ with handler.queue_debug():
+ logger.info("checking bar... ")
+ logger.debug("do foo")
+ logger.debug("do bar")
+ logger.debug("do baz")
+ logger.debug("do qux")
+ logger.debug("do hoge")
+ logger.info("no")
+ logger.error("fail")
+
+ self.assertEqual(
+ out.getvalue(),
+ "checking bar... no\n"
+ "DEBUG:<truncated - see config.log for full output>\n"
+ "DEBUG:do baz\n"
+ "DEBUG:do qux\n"
+ "DEBUG:do hoge\n"
+ "ERROR:fail\n",
+ )
+
+ out.seek(0)
+ out.truncate()
+
+ try:
+ with handler.queue_debug():
+ logger.info("checking bar... ")
+ logger.debug("do foo")
+ logger.debug("do bar")
+ logger.info("no")
+ e = Exception("fail")
+ raise e
+ except Exception as caught:
+ self.assertIs(caught, e)
+
+ self.assertEqual(
+ out.getvalue(), "checking bar... no\n" "DEBUG:do foo\n" "DEBUG:do bar\n"
+ )
+
+ def test_queue_debug_reentrant(self):
+ out = StringIO()
+ name = "%s.test_queue_debug_reentrant" % self.__class__.__name__
+ logger = logging.getLogger(name)
+ logger.setLevel(logging.DEBUG)
+ handler = ConfigureOutputHandler(out, out, maxlen=10)
+ handler.setFormatter(logging.Formatter("%(levelname)s| %(message)s"))
+ logger.addHandler(handler)
+
+ try:
+ with handler.queue_debug():
+ logger.info("outer info")
+ logger.debug("outer debug")
+ with handler.queue_debug():
+ logger.info("inner info")
+ logger.debug("inner debug")
+ e = Exception("inner exception")
+ raise e
+ except Exception as caught:
+ self.assertIs(caught, e)
+
+ self.assertEqual(
+ out.getvalue(),
+ "outer info\n" "inner info\n" "DEBUG| outer debug\n" "DEBUG| inner debug\n",
+ )
+
+ out.seek(0)
+ out.truncate()
+
+ try:
+ with handler.queue_debug():
+ logger.info("outer info")
+ logger.debug("outer debug")
+ with handler.queue_debug():
+ logger.info("inner info")
+ logger.debug("inner debug")
+ e = Exception("outer exception")
+ raise e
+ except Exception as caught:
+ self.assertIs(caught, e)
+
+ self.assertEqual(
+ out.getvalue(),
+ "outer info\n" "inner info\n" "DEBUG| outer debug\n" "DEBUG| inner debug\n",
+ )
+
+ out.seek(0)
+ out.truncate()
+
+ with handler.queue_debug():
+ logger.info("outer info")
+ logger.debug("outer debug")
+ with handler.queue_debug():
+ logger.info("inner info")
+ logger.debug("inner debug")
+ logger.error("inner error")
+ self.assertEqual(
+ out.getvalue(),
+ "outer info\n"
+ "inner info\n"
+ "DEBUG| outer debug\n"
+ "DEBUG| inner debug\n"
+ "ERROR| inner error\n",
+ )
+
+ out.seek(0)
+ out.truncate()
+
+ with handler.queue_debug():
+ logger.info("outer info")
+ logger.debug("outer debug")
+ with handler.queue_debug():
+ logger.info("inner info")
+ logger.debug("inner debug")
+ logger.error("outer error")
+ self.assertEqual(
+ out.getvalue(),
+ "outer info\n"
+ "inner info\n"
+ "DEBUG| outer debug\n"
+ "DEBUG| inner debug\n"
+ "ERROR| outer error\n",
+ )
+
+ def test_is_same_output(self):
+ fd1 = sys.stderr.fileno()
+ fd2 = os.dup(fd1)
+ try:
+ self.assertTrue(ConfigureOutputHandler._is_same_output(fd1, fd2))
+ finally:
+ os.close(fd2)
+
+ fd2, path = tempfile.mkstemp()
+ try:
+ self.assertFalse(ConfigureOutputHandler._is_same_output(fd1, fd2))
+
+ fd3 = os.dup(fd2)
+ try:
+ self.assertTrue(ConfigureOutputHandler._is_same_output(fd2, fd3))
+ finally:
+ os.close(fd3)
+
+ with open(path, "a") as fh:
+ fd3 = fh.fileno()
+ self.assertTrue(ConfigureOutputHandler._is_same_output(fd2, fd3))
+
+ finally:
+ os.close(fd2)
+ os.remove(path)
+
+
+class TestLineIO(unittest.TestCase):
+ def test_lineio(self):
+ lines = []
+ l = LineIO(lambda l: lines.append(l))
+
+ l.write("a")
+ self.assertEqual(lines, [])
+
+ l.write("b")
+ self.assertEqual(lines, [])
+
+ l.write("\n")
+ self.assertEqual(lines, ["ab"])
+
+ l.write("cdef")
+ self.assertEqual(lines, ["ab"])
+
+ l.write("\n")
+ self.assertEqual(lines, ["ab", "cdef"])
+
+ l.write("ghi\njklm")
+ self.assertEqual(lines, ["ab", "cdef", "ghi"])
+
+ l.write("nop\nqrst\nuv\n")
+ self.assertEqual(lines, ["ab", "cdef", "ghi", "jklmnop", "qrst", "uv"])
+
+ l.write("wx\nyz")
+ self.assertEqual(lines, ["ab", "cdef", "ghi", "jklmnop", "qrst", "uv", "wx"])
+
+ l.close()
+ self.assertEqual(
+ lines, ["ab", "cdef", "ghi", "jklmnop", "qrst", "uv", "wx", "yz"]
+ )
+
+ def test_lineio_contextmanager(self):
+ lines = []
+ with LineIO(lambda l: lines.append(l)) as l:
+ l.write("a\nb\nc")
+
+ self.assertEqual(lines, ["a", "b"])
+
+ self.assertEqual(lines, ["a", "b", "c"])
+
+
+class TestLogSubprocessOutput(unittest.TestCase):
+ def test_non_ascii_subprocess_output(self):
+ out = StringIO()
+ sandbox = ConfigureSandbox({}, {}, ["configure"], out, out)
+
+ sandbox.include_file(
+ mozpath.join(topsrcdir, "build", "moz.configure", "util.configure")
+ )
+ sandbox.include_file(
+ mozpath.join(
+ topsrcdir,
+ "python",
+ "mozbuild",
+ "mozbuild",
+ "test",
+ "configure",
+ "data",
+ "subprocess.configure",
+ )
+ )
+ status = 0
+ try:
+ sandbox.run()
+ except SystemExit as e:
+ status = e.code
+
+ self.assertEqual(status, 0)
+ quote_char = "'"
+ if getpreferredencoding().lower() == "utf-8":
+ quote_char = "\u00B4"
+ self.assertEqual(six.ensure_text(out.getvalue().strip()), quote_char)
+
+
+class TestVersion(unittest.TestCase):
+ def test_version_simple(self):
+ v = Version("1")
+ self.assertEqual(v, "1")
+ self.assertLess(v, "2")
+ self.assertGreater(v, "0.5")
+ self.assertEqual(v.major, 1)
+ self.assertEqual(v.minor, 0)
+ self.assertEqual(v.patch, 0)
+
+ def test_version_more(self):
+ v = Version("1.2.3b")
+ self.assertLess(v, "2")
+ self.assertEqual(v.major, 1)
+ self.assertEqual(v.minor, 2)
+ self.assertEqual(v.patch, 3)
+
+ def test_version_bad(self):
+ # A version with a letter in the middle doesn't really make sense,
+ # so everything after it should be ignored.
+ v = Version("1.2b.3")
+ self.assertLess(v, "2")
+ self.assertEqual(v.major, 1)
+ self.assertEqual(v.minor, 2)
+ self.assertEqual(v.patch, 0)
+
+ def test_version_badder(self):
+ v = Version("1b.2.3")
+ self.assertLess(v, "2")
+ self.assertEqual(v.major, 1)
+ self.assertEqual(v.minor, 0)
+ self.assertEqual(v.patch, 0)
+
+
+class TestCheckCmdOutput(unittest.TestCase):
+ def get_result(self, command="", paths=None):
+ paths = paths or {}
+ config = {}
+ out = StringIO()
+ sandbox = ConfigureTestSandbox(paths, config, {}, ["/bin/configure"], out, out)
+ sandbox.include_file(
+ mozpath.join(topsrcdir, "build", "moz.configure", "util.configure")
+ )
+ status = 0
+ try:
+ exec_(command, sandbox)
+ sandbox.run()
+ except SystemExit as e:
+ status = e.code
+ return config, out.getvalue(), status
+
+ def test_simple_program(self):
+ def mock_simple_prog(_, args):
+ if len(args) == 1 and args[0] == "--help":
+ return 0, "simple program help...", ""
+ self.fail("Unexpected arguments to mock_simple_program: %s" % args)
+
+ prog_path = mozpath.abspath("/simple/prog")
+ cmd = "log.info(check_cmd_output('%s', '--help'))" % prog_path
+ config, out, status = self.get_result(cmd, paths={prog_path: mock_simple_prog})
+ self.assertEqual(config, {})
+ self.assertEqual(status, 0)
+ self.assertEqual(out, "simple program help...\n")
+
+ def test_failing_program(self):
+ def mock_error_prog(_, args):
+ if len(args) == 1 and args[0] == "--error":
+ return (127, "simple program output", "simple program error output")
+ self.fail("Unexpected arguments to mock_error_program: %s" % args)
+
+ prog_path = mozpath.abspath("/simple/prog")
+ cmd = "log.info(check_cmd_output('%s', '--error'))" % prog_path
+ config, out, status = self.get_result(cmd, paths={prog_path: mock_error_prog})
+ self.assertEqual(config, {})
+ self.assertEqual(status, 1)
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ DEBUG: Executing: `%s --error`
+ DEBUG: The command returned non-zero exit status 127.
+ DEBUG: Its output was:
+ DEBUG: | simple program output
+ DEBUG: Its error output was:
+ DEBUG: | simple program error output
+ ERROR: Command `%s --error` failed with exit status 127.
+ """
+ % (prog_path, prog_path)
+ ),
+ )
+
+ def test_error_callback(self):
+ def mock_error_prog(_, args):
+ if len(args) == 1 and args[0] == "--error":
+ return 127, "simple program error...", ""
+ self.fail("Unexpected arguments to mock_error_program: %s" % args)
+
+ prog_path = mozpath.abspath("/simple/prog")
+ cmd = textwrap.dedent(
+ """\
+ check_cmd_output('%s', '--error',
+ onerror=lambda: die('`prog` produced an error'))
+ """
+ % prog_path
+ )
+ config, out, status = self.get_result(cmd, paths={prog_path: mock_error_prog})
+ self.assertEqual(config, {})
+ self.assertEqual(status, 1)
+ self.assertEqual(
+ out,
+ textwrap.dedent(
+ """\
+ DEBUG: Executing: `%s --error`
+ DEBUG: The command returned non-zero exit status 127.
+ DEBUG: Its output was:
+ DEBUG: | simple program error...
+ ERROR: `prog` produced an error
+ """
+ % prog_path
+ ),
+ )
+
+
+if __name__ == "__main__":
+ main()