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:
- 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.
- The new ``DirectoryHTMLBuilder`` (short name ``dirhtml``) creates

View File

@ -31,7 +31,7 @@ release = version
show_authors = True
# The HTML template theme.
html_theme = 'sphinxdoc'
html_theme = 'default'
# A list of ignored prefixes names for module index sorting.
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
: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
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
Python object. The boolean value *rebuild_env* must be ``True`` if a change
in the setting only takes effect when a document is parsed -- this means that
the whole environment must be rebuilt.
Python object. The string value *rebuild* must be one of those values:
* ``'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
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
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)
Register an event called *name*.

View File

@ -260,10 +260,12 @@ class Sphinx(object):
builder.name, self.builderclasses[builder.name].__module__))
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:
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):
if name in self._events:

View File

@ -15,6 +15,13 @@ import shutil
import posixpath
import cPickle as pickle
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.io import DocTreeInput, StringOutput
@ -67,6 +74,8 @@ class StandaloneHTMLBuilder(Builder):
script_files = ['_static/jquery.js', '_static/doctools.js']
def init(self):
# a hash of all config values that, if changed, cause a full rebuild
self.config_hash = ''
self.init_templates()
self.init_highlighter()
self.init_translator_class()
@ -102,6 +111,55 @@ class StandaloneHTMLBuilder(Builder):
else:
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):
"""Utility: Render a lone doctree node."""
doc = new_document('<partial node>')
@ -479,6 +537,17 @@ class StandaloneHTMLBuilder(Builder):
logobase = path.basename(self.config.html_logo)
shutil.copyfile(path.join(self.confdir, self.config.html_logo),
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')
# dump the search index
@ -511,30 +580,6 @@ class StandaloneHTMLBuilder(Builder):
node.replace_self(reference)
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):
keep = set(self.env.all_docs) - set(docnames)
try:

View File

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

View File

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