@@ -30,6 +30,10 @@ def __init__(self, env):
30
30
# Simple logging attributes
31
31
self .component_changes : List [str ] = []
32
32
33
+ # MCU-specific backup tracking
34
+ self .backup_created_per_mcu = {} # Track backups per MCU
35
+ self .yaml_backup_created = False
36
+
33
37
self .arduino_framework_dir = self .platform .get_package_dir ("framework-arduinoespressif32" )
34
38
self .arduino_libs_mcu = join (self .platform .get_package_dir ("framework-arduinoespressif32-libs" ), self .mcu )
35
39
@@ -52,15 +56,16 @@ def handle_component_settings(self, add_components: bool = False, remove_compone
52
56
remove_components: Whether to process component removals
53
57
"""
54
58
55
- # Create backup before first component removal and on every add of a component
56
- if remove_components and not self .removed_components or add_components :
57
- self ._backup_pioarduino_build_py ()
58
- self ._log_change ("Created backup of build file " )
59
+ # Create MCU-specific backup before first component removal and on every add of a component
60
+ if remove_components and not self .backup_created_per_mcu . get ( self . mcu , False ) or add_components :
61
+ if self ._backup_pioarduino_build_py ():
62
+ self ._log_change (f "Created MCU backup for { self . mcu } " )
59
63
60
64
# Check if env and GetProjectOption are available
61
- if hasattr (self , 'env' ) or hasattr (self .env , 'GetProjectOption' ):
65
+ if hasattr (self , 'env' ) and hasattr (self .env , 'GetProjectOption' ):
62
66
component_yml_path = self ._get_or_create_component_yml ()
63
67
component_data = self ._load_component_yml (component_yml_path )
68
+ original_data = component_data .copy ()
64
69
65
70
if remove_components :
66
71
try :
@@ -80,7 +85,9 @@ def handle_component_settings(self, add_components: bool = False, remove_compone
80
85
except Exception as e :
81
86
self ._log_change (f"Error adding components: { str (e )} " )
82
87
83
- self ._save_component_yml (component_yml_path , component_data )
88
+ # Only save if changes were made
89
+ if component_data != original_data :
90
+ self ._save_component_yml (component_yml_path , component_data )
84
91
85
92
# Clean up removed components
86
93
if self .removed_components :
@@ -94,10 +101,6 @@ def handle_component_settings(self, add_components: bool = False, remove_compone
94
101
95
102
def handle_lib_ignore (self ) -> None :
96
103
"""Handle lib_ignore entries from platformio.ini and remove corresponding includes."""
97
- # Create backup before processing lib_ignore
98
- if not self .ignored_libs :
99
- self ._backup_pioarduino_build_py ()
100
-
101
104
# Get lib_ignore entries from current environment only
102
105
lib_ignore_entries = self ._get_lib_ignore_entries ()
103
106
@@ -434,9 +437,9 @@ def _remove_ignored_lib_includes(self) -> None:
434
437
435
438
try :
436
439
with open (build_py_path , 'r' ) as f :
437
- content = f .read ()
440
+ original_content = f .read ()
438
441
439
- original_content = content
442
+ modified_content = original_content
440
443
total_removed = 0
441
444
442
445
# Remove CPPPATH entries for each ignored library
@@ -465,24 +468,30 @@ def _remove_ignored_lib_includes(self) -> None:
465
468
466
469
removed_count = 0
467
470
for pattern in patterns :
468
- matches = re .findall (pattern , content )
471
+ matches = re .findall (pattern , modified_content )
469
472
if matches :
470
- content = re .sub (pattern , '' , content )
473
+ modified_content = re .sub (pattern , '' , modified_content )
471
474
removed_count += len (matches )
472
475
473
476
if removed_count > 0 :
474
477
self ._log_change (f"Ignored library: { lib_name } ({ removed_count } entries)" )
475
478
total_removed += removed_count
476
479
477
480
# Clean up empty lines and trailing commas
478
- content = re .sub (r'\n\s*\n' , '\n ' , content )
479
- content = re .sub (r',\s*\n\s*\]' , '\n ]' , content )
481
+ modified_content = re .sub (r'\n\s*\n' , '\n ' , modified_content )
482
+ modified_content = re .sub (r',\s*\n\s*\]' , '\n ]' , modified_content )
480
483
481
- # Validate and write changes
482
- if self ._validate_changes (original_content , content ) and content != original_content :
484
+ # Only write if content actually changed
485
+ if self ._validate_changes (original_content , modified_content ) and modified_content != original_content :
486
+ # Ensure MCU-specific backup exists before modification
487
+ if not self .backup_created_per_mcu .get (self .mcu , False ):
488
+ self ._backup_pioarduino_build_py ()
489
+
483
490
with open (build_py_path , 'w' ) as f :
484
- f .write (content )
491
+ f .write (modified_content )
485
492
self ._log_change (f"Updated build file ({ total_removed } total removals)" )
493
+ else :
494
+ self ._log_change ("No library changes needed" )
486
495
487
496
except Exception as e :
488
497
self ._log_change (f"Error processing libraries: { str (e )} " )
@@ -528,16 +537,28 @@ def _get_or_create_component_yml(self) -> str:
528
537
self ._create_default_component_yml (project_yml )
529
538
return project_yml
530
539
531
- def _create_backup (self , file_path : str ) -> None :
540
+ def _create_backup (self , file_path : str ) -> bool :
532
541
"""
533
- Create backup of a file.
542
+ Create single backup of a file if it doesn't exist .
534
543
535
544
Args:
536
545
file_path: Path to file to backup
546
+
547
+ Returns:
548
+ True if backup was created or already exists
537
549
"""
538
550
backup_path = f"{ file_path } .orig"
539
- if not os .path .exists (backup_path ):
540
- shutil .copy (file_path , backup_path )
551
+
552
+ if os .path .exists (file_path ) and not os .path .exists (backup_path ):
553
+ try :
554
+ shutil .copy2 (file_path , backup_path )
555
+ self ._log_change (f"Single backup created: { backup_path } " )
556
+ return True
557
+ except Exception as e :
558
+ self ._log_change (f"Backup failed: { str (e )} " )
559
+ return False
560
+
561
+ return os .path .exists (backup_path )
541
562
542
563
def _create_default_component_yml (self , file_path : str ) -> None :
543
564
"""
@@ -573,17 +594,36 @@ def _load_component_yml(self, file_path: str) -> Dict[str, Any]:
573
594
574
595
def _save_component_yml (self , file_path : str , data : Dict [str , Any ]) -> None :
575
596
"""
576
- Save component data to YAML file.
597
+ Save component data to YAML file only if changed .
577
598
578
599
Args:
579
600
file_path: Path to YAML file
580
601
data: Data to save
581
602
"""
582
603
try :
604
+ # Check if content would actually change
605
+ if os .path .exists (file_path ):
606
+ with open (file_path , "r" ) as f :
607
+ existing_data = yaml .load (f , Loader = SafeLoader ) or {}
608
+
609
+ # Compare data structures
610
+ if existing_data == data :
611
+ self ._log_change (f"YAML unchanged, skipping write: { file_path } " )
612
+ return
613
+
614
+ # Create backup before first modification (only once)
615
+ if not self .yaml_backup_created :
616
+ self ._create_backup (file_path )
617
+ self .yaml_backup_created = True
618
+
619
+ # Write only if changed
583
620
with open (file_path , "w" ) as f :
584
- yaml .dump (data , f )
585
- except Exception :
586
- pass
621
+ yaml .dump (data , f , default_flow_style = False , sort_keys = True )
622
+
623
+ self ._log_change (f"YAML updated: { file_path } " )
624
+
625
+ except Exception as e :
626
+ self ._log_change (f"Error saving YAML: { str (e )} " )
587
627
588
628
def _remove_components (self , component_data : Dict [str , Any ], components_to_remove : list ) -> None :
589
629
"""
@@ -660,16 +700,61 @@ def _convert_component_name_to_filesystem(self, component_name: str) -> str:
660
700
"""
661
701
return component_name .replace ("/" , "__" )
662
702
663
- def _backup_pioarduino_build_py (self ) -> None :
664
- """Create backup of the original pioarduino-build.py."""
703
+ def _backup_pioarduino_build_py (self ) -> bool :
704
+ """Create MCU-specific backup of pioarduino-build.py only once per MCU ."""
665
705
if "arduino" not in self .env .subst ("$PIOFRAMEWORK" ):
666
- return
706
+ return False
707
+
708
+ # Check if backup already created for this MCU in this session
709
+ if self .backup_created_per_mcu .get (self .mcu , False ):
710
+ return True
667
711
668
712
build_py_path = join (self .arduino_libs_mcu , "pioarduino-build.py" )
669
713
backup_path = join (self .arduino_libs_mcu , f"pioarduino-build.py.{ self .mcu } " )
670
714
715
+ # Only create backup if source exists and MCU-specific backup doesn't exist
671
716
if os .path .exists (build_py_path ) and not os .path .exists (backup_path ):
672
- shutil .copy2 (build_py_path , backup_path )
717
+ try :
718
+ shutil .copy2 (build_py_path , backup_path )
719
+ self .backup_created_per_mcu [self .mcu ] = True
720
+ self ._log_change (f"Created MCU-specific backup for { self .mcu } : { backup_path } " )
721
+ return True
722
+ except Exception as e :
723
+ self ._log_change (f"MCU backup creation failed for { self .mcu } : { str (e )} " )
724
+ return False
725
+ elif os .path .exists (backup_path ):
726
+ self .backup_created_per_mcu [self .mcu ] = True
727
+ self ._log_change (f"MCU backup already exists for { self .mcu } " )
728
+ return True
729
+
730
+ return False
731
+
732
+ def _validate_mcu_backup (self ) -> bool :
733
+ """
734
+ Validate that MCU-specific backup exists and is valid.
735
+
736
+ Returns:
737
+ True if backup is valid
738
+ """
739
+ backup_path = join (self .arduino_libs_mcu , f"pioarduino-build.py.{ self .mcu } " )
740
+
741
+ if not os .path .exists (backup_path ):
742
+ return False
743
+
744
+ try :
745
+ # Basic validation - check if file is readable and not empty
746
+ with open (backup_path , 'r' ) as f :
747
+ content = f .read ()
748
+ if len (content ) < 100 : # Minimum expected size
749
+ self ._log_change (f"MCU backup for { self .mcu } appears corrupted" )
750
+ return False
751
+
752
+ self ._log_change (f"MCU backup for { self .mcu } validated" )
753
+ return True
754
+
755
+ except Exception as e :
756
+ self ._log_change (f"MCU backup validation failed for { self .mcu } : { str (e )} " )
757
+ return False
673
758
674
759
def _cleanup_removed_components (self ) -> None :
675
760
"""Clean up removed components and restore original build file."""
@@ -723,7 +808,7 @@ def _remove_cpppath_entries(self) -> None:
723
808
724
809
def restore_pioarduino_build_py (self , source = None , target = None , env = None ) -> None :
725
810
"""
726
- Restore the original pioarduino-build.py from backup.
811
+ Restore the MCU-specific pioarduino-build.py from backup.
727
812
728
813
Args:
729
814
source: Build source (unused)
@@ -734,8 +819,35 @@ def restore_pioarduino_build_py(self, source=None, target=None, env=None) -> Non
734
819
backup_path = join (self .arduino_libs_mcu , f"pioarduino-build.py.{ self .mcu } " )
735
820
736
821
if os .path .exists (backup_path ):
737
- shutil .copy2 (backup_path , build_py_path )
738
- os .remove (backup_path )
822
+ try :
823
+ shutil .copy2 (backup_path , build_py_path )
824
+ self ._log_change (f"Restored MCU-specific build file for { self .mcu } " )
825
+
826
+ # Optional: Remove backup after restore if desired
827
+ # os.remove(backup_path)
828
+ # self._log_change(f"Removed MCU backup for {self.mcu}")
829
+
830
+ except Exception as e :
831
+ self ._log_change (f"Failed to restore MCU backup for { self .mcu } : { str (e )} " )
832
+ else :
833
+ self ._log_change (f"No MCU backup found for { self .mcu } " )
834
+
835
+ def get_mcu_backup_status (self ) -> Dict [str , bool ]:
836
+ """
837
+ Get status of MCU-specific backups.
838
+
839
+ Returns:
840
+ Dictionary with MCU types as keys and backup existence as values
841
+ """
842
+ backup_dir = self .arduino_libs_mcu
843
+ mcu_types = ["esp32" , "esp32s2" , "esp32s3" , "esp32c3" , "esp32c6" , "esp32h2" ]
844
+
845
+ status = {}
846
+ for mcu in mcu_types :
847
+ backup_path = join (backup_dir , f"pioarduino-build.py.{ mcu } " )
848
+ status [mcu ] = os .path .exists (backup_path )
849
+
850
+ return status
739
851
740
852
def get_changes_summary (self ) -> List [str ]:
741
853
"""
0 commit comments