Skip to content

Commit efac91c

Browse files
authored
refactor backup of components yml and pioarduino-build.py
1 parent 358d1be commit efac91c

File tree

1 file changed

+146
-34
lines changed

1 file changed

+146
-34
lines changed

builder/frameworks/component_manager.py

Lines changed: 146 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ def __init__(self, env):
3030
# Simple logging attributes
3131
self.component_changes: List[str] = []
3232

33+
# MCU-specific backup tracking
34+
self.backup_created_per_mcu = {} # Track backups per MCU
35+
self.yaml_backup_created = False
36+
3337
self.arduino_framework_dir = self.platform.get_package_dir("framework-arduinoespressif32")
3438
self.arduino_libs_mcu = join(self.platform.get_package_dir("framework-arduinoespressif32-libs"), self.mcu)
3539

@@ -52,15 +56,16 @@ def handle_component_settings(self, add_components: bool = False, remove_compone
5256
remove_components: Whether to process component removals
5357
"""
5458

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}")
5963

6064
# 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'):
6266
component_yml_path = self._get_or_create_component_yml()
6367
component_data = self._load_component_yml(component_yml_path)
68+
original_data = component_data.copy()
6469

6570
if remove_components:
6671
try:
@@ -80,7 +85,9 @@ def handle_component_settings(self, add_components: bool = False, remove_compone
8085
except Exception as e:
8186
self._log_change(f"Error adding components: {str(e)}")
8287

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)
8491

8592
# Clean up removed components
8693
if self.removed_components:
@@ -94,10 +101,6 @@ def handle_component_settings(self, add_components: bool = False, remove_compone
94101

95102
def handle_lib_ignore(self) -> None:
96103
"""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-
101104
# Get lib_ignore entries from current environment only
102105
lib_ignore_entries = self._get_lib_ignore_entries()
103106

@@ -434,9 +437,9 @@ def _remove_ignored_lib_includes(self) -> None:
434437

435438
try:
436439
with open(build_py_path, 'r') as f:
437-
content = f.read()
440+
original_content = f.read()
438441

439-
original_content = content
442+
modified_content = original_content
440443
total_removed = 0
441444

442445
# Remove CPPPATH entries for each ignored library
@@ -465,24 +468,30 @@ def _remove_ignored_lib_includes(self) -> None:
465468

466469
removed_count = 0
467470
for pattern in patterns:
468-
matches = re.findall(pattern, content)
471+
matches = re.findall(pattern, modified_content)
469472
if matches:
470-
content = re.sub(pattern, '', content)
473+
modified_content = re.sub(pattern, '', modified_content)
471474
removed_count += len(matches)
472475

473476
if removed_count > 0:
474477
self._log_change(f"Ignored library: {lib_name} ({removed_count} entries)")
475478
total_removed += removed_count
476479

477480
# 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)
480483

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+
483490
with open(build_py_path, 'w') as f:
484-
f.write(content)
491+
f.write(modified_content)
485492
self._log_change(f"Updated build file ({total_removed} total removals)")
493+
else:
494+
self._log_change("No library changes needed")
486495

487496
except Exception as e:
488497
self._log_change(f"Error processing libraries: {str(e)}")
@@ -528,16 +537,28 @@ def _get_or_create_component_yml(self) -> str:
528537
self._create_default_component_yml(project_yml)
529538
return project_yml
530539

531-
def _create_backup(self, file_path: str) -> None:
540+
def _create_backup(self, file_path: str) -> bool:
532541
"""
533-
Create backup of a file.
542+
Create single backup of a file if it doesn't exist.
534543
535544
Args:
536545
file_path: Path to file to backup
546+
547+
Returns:
548+
True if backup was created or already exists
537549
"""
538550
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)
541562

542563
def _create_default_component_yml(self, file_path: str) -> None:
543564
"""
@@ -573,17 +594,36 @@ def _load_component_yml(self, file_path: str) -> Dict[str, Any]:
573594

574595
def _save_component_yml(self, file_path: str, data: Dict[str, Any]) -> None:
575596
"""
576-
Save component data to YAML file.
597+
Save component data to YAML file only if changed.
577598
578599
Args:
579600
file_path: Path to YAML file
580601
data: Data to save
581602
"""
582603
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
583620
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)}")
587627

588628
def _remove_components(self, component_data: Dict[str, Any], components_to_remove: list) -> None:
589629
"""
@@ -660,16 +700,61 @@ def _convert_component_name_to_filesystem(self, component_name: str) -> str:
660700
"""
661701
return component_name.replace("/", "__")
662702

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."""
665705
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
667711

668712
build_py_path = join(self.arduino_libs_mcu, "pioarduino-build.py")
669713
backup_path = join(self.arduino_libs_mcu, f"pioarduino-build.py.{self.mcu}")
670714

715+
# Only create backup if source exists and MCU-specific backup doesn't exist
671716
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
673758

674759
def _cleanup_removed_components(self) -> None:
675760
"""Clean up removed components and restore original build file."""
@@ -723,7 +808,7 @@ def _remove_cpppath_entries(self) -> None:
723808

724809
def restore_pioarduino_build_py(self, source=None, target=None, env=None) -> None:
725810
"""
726-
Restore the original pioarduino-build.py from backup.
811+
Restore the MCU-specific pioarduino-build.py from backup.
727812
728813
Args:
729814
source: Build source (unused)
@@ -734,8 +819,35 @@ def restore_pioarduino_build_py(self, source=None, target=None, env=None) -> Non
734819
backup_path = join(self.arduino_libs_mcu, f"pioarduino-build.py.{self.mcu}")
735820

736821
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
739851

740852
def get_changes_summary(self) -> List[str]:
741853
"""

0 commit comments

Comments
 (0)