diff options
Diffstat (limited to 'tools/docs')
-rw-r--r-- | tools/docs/Vagrantfile | 13 | ||||
-rw-r--r-- | tools/docs/conf.py | 83 | ||||
-rw-r--r-- | tools/docs/index.rst | 58 | ||||
-rw-r--r-- | tools/docs/mach_commands.py | 117 | ||||
-rw-r--r-- | tools/docs/moztreedocs/__init__.py | 126 |
5 files changed, 397 insertions, 0 deletions
diff --git a/tools/docs/Vagrantfile b/tools/docs/Vagrantfile new file mode 100644 index 000000000..247afe1b3 --- /dev/null +++ b/tools/docs/Vagrantfile @@ -0,0 +1,13 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# We intentionally use the old config format because Mozilla's Jenkins +# server doesn't run a modern Vagrant. +Vagrant::Config.run do |config| + config.vm.box = "precise64" + config.vm.box_url = "http://files.vagrantup.com/precise64.box" + config.vm.share_folder("gecko", "/gecko", "../..") + # Doxygen needs more than the default memory or it will swap and be + # extremely slow. + config.vm.customize ["modifyvm", :id, "--memory", 2048] +end diff --git a/tools/docs/conf.py b/tools/docs/conf.py new file mode 100644 index 000000000..38cea035a --- /dev/null +++ b/tools/docs/conf.py @@ -0,0 +1,83 @@ +# 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 +import re +import sys + +from datetime import datetime + +# Set up Python environment to load build system packages. +OUR_DIR = os.path.dirname(__file__) +topsrcdir = os.path.normpath(os.path.join(OUR_DIR, '..', '..')) + +EXTRA_PATHS = ( + 'layout/tools/reftest', + 'python/futures', + 'python/jsmin', + 'python/mach', + 'python/mozbuild', + 'python/mozversioncontrol', + 'python/which', + 'testing/mozbase/manifestparser', + 'testing/mozbase/mozfile', + 'testing/mozbase/mozprocess', +) + +sys.path[:0] = [os.path.join(topsrcdir, p) for p in EXTRA_PATHS] + +sys.path.insert(0, OUR_DIR) + +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.graphviz', + 'sphinx.ext.todo', + 'mozbuild.sphinx', +] + +templates_path = ['_templates'] +source_suffix = '.rst' +master_doc = 'index' +project = u'Mozilla Source Tree Docs' +year = datetime.now().year + +# Grab the version from the source tree's milestone. +# FUTURE Use Python API from bug 941299. +with open(os.path.join(topsrcdir, 'config', 'milestone.txt'), 'rt') as fh: + for line in fh: + line = line.strip() + + if not line or line.startswith('#'): + continue + + release = line + break + +version = re.sub(r'[ab]\d+$', '', release) + +exclude_patterns = ['_build', '_staging', '_venv'] +pygments_style = 'sphinx' + +# We need to perform some adjustment of the settings and environment +# when running on Read The Docs. +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' + +if on_rtd: + # SHELL isn't set on RTD and mach.mixin.process's import raises if a + # shell-related environment variable can't be found. Set the variable here + # to hack us into working on RTD. + assert 'SHELL' not in os.environ + os.environ['SHELL'] = '/bin/bash' +else: + # We only need to set the RTD theme when not on RTD because the RTD + # environment handles this otherwise. + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + + +html_static_path = ['_static'] +htmlhelp_basename = 'MozillaTreeDocs' diff --git a/tools/docs/index.rst b/tools/docs/index.rst new file mode 100644 index 000000000..ae2a9d593 --- /dev/null +++ b/tools/docs/index.rst @@ -0,0 +1,58 @@ +================================= +Mozilla Source Tree Documentation +================================= + +.. toctree:: + :maxdepth: 1 + + {indexes} + +Python Packages +=============== + +.. toctree:: + :maxdepth: 2 + + {python_packages} + +Managing Documentation +====================== + +This documentation is generated via the +`Sphinx <http://sphinx-doc.org/>`_ tool from sources in the tree. + +To build the documentation, run ``mach doc``. Run +``mach help doc`` 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. + +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']``. + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/tools/docs/mach_commands.py b/tools/docs/mach_commands.py new file mode 100644 index 000000000..825e5ec38 --- /dev/null +++ b/tools/docs/mach_commands.py @@ -0,0 +1,117 @@ +# 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 absolute_import, print_function, unicode_literals + +import os +import sys + +from mach.decorators import ( + Command, + CommandArgument, + CommandProvider, +) + +import mozhttpd + +from mozbuild.base import MachCommandBase + + +@CommandProvider +class Documentation(MachCommandBase): + """Helps manage in-tree documentation.""" + + @Command('doc', category='devenv', + description='Generate and display documentation from the tree.') + @CommandArgument('what', nargs='*', metavar='DIRECTORY [, DIRECTORY]', + help='Path(s) to documentation to build and display.') + @CommandArgument('--format', default='html', + help='Documentation format to write.') + @CommandArgument('--outdir', default=None, metavar='DESTINATION', + help='Where to write output.') + @CommandArgument('--no-open', dest='auto_open', default=True, action='store_false', + help="Don't automatically open HTML docs in a browser.") + @CommandArgument('--http', const=':6666', metavar='ADDRESS', nargs='?', + help='Serve documentation on an HTTP server, e.g. ":6666".') + def build_docs(self, what=None, format=None, outdir=None, auto_open=True, http=None): + self._activate_virtualenv() + self.virtualenv_manager.install_pip_package('sphinx_rtd_theme==0.1.6') + + import sphinx + import webbrowser + + if not outdir: + outdir = os.path.join(self.topobjdir, 'docs') + if not what: + what = [os.path.join(self.topsrcdir, 'tools')] + outdir = os.path.join(outdir, format) + + generated = [] + failed = [] + for path in what: + path = os.path.normpath(os.path.abspath(path)) + docdir = self._find_doc_dir(path) + + if not docdir: + failed.append((path, 'could not find docs at this location')) + continue + + # find project name to use as a namespace within `outdir` + project = self._find_project_name(docdir) + savedir = os.path.join(outdir, project) + + args = [ + 'sphinx', + '-b', format, + docdir, + savedir, + ] + result = sphinx.build_main(args) + if result != 0: + failed.append((path, 'sphinx return code %d' % result)) + else: + generated.append(savedir) + + index_path = os.path.join(savedir, 'index.html') + if not http and auto_open and os.path.isfile(index_path): + webbrowser.open(index_path) + + if generated: + print('\nGenerated documentation:\n%s\n' % '\n'.join(generated)) + + if failed: + failed = ['%s: %s' % (f[0], f[1]) for f in failed] + return die('failed to generate documentation:\n%s' % '\n'.join(failed)) + + if http is not None: + host, port = http.split(':', 1) + addr = (host, int(port)) + if len(addr) != 2: + return die('invalid address: %s' % http) + + httpd = mozhttpd.MozHttpd(host=addr[0], port=addr[1], docroot=outdir) + print('listening on %s:%d' % addr) + httpd.start(block=True) + + def _find_project_name(self, path): + import imp + path = os.path.join(path, 'conf.py') + with open(path, 'r') as fh: + conf = imp.load_module('doc_conf', fh, path, + ('.py', 'r', imp.PY_SOURCE)) + + return conf.project.replace(' ', '_') + + def _find_doc_dir(self, path): + search_dirs = ('doc', 'docs') + for d in search_dirs: + p = os.path.join(path, d) + if os.path.isfile(os.path.join(p, 'conf.py')): + return p + + +def die(msg, exit_code=1): + msg = '%s: %s' % (sys.argv[0], msg) + print(msg, file=sys.stderr) + return exit_code diff --git a/tools/docs/moztreedocs/__init__.py b/tools/docs/moztreedocs/__init__.py new file mode 100644 index 000000000..a67edbded --- /dev/null +++ b/tools/docs/moztreedocs/__init__.py @@ -0,0 +1,126 @@ +# 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 mozpack.copier import FileCopier +from mozpack.files import FileFinder +from mozpack.manifests import InstallManifest + +import sphinx +import sphinx.apidoc + + +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') + 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) + reader = BuildReader(config) + + for path, name, key, value in reader.find_sphinx_variables(): + reldir = os.path.dirname(path) + + if name == 'SPHINX_TREES': + assert key + self.add_tree(os.path.join(reldir, value), + os.path.join(reldir, key)) + + if name == 'SPHINX_PYTHON_PACKAGE_DIRS': + self.add_python_package_dir(os.path.join(reldir, value)) + + 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 + + def add_python_package_dir(self, source_dir): + """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.""" + 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') + base_args = ['sphinx', '--no-toc', '-o', out_dir] + + for p in sorted(self._python_package_dirs): + full = os.path.join(self._topsrcdir, p) + + finder = FileFinder(full, find_executables=False) + 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_symlink(self._conf_py_path, 'conf.py') + + for dest, source in sorted(self._trees.items()): + 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_symlink(source_path, os.path.join(dest, rel_source)) + + copier = FileCopier() + m.populate_registry(copier) + copier.copy(self._docs_dir) + + 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: + fh.write(data) |