Bug 1132771 - Convert Sphinx to sub-contexts draft
authorGregory Szorc <gps@mozilla.com>
Tue, 24 Feb 2015 15:33:56 -0800
changeset 245808 438964baab5646e4bbee4a28cf2d549c93ccf86e
parent 245807 eedb449eadbedc66d15ac739e4a8cca565847798
child 245809 01d7fabaeb31e3581f758d1dba09de090df1a3ec
push id801
push usergszorc@mozilla.com
push dateWed, 25 Feb 2015 01:23:12 +0000
bugs1132771
milestone39.0a1
Bug 1132771 - Convert Sphinx to sub-contexts Now that we have support for defining sub-contexts and for reading all moz.build files without a build config, convert Sphinx to be sub-contexts. This gets rid of the gross AST-based moz.build reading hacks that were previously introduced in order to read Sphinx variables without having a full build system context. Good riddance. Since this is the first use of sub-contexts, we need to teach the emitter to not treat them like normal contexts. Since Sphinx data is handled by code under tools/docs, we just ignore them in the emitter. This is arguably not correct. But it is easier then teaching all downstream systems about sub-contexts.
browser/base/moz.build
browser/experiments/moz.build
browser/moz.build
build/moz.build
dom/bindings/moz.build
mobile/android/base/moz.build
python/moz.build
python/mozbuild/mozbuild/frontend/context.py
python/mozbuild/mozbuild/frontend/emitter.py
python/mozbuild/mozbuild/frontend/reader.py
services/cloudsync/moz.build
services/healthreport/moz.build
services/moz.build
toolkit/components/crashes/moz.build
toolkit/components/telemetry/moz.build
toolkit/crashreporter/moz.build
toolkit/modules/moz.build
tools/docs/index.rst
tools/docs/moztreedocs/__init__.py
--- a/browser/base/moz.build
+++ b/browser/base/moz.build
@@ -1,15 +1,16 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-SPHINX_TREES['sslerrorreport'] = 'content/docs/sslerrorreport'
+with Sphinx():
+    TREES['sslerrorreport'] = 'content/docs/sslerrorreport'
 
 TESTING_JS_MODULES += [
     'content/test/BrowserUITestUtils.jsm',
 ]
 
 MOCHITEST_MANIFESTS += [
     'content/test/general/mochitest.ini',
 ]
--- a/browser/experiments/moz.build
+++ b/browser/experiments/moz.build
@@ -10,9 +10,10 @@ EXTRA_COMPONENTS += [
 ]
 
 EXTRA_JS_MODULES.experiments += [
   'Experiments.jsm',
 ]
 
 XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
 
-SPHINX_TREES['experiments'] = 'docs'
+with Sphinx():
+    TREES['experiments'] = 'docs'
--- a/browser/moz.build
+++ b/browser/moz.build
@@ -1,17 +1,18 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 CONFIGURE_SUBST_FILES += ['installer/Makefile']
 
