-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathadd_target_exceptions.py
More file actions
184 lines (145 loc) · 5.58 KB
/
add_target_exceptions.py
File metadata and controls
184 lines (145 loc) · 5.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
#!/usr/bin/env python3
"""
Add target membership exceptions to a PBXFileSystemSynchronizedRootGroup.
Author: Deszip
Usage: python add_target_exceptions.py <json_path> <project_path> <group_name>
"""
import json
import re
import sys
from secrets import token_hex
def generate_uuid():
"""Generate Xcode-compatible 24-char hex UUID"""
return token_hex(12).upper()
def add_exception_set(content, target_uuid, target_name, excluded_files):
"""Add PBXFileSystemSynchronizedBuildFileExceptionSet to project content"""
exception_uuid = generate_uuid()
# Format file paths - each on its own line with proper indentation
formatted_files = []
for path in excluded_files:
if ' ' in path:
formatted_files.append(f'\t\t\t\t"{path}",')
else:
formatted_files.append(f'\t\t\t\t{path},')
files_text = '\n'.join(formatted_files)
# Multi-line format matching existing exception sets
entry = (
f'\t\t{exception_uuid} /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {{\n'
f'\t\t\tisa = PBXFileSystemSynchronizedBuildFileExceptionSet;\n'
f'\t\t\tmembershipExceptions = (\n'
f'{files_text}\n'
f'\t\t\t);\n'
f'\t\t\ttarget = {target_uuid} /* {target_name} */;\n'
f'\t\t}};\n'
)
# Find section
section_start = content.find(
'/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */'
)
if section_start == -1:
print("Error: Could not find PBXFileSystemSynchronizedBuildFileExceptionSet section")
return None, None
insert_pos = content.find('\n', section_start) + 1
content = content[:insert_pos] + entry + content[insert_pos:]
return content, exception_uuid
def update_group_exceptions(content, group_name, exception_uuids):
"""Add exception UUIDs to the synced root group's exceptions array"""
# Find the group definition
pattern = rf'([A-F0-9]{{24}}) /\* {re.escape(group_name)} \*/ = {{isa = PBXFileSystemSynchronizedRootGroup;[^}}]+}};'
match = re.search(pattern, content)
if not match:
print(f"Error: Could not find synced root group '{group_name}'")
return None
group_def = match.group(0)
# Build exceptions array
exceptions_list = ', '.join([
f'{uuid} /* PBXFileSystemSynchronizedBuildFileExceptionSet */'
for uuid in exception_uuids
])
# Check if exceptions array exists
if 'exceptions = (' in group_def:
# Replace existing exceptions
new_group_def = re.sub(
r'exceptions = \([^)]*\)',
f'exceptions = ({exceptions_list}, )',
group_def
)
else:
# Add exceptions after isa
new_group_def = group_def.replace(
'isa = PBXFileSystemSynchronizedRootGroup;',
f'isa = PBXFileSystemSynchronizedRootGroup; exceptions = ({exceptions_list}, );'
)
content = content.replace(group_def, new_group_def)
return content
def main():
if len(sys.argv) != 4:
print("Usage: python add_target_exceptions.py <json_path> <project_path> <group_name>")
print()
print("Example:")
print(" python add_target_exceptions.py \\")
print(" \"/tmp/xcode_target_memberships_Full_Text_Search.json\" \\")
print(" \"ReaddleDocs2.xcodeproj/project.pbxproj\" \\")
print(" \"Full Text Search\"")
sys.exit(1)
json_path = sys.argv[1]
project_path = sys.argv[2]
group_name = sys.argv[3]
print(f"\n=== Adding Target Exceptions ===")
print(f"Group: {group_name}")
print(f"JSON: {json_path}")
print(f"Project: {project_path}")
print()
# Read JSON with target memberships
try:
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
except Exception as e:
print(f"Error reading JSON file: {e}")
return 1
# Read project file
try:
with open(project_path, 'r', encoding='utf-8') as f:
content = f.read()
except Exception as e:
print(f"Error reading project file: {e}")
return 1
print(f"Loaded {len(data['files'])} file memberships")
print()
# Create exception sets for each target
exception_uuids = []
for target_name, target_uuid in data['targets'].items():
excluded_files = []
# Find files that should NOT be in this target
for file_path, file_data in data['files'].items():
if target_name not in file_data['targets']:
excluded_files.append(file_path)
if excluded_files:
print(f" {target_name}: Excluding {len(excluded_files)} files")
content, exception_uuid = add_exception_set(
content, target_uuid, target_name, excluded_files
)
if exception_uuid:
exception_uuids.append(exception_uuid)
else:
print(f" {target_name}: No exclusions (all files included)")
print()
print(f"Created {len(exception_uuids)} exception sets")
print()
# Update group with exception references
print(f"Updating '{group_name}' group with exception references...")
content = update_group_exceptions(content, group_name, exception_uuids)
if content is None:
return 1
# Write back
try:
with open(project_path, 'w', encoding='utf-8') as f:
f.write(content)
except Exception as e:
print(f"Error writing project file: {e}")
return 1
print(f"✓ Added {len(exception_uuids)} exception sets to '{group_name}'")
print()
return 0
if __name__ == '__main__':
sys.exit(main())