--- a/python/mozbuild/mozbuild/frontend/context.py
+++ b/python/mozbuild/mozbuild/frontend/context.py
@@ -84,27 +84,32 @@ class Context(KeyedDefaultDict):
allowed_variables is a dict of the variables that can be set and read in
this context instance. Keys in this dict are the strings representing keys
in this context which are valid. Values are tuples of stored type,
assigned type, default value, a docstring describing the purpose of the
variable, and a tier indicator (see comment above the VARIABLES declaration
in this module).
config is the ConfigEnvironment for this context.
+
+ eval_flags indicates special evaluation modes that are in effect.
+ See the documentation in reader.py for more.
"""
- def __init__(self, allowed_variables=None, config=None, finder=None):
+ def __init__(self, allowed_variables=None, config=None, finder=None,
+ eval_flags=None):
self._allowed_variables = allowed_variables or {}
self.main_path = None
self.current_path = None
# There aren't going to be enough paths for the performance of scanning
# a list to be a problem.
self._all_paths = []
self.config = config
self._sandbox = None
self._finder = finder
+ self.eval_flags = eval_flags or set()
KeyedDefaultDict.__init__(self, self._factory)
def push_source(self, path):
"""Adds the given path as source of the data from this context and make
it the current path for the context."""
assert os.path.isabs(path)
if not self.main_path:
self.main_path = path
@@ -249,19 +254,21 @@ class Context(KeyedDefaultDict):
if not isinstance(value, stored_type):
update[key] = stored_type(value)
else:
update[key] = value
KeyedDefaultDict.update(self, update)
class TemplateContext(Context):
- def __init__(self, template=None, allowed_variables=None, config=None):
+ def __init__(self, template=None, allowed_variables=None, config=None,
+ eval_flags=None):
self.template = template
- super(TemplateContext, self).__init__(allowed_variables, config)
+ super(TemplateContext, self).__init__(allowed_variables, config,
+ eval_flags=eval_flags)
def _validate(self, key, value):
return Context._validate(self, key, value, True)
class SubContext(Context, ContextDerivedValue):
"""A Context derived from another Context.
@@ -269,17 +276,17 @@ class SubContext(Context, ContextDerived
Sub-contexts inherit paths and other relevant state from the parent
context.
"""
def __init__(self, parent):
assert isinstance(parent, Context)
Context.__init__(self, allowed_variables=self.VARIABLES,
- config=parent.config)
+ config=parent.config, eval_flags=parent.eval_flags)
# Copy state from parent.
for p in parent.source_stack:
self.push_source(p)
self._sandbox = parent._sandbox
def __enter__(self):
if not self._sandbox or self._sandbox() is None:
--- a/python/mozbuild/mozbuild/frontend/reader.py
+++ b/python/mozbuild/mozbuild/frontend/reader.py
@@ -362,17 +362,18 @@ class MozbuildSandbox(Sandbox):
That function creates a new sandbox for execution of the template.
After the template is executed, the data from its execution is merged
with the context of the calling sandbox.
"""
def template_wrapper(*args, **kwargs):
context = TemplateContext(
template=template.name,
allowed_variables=self._context._allowed_variables,
- config=self._context.config)
+ config=self._context.config,
+ eval_flags=self._context.eval_flags)
context.add_source(self._context.current_path)
for p in self._context.all_paths:
context.add_source(p)
sandbox = MozbuildSandbox(context, metadata={
# We should arguably set these defaults to something else.
# Templates, for example, should arguably come from the state
# of the sandbox from when the template was declared, not when
@@ -1025,17 +1026,18 @@ class BuildReader(object):
source = fh.read()
tree = ast.parse(source, full)
Visitor().visit(tree)
for name, key, value in assignments:
yield p, name, key, value
- def read_mozbuild(self, path, config, descend=True, metadata=None):
+ def read_mozbuild(self, path, config, descend=True, metadata=None,
+ eval_flags=None):
"""Read and process a mozbuild file, descending into children.
This starts with a single mozbuild file, executes it, and descends into
other referenced files per our traversal logic.
The traversal logic is to iterate over the *DIRS variables, treating
each element as a relative directory path. For each encountered
directory, we will open the moz.build file located in that
@@ -1045,23 +1047,29 @@ class BuildReader(object):
directories and files per variable values.
Arbitrary metadata in the form of a dict can be passed into this
function. This feature is intended to facilitate the build reader
injecting state and annotations into moz.build files that is
independent of the sandbox's execution context.
Traversal is performed depth first (for no particular reason).
+
+ ``eval_flags`` is a set specifying additional flags to control
+ evaluation. The flags are passed down to the Context, where they
+ can be used to influence run-time behavior.
"""
metadata = metadata or {}
+ eval_flags = eval_flags or set()
self._execution_stack.append(path)
try:
for s in self._read_mozbuild(path, config, descend=descend,
- metadata=metadata):
+ metadata=metadata,
+ eval_flags=eval_flags):
yield s
except BuildReaderError as bre:
raise bre
except SandboxCalledError as sce:
raise BuildReaderError(list(self._execution_stack),
sys.exc_info()[2], sandbox_called_error=sce)
@@ -1077,17 +1085,17 @@ class BuildReader(object):
except SandboxValidationError as ve:
raise BuildReaderError(list(self._execution_stack),
sys.exc_info()[2], validation_error=ve)
except Exception as e:
raise BuildReaderError(list(self._execution_stack),
sys.exc_info()[2], other_error=e)
- def _read_mozbuild(self, path, config, descend, metadata):
+ def _read_mozbuild(self, path, config, descend, metadata, eval_flags):
path = mozpath.normpath(path)
log(self._log, logging.DEBUG, 'read_mozbuild', {'path': path},
'Reading file: {path}')
if path in self._read_files:
log(self._log, logging.WARNING, 'read_already', {'path': path},
'File already read. Skipping: {path}')
return
@@ -1111,17 +1119,18 @@ class BuildReader(object):
if mozpath.dirname(relpath) == 'js/src' and \
not config.substs.get('JS_STANDALONE'):
config = ConfigEnvironment.from_config_status(
mozpath.join(topobjdir, reldir, 'config.status'))
config.topobjdir = topobjdir
config.external_source_dir = None
- context = Context(VARIABLES, config, self.finder)
+ context = Context(VARIABLES, config=config, finder=self.finder,
+ eval_flags=eval_flags)
sandbox = MozbuildSandbox(context, metadata=metadata,
finder=self.finder)
sandbox.exec_file(path)
self._execution_time += time.time() - time_start
self._file_count += len(context.all_paths)
# Yield main context before doing any processing. This gives immediate
# consumers an opportunity to change state before our remaining
@@ -1201,17 +1210,18 @@ class BuildReader(object):
raise SandboxValidationError(
'Attempting to process file outside of allowed paths: %s' %
child_path, context)
if not descend:
continue
for res in self.read_mozbuild(child_path, context.config,
- metadata=child_metadata):
+ metadata=child_metadata,
+ eval_flags=eval_flags):
yield res
self._execution_stack.pop()
def _find_relevant_mozbuilds(self, paths):
"""Given a set of filesystem paths, find all relevant moz.build files.
We assume that a moz.build file in the directory ancestry of a given path
@@ -1252,17 +1262,17 @@ class BuildReader(object):
if not mozpath.basedir(path, [root]):
raise Exception('Path outside topsrcdir: %s' % path)
path = mozpath.relpath(path, root)
result[path] = [p for p in itermozbuild(path) if exists(p)]
return result
- def read_relevant_mozbuilds(self, paths):
+ def read_relevant_mozbuilds(self, paths, eval_flags=None):
"""Read and process moz.build files relevant for a set of paths.
For an iterable of relative-to-root filesystem paths ``paths``,
find all moz.build files that may apply to them based on filesystem
hierarchy and read those moz.build files.
The return value is a 2-tuple. The first item is a dict mapping each
input filesystem path to a list of Context instances that are relevant
@@ -1300,17 +1310,18 @@ class BuildReader(object):
metadata = {
'functions': functions,
}
contexts = defaultdict(list)
all_contexts = []
for context in self.read_mozbuild(mozpath.join(topsrcdir, 'moz.build'),
- self.config, metadata=metadata):
+ self.config, metadata=metadata,
+ eval_flags=eval_flags):
# Explicitly set directory traversal variables to override default
# traversal rules.
if not isinstance(context, SubContext):
for v in ('DIRS', 'GYP_DIRS'):
context[v][:] = []
context['DIRS'] = sorted(dirs[context.main_path])
@@ -1336,17 +1347,19 @@ class BuildReader(object):
1. Determine the set of moz.build files relevant to that file by
looking for moz.build files in ancestor directories.
2. Evaluate moz.build files starting with the most distant.
3. Iterate over Files sub-contexts.
4. If the file pattern matches the file we're seeking info on,
apply attribute updates.
5. Return the most recent value of attributes.
"""
- paths, _ = self.read_relevant_mozbuilds(paths)
+ eval_flags = {'files-info'}
+
+ paths, _ = self.read_relevant_mozbuilds(paths, eval_flags=eval_flags)
# For thousands of inputs (say every file in a sub-tree),
# test_defaults_for_path() gets called with the same contexts multiple
# times (once for every path in a directory that doesn't have any
# test metadata). So, we cache the function call.
defaults_cache = {}
def test_defaults_for_path(ctxs):
key = tuple(ctx.current_path or ctx.main_path for ctx in ctxs)
@@ -1357,17 +1370,17 @@ class BuildReader(object):
return defaults_cache[key]
r = {}
for path, ctxs in paths.items():
# Should be normalized by read_relevant_mozbuilds.
assert '\\' not in path
- flags = Files(Context())
+ flags = Files(Context(eval_flags=eval_flags))
for ctx in ctxs:
if not isinstance(ctx, Files):
continue
# read_relevant_mozbuilds() normalizes paths and ensures that
# the contexts have paths in the ancestry of the path. When
# iterating over tens of thousands of paths, mozpath.relpath()