Skip to content

Commit b8fda7a

Browse files
authored
Merge pull request #843 from sphinx-contrib/rework-build-caching
Rework build caching
2 parents 8214684 + df1d629 commit b8fda7a

File tree

7 files changed

+651
-27
lines changed

7 files changed

+651
-27
lines changed

sphinxcontrib/confluencebuilder/__init__.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def setup(app):
8686
# Default alignment for tables, figures, etc.
8787
cm.add_conf('confluence_default_alignment', 'confluence')
8888
# Enablement of a generated domain index documents
89-
cm.add_conf('confluence_domain_indices')
89+
cm.add_conf('confluence_domain_indices', 'confluence')
9090
# Confluence editor to target for publication.
9191
cm.add_conf('confluence_editor', 'confluence')
9292
# File to get page header information from.
@@ -98,19 +98,19 @@ def setup(app):
9898
# Dictionary to pass to footer when rendering template.
9999
cm.add_conf('confluence_footer_data', 'confluence')
100100
# Enablement of a generated search documents
101-
cm.add_conf_bool('confluence_include_search')
101+
cm.add_conf_bool('confluence_include_search', 'confluence')
102102
# Enablement of a "page generated" notice.
103103
cm.add_conf_bool('confluence_page_generation_notice', 'confluence')
104104
# Enablement of publishing pages into a hierarchy from a root toctree.
105-
cm.add_conf_bool('confluence_page_hierarchy')
105+
cm.add_conf_bool('confluence_page_hierarchy', 'confluence')
106106
# Show previous/next buttons (bottom, top, both, None).
107107
cm.add_conf('confluence_prev_next_buttons_location', 'confluence')
108108
# Suffix to put after section numbers, before section name
109109
cm.add_conf('confluence_secnumber_suffix', 'confluence')
110110
# Enablement of a "Edit/Show Source" reference on each document
111111
cm.add_conf('confluence_sourcelink', 'confluence')
112112
# Enablement of a generated index document
113-
cm.add_conf_bool('confluence_use_index')
113+
cm.add_conf_bool('confluence_use_index', 'confluence')
114114
# Enablement for toctrees for singleconfluence documents.
115115
cm.add_conf_bool('singleconfluence_toctree', 'singleconfluence')
116116

@@ -132,7 +132,7 @@ def setup(app):
132132
# Explicitly prevent page notifications on update.
133133
cm.add_conf_bool('confluence_disable_notifications')
134134
# Define a series of labels to apply to all published pages.
135-
cm.add_conf('confluence_global_labels')
135+
cm.add_conf('confluence_global_labels', 'confluence')
136136
# Enablement of configuring root as space's homepage.
137137
cm.add_conf_bool('confluence_root_homepage')
138138
# Translation to override parent page identifier to publish to.

sphinxcontrib/confluencebuilder/builder.py

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
from sphinxcontrib.confluencebuilder.config.checks import validate_configuration
2020
from sphinxcontrib.confluencebuilder.config.defaults import apply_defaults
2121
from sphinxcontrib.confluencebuilder.config.env import apply_env_overrides
22+
from sphinxcontrib.confluencebuilder.config.env import build_hash
23+
from sphinxcontrib.confluencebuilder.env import ConfluenceCacheInfo
2224
from sphinxcontrib.confluencebuilder.intersphinx import build_intersphinx
2325
from sphinxcontrib.confluencebuilder.logger import ConfluenceLogger
2426
from sphinxcontrib.confluencebuilder.nodes import confluence_footer
@@ -67,12 +69,15 @@ def __init__(self, app, env=None):
6769
self.domain_indices = {}
6870
self.file_suffix = '.conf'
6971
self.info = ConfluenceLogger.info
72+
self.legacy_assets = {}
73+
self.legacy_pages = None
7074
self.link_suffix = None
7175
self.metadata = defaultdict(dict)
7276
self.nav_next = {}
7377
self.nav_prev = {}
7478
self.omitted_docnames = []
7579
self.orphan_docnames = []
80+
self.parent_id = None
7681
self.publish_allowlist = None
7782
self.publish_denylist = None
7883
self.publish_docnames = []
@@ -84,8 +89,10 @@ def __init__(self, app, env=None):
8489
self.use_search = None
8590
self.verbose = ConfluenceLogger.verbose
8691
self.warn = ConfluenceLogger.warn
92+
self._cache_info = ConfluenceCacheInfo(self)
8793
self._cached_footer_data = None
8894
self._cached_header_data = None
95+
self._config_confluence_hash = None
8996
self._original_get_doctree = None
9097
self._verbose = self.app.verbosity
9198

@@ -148,6 +155,14 @@ def init(self):
148155
self.config.sphinx_verbosity = self._verbose
149156
self.publisher.init(self.config, self.cloud)
150157

158+
# With the configuration finalizes, generate a Confluence-specific
159+
# configuration hash that is applicable to this run
160+
self._config_confluence_hash = build_hash(config)
161+
self.verbose('configuration hash ' + self._config_confluence_hash)
162+
163+
self._cache_info.load_cache()
164+
self._cache_info.configure(self._config_confluence_hash)
165+
151166
self.create_template_bridge()
152167
self.templates.init(self)
153168

