The HTML builder now stores a small file named `.buildinfo` in

its output directory.  It stores a hash of config values that
can be used to determine if a full rebuild needs to be done (e.g.
after changing ``html_theme``).
This commit is contained in:
Georg Brandl
2009-02-19 16:15:36 +01:00
parent 2dff2e3bfa
commit d4f5796f3b
7 changed files with 158 additions and 97 deletions

View File

@@ -96,6 +96,11 @@ New features added
* Builders: * Builders:
- The HTML builder now stores a small file named ``.buildinfo`` in
its output directory. It stores a hash of config values that
can be used to determine if a full rebuild needs to be done (e.g.
after changing ``html_theme``).
- New builder for Qt help collections, by Antonio Valentino. - New builder for Qt help collections, by Antonio Valentino.
- The new ``DirectoryHTMLBuilder`` (short name ``dirhtml``) creates - The new ``DirectoryHTMLBuilder`` (short name ``dirhtml``) creates

View File

@@ -31,7 +31,7 @@ release = version
show_authors = True show_authors = True
# The HTML template theme. # The HTML template theme.
html_theme = 'sphinxdoc' html_theme = 'default'
# A list of ignored prefixes names for module index sorting. # A list of ignored prefixes names for module index sorting.
modindex_common_prefix = ['sphinx.'] modindex_common_prefix = ['sphinx.']

View File

@@ -15,20 +15,29 @@ the following public API:
Register a new builder. *builder* must be a class that inherits from Register a new builder. *builder* must be a class that inherits from
:class:`~sphinx.builders.Builder`. :class:`~sphinx.builders.Builder`.
.. method:: Sphinx.add_config_value(name, default, rebuild_env) .. method:: Sphinx.add_config_value(name, default, rebuild)
Register a configuration value. This is necessary for Sphinx to recognize Register a configuration value. This is necessary for Sphinx to recognize
new values and set default values accordingly. The *name* should be prefixed new values and set default values accordingly. The *name* should be prefixed
with the extension name, to avoid clashes. The *default* value can be any with the extension name, to avoid clashes. The *default* value can be any
Python object. The boolean value *rebuild_env* must be ``True`` if a change Python object. The string value *rebuild* must be one of those values:
in the setting only takes effect when a document is parsed -- this means that
the whole environment must be rebuilt. * ``'env'`` if a change in the setting only takes effect when a document is
parsed -- this means that the whole environment must be rebuilt.
* ``'html'`` if a change in the setting needs a full rebuild of HTML
documents.
* ``''`` if a change in the setting will not need any special rebuild.
.. versionchanged:: 0.4 .. versionchanged:: 0.4
If the *default* value is a callable, it will be called with the config If the *default* value is a callable, it will be called with the config
object as its argument in order to get the default value. This can be object as its argument in order to get the default value. This can be
used to implement config values whose default depends on other values. used to implement config values whose default depends on other values.
.. versionchanged:: 0.6
Changed *rebuild* from a simple boolean (equivalent to ``''`` or
``'env'``) to a string. However, booleans are still accepted and
converted internally.
.. method:: Sphinx.add_event(name) .. method:: Sphinx.add_event(name)
Register an event called *name*. Register an event called *name*.

View File

@@ -260,10 +260,12 @@ class Sphinx(object):
builder.name, self.builderclasses[builder.name].__module__)) builder.name, self.builderclasses[builder.name].__module__))
self.builderclasses[builder.name] = builder self.builderclasses[builder.name] = builder
def add_config_value(self, name, default, rebuild_env): def add_config_value(self, name, default, rebuild):
if name in self.config.values: if name in self.config.values:
raise ExtensionError('Config value %r already present' % name) raise ExtensionError('Config value %r already present' % name)
self.config.values[name] = (default, rebuild_env) if rebuild in (False, True):
rebuild = rebuild and 'env' or ''
self.config.values[name] = (default, rebuild)
def add_event(self, name): def add_event(self, name):
if name in self._events: if name in self._events:

View File

