From d4f5796f3bd754e9fbcd66c5656146d4ea747229 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 19 Feb 2009 16:15:36 +0100 Subject: [PATCH] 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``). --- CHANGES | 5 ++ doc/conf.py | 2 +- doc/ext/appapi.rst | 17 ++++-- sphinx/application.py | 6 +- sphinx/builders/html.py | 93 +++++++++++++++++++++-------- sphinx/config.py | 128 ++++++++++++++++++++-------------------- sphinx/environment.py | 4 +- 7 files changed, 158 insertions(+), 97 deletions(-) diff --git a/CHANGES b/CHANGES index 8170cb94b..8085b39fb 100644 --- a/CHANGES +++ b/CHANGES @@ -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 diff --git a/doc/conf.py b/doc/conf.py index e1a48aa20..a86f86489 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -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.'] diff --git a/doc/ext/appapi.rst b/doc/ext/appapi.rst index aad41e359..b23b6774c 100644 --- a/doc/ext/appapi.rst +++ b/doc/ext/appapi.rst @@ -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*. diff --git a/sphinx/application.py b/sphinx/application.py index f221e1c71..45c340d20 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -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: diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 93f5181c0..234c1fb9f 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -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('') @@ -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: diff --git a/sphinx/config.py b/sphinx/config.py index d5bb64716..d4604b532 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -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): diff --git a/sphinx/environment.py b/sphinx/environment.py index 4b6e199f9..805d579d4 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -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] '