Skip to content

Commit f5142b8

Browse files
authored
Tools - upload.py exception handling fixes (#9186)
- don't check exc_info() in `finally`, it only works without `except` with just `try` and `finally` see https://docs.python.org/3/reference/compound_stmts.html#try after `except Exception as e:` block, `e` is already deleted - handle a rare case when esptool code does not close 'erase_file'. printing paths may cause encoding issues, so just fall through silently - simplify ordering of write_flash & erase_region arguments since we always end up in write_flash, prefer to think of it as argument pairs 'addr' + 'path'. actual binaries go first, erase temporaries go last. construct write args beforehand and apply when finishing with the command line. both commands can appear multiple times, for manual or scripting cases, where multiple regions should be erased / written to
1 parent bc25138 commit f5142b8

File tree

1 file changed

+102
-50
lines changed

1 file changed

+102
-50
lines changed

tools/upload.py

Lines changed: 102 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,126 @@
11
#!/usr/bin/env python3
22

33
# Wrapper for Arduino core / others that can call esptool.py possibly multiple times
4-
# Adds pyserial to sys.path automatically based on the path of the current file
5-
6-
# First parameter is pyserial path, second is esptool path, then a series of command arguments
7-
# i.e. upload.py tools/pyserial tools/esptool write_flash file 0x0
4+
# Adds pyserial & esptool that are in the same directory as the script to sys.path
85

96
import os
7+
import atexit
8+
import pathlib
109
import sys
1110
import tempfile
11+
import traceback
12+
13+
from typing import List
14+
15+
# Add neighbouring pyserial & esptool to search path
16+
MODULES = [
17+
"pyserial",
18+
"esptool",
19+
]
1220

13-
sys.argv.pop(0) # Remove executable name
14-
toolspath = os.path.dirname(os.path.realpath(__file__))
21+
PWD = pathlib.Path(__file__).resolve().parent
22+
for m in MODULES:
23+
sys.path.insert(0, (PWD / m).as_posix())
24+
25+
# If this fails, we can't continue and will bomb below
1526
try:
16-
sys.path.insert(0, os.path.join(toolspath, "pyserial")) # Add pyserial dir to search path
17-
sys.path.insert(0, os.path.join(toolspath, "esptool")) # Add esptool dir to search path
18-
import esptool # If this fails, we can't continue and will bomb below
27+
import esptool
1928
except ImportError:
20-
sys.stderr.write("pyserial or esptool directories not found next to this upload.py tool.\n")
29+
sys.stderr.write(
30+
"\n*** pyserial or esptool directories not found next to upload.py tool (this script) ***\n"
31+
)
32+
traceback.print_exc(file=sys.stderr)
33+
sys.stderr.flush()
34+
2135
sys.exit(1)
2236

23-
cmdline = []
24-
write_option = ''
25-
write_addr = '0x0'
26-
erase_addr = ''
27-
erase_len = ''
2837

29-
while sys.argv:
30-
thisarg = sys.argv.pop(0)
38+
def make_erase_pair(addr: str, dest_size: int, block_size=2**16):
39+
dest, path = tempfile.mkstemp()
40+
41+
buffer = bytearray(b"\xff" * block_size)
42+
while dest_size:
43+
remainder = dest_size % block_size
44+
45+
if remainder:
46+
src = buffer[:remainder]
47+
src_size = remainder
48+
else:
49+
src = buffer
50+
src_size = block_size
51+
52+
os.write(dest, src)
53+
dest_size -= src_size
54+
55+
os.close(dest)
56+
57+
def maybe_remove(path):
58+
try:
59+
os.remove(path)
60+
except Exception:
61+
pass
62+
63+
atexit.register(maybe_remove, path)
64+
return [addr, path]
65+
3166

32-
# We silently replace the 921kbaud setting with 460k to enable backward
67+
argv = sys.argv[1:] # Remove executable name
68+
69+
cmdline: List[str] = []
70+
write_options: List[str] = ["--flash_size", "detect"]
71+
erase_options: List[str] = []
72+
73+
thisarg = ""
74+
lastarg = ""
75+
while argv:
76+
lastarg = thisarg
77+
thisarg = argv.pop(0)
78+
79+
# We silently replace the high-speed setting with 460k to enable backward
3380
# compatibility with the old esptool-ck.exe. Esptool.py doesn't seem
34-
# work reliably at 921k, but is still significantly faster at 460kbaud.
35-
if thisarg == "921600":
81+
# work reliably, but 460kbaud is still plenty fast.
82+
if lastarg == "--baud" and thisarg in ("921600", "3000000"):
3683
thisarg = "460800"
3784

3885
# 'erase_flash' command is translated to the write_flash --erase-all option
3986
# https://github.com/esp8266/Arduino/issues/6755#issuecomment-553208688
4087
if thisarg == "erase_flash":
41-
write_option = '--erase-all'
42-
elif thisarg == 'erase_region':
43-
erase_addr = sys.argv.pop(0)
44-
erase_len = sys.argv.pop(0)
45-
elif thisarg == 'write_flash':
46-
write_addr = sys.argv.pop(0)
47-
binary = sys.argv.pop(0)
88+
write_options.append("--erase-all")
89+
90+
# instead of providing esptool with separate targets,
91+
# everything below becomes 'write_flash' [<addr> <path>] pairs
92+
93+
# 'erase_region' becomes a temporary file filled with 0xff
94+
# this pair is appended *after* 'write_flash' pairs
95+
elif thisarg == "erase_region":
96+
addr = argv.pop(0)
97+
size = int(argv.pop(0), 0)
98+
erase_options.extend(make_erase_pair(addr, size))
99+
100+
# 'write_flash' pair taken in order it was specified
101+
elif thisarg == "write_flash":
102+
addr = argv.pop(0)
103+
path = argv.pop(0)
104+
write_options.extend([addr, path])
105+
106+
# everything else is used as-is
48107
elif thisarg:
49-
cmdline = cmdline + [thisarg]
50-
51-
cmdline = cmdline + ['write_flash']
52-
if write_option:
53-
cmdline = cmdline + [write_option]
54-
cmdline = cmdline + ['--flash_size', 'detect']
55-
cmdline = cmdline + [write_addr, binary]
56-
57-
erase_file = ''
58-
if erase_addr:
59-
# Generate temporary empty (0xff) file
60-
eraser = tempfile.mkstemp()
61-
erase_file = eraser[1]
62-
os.write(eraser[0], bytearray([0xff] * int(erase_len, 0)))
63-
os.close(eraser[0])
64-
cmdline = cmdline + [erase_addr, erase_file]
108+
cmdline.append(thisarg)
109+
110+
111+
cmdline.append("write_flash")
112+
for opts in (write_options, erase_options):
113+
if opts:
114+
cmdline.extend(opts)
65115

66116
try:
67117
esptool.main(cmdline)
68-
except Exception as e:
69-
sys.stderr.write('\nA fatal esptool.py error occurred: %s' % e)
70-
finally:
71-
if erase_file:
72-
os.remove(erase_file)
73-
if any(sys.exc_info()):
74-
sys.exit(2)
118+
except Exception:
119+
etype, evalue, _ = sys.exc_info()
120+
estring = "\n".join(traceback.format_exception_only(etype, value=evalue))
121+
122+
sys.stderr.write("\n*** upload.py fatal error ***\n")
123+
sys.stderr.write(estring)
124+
sys.stderr.flush()
125+
126+
sys.exit(2)

0 commit comments

Comments
 (0)