@@ -15,6 +15,13 @@ import shutil
import posixpath import posixpath
import cPickle as pickle import cPickle as pickle
from os import path from os import path
try:
import hashlib
md5 = hashlib.md5
except ImportError:
# 2.4 compatibility
import md5
md5 = md5.new
from docutils import nodes from docutils import nodes
from docutils.io import DocTreeInput, StringOutput from docutils.io import DocTreeInput, StringOutput
@@ -67,6 +74,8 @@ class StandaloneHTMLBuilder(Builder):
script_files = ['_static/jquery.js', '_static/doctools.js'] script_files = ['_static/jquery.js', '_static/doctools.js']
def init(self): def init(self):
# a hash of all config values that, if changed, cause a full rebuild
self.config_hash = ''
self.init_templates() self.init_templates()
self.init_highlighter() self.init_highlighter()
self.init_translator_class() self.init_translator_class()
@@ -102,6 +111,55 @@ class StandaloneHTMLBuilder(Builder):
else: else:
self.translator_class = HTMLTranslator self.translator_class = HTMLTranslator
def get_outdated_docs(self):
cfgdict = dict((name, self.config[name])
for (name, desc) in self.config.values.iteritems()
if desc[1] == 'html')
self.config_hash = md5(str(cfgdict)).hexdigest()
try:
fp = open(path.join(self.outdir, '.buildinfo'))
version = fp.readline()
if version.rstrip() != '# Sphinx build info version 1':
raise ValueError
fp.readline() # skip commentary
cfg, old_hash = fp.readline().strip().split(': ')
if cfg != 'config':
raise ValueError
fp.close()
except ValueError:
self.warn('unsupported build info format in %r, building all' %
path.join(self.outdir, '.buildinfo'))
old_hash = ''
except Exception:
old_hash = ''
if old_hash != self.config_hash:
for docname in self.env.found_docs:
yield docname
return
if self.templates:
template_mtime = self.templates.newest_template_mtime()
else:
template_mtime = 0
for docname in self.env.found_docs:
if docname not in self.env.all_docs:
yield docname
continue
targetname = self.env.doc2path(docname, self.outdir,
self.out_suffix)
try:
targetmtime = path.getmtime(targetname)
except Exception:
targetmtime = 0
try:
srcmtime = max(path.getmtime(self.env.doc2path(docname)),
template_mtime)
if srcmtime > targetmtime:
yield docname
except EnvironmentError:
# source doesn't exist anymore
pass
def render_partial(self, node): def render_partial(self, node):
"""Utility: Render a lone doctree node.""" """Utility: Render a lone doctree node."""
doc = new_document('<partial node>') doc = new_document('<partial node>')
@@ -479,6 +537,17 @@ class StandaloneHTMLBuilder(Builder):
logobase = path.basename(self.config.html_logo) logobase = path.basename(self.config.html_logo)
shutil.copyfile(path.join(self.confdir, self.config.html_logo), shutil.copyfile(path.join(self.confdir, self.config.html_logo),
path.join(self.outdir, '_static', logobase)) path.join(self.outdir, '_static', logobase))
# write build info file
fp = open(path.join(self.outdir, '.buildinfo'), 'w')
try:
fp.write('# Sphinx build info version 1\n'
'# This file hashes the configuration used when building'
' these files. When it is not found, a full rebuild will'
' be done.\nconfig: %s\n' % self.config_hash)
finally:
fp.close()
self.info('done') self.info('done')
# dump the search index # dump the search index
@@ -511,30 +580,6 @@ class StandaloneHTMLBuilder(Builder):
node.replace_self(reference) node.replace_self(reference)
reference.append(node) reference.append(node)
def get_outdated_docs(self):
if self.templates:
template_mtime = self.templates.newest_template_mtime()
else:
template_mtime = 0
for docname in self.env.found_docs:
if docname not in self.env.all_docs:
yield docname
continue
targetname = self.env.doc2path(docname, self.outdir,
self.out_suffix)
try:
targetmtime = path.getmtime(targetname)
except Exception:
targetmtime = 0
try:
srcmtime = max(path.getmtime(self.env.doc2path(docname)),
template_mtime)
if srcmtime > targetmtime:
yield docname
except EnvironmentError:
# source doesn't exist anymore
pass
def load_indexer(self, docnames): def load_indexer(self, docnames):
keep = set(self.env.all_docs) - set(docnames) keep = set(self.env.all_docs) - set(docnames)
try: try:

View File

