diff --git a/.github/workflows/update-dependencies.yml b/.github/workflows/update-dependencies.yml index 836f4cb..9d4b753 100644 --- a/.github/workflows/update-dependencies.yml +++ b/.github/workflows/update-dependencies.yml @@ -43,7 +43,8 @@ jobs: - name: Update dependencies run: | # Run the dependency script with the default dep-bdg.json file - python scripts/add_dependency.py + # Include debug flag for detailed output if there are errors + python scripts/add_dependency.py --debug - name: Commit changes if any run: | diff --git a/scripts/add_dependency.py b/scripts/add_dependency.py index 9d94e0c..d9430a5 100644 --- a/scripts/add_dependency.py +++ b/scripts/add_dependency.py @@ -25,6 +25,7 @@ import json import os import re +import sys import uuid import subprocess from datetime import datetime @@ -153,13 +154,42 @@ def add_to_project_file(project_content, dependency): insert_position = product_dependency_section_end.start() project_content = project_content[:insert_position] + product_dependencies + project_content[insert_position:] - # Find the PBXFrameworksBuildPhase section - frameworks_section = re.search(r'\/\* Frameworks \*\/,\s*\{\s*isa = PBXFrameworksBuildPhase;[^;]*files = \(\s*([^;]*)\s*\);', project_content) - if not frameworks_section: - raise Exception("Could not find PBXFrameworksBuildPhase section") + # Find the PBXFrameworksBuildPhase section using a more robust approach + # First, find the begin marker for the PBXFrameworksBuildPhase section + frameworks_begin = re.search(r'\/\* Begin PBXFrameworksBuildPhase section \*\/', project_content) + frameworks_end = re.search(r'\/\* End PBXFrameworksBuildPhase section \*\/', project_content) + + if not frameworks_begin or not frameworks_end: + print("Project structure diagnostic information:") + print(f"Found Begin PBXFrameworksBuildPhase: {frameworks_begin is not None}") + print(f"Found End PBXFrameworksBuildPhase: {frameworks_end is not None}") + # Look for similar sections to help with debugging + other_sections = re.findall(r'\/\* Begin ([A-Za-z]+) section \*\/', project_content) + print(f"Found these sections: {', '.join(other_sections)}") + raise Exception("Could not find PBXFrameworksBuildPhase section markers") + + # Extract the frameworks section content + frameworks_section_content = project_content[frameworks_begin.end():frameworks_end.start()] + + # Find the build phase for the main target + # This is more flexible than the previous approach + build_phase_match = re.search(r'([A-F0-9]+)\s+\/\*\s*Frameworks\s*\*\/\s+=\s+\{\s*isa\s+=\s+PBXFrameworksBuildPhase;[^{]*files\s+=\s+\(\s*(.*?)\s*\);', + frameworks_section_content, re.DOTALL) + + if not build_phase_match: + # Alternative pattern, try a more general match + build_phase_match = re.search(r'([A-F0-9]+).*?isa\s+=\s+PBXFrameworksBuildPhase;.*?files\s+=\s+\(\s*(.*?)\s*\);', + frameworks_section_content, re.DOTALL) + + if not build_phase_match: + print("Failed to find framework build phase. Section content:") + print(frameworks_section_content[:500] + "..." if len(frameworks_section_content) > 500 else frameworks_section_content) + raise Exception("Could not find PBXFrameworksBuildPhase files section") + + build_phase_id = build_phase_match.group(1) + frameworks_list = build_phase_match.group(2) # Add the products to the frameworks build phase - frameworks_list = frameworks_section.group(1) frameworks_entries = "" for product_id, product in product_ids: @@ -173,11 +203,27 @@ def add_to_project_file(project_content, dependency): # If no frameworks yet, just add the new ones new_frameworks_list = frameworks_entries.lstrip() - project_content = re.sub( - r'(\/\* Frameworks \*\/,\s*\{\s*isa = PBXFrameworksBuildPhase;[^;]*files = \()\s*([^;]*)\s*(\);)', - lambda m: f"{m.group(1)}{new_frameworks_list}{m.group(3)}", - project_content - ) + # Replace the old files list with the new one + # This is more robust as it matches the exact pattern we found + old_files_section = f"files = (\n\t\t\t\t{frameworks_list}\n\t\t\t);" + new_files_section = f"files = (\n\t\t\t\t{new_frameworks_list}\n\t\t\t);" + + project_content = project_content.replace(old_files_section, new_files_section) + + # If the exact replacement failed, try a more flexible approach + if old_files_section not in project_content: + # Create a more flexible pattern with a regex + pattern = re.compile(r'(files\s+=\s+\()(\s*.*?\s*)(\);)', re.DOTALL) + # Replace within the PBXFrameworksBuildPhase section + section_start = project_content.find("/* Begin PBXFrameworksBuildPhase section */") + section_end = project_content.find("/* End PBXFrameworksBuildPhase section */", section_start) + + if section_start != -1 and section_end != -1: + section = project_content[section_start:section_end] + updated_section = pattern.sub(lambda m: f"{m.group(1)}\n\t\t\t\t{new_frameworks_list}\n\t\t\t{m.group(3)}", section) + project_content = project_content[:section_start] + updated_section + project_content[section_end:] + else: + raise Exception("Could not find PBXFrameworksBuildPhase section boundaries for flexible replacement") # Add PBXBuildFile entries for the products build_file_section_end = re.search(r'\/\* End PBXBuildFile section \*\/', project_content) @@ -291,6 +337,8 @@ def main(): parser = argparse.ArgumentParser(description='Add Swift Package Manager dependencies to Xcode project') parser.add_argument('dependency_file', nargs='?', default=DEFAULT_DEPENDENCY_FILE, help=f'JSON file containing dependencies to add (defaults to {DEFAULT_DEPENDENCY_FILE})') + parser.add_argument('--debug', action='store_true', + help='Enable debug mode with more detailed output') args = parser.parse_args() try: @@ -317,16 +365,46 @@ def main(): # Read the current project file and package resolved project_content = read_project_file() + + # Debug: Print project file structure if debug mode is enabled + if args.debug: + print("\n--- Project Structure Analysis ---") + sections = re.findall(r'\/\* Begin ([A-Za-z]+) section \*\/', project_content) + print(f"Detected sections: {', '.join(sections)}") + + # Look for PBXFrameworksBuildPhase specifically + framework_section = re.search(r'\/\* Begin PBXFrameworksBuildPhase section \*\/(.*?)\/\* End PBXFrameworksBuildPhase section \*\/', + project_content, re.DOTALL) + if framework_section: + print("\nPBXFrameworksBuildPhase section found:") + print(framework_section.group(1)[:300] + "..." if len(framework_section.group(1)) > 300 else framework_section.group(1)) + else: + print("\nPBXFrameworksBuildPhase section NOT found!") + package_data = read_package_resolved() # Process each dependency for dependency in dependencies: print(f"Adding dependency: {dependency['name']}") # Update the project file - project_content = add_to_project_file(project_content, dependency) + try: + project_content = add_to_project_file(project_content, dependency) + except Exception as e: + print(f"Error adding {dependency['name']} to project file: {str(e)}") + if args.debug: + import traceback + traceback.print_exc() + raise # Update the package resolved - package_data = add_to_package_resolved(package_data, dependency) + try: + package_data = add_to_package_resolved(package_data, dependency) + except Exception as e: + print(f"Error adding {dependency['name']} to Package.resolved: {str(e)}") + if args.debug: + import traceback + traceback.print_exc() + raise # Write the updated files write_project_file(project_content) @@ -339,6 +417,17 @@ def main(): print(f"Error adding dependencies: {e}") import traceback traceback.print_exc() + + # Print additional diagnostic info in case of error + print("\n--- Diagnostic Information ---") + print(f"Python version: {sys.version}") + print(f"Operating system: {os.name} - {sys.platform}") + print(f"Working directory: {os.getcwd()}") + print(f"File exists - project.pbxproj: {os.path.exists(PROJECT_FILE)}") + print(f"File exists - Package.resolved: {os.path.exists(PACKAGE_RESOLVED_FILE)}") + print(f"File exists - {args.dependency_file}: {os.path.exists(args.dependency_file)}") + + sys.exit(1) # Exit with error code if __name__ == '__main__': main() diff --git a/scripts/analyze_project.py b/scripts/analyze_project.py new file mode 100644 index 0000000..0a1f3c9 --- /dev/null +++ b/scripts/analyze_project.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +""" +A diagnostic tool to analyze the structure of an Xcode project file. +This tool helps identify the correct patterns for the dependency scripts. + +Usage: + python3 analyze_project.py [project_file] +""" + +import argparse +import re +import os +import sys +import json + +DEFAULT_PROJECT_FILE = 'backdoor.xcodeproj/project.pbxproj' + +def analyze_project(project_file): + """Analyze the structure of the Xcode project file.""" + try: + with open(project_file, 'r') as f: + content = f.read() + except Exception as e: + print(f"Error reading project file: {e}") + return 1 + + print(f"\n=== Project File Analysis: {project_file} ===") + print(f"File size: {len(content)} bytes") + + # Find all section markers + begin_sections = re.findall(r'\/\* Begin ([A-Za-z]+) section \*\/', content) + end_sections = re.findall(r'\/\* End ([A-Za-z]+) section \*\/', content) + + print(f"\n=== Detected {len(begin_sections)} section types ===") + for section in sorted(set(begin_sections)): + print(f"- {section}") + + if set(begin_sections) != set(end_sections): + print("\nWARNING: Mismatched begin/end section markers!") + only_begin = set(begin_sections) - set(end_sections) + only_end = set(end_sections) - set(begin_sections) + + if only_begin: + print(f"Sections with only BEGIN marker: {', '.join(only_begin)}") + if only_end: + print(f"Sections with only END marker: {', '.join(only_end)}") + + # Analyze PBXFrameworksBuildPhase section specifically + print("\n=== PBXFrameworksBuildPhase Analysis ===") + frameworks_match = re.search(r'\/\* Begin PBXFrameworksBuildPhase section \*\/(.*?)\/\* End PBXFrameworksBuildPhase section \*\/', + content, re.DOTALL) + + if frameworks_match: + frameworks_section = frameworks_match.group(1) + print(f"Section length: {len(frameworks_section)} bytes") + + # Find all framework build phases + build_phases = re.findall(r'([A-F0-9]+)\s+\/\*\s*([^*]+)\s*\*\/\s+=\s+\{\s*isa\s+=\s+PBXFrameworksBuildPhase;(.*?)};', + frameworks_section, re.DOTALL) + + print(f"Found {len(build_phases)} framework build phases:") + + for i, (phase_id, phase_name, phase_content) in enumerate(build_phases): + print(f"\n--- Build Phase {i+1}: {phase_name.strip()} ({phase_id}) ---") + + # Extract attributes + attributes = re.findall(r'(\w+)\s*=\s*([^;]+);', phase_content) + for attr_name, attr_value in attributes: + if attr_name == "files": + file_entries = re.findall(r'([A-F0-9]+)\s+\/\*\s*([^*]+)\*\/', attr_value) + print(f" {attr_name} = ({len(file_entries)} entries)") + for file_id, file_name in file_entries[:5]: # Show first 5 + print(f" - {file_name.strip()} ({file_id})") + if len(file_entries) > 5: + print(f" ... and {len(file_entries) - 5} more") + else: + print(f" {attr_name} = {attr_value.strip()}") + else: + print("WARNING: Could not find PBXFrameworksBuildPhase section!") + + # Try to find similar sections + similar_sections = [section for section in begin_sections if "Phase" in section] + if similar_sections: + print(f"Found these similar phase sections: {', '.join(similar_sections)}") + + # Sample the first one + for section in similar_sections: + section_match = re.search(f'\/\* Begin {section} section \*\/(.*?)\/\* End {section} section \*\/', + content, re.DOTALL) + if section_match: + print(f"\nSample from {section} section (first 200 chars):") + print(section_match.group(1)[:200] + "...") + break + + # Look at PBXBuildFile section for framework entries + print("\n=== Framework Build File Entries ===") + buildfile_match = re.search(r'\/\* Begin PBXBuildFile section \*\/(.*?)\/\* End PBXBuildFile section \*\/', + content, re.DOTALL) + + if buildfile_match: + buildfile_section = buildfile_match.group(1) + framework_entries = re.findall(r'([A-F0-9]+)\s+\/\*\s*([^*]+in Frameworks)\s*\*\/\s+=\s+\{(.*?)\};', + buildfile_section, re.DOTALL) + + print(f"Found {len(framework_entries)} framework build file entries:") + for i, (file_id, file_name, file_content) in enumerate(framework_entries[:10]): # Show first 10 + print(f" - {file_name.strip()} ({file_id})") + + if len(framework_entries) > 10: + print(f" ... and {len(framework_entries) - 10} more") + else: + print("WARNING: Could not find PBXBuildFile section!") + + return 0 + +def main(): + parser = argparse.ArgumentParser(description='Analyze Xcode project structure for diagnostics') + parser.add_argument('project_file', nargs='?', default=DEFAULT_PROJECT_FILE, + help=f'Path to project.pbxproj file (defaults to {DEFAULT_PROJECT_FILE})') + args = parser.parse_args() + + return analyze_project(args.project_file) + +if __name__ == '__main__': + sys.exit(main())