forked from appsquickly/XcodeEditor
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathXCProject+SubProject.m
More file actions
378 lines (353 loc) · 17.7 KB
/
XCProject+SubProject.m
File metadata and controls
378 lines (353 loc) · 17.7 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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
////////////////////////////////////////////////////////////////////////////////
//
// JASPER BLUES
// Copyright 2012 Jasper Blues
// All Rights Reserved.
//
// NOTICE: Jasper Blues permits you to use, modify, and distribute this file
// in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////
#import "XCSourceFile.h"
#import "XCTarget.h"
#import "XCKeyBuilder.h"
#import "XCProject+SubProject.h"
#import "XCSubProjectDefinition.h"
#import <Foundation/Foundation.h>
@implementation XCProject (SubProject)
#pragma mark sub-project related public methods
// returns the key for the reference proxy with the given path (nil if not found)
// does not use keysForProjectObjectsOfType:withIdentifier: because the identifier it uses for
// PBXReferenceProxy is different.
- (NSString *)referenceProxyKeyForName:(NSString *)name
{
__block NSString *result = nil;
[[self objects] enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSDictionary *obj, BOOL *stop) {
if ([[obj valueForKey:@"isa"] xce_hasReferenceProxyType]) {
NSString *candidate = [obj valueForKey:@"path"];
if ([candidate isEqualToString:name]) {
result = key;
*stop = YES;
}
}
}];
return result;
}
// returns an array of build products, excluding bundles with extensions other than ".bundle" (which is kind
// of gross, but I didn't see a better way to exclude test bundles without giving them their own XcodeSourceFileType)
- (NSArray *)buildProductsForTargets:(NSString *)xcodeprojKey
{
NSMutableArray *results = [[NSMutableArray alloc] init];
[[self objects] enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSDictionary *obj, BOOL *stop) {
if ([[obj valueForKey:@"isa"] xce_hasReferenceProxyType]) {
// make sure it belongs to the xcodeproj we're adding
NSString *remoteRef = [obj valueForKey:@"remoteRef"];
NSDictionary *containerProxy = [[self objects] valueForKey:remoteRef];
NSString *containerPortal = [containerProxy valueForKey:@"containerPortal"];
if ([containerPortal isEqualToString:xcodeprojKey]) {
XcodeSourceFileType type = XCSourceFileTypeFromStringRepresentation([obj valueForKey:@"fileType"]);
NSString *path = (NSString *)[obj valueForKey:@"path"];
if (type != Bundle || [[path pathExtension] isEqualToString:@"bundle"]) {
[results addObject:[XCSourceFile sourceFileWithProject:self key:key type:type name:path
sourceTree:nil path:nil]];
}
}
}
}];
return results;
}
// makes PBXContainerItemProxy and PBXTargetDependency objects for the xcodeproj, and adds the dependency key
// to all the specified targets
- (void)addAsTargetDependency:(XCSubProjectDefinition *)xcodeprojDefinition toTargets:(NSArray *)targets
{
for (XCTarget *target in targets) {
// make a new PBXContainerItemProxy
NSString *key = [[self fileWithName:[xcodeprojDefinition pathRelativeToProjectRoot]] key];
NSString *containerItemProxyKey = [self makeContainerItemProxyForName:[xcodeprojDefinition name] fileRef:key
proxyType:@"1" uniqueName:[target name]];
// make a PBXTargetDependency
NSString *targetDependencyKey = [self makeTargetDependency:[xcodeprojDefinition name]
forContainerItemProxyKey:containerItemProxyKey uniqueName:[target name]];
// add entry in each targets dependencies list
[target addDependency:targetDependencyKey];
}
}
// returns an array of keys for all project objects (not just files) that match the given criteria. Since this is
// a convenience method intended to save typing elsewhere, each type has its own field to match to rather than each
// matching on name or path as you might expect.
- (NSArray *)keysForProjectObjectsOfType:(XcodeMemberType)memberType withIdentifier:(NSString *)identifier
singleton:(BOOL)singleton required:(BOOL)required
{
__block NSMutableArray *returnValue = [[NSMutableArray alloc] init];
[[self objects] enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSDictionary *obj, BOOL *stop) {
if ([[obj valueForKey:@"isa"] xce_asMemberType] == memberType) {
if (memberType == PBXContainerItemProxyType) {
if ([[obj valueForKey:@"containerPortal"] isEqualToString:identifier]) {
[returnValue addObject:key];
}
}
else if (memberType == PBXReferenceProxyType) {
if ([[obj valueForKey:@"remoteRef"] isEqualToString:identifier]) {
[returnValue addObject:key];
}
}
else if (memberType == PBXTargetDependencyType || memberType == PBXGroupType || memberType == PBXVariantGroupType) {
if ([[obj valueForKey:@"name"] isEqualToString:identifier]) {
[returnValue addObject:key];
}
}
else if (memberType == PBXNativeTargetType) {
for (NSString *dependencyKey in [obj valueForKey:@"dependencies"]) {
if ([dependencyKey isEqualToString:identifier]) {
[returnValue addObject:key];
}
}
}
else if (memberType == PBXBuildFileType) {
if ([[obj valueForKey:@"fileRef"] isEqualToString:identifier]) {
[returnValue addObject:key];
}
}
else if (memberType == PBXProjectType) {
[returnValue addObject:key];
}
else if (memberType == PBXFileReferenceType) {
if ([[obj valueForKey:@"path"] isEqualToString:identifier]) {
[returnValue addObject:key];
}
}
else if (memberType == PBXFrameworksBuildPhaseType || memberType == PBXResourcesBuildPhaseType) {
[returnValue addObject:key];
}
else {
[NSException raise:NSInvalidArgumentException
format:@"Unrecognized member type %@", [NSString xce_stringFromMemberType:memberType]];
}
}
}];
if (singleton && [returnValue count] > 1) {
[NSException raise:NSGenericException
format:@"Searched for one instance of member type %@ with value %@, but found %ld",
[NSString xce_stringFromMemberType:memberType], identifier,
(unsigned long) [returnValue count]];
}
if (required && [returnValue count] == 0) {
[NSException raise:NSGenericException
format:@"Searched for instances of member type %@ with value %@, but did not find any",
[NSString xce_stringFromMemberType:memberType], identifier];
}
return returnValue;
}
// returns the dictionary for the PBXProject. Raises an exception if more or less than 1 are found.
- (NSMutableDictionary *)PBXProjectDict
{
NSString *PBXProjectKey;
NSArray *PBXProjectKeys = [self keysForProjectObjectsOfType:PBXProjectType withIdentifier:nil singleton:YES
required:YES];
PBXProjectKey = [PBXProjectKeys objectAtIndex:0];
NSMutableDictionary *PBXProjectDict = [[self objects] valueForKey:PBXProjectKey];
return PBXProjectDict;
}
// returns the key of the PBXContainerItemProxy for the given name and proxy type. nil if not found.
- (NSString *)containerItemProxyKeyForName:(NSString *)name proxyType:(NSString *)proxyType
{
NSMutableArray *results = [[NSMutableArray alloc] init];
[[self objects] enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSDictionary *obj, BOOL *stop) {
if ([[obj valueForKey:@"isa"] xce_hasContainerItemProxyType]) {
NSString *remoteInfo = [obj valueForKey:@"remoteInfo"];
NSString *proxy = [obj valueForKey:@"proxyType"];
if ([remoteInfo isEqualToString:name] && [proxy isEqualToString:proxyType]) {
[results addObject:key];
}
}
}];
if ([results count] > 1) {
[NSException raise:NSGenericException
format:@"Searched for one instance of member type %@ with value %@, but found %ld",
@"PBXContainerItemProxy", [NSString stringWithFormat:@"%@ and proxyType of %@", name, proxyType],
(unsigned long) [results count]];
}
if ([results count] == 0) {
return nil;
}
return results[0];
}
//-------------------------------------------------------------------------------------------
#pragma mark - Private Methods
//-------------------------------------------------------------------------------------------
// makes a PBXContainerItemProxy object for a given PBXFileReference object. Replaces pre-existing objects.
- (NSString *)makeContainerItemProxyForName:(NSString *)name fileRef:(NSString *)fileRef proxyType:(NSString *)proxyType
uniqueName:(NSString *)uniqueName
{
NSString *keyName;
if (uniqueName != nil) {
keyName = [NSString stringWithFormat:@"%@-%@", name, uniqueName];
}
else {
keyName = name;
}
// remove old if it exists
NSString *existingProxyKey = [self containerItemProxyKeyForName:keyName proxyType:proxyType];
if (existingProxyKey) {
[[self objects] removeObjectForKey:existingProxyKey];
}
// make new one
NSMutableDictionary *proxy = [NSMutableDictionary dictionary];
proxy[@"isa"] = [NSString xce_stringFromMemberType:PBXContainerItemProxyType];
proxy[@"containerPortal"] = fileRef;
proxy[@"proxyType"] = proxyType;
// give it a random key - the keys xcode puts here are not in the project file anywhere else
NSString *key = [[XCKeyBuilder forItemNamed:[NSString stringWithFormat:@"%@-junk", keyName]] build];
proxy[@"remoteGlobalIDString"] = key;
proxy[@"remoteInfo"] = name;
// add to project. use proxyType to generate key, so that multiple keys for the same name don't overwrite each other
key = [[XCKeyBuilder forItemNamed:[NSString stringWithFormat:@"%@-containerProxy-%@", keyName, proxyType]] build];
[self objects][key] = proxy;
return key;
}
// makes a PBXReferenceProxy object for a given PBXContainerProxy object. Replaces pre-existing objects.
- (void)makeReferenceProxyForContainerItemProxy:(NSString *)containerItemProxyKey
buildProductReference:(NSDictionary *)buildProductReference
{
NSString *path = [buildProductReference valueForKey:@"path"];
// remove old if any exists
NSArray *existingProxyKeys = [self keysForProjectObjectsOfType:PBXReferenceProxyType withIdentifier:path
singleton:NO required:NO];
if ([existingProxyKeys count] > 0) {
for (NSString *existingProxyKey in existingProxyKeys) {
[[self objects] removeObjectForKey:existingProxyKey];
}
}
// make new one
NSMutableDictionary *proxy = [NSMutableDictionary dictionary];
proxy[@"isa"] = [NSString xce_stringFromMemberType:PBXReferenceProxyType];
proxy[@"fileType"] = [buildProductReference valueForKey:@"explicitFileType"];
proxy[@"path"] = path;
proxy[@"remoteRef"] = containerItemProxyKey;
proxy[@"sourceTree"] = [buildProductReference valueForKey:@"sourceTree"];
// add to project
NSString *key = [[XCKeyBuilder forItemNamed:[NSString stringWithFormat:@"%@-referenceProxy", path]] build];
[self objects][key] = proxy;
}
// makes a PBXTargetDependency object for a given PBXContainerItemProxy. Replaces pre-existing objects.
- (NSString *)makeTargetDependency:(NSString *)name forContainerItemProxyKey:(NSString *)containerItemProxyKey
uniqueName:(NSString *)uniqueName
{
NSString *keyName;
if (uniqueName != nil) {
keyName = [NSString stringWithFormat:@"%@-%@", name, uniqueName];
}
else {
keyName = name;
}
// remove old if it exists
NSArray *existingDependencyKeys = [self keysForProjectObjectsOfType:PBXTargetDependencyType withIdentifier:keyName
singleton:NO required:NO];
if ([existingDependencyKeys count] > 0) {
for (NSString *existingDependencyKey in existingDependencyKeys) {
[[self objects] removeObjectForKey:existingDependencyKey];
}
}
// make new one
NSMutableDictionary *targetDependency = [NSMutableDictionary dictionary];
targetDependency[@"isa"] = [NSString xce_stringFromMemberType:PBXTargetDependencyType];
targetDependency[@"name"] = name;
targetDependency[@"targetProxy"] = containerItemProxyKey;
NSString *targetDependencyKey = [[XCKeyBuilder forItemNamed:[NSString stringWithFormat:@"%@-targetProxy", keyName]]
build];
[self objects][targetDependencyKey] = targetDependency;
return targetDependencyKey;
}
// make a PBXContainerItemProxy and PBXReferenceProxy for each target in the subProject
- (void)addProxies:(XCSubProjectDefinition *)xcodeproj
{
NSString *fileRef = [[self fileWithName:[xcodeproj pathRelativeToProjectRoot]] key];
for (NSDictionary *target in [xcodeproj.subProject targets]) {
NSString *containerItemProxyKey = [self makeContainerItemProxyForName:[target valueForKey:@"name"]
fileRef:fileRef proxyType:@"2" uniqueName:nil];
NSString *productFileReferenceKey = [target valueForKey:@"productReference"];
NSDictionary *productFileReference = [[xcodeproj.subProject objects] valueForKey:productFileReferenceKey];
[self makeReferenceProxyForContainerItemProxy:containerItemProxyKey buildProductReference:productFileReference];
}
}
// remove the PBXContainerItemProxy and PBXReferenceProxy objects for the given object key (which is the PBXFilereference
// for the xcodeproj file)
- (void)removeProxies:(NSString *)xcodeprojKey
{
NSMutableArray *keysToDelete = [NSMutableArray array];
// use the xcodeproj's PBXFileReference key to get the PBXContainerItemProxy keys
NSArray *containerItemProxyKeys = [self keysForProjectObjectsOfType:PBXContainerItemProxyType
withIdentifier:xcodeprojKey singleton:NO required:YES];
// use the PBXContainerItemProxy keys to get the PBXReferenceProxy keys
for (NSString *key in containerItemProxyKeys) {
[keysToDelete addObjectsFromArray:[self keysForProjectObjectsOfType:PBXReferenceProxyType withIdentifier:key
singleton:NO required:NO]];
[keysToDelete addObject:key];
}
// remove all objects located above
[keysToDelete enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[[self objects] removeObjectForKey:obj];
}];
}
// returns the Products _group key for the given PBXFileReference key, nil if not found.
- (NSString *)productsGroupKeyForKey:(NSString *)key
{
NSMutableArray *projectReferences = [[self PBXProjectDict] valueForKey:@"projectReferences"];
NSString *productsGroupKey = nil;
for (NSDictionary *projectRef in projectReferences) {
if ([[projectRef valueForKey:@"ProjectRef"] isEqualToString:key]) {
// it's an error if we find more than one
if (productsGroupKey != nil) {
[NSException raise:NSGenericException format:@"Found more than one project reference for key %@", key];
}
productsGroupKey = [projectRef valueForKey:@"ProductGroup"];
}
}
return productsGroupKey;
}
// removes a file reference from the projectReferences array in PBXProject (removing the array itself if this action
// leaves it empty).
- (void)removeFromProjectReferences:(NSString *)key forProductsGroup:(NSString *)productsGroupKey
{
NSMutableArray *projectReferences = [[self PBXProjectDict] valueForKey:@"projectReferences"];
// remove entry from PBXProject's projectReferences
NSMutableArray *referencesToRemove = [NSMutableArray array];
for (NSDictionary *projectRef in projectReferences) {
if ([[projectRef valueForKey:@"ProjectRef"] isEqualToString:key]) {
[referencesToRemove addObject:projectRef];
}
}
for (NSDictionary *projectRef in referencesToRemove) {
[projectReferences removeObject:projectRef];
}
// if that was the last project reference, remove the array from the project
if ([projectReferences count] == 0) {
[[self PBXProjectDict] removeObjectForKey:@"projectReferences"];
}
}
// removes a specific xcodeproj file from any targets (by name). It's not an error if no entries are found,
// because we support adding a project file without adding it to any targets.
- (void)removeTargetDependencies:(NSString *)name
{
// get the key for the PBXTargetDependency with name = xcodeproj file name (without extension)
NSArray *targetDependencyKeys = [self keysForProjectObjectsOfType:PBXTargetDependencyType withIdentifier:name
singleton:NO required:NO];
// we might not find any if the project wasn't added to targets in the first place
if ([targetDependencyKeys count] == 0) {
return;
}
NSString *targetDependencyKey = targetDependencyKeys[0];
// use the key for the PBXTargetDependency to get the key for any PBXNativeTargets that depend on it
NSArray *nativeTargetKeys = [self keysForProjectObjectsOfType:PBXNativeTargetType withIdentifier:targetDependencyKey
singleton:NO required:NO];
// remove the key for the PBXTargetDependency from the PBXNativeTarget's dependencies arrays (leave in place even if empty)
for (NSString *nativeTargetKey in nativeTargetKeys) {
NSMutableDictionary *nativeTarget = [self objects][nativeTargetKey];
NSMutableArray *dependencies = [nativeTarget valueForKey:@"dependencies"];
[dependencies removeObject:targetDependencyKey];
nativeTarget[@"dependencies"] = dependencies;
}
// remove the PBXTargetDependency
[[self objects] removeObjectForKey:targetDependencyKey];
}
@end