36
36
from platformio .util import get_serial_ports
37
37
38
38
39
+ def add_to_pythonpath (path ):
40
+ """
41
+ Add a path to the PYTHONPATH environment variable (cross-platform).
42
+
43
+ Args:
44
+ path (str): The path to add to PYTHONPATH
45
+ """
46
+ # Normalize the path for the current OS
47
+ normalized_path = os .path .normpath (path )
48
+
49
+ # Add to PYTHONPATH environment variable
50
+ if "PYTHONPATH" in os .environ :
51
+ current_paths = os .environ ["PYTHONPATH" ].split (os .pathsep )
52
+ if normalized_path not in current_paths :
53
+ os .environ ["PYTHONPATH" ] += os .pathsep + normalized_path
54
+ else :
55
+ os .environ ["PYTHONPATH" ] = normalized_path
56
+
57
+ # Also add to sys.path for immediate availability
58
+ if normalized_path not in sys .path :
59
+ sys .path .insert (0 , normalized_path )
60
+
61
+
39
62
# Initialize environment and configuration
40
63
env = DefaultEnvironment ()
41
64
platform = env .PioPlatform ()
45
68
# Framework directory path
46
69
FRAMEWORK_DIR = platform .get_package_dir ("framework-arduinoespressif32" )
47
70
71
+ # Add framework Python tools to path if available
72
+ if FRAMEWORK_DIR :
73
+ framework_python_path = os .path .join (FRAMEWORK_DIR , "tools" , "python" )
74
+ if os .path .isdir (framework_python_path ):
75
+ add_to_pythonpath (framework_python_path )
76
+
77
+ # Python dependencies required for the build process
48
78
python_deps = {
49
79
"uv" : ">=0.1.0" ,
50
80
"pyyaml" : ">=6.0.2" ,
57
87
58
88
59
89
def get_packages_to_install (deps , installed_packages ):
60
- """Generator for Python packages to install"""
90
+ """
91
+ Generator for Python packages that need to be installed.
92
+
93
+ Args:
94
+ deps (dict): Dictionary of package names and version specifications
95
+ installed_packages (dict): Dictionary of currently installed packages
96
+
97
+ Yields:
98
+ str: Package name that needs to be installed
99
+ """
61
100
for package , spec in deps .items ():
62
101
if package not in installed_packages :
63
102
yield package
@@ -68,7 +107,12 @@ def get_packages_to_install(deps, installed_packages):
68
107
69
108
70
109
def install_python_deps ():
71
- """Ensure uv package manager is available, install with pip if not"""
110
+ """
111
+ Ensure uv package manager is available and install required Python dependencies.
112
+
113
+ Returns:
114
+ bool: True if successful, False otherwise
115
+ """
72
116
try :
73
117
result = subprocess .run (
74
118
["uv" , "--version" ],
@@ -86,7 +130,8 @@ def install_python_deps():
86
130
[env .subst ("$PYTHONEXE" ), "-m" , "pip" , "install" , "uv>=0.1.0" , "-q" , "-q" , "-q" ],
87
131
capture_output = True ,
88
132
text = True ,
89
- timeout = 30 # 30 second timeout
133
+ timeout = 30 , # 30 second timeout
134
+ env = os .environ # Use modified environment with custom PYTHONPATH
90
135
)
91
136
if result .returncode != 0 :
92
137
if result .stderr :
@@ -104,6 +149,12 @@ def install_python_deps():
104
149
105
150
106
151
def _get_installed_uv_packages ():
152
+ """
153
+ Get list of installed packages using uv.
154
+
155
+ Returns:
156
+ dict: Dictionary of installed packages with versions
157
+ """
107
158
result = {}
108
159
try :
109
160
cmd = ["uv" , "pip" , "list" , "--format=json" ]
@@ -112,7 +163,8 @@ def _get_installed_uv_packages():
112
163
capture_output = True ,
113
164
text = True ,
114
165
encoding = 'utf-8' ,
115
- timeout = 30 # 30 second timeout
166
+ timeout = 30 , # 30 second timeout
167
+ env = os .environ # Use modified environment with custom PYTHONPATH
116
168
)
117
169
118
170
if result_obj .returncode == 0 :
@@ -154,7 +206,8 @@ def _get_installed_uv_packages():
154
206
cmd ,
155
207
capture_output = True ,
156
208
text = True ,
157
- timeout = 30 # 30 second timeout for package installation
209
+ timeout = 30 , # 30 second timeout for package installation
210
+ env = os .environ # Use modified environment with custom PYTHONPATH
158
211
)
159
212
160
213
if result .returncode != 0 :
@@ -177,10 +230,22 @@ def _get_installed_uv_packages():
177
230
178
231
179
232
def install_esptool (env ):
180
- """Install esptool from package folder "tool-esptoolpy" using uv package manager"""
233
+ """
234
+ Install esptool from package folder "tool-esptoolpy" using uv package manager.
235
+
236
+ Args:
237
+ env: SCons environment object
238
+
239
+ Returns:
240
+ bool: True if successful, False otherwise
241
+ """
181
242
try :
182
- subprocess .check_call ([env .subst ("$PYTHONEXE" ), "-c" , "import esptool" ],
183
- stdout = subprocess .DEVNULL , stderr = subprocess .DEVNULL )
243
+ subprocess .check_call (
244
+ [env .subst ("$PYTHONEXE" ), "-c" , "import esptool" ],
245
+ stdout = subprocess .DEVNULL ,
246
+ stderr = subprocess .DEVNULL ,
247
+ env = os .environ # Use modified environment with custom PYTHONPATH
248
+ )
184
249
return True
185
250
except (subprocess .CalledProcessError , FileNotFoundError ):
186
251
pass
@@ -192,7 +257,7 @@ def install_esptool(env):
192
257
"uv" , "pip" , "install" , "--quiet" ,
193
258
f"--python={ env .subst ('$PYTHONEXE' )} " ,
194
259
"-e" , esptool_repo_path
195
- ])
260
+ ], env = os . environ ) # Use modified environment with custom PYTHONPATH
196
261
return True
197
262
except subprocess .CalledProcessError as e :
198
263
print (f"Warning: Failed to install esptool: { e } " )
@@ -201,6 +266,7 @@ def install_esptool(env):
201
266
return False
202
267
203
268
269
+ # Install Python dependencies and esptool
204
270
install_python_deps ()
205
271
install_esptool (env )
206
272
@@ -209,6 +275,11 @@ def BeforeUpload(target, source, env):
209
275
"""
210
276
Prepare the environment before uploading firmware.
211
277
Handles port detection and special upload configurations.
278
+
279
+ Args:
280
+ target: SCons target
281
+ source: SCons source
282
+ env: SCons environment object
212
283
"""
213
284
upload_options = {}
214
285
if "BOARD" in env :
@@ -228,7 +299,12 @@ def BeforeUpload(target, source, env):
228
299
def _get_board_memory_type (env ):
229
300
"""
230
301
Determine the memory type configuration for the board.
231
- Returns the appropriate memory type string based on board configuration.
302
+
303
+ Args:
304
+ env: SCons environment object
305
+
306
+ Returns:
307
+ str: The appropriate memory type string based on board configuration
232
308
"""
233
309
board_config = env .BoardConfig ()
234
310
default_type = "%s_%s" % (
@@ -249,20 +325,41 @@ def _get_board_memory_type(env):
249
325
def _normalize_frequency (frequency ):
250
326
"""
251
327
Convert frequency value to normalized string format (e.g., "40m").
252
- Removes 'L' suffix and converts to MHz format.
328
+
329
+ Args:
330
+ frequency: Frequency value to normalize
331
+
332
+ Returns:
333
+ str: Normalized frequency string with 'm' suffix
253
334
"""
254
335
frequency = str (frequency ).replace ("L" , "" )
255
336
return str (int (int (frequency ) / 1000000 )) + "m"
256
337
257
338
258
339
def _get_board_f_flash (env ):
259
- """Get the flash frequency for the board."""
340
+ """
341
+ Get the flash frequency for the board.
342
+
343
+ Args:
344
+ env: SCons environment object
345
+
346
+ Returns:
347
+ str: Flash frequency string
348
+ """
260
349
frequency = env .subst ("$BOARD_F_FLASH" )
261
350
return _normalize_frequency (frequency )
262
351
263
352
264
353
def _get_board_f_image (env ):
265
- """Get the image frequency for the board, fallback to flash frequency."""
354
+ """
355
+ Get the image frequency for the board, fallback to flash frequency.
356
+
357
+ Args:
358
+ env: SCons environment object
359
+
360
+ Returns:
361
+ str: Image frequency string
362
+ """
266
363
board_config = env .BoardConfig ()
267
364
if "build.f_image" in board_config :
268
365
return _normalize_frequency (board_config .get ("build.f_image" ))
@@ -271,7 +368,15 @@ def _get_board_f_image(env):
271
368
272
369
273
370
def _get_board_f_boot (env ):
274
- """Get the boot frequency for the board, fallback to flash frequency."""
371
+ """
372
+ Get the boot frequency for the board, fallback to flash frequency.
373
+
374
+ Args:
375
+ env: SCons environment object
376
+
377
+ Returns:
378
+ str: Boot frequency string
379
+ """
275
380
board_config = env .BoardConfig ()
276
381
if "build.f_boot" in board_config :
277
382
return _normalize_frequency (board_config .get ("build.f_boot" ))
@@ -283,6 +388,12 @@ def _get_board_flash_mode(env):
283
388
"""
284
389
Determine the appropriate flash mode for the board.
285
390
Handles special cases for OPI memory types.
391
+
392
+ Args:
393
+ env: SCons environment object
394
+
395
+ Returns:
396
+ str: Flash mode string
286
397
"""
287
398
if _get_board_memory_type (env ) in ("opi_opi" , "opi_qspi" ):
288
399
return "dout"
@@ -297,6 +408,12 @@ def _get_board_boot_mode(env):
297
408
"""
298
409
Determine the boot mode for the board.
299
410
Handles special cases for OPI memory types.
411
+
412
+ Args:
413
+ env: SCons environment object
414
+
415
+ Returns:
416
+ str: Boot mode string
300
417
"""
301
418
memory_type = env .BoardConfig ().get ("build.arduino.memory_type" , "" )
302
419
build_boot = env .BoardConfig ().get ("build.boot" , "$BOARD_FLASH_MODE" )
@@ -308,7 +425,12 @@ def _get_board_boot_mode(env):
308
425
def _parse_size (value ):
309
426
"""
310
427
Parse size values from various formats (int, hex, K/M suffixes).
311
- Returns the size in bytes as an integer.
428
+
429
+ Args:
430
+ value: Size value to parse
431
+
432
+ Returns:
433
+ int: Size in bytes as an integer
312
434
"""
313
435
if isinstance (value , int ):
314
436
return value
@@ -326,6 +448,12 @@ def _parse_partitions(env):
326
448
"""
327
449
Parse the partition table CSV file and return partition information.
328
450
Also sets the application offset for the environment.
451
+
452
+ Args:
453
+ env: SCons environment object
454
+
455
+ Returns:
456
+ list: List of partition dictionaries
329
457
"""
330
458
partitions_csv = env .subst ("$PARTITIONS_TABLE_CSV" )
331
459
if not isfile (partitions_csv ):
@@ -377,6 +505,9 @@ def _update_max_upload_size(env):
377
505
"""
378
506
Update the maximum upload size based on partition table configuration.
379
507
Prioritizes user-specified partition names.
508
+
509
+ Args:
510
+ env: SCons environment object
380
511
"""
381
512
if not env .get ("PARTITIONS_TABLE_CSV" ):
382
513
return
@@ -412,14 +543,25 @@ def _update_max_upload_size(env):
412
543
413
544
414
545
def _to_unix_slashes (path ):
415
- """Convert Windows-style backslashes to Unix-style forward slashes."""
546
+ """
547
+ Convert Windows-style backslashes to Unix-style forward slashes.
548
+
549
+ Args:
550
+ path (str): Path to convert
551
+
552
+ Returns:
553
+ str: Path with Unix-style slashes
554
+ """
416
555
return path .replace ("\\ " , "/" )
417
556
418
557
419
558
def fetch_fs_size (env ):
420
559
"""
421
560
Extract filesystem size and offset information from partition table.
422
561
Sets FS_START, FS_SIZE, FS_PAGE, and FS_BLOCK environment variables.
562
+
563
+ Args:
564
+ env: SCons environment object
423
565
"""
424
566
fs = None
425
567
for p in _parse_partitions (env ):
@@ -450,15 +592,27 @@ def fetch_fs_size(env):
450
592
451
593
452
594
def __fetch_fs_size (target , source , env ):
453
- """Wrapper function for fetch_fs_size to be used as SCons emitter."""
595
+ """
596
+ Wrapper function for fetch_fs_size to be used as SCons emitter.
597
+
598
+ Args:
599
+ target: SCons target
600
+ source: SCons source
601
+ env: SCons environment object
602
+
603
+ Returns:
604
+ tuple: (target, source) tuple
605
+ """
454
606
fetch_fs_size (env )
455
607
return (target , source )
456
608
457
609
458
610
def check_lib_archive_exists ():
459
611
"""
460
612
Check if lib_archive is set in platformio.ini configuration.
461
- Returns True if found, False otherwise.
613
+
614
+ Returns:
615
+ bool: True if found, False otherwise
462
616
"""
463
617
for section in projectconfig .sections ():
464
618
if "lib_archive" in projectconfig .options (section ):
@@ -611,8 +765,13 @@ def check_lib_archive_exists():
611
765
612
766
def firmware_metrics (target , source , env ):
613
767
"""
614
- Custom target to run esp-idf-size with support for command line parameters
768
+ Custom target to run esp-idf-size with support for command line parameters.
615
769
Usage: pio run -t metrics -- [esp-idf-size arguments]
770
+
771
+ Args:
772
+ target: SCons target
773
+ source: SCons source
774
+ env: SCons environment object
616
775
"""
617
776
if terminal_cp != "utf-8" :
618
777
print ("Firmware metrics can not be shown. Set the terminal codepage to \" utf-8\" " )
@@ -655,8 +814,8 @@ def firmware_metrics(target, source, env):
655
814
if env .GetProjectOption ("custom_esp_idf_size_verbose" , False ):
656
815
print (f"Running command: { ' ' .join (cmd )} " )
657
816
658
- # Call esp-idf-size
659
- result = subprocess .run (cmd , check = False , capture_output = False )
817
+ # Call esp-idf-size with modified environment
818
+ result = subprocess .run (cmd , check = False , capture_output = False , env = os . environ )
660
819
661
820
if result .returncode != 0 :
662
821
print (f"Warning: esp-idf-size exited with code { result .returncode } " )
0 commit comments