BuildEnvironment always own application object (after read phase)

This commit is contained in:
Takeshi KOMIYA 2017-03-06 00:30:12 +09:00
parent a523f9893a
commit c7bec75bcd
5 changed files with 50 additions and 55 deletions

View File

@ -301,15 +301,15 @@ class Sphinx(object):
def _init_env(self, freshenv): def _init_env(self, freshenv):
# type: (bool) -> None # type: (bool) -> None
if freshenv: if freshenv:
self.env = BuildEnvironment(self.srcdir, self.doctreedir, self.config) self.env = BuildEnvironment(self)
self.env.find_files(self.config, self.buildername) self.env.find_files(self.config, self.buildername)
for domain in self.domains.keys(): for domain in self.domains.keys():
self.env.domains[domain] = self.domains[domain](self.env) self.env.domains[domain] = self.domains[domain](self.env)
else: else:
try: try:
logger.info(bold('loading pickled environment... '), nonl=True) logger.info(bold('loading pickled environment... '), nonl=True)
self.env = BuildEnvironment.frompickle( filename = path.join(self.doctreedir, ENV_PICKLE_FILENAME)
self.srcdir, self.config, path.join(self.doctreedir, ENV_PICKLE_FILENAME)) self.env = BuildEnvironment.frompickle(filename, self)
self.env.domains = {} self.env.domains = {}
for domain in self.domains.keys(): for domain in self.domains.keys():
# this can raise if the data version doesn't fit # this can raise if the data version doesn't fit
@ -372,6 +372,7 @@ class Sphinx(object):
else: else:
self.emit('build-finished', None) self.emit('build-finished', None)
self.builder.cleanup() self.builder.cleanup()
self.env = None # clear environment
# ---- logging handling ---------------------------------------------------- # ---- logging handling ----------------------------------------------------
def warn(self, message, location=None, prefix=None, def warn(self, message, location=None, prefix=None,

View File

@ -289,8 +289,7 @@ class Builder(object):
# while reading, collect all warnings from docutils # while reading, collect all warnings from docutils
with logging.pending_warnings(): with logging.pending_warnings():
updated_docnames = set(self.env.update(self.config, self.srcdir, updated_docnames = set(self.env.update(self.config, self.srcdir, self.doctreedir))
self.doctreedir, self.app))
doccount = len(updated_docnames) doccount = len(updated_docnames)
logger.info(bold('looking for now-outdated files... '), nonl=1) logger.info(bold('looking for now-outdated files... '), nonl=1)

View File

@ -104,33 +104,36 @@ class BuildEnvironment(object):
# --------- ENVIRONMENT PERSISTENCE ---------------------------------------- # --------- ENVIRONMENT PERSISTENCE ----------------------------------------
@staticmethod @staticmethod
def load(f, srcdir=None, config=None): def load(f, app=None):
# type: (IO, unicode, Config) -> BuildEnvironment # type: (IO, Sphinx) -> BuildEnvironment
env = pickle.load(f) env = pickle.load(f)
if env.version != ENV_VERSION: if env.version != ENV_VERSION:
raise IOError('build environment version not current') raise IOError('build environment version not current')
if srcdir and env.srcdir != srcdir: if app:
env.app = app
env.config.values = app.config.values
if env.srcdir != app.srcdir:
raise IOError('source directory has changed') raise IOError('source directory has changed')
if config:
env.config.values = config.values
return env return env
@classmethod @classmethod
def loads(cls, string, srcdir=None, config=None): def loads(cls, string, app=None):
# type: (unicode, unicode, Config) -> BuildEnvironment # type: (unicode, Sphinx) -> BuildEnvironment
io = StringIO(string) io = StringIO(string)
return cls.load(io) return cls.load(io, app)
@classmethod @classmethod
def frompickle(cls, srcdir, config, filename): def frompickle(cls, filename, app):
# type: (unicode, Config, unicode) -> BuildEnvironment # type: (unicode, Sphinx) -> BuildEnvironment
with open(filename, 'rb') as f: with open(filename, 'rb') as f:
return cls.load(f, srcdir, config) return cls.load(f, app)
@staticmethod @staticmethod
def dump(env, f): def dump(env, f):
# type: (BuildEnvironment, IO) -> None # type: (BuildEnvironment, IO) -> None
# remove unpicklable attributes # remove unpicklable attributes
app = env.app
del env.app
values = env.config.values values = env.config.values
del env.config.values del env.config.values
domains = env.domains domains = env.domains
@ -146,6 +149,7 @@ class BuildEnvironment(object):
# reset attributes # reset attributes
env.domains = domains env.domains = domains
env.config.values = values env.config.values = values
env.app = app
@classmethod @classmethod
def dumps(cls, env): def dumps(cls, env):
@ -161,19 +165,17 @@ class BuildEnvironment(object):
# --------- ENVIRONMENT INITIALIZATION ------------------------------------- # --------- ENVIRONMENT INITIALIZATION -------------------------------------
def __init__(self, srcdir, doctreedir, config): def __init__(self, app):
# type: (unicode, unicode, Config) -> None # type: (Sphinx) -> None
self.doctreedir = doctreedir self.app = app
self.srcdir = srcdir # type: unicode self.doctreedir = app.doctreedir
self.config = config # type: Config self.srcdir = app.srcdir
self.config = app.config
# the method of doctree versioning; see set_versioning_method # the method of doctree versioning; see set_versioning_method
self.versioning_condition = None # type: Union[bool, Callable] self.versioning_condition = None # type: Union[bool, Callable]
self.versioning_compare = None # type: bool self.versioning_compare = None # type: bool
# the application object; only set while update() runs
self.app = None # type: Sphinx
# all the registered domains, set by the application # all the registered domains, set by the application
self.domains = {} self.domains = {}
@ -490,8 +492,8 @@ class BuildEnvironment(object):
return added, changed, removed return added, changed, removed
def update(self, config, srcdir, doctreedir, app): def update(self, config, srcdir, doctreedir):
# type: (Config, unicode, unicode, Sphinx) -> List[unicode] # type: (Config, unicode, unicode) -> List[unicode]
"""(Re-)read all files new or changed since last update. """(Re-)read all files new or changed since last update.
Store all environment docnames in the canonical format (ie using SEP as Store all environment docnames in the canonical format (ie using SEP as
@ -519,7 +521,7 @@ class BuildEnvironment(object):
# the source and doctree directories may have been relocated # the source and doctree directories may have been relocated
self.srcdir = srcdir self.srcdir = srcdir
self.doctreedir = doctreedir self.doctreedir = doctreedir
self.find_files(config, app.buildername) self.find_files(config, self.app.buildername)
self.config = config self.config = config
# this cache also needs to be updated every time # this cache also needs to be updated every time
@ -530,7 +532,7 @@ class BuildEnvironment(object):
added, changed, removed = self.get_outdated_files(config_changed) added, changed, removed = self.get_outdated_files(config_changed)
# allow user intervention as well # allow user intervention as well
for docs in app.emit('env-get-outdated', self, added, changed, removed): for docs in self.app.emit('env-get-outdated', self, added, changed, removed):
changed.update(set(docs) & self.found_docs) changed.update(set(docs) & self.found_docs)
# if files were added or removed, all documents with globbed toctrees # if files were added or removed, all documents with globbed toctrees
@ -543,23 +545,21 @@ class BuildEnvironment(object):
len(removed)) len(removed))
logger.info(msg) logger.info(msg)
self.app = app
# clear all files no longer present # clear all files no longer present
for docname in removed: for docname in removed:
app.emit('env-purge-doc', self, docname) self.app.emit('env-purge-doc', self, docname)
self.clear_doc(docname) self.clear_doc(docname)
# read all new and changed files # read all new and changed files
docnames = sorted(added | changed) docnames = sorted(added | changed)
# allow changing and reordering the list of docs to read # allow changing and reordering the list of docs to read
app.emit('env-before-read-docs', self, docnames) self.app.emit('env-before-read-docs', self, docnames)
# check if we should do parallel or serial read # check if we should do parallel or serial read
par_ok = False par_ok = False
if parallel_available and len(docnames) > 5 and app.parallel > 1: if parallel_available and len(docnames) > 5 and self.app.parallel > 1:
par_ok = True par_ok = True
for extname, md in app._extension_metadata.items(): for extname, md in self.app._extension_metadata.items():
ext_ok = md.get('parallel_read_safe') ext_ok = md.get('parallel_read_safe')
if ext_ok: if ext_ok:
continue continue
@ -575,17 +575,15 @@ class BuildEnvironment(object):
par_ok = False par_ok = False
break break
if par_ok: if par_ok:
self._read_parallel(docnames, app, nproc=app.parallel) self._read_parallel(docnames, self.app, nproc=self.app.parallel)
else: else:
self._read_serial(docnames, app) self._read_serial(docnames, self.app)
if config.master_doc not in self.all_docs: if config.master_doc not in self.all_docs:
raise SphinxError('master file %s not found' % raise SphinxError('master file %s not found' %
self.doc2path(config.master_doc)) self.doc2path(config.master_doc))
self.app = None for retval in self.app.emit('env-updated', self):
for retval in app.emit('env-updated', self):
if retval is not None: if retval is not None:
docnames.extend(retval) docnames.extend(retval)
@ -608,20 +606,17 @@ class BuildEnvironment(object):
self.clear_doc(docname) self.clear_doc(docname)
def read_process(docs): def read_process(docs):
# type: (List[unicode]) -> BuildEnvironment # type: (List[unicode]) -> unicode
self.app = app self.app = app
for docname in docs: for docname in docs:
self.read_doc(docname, app) self.read_doc(docname, app)
# allow pickling self to send it back # allow pickling self to send it back
del self.app return BuildEnvironment.dumps(self)
del self.domains
del self.config.values
del self.config
return self
def merge(docs, otherenv): def merge(docs, otherenv):
# type: (List[unicode], BuildEnvironment) -> None # type: (List[unicode], unicode) -> None
self.merge_info_from(docs, otherenv, app) env = BuildEnvironment.loads(otherenv)
self.merge_info_from(docs, env, app)
tasks = ParallelTasks(nproc) tasks = ParallelTasks(nproc)
chunks = make_chunks(docnames, nproc) chunks = make_chunks(docnames, nproc)

View File

@ -31,7 +31,7 @@ def teardown_module():
# afford to not run update() in the setup but in its own test # afford to not run update() in the setup but in its own test
def test_first_update(): def test_first_update():
updated = env.update(app.config, app.srcdir, app.doctreedir, app) updated = env.update(app.config, app.srcdir, app.doctreedir)
assert set(updated) == env.found_docs == set(env.all_docs) assert set(updated) == env.found_docs == set(env.all_docs)
# test if exclude_patterns works ok # test if exclude_patterns works ok
assert 'subdir/excluded' not in env.found_docs assert 'subdir/excluded' not in env.found_docs
@ -71,7 +71,7 @@ def test_second_update():
# the contents.txt toctree; otherwise section numbers would shift # the contents.txt toctree; otherwise section numbers would shift
(root / 'autodoc.txt').unlink() (root / 'autodoc.txt').unlink()
(root / 'new.txt').write_text('New file\n========\n') (root / 'new.txt').write_text('New file\n========\n')
updated = env.update(app.config, app.srcdir, app.doctreedir, app) updated = env.update(app.config, app.srcdir, app.doctreedir)
# "includes" and "images" are in there because they contain references # "includes" and "images" are in there because they contain references
# to nonexisting downloadable or image files, which are given another # to nonexisting downloadable or image files, which are given another
# chance to exist # chance to exist
@ -87,7 +87,7 @@ def test_env_read_docs():
app.connect('env-before-read-docs', on_env_read_docs_1) app.connect('env-before-read-docs', on_env_read_docs_1)
read_docnames = env.update(app.config, app.srcdir, app.doctreedir, app) read_docnames = env.update(app.config, app.srcdir, app.doctreedir)
assert len(read_docnames) > 2 and read_docnames == sorted(read_docnames) assert len(read_docnames) > 2 and read_docnames == sorted(read_docnames)
def on_env_read_docs_2(app, env, docnames): def on_env_read_docs_2(app, env, docnames):
@ -95,7 +95,7 @@ def test_env_read_docs():
app.connect('env-before-read-docs', on_env_read_docs_2) app.connect('env-before-read-docs', on_env_read_docs_2)
read_docnames = env.update(app.config, app.srcdir, app.doctreedir, app) read_docnames = env.update(app.config, app.srcdir, app.doctreedir)
assert len(read_docnames) == 2 assert len(read_docnames) == 2

View File

@ -497,7 +497,7 @@ def test_gettext_buildr_ignores_only_directive(app):
def test_gettext_dont_rebuild_mo(make_app, app_params, build_mo): def test_gettext_dont_rebuild_mo(make_app, app_params, build_mo):
# --- don't rebuild by .mo mtime # --- don't rebuild by .mo mtime
def get_number_of_update_targets(app_): def get_number_of_update_targets(app_):
updated = app_.env.update(app_.config, app_.srcdir, app_.doctreedir, app_) updated = app_.env.update(app_.config, app_.srcdir, app_.doctreedir)
return len(updated) return len(updated)
# setup new directory # setup new directory
@ -680,12 +680,12 @@ def test_html_rebuild_mo(app):
app.build() app.build()
# --- rebuild by .mo mtime # --- rebuild by .mo mtime
app.builder.build_update() app.builder.build_update()
updated = app.env.update(app.config, app.srcdir, app.doctreedir, app) updated = app.env.update(app.config, app.srcdir, app.doctreedir)
assert len(updated) == 0 assert len(updated) == 0
mtime = (app.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo').stat().st_mtime mtime = (app.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo').stat().st_mtime
(app.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo').utime((mtime + 5, mtime + 5)) (app.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo').utime((mtime + 5, mtime + 5))
updated = app.env.update(app.config, app.srcdir, app.doctreedir, app) updated = app.env.update(app.config, app.srcdir, app.doctreedir)
assert len(updated) == 1 assert len(updated) == 1