@@ -18,92 +18,92 @@ from sphinx.util import make_filename
class Config(object): class Config(object):
"""Configuration file abstraction.""" """Configuration file abstraction."""
# the values are: (default, needs fresh doctrees if changed) # the values are: (default, what needs to be rebuilt if changed)
# If you add a value here, don't forget to include it in the # If you add a value here, don't forget to include it in the
# quickstart.py file template as well as in the docs! # quickstart.py file template as well as in the docs!
config_values = dict( config_values = dict(
# general options # general options
project = ('Python', True), project = ('Python', 'env'),
copyright = ('', False), copyright = ('', 'html'),
version = ('', True), version = ('', 'env'),
release = ('', True), release = ('', 'env'),
today = ('', True), today = ('', 'env'),
today_fmt = (None, True), # the real default is locale-dependent today_fmt = (None, 'env'), # the real default is locale-dependent
language = (None, True), language = (None, 'env'),
locale_dirs = ([], True), locale_dirs = ([], 'env'),
master_doc = ('contents', True), master_doc = ('contents', 'env'),
source_suffix = ('.rst', True), source_suffix = ('.rst', 'env'),
source_encoding = ('utf-8', True), source_encoding = ('utf-8', 'env'),
unused_docs = ([], True), unused_docs = ([], 'env'),
exclude_dirs = ([], True), exclude_dirs = ([], 'env'),
exclude_trees = ([], True), exclude_trees = ([], 'env'),
exclude_dirnames = ([], True), exclude_dirnames = ([], 'env'),
default_role = (None, True), default_role = (None, 'env'),
add_function_parentheses = (True, True), add_function_parentheses = (True, 'env'),
add_module_names = (True, True), add_module_names = (True, 'env'),
trim_footnote_reference_space = (False, True), trim_footnote_reference_space = (False, 'env'),
show_authors = (False, True), show_authors = (False, 'env'),
pygments_style = (None, False), pygments_style = (None, 'html'),
highlight_language = ('python', False), highlight_language = ('python', 'env'),
templates_path = ([], False), templates_path = ([], 'html'),
template_bridge = (None, False), template_bridge = (None, 'html'),
keep_warnings = (False, True), keep_warnings = (False, 'env'),
modindex_common_prefix = ([], False), modindex_common_prefix = ([], 'html'),
rst_epilog = (None, True), rst_epilog = (None, 'env'),
# HTML options # HTML options
html_theme = ('default', False), html_theme = ('default', 'html'),
html_theme_path = ([], False), html_theme_path = ([], 'html'),
html_theme_options = ({}, False), html_theme_options = ({}, 'html'),
html_title = (lambda self: '%s v%s documentation' % html_title = (lambda self: '%s v%s documentation' %
(self.project, self.release), (self.project, self.release),
False), 'html'),
html_short_title = (lambda self: self.html_title, False), html_short_title = (lambda self: self.html_title, 'html'),
html_style = (None, False), html_style = (None, 'html'),
html_logo = (None, False), html_logo = (None, 'html'),
html_favicon = (None, False), html_favicon = (None, 'html'),
html_static_path = ([], False), html_static_path = ([], 'html'),
# the real default is locale-dependent # the real default is locale-dependent
html_last_updated_fmt = (None, False), html_last_updated_fmt = (None, 'html'),
html_use_smartypants = (True, False), html_use_smartypants = (True, 'html'),
html_translator_class = (None, False), html_translator_class = (None, 'html'),
html_sidebars = ({}, False), html_sidebars = ({}, 'html'),
html_additional_pages = ({}, False), html_additional_pages = ({}, 'html'),
html_use_modindex = (True, False), html_use_modindex = (True, 'html'),
html_add_permalinks = (True, False), html_add_permalinks = (True, 'html'),
html_use_index = (True, False), html_use_index = (True, 'html'),
html_split_index = (False, False), html_split_index = (False, 'html'),
html_copy_source = (True, False), html_copy_source = (True, 'html'),
html_show_sourcelink = (True, False), html_show_sourcelink = (True, 'html'),
html_use_opensearch = ('', False), html_use_opensearch = ('', 'html'),
html_file_suffix = (None, False), html_file_suffix = (None, 'html'),
html_link_suffix = (None, False), html_link_suffix = (None, 'html'),
html_show_sphinx = (True, False), html_show_sphinx = (True, 'html'),
html_context = ({}, False), html_context = ({}, 'html'),
# HTML help only options # HTML help only options
htmlhelp_basename = (lambda self: make_filename(self.project), False), htmlhelp_basename = (lambda self: make_filename(self.project), None),
# Qt help only options # Qt help only options
qthelp_basename = (lambda self: make_filename(self.project), False), qthelp_basename = (lambda self: make_filename(self.project), None),
# LaTeX options # LaTeX options
latex_documents = ([], False), latex_documents = ([], None),
latex_logo = (None, False), latex_logo = (None, None),
latex_appendices = ([], False), latex_appendices = ([], None),
latex_use_parts = (False, False), latex_use_parts = (False, None),
latex_use_modindex = (True, False), latex_use_modindex = (True, None),
# paper_size and font_size are still separate values # paper_size and font_size are still separate values
# so that you can give them easily on the command line # so that you can give them easily on the command line
latex_paper_size = ('letter', False), latex_paper_size = ('letter', None),
latex_font_size = ('10pt', False), latex_font_size = ('10pt', None),
latex_elements = ({}, False), latex_elements = ({}, None),
# now deprecated - use latex_elements # now deprecated - use latex_elements
latex_preamble = ('', False), latex_preamble = ('', None),
) )
def __init__(self, dirname, filename, overrides): def __init__(self, dirname, filename, overrides):

View File

@@ -460,8 +460,8 @@ class BuildEnvironment:
else: else:
# check if a config value was changed that affects how # check if a config value was changed that affects how
# doctrees are read # doctrees are read
for key, descr in config.config_values.iteritems(): for key, descr in config.values.iteritems():
if not descr[1]: if descr[1] != 'env':
continue continue
if self.config[key] != config[key]: if self.config[key] != config[key]:
msg = '[config changed] ' msg = '[config changed] '