Bug 1454640 - [docs] Use a single SphinxManager instance across all rebuilds draft
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Wed, 18 Apr 2018 12:56:55 -0400
changeset 793772 dc543878c288deebc1961b52294813b4134420d9
parent 793771 33b25471b2a61d017b12dd42b82b482bcead2998
child 793773 dbdb155612fd89c3f0a57627ddefd8d4ae596226
push id109495
push userahalberstadt@mozilla.com
push dateThu, 10 May 2018 18:20:10 +0000
bugs1454640
milestone62.0a1
Bug 1454640 - [docs] Use a single SphinxManager instance across all rebuilds In the mozbuild.sphinx extension, we create a new SphinxManager instance each time. However this isn't ideal now that we can rebuild the docs within the same interpreter using the livereload server. This makes use of a singleton so that we can share state not only between multiple invocations of sphinx-build, but also with the mach command. This will be taken advantage of more heavily in future commits in this series. MozReview-Commit-ID: 7ERYeN5BPeI
python/mozbuild/mozbuild/sphinx.py
tools/docs/moztreedocs/__init__.py
--- a/python/mozbuild/mozbuild/sphinx.py
+++ b/python/mozbuild/mozbuild/sphinx.py
@@ -164,37 +164,32 @@ class MozbuildSymbols(Directive):
         # into the parser for conversion. We don't even emit ourselves, so
         # there's no record of us.
         self.state_machine.insert_input(format_module(module), fname)
 
         return []
 
 
 def setup(app):
+    from mozbuild.virtualenv import VirtualenvManager
+    from moztreedocs import manager
+
     app.add_directive('mozbuildsymbols', MozbuildSymbols)
 
     # Unlike typical Sphinx installs, our documentation is assembled from
     # many sources and staged in a common location. This arguably isn't a best
     # practice, but it was the easiest to implement at the time.
     #
     # Here, we invoke our custom code for staging/generating all our
     # documentation.
-    from moztreedocs import SphinxManager
-
-    topsrcdir = app.config._raw_config['topsrcdir']
-    manager = SphinxManager(topsrcdir,
-        os.path.join(topsrcdir, 'tools', 'docs'),
-        app.outdir)
     manager.generate_docs(app)
-
-    app.srcdir = os.path.join(app.outdir, '_staging')
+    app.srcdir = manager.staging_dir
 
     # We need to adjust sys.path in order for Python API docs to get generated
     # properly. We leverage the in-tree virtualenv for this.
-    from mozbuild.virtualenv import VirtualenvManager
-
+    topsrcdir = manager.topsrcdir
     ve = VirtualenvManager(topsrcdir,
         os.path.join(topsrcdir, 'dummy-objdir'),
         os.path.join(app.outdir, '_venv'),
         sys.stderr,
         os.path.join(topsrcdir, 'build', 'virtualenv_packages.txt'))
     ve.ensure()
     ve.activate()
--- a/tools/docs/moztreedocs/__init__.py
+++ b/tools/docs/moztreedocs/__init__.py
@@ -1,52 +1,59 @@
 # 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.base import MozbuildObject
 from mozbuild.frontend.reader import BuildReader
 from mozpack.copier import FileCopier
 from mozpack.files import FileFinder
 from mozpack.manifests import InstallManifest
 
 import sphinx
 import sphinx.apidoc
 
+here = os.path.abspath(os.path.dirname(__file__))
+build = MozbuildObject.from_environment(cwd=here)
 
-class SphinxManager(object):
+MAIN_DOC_PATH = os.path.join(build.topsrcdir, 'tools', 'docs')
+
+
+class _SphinxManager(object):
     """Manages the generation of Sphinx documentation for the tree."""
 
-    def __init__(self, topsrcdir, main_path, output_dir):
-        self._topsrcdir = topsrcdir
-        self._output_dir = output_dir
-        self._docs_dir = os.path.join(output_dir, '_staging')
-        self._conf_py_path = os.path.join(main_path, 'conf.py')
-        self._index_path = os.path.join(main_path, 'index.rst')
+    def __init__(self, topsrcdir, main_path):
+        self.topsrcdir = topsrcdir
+        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()
 
+        # Instance variables that get set in self.generate_docs()
+        self.staging_dir = None
+
     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 = fakeconfig(self.topsrcdir)
         reader = BuildReader(config)
 
         for path, name, key, value in reader.find_sphinx_variables():
             reldir = os.path.dirname(path)
-
             if name == 'SPHINX_TREES':
                 assert key
                 if key.startswith('/'):
                     key = key[1:]
                 else:
                     key = os.path.join(reldir, key)
                 self.add_tree(os.path.join(reldir, value), key)
 
@@ -65,65 +72,70 @@ class SphinxManager(object):
         """Add a directory containing Python packages.
 
         Added directories will have Python API docs generated automatically.
         """
         self._python_package_dirs.add(source_dir)
 
     def generate_docs(self, app):
         """Generate/stage documentation."""
+        self.staging_dir = os.path.join(app.outdir, '_staging')
+
         app.info('Reading Sphinx metadata from build configuration')
         self.read_build_config()
         app.info('Staging static documentation')
         self._synchronize_docs()
         app.info('Generating Python API documentation')
         self._generate_python_api_docs()
 
     def _generate_python_api_docs(self):
         """Generate Python API doc files."""
-        out_dir = os.path.join(self._docs_dir, 'python')
+        out_dir = os.path.join(self.staging_dir, 'python')
         base_args = ['sphinx', '--no-toc', '-o', out_dir]
 
         for p in sorted(self._python_package_dirs):
-            full = os.path.join(self._topsrcdir, p)
+            full = os.path.join(self.topsrcdir, p)
 
             finder = FileFinder(full)
             dirs = {os.path.dirname(f[0]) for f in finder.find('**')}
 
             excludes = {d for d in dirs if d.endswith('test')}
 
             args = list(base_args)
             args.append(full)
             args.extend(excludes)
 
             sphinx.apidoc.main(args)
 
     def _synchronize_docs(self):
         m = InstallManifest()
 
-        m.add_link(self._conf_py_path, 'conf.py')
+        m.add_link(self.conf_py_path, 'conf.py')
 
         for dest, source in sorted(self._trees.items()):
-            source_dir = os.path.join(self._topsrcdir, source)
+            source_dir = os.path.join(self.topsrcdir, source)
             for root, dirs, files in os.walk(source_dir):
                 for f in files:
                     source_path = os.path.join(root, f)
                     rel_source = source_path[len(source_dir) + 1:]
 
                     m.add_link(source_path, os.path.join(dest, rel_source))
 
         copier = FileCopier()
         m.populate_registry(copier)
-        copier.copy(self._docs_dir)
+        copier.copy(self.staging_dir)
 
-        with open(self._index_path, 'rb') as fh:
+        with open(self.index_path, 'rb') as fh:
             data = fh.read()
 
         indexes = ['%s/index' % p for p in sorted(self._trees.keys())]
         indexes = '\n   '.join(indexes)
 
         packages = [os.path.basename(p) for p in self._python_package_dirs]
         packages = ['python/%s' % p for p in packages]
         packages = '\n   '.join(sorted(packages))
         data = data.format(indexes=indexes, python_packages=packages)
 
-        with open(os.path.join(self._docs_dir, 'index.rst'), 'wb') as fh:
+        with open(os.path.join(self.staging_dir, 'index.rst'), 'wb') as fh:
             fh.write(data)
+
+
+manager = _SphinxManager(build.topsrcdir, MAIN_DOC_PATH)