Skip to content

Commit fadb6b1

Browse files
AA-Turnerjbms
andauthored
Stop exiting early with --fail-on-warnings; add --exception-on-warning (#12743)
Co-authored-by: Jeremy Maitin-Shepard <[email protected]>
1 parent 0b17bb1 commit fadb6b1

File tree

20 files changed

+160
-177
lines changed

20 files changed

+160
-177
lines changed

.github/workflows/builddoc.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,3 @@ jobs:
4444
--jobs=auto
4545
--show-traceback
4646
--fail-on-warning
47-
--keep-going

CHANGES.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ Features added
3030
output files.
3131
* #12474: Support type-dependent search result highlighting via CSS.
3232
Patch by Tim Hoffmann.
33+
* #12743: No longer exit on the first warning when
34+
:option:`--fail-on-warning <sphinx-build --fail-on-warning>` is used.
35+
Instead, exit with a non-zero status if any warnings were generated
36+
during the build.
37+
Patch by Adam Turner.
38+
* #12743: Add :option:`sphinx-build --exception-on-warning`,
39+
to raise an exception when warnings are emitted during the build.
40+
Patch by Adam Turner and Jeremy Maitin-Shepard.
3341

3442
Bugs fixed
3543
----------

doc/internals/contributing.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ To build the documentation, run the following command:
263263

264264
.. code-block:: shell
265265
266-
sphinx-build -M html ./doc ./build/sphinx --fail-on-warning --keep-going
266+
sphinx-build -M html ./doc ./build/sphinx --fail-on-warning
267267
268268
This will parse the Sphinx documentation's source files and generate HTML for
269269
you to preview in :file:`build/sphinx/html`.

doc/man/sphinx-build.rst

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ Options
4343
the source and output directories, before any other options are passed.
4444
For example::
4545

46-
sphinx-build -M html ./source ./build --fail-on-warning --keep-going
46+
sphinx-build -M html ./source ./build --fail-on-warning
4747

4848
The *make-mode* provides the same build functionality as
4949
a default :ref:`Makefile or Make.bat <makefile_options>`,
@@ -253,20 +253,35 @@ Options
253253

254254
.. option:: -W, --fail-on-warning
255255

256-
Turn warnings into errors. This means that the build stops at the first
257-
warning and ``sphinx-build`` exits with exit status 1.
256+
Turn warnings into errors.
257+
This means that :program:`sphinx-build` exits with exit status 1
258+
if any warnings are generated during the build.
258259

259260
.. versionchanged:: 7.3
260261
Add ``--fail-on-warning`` long option.
262+
.. versionchanged:: 8.1
263+
:program:`sphinx-build` no longer exits on the first warning,
264+
but instead runs the entire build and exits with exit status 1
265+
if any warnings were generated.
266+
This behaviour was previously enabled with :option:`--keep-going`.
261267

262268
.. option:: --keep-going
263269

264-
Only applicable whilst using :option:`--fail-on-warning`,
265-
which by default exits :program:`sphinx-build` on the first warning.
270+
From Sphinx 8.1, :option:`!--keep-going` is always enabled.
271+
Previously, it was only applicable whilst using :option:`--fail-on-warning`,
272+
which by default exited :program:`sphinx-build` on the first warning.
266273
Using :option:`!--keep-going` runs :program:`!sphinx-build` to completion
267274
and exits with exit status 1 if errors are encountered.
268275

269276
.. versionadded:: 1.8
277+
.. versionchanged:: 8.1
278+
:program:`sphinx-build` no longer exits on the first warning,
279+
meaning that in effect :option:`!--fail-on-warning` is always enabled.
280+
The option is retained for compatibility, but may be removed at some
281+
later date.
282+
283+
.. xref RemovedInSphinx10Warning: deprecate this option in Sphinx 10
284+
or no earlier than 2026-01-01.
270285
271286
.. option:: -T, --show-traceback
272287

@@ -287,6 +302,13 @@ Options
287302
.. versionchanged:: 7.3
288303
Add ``--pdb`` long option.
289304

305+
.. option:: --exception-on-warning
306+
307+
Raise an exception when a warning is emitted during the build.
308+
This can be useful in combination with :option:`--pdb` to debug warnings.
309+
310+
.. versionadded:: 8.1
311+
290312
.. option:: -h, --help, --version
291313

292314
Display usage summary or Sphinx version.

doc/usage/extensions/autodoc.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,10 @@ There are also config values that you can set:
770770
If ``False`` is given, autodoc forcedly suppresses the error if the imported
771771
module emits warnings. By default, ``True``.
772772

773+
.. versionchanged:: 8.1
774+
This option now has no effect as :option:`!--fail-on-warning`
775+
no longer exits early.
776+
773777
.. confval:: autodoc_inherit_docstrings
774778

775779
This value controls the docstrings inheritance.

sphinx/application.py

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
from sphinx.util.tags import Tags
4242

4343
if TYPE_CHECKING:
44+
from typing import Final
45+
4446
from docutils import nodes
4547
from docutils.nodes import Element, Node
4648
from docutils.parsers import Parser
@@ -134,7 +136,7 @@ class Sphinx:
134136
:ivar outdir: Directory for storing build documents.
135137
"""
136138

137-
warningiserror: bool
139+
warningiserror: Final = False
138140
_warncount: int
139141

140142
def __init__(self, srcdir: str | os.PathLike[str], confdir: str | os.PathLike[str] | None,
@@ -144,7 +146,7 @@ def __init__(self, srcdir: str | os.PathLike[str], confdir: str | os.PathLike[st
144146
freshenv: bool = False, warningiserror: bool = False,
145147
tags: Sequence[str] = (),
146148
verbosity: int = 0, parallel: int = 0, keep_going: bool = False,
147-
pdb: bool = False) -> None:
149+
pdb: bool = False, exception_on_warning: bool = False) -> None:
148150
"""Initialize the Sphinx application.
149151
150152
:param srcdir: The path to the source directory.
@@ -163,8 +165,9 @@ def __init__(self, srcdir: str | os.PathLike[str], confdir: str | os.PathLike[st
163165
:param verbosity: The verbosity level.
164166
:param parallel: The maximum number of parallel jobs to use
165167
when reading/writing documents.
166-
:param keep_going: If true, continue processing when an error occurs.
168+
:param keep_going: Unused.
167169
:param pdb: If true, enable the Python debugger on an exception.
170+
:param exception_on_warning: If true, raise an exception on warnings.
168171
"""
169172
self.phase = BuildPhase.INITIALIZATION
170173
self.verbosity = verbosity
@@ -203,12 +206,10 @@ def __init__(self, srcdir: str | os.PathLike[str], confdir: str | os.PathLike[st
203206
else:
204207
self._warning = warning
205208
self._warncount = 0
206-
self.keep_going = warningiserror and keep_going
207-
if self.keep_going:
208-
self.warningiserror = False
209-
else:
210-
self.warningiserror = warningiserror
209+
self.keep_going = bool(warningiserror) # Unused
210+
self._fail_on_warnings = bool(warningiserror)
211211
self.pdb = pdb
212+
self._exception_on_warning = exception_on_warning
212213
logging.setup(self, self._status, self._warning)
213214

214215
self.events = EventManager(self)
@@ -386,26 +387,31 @@ def build(self, force_all: bool = False, filenames: list[str] | None = None) ->
386387
self.events.emit('build-finished', err)
387388
raise
388389

389-
if self._warncount and self.keep_going:
390-
self.statuscode = 1
391-
392-
status = (__('succeeded') if self.statuscode == 0
393-
else __('finished with problems'))
394-
if self._warncount:
395-
if self.warningiserror:
396-
if self._warncount == 1:
397-
msg = __('build %s, %s warning (with warnings treated as errors).')
398-
else:
399-
msg = __('build %s, %s warnings (with warnings treated as errors).')
390+
if self._warncount == 0:
391+
if self.statuscode != 0:
392+
logger.info(bold(__('build finished with problems.')))
400393
else:
401-
if self._warncount == 1:
402-
msg = __('build %s, %s warning.')
403-
else:
404-
msg = __('build %s, %s warnings.')
405-
406-
logger.info(bold(msg), status, self._warncount)
394+
logger.info(bold(__('build succeeded.')))
395+
elif self._warncount == 1:
396+
if self._fail_on_warnings:
397+
self.statuscode = 1
398+
msg = __('build finished with problems, 1 warning '
399+
'(with warnings treated as errors).')
400+
elif self.statuscode != 0:
401+
msg = __('build finished with problems, 1 warning.')
402+
else:
403+
msg = __('build succeeded, 1 warning.')
404+
logger.info(bold(msg))
407405
else:
408-
logger.info(bold(__('build %s.')), status)
406+
if self._fail_on_warnings:
407+
self.statuscode = 1
408+
msg = __('build finished with problems, %s warnings '
409+
'(with warnings treated as errors).')
410+
elif self.statuscode != 0:
411+
msg = __('build finished with problems, %s warnings.')
412+
else:
413+
msg = __('build succeeded, %s warnings.')
414+
logger.info(bold(msg), self._warncount)
409415

410416
if self.statuscode == 0 and self.builder.epilog:
411417
logger.info('')

sphinx/builders/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import pickle
77
import re
88
import time
9+
from contextlib import nullcontext
910
from os import path
1011
from typing import TYPE_CHECKING, Any, Literal, final
1112

@@ -327,7 +328,7 @@ def build(
327328
logger.info(bold(__('building [%s]: ')) + summary, self.name)
328329

329330
# while reading, collect all warnings from docutils
330-
with logging.pending_warnings():
331+
with nullcontext() if self.app._exception_on_warning else logging.pending_warnings():
331332
updated_docnames = set(self.read())
332333

333334
doccount = len(updated_docnames)
@@ -627,7 +628,7 @@ def write(
627628
self._write_serial(sorted(docnames))
628629

629630
def _write_serial(self, docnames: Sequence[str]) -> None:
630-
with logging.pending_warnings():
631+
with nullcontext() if self.app._exception_on_warning else logging.pending_warnings():
631632
for docname in status_iterator(docnames, __('writing output... '), "darkgreen",
632633
len(docnames), self.app.verbosity):
633634
self.app.phase = BuildPhase.RESOLVING

sphinx/builders/linkcheck.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def process_result(self, result: CheckResult) -> None:
106106
elif result.status == 'working':
107107
logger.info(darkgreen('ok ') + result.uri + result.message)
108108
elif result.status == 'timeout':
109-
if self.app.quiet or self.app.warningiserror:
109+
if self.app.quiet:
110110
logger.warning('timeout ' + result.uri + result.message,
111111
location=(result.docname, result.lineno))
112112
else:
@@ -115,7 +115,7 @@ def process_result(self, result: CheckResult) -> None:
115115
result.uri + ': ' + result.message)
116116
self.timed_out_hyperlinks += 1
117117
elif result.status == 'broken':
118-
if self.app.quiet or self.app.warningiserror:
118+
if self.app.quiet:
119119
logger.warning(__('broken link: %s (%s)'), result.uri, result.message,
120120
location=(result.docname, result.lineno))
121121
else:

sphinx/cmd/build.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -201,12 +201,14 @@ def get_parser() -> argparse.ArgumentParser:
201201
help=__('write warnings (and errors) to given file'))
202202
group.add_argument('--fail-on-warning', '-W', action='store_true', dest='warningiserror',
203203
help=__('turn warnings into errors'))
204-
group.add_argument('--keep-going', action='store_true', dest='keep_going',
205-
help=__("with --fail-on-warning, keep going when getting warnings"))
204+
group.add_argument('--keep-going', action='store_true', help=argparse.SUPPRESS)
206205
group.add_argument('--show-traceback', '-T', action='store_true', dest='traceback',
207206
help=__('show full traceback on exception'))
208207
group.add_argument('--pdb', '-P', action='store_true', dest='pdb',
209208
help=__('run Pdb on exception'))
209+
group.add_argument('--exception-on-warning', action='store_true',
210+
dest='exception_on_warning',
211+
help=__('raise an exception on warnings'))
210212

211213
return parser
212214

@@ -329,11 +331,16 @@ def build_main(argv: Sequence[str]) -> int:
329331
try:
330332
confdir = args.confdir or args.sourcedir
331333
with patch_docutils(confdir), docutils_namespace():
332-
app = Sphinx(args.sourcedir, args.confdir, args.outputdir,
333-
args.doctreedir, args.builder, args.confoverrides, args.status,
334-
args.warning, args.freshenv, args.warningiserror,
335-
args.tags, args.verbosity, args.jobs, args.keep_going,
336-
args.pdb)
334+
app = Sphinx(
335+
srcdir=args.sourcedir, confdir=args.confdir,
336+
outdir=args.outputdir, doctreedir=args.doctreedir,
337+
buildername=args.builder, confoverrides=args.confoverrides,
338+
status=args.status, warning=args.warning,
339+
freshenv=args.freshenv, warningiserror=args.warningiserror,
340+
tags=args.tags,
341+
verbosity=args.verbosity, parallel=args.jobs, keep_going=False,
342+
pdb=args.pdb, exception_on_warning=args.exception_on_warning,
343+
)
337344
app.build(args.force_all, args.filenames)
338345
return app.statuscode
339346
except (Exception, KeyboardInterrupt) as exc:

sphinx/ext/autodoc/__init__.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -415,9 +415,10 @@ def import_object(self, raiseerror: bool = False) -> bool:
415415
"""
416416
with mock(self.config.autodoc_mock_imports):
417417
try:
418-
ret = import_object(self.modname, self.objpath, self.objtype,
419-
attrgetter=self.get_attr,
420-
warningiserror=self.config.autodoc_warningiserror)
418+
ret = import_object(
419+
self.modname, self.objpath, self.objtype,
420+
attrgetter=self.get_attr,
421+
)
421422
self.module, self.parent, self.object_name, self.object = ret
422423
if ismock(self.object):
423424
self.object = undecorate(self.object)
@@ -1960,7 +1961,7 @@ def import_object(self, raiseerror: bool = False) -> bool:
19601961
# annotation only instance variable (PEP-526)
19611962
try:
19621963
with mock(self.config.autodoc_mock_imports):
1963-
parent = import_module(self.modname, self.config.autodoc_warningiserror)
1964+
parent = import_module(self.modname)
19641965
annotations = get_type_hints(parent, None,
19651966
self.config.autodoc_type_aliases,
19661967
include_extras=True)
@@ -2455,9 +2456,10 @@ def import_object(self, raiseerror: bool = False) -> bool:
24552456
except ImportError as exc:
24562457
try:
24572458
with mock(self.config.autodoc_mock_imports):
2458-
ret = import_object(self.modname, self.objpath[:-1], 'class',
2459-
attrgetter=self.get_attr, # type: ignore[attr-defined]
2460-
warningiserror=self.config.autodoc_warningiserror)
2459+
ret = import_object(
2460+
self.modname, self.objpath[:-1], 'class',
2461+
attrgetter=self.get_attr, # type: ignore[attr-defined]
2462+
)
24612463
parent = ret[3]
24622464
if self.is_runtime_instance_attribute(parent):
24632465
self.object = self.RUNTIME_INSTANCE_ATTRIBUTE
@@ -2509,9 +2511,10 @@ def import_object(self, raiseerror: bool = False) -> bool:
25092511
return super().import_object(raiseerror=True) # type: ignore[misc]
25102512
except ImportError as exc:
25112513
try:
2512-
ret = import_object(self.modname, self.objpath[:-1], 'class',
2513-
attrgetter=self.get_attr, # type: ignore[attr-defined]
2514-
warningiserror=self.config.autodoc_warningiserror)
2514+
ret = import_object(
2515+
self.modname, self.objpath[:-1], 'class',
2516+
attrgetter=self.get_attr, # type: ignore[attr-defined]
2517+
)
25152518
parent = ret[3]
25162519
if self.is_uninitialized_instance_attribute(parent):
25172520
self.object = UNINITIALIZED_ATTR

0 commit comments

Comments
 (0)