Bug 1132771 - API to return moz.build files relevant for a set of paths draft
authorGregory Szorc <gps@mozilla.com>
Thu, 12 Feb 2015 23:15:12 -0800
changeset 243048 7f1ba6df67a715847ba99842269472c765219871
parent 243047 951663fa5951fccf20d9933fae30ce084b56bd3c
child 243049 0fbc158c1aa0233d087c8ea270a3773838852a9d
push id698
push usergszorc@mozilla.com
push dateMon, 16 Feb 2015 20:34:37 +0000
bugs1132771
milestone38.0a1
Bug 1132771 - API to return moz.build files relevant for a set of paths We have an eventual goal to store file-level metadata in moz.build files and to have this metadata "cascade" down directory hierarchies. e.g. metadata in the root directory will apply to all children directories. A prerequisite for this feature is a way to query which moz.build files are relevant to a given file. In this patch, we implement an API that returns this information.
python/mozbuild/mozbuild/frontend/reader.py
python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/every-level/a/file
python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/every-level/a/moz.build
python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/every-level/b/file
python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/every-level/b/moz.build
python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/every-level/moz.build
python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/file1
python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/file2
python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/moz.build
python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/no-intermediate-moz-build/child/file
python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/no-intermediate-moz-build/child/moz.build
python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/parent-is-far/dir1/dir2/dir3/file
python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d1/parent-is-far/moz.build
python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d2/dir1/file
python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d2/dir1/moz.build
python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d2/dir2/file
python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d2/dir2/moz.build
python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/d2/moz.build
python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/file
python/mozbuild/mozbuild/test/frontend/data/reader-relevant-mozbuild/moz.build
python/mozbuild/mozbuild/test/frontend/test_reader.py
--- a/python/mozbuild/mozbuild/frontend/reader.py
+++ b/python/mozbuild/mozbuild/frontend/reader.py
@@ -1057,8 +1057,65 @@ class BuildReader(object):
             if not descend:
                 continue
 
             for res in self.read_mozbuild(child_path, context.config,
                 read_tiers=False, metadata=child_metadata):
                 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 a directory ancestry of a given path
