#!/usr/bin/env python3 """ Updates Solverv.xcodeproj/project.pbxproj to: - Add Shared/{Models,Utilities} file references for 5 shared files - Add those files to both targets' PBXSourcesBuildPhase - Remove old duplicate file references and build entries from both targets - Add a Shared group in the project navigator Run from the project root: python3 scripts/update_pbxproj.py """ import re import secrets PROJ = "Solverv.xcodeproj/project.pbxproj" MODELS_FILES = ["Season.swift", "SolsticeEvent.swift", "SolsticeData.swift"] UTIL_FILES = ["AppGroupManager.swift", "SunTimes.swift"] ALL_FILES = MODELS_FILES + UTIL_FILES def uid(): return secrets.token_hex(12).upper() with open(PROJ) as f: src = f.read() # ── 1. Find existing file-reference UUIDs (expect 2 per name: one per target) ── old_ref_uids = {} for name in ALL_FILES: pat = r'([0-9A-Fa-f]{24})\s*/\*\s*' + re.escape(name) + r'\s*\*/' found = re.findall(pat, src) old_ref_uids[name] = [u.upper() for u in found] if len(found) != 2: print(f"WARNING: expected 2 PBXFileReference entries for {name}, found {len(found)}: {found}") # ── 2. Find existing build-file UUIDs (expect 2 per name: one per target) ── old_build_uids = {} for name in ALL_FILES: pat = r'([0-9A-Fa-f]{24})\s*/\*\s*' + re.escape(name) + r'\s+in\s+Sources\s*\*/' found = re.findall(pat, src) old_build_uids[name] = [u.upper() for u in found] if len(found) != 2: print(f"WARNING: expected 2 PBXBuildFile entries for {name}, found {len(found)}: {found}") # ── 3. Generate new UUIDs ── new_ref_uid = {name: uid() for name in ALL_FILES} new_build_uid = {name: [uid(), uid()] for name in ALL_FILES} # [target0, target1] # ── 4. Insert new PBXFileReference entries ── new_refs = "\n".join( f'\t\t{new_ref_uid[name]} /* {name} */ = ' f'{{isa = PBXFileReference; lastKnownFileType = sourcecode.swift; ' f'path = {name}; sourceTree = ""; }};' for name in ALL_FILES ) + "\n" src = src.replace( "/* Begin PBXFileReference section */", "/* Begin PBXFileReference section */\n" + new_refs, 1 ) # ── 5. Insert new PBXBuildFile entries ── new_builds = "\n".join( f'\t\t{new_build_uid[name][i]} /* {name} in Sources */ = ' f'{{isa = PBXBuildFile; fileRef = {new_ref_uid[name]} /* {name} */; }};' for name in ALL_FILES for i in range(2) ) + "\n" src = src.replace( "/* Begin PBXBuildFile section */", "/* Begin PBXBuildFile section */\n" + new_builds, 1 ) # ── 6. Add new build files to both PBXSourcesBuildPhase sections ── phases = list(re.finditer( r'(isa = PBXSourcesBuildPhase;.*?files = \()(.*?)(\);)', src, re.DOTALL )) if len(phases) != 2: print(f"ERROR: expected 2 PBXSourcesBuildPhase, found {len(phases)}. Aborting.") exit(1) # Replace in reverse order so offsets stay valid for i, phase in reversed(list(enumerate(phases))): additions = "".join( f'\t\t\t\t{new_build_uid[name][i]} /* {name} in Sources */,\n' for name in ALL_FILES ) new_phase = phase.group(1) + phase.group(2) + additions + phase.group(3) src = src[:phase.start()] + new_phase + src[phase.end():] # ── 7. Remove old build-file entries from PBXSourcesBuildPhase ── for name in ALL_FILES: for bu in old_build_uids[name]: src = re.sub(r'\t{3,4}' + bu + r'\s*/\*[^*]*\*/,\n', '', src) # ── 8. Remove old PBXBuildFile entries ── for name in ALL_FILES: for bu in old_build_uids[name]: src = re.sub(r'\t\t' + bu + r'\s*/\*[^*]*\*/\s*=\s*\{[^}]*\};\n', '', src) # ── 9. Remove old PBXFileReference entries ── for name in ALL_FILES: for ru in old_ref_uids[name]: src = re.sub(r'\t\t' + ru + r'\s*/\*[^*]*\*/\s*=\s*\{[^}]*\};\n', '', src) # ── 10. Remove old file refs from PBXGroup children ── for name in ALL_FILES: for ru in old_ref_uids[name]: src = re.sub(r'\t{3,4}' + ru + r'\s*/\*[^*]*\*/,\n', '', src) # ── 11. Add Shared group hierarchy ── models_grp_uid = uid() utils_grp_uid = uid() shared_grp_uid = uid() models_children = "".join( f'\t\t\t\t{new_ref_uid[n]} /* {n} */,\n' for n in MODELS_FILES ) utils_children = "".join( f'\t\t\t\t{new_ref_uid[n]} /* {n} */,\n' for n in UTIL_FILES ) new_groups = ( f'\t\t{models_grp_uid} /* Models */ = {{\n' f'\t\t\tisa = PBXGroup;\n' f'\t\t\tchildren = (\n' f'{models_children}' f'\t\t\t);\n' f'\t\t\tpath = Models;\n' f'\t\t\tsourceTree = "";\n' f'\t\t}};\n' f'\t\t{utils_grp_uid} /* Utilities */ = {{\n' f'\t\t\tisa = PBXGroup;\n' f'\t\t\tchildren = (\n' f'{utils_children}' f'\t\t\t);\n' f'\t\t\tpath = Utilities;\n' f'\t\t\tsourceTree = "";\n' f'\t\t}};\n' f'\t\t{shared_grp_uid} /* Shared */ = {{\n' f'\t\t\tisa = PBXGroup;\n' f'\t\t\tchildren = (\n' f'\t\t\t\t{models_grp_uid} /* Models */,\n' f'\t\t\t\t{utils_grp_uid} /* Utilities */,\n' f'\t\t\t);\n' f'\t\t\tpath = Shared;\n' f'\t\t\tsourceTree = "";\n' f'\t\t}};\n' ) src = src.replace( "/* Begin PBXGroup section */", "/* Begin PBXGroup section */\n" + new_groups, 1 ) # Add Shared group as child of the main project group main_grp_match = re.search(r'mainGroup = ([0-9A-Fa-f]{24})', src) if main_grp_match: main_grp_uid = main_grp_match.group(1).upper() # Find that group's children list and prepend the Shared group src = re.sub( r'(' + main_grp_uid + r'\s*/\*[^*]*\*/\s*=\s*\{[^{]*isa = PBXGroup;[^{]*children = \()', r'\1\n\t\t\t\t' + shared_grp_uid + r' /* Shared */,', src ) else: print("WARNING: could not find mainGroup — add Shared group manually in Xcode") with open(PROJ, "w") as f: f.write(src) print("project.pbxproj updated.") print("Open Xcode and verify the Shared group appears with all 5 files under both targets.")