71
71
logging .basicConfig (format = '%(asctime)-15s [%(levelname)7s] %(funcName)s - %(message)s' ,
72
72
level = level )
73
73
74
+ # Global flag to track if an unhandled exception occurred
75
+ _unhandled_exception_occurred = False
76
+
77
+ def _custom_excepthook (exc_type , exc_value , traceback ):
78
+ global _unhandled_exception_occurred
79
+ _unhandled_exception_occurred = True
80
+
81
+ # Call the default excepthook to allow normal error reporting
82
+ sys .__excepthook__ (exc_type , exc_value , traceback )
83
+
84
+ # Override the default excepthook
85
+ sys .excepthook = _custom_excepthook
86
+
87
+
74
88
# Process and provide the scenario.
75
89
if __name__ == "__main__" :
76
90
@@ -86,7 +100,13 @@ if __name__ == "__main__":
86
100
# Udapi documents have a many cyclic references, so running GC is quite slow.
87
101
if not args .gc :
88
102
gc .disable ()
89
- atexit .register (os ._exit , 0 )
103
+ # When an exception/error has happened, udapy should exit with a non-zero exit code,
104
+ # so that users can use `udapy ... || echo "Error detected"` (or Makefile reports errors).
105
+ # However, we cannot use `atexit.register(lambda: os._exit(1 if sys.exc_info()[0] else 0))`
106
+ # because the Python has already exited the exception-handling block
107
+ # (the exception/error has been already reported and sys.exc_info()[0] is None).
108
+ # We thus keep record whether _unhandled_exception_occurred.
109
+ atexit .register (lambda : os ._exit (1 if _unhandled_exception_occurred else 0 ))
90
110
atexit .register (sys .stderr .flush )
91
111
if args .save :
92
112
args .scenario = args .scenario + ['write.Conllu' ]
0 commit comments