forked from spritebuilder/SpriteBuilder
/
CCBProjectCreator.m
239 lines (192 loc) · 11.6 KB
/
CCBProjectCreator.m
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
//
// CCBProjectCreator.m
// SpriteBuilder
//
// Created by Viktor on 10/11/13.
//
//
#import "CCBProjectCreator.h"
#import "AppDelegate.h"
#import "CCBFileUtil.h"
@implementation NSString (IdentifierSanitizer)
- (NSString *)sanitizedIdentifier
{
NSString* identifier = [self stringByTrimmingCharactersInSet:[[NSCharacterSet letterCharacterSet] invertedSet]];
NSMutableString* sanitized = [NSMutableString new];
for (int idx = 0; idx < [identifier length]; idx++)
{
unichar ch = [identifier characterAtIndex:idx];
if (!isalpha(ch))
{
ch = '_';
}
[sanitized appendString:[NSString stringWithCharacters:&ch length:1]];
}
NSString *trimmed = [sanitized stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"_"]];
if ([trimmed length] == 0)
{
trimmed = @"identifier";
}
return trimmed;
}
@end
@implementation CCBProjectCreator
-(BOOL) createDefaultProjectAtPath:(NSString*)fileName engine:(CCBTargetEngine)engine programmingLanguage:(CCBProgrammingLanguage)programmingLanguage
{
NSError *error = nil;
NSFileManager* fm = [NSFileManager defaultManager];
NSString* substitutableProjectName = @"PROJECTNAME";
NSString* substitutableProjectIdentifier = @"PROJECTIDENTIFIER";
NSString* parentPath = [fileName stringByDeletingLastPathComponent];
if (engine == CCBTargetEngineSpriteKit)
{
substitutableProjectName = [NSString stringWithFormat:@"SPRITEKIT%@", substitutableProjectName];
}
NSString* zipFile = [[NSBundle mainBundle] pathForResource:substitutableProjectName ofType:@"zip" inDirectory:@"Generated"];
// Check that zip file exists
if (![fm fileExistsAtPath:zipFile])
{
[[AppDelegate appDelegate] modalDialogTitle:@"Failed to Create Project"
message:@"The default SpriteBuilder project is missing from this build. Make sure that you build SpriteBuilder using 'Scripts/build_distribution.py --version <versionstr>' the first time you build the program."];
return NO;
}
// Unzip resources
NSTask* zipTask = [[NSTask alloc] init];
[zipTask setCurrentDirectoryPath:parentPath];
[zipTask setLaunchPath:@"/usr/bin/unzip"];
NSArray* args = [NSArray arrayWithObjects:@"-o", zipFile, nil];
[zipTask setArguments:args];
[zipTask launch];
[zipTask waitUntilExit];
// Rename ccbproj
NSString* ccbproj = [NSString stringWithFormat:@"%@.ccbproj", substitutableProjectName];
[fm moveItemAtPath:[parentPath stringByAppendingPathComponent:ccbproj] toPath:fileName error:NULL];
// Update the Xcode project
NSString* xcodeproj = [NSString stringWithFormat:@"%@.xcodeproj", substitutableProjectName];
NSString* xcodeFileName = [parentPath stringByAppendingPathComponent:xcodeproj];
NSString* projName = [[fileName lastPathComponent] stringByDeletingPathExtension];
NSString* identifier = [projName sanitizedIdentifier];
// Update the project
NSString *pbxprojFile = [xcodeFileName stringByAppendingPathComponent:@"project.pbxproj"];
[self setName:projName inFile:pbxprojFile search:substitutableProjectName];
[self setName:identifier inFile:pbxprojFile search:substitutableProjectIdentifier];
if (programmingLanguage == CCBProgrammingLanguageObjectiveC)
{
[self setName:@"IPHONEOS_DEPLOYMENT_TARGET = 5.0"
inFile:pbxprojFile
search:@"IPHONEOS_DEPLOYMENT_TARGET = 7.0"];
[self removeLinesMatching:@".*MainScene[.]swift.*" inFile:pbxprojFile];
}
else if (programmingLanguage == CCBProgrammingLanguageSwift)
{
[self removeLinesMatching:@".*MainScene[.][hm].*" inFile:pbxprojFile];
}
// Update workspace data
[self setName:projName inFile:[xcodeFileName stringByAppendingPathComponent:@"project.xcworkspace/contents.xcworkspacedata"] search:substitutableProjectName];
NSArray *platforms = @[@"iOS", @"Android", @"Mac"];
for (id platform in platforms) {
// Update scheme
NSString* templateScheme = [NSString stringWithFormat:@"xcshareddata/xcschemes/%@ %@.xcscheme", substitutableProjectName, platform];
[self setName:projName inFile:[xcodeFileName stringByAppendingPathComponent:templateScheme] search:substitutableProjectName];
// Rename scheme file
NSString* schemeFile = [xcodeFileName stringByAppendingPathComponent:templateScheme];
NSString* format = [@"iOS" isEqualToString:platform] ? @"%@" : @"%@ %@"; // we want iOS on top
NSString* newSchemeFile = [[[schemeFile stringByDeletingLastPathComponent] stringByAppendingPathComponent:[NSString stringWithFormat:format, projName, platform]]
stringByAppendingPathExtension:@"xcscheme"];
if (![fm moveItemAtPath:schemeFile toPath:newSchemeFile error:&error])
{
return NO;
}
if (![@"iOS" isEqualToString:platform] && programmingLanguage == CCBProgrammingLanguageSwift)
{
// Hide scheme for non-iOS Swift projects for now
if (![fm removeItemAtPath:newSchemeFile error:&error])
{
return NO;
}
}
// Update plist
NSString* plistFileName = [parentPath stringByAppendingPathComponent:[NSString stringWithFormat:@"Source/Resources/Platforms/%@/Info.plist", platform]];
[self setName:identifier inFile:plistFileName search:substitutableProjectIdentifier];
[self setName:projName inFile:plistFileName search:substitutableProjectName];
}
// Rename Xcode project file
NSString* newXcodeFileName = [[[xcodeFileName stringByDeletingLastPathComponent] stringByAppendingPathComponent:projName] stringByAppendingPathExtension:@"xcodeproj"];
[fm moveItemAtPath:xcodeFileName toPath:newXcodeFileName error:NULL];
// Update Mac Xib file
NSString* xibFileName = [parentPath stringByAppendingPathComponent:@"Source/Resources/Platforms/Mac/MainMenu.xib"];
[self setName:identifier inFile:xibFileName search:substitutableProjectIdentifier];
[self setName:projName inFile:xibFileName search:substitutableProjectName];
// Rename Approj project file (apportable)
NSString* approjFileName = [parentPath stringByAppendingPathComponent:@"PROJECTNAME.approj"];
projName = [[fileName lastPathComponent] stringByDeletingPathExtension];
NSString* newApprojFileName = [[[approjFileName stringByDeletingLastPathComponent] stringByAppendingPathComponent:projName] stringByAppendingPathExtension:@"approj"];
[fm moveItemAtPath:approjFileName toPath:newApprojFileName error:NULL];
// Android
NSString* activityJavaFileName = [parentPath stringByAppendingPathComponent:[NSString stringWithFormat:@"Source/Platforms/Android/java/org/cocos2d/%@/%@Activity.java", substitutableProjectIdentifier, substitutableProjectIdentifier]];
if ([fm fileExistsAtPath:activityJavaFileName])
{
NSString* resultActivityJavaFileName = [parentPath stringByAppendingPathComponent:[NSString stringWithFormat:@"Source/Platforms/Android/java/org/cocos2d/%@/%@Activity.java", identifier, identifier]];
if (![fm createDirectoryAtPath:[resultActivityJavaFileName stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:&error]) {
return NO;
}
if (![fm moveItemAtPath:activityJavaFileName toPath:resultActivityJavaFileName error:&error]) {
return NO;
}
[self setName:identifier inFile:resultActivityJavaFileName search:substitutableProjectIdentifier];
NSString* activityMFileName = [parentPath stringByAppendingPathComponent:[NSString stringWithFormat:@"Source/Platforms/Android/%@Activity.m", substitutableProjectIdentifier]];
NSString* resultActivityMFileName = [parentPath stringByAppendingPathComponent:[NSString stringWithFormat:@"Source/Platforms/Android/%@Activity.m", identifier]];
if (![fm moveItemAtPath:activityMFileName toPath:resultActivityMFileName error:&error]) {
return NO;
}
[self setName:identifier inFile:resultActivityMFileName search:substitutableProjectIdentifier];
NSString* activityHFileName = [parentPath stringByAppendingPathComponent:[NSString stringWithFormat:@"Source/Platforms/Android/%@Activity.h", substitutableProjectIdentifier]];
NSString* resultActivityHFileName = [parentPath stringByAppendingPathComponent:[NSString stringWithFormat:@"Source/Platforms/Android/%@Activity.h", identifier]];
if (![fm moveItemAtPath:activityHFileName toPath:resultActivityHFileName error:&error]) {
return NO;
}
[self setName:identifier inFile:resultActivityHFileName search:substitutableProjectIdentifier];
NSString* manifestFileName = [parentPath stringByAppendingPathComponent:@"Source/Resources/Platforms/Android/AndroidManifest.xml"];
[self setName:identifier inFile:manifestFileName search:substitutableProjectIdentifier];
[self setName:projName inFile:manifestFileName search:substitutableProjectName];
}
// configure default configuration.json and include opengles2 as a feature
NSString *apportableConfigFile = [NSString stringWithFormat:@"%@%@", newApprojFileName, @"/configuration.json"];
NSString *apportableConfigurationContents = [NSString stringWithContentsOfFile:apportableConfigFile encoding:NSUTF8StringEncoding error:&error];
NSString *replacement = [NSString stringWithFormat:@"\"default_target\": {\"project\": \"%@\", \"project_config\": \"Release\", \"target\": \"%@\"},", projName, projName];
apportableConfigurationContents = [apportableConfigurationContents stringByReplacingOccurrencesOfString:@"default_target" withString:replacement];
[apportableConfigurationContents writeToFile:apportableConfigFile atomically:YES encoding:NSUTF8StringEncoding error:&error];
// perform cleanup to remove unnecessary files which only bloat the project
[CCBFileUtil cleanupSpriteBuilderProjectAtPath:fileName];
return [fm fileExistsAtPath:fileName];
}
- (void) setName:(NSString*)name inFile:(NSString*)fileName search:(NSString*)searchStr
{
NSMutableData *fileData = [NSMutableData dataWithContentsOfFile:fileName];
NSData *search = [searchStr dataUsingEncoding:NSUTF8StringEncoding];
NSData *replacement = [name dataUsingEncoding:NSUTF8StringEncoding];
NSRange found;
do {
found = [fileData rangeOfData:search options:0 range:NSMakeRange(0, [fileData length])];
if (found.location != NSNotFound)
{
[fileData replaceBytesInRange:found withBytes:[replacement bytes] length:[replacement length]];
}
} while (found.location != NSNotFound && found.length > 0);
[fileData writeToFile:fileName atomically:YES];
}
- (void) removeLinesMatching:(NSString*)pattern inFile:(NSString*)fileName
{
NSData *fileData = [NSData dataWithContentsOfFile:fileName];
NSString *fileString = [[NSString alloc] initWithData:fileData encoding:NSUTF8StringEncoding];
NSRegularExpression *regex = [NSRegularExpression
regularExpressionWithPattern:pattern
options:NSRegularExpressionCaseInsensitive error:nil];
NSString *updatedString = [regex stringByReplacingMatchesInString:fileString
options:0
range:NSMakeRange(0, [fileString length])
withTemplate:@""];
NSData *updatedFileData = [updatedString dataUsingEncoding:NSUTF8StringEncoding];
[updatedFileData writeToFile:fileName atomically:YES];
}
@end