1 # Copyright 2011 OpenStack Foundation
2 # Copyright 2012-2013 Hewlett-Packard Development Company, L.P.
5 # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 # not use this file except in compliance with the License. You may obtain
7 # a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 # License for the specific language governing permissions and limitations
17 from distutils import log
25 import io as cStringIO
29 # NOTE(dhellmann): Newer versions of Sphinx have moved the apidoc
30 # module into sphinx.ext and the API is slightly different (the
31 # function expects sys.argv[1:] instead of sys.argv[:]. So, figure
32 # out where we can import it from and set a flag so we can invoke
33 # it properly. See this change in sphinx for details:
34 # https://github.com/sphinx-doc/sphinx/commit/87630c8ae8bff8c0e23187676e6343d8903003a6
36 from sphinx.ext import apidoc
37 apidoc_use_padding = False
39 from sphinx import apidoc
40 apidoc_use_padding = True
41 from sphinx import application
42 from sphinx import setup_command
43 except Exception as e:
44 # NOTE(dhellmann): During the installation of docutils, setuptools
45 # tries to import pbr code to find the egg_info.writer hooks. That
46 # imports this module, which imports sphinx, which imports
47 # docutils, which is being installed. Because docutils uses 2to3
48 # to convert its code during installation under python 3, the
49 # import fails, but it fails with an error other than ImportError
50 # (today it's a NameError on StandardError, an exception base
51 # class). Convert the exception type here so it can be caught in
52 # packaging.py where we try to determine if we can import and use
53 # sphinx by importing this module. See bug #1403510 for details.
54 raise ImportError(str(e))
56 from pbr import options
57 from pbr import version
60 _deprecated_options = ['autodoc_tree_index_modules', 'autodoc_index_modules',
61 'autodoc_tree_excludes', 'autodoc_exclude_modules']
62 _deprecated_envs = ['AUTODOC_TREE_INDEX_MODULES', 'AUTODOC_INDEX_MODULES']
63 _rst_template = """%(heading)s
66 .. automodule:: %(module)s
73 def _find_modules(arg, dirname, files):
74 for filename in files:
75 if filename.endswith('.py') and filename != '__init__.py':
76 arg["%s.%s" % (dirname.replace('/', '.'),
77 filename[:-3])] = True
80 class LocalBuildDoc(setup_command.BuildDoc):
83 command_name = 'build_sphinx'
84 sphinx_initialized = False
86 def _get_source_dir(self):
87 option_dict = self.distribution.get_option_dict('build_sphinx')
88 pbr_option_dict = self.distribution.get_option_dict('pbr')
89 _, api_doc_dir = pbr_option_dict.get('api_doc_dir', (None, 'api'))
90 if 'source_dir' in option_dict:
91 source_dir = os.path.join(option_dict['source_dir'][1],
94 source_dir = 'doc/source/' + api_doc_dir
95 if not os.path.exists(source_dir):
96 os.makedirs(source_dir)
99 def generate_autoindex(self, excluded_modules=None):
100 log.info("[pbr] Autodocumenting from %s"
101 % os.path.abspath(os.curdir))
103 source_dir = self._get_source_dir()
104 for pkg in self.distribution.packages:
106 for dirpath, dirnames, files in os.walk(pkg):
107 _find_modules(modules, dirpath, files)
110 return not any(fnmatch.fnmatch(module, pat)
111 for pat in excluded_modules)
113 module_list = sorted(mod for mod in modules.keys() if include(mod))
114 autoindex_filename = os.path.join(source_dir, 'autoindex.rst')
115 with open(autoindex_filename, 'w') as autoindex:
116 autoindex.write(""".. toctree::
120 for module in module_list:
121 output_filename = os.path.join(source_dir,
123 heading = "The :mod:`%s` Module" % module
124 underline = "=" * len(heading)
125 values = dict(module=module, heading=heading,
128 log.info("[pbr] Generating %s"
130 with open(output_filename, 'w') as output_file:
131 output_file.write(_rst_template % values)
132 autoindex.write(" %s.rst\n" % module)
134 def _sphinx_tree(self):
135 source_dir = self._get_source_dir()
136 cmd = ['-H', 'Modules', '-o', source_dir, '.']
137 if apidoc_use_padding:
138 cmd.insert(0, 'apidoc')
139 apidoc.main(cmd + self.autodoc_tree_excludes)
141 def _sphinx_run(self):
143 status_stream = cStringIO.StringIO()
145 status_stream = sys.stdout
148 confoverrides['project'] = self.project
150 confoverrides['version'] = self.version
152 confoverrides['release'] = self.release
154 confoverrides['today'] = self.today
155 if self.sphinx_initialized:
156 confoverrides['suppress_warnings'] = [
157 'app.add_directive', 'app.add_role',
158 'app.add_generic_role', 'app.add_node',
159 'image.nonlocal_uri',
161 app = application.Sphinx(
162 self.source_dir, self.config_dir,
163 self.builder_target_dir, self.doctree_dir,
164 self.builder, confoverrides, status_stream,
165 freshenv=self.fresh_env, warningiserror=self.warning_is_error)
166 self.sphinx_initialized = True
169 app.build(force_all=self.all_files)
170 except Exception as err:
171 from docutils import utils
172 if isinstance(err, utils.SystemMessage):
173 sys.stder.write('reST markup error:\n')
174 sys.stderr.write(err.args[0].encode('ascii',
176 sys.stderr.write('\n')
181 src = app.config.master_doc + app.builder.out_suffix
182 dst = app.builder.get_outfilename('index')
186 option_dict = self.distribution.get_option_dict('pbr')
188 # TODO(stephenfin): Remove this (and the entire file) when 5.0 is
190 warn_opts = set(option_dict.keys()).intersection(_deprecated_options)
191 warn_env = list(filter(lambda x: os.getenv(x), _deprecated_envs))
192 if warn_opts or warn_env:
193 msg = ('The autodoc and autodoc_tree features are deprecated in '
194 '4.2 and will be removed in a future release. You should '
195 'use the sphinxcontrib-apidoc Sphinx extension instead. '
196 'Refer to the pbr documentation for more information.')
198 msg += ' Deprecated options: %s' % list(warn_opts)
200 msg += ' Deprecated environment variables: %s' % warn_env
204 if git._git_is_installed():
205 git.write_git_changelog(option_dict=option_dict)
206 git.generate_authors(option_dict=option_dict)
207 tree_index = options.get_boolean_option(option_dict,
208 'autodoc_tree_index_modules',
209 'AUTODOC_TREE_INDEX_MODULES')
210 auto_index = options.get_boolean_option(option_dict,
211 'autodoc_index_modules',
212 'AUTODOC_INDEX_MODULES')
213 if not os.getenv('SPHINX_DEBUG'):
214 # NOTE(afazekas): These options can be used together,
215 # but they do a very similar thing in a different way
219 self.generate_autoindex(
221 "autodoc_exclude_modules",
222 [None, ""])[1].split()))
224 self.finalize_options()
226 is_multibuilder_sphinx = version.SemanticVersion.from_pip_string(
227 sphinx.__version__) >= version.SemanticVersion(1, 6)
229 # TODO(stephenfin): Remove support for Sphinx < 1.6 in 4.0
230 if not is_multibuilder_sphinx:
231 log.warn('[pbr] Support for Sphinx < 1.6 will be dropped in '
232 'pbr 4.0. Upgrade to Sphinx 1.6+')
234 # TODO(stephenfin): Remove this at the next MAJOR version bump
235 if self.builders != ['html']:
236 log.warn("[pbr] Sphinx 1.6 added native support for "
237 "specifying multiple builders in the "
238 "'[sphinx_build] builder' configuration option, "
239 "found in 'setup.cfg'. As a result, the "
240 "'[sphinx_build] builders' option has been "
241 "deprecated and will be removed in pbr 4.0. Migrate "
242 "to the 'builder' configuration option.")
243 if is_multibuilder_sphinx:
244 self.builder = self.builders
246 if is_multibuilder_sphinx:
248 return setup_command.BuildDoc.run(self)
251 for builder in self.builders:
252 self.builder = builder
253 self.finalize_options()
256 def initialize_options(self):
257 # Not a new style class, super keyword does not work.
258 setup_command.BuildDoc.initialize_options(self)
260 # NOTE(dstanek): exclude setup.py from the autodoc tree index
261 # builds because all projects will have an issue with it
262 self.autodoc_tree_excludes = ['setup.py']
264 def finalize_options(self):
267 # Not a new style class, super keyword does not work.
268 setup_command.BuildDoc.finalize_options(self)
270 # Handle builder option from command line - override cfg
271 option_dict = self.distribution.get_option_dict('build_sphinx')
272 if 'command line' in option_dict.get('builder', [[]])[0]:
273 self.builders = option_dict['builder'][1]
274 # Allow builders to be configurable - as a comma separated list.
275 if not isinstance(self.builders, list) and self.builders:
276 self.builders = self.builders.split(',')
278 self.project = self.distribution.get_name()
279 self.version = self.distribution.get_version()
280 self.release = self.distribution.get_version()
282 # NOTE(dstanek): check for autodoc tree exclusion overrides
284 opt = 'autodoc_tree_excludes'
285 option_dict = self.distribution.get_option_dict('pbr')
286 if opt in option_dict:
287 self.autodoc_tree_excludes = util.split_multiline(
290 # handle Sphinx < 1.5.0
291 if not hasattr(self, 'warning_is_error'):
292 self.warning_is_error = False