14
14
WorkRequest = object
15
15
WorkResponse = object
16
16
17
+
18
+ class SphinxMainError (Exception ):
19
+ def __init__ (self , message , exit_code ):
20
+ super ().__init__ (message )
21
+ self .exit_code = exit_code
22
+
23
+
17
24
logger = logging .getLogger ("sphinxdocs_build" )
18
25
19
26
_WORKER_SPHINX_EXT_MODULE_NAME = "bazel_worker_sphinx_ext"
@@ -58,7 +65,7 @@ def __init__(
58
65
def __enter__ (self ):
59
66
return self
60
67
61
- def __exit__ (self ):
68
+ def __exit__ (self , exc_type , exc_val , exc_tb ):
62
69
for worker_outdir in self ._worker_outdirs :
63
70
shutil .rmtree (worker_outdir , ignore_errors = True )
64
71
@@ -75,6 +82,17 @@ def run(self) -> None:
75
82
response = self ._process_request (request )
76
83
if response :
77
84
self ._send_response (response )
85
+ except SphinxMainError as e :
86
+ logger .error ("Sphinx main returned failure: exit_code=%s request=%s" ,
87
+ request , e .exit_code )
88
+ request_id = 0 if not request else request .get ("requestId" , 0 )
89
+ self ._send_response (
90
+ {
91
+ "exitCode" : e .exit_code ,
92
+ "output" : str (e ),
93
+ "requestId" : request_id ,
94
+ }
95
+ )
78
96
except Exception :
79
97
logger .exception ("Unhandled error: request=%s" , request )
80
98
output = (
@@ -142,13 +160,10 @@ def _prepare_sphinx(self, request):
142
160
143
161
@contextlib .contextmanager
144
162
def _redirect_streams (self ):
145
- out = io .StringIO ()
146
- orig_stdout = sys .stdout
147
- try :
148
- sys .stdout = out
149
- yield out
150
- finally :
151
- sys .stdout = orig_stdout
163
+ stdout = io .StringIO ()
164
+ stderr = io .StringIO ()
165
+ with contextlib .redirect_stdout (stdout ), contextlib .redirect_stderr (stderr ):
166
+ yield stdout , stderr
152
167
153
168
def _process_request (self , request : "WorkRequest" ) -> "WorkResponse | None" :
154
169
logger .info ("Request: %s" , json .dumps (request , sort_keys = True , indent = 2 ))
@@ -159,19 +174,50 @@ def _process_request(self, request: "WorkRequest") -> "WorkResponse | None":
159
174
160
175
# Prevent anything from going to stdout because it breaks the worker
161
176
# protocol. We have limited control over where Sphinx sends output.
162
- with self ._redirect_streams () as stdout :
177
+ with self ._redirect_streams () as ( stdout , stderr ) :
163
178
logger .info ("main args: %s" , sphinx_args )
164
179
exit_code = main (sphinx_args )
180
+ # Running Sphinx multiple times in a process can give spurious
181
+ # errors. An invocation after an error seems to work, though.
182
+ if exit_code == 2 :
183
+ logger .warning ("Sphinx main() returned exit_code=2, retrying..." )
184
+ # Reset streams to capture output of the retry cleanly
185
+ stdout .seek (0 )
186
+ stdout .truncate (0 )
187
+ stderr .seek (0 )
188
+ stderr .truncate (0 )
189
+ exit_code = main (sphinx_args )
165
190
166
191
if exit_code :
167
- raise Exception (
192
+ stdout_output = stdout .getvalue ().strip ()
193
+ stderr_output = stderr .getvalue ().strip ()
194
+ if stdout_output :
195
+ stdout_output = (
196
+ "========== STDOUT START ==========\n "
197
+ + stdout_output
198
+ + "\n "
199
+ + "========== STDOUT END ==========\n "
200
+ )
201
+ else :
202
+ stdout_output = "========== STDOUT EMPTY ==========\n "
203
+ if stderr_output :
204
+ stderr_output = (
205
+ "========== STDERR START ==========\n "
206
+ + stderr_output
207
+ + "\n "
208
+ + "========== STDERR END ==========\n "
209
+ )
210
+ else :
211
+ stderr_output = "========== STDERR EMPTY ==========\n "
212
+
213
+ message = (
168
214
"Sphinx main() returned failure: "
169
215
+ f" exit code: { exit_code } \n "
170
- + "========== STDOUT START ==========\n "
171
- + stdout .getvalue ().rstrip ("\n " )
172
- + "\n "
173
- + "========== STDOUT END ==========\n "
216
+ + stdout_output
217
+ + stderr_output
174
218
)
219
+ raise SphinxMainError (message , exit_code )
220
+
175
221
176
222
# Copying is unfortunately necessary because Bazel doesn't know to
177
223
# implicily bring along what the symlinks point to.
0 commit comments