bug 1241272 - Allow error() in moz.build files to be treated as non-fatal. r?gps
Calling error() in moz.build files to indicate unsupported configurations is
useful, but readers using EmptyConfig will trigger them currently. This patch
allows a Config to have an `error_is_fatal` attribute, which will make
error emit a warning instead.
--- a/python/mozbuild/mozbuild/frontend/context.py
+++ b/python/mozbuild/mozbuild/frontend/context.py
@@ -124,16 +124,21 @@ class Context(KeyedDefaultDict):
if not self.main_path:
self.main_path = self.current_path = path
# Insert at the beginning of the list so that it's always before the
# main path.
if path not in self._all_paths:
self._all_paths.insert(0, path)
@property
+ def error_is_fatal(self):
+ """Returns True if the error function should be fatal."""
+ return self.config and getattr(self.config, 'error_is_fatal', True)
+
+ @property
def all_paths(self):
"""Returns all paths ever added to the context."""
return set(self._all_paths)
@property
def source_stack(self):
"""Returns the current stack of pushed sources."""
if not self.current_path:
--- a/python/mozbuild/mozbuild/frontend/reader.py
+++ b/python/mozbuild/mozbuild/frontend/reader.py
@@ -129,16 +129,17 @@ class EmptyConfig(object):
for k, v in self.substs.items():
if isinstance(v, str):
udict[k.decode('utf-8')] = v.decode('utf-8')
else:
udict[k] = v
self.substs_unicode = self.PopulateOnGetDict(EmptyValue, udict)
self.defines = self.substs
self.external_source_dir = None
+ self.error_is_fatal = False
def is_read_allowed(path, config):
"""Whether we are allowed to load a mozbuild file at the specified path.
This is used as cheap security to ensure the build is isolated to known
source directories.
@@ -320,17 +321,20 @@ class MozbuildSandbox(Sandbox):
# path is a SourcePath
self.exec_file(path.full_path)
def _warning(self, message):
# FUTURE consider capturing warnings in a variable instead of printing.
print('WARNING: %s' % message, file=sys.stderr)
def _error(self, message):
- raise SandboxCalledError(self._context.source_stack, message)
+ if self._context.error_is_fatal:
+ raise SandboxCalledError(self._context.source_stack, message)
+ else:
+ self._warning(message)
def _template_decorator(self, func):
"""Registers a template function."""
if not inspect.isfunction(func):
raise Exception('`template` is a function decorator. You must '
'use it as `@template` preceding a function declaration.')
--- a/python/mozbuild/mozbuild/test/common.py
+++ b/python/mozbuild/mozbuild/test/common.py
@@ -13,17 +13,21 @@ import mozpack.path as mozpath
# By including this module, tests get structured logging.
log_manager = LoggingManager()
log_manager.add_terminal_logging()
# mozconfig is not a reusable type (it's actually a module) so, we
# have to mock it.
class MockConfig(object):
- def __init__(self, topsrcdir='/path/to/topsrcdir', extra_substs={}):
+ def __init__(self,
+ topsrcdir='/path/to/topsrcdir',
+ extra_substs={},
+ error_is_fatal=True,
+ ):
self.topsrcdir = mozpath.abspath(topsrcdir)
self.topobjdir = mozpath.abspath('/path/to/topobjdir')
self.substs = ReadOnlyDict({
'MOZ_FOO': 'foo',
'MOZ_BAR': 'bar',
'MOZ_TRUE': '1',
'MOZ_FALSE': '',
@@ -36,8 +40,9 @@ class MockConfig(object):
self.external_source_dir = None
self.lib_prefix = 'lib'
self.lib_suffix = '.a'
self.import_prefix = 'lib'
self.import_suffix = '.so'
self.dll_prefix = 'lib'
self.dll_suffix = '.so'
+ self.error_is_fatal = error_is_fatal
--- a/python/mozbuild/mozbuild/test/frontend/test_reader.py
+++ b/python/mozbuild/mozbuild/test/frontend/test_reader.py
@@ -37,21 +37,23 @@ class TestBuildReader(unittest.TestCase)
os.environ.clear()
os.environ.update(self._old_env)
def config(self, name, **kwargs):
path = mozpath.join(data_path, name)
return MockConfig(path, **kwargs)
- def reader(self, name, enable_tests=False, **kwargs):
+ def reader(self, name, enable_tests=False, error_is_fatal=True, **kwargs):
extra = {}
if enable_tests:
extra['ENABLE_TESTS'] = '1'
- config = self.config(name, extra_substs=extra)
+ config = self.config(name,
+ extra_substs=extra,
+ error_is_fatal=error_is_fatal)
return BuildReader(config, **kwargs)
def file_path(self, name, *args):
return mozpath.join(data_path, name, *args)
def test_dirs_traversal_simple(self):
reader = self.reader('traversal-simple')
@@ -234,16 +236,21 @@ class TestBuildReader(unittest.TestCase)
with self.assertRaises(BuildReaderError) as bre:
list(reader.read_topsrcdir())
e = bre.exception
self.assertIn('A moz.build file called the error() function.', str(e))
self.assertIn(' Some error.', str(e))
+ def test_error_error_func_ok(self):
+ reader = self.reader('reader-error-error-func', error_is_fatal=False)
+
+ contexts = list(reader.read_topsrcdir())
+
def test_inheriting_variables(self):
reader = self.reader('inheriting-variables')
contexts = list(reader.read_topsrcdir())
self.assertEqual(len(contexts), 4)
self.assertEqual([context.relsrcdir for context in contexts],
['', 'foo', 'foo/baz', 'bar'])