Bug 1454640 - [docs] Always build docs with the tools/docs/conf.py draft
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Wed, 18 Apr 2018 17:18:11 -0400
changeset 793775 3d7db5c7db49b75aa47c4de3ca4c6c1af8371de1
parent 793774 42b4ce8b70bd89f20216ad16d92b26fbeb9918b7
child 793776 80bd7d63ebb3f56e07e378a873f039f56779b691
push id109495
push userahalberstadt@mozilla.com
push dateThu, 10 May 2018 18:20:10 +0000
bugs1454640
milestone62.0a1
Bug 1454640 - [docs] Always build docs with the tools/docs/conf.py Previously, running |mach doc <subtree>| would use whatever conf.py file happened to live in the subtree. For example, running: ./mach doc tools/lint Would build with tools/lint/docs/conf.py. This is bad because it means the generated docs will look different from the docs that eventually will be published to firefox-source-docs.mozilla.com. This patch makes sure we always use tools/docs/conf.py for building, even when only generating a subtree. Furthermore, this sets things up such that when you modify a file, only the subtree containing the modified file will be re-generated. This cuts down rebuild times from ~2 minutes to ~20 seconds. There is one caveat. When rebuilding a subtree, the index of other trees will be overwritten in that particular subtree. I couldn't figure out anyway around this. This tradeoff for *much* faster rebuild times seems worth it. MozReview-Commit-ID: Ly88mvHKpo7
tools/docs/mach_commands.py
--- a/tools/docs/mach_commands.py
+++ b/tools/docs/mach_commands.py
@@ -19,16 +19,21 @@ from mozbuild.base import MachCommandBas
 
 here = os.path.abspath(os.path.dirname(__file__))
 
 
 @CommandProvider
 class Documentation(MachCommandBase):
     """Helps manage in-tree documentation."""
 
+    def __init__(self, context):
+        super(Documentation, self).__init__(context)
+
+        self._manager = None
+
     @Command('doc', category='devenv',
              description='Generate and serve documentation from the tree.')
     @CommandArgument('path', default=None, metavar='DIRECTORY', nargs='?',
                      help='Path to documentation to build and display.')
     @CommandArgument('--format', default='html', dest='fmt',
                      help='Documentation format to write.')
     @CommandArgument('--outdir', default=None, metavar='DESTINATION',
                      help='Where to write output.')
@@ -55,31 +60,28 @@ class Documentation(MachCommandBase):
         self.virtualenv_manager.install_pip_requirements(
             os.path.join(here, 'requirements.txt'), quiet=True)
 
         import webbrowser
         from livereload import Server
         from moztreedocs.package import create_tarball
 
         outdir = outdir or os.path.join(self.topobjdir, 'docs')
-        format_outdir = os.path.join(outdir, fmt)
+        savedir = os.path.join(outdir, fmt)
 
         path = path or os.path.join(self.topsrcdir, 'tools')
         path = os.path.normpath(os.path.abspath(path))
 
         docdir = self._find_doc_dir(path)
         if not docdir:
             return die('failed to generate documentation:\n'
                        '%s: could not find docs at this location' % path)
 
         props = self._project_properties(docdir)
-        savedir = os.path.join(format_outdir, props['project'])
-
-        run_sphinx = partial(self._run_sphinx, docdir, savedir, fmt)
-        result = run_sphinx()
+        result = self._run_sphinx(docdir, savedir, fmt=fmt)
         if result != 0:
             return die('failed to generate documentation:\n'
                        '%s: sphinx return code %d' % (path, result))
         else:
             print('\nGenerated documentation:\n%s' % savedir)
 
         if archive:
             archive_path = os.path.join(outdir,
@@ -100,53 +102,72 @@ class Documentation(MachCommandBase):
         # will cause a re-build and refresh of the browser (if open).
         try:
             host, port = http.split(':', 1)
             port = int(port)
         except ValueError:
             return die('invalid address: %s' % http)
 
         server = Server()
-        server.watch(docdir, run_sphinx)
+
+        sphinx_trees = self.manager.trees or {savedir: docdir}
+        for dest, src in sphinx_trees.items():
+            run_sphinx = partial(self._run_sphinx, src, savedir, fmt=fmt)
+            server.watch(src, run_sphinx)
         server.serve(host=host, port=port, root=savedir,
                      open_url_delay=0.1 if auto_open else None)
 
-    def _run_sphinx(self, docdir, savedir, fmt='html'):
+    def _run_sphinx(self, docdir, savedir, config=None, fmt='html'):
         import sphinx
+        config = config or self.manager.conf_py_path
         args = [
             'sphinx',
             '-b', fmt,
+            '-c', os.path.dirname(config),
             docdir,
             savedir,
         ]
         return sphinx.build_main(args)
 
+    @property
+    def manager(self):
+        if not self._manager:
+            from moztreedocs import manager
+            self._manager = manager
+        return self._manager
+
     def _project_properties(self, path):
         import imp
-        path = os.path.join(path, 'conf.py')
+        path = os.path.normpath(self.manager.conf_py_path)
         with open(path, 'r') as fh:
             conf = imp.load_module('doc_conf', fh, path,
                                    ('.py', 'r', imp.PY_SOURCE))
 
         # Prefer the Mozilla project name, falling back to Sphinx's
         # default variable if it isn't defined.
         project = getattr(conf, 'moz_project_name', None)
         if not project:
             project = conf.project.replace(' ', '_')
 
         return {
             'project': project,
             'version': getattr(conf, 'version', None)
         }
 
     def _find_doc_dir(self, path):
-        search_dirs = ('doc', 'docs')
-        for d in search_dirs:
+        if os.path.isfile(path):
+            return
+
+        valid_doc_dirs = ('doc', 'docs')
+        if os.path.basename(path) in valid_doc_dirs:
+            return path
+
+        for d in valid_doc_dirs:
             p = os.path.join(path, d)
-            if os.path.isfile(os.path.join(p, 'conf.py')):
+            if os.path.isdir(p):
                 return p
 
     def _s3_upload(self, root, project, version=None):
         self.virtualenv_manager.install_pip_package('boto3==1.4.4')
 
         from moztreedocs.package import distribution_files
         from moztreedocs.upload import s3_upload