Find third-party builders by entry point

A builder is uniquely identified by its name, which can be used as an
entry point in the 'sphinx.builders' entry point group. This obviates
the need to register the builder as an extension.

The built-in builders are still loaded as before. New third-party builders
should provide an entry point in their setup.py:

    entry_points={
        'sphinx.builders': [
            'mybuilder = mypackage.mymodule:MyBuilder',
        ],
    }

Like before, builders should define a setup(app) function in the
'mypackage.module' module to define configuration variables etc. It is
no longer necessary to register the builder using Sphinx.add_builder().

Existing builders can still be loaded the traditional way, by including
their module name in the extensions list in conf.py.
This commit is contained in:
Brecht Machiels 2016-07-20 19:41:54 +02:00
parent 1c4915f904
commit 405ef96d2a
2 changed files with 42 additions and 4 deletions

View File

@ -22,6 +22,34 @@ The configuration file itself can be treated as an extension if it contains a
``setup()`` function. All other extensions to load must be listed in the
:confval:`extensions` configuration value.
Discovery of builders by entry point
------------------------------------
.. versionadded:: 1.6
:term:`Builder` extensions can be discovered by means of `entry points`_ so
they do not have to be listed in the :confval:`extensions` configuration value.
Builder extensions need to define an entry point in the ``sphinx.builders``
group. The name of the entry point needs to be set to the name of the builder,
which is the name passed to the :option:`sphinx-build -b` option. Here is an
example of how an entry point for 'mybuilder' can be defined in ``setup.py``::
setup(
# ...
entry_points={
'sphinx.builders': [
'mybuilder = mypackage.mymodule:MyBuilder',
],
}
)
It is no longer strictly necessary to register the builder using
:meth:`~.Sphinx.add_builder` in the extension's ``setup()`` function, but it is
recommended to not remove this in order to ensure backward compatiblity.
.. _entry points: https://setuptools.readthedocs.io/en/latest/setuptools.html#dynamic-discovery-of-services-and-plugins
Extension metadata
------------------

View File

@ -26,6 +26,7 @@ from six.moves import cStringIO
from docutils import nodes
from docutils.parsers.rst import convert_directive_function, \
directives, roles
from pkg_resources import iter_entry_points
import sphinx
from sphinx import package_dir, locale
@ -298,10 +299,19 @@ class Sphinx(object):
if buildername is None:
logger.info(_('No builder selected, using default: html'))
buildername = 'html'
if buildername not in self.builderclasses:
raise SphinxError(_('Builder name %s not registered') % buildername)
builderclass = self.builderclasses[buildername]
if buildername in self.builderclasses:
builderclass = self.builderclasses[buildername]
else:
entry_points = iter_entry_points('sphinx.builders', buildername)
try:
entry_point = next(entry_points)
except StopIteration:
raise SphinxError('Builder name %s not registered or available'
' through entry point' % buildername)
else:
builderclass = entry_point.load()
extension_module = sys.modules[builderclass.__module__]
extension_module.setup(self)
return builderclass(self)
def _init_builder(self):