feat(errors): Print errors to STDERR, catch KeyboardInterrupt

BREAKING CHANGE

Closes https://github.com/espressif/esptool/issues/981

Closes https://github.com/espressif/esptool/issues/888

Closes https://github.com/espressif/esptool/issues/934
This commit is contained in:
Radim Karniš
2025-01-22 15:56:08 +01:00
parent dbf3d1ccb3
commit 0864e17530
8 changed files with 47 additions and 17 deletions

View File

@@ -56,3 +56,20 @@ The ``--verify`` option for the :ref:`write_flash <write-flash>` command has bee
1. Remove all ``--verify`` arguments from existing ``write_flash`` commands.
2. Update scripts/CI pipelines to remove ``--verify`` flags.
Error Output Handling
*********************
In ``v5``, error handling and output behavior have been improved to provide better user experience and script compatibility.
**Key Changes:**
- All error messages, including fatal errors, are now printed to **STDERR** instead of STDOUT.
- User keyboard interrupts (e.g., Ctrl+C) are caught and raise an exit code of 2 to indicate an operation interruption.
- Error messages are displayed in **red text** for better visibility.
- This change ensures that errors are not lost when STDOUT is filtered or redirected.
**Migration Steps:**
1. Update scripts that rely on parsing STDOUT for error messages to check STDERR instead.
2. Ensure scripts handle non-zero exit codes correctly in the case of operations interrupted by the user.

View File

@@ -25,6 +25,7 @@ import espefuse.efuse.esp32s3 as esp32s3_efuse
import espefuse.efuse.esp32s3beta2 as esp32s3beta2_efuse
import esptool
from esptool.logger import log
DefChip = namedtuple("DefChip", ["chip_name", "efuse_lib", "chip_class"])
@@ -361,7 +362,10 @@ def _main():
try:
main()
except esptool.FatalError as e:
print("\nA fatal error occurred: %s" % e)
log.error(f"\nA fatal error occurred: {e}")
sys.exit(2)
except KeyboardInterrupt:
log.error("KeyboardInterrupt: Run cancelled by user.")
sys.exit(2)

View File

@@ -21,6 +21,8 @@ from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa, utils
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.utils import int_to_bytes
from esptool.logger import log
import ecdsa
import esptool
@@ -1921,18 +1923,21 @@ def _main():
try:
main()
except esptool.FatalError as e:
print("\nA fatal error occurred: %s" % e)
log.error(f"\nA fatal error occurred: {e}")
sys.exit(2)
except ValueError as e:
try:
if [arg for arg in e.args if "Could not deserialize key data." in arg]:
print(
log.error(
"Note: This error originates from the cryptography module. "
"It is likely not a problem with espsecure, "
"please make sure you are using a compatible OpenSSL backend."
)
finally:
raise
except KeyboardInterrupt:
log.error("KeyboardInterrupt: Run cancelled by user.")
sys.exit(2)
if __name__ == "__main__":

View File

@@ -814,7 +814,7 @@ def main(argv=None, esp=None):
if esp is None:
raise FatalError(
"Could not connect to an Espressif device "
"on any of the %d available serial ports." % len(ser_list)
f"on any of the {len(ser_list)} available serial ports."
)
if esp.secure_download_mode:
@@ -1226,7 +1226,7 @@ def get_default_connected_device(
except (FatalError, OSError) as err:
if port is not None:
raise
log.print(f"{each_port} failed to connect: {err}")
log.error(f"{each_port} failed to connect: {err}")
if _esp and _esp._port:
_esp._port.close()
_esp = None
@@ -1338,23 +1338,26 @@ def _main():
try:
main()
except FatalError as e:
log.print(f"\nA fatal error occurred: {e}")
log.error(f"\nA fatal error occurred: {e}")
sys.exit(2)
except serial.serialutil.SerialException as e:
log.print(f"\nA serial exception error occurred: {e}")
log.print(
log.error(f"\nA serial exception error occurred: {e}")
log.error(
"Note: This error originates from pySerial. "
"It is likely not a problem with esptool, "
"but with the hardware connection or drivers."
)
log.print(
log.error(
"For troubleshooting steps visit: "
"https://docs.espressif.com/projects/esptool/en/latest/troubleshooting.html"
)
sys.exit(1)
except StopIteration:
log.print(traceback.format_exc())
log.print("A fatal error occurred: The chip stopped responding.")
log.error(traceback.format_exc())
log.error("A fatal error occurred: The chip stopped responding.")
sys.exit(2)
except KeyboardInterrupt:
log.error("KeyboardInterrupt: Run cancelled by user.")
sys.exit(2)

View File

@@ -32,9 +32,9 @@ from .util import byte, hexify, mask_to_shift, pad_to, strip_chip_name
try:
import serial
except ImportError:
log.print(
f"Pyserial is not installed for {sys.executable}. "
"Check the README for installation instructions."
log.error(
f"PySerial is not installed for {sys.executable}. "
"Check the documentation for installation instructions."
)
raise
@@ -59,7 +59,7 @@ except TypeError:
try:
import serial.tools.list_ports as list_ports
except ImportError:
log.print(
log.error(
f"The installed version ({serial.VERSION}) of pySerial appears to be too old "
f"for esptool.py (Python interpreter {sys.executable}). "
"Check the documentation for installation instructions."

View File

@@ -161,6 +161,7 @@ class EfuseTestCase:
shell=False,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
)
output, _ = p.communicate()

View File

@@ -45,7 +45,7 @@ class TestImageInfo:
print("\nExecuting {}".format(" ".join(cmd)))
try:
output = subprocess.check_output(cmd)
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
output = output.decode("utf-8")
print(output) # for more complete stdout logs on failure
assert (

View File

@@ -146,7 +146,7 @@ class BaseTestCase:
cmd += [elf_path] + extra_args
print("\nExecuting {}".format(" ".join(cmd)))
try:
output = subprocess.check_output(cmd)
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
output = output.decode("utf-8")
print(output)
assert (