Bug 1397406 - Add a helper function to retrieve a BuildReader; r?dustin
The code for obtaining a BuildReader for evaluating moz.build files
is generic and non-trivial. We already had a custom implementation
for `mach file-info` that implemented support for Mercurial
integration.
Bug 1383880 will introduce a second consumer.
So this commit factors out the "obtain a BuildReader" logic into
a reusable function on our base MozbuildObject class. This makes
it easily available to various parts of the build system and mach
commands.
As part of the change, we detect when ``.`` is being used as the
revision and verify the working directory is clean. This behavior
can be disabled via argument if unwanted. But it's useful by default
to ensure consumers aren't expecting to read uncommitted changes.
MozReview-Commit-ID: LeYFqAb3HAe
--- a/python/mozbuild/mozbuild/base.py
+++ b/python/mozbuild/mozbuild/base.py
@@ -12,16 +12,17 @@ import os
import subprocess
import sys
import which
from mach.mixin.process import ProcessExecutionMixin
from mozversioncontrol import (
get_repository_from_build_config,
get_repository_object,
+ InvalidRepoPath,
)
from .backend.configenvironment import ConfigEnvironment
from .controller.clobber import Clobberer
from .mozconfig import (
MozconfigFindException,
MozconfigLoadException,
MozconfigLoader,
@@ -293,16 +294,90 @@ class MozbuildObject(ProcessExecutionMix
# If we don't have a configure context, fall back to auto-detection.
try:
return get_repository_from_build_config(self)
except BuildEnvironmentNotFoundException:
pass
return get_repository_object(self.topsrcdir)
+ def mozbuild_reader(self, config_mode='build', vcs_revision=None,
+ vcs_check_clean=True):
+ """Obtain a ``BuildReader`` for evaluating moz.build files.
+
+ Given arguments, returns a ``mozbuild.frontend.reader.BuildReader``
+ that can be used to evaluate moz.build files for this repo.
+
+ ``config_mode`` is either ``build`` or ``empty``. If ``build``,
+ ``self.config_environment`` is used. This requires a configured build
+ system to work. If ``empty``, an empty config is used. ``empty`` is
+ appropriate for file-based traversal mode where ``Files`` metadata is
+ read.
+
+ If ``vcs_revision`` is defined, it specifies a version control revision
+ to use to obtain files content. The default is to use the filesystem.
+ This mode is only supported with Mercurial repositories.
+
+ If ``vcs_revision`` is not defined and the version control checkout is
+ sparse, this implies ``vcs_revision='.'``.
+
+ If ``vcs_revision`` is ``.`` (denotes the parent of the working
+ directory), we will verify that the working directory is clean unless
+ ``vcs_check_clean`` is False. This prevents confusion due to uncommitted
+ file changes not being reflected in the reader.
+ """
+ from mozbuild.frontend.reader import (
+ default_finder,
+ BuildReader,
+ EmptyConfig,
+ )
+ from mozpack.files import (
+ MercurialRevisionFinder,
+ )
+
+ if config_mode == 'build':
+ config = self.config_environment
+ elif config_mode == 'empty':
+ config = EmptyConfig(self.topsrcdir)
+ else:
+ raise ValueError('unknown config_mode value: %s' % config_mode)
+
+ try:
+ repo = self.repository
+ except InvalidRepoPath:
+ repo = None
+
+ if repo and not vcs_revision and repo.sparse_checkout_present():
+ vcs_revision = '.'
+
+ if vcs_revision is None:
+ finder = default_finder
+ else:
+ # If we failed to detect the repo prior, check again to raise its
+ # exception.
+ if not repo:
+ self.repository
+ assert False
+
+ if repo.name != 'hg':
+ raise Exception('do not support VCS reading mode for %s' %
+ repo.name)
+
+ if vcs_revision == '.' and vcs_check_clean:
+ with repo:
+ if not repo.working_directory_clean():
+ raise Exception('working directory is not clean; '
+ 'refusing to use a VCS-based finder')
+
+ finder = MercurialRevisionFinder(self.topsrcdir, rev=vcs_revision,
+ recognize_repo_paths=True)
+
+ return BuildReader(config, finder=finder)
+
+
@memoized_property
def python3(self):
"""Obtain info about a Python 3 executable.
Returns a tuple of an executable path and its version (as a tuple).
Either both entries will have a value or both will be None.
"""
# Search configured build info first. Then fall back to system.
--- a/python/mozbuild/mozbuild/frontend/mach_commands.py
+++ b/python/mozbuild/mozbuild/frontend/mach_commands.py
@@ -154,70 +154,41 @@ class MozbuildFileCommands(MachCommandBa
for p in m.test_flavors:
print('\t\t%s' % p)
except InvalidPathException as e:
print(e.message)
return 1
- def _get_reader(self, finder):
- from mozbuild.frontend.reader import (
- BuildReader,
- EmptyConfig,
- )
-
- config = EmptyConfig(self.topsrcdir)
- return BuildReader(config, finder=finder)
-
def _get_files_info(self, paths, rev=None):
- from mozbuild.frontend.reader import default_finder
- from mozpack.files import FileFinder, MercurialRevisionFinder
+ reader = self.mozbuild_reader(config_mode='empty', vcs_revision=rev)
# Normalize to relative from topsrcdir.
relpaths = []
for p in paths:
a = mozpath.abspath(p)
if not mozpath.basedir(a, [self.topsrcdir]):
raise InvalidPathException('path is outside topsrcdir: %s' % p)
relpaths.append(mozpath.relpath(a, self.topsrcdir))
- repo = None
- if rev:
- hg_path = os.path.join(self.topsrcdir, '.hg')
- if not os.path.exists(hg_path):
- raise InvalidPathException('a Mercurial repo is required '
- 'when specifying a revision')
-
- repo = self.topsrcdir
-
- # We need two finders because the reader's finder operates on
- # absolute paths.
- finder = FileFinder(self.topsrcdir)
- if repo:
- reader_finder = MercurialRevisionFinder(repo, rev=rev,
- recognize_repo_paths=True)
- else:
- reader_finder = default_finder
-
# Expand wildcards.
# One variable is for ordering. The other for membership tests.
# (Membership testing on a list can be slow.)
allpaths = []
all_paths_set = set()
for p in relpaths:
if '*' not in p:
if p not in all_paths_set:
all_paths_set.add(p)
allpaths.append(p)
continue
- if repo:
+ if rev:
raise InvalidPathException('cannot use wildcard in version control mode')
- for path, f in finder.find(p):
+ for path, f in reader.finder.find(p):
if path not in all_paths_set:
all_paths_set.add(path)
allpaths.append(path)
- reader = self._get_reader(finder=reader_finder)
return reader.files_info(allpaths)