+        is relevant to that path. Let's say we have the following files on disk::
+
+           moz.build
+           foo/moz.build
+           foo/baz/moz.build
+           foo/baz/file1
+           other/moz.build
+           other/file2
+
+        If ``foo/baz/file1`` is passed in, the relevant moz.build files are
+        ``moz.build``, ``foo/moz.build``, and ``foo/baz/moz.build``. For
+        ``other/file2``, the relevant moz.build files are ``moz.build`` and
+        ``other/moz.build``.
+
+        Returns a 2-tuple containing a dict of input paths to a list of
+        relevant moz.build files, root first and a set of all relevant
+        moz.build paths.
+        """
+        relevant = {}
+        seen_mozbuild = set()
+
+        root = self.config.topsrcdir
+        for path in sorted(paths, key=len):
+            path = mozpath.normpath(path)
+            path = mozpath.join(root, path)
+
+            if not path.startswith(root):
+                raise Exception('Path outside of topsrcdir: %s' % path)
+
+            current_dir = path
+            if not os.path.isdir(path):
+                current_dir = mozpath.dirname(path)
+
+            current_relevant = []
+
+            while True:
+                candidate = mozpath.join(current_dir, 'moz.build')
+                # Avoid excessive stat().
+                if candidate in seen_mozbuild or os.path.exists(candidate):
+                    current_relevant.append(candidate)
+                    seen_mozbuild.add(candidate)
+
+                if current_dir == root:
+                    break
+
+                current_dir = mozpath.dirname(current_dir)
+
+            key = mozpath.relpath(path, root)
+            relevant[key] = [mozpath.relpath(p, root)
+                             for p in reversed(current_relevant)]
+
+        return relevant, {mozpath.relpath(p, root) for p in seen_mozbuild}
new file mode 100644
new file mode 100644
new file mode 100644
new file mode 100644
new file mode 100644
new file mode 100644
new file mode 100644
new file mode 100644
new file mode 100644
new file mode 100644
new file mode 100644
new file mode 100644
new file mode 100644
new file mode 100644
new file mode 100644
new file mode 100644
new file mode 100644
new file mode 100644
new file mode 100644
--- a/python/mozbuild/mozbuild/test/frontend/test_reader.py
+++ b/python/mozbuild/mozbuild/test/frontend/test_reader.py
@@ -247,11 +247,85 @@ class TestBuildReader(unittest.TestCase)
 
         count = [0]
 
         contexts = list(reader.read_topsrcdir())
 
         self.assertEqual(len(contexts), 1)
         self.assertEqual(len(count), 1)
 
+    def test_find_relevant_mozbuilds(self):
+        reader = self.reader('reader-relevant-mozbuild')
+
+        # File in root directory.
+        paths, mozbuilds = reader._find_relevant_mozbuilds(['file'])
+        self.assertEqual(paths, {'file': ['moz.build']})
+        self.assertEqual(mozbuilds, {'moz.build'})
+
+        # File in child directory.
+        paths, mozbuilds = reader._find_relevant_mozbuilds(['d1/file1'])
+        self.assertEqual(paths, {'d1/file1': ['moz.build', 'd1/moz.build']})
+        self.assertEqual(mozbuilds, {'moz.build', 'd1/moz.build'})
+
+        # Multiple files in same directory.
+        paths, mozbuilds = reader._find_relevant_mozbuilds(['d1/file1', 'd1/file2'])
+        self.assertEqual(paths, {
+            'd1/file1': ['moz.build', 'd1/moz.build'],
+            'd1/file2': ['moz.build', 'd1/moz.build']})
+        self.assertEqual(mozbuilds, {'moz.build', 'd1/moz.build'})
+
+        # Missing moz.build from missing intermediate directory.
+        paths, mozbuilds = reader._find_relevant_mozbuilds(
+            ['d1/no-intermediate-moz-build/child/file'])
+        self.assertEqual(paths, {
+            'd1/no-intermediate-moz-build/child/file': [
+                'moz.build', 'd1/moz.build', 'd1/no-intermediate-moz-build/child/moz.build']})
+        self.assertEqual(mozbuilds, {
+            'moz.build',
+            'd1/moz.build',
+            'd1/no-intermediate-moz-build/child/moz.build',
+        })
+
+        # Lots of empty directories.
+        paths, mozbuilds = reader._find_relevant_mozbuilds([
+            'd1/parent-is-far/dir1/dir2/dir3/file'])
+        self.assertEqual(paths, {
+            'd1/parent-is-far/dir1/dir2/dir3/file':
+                ['moz.build', 'd1/moz.build', 'd1/parent-is-far/moz.build']})
+        self.assertEqual(mozbuilds, {'moz.build', 'd1/moz.build', 'd1/parent-is-far/moz.build'})
+
+        # Lots of levels.
+        paths, mozbuilds = reader._find_relevant_mozbuilds([
+            'd1/every-level/a/file', 'd1/every-level/b/file'])
+        self.assertEqual(paths, {
+            'd1/every-level/a/file': [
+                'moz.build',
+                'd1/moz.build',
+                'd1/every-level/moz.build',
+                'd1/every-level/a/moz.build',
+            ],
+            'd1/every-level/b/file': [
+                'moz.build',
+                'd1/moz.build',
+                'd1/every-level/moz.build',
+                'd1/every-level/b/moz.build',
+            ],
+        })
+        self.assertEqual(mozbuilds, {
+            'moz.build',
+            'd1/moz.build',
+            'd1/every-level/moz.build',
+            'd1/every-level/a/moz.build',
+            'd1/every-level/b/moz.build',
+        })
+
+        # Different root directories.
+        paths, mozbuilds = reader._find_relevant_mozbuilds(['d1/file', 'd2/file', 'file'])
+        self.assertEqual(paths, {
+            'file': ['moz.build'],
+            'd1/file': ['moz.build', 'd1/moz.build'],
+            'd2/file': ['moz.build', 'd2/moz.build'],
+        })
+        self.assertEqual(mozbuilds, {'moz.build', 'd1/moz.build', 'd2/moz.build'})
+
 
 if __name__ == '__main__':
     main()