Bug 1428608: Allow explicitly specifying dirs from EXTERNAL_SOURCE_DIR to recurse into; r?Build draft
authorTom Prince <mozilla@hocat.ca>
Sun, 07 Jan 2018 20:41:41 -0700
changeset 717048 741a6d79b8ac779175803023d61b43ab5bbde05a
parent 717047 8c3fb3a9ba4b6d441c99f59738d76144586e00cd
child 717049 20b49adae05720d95c62ffb5905e7b64205fd56e
push id94541
push userbmo:mozilla@hocat.ca
push dateMon, 08 Jan 2018 03:54:49 +0000
reviewersBuild
bugs1428608
milestone59.0a1
Bug 1428608: Allow explicitly specifying dirs from EXTERNAL_SOURCE_DIR to recurse into; r?Build MozReview-Commit-ID: DuQpmyMOtpM
python/mozbuild/mozbuild/backend/recursivemake.py
python/mozbuild/mozbuild/frontend/context.py
python/mozbuild/mozbuild/frontend/emitter.py
python/mozbuild/mozbuild/frontend/reader.py
--- a/python/mozbuild/mozbuild/backend/recursivemake.py
+++ b/python/mozbuild/mozbuild/backend/recursivemake.py
@@ -20,16 +20,17 @@ from mozpack.manifests import (
 )
 import mozpack.path as mozpath
 
 from mozbuild.frontend.context import (
     AbsolutePath,
     Path,
     RenamedSourcePath,
     SourcePath,
+    ExternalSourcePath,
     ObjDirPath,
 )
 from .common import CommonBackend
 from ..frontend.data import (
     AndroidAssetsDirs,
     AndroidResDirs,
     AndroidExtraResDirs,
     AndroidExtraPackages,
@@ -962,16 +963,18 @@ class RecursiveMakeBackend(CommonBackend
                 return '$(srcdir)', path.full_path[len(backend_file.srcdir):]
             if path.full_path.startswith(backend_file.topsrcdir):
                 return '$(topsrcdir)', path.full_path[len(backend_file.topsrcdir):]
         elif isinstance(path, ObjDirPath):
             if path.full_path.startswith(backend_file.objdir):
                 return '', path.full_path[len(backend_file.objdir) + 1:]
             if path.full_path.startswith(self.environment.topobjdir):
                 return '$(DEPTH)', path.full_path[len(self.environment.topobjdir):]
+        elif isinstance(path, ExternalSourcePath):
+            return '$(EXTERNAL_SOURCE_DIR)', path.full_path[len(self.environment.external_source_dir):]
 
         return '', path.full_path
 
     def _pretty_path(self, path, backend_file):
         return ''.join(self._pretty_path_parts(path, backend_file))
 
     def _process_unified_sources(self, obj):
         backend_file = self._get_backend_file_for(obj)
--- a/python/mozbuild/mozbuild/frontend/context.py
+++ b/python/mozbuild/mozbuild/frontend/context.py
@@ -677,16 +677,48 @@ class SourcePath(Path):
 
         Ideally, we wouldn't need this function, but the fact that both source
         path under topsrcdir and the external source dir end up mixed in the
         objdir (aka pseudo-rework), this is needed.
         """
         return ObjDirPath(self.context, '!%s' % self).full_path
 
 
+class ExternalSourcePath(SourcePath):
+    """Like Path, but limited to paths in the source directory."""
+    def __init__(self, context, value):
+        if not os.path.isabs(value):
+            raise ValueError('Path \'%s\' is not absolute' % value)
+        # We explicitly skip calling SourcePath's __init__ here
+        super(SourcePath, self).__init__(context, value)
+
+        if context.config.external_source_dir:
+            path = mozpath.join(context.config.external_source_dir,
+                                value[1:])
+        else:
+            raise ValueError('External source directory requested but none specified')
+        self.full_path = mozpath.normpath(path)
+
+    def join(self, *p):
+        """ContextDerived equivalent of mozpath.join(self, *p), returning a
+        new ExternalSourcePath instance.
+        """
+        return ExternalSourcePath(self.context, mozpath.join(self, *p))
+
+    @memoized_property
+    def translated(self):
+        """Returns the corresponding path in the objdir.
+
+        Ideally, we wouldn't need this function, but the fact that both source
+        path under topsrcdir and the external source dir end up mixed in the
+        objdir (aka pseudo-rework), this is needed.
+        """
+        return ObjDirPath(self.context, '!%s' % self).full_path
+
+
 class RenamedSourcePath(SourcePath):
     """Like SourcePath, but with a different base name when installed.
 
     The constructor takes a tuple of (source, target_basename).
 
     This class is not meant to be exposed to moz.build sandboxes as of now,
     and is not supported by the RecursiveMake backend.
     """
@@ -1386,16 +1418,20 @@ VARIABLES = {
         and read the frontend file there. If there is no frontend file, an error
         is raised.
 
         Values are relative paths. They can be multiple directory levels
         above or below. Use ``..`` for parent directories and ``/`` for path
         delimiters.
         """),
 
+    'EXTERNAL_SOURCE_DIRS': (ContextDerivedTypedList(ExternalSourcePath), list,
+        """Directories relative to EXTERNAL_SOURCE_DIR.
+        """),
+
     'HAS_MISC_RULE': (bool, bool,
         """Whether this directory should be traversed in the ``misc`` tier.
 
         Many ``libs`` rules still exist in Makefile.in files. We highly prefer
         that these rules exist in the ``misc`` tier/target so that they can be
         executed concurrently during tier traversal (the ``misc`` tier is
         fully concurrent).
 
--- a/python/mozbuild/mozbuild/frontend/emitter.py
+++ b/python/mozbuild/mozbuild/frontend/emitter.py
@@ -1591,15 +1591,16 @@ class TreeMetadataEmitter(LoggingMixin):
             if 'jar.mn' not in jar_manifests:
                 raise SandboxValidationError('A jar.mn exists but it '
                     'is not referenced in the moz.build file. '
                     'Please define JAR_MANIFESTS.', context)
 
     def _emit_directory_traversal_from_context(self, context):
         o = DirectoryTraversal(context)
         o.dirs += context.get('DIRS', [])
+        o.dirs += context.get('EXTERNAL_SOURCE_DIRS', [])
 
         # Some paths have a subconfigure, yet also have a moz.build. Those
         # shouldn't end up in self._external_paths.
         if o.objdir:
             self._external_paths -= { o.relobjdir }
 
         yield o
--- a/python/mozbuild/mozbuild/frontend/reader.py
+++ b/python/mozbuild/mozbuild/frontend/reader.py
@@ -1123,16 +1123,17 @@ class BuildReader(object):
 
         # Yield main context before doing any processing. This gives immediate
         # consumers an opportunity to change state before our remaining
         # processing is performed.
         yield context
 
         # We need the list of directories pre-gyp processing for later.
         dirs = list(context.get('DIRS', []))
+        dirs += list(context.get('EXTERNAL_SOURCE_DIRS', []))
 
         curdir = mozpath.dirname(path)
 
         for target_dir in context.get('GYP_DIRS', []):
             gyp_dir = context['GYP_DIRS'][target_dir]
             for v in ('input', 'variables'):
                 if not getattr(gyp_dir, v):
                     raise SandboxValidationError('Missing value for '
@@ -1302,17 +1303,17 @@ class BuildReader(object):
 
         contexts = defaultdict(list)
         all_contexts = []
         for context in self.read_mozbuild(mozpath.join(topsrcdir, 'moz.build'),
                                           self.config, metadata=metadata):
             # Explicitly set directory traversal variables to override default
             # traversal rules.
             if not isinstance(context, SubContext):
-                for v in ('DIRS', 'GYP_DIRS'):
+                for v in ('DIRS', 'GYP_DIRS', 'EXTERNAL_SOURCE_DIRS'):
                     context[v][:] = []
 
                 context['DIRS'] = sorted(dirs[context.main_path])
 
             contexts[context.main_path].append(context)
             all_contexts.append(context)
 
         result = {}