Skip to content

Commit

Permalink
feat(errors): Print errors to STDERR, catch KeyboardInterrupt
Browse files Browse the repository at this point in the history
BREAKING CHANGE

Closes #981

Closes #888

Closes #934
  • Loading branch information
radimkarnis committed Jan 29, 2025
1 parent dbf3d1c commit 0864e17
Show file tree
Hide file tree
Showing 8 changed files with 47 additions and 17 deletions.
17 changes: 17 additions & 0 deletions docs/en/migration-guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
6 changes: 5 additions & 1 deletion espefuse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import espefuse.efuse.esp32s3beta2 as esp32s3beta2_efuse

import esptool
from esptool.logger import log

DefChip = namedtuple("DefChip", ["chip_name", "efuse_lib", "chip_class"])

Expand Down Expand Up @@ -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)


Expand Down
9 changes: 7 additions & 2 deletions espsecure/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
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
Expand Down Expand Up @@ -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__":
Expand Down
19 changes: 11 additions & 8 deletions esptool/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,7 @@ def add_spi_flash_subparsers(
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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)


Expand Down
8 changes: 4 additions & 4 deletions esptool/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@
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

Expand All @@ -59,7 +59,7 @@
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."
Expand Down
1 change: 1 addition & 0 deletions test/test_espefuse.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ def _run_command(self, cmd, check_msg, ret_code):
shell=False,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
)
output, _ = p.communicate()
Expand Down
2 changes: 1 addition & 1 deletion test/test_image_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def run_image_info(self, chip, file):
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 (
Expand Down
2 changes: 1 addition & 1 deletion test/test_imagegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def run_elf2image(self, chip, elf_path, version=None, extra_args=[]):
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 (
Expand Down

0 comments on commit 0864e17

Please sign in to comment.