-SPHINX_TREES['browser'] = 'docs'
+with Sphinx():
+    TREES['browser'] = 'docs'
 
 DIRS += [
     'base',
     'components',
     'experiments',
     'fuel',
     'locales',
     'modules',
--- a/build/moz.build
+++ b/build/moz.build
@@ -1,16 +1,17 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-# This cannot be named "build" because of bug 922191.
-SPHINX_TREES['buildsystem'] = 'docs'
+with Sphinx():
+    # This cannot be named "build" because of bug 922191.
+    TREES['buildsystem'] = 'docs'
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     DIRS += ['win32']
 else:
     DIRS += ['unix']
 
 if CONFIG['OS_TARGET'] == 'Android' and not CONFIG['MOZ_ANDROID_LIBSTDCXX']:
     DIRS += ['stlport']
--- a/dom/bindings/moz.build
+++ b/dom/bindings/moz.build
@@ -86,18 +86,19 @@ include('/ipc/chromium/chromium-config.m
 
 if CONFIG['MOZ_AUDIO_CHANNEL_MANAGER']:
     LOCAL_INCLUDES += [
         '/dom/system/gonk',
     ]
 
 FINAL_LIBRARY = 'xul'
 
-SPHINX_TREES['webidl'] = 'docs'
-SPHINX_PYTHON_PACKAGE_DIRS += ['mozwebidlcodegen']
+with Sphinx():
+    TREES['webidl'] = 'docs'
+    PYTHON_PACKAGE_DIRS += ['mozwebidlcodegen']
 
 if CONFIG['MOZ_BUILD_APP'] in ['browser', 'mobile/android', 'xulrunner']:
     # This is needed for Window.webidl
     DEFINES['HAVE_SIDEBAR'] = True
 
 PYTHON_UNIT_TESTS += [
     'mozwebidlcodegen/test/test_mozwebidlcodegen.py',
 ]
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -1,16 +1,18 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DIRS += ['locales']
-SPHINX_TREES['fennec'] = 'docs'
+
+with Sphinx():
+    TREES['fennec'] = 'docs'
 
 include('android-services.mozbuild')
 
 thirdparty_source_dir = TOPSRCDIR + '/mobile/android/thirdparty/'
 
 constants_jar = add_java_jar('constants')
 constants_jar.sources = [
     'SysInfo.java',
--- a/python/moz.build
+++ b/python/moz.build
@@ -1,22 +1,22 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-SPHINX_PYTHON_PACKAGE_DIRS += [
-    'mach',
-    'mozbuild/mozbuild',
-    'mozbuild/mozpack',
-    'mozversioncontrol/mozversioncontrol',
-]
-
-SPHINX_TREES['mach'] = 'mach/docs'
+with Sphinx():
+    TREES['mach'] = 'mach/docs'
+    PYTHON_PACKAGE_DIRS += [
+        'mach',
+        'mozbuild/mozbuild',
+        'mozbuild/mozpack',
+        'mozversioncontrol/mozversioncontrol',
+    ]
 
 PYTHON_UNIT_TESTS += [
     'mach/mach/test/__init__.py',
     'mach/mach/test/common.py',
     'mach/mach/test/test_conditions.py',
     'mach/mach/test/test_config.py',
     'mach/mach/test/test_entry_point.py',
     'mach/mach/test/test_error_output.py',
--- a/python/mozbuild/mozbuild/frontend/context.py
+++ b/python/mozbuild/mozbuild/frontend/context.py
@@ -391,25 +391,46 @@ def ContextDerivedTypedList(type, base_c
                 def __new__(cls, obj):
                     return type(context, obj)
             self.TYPE = _Type
             super(_TypedList, self).__init__(iterable)
 
     return _TypedList
 
 
+class Sphinx(SubContext):
+    """Describes how Sphinx documentation should be built.
+    """
+
+    VARIABLES = {
+        'TREES': (dict, dict,
+            """Describes what the Sphinx documentation tree will look like.
+
+            Keys are relative directories inside the final Sphinx documentation
+            tree to install files into. Values are directories (relative to
+            this file) whose content to copy into the Sphinx documentation
+            tree.
+            """, None),
+
+        'PYTHON_PACKAGE_DIRS': (StrictOrderingOnAppendList, list,
+            """Directories containing Python packages that Sphinx documents.
+            """, None),
+    }
+
+
 # This defines functions that create sub-contexts.
 #
 # Values are classes that are SubContexts. The class name will be turned into
 # a function that when called emits an instance of that class.
 #
 # Arbitrary arguments can be passed to the class constructor. The first
 # argument is always the parent context. It is up to each class to perform
 # argument validation.
 SUBCONTEXTS = [
+    Sphinx,
 ]
 
 for cls in SUBCONTEXTS:
     if not issubclass(cls, SubContext):
         raise ValueError('SUBCONTEXTS entry not a SubContext class: %s' % cls)
 
     if not hasattr(cls, 'VARIABLES'):
         raise ValueError('SUBCONTEXTS entry does not have VARIABLES: %s' % cls)
@@ -1150,28 +1171,16 @@ VARIABLES = {
             GYP_DIRS['foo'].input = 'foo/foo.gyp'
             GYP_DIRS['foo'].variables = {
                 'foo': 'bar',
                 (...)
             }
             (...)
         """, None),
 
-    'SPHINX_TREES': (dict, dict,
-        """Describes what the Sphinx documentation tree will look like.
-
-        Keys are relative directories inside the final Sphinx documentation
-        tree to install files into. Values are directories (relative to this
-        file) whose content to copy into the Sphinx documentation tree.
-        """, None),
-
-    'SPHINX_PYTHON_PACKAGE_DIRS': (StrictOrderingOnAppendList, list,
-        """Directories containing Python packages that Sphinx documents.
-        """, None),
-
     'CFLAGS': (List, list,
         """Flags passed to the C compiler for all of the C source files
            declared in this directory.
 
            Note that the ordering of flags matters here, these flags will be
            added to the compiler's command line in the same order as they
            appear in the moz.build file.
         """, None),
--- a/python/mozbuild/mozbuild/frontend/emitter.py
+++ b/python/mozbuild/mozbuild/frontend/emitter.py
@@ -69,17 +69,20 @@ from .data import (
     UnifiedSources,
     VariablePassthru,
     WebIDLFile,
     XPIDLFile,
 )
 
 from .reader import SandboxValidationError
 
-from .context import Context
+from .context import
+    Context,
+    SubContext,
+)
 
 
 class TreeMetadataEmitter(LoggingMixin):
     """Converts the executed mozbuild files into data structures.
 
     This is a bridge between reader.py and data.py. It takes what was read by
     reader.BuildReader and converts it into the classes defined in the data
     module.
@@ -130,16 +133,21 @@ class TreeMetadataEmitter(LoggingMixin):
 
         def emit_objs(objs):
             for o in objs:
                 yield o
                 if not o._ack:
                     raise Exception('Unhandled object of type %s' % type(o))
 
         for out in output:
+            # Nothing in sub-contexts is currently of interest to us. Filter
+            # them all out.
+            if isinstance(out, SubContext):
+                continue
+
             if isinstance(out, Context):
                 # Keep all contexts around, we will need them later.
                 contexts[out.objdir] = out
 
                 start = time.time()
                 # We need to expand the generator for the timings to work.
                 objs = list(self.emit_from_context(out))
                 emitter_time += time.time() - start
--- a/python/mozbuild/mozbuild/frontend/reader.py
+++ b/python/mozbuild/mozbuild/frontend/reader.py
@@ -13,17 +13,16 @@ this file, which is represented by the S
 to fill a Context, representing the output of an individual mozbuild file. The
 
 The BuildReader contains basic logic for traversing a tree of mozbuild files.
 It does this by examining specific variables populated during execution.
 """
 
 from __future__ import print_function, unicode_literals
 
-import ast
 import inspect
 import logging
 import os
 import sys
 import textwrap
 import time
 import tokenize
 import traceback
@@ -818,124 +817,16 @@ class BuildReader(object):
             ignore=ignore)
 
         # The root doesn't get picked up by FileFinder.
         yield 'moz.build'
 
         for path, f in finder.find('**/moz.build'):
             yield path
 
-    def find_sphinx_variables(self):
-        """This function finds all assignments of Sphinx documentation variables.
-
-        This is a generator of tuples of (moz.build path, var, key, value). For
-        variables that assign to keys in objects, key will be defined.
-
-        With a little work, this function could be made more generic. But if we
-        end up writing a lot of ast code, it might be best to import a
-        high-level AST manipulation library into the tree.
-        """
-        # This function looks for assignments to SPHINX_TREES and
-        # SPHINX_PYTHON_PACKAGE_DIRS variables.
-        #
-        # SPHINX_TREES is a dict. Keys and values should both be strings. The
-        # target of the assignment should be a Subscript node. The value
-        # assigned should be a Str node. e.g.
-        #
-        #  SPHINX_TREES['foo'] = 'bar'
-        #
-        # This is an Assign node with a Subscript target. The Subscript's value
-        # is a Name node with id "SPHINX_TREES." The slice of this target
-        # is an Index node and its value is a Str with value "foo."
-        #
-        # SPHINX_PYTHON_PACKAGE_DIRS is a simple list. The target of the
-        # assignment should be a Name node. Values should be a List node, whose
-        # elements are Str nodes. e.g.
-        #
-        #  SPHINX_PYTHON_PACKAGE_DIRS += ['foo']
-        #
-        # This is an AugAssign node with a Name target with id
-        # "SPHINX_PYTHON_PACKAGE_DIRS." The value is a List node containing 1
-        # Str elt whose value is "foo."
-        relevant = [
-            'SPHINX_TREES',
-            'SPHINX_PYTHON_PACKAGE_DIRS',
-        ]
-
-        def assigned_variable(node):
-            # This is not correct, but we don't care yet.
-            if hasattr(node, 'targets'):
-                # Nothing in moz.build does multi-assignment (yet). So error if
-                # we see it.
-                assert len(node.targets) == 1
-
-                target = node.targets[0]
-            else:
-                target = node.target
-
-            if isinstance(target, ast.Subscript):
-                if not isinstance(target.value, ast.Name):
-                    return None, None
-                name = target.value.id
-            elif isinstance(target, ast.Name):
-                name = target.id
-            else:
-                return None, None
-
-            if name not in relevant:
-                return None, None
-
-            key = None
-            if isinstance(target, ast.Subscript):
-                assert isinstance(target.slice, ast.Index)
-                assert isinstance(target.slice.value, ast.Str)
-                key = target.slice.value.s
-
-            return name, key
-
-        def assigned_values(node):
-            value = node.value
-            if isinstance(value, ast.List):
-                for v in value.elts:
-                    assert isinstance(v, ast.Str)
-                    yield v.s
-            else:
-                assert isinstance(value, ast.Str)
-                yield value.s
-
-        assignments = []
-
-        class Visitor(ast.NodeVisitor):
-            def helper(self, node):
-                name, key = assigned_variable(node)
-                if not name:
-                    return
-
-                for v in assigned_values(node):
-                    assignments.append((name, key, v))
-
-            def visit_Assign(self, node):
-                self.helper(node)
-
-            def visit_AugAssign(self, node):
-                self.helper(node)
-
-        for p in self.all_mozbuild_paths():
-            assignments[:] = []
-            full = os.path.join(self.config.topsrcdir, p)
-
-            with open(full, 'rb') as fh:
-                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, read_tiers=False, descend=True,
                       metadata={}):
         """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
--- a/services/cloudsync/moz.build
+++ b/services/cloudsync/moz.build
@@ -1,15 +1,16 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-SPHINX_TREES['cloudsync'] = 'docs'
+with Sphinx():
+    TREES['cloudsync'] = 'docs'
 
 EXTRA_JS_MODULES += [
   'CloudSync.jsm',
   'CloudSyncAdapters.jsm',
   'CloudSyncBookmarks.jsm',
   'CloudSyncBookmarksFolderCache.jsm',
   'CloudSyncEventSource.jsm',
   'CloudSyncLocal.jsm',
--- a/services/healthreport/moz.build
+++ b/services/healthreport/moz.build
@@ -1,15 +1,16 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-SPHINX_TREES['healthreport'] = 'docs'
+with Sphinx():
+    TREES['healthreport'] = 'docs'
 
 XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
 
 EXTRA_PP_COMPONENTS += [
     'HealthReportComponents.manifest',
 ]
 
 EXTRA_PP_JS_MODULES += [
--- a/services/moz.build
+++ b/services/moz.build
@@ -26,9 +26,10 @@ if CONFIG['MOZ_SERVICES_SYNC']:
     DIRS += ['sync']
 
 if CONFIG['MOZ_B2G']:
     DIRS += ['mobileid']
 
 if CONFIG['MOZ_SERVICES_CLOUDSYNC']:
     DIRS += ['cloudsync']
 
-SPHINX_TREES['services'] = 'docs'
+with Sphinx():
+    TREES['services'] = 'docs'
--- a/toolkit/components/crashes/moz.build
+++ b/toolkit/components/crashes/moz.build
@@ -1,15 +1,16 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-SPHINX_TREES['crash-manager'] = 'docs'
+with Sphinx():
+    TREES['crash-manager'] = 'docs'
 
 EXTRA_COMPONENTS += [
     'CrashService.js',
     'CrashService.manifest',
 ]
 
 EXTRA_JS_MODULES += [
     'CrashManager.jsm',
--- a/toolkit/components/telemetry/moz.build
+++ b/toolkit/components/telemetry/moz.build
@@ -55,9 +55,10 @@ GENERATED_FILES = [
 if CONFIG['MOZILLA_OFFICIAL']:
     DEFINES['MOZILLA_OFFICIAL'] = True
 
 LOCAL_INCLUDES += [
     '/xpcom/build',
     '/xpcom/threads',
 ]
 
-SPHINX_TREES['telemetry'] = 'docs'
+with Sphinx():
+    TREES['telemetry'] = 'docs'
--- a/toolkit/crashreporter/moz.build
+++ b/toolkit/crashreporter/moz.build
@@ -1,15 +1,16 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-SPHINX_TREES['crashreporter'] = 'docs'
+with Sphinx():
+    TREES['crashreporter'] = 'docs'
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     DIRS += [
     'google-breakpad/src/common',
     'google-breakpad/src/processor',
     'breakpad-windows-libxul'
     ]
 
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -4,17 +4,18 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
 BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
 MOCHITEST_MANIFESTS += ['tests/mochitest/mochitest.ini']
 MOCHITEST_CHROME_MANIFESTS += ['tests/chrome/chrome.ini']
 
-SPHINX_TREES['toolkit_modules'] = 'docs'
+with Sphinx():
+    TREES['toolkit_modules'] = 'docs'
 
 EXTRA_JS_MODULES += [
     'Battery.jsm',
     'BinarySearch.jsm',
     'BrowserUtils.jsm',
     'CharsetMenu.jsm',
     'debug.js',
     'DeferredTask.jsm',
--- a/tools/docs/index.rst
+++ b/tools/docs/index.rst
@@ -22,37 +22,34 @@ This documentation is generated via the
 `Sphinx <http://sphinx-doc.org/>`_ tool from sources in the tree.
 
 To build the documentation, run ``mach build-docs``. Run
 ``mach help build-docs`` to see configurable options.
 
 Adding Documentation
 --------------------
 
-To add new documentation, define the ``SPHINX_TREES`` and
-``SPHINX_PYTHON_PACKAGE_DIRS`` variables in ``moz.build`` files in
-the tree and documentation will automatically get picked up.
+To add new documentation, the
+:ref:`Sphinx sub-context <mozbuild_subcontext_Sphinx>` should be used.
 
 Say you have a directory ``featureX`` you would like to write some
 documentation for. Here are the steps to create Sphinx documentation
 for it:
 
 1. Create a directory for the docs. This is typically ``docs``. e.g.
    ``featureX/docs``.
 2. Create an ``index.rst`` file in this directory. The ``index.rst`` file
    is the root documentation for that section. See ``build/docs/index.rst``
    for an example file.
-3. In a ``moz.build`` file (typically the one in the parent directory of
-   the ``docs`` directory), define ``SPHINX_TREES`` to hook up the plumbing.
-   e.g. ``SPHINX_TREES['featureX'] = 'docs'``. This says *the ``docs``
-   directory under the current directory should be installed into the
-   Sphinx documentation tree under ``/featureX``*.
-4. If you have Python packages you would like to generate Python API
-   documentation for, you can use ``SPHINX_PYTHON_PACKAGE_DIRS`` to
-   declare directories containing Python packages. e.g.
-   ``SPHINX_PYTHON_PACKAGE_DIRS += ['mozpackage']``.
+3. Add the following to your moz.build file::
+
+    with Sphinx():
+        TREES['featureX'] = 'docs'
+
+This says *the docs directory under the current directory should be
+installed into the Sphinx documentation tree under /featureX*.
 
 Indices and tables
 ==================
 
 * :ref:`genindex`
 * :ref:`modindex`
 * :ref:`search`
--- a/tools/docs/moztreedocs/__init__.py
+++ b/tools/docs/moztreedocs/__init__.py
@@ -1,17 +1,21 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import unicode_literals
 
 import os
 
-from mozbuild.frontend.reader import BuildReader
+from mozbuild.frontend.context import Sphinx
+from mozbuild.frontend.reader import (
+    EmptyConfig,
+    BuildReader,
+)
 from mozpack.copier import FileCopier
 from mozpack.files import FileFinder
 from mozpack.manifests import InstallManifest
 
 import sphinx
 import sphinx.apidoc
 
 
@@ -25,35 +29,30 @@ class SphinxManager(object):
         self._conf_py_path = os.path.join(main_path, 'conf.py')
         self._index_path = os.path.join(main_path, 'index.rst')
         self._trees = {}
         self._python_package_dirs = set()
 
     def read_build_config(self):
         """Read the active build config and add docs to this instance."""
 
-        # Reading the Sphinx variables doesn't require a full build context.
-        # Only define the parts we need.
-        class fakeconfig(object):
-            def __init__(self, topsrcdir):
-                self.topsrcdir = topsrcdir
-
-        config = fakeconfig(self._topsrcdir)
+        config = EmptyConfig(self._topsrcdir)
         reader = BuildReader(config)
 
-        for path, name, key, value in reader.find_sphinx_variables():
-            reldir = os.path.dirname(path)
+        paths, contexts = reader.read_relevant_mozbuilds(reader.all_mozbuild_paths())
+        for context in contexts:
+            if not isinstance(context, Sphinx):
+                continue
 
-            if name == 'SPHINX_TREES':
-                assert key
-                self.add_tree(os.path.join(reldir, value),
-                    os.path.join(reldir, key))
+            for tree, path in context['TREES'].items():
+                self.add_tree(os.path.join(context.relsrcdir, path),
+                              os.path.join(context.relsrcdir, tree))
 
-            if name == 'SPHINX_PYTHON_PACKAGE_DIRS':
-                self.add_python_package_dir(os.path.join(reldir, value))
+            for path in context['PYTHON_PACKAGE_DIRS']:
+                self.add_python_package_dir(os.path.join(context.relsrcdir, path))
 
     def add_tree(self, source_dir, dest_dir):
         """Add a directory from where docs should be sourced."""
         if dest_dir in self._trees:
             raise Exception('%s has already been registered as a destination.'
                 % dest_dir)
 
         self._trees[dest_dir] = source_dir