@@ -209,27 +224,11 @@ def get_outdated_docs(self):
209224
"""
210225
Return an iterable of input files that are outdated.
211226
"""
212-
# This method is taken from TextBuilder.get_outdated_docs()
213-
# with minor changes to support :confval:`rst_file_transform`.
227+
214228
for docname in self.env.found_docs:
215-
if docname not in self.env.all_docs:
229+
if self._cache_info.is_outdated(docname):
216230
yield docname
217231
continue
218-
sourcename = path.join(self.env.srcdir, docname +
219-
self.file_suffix)
220-
targetname = path.join(self.outdir, self.file_transform(docname))
221-
222-
try:
223-
targetmtime = path.getmtime(targetname)
224-
except Exception:
225-
targetmtime = 0
226-
try:
227-
srcmtime = path.getmtime(sourcename)
228-
if srcmtime > targetmtime:
229-
yield docname
230-
except OSError:
231-
# source doesn't exist anymore
232-
pass
233232

234233
def get_target_uri(self, docname, typ=None):
235234
return self.link_transform(docname)
@@ -483,6 +482,8 @@ def write_doc(self, docname, doctree):
483482
except OSError as err:
484483
self.warn(f'error writing file {outfilename}: {err}')
485484

485+
self._cache_info.track_page_hash(docname)
486+
486487
def publish_doc(self, docname, output):
487488
conf = self.config
488489
title = self.state.title(docname)
@@ -519,6 +520,8 @@ def publish_doc(self, docname, output):
519520
uploaded_id = self.publisher.store_page(title, data, parent_id)
520521
self.state.register_upload_id(docname, uploaded_id)
521522

523+
self._cache_info.track_last_page_id(docname, uploaded_id)
524+
522525
if self.config.root_doc == docname:
523526
self.root_doc_page_id = uploaded_id
524527

@@ -751,8 +754,6 @@ def finish(self):
751754

752755
# publish generated output (if desired)
753756
if self.publish:
754-
self.legacy_assets = {}
755-
self.legacy_pages = None
756757
self.parent_id = self.publisher.get_base_page_id()
757758

758759
for docname in status_iterator(
@@ -802,9 +803,21 @@ def to_asset_name(asset):
802803
except OSError as err:
803804
self.warn(f'error reading asset {key}: {err}')
804805

806+
# if we have documents that were not changes (and therefore, not
807+
# needing to be republished), assume any cached publish page ids
808+
# are still valid and remove them from the legacy pages list
809+
other_docs = self.env.all_docs.keys() - set(self.publish_docnames)
810+
for unchanged_doc in other_docs:
811+
lpid = self._cache_info.last_page_id(unchanged_doc)
812+
if lpid is not None and lpid in self.legacy_pages:
813+
self.legacy_pages.remove(lpid)
814+
805815
self.publish_cleanup()
806816
self.publish_finalize()
807817

818+
# persist cache from this run
819+
self._cache_info.save_cache()
820+
808821
def cleanup(self):
809822
if self.publish:
810823
self.publisher.disconnect()

sphinxcontrib/confluencebuilder/config/env.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# Copyright Sphinx Confluence Builder Contributors (AUTHORS)
33

44
from sphinxcontrib.confluencebuilder.logger import ConfluenceLogger as logger
5+
from sphinxcontrib.confluencebuilder.util import ConfluenceUtil
56
from sphinxcontrib.confluencebuilder.util import str2bool
67
import os
78

@@ -38,3 +39,42 @@ def apply_env_overrides(builder):
3839
conf[key] = int(env_val)
3940
else:
4041
conf[key] = env_val
42+
43+
44+
def build_hash(config):
45+
"""
46+
builds a confluence configuration hash
47+
48+
This call will build a hash based on Confluence-specific configuration
49+
entries. This hash can later be used to determine whether or not
50+
re-processing documents is needed based certain configuration values
51+
being changed.
52+
53+
Args:
54+
config: the configuration
55+
"""
56+
57+
# extract confluence configuration options
58+
entries = []
59+
for c in sorted(config.filter(['confluence'])):
60+
entries.append(c.name)
61+
entries.append(c.value)
62+
63+
# compile a string to hash, sorting dictionary/list/etc. entries along
64+
# the way
65+
hash_data = []
66+
while entries:
67+
value = entries.pop(0)
68+
69+
if isinstance(value, dict):
70+
sorted_value = dict(sorted(value.items()))
71+
for k, v in sorted_value.items():
72+
entries.append(k)
73+
entries.append(v)
74+
elif isinstance(value, (list, set, tuple)):
75+
entries.extend(sorted(value))
76+
else:
77+
hash_data.append(str(value))
78+
79+
# generate a configuration hash
80+
return ConfluenceUtil.hash(''.join(hash_data))

0 commit comments

Comments
 (0)