Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/update-dependencies.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
113 changes: 101 additions & 12 deletions scripts/add_dependency.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import json
import os
import re
import sys
import uuid
import subprocess
from datetime import datetime
Expand Down Expand Up @@ -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:
Expand All @@ -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)
Expand Down Expand Up @@ -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:
Expand All @@ -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)
Expand All @@ -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()
125 changes: 125 additions & 0 deletions scripts/analyze_project.py
Original file line number Diff line number Diff line change
@@ -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())
Loading