Add BuildEnvironment.setup() to re-initialize (after unpickle)

This commit is contained in:
Takeshi KOMIYA 2018-05-10 11:48:54 +09:00
parent ad991e7f4c
commit 3ffde92c54
4 changed files with 93 additions and 61 deletions

View File

@ -287,24 +287,15 @@ class Sphinx(object):
# type: (bool) -> None
filename = path.join(self.doctreedir, ENV_PICKLE_FILENAME)
if freshenv or not os.path.exists(filename):
self.env = BuildEnvironment(self)
self.env = BuildEnvironment()
self.env.setup(self)
self.env.find_files(self.config, self.builder)
for domain in self.registry.create_domains(self.env):
self.env.domains[domain.name] = domain
else:
try:
logger.info(bold(__('loading pickled environment... ')), nonl=True)
with open(filename, 'rb') as f:
self.env = pickle.load(f)
self.env.app = self
self.env.config.values = self.config.values
needed, reason = self.env.need_refresh(self)
if needed:
raise IOError(reason)
self.env.domains = {}
for domain in self.registry.create_domains(self.env):
# this can raise if the data version doesn't fit
self.env.domains[domain.name] = domain
self.env.setup(self)
logger.info(__('done'))
except Exception as err:
logger.info(__('failed: %s'), err)

View File

