@@ -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 alwyas when a component is added
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
@@ -515,6 +518,10 @@ def _remove_ignored_lib_includes(self) -> None:
515
518
516
519
# Validate and write changes
517
520
if self ._validate_changes (original_content , content ) and content != original_content :
521
+ # Ensure MCU-specific backup exists before modification
522
+ if not self .backup_created_per_mcu .get (self .mcu , False ):
523
+ self ._backup_pioarduino_build_py ()
524
+
518
525
with open (build_py_path , 'w' ) as f :
519
526
f .write (content )
520
527
self ._log_change (f"Successfully updated build file with { total_removed } total removals" )
@@ -550,12 +557,28 @@ def _get_or_create_component_yml(self) -> str:
550
557
self ._log_change (f"Created new component.yml file at { project_yml } " )
551
558
return project_yml
552
559
553
- def _create_backup (self , file_path : str ) -> None :
554
- """Create backup of a file."""
560
+ def _create_backup (self , file_path : str ) -> bool :
561
+ """
562
+ Create single backup of a file if it doesn't exist.
563
+
564
+ Args:
565
+ file_path: Path to file to backup
566
+
567
+ Returns:
568
+ True if backup was created or already exists
569
+ """
555
570
backup_path = f"{ file_path } .orig"
556
- if not os .path .exists (backup_path ):
557
- shutil .copy (file_path , backup_path )
558
- self ._log_change (f"Created backup: { backup_path } " )
571
+
572
+ if os .path .exists (file_path ) and not os .path .exists (backup_path ):
573
+ try :
574
+ shutil .copy2 (file_path , backup_path )
575
+ self ._log_change (f"Single backup created: { backup_path } " )
576
+ return True
577
+ except Exception as e :
578
+ self ._log_change (f"Backup failed: { str (e )} " )
579
+ return False
580
+
581
+ return os .path .exists (backup_path )
559
582
560
583
def _create_default_component_yml (self , file_path : str ) -> None :
561
584
"""Create a default idf_component.yml file."""
@@ -577,13 +600,37 @@ def _load_component_yml(self, file_path: str) -> Dict[str, Any]:
577
600
return {"dependencies" : {}}
578
601
579
602
def _save_component_yml (self , file_path : str , data : Dict [str , Any ]) -> None :
580
- """Save component data to YAML file."""
603
+ """
604
+ Save component data to YAML file only if changed.
605
+
606
+ Args:
607
+ file_path: Path to YAML file
608
+ data: Data to save
609
+ """
581
610
try :
611
+ # Check if content would actually change
612
+ if os .path .exists (file_path ):
613
+ with open (file_path , "r" ) as f :
614
+ existing_data = yaml .load (f , Loader = SafeLoader ) or {}
615
+
616
+ # Compare data structures
617
+ if existing_data == data :
618
+ self ._log_change (f"YAML unchanged, skipping write: { file_path } " )
619
+ return
620
+
621
+ # Create backup before first modification (only once)
622
+ if not self .yaml_backup_created :
623
+ self ._create_backup (file_path )
624
+ self .yaml_backup_created = True
625
+
626
+ # Write only if changed
582
627
with open (file_path , "w" ) as f :
583
- yaml .dump (data , f )
584
- self ._log_change (f"Saved component configuration to { file_path } " )
628
+ yaml .dump (data , f , default_flow_style = False , sort_keys = True )
629
+
630
+ self ._log_change (f"YAML updated: { file_path } " )
631
+
585
632
except Exception as e :
586
- self ._log_change (f"Error saving component configuration : { str (e )} " )
633
+ self ._log_change (f"Error saving YAML : { str (e )} " )
587
634
588
635
def _remove_components (self , component_data : Dict [str , Any ], components_to_remove : list ) -> None :
589
636
"""Remove specified components from the configuration."""
@@ -628,17 +675,34 @@ def _convert_component_name_to_filesystem(self, component_name: str) -> str:
628
675
"""Convert component name from registry format to filesystem format."""
629
676
return component_name .replace ("/" , "__" )
630
677
631
- def _backup_pioarduino_build_py (self ) -> None :
632
- """Create backup of the original pioarduino-build.py."""
678
+ def _backup_pioarduino_build_py (self ) -> bool :
679
+ """Create MCU-specific backup of pioarduino-build.py only once per MCU ."""
633
680
if "arduino" not in self .env .subst ("$PIOFRAMEWORK" ):
634
- return
681
+ return False
682
+
683
+ # Check if backup already created for this MCU in this session
684
+ if self .backup_created_per_mcu .get (self .mcu , False ):
685
+ return True
635
686
636
687
build_py_path = join (self .arduino_libs_mcu , "pioarduino-build.py" )
637
688
backup_path = join (self .arduino_libs_mcu , f"pioarduino-build.py.{ self .mcu } " )
638
689
690
+ # Only create backup if source exists and MCU-specific backup doesn't exist
639
691
if os .path .exists (build_py_path ) and not os .path .exists (backup_path ):
640
- shutil .copy2 (build_py_path , backup_path )
641
- self ._log_change (f"Created backup of pioarduino-build.py for { self .mcu } " )
692
+ try :
693
+ shutil .copy2 (build_py_path , backup_path )
694
+ self .backup_created_per_mcu [self .mcu ] = True
695
+ self ._log_change (f"Created MCU-specific backup for { self .mcu } : { backup_path } " )
696
+ return True
697
+ except Exception as e :
698
+ self ._log_change (f"MCU backup creation failed for { self .mcu } : { str (e )} " )
699
+ return False
700
+ elif os .path .exists (backup_path ):
701
+ self .backup_created_per_mcu [self .mcu ] = True
702
+ self ._log_change (f"MCU backup already exists for { self .mcu } " )
703
+ return True
704
+
705
+ return False
642
706
643
707
def _cleanup_removed_components (self ) -> None :
644
708
"""Clean up removed components and restore original build file."""
@@ -688,11 +752,59 @@ def _remove_cpppath_entries(self) -> None:
688
752
self ._log_change (f"Error cleaning up CPPPATH entries: { str (e )} " )
689
753
690
754
def restore_pioarduino_build_py (self , source = None , target = None , env = None ) -> None :
691
- """Restore the original pioarduino-build.py from backup."""
755
+ """
756
+ Restore the MCU-specific pioarduino-build.py from backup.
757
+
758
+ Args:
759
+ source: Build source (unused)
760
+ target: Build target (unused)
761
+ env: Environment (unused)
762
+ """
692
763
build_py_path = join (self .arduino_libs_mcu , "pioarduino-build.py" )
693
764
backup_path = join (self .arduino_libs_mcu , f"pioarduino-build.py.{ self .mcu } " )
694
765
695
766
if os .path .exists (backup_path ):
696
- shutil .copy2 (backup_path , build_py_path )
697
- os .remove (backup_path )
698
- self ._log_change ("Restored original pioarduino-build.py from backup" )
767
+ try :
768
+ shutil .copy2 (backup_path , build_py_path )
769
+ self ._log_change (f"Restored MCU-specific build file for { self .mcu } " )
770
+
771
+ except Exception as e :
772
+ self ._log_change (f"Failed to restore MCU backup for { self .mcu } : { str (e )} " )
773
+ else :
774
+ self ._log_change (f"No MCU backup found for { self .mcu } " )
775
+
776
+ def get_mcu_backup_status (self ) -> Dict [str , bool ]:
777
+ """
778
+ Get status of MCU-specific backups.
779
+
780
+ Returns:
781
+ Dictionary with MCU types as keys and backup existence as values
782
+ """
783
+ backup_dir = self .arduino_libs_mcu
784
+ mcu_types = ["esp32" , "esp32s2" , "esp32s3" , "esp32c3" , "esp32c6" , "esp32h2" ]
785
+
786
+ status = {}
787
+ for mcu in mcu_types :
788
+ backup_path = join (backup_dir , f"pioarduino-build.py.{ mcu } " )
789
+ status [mcu ] = os .path .exists (backup_path )
790
+
791
+ return status
792
+
793
+ def get_changes_summary (self ) -> List [str ]:
794
+ """
795
+ Get simple list of all changes made.
796
+
797
+ Returns:
798
+ List of change messages
799
+ """
800
+ return self .component_changes .copy ()
801
+
802
+ def print_changes_summary (self ) -> None :
803
+ """Print a simple summary of all changes."""
804
+ if self .component_changes :
805
+ print ("\n === Component Manager Changes ===" )
806
+ for change in self .component_changes :
807
+ print (f" { change } " )
808
+ print ("=" * 35 )
809
+ else :
810
+ print ("[ComponentManager] No changes made" )
0 commit comments