diff --git a/sphinx/config.py b/sphinx/config.py index 9e49423d5cc..922fa710fbb 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -465,6 +465,17 @@ def __getstate__(self) -> dict: for name, opt in self._options.items(): real_value = getattr(self, name) if not is_serializable(real_value): + if opt.rebuild: + # if the value is not cached, then any build that utilises this cache + # will always mark the config value as changed, + # and thus always invalidate the cache and perform a rebuild. + logger.warning( + __('cannot cache unpickable configuration value: %r'), + name, + type='config', + subtype='cache', + once=True, + ) # omit unserializable value real_value = None # valid_types is also omitted diff --git a/tests/test_builders/test_build_warnings.py b/tests/test_builders/test_build_warnings.py index 1db4e051f47..8605d5d26ef 100644 --- a/tests/test_builders/test_build_warnings.py +++ b/tests/test_builders/test_build_warnings.py @@ -70,3 +70,19 @@ def test_texinfo_warnings(app, warning): app.build(force_all=True) warnings_exp = TEXINFO_WARNINGS.format(root=re.escape(app.srcdir.as_posix())) _check_warnings(warnings_exp, warning.getvalue()) + + +def test_uncacheable_config_warning(make_app, tmp_path): + """Test than an unpickleable config value raises a warning.""" + tmp_path.joinpath('conf.py').write_text(""" +my_config = lambda: None +show_warning_types = True +def setup(app): + app.add_config_value('my_config', None, 'env') + """, encoding='utf-8') + tmp_path.joinpath('index.rst').write_text('Test\n====\n', encoding='utf-8') + app = make_app(srcdir=tmp_path) + app.build() + assert strip_colors(app.warning.getvalue()).strip() == ( + "WARNING: cannot cache unpickable configuration value: 'my_config' [config.cache]" + )