@ -17,6 +17,7 @@ from docutils import nodes
from six.moves import cPickle as pickle
from sphinx.deprecation import RemovedInSphinx20Warning
from sphinx.environment import CONFIG_OK, CONFIG_CHANGED_REASON
from sphinx.environment.adapters.asset import ImageAdapter
from sphinx.errors import SphinxError
from sphinx.io import read_doc
@ -422,11 +423,10 @@ class Builder(object):
Store all environment docnames in the canonical format (ie using SEP as
a separator in place of os.path.sep).
"""
updated, reason = self.env.update_config(self.config, self.srcdir, self.doctreedir)
logger.info(bold('updating environment: '), nonl=True)
self.env.find_files(self.config, self)
updated = (self.env.config_status != CONFIG_OK)
added, changed, removed = self.env.get_outdated_files(updated)
# allow user intervention as well
@ -440,6 +440,7 @@ class Builder(object):
changed.update(self.env.glob_toctrees & self.env.found_docs)
if changed:
reason = CONFIG_CHANGED_REASON.get(self.env.config_status, '')
logger.info('[%s] ', reason, nonl=True)
logger.info('%s added, %s changed, %s removed',
len(added), len(changed), len(removed))
@ -473,6 +474,9 @@ class Builder(object):
if retval is not None:
docnames.extend(retval)
# workaround: marked as okay to call builder.read() twice in same process
self.env.config_status = CONFIG_OK
return sorted(docnames)
def _read_serial(self, docnames):

View File

@ -25,7 +25,7 @@ from sphinx import addnodes
from sphinx.deprecation import RemovedInSphinx20Warning, RemovedInSphinx30Warning
from sphinx.environment.adapters.indexentries import IndexEntries
from sphinx.environment.adapters.toctree import TocTree
from sphinx.errors import SphinxError, ExtensionError
from sphinx.errors import SphinxError, BuildEnvironmentError, ExtensionError
from sphinx.locale import __
from sphinx.transforms import SphinxTransformer
from sphinx.util import get_matching_docs, FilenameUniqDict
@ -69,6 +69,18 @@ default_settings = {
# NOTE: increase base version by 2 to have distinct numbers for Py2 and 3
ENV_VERSION = 53 + (sys.version_info[0] - 2)
# config status
CONFIG_OK = 1
CONFIG_NEW = 2
CONFIG_CHANGED = 3
CONFIG_EXTENSIONS_CHANGED = 4
CONFIG_CHANGED_REASON = {
CONFIG_NEW: __('new config'),
CONFIG_CHANGED: __('config changed'),
CONFIG_EXTENSIONS_CHANGED: __('extensions changed'),
}
versioning_conditions = {
'none': False,
@ -93,12 +105,14 @@ class BuildEnvironment(object):
# --------- ENVIRONMENT INITIALIZATION -------------------------------------
def __init__(self, app):
def __init__(self, app=None):
# type: (Sphinx) -> None
self.app = app
self.doctreedir = app.doctreedir
self.srcdir = app.srcdir # type: unicode
self.config = app.config # type: Config
self.app = None # type: Sphinx
self.doctreedir = None # type: unicode
self.srcdir = None # type: unicode
self.config = None # type: Config
self.config_status = None # type: int
self.version = None # type: Dict[unicode, unicode]
# the method of doctree versioning; see set_versioning_method
self.versioning_condition = None # type: Union[bool, Callable]
@ -114,9 +128,6 @@ class BuildEnvironment(object):
# the function to write warning messages with
self._warnfunc = None # type: Callable
# this is to invalidate old pickles
self.version = app.registry.get_envversion(app) # type: Dict[unicode, unicode]
# All "docnames" here are /-separated and relative and exclude
# the source suffix.
@ -193,6 +204,10 @@ class BuildEnvironment(object):
# attributes of "any" cross references
self.ref_context = {} # type: Dict[unicode, Any]
# set up environment
if app:
self.setup(app)
def __getstate__(self):
# type: () -> Dict
"""Obtains serializable data for pickling."""
@ -204,6 +219,61 @@ class BuildEnvironment(object):
# type: (Dict) -> None
self.__dict__.update(state)
def setup(self, app):
# type: (Sphinx) -> None
"""Set up BuildEnvironment object."""
if self.version and self.version != app.registry.get_envversion(app):
raise BuildEnvironmentError(__('build environment version not current'))
elif self.srcdir and self.srcdir != app.srcdir:
raise BuildEnvironmentError(__('source directory has changed'))
self.app = app
self.doctreedir = app.doctreedir
self.srcdir = app.srcdir
self.version = app.registry.get_envversion(app)
# initialize domains
self.domains = {}
for domain in app.registry.create_domains(self):
self.domains[domain.name] = domain
# initialize config
self._update_config(app.config)
# initialie settings
self._update_settings(app.config)
def _update_config(self, config):
# type: (Config) -> None
"""Update configurations by new one."""
self.config_status = CONFIG_OK
if self.config is None:
self.config_status = CONFIG_NEW
else:
# check if a config value was changed that affects how
# doctrees are read
for item in config.filter('env'):
if self.config[item.name] != item.value:
self.config_status = CONFIG_CHANGED
break
# this value is not covered by the above loop because it is handled
# specially by the config class
if self.config.extensions != config.extensions:
self.config_status = CONFIG_EXTENSIONS_CHANGED
self.config = config
def _update_settings(self, config):
# type: (Config) -> None
"""Update settings by new config."""
self.settings['input_encoding'] = config.source_encoding
self.settings['trim_footnote_reference_space'] = config.trim_footnote_reference_space
self.settings['language_code'] = config.language or 'en'
# Allow to disable by 3rd party extension (workaround)
self.settings.setdefault('smart_quotes', True)
def set_warnfunc(self, func):
# type: (Callable) -> None
warnings.warn('env.set_warnfunc() is now deprecated. Use sphinx.util.logging instead.',
@ -451,44 +521,6 @@ class BuildEnvironment(object):
if docname not in already:
yield docname
def update_config(self, config, srcdir, doctreedir):
# type: (Config, unicode, unicode) -> Tuple[bool, unicode]
"""Update configurations by new one."""
changed_reason = ''
if self.config is None:
changed_reason = __('new config')
else:
# check if a config value was changed that affects how
# doctrees are read
for confval in config.filter('env'):
if self.config[confval.name] != confval.value:
changed_reason = __('config changed')
break
# this value is not covered by the above loop because it is handled
# specially by the config class
if self.config.extensions != config.extensions:
changed_reason = __('extensions changed')
# the source and doctree directories may have been relocated
self.srcdir = srcdir
self.doctreedir = doctreedir
self.config = config
self._update_settings(config)
# return tuple of (changed, reason)
return bool(changed_reason), changed_reason
def _update_settings(self, config):
# type: (Config) -> None
"""Update settings by new config."""
self.settings['input_encoding'] = config.source_encoding
self.settings['trim_footnote_reference_space'] = config.trim_footnote_reference_space
self.settings['language_code'] = config.language or 'en'
# Allow to disable by 3rd party extension (workaround)
self.settings.setdefault('smart_quotes', True)
# --------- SINGLE FILE READING --------------------------------------------
def prepare_settings(self, docname):

View File

@ -72,6 +72,11 @@ class ExtensionError(SphinxError):
return parent_str
class BuildEnvironmentError(SphinxError):
"""BuildEnvironment error."""
category = 'BuildEnvironment error'
class ConfigError(SphinxError):
"""Configuration error."""
category = 'Configuration error'