mach_commands.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. # This Source Code Form is subject to the terms of the Mozilla Public
  2. # License, v. 2.0. If a copy of the MPL was not distributed with this
  3. # file, # You can obtain one at http://mozilla.org/MPL/2.0/.
  4. from __future__ import absolute_import, print_function, unicode_literals
  5. import os
  6. import sys
  7. from mach.decorators import (
  8. Command,
  9. CommandArgument,
  10. CommandProvider,
  11. )
  12. import mozhttpd
  13. from mozbuild.base import MachCommandBase
  14. @CommandProvider
  15. class Documentation(MachCommandBase):
  16. """Helps manage in-tree documentation."""
  17. @Command('doc', category='devenv',
  18. description='Generate and display documentation from the tree.')
  19. @CommandArgument('what', nargs='*', metavar='DIRECTORY [, DIRECTORY]',
  20. help='Path(s) to documentation to build and display.')
  21. @CommandArgument('--format', default='html',
  22. help='Documentation format to write.')
  23. @CommandArgument('--outdir', default=None, metavar='DESTINATION',
  24. help='Where to write output.')
  25. @CommandArgument('--no-open', dest='auto_open', default=True, action='store_false',
  26. help="Don't automatically open HTML docs in a browser.")
  27. @CommandArgument('--http', const=':6666', metavar='ADDRESS', nargs='?',
  28. help='Serve documentation on an HTTP server, e.g. ":6666".')
  29. def build_docs(self, what=None, format=None, outdir=None, auto_open=True, http=None):
  30. self._activate_virtualenv()
  31. self.virtualenv_manager.install_pip_package('sphinx_rtd_theme==0.1.6')
  32. import sphinx
  33. import webbrowser
  34. if not outdir:
  35. outdir = os.path.join(self.topobjdir, 'docs')
  36. if not what:
  37. what = [os.path.join(self.topsrcdir, 'tools')]
  38. outdir = os.path.join(outdir, format)
  39. generated = []
  40. failed = []
  41. for path in what:
  42. path = os.path.normpath(os.path.abspath(path))
  43. docdir = self._find_doc_dir(path)
  44. if not docdir:
  45. failed.append((path, 'could not find docs at this location'))
  46. continue
  47. # find project name to use as a namespace within `outdir`
  48. project = self._find_project_name(docdir)
  49. savedir = os.path.join(outdir, project)
  50. args = [
  51. 'sphinx',
  52. '-b', format,
  53. docdir,
  54. savedir,
  55. ]
  56. result = sphinx.build_main(args)
  57. if result != 0:
  58. failed.append((path, 'sphinx return code %d' % result))
  59. else:
  60. generated.append(savedir)
  61. index_path = os.path.join(savedir, 'index.html')
  62. if not http and auto_open and os.path.isfile(index_path):
  63. webbrowser.open(index_path)
  64. if generated:
  65. print('\nGenerated documentation:\n%s\n' % '\n'.join(generated))
  66. if failed:
  67. failed = ['%s: %s' % (f[0], f[1]) for f in failed]
  68. return die('failed to generate documentation:\n%s' % '\n'.join(failed))
  69. if http is not None:
  70. host, port = http.split(':', 1)
  71. addr = (host, int(port))
  72. if len(addr) != 2:
  73. return die('invalid address: %s' % http)
  74. httpd = mozhttpd.MozHttpd(host=addr[0], port=addr[1], docroot=outdir)
  75. print('listening on %s:%d' % addr)
  76. httpd.start(block=True)
  77. def _find_project_name(self, path):
  78. import imp
  79. path = os.path.join(path, 'conf.py')
  80. with open(path, 'r') as fh:
  81. conf = imp.load_module('doc_conf', fh, path,
  82. ('.py', 'r', imp.PY_SOURCE))
  83. return conf.project.replace(' ', '_')
  84. def _find_doc_dir(self, path):
  85. search_dirs = ('doc', 'docs')
  86. for d in search_dirs:
  87. p = os.path.join(path, d)
  88. if os.path.isfile(os.path.join(p, 'conf.py')):
  89. return p
  90. def die(msg, exit_code=1):
  91. msg = '%s: %s' % (sys.argv[0], msg)
  92. print(msg, file=sys.stderr)
  93. return exit_code