Bug 1337391 - Don't skip all directories that aren't traversed with a DIRS in a moz.build file. r?chmanchester draft
authorMike Hommey <mh+mozilla@glandium.org>
Thu, 09 Feb 2017 15:22:34 +0900
changeset 481058 25f931f0a1d8dd86ea78aa7ff89892c298d0ad71
parent 481016 e0dba8cdf2fc55048d9e9470539f2729dbf02625
child 545095 de1f103b429d47411fbe8e6dce7a02aebaf05259
push id44703
push userbmo:mh+mozilla@glandium.org
push dateThu, 09 Feb 2017 06:39:45 +0000
reviewerschmanchester
bugs1337391, 1308982, 1308992
milestone54.0a1
Bug 1337391 - Don't skip all directories that aren't traversed with a DIRS in a moz.build file. r?chmanchester The way directory traversal is computed relies on the RecursiveMakeTraversal class, which is used to reproduce the old traversal order from the old entirely-in-make traversal with DIRS, PARALLEL_DIRS, etc. because of the undeclared intra-directory dependencies that are looming here and there. It's fed through DirectoryTraversal objects emitted by the frontend. Normally, DirectoryTraversal objects are emitted for a directory, possibly giving the subdirectories defined in DIRS/TEST_DIRS its moz.build. But in the case of gyp processing, nothing places the gyp objdirs in some virtual DIRS of some parent moz.build since bug 1308982. As a consequence, the corresponding entries in the RecursiveMakeTraversal instance attached to the backend are not attached to any parent directory. When subsequently traversing the tree from the root, they are never found, and end up being skipped, irregarding of their actual _no_skip status. It would probably be possible to revert the changes from bug 1308992, but we might as well not rely on remains from the old ways. So instead, we make the RecursiveMakeTraversal consider directories without a declared parent attached directly to the root directory. They don't need to depend on any other directory anyways.
python/mozbuild/mozbuild/backend/recursivemake.py
python/mozbuild/mozbuild/test/backend/test_recursivemake.py
--- a/python/mozbuild/mozbuild/backend/recursivemake.py
+++ b/python/mozbuild/mozbuild/backend/recursivemake.py
@@ -258,42 +258,45 @@ class RecursiveMakeTraversal(object):
     SubDirectoryCategories = ['dirs', 'tests']
     SubDirectoriesTuple = namedtuple('SubDirectories', SubDirectoryCategories)
     class SubDirectories(SubDirectoriesTuple):
         def __new__(self):
             return RecursiveMakeTraversal.SubDirectoriesTuple.__new__(self, [], [])
 
     def __init__(self):
         self._traversal = {}
+        self._attached = set()
 
     def add(self, dir, dirs=[], tests=[]):
         """
         Adds a directory to traversal, registering its subdirectories,
         sorted by categories. If the directory was already added to
         traversal, adds the new subdirectories to the already known lists.
         """
         subdirs = self._traversal.setdefault(dir, self.SubDirectories())
         for key, value in (('dirs', dirs), ('tests', tests)):
             assert(key in self.SubDirectoryCategories)
+            # Callers give us generators
+            value = list(value)
             getattr(subdirs, key).extend(value)
+            self._attached |= set(value)
 
     @staticmethod
     def default_filter(current, subdirs):
         """
         Default filter for use with compute_dependencies and traverse.
         """
         return current, [], subdirs.dirs + subdirs.tests
 
     def call_filter(self, current, filter):
         """
         Helper function to call a filter from compute_dependencies and
         traverse.
         """
-        return filter(current, self._traversal.get(current,
-            self.SubDirectories()))
+        return filter(current, self.get_subdirs(current))
 
     def compute_dependencies(self, filter=None):
         """
         Compute make dependencies corresponding to the registered directory
         traversal.
 
         filter is a function with the following signature:
             def filter(current, subdirs)
@@ -352,17 +355,26 @@ class RecursiveMakeTraversal(object):
         for dir in sequential:
             for d in self.traverse(dir, filter):
                 yield d
 
     def get_subdirs(self, dir):
         """
         Returns all direct subdirectories under the given directory.
         """
-        return self._traversal.get(dir, self.SubDirectories())
+        result = self._traversal.get(dir, self.SubDirectories())
+        if dir == '':
+            unattached = set(self._traversal) - self._attached - set([''])
+            if unattached:
+                new_result = self.SubDirectories()
+                new_result.dirs.extend(result.dirs)
+                new_result.dirs.extend(sorted(unattached))
+                new_result.tests.extend(result.tests)
+                result = new_result
+        return result
 
 
 class RecursiveMakeBackend(CommonBackend):
     """Backend that integrates with the existing recursive make build system.
 
     This backend facilitates the transition from Makefile.in to moz.build
     files.
 
--- a/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
+++ b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
@@ -153,16 +153,48 @@ class TestRecursiveMakeTraversal(unittes
             'D': ('A',),
             'E': ('D',),
             'F': ('E',),
             'G': ('C',),
             'H': ('G',),
             'I': ('H',),
         })
 
+    def test_traversal_parallel(self):
+        traversal = RecursiveMakeTraversal()
+        traversal.add('', dirs=['A', 'B', 'C'])
+        traversal.add('A')
+        traversal.add('B', dirs=['D', 'E', 'F'])
+        traversal.add('C', dirs=['G', 'H', 'I'])
+        traversal.add('D')
+        traversal.add('E')
+        traversal.add('F')
+        traversal.add('G')
+        traversal.add('H')
+        traversal.add('I')
+        traversal.add('J')
+
+        def filter(current, subdirs):
+            return current, subdirs.dirs, []
+
+        start, deps = traversal.compute_dependencies(filter)
+        self.assertEqual(start, ('A', 'D', 'E', 'F', 'G', 'H', 'I', 'J'))
+        self.assertEqual(deps, {
+            'A': ('',),
+            'B': ('',),
+            'C': ('',),
+            'D': ('B',),
+            'E': ('B',),
+            'F': ('B',),
+            'G': ('C',),
+            'H': ('C',),
+            'I': ('C',),
+            'J': ('',),
+        })
+
 class TestRecursiveMakeBackend(BackendTester):
     def test_basic(self):
         """Ensure the RecursiveMakeBackend works without error."""
         env = self._consume('stub0', RecursiveMakeBackend)
         self.assertTrue(os.path.exists(mozpath.join(env.topobjdir,
             'backend.RecursiveMakeBackend')))
         self.assertTrue(os.path.exists(mozpath.join(env.topobjdir,
             'backend.RecursiveMakeBackend.in')))