@@ -95,50 +95,53 @@ def is_quiet(cls):
95
95
assert cls ._options is not None
96
96
return cls ._options .quiet
97
97
98
+ @classmethod
99
+ def should_remote_print (cls ):
100
+ assert cls ._options is not None
101
+ return not cls ._options .no_remote_print
102
+
98
103
99
104
class LogfileManager :
100
- _open_logfiles : dict [str , IO [str ]] = {}
105
+ _logfiles : dict [str , IO [str ]] = {}
106
+ _lock = threading .RLock ()
101
107
102
108
@classmethod
103
109
def create_logfile (cls , build_id , outdir ):
104
- # No lock needed since this is only called by the main thread.
105
- if logfile := cls ._open_logfiles .get (build_id , None ):
110
+ with cls ._lock :
111
+ if logfile := cls ._logfiles .get (build_id , None ):
112
+ return logfile
113
+
114
+ outdir = pathlib .Path (outdir )
115
+ latest_logfile = outdir / f'{ _LOGFILE_NAME } .0'
116
+
117
+ if latest_logfile .exists ():
118
+ with latest_logfile .open ('rt' ) as f :
119
+ first_line = f .readline ()
120
+ if log_build_id := BUILD_ID_RE .search (first_line ):
121
+ # If the newest logfile on disk is referencing the same build we are
122
+ # currently processing, we probably crashed previously and we should
123
+ # pick up where we left off in the same logfile.
124
+ if log_build_id .group ('build_id' ) == build_id :
125
+ cls ._logfiles [build_id ] = latest_logfile .open ('at' )
126
+ return cls ._logfiles [build_id ]
127
+
128
+ # Do the logfile name shift.
129
+ filenames = os .listdir (outdir )
130
+ logfiles = {f for f in filenames if f .startswith (_LOGFILE_NAME )}
131
+ for idx in reversed (range (_MAX_LOGFILES )):
132
+ current_name = f'{ _LOGFILE_NAME } .{ idx } '
133
+ next_name = f'{ _LOGFILE_NAME } .{ idx + 1 } '
134
+ if current_name in logfiles :
135
+ shutil .move (os .path .join (outdir , current_name ),
136
+ os .path .join (outdir , next_name ))
137
+
138
+ # Create a new 0th logfile.
139
+ logfile = latest_logfile .open ('wt' )
140
+ logfile .write (FIRST_LOG_LINE .format (build_id = build_id , outdir = outdir ))
141
+ logfile .flush ()
142
+ cls ._logfiles [build_id ] = logfile
106
143
return logfile
107
144
108
- outdir = pathlib .Path (outdir )
109
- latest_logfile = outdir / f'{ _LOGFILE_NAME } .0'
110
-
111
- if latest_logfile .exists ():
112
- with latest_logfile .open ('rt' ) as f :
113
- first_line = f .readline ()
114
- if log_build_id := BUILD_ID_RE .search (first_line ):
115
- # If the newest logfile on disk is referencing the same build we are
116
- # currently processing, we probably crashed previously and we should
117
- # pick up where we left off in the same logfile.
118
- if log_build_id .group ('build_id' ) == build_id :
119
- cls ._open_logfiles [build_id ] = latest_logfile .open ('at' )
120
- return cls ._open_logfiles [build_id ]
121
-
122
- # Do the logfile name shift.
123
- filenames = os .listdir (outdir )
124
- logfiles = {f for f in filenames if f .startswith (_LOGFILE_NAME )}
125
- for idx in reversed (range (_MAX_LOGFILES )):
126
- current_name = f'{ _LOGFILE_NAME } .{ idx } '
127
- next_name = f'{ _LOGFILE_NAME } .{ idx + 1 } '
128
- if current_name in logfiles :
129
- shutil .move (os .path .join (outdir , current_name ),
130
- os .path .join (outdir , next_name ))
131
-
132
- # Create a new 0th logfile.
133
- logfile = latest_logfile .open ('wt' )
134
- # Logfiles are never closed thus are leaked but there should not be too many
135
- # of them since only one per build is created and the server exits on idle
136
- # in normal operation.
137
- cls ._open_logfiles [build_id ] = logfile
138
- logfile .write (FIRST_LOG_LINE .format (build_id = build_id , outdir = outdir ))
139
- logfile .flush ()
140
- return logfile
141
-
142
145
143
146
class TaskStats :
144
147
"""Class to keep track of aggregate stats for all tasks across threads."""
@@ -278,27 +281,35 @@ def task_done(self, task: Task, status_string: str):
278
281
279
282
# We synchronize all terminal title info rather than having it per build
280
283
# since if two builds are happening in the same terminal concurrently, both
281
- # builds will be overriding each other's titles continously . Usually we only
282
- # have the one build anyways so it should equivalent in most cases.
284
+ # builds will be overriding each other's titles continuously . Usually we
285
+ # only have the one build anyways so it should equivalent in most cases.
283
286
BuildManager .update_remote_titles ()
284
- if not self .is_active ():
285
- self ._logfile .close ()
286
- # Reset in case its the last build.
287
- BuildManager .update_remote_titles ('' )
287
+ with self ._lock :
288
+ if not self .is_active ():
289
+ self ._logfile .close ()
290
+ # Reset in case its the last build.
291
+ BuildManager .update_remote_titles ('' )
288
292
289
293
def process_complete (self ):
290
294
with self ._lock :
291
295
self ._active_process_count -= 1
292
296
TaskStats .remove_process ()
293
297
294
298
def ensure_logfile (self ):
295
- if not self ._logfile :
296
- assert self .cwd is not None
297
- self ._logfile = LogfileManager .create_logfile (self .id , self .cwd )
299
+ with self ._lock :
300
+ if not self ._logfile :
301
+ assert self .cwd is not None
302
+ self ._logfile = LogfileManager .create_logfile (self .id , self .cwd )
298
303
299
304
def log (self , message : str ):
300
- self .ensure_logfile ()
301
- print (message , file = self ._logfile , flush = True )
305
+ with self ._lock :
306
+ self .ensure_logfile ()
307
+ if self ._logfile .closed :
308
+ # BuildManager#broadcast can call log after the build is done and the
309
+ # log is closed. Might make sense to separate out that flow so we can
310
+ # raise an exception here otherwise.
311
+ return
312
+ print (message , file = self ._logfile , flush = True )
302
313
303
314
def _status_update (self , status_message ):
304
315
prefix = f'[{ TaskStats .prefix (self .id )} ] '
@@ -412,12 +423,16 @@ def get_all_builds(cls) -> List[Build]:
412
423
def broadcast (cls , msg : str ):
413
424
with cls ._lock :
414
425
ttys = list (cls ._cached_ttys .values ())
415
- for tty , _unused in ttys :
416
- try :
417
- tty .write (msg + '\n ' )
418
- tty .flush ()
419
- except BrokenPipeError :
420
- pass
426
+ builds = list (cls ._builds_by_id .values ())
427
+ if OptionsManager .should_remote_print ():
428
+ for tty , _unused in ttys :
429
+ try :
430
+ tty .write (msg + '\n ' )
431
+ tty .flush ()
432
+ except BrokenPipeError :
433
+ pass
434
+ for build in builds :
435
+ build .log (msg )
421
436
# Write to the current terminal if we have not written to it yet.
422
437
st = os .stat (sys .stderr .fileno ())
423
438
stderr_key = (st .st_ino , st .st_dev )
@@ -665,24 +680,20 @@ def _complete(self, stdout: str = ''):
665
680
self .build .log (message )
666
681
server_log (message )
667
682
668
- # Add emoji to show that output is from the build server.
669
- preamble = [f'⏩ { line } ' for line in preamble ]
670
- remote_message = '\n ' .join (preamble + [stdout ])
671
- # Add a new line at start of message to clearly delineate from previous
672
- # output/text already on the remote tty we are printing to.
673
- self .build .stdout .write (f'\n { remote_message } ' )
674
- self .build .stdout .flush ()
683
+ if OptionsManager .should_remote_print ():
684
+ # Add emoji to show that output is from the build server.
685
+ preamble = [f'⏩ { line } ' for line in preamble ]
686
+ remote_message = '\n ' .join (preamble + [stdout ])
687
+ # Add a new line at start of message to clearly delineate from previous
688
+ # output/text already on the remote tty we are printing to.
689
+ self .build .stdout .write (f'\n { remote_message } ' )
690
+ self .build .stdout .flush ()
675
691
if delete_stamp :
676
692
# Force siso to consider failed targets as dirty.
677
693
try :
678
694
os .unlink (os .path .join (self .build .cwd , self .stamp_file ))
679
695
except FileNotFoundError :
680
696
pass
681
- else :
682
- # We do not care about the action writing a too new mtime. Siso only cares
683
- # about the mtime that is recorded in its database at the time the
684
- # original action finished.
685
- pass
686
697
self .build .task_done (self , status_string )
687
698
688
699
@@ -1067,6 +1078,9 @@ def main():
1067
1078
parser .add_argument ('--quiet' ,
1068
1079
action = 'store_true' ,
1069
1080
help = 'Do not output status updates.' )
1081
+ parser .add_argument ('--no-remote-print' ,
1082
+ action = 'store_true' ,
1083
+ help = 'Do not output errors to remote terminals.' )
1070
1084
parser .add_argument ('--wait-for-build' ,
1071
1085
metavar = 'BUILD_ID' ,
1072
1086
help = 'Wait for build server to finish with all tasks '
0 commit comments