UNPKG

56.8 kBJavaScriptView Raw
1var util = require('util'),
2 f = util.format,
3 EventEmitter = require('events').EventEmitter,
4 path = require('path'),
5 uuid = require('node-uuid'),
6 fork = require('child_process').fork,
7 pbxWriter = require('./pbxWriter'),
8 pbxFile = require('./pbxFile'),
9 fs = require('fs'),
10 parser = require('./parser/pbxproj'),
11 plist = require('simple-plist'),
12 COMMENT_KEY = /_comment$/
13
14function pbxProject(filename) {
15 if (!(this instanceof pbxProject))
16 return new pbxProject(filename);
17
18 this.filepath = path.resolve(filename)
19}
20
21util.inherits(pbxProject, EventEmitter)
22
23pbxProject.prototype.parse = function(cb) {
24 var worker = fork(__dirname + '/parseJob.js', [this.filepath])
25
26 worker.on('message', function(msg) {
27 if (msg.name == 'SyntaxError' || msg.code) {
28 this.emit('error', msg);
29 } else {
30 this.hash = msg;
31 this.emit('end', null, msg)
32 }
33 }.bind(this));
34
35 if (cb) {
36 this.on('error', cb);
37 this.on('end', cb);
38 }
39
40 return this;
41}
42
43pbxProject.prototype.parseSync = function() {
44 var file_contents = fs.readFileSync(this.filepath, 'utf-8');
45
46 this.hash = parser.parse(file_contents);
47 return this;
48}
49
50pbxProject.prototype.writeSync = function() {
51 this.writer = new pbxWriter(this.hash);
52 return this.writer.writeSync();
53}
54
55pbxProject.prototype.allUuids = function() {
56 var sections = this.hash.project.objects,
57 uuids = [],
58 section;
59
60 for (key in sections) {
61 section = sections[key]
62 uuids = uuids.concat(Object.keys(section))
63 }
64
65 uuids = uuids.filter(function(str) {
66 return !COMMENT_KEY.test(str) && str.length == 24;
67 });
68
69 return uuids;
70}
71
72pbxProject.prototype.generateUuid = function() {
73 var id = uuid.v4()
74 .replace(/-/g, '')
75 .substr(0, 24)
76 .toUpperCase()
77
78 if (this.allUuids().indexOf(id) >= 0) {
79 return this.generateUuid();
80 } else {
81 return id;
82 }
83}
84
85pbxProject.prototype.addPluginFile = function(path, opt) {
86 var file = new pbxFile(path, opt);
87
88 file.plugin = true; // durr
89 correctForPluginsPath(file, this);
90
91 // null is better for early errors
92 if (this.hasFile(file.path)) return null;
93
94 file.fileRef = this.generateUuid();
95
96 this.addToPbxFileReferenceSection(file); // PBXFileReference
97 this.addToPluginsPbxGroup(file); // PBXGroup
98
99 return file;
100}
101
102pbxProject.prototype.removePluginFile = function(path, opt) {
103 var file = new pbxFile(path, opt);
104 correctForPluginsPath(file, this);
105
106 this.removeFromPbxFileReferenceSection(file); // PBXFileReference
107 this.removeFromPluginsPbxGroup(file); // PBXGroup
108
109 return file;
110}
111
112pbxProject.prototype.addProductFile = function(targetPath, opt) {
113 var file = new pbxFile(targetPath, opt);
114
115 file.includeInIndex = 0;
116 file.fileRef = this.generateUuid();
117 file.target = opt ? opt.target : undefined;
118 file.group = opt ? opt.group : undefined;
119 file.uuid = this.generateUuid();
120 file.path = file.basename;
121
122 this.addToPbxFileReferenceSection(file);
123 this.addToProductsPbxGroup(file); // PBXGroup
124
125 return file;
126}
127
128pbxProject.prototype.removeProductFile = function(path, opt) {
129 var file = new pbxFile(path, opt);
130
131 this.removeFromProductsPbxGroup(file); // PBXGroup
132
133 return file;
134}
135
136/**
137 *
138 * @param path {String}
139 * @param opt {Object} see pbxFile for avail options
140 * @param group {String} group key
141 * @returns {Object} file; see pbxFile
142 */
143pbxProject.prototype.addSourceFile = function (path, opt, group) {
144 var file;
145 if (group) {
146 file = this.addFile(path, group, opt);
147 }
148 else {
149 file = this.addPluginFile(path, opt);
150 }
151
152 if (!file) return false;
153
154 file.target = opt ? opt.target : undefined;
155 file.uuid = this.generateUuid();
156
157 this.addToPbxBuildFileSection(file); // PBXBuildFile
158 this.addToPbxSourcesBuildPhase(file); // PBXSourcesBuildPhase
159
160 return file;
161}
162
163/**
164 *
165 * @param path {String}
166 * @param opt {Object} see pbxFile for avail options
167 * @param group {String} group key
168 * @returns {Object} file; see pbxFile
169 */
170pbxProject.prototype.removeSourceFile = function (path, opt, group) {
171 var file;
172 if (group) {
173 file = this.removeFile(path, group, opt);
174 }
175 else {
176 file = this.removePluginFile(path, opt);
177 }
178 file.target = opt ? opt.target : undefined;
179 this.removeFromPbxBuildFileSection(file); // PBXBuildFile
180 this.removeFromPbxSourcesBuildPhase(file); // PBXSourcesBuildPhase
181
182 return file;
183}
184
185/**
186 *
187 * @param path {String}
188 * @param opt {Object} see pbxFile for avail options
189 * @param group {String} group key
190 * @returns {Object} file; see pbxFile
191 */
192pbxProject.prototype.addHeaderFile = function (path, opt, group) {
193 if (group) {
194 return this.addFile(path, group, opt);
195 }
196 else {
197 return this.addPluginFile(path, opt);
198 }
199}
200
201/**
202 *
203 * @param path {String}
204 * @param opt {Object} see pbxFile for avail options
205 * @param group {String} group key
206 * @returns {Object} file; see pbxFile
207 */
208pbxProject.prototype.removeHeaderFile = function (path, opt, group) {
209 if (group) {
210 return this.removeFile(path, group, opt);
211 }
212 else {
213 return this.removePluginFile(path, opt);
214 }
215}
216
217pbxProject.prototype.addResourceFile = function(path, opt) {
218 opt = opt || {};
219
220 var file;
221
222 if (opt.plugin) {
223 file = this.addPluginFile(path, opt);
224 if (!file) return false;
225 } else {
226 file = new pbxFile(path, opt);
227 if (this.hasFile(file.path)) return false;
228 }
229
230 file.uuid = this.generateUuid();
231 file.target = opt ? opt.target : undefined;
232
233 if (!opt.plugin) {
234 correctForResourcesPath(file, this);
235 file.fileRef = this.generateUuid();
236 }
237
238 this.addToPbxBuildFileSection(file); // PBXBuildFile
239 this.addToPbxResourcesBuildPhase(file); // PBXResourcesBuildPhase
240
241 if (!opt.plugin) {
242 this.addToPbxFileReferenceSection(file); // PBXFileReference
243 this.addToResourcesPbxGroup(file); // PBXGroup
244 }
245
246 return file;
247}
248
249pbxProject.prototype.removeResourceFile = function(path, opt) {
250 var file = new pbxFile(path, opt);
251 file.target = opt ? opt.target : undefined;
252
253 correctForResourcesPath(file, this);
254
255 this.removeFromPbxBuildFileSection(file); // PBXBuildFile
256 this.removeFromPbxFileReferenceSection(file); // PBXFileReference
257 this.removeFromResourcesPbxGroup(file); // PBXGroup
258 this.removeFromPbxResourcesBuildPhase(file); // PBXResourcesBuildPhase
259
260 return file;
261}
262
263pbxProject.prototype.addFramework = function(fpath, opt) {
264
265 var file = new pbxFile(fpath, opt);
266
267 file.uuid = this.generateUuid();
268 file.fileRef = this.generateUuid();
269 file.target = opt ? opt.target : undefined;
270
271 if (this.hasFile(file.path)) return false;
272
273 this.addToPbxBuildFileSection(file); // PBXBuildFile
274 this.addToPbxFileReferenceSection(file); // PBXFileReference
275 this.addToFrameworksPbxGroup(file); // PBXGroup
276 this.addToPbxFrameworksBuildPhase(file); // PBXFrameworksBuildPhase
277
278 if (opt && opt.customFramework == true) {
279 this.addToFrameworkSearchPaths(file);
280 }
281
282 return file;
283}
284
285pbxProject.prototype.removeFramework = function(fpath, opt) {
286 var file = new pbxFile(fpath, opt);
287 file.target = opt ? opt.target : undefined;
288
289 this.removeFromPbxBuildFileSection(file); // PBXBuildFile
290 this.removeFromPbxFileReferenceSection(file); // PBXFileReference
291 this.removeFromFrameworksPbxGroup(file); // PBXGroup
292 this.removeFromPbxFrameworksBuildPhase(file); // PBXFrameworksBuildPhase
293
294 if (opt && opt.customFramework) {
295 this.removeFromFrameworkSearchPaths(path.dirname(fpath));
296 }
297
298 return file;
299}
300
301
302pbxProject.prototype.addCopyfile = function(fpath, opt) {
303 var file = new pbxFile(fpath, opt);
304
305 // catch duplicates
306 if (this.hasFile(file.path)) {
307 file = this.hasFile(file.path);
308 }
309
310 file.fileRef = file.uuid = this.generateUuid();
311 file.target = opt ? opt.target : undefined;
312
313 this.addToPbxBuildFileSection(file); // PBXBuildFile
314 this.addToPbxFileReferenceSection(file); // PBXFileReference
315 this.addToPbxCopyfilesBuildPhase(file); // PBXCopyFilesBuildPhase
316
317 return file;
318}
319
320pbxProject.prototype.pbxCopyfilesBuildPhaseObj = function(target) {
321 return this.buildPhaseObject('PBXCopyFilesBuildPhase', 'Copy Files', target);
322}
323
324pbxProject.prototype.addToPbxCopyfilesBuildPhase = function(file) {
325 var sources = this.buildPhaseObject('PBXCopyFilesBuildPhase', 'Copy Files', file.target);
326 sources.files.push(pbxBuildPhaseObj(file));
327}
328
329pbxProject.prototype.removeCopyfile = function(fpath, opt) {
330 var file = new pbxFile(fpath, opt);
331 file.target = opt ? opt.target : undefined;
332
333 this.removeFromPbxBuildFileSection(file); // PBXBuildFile
334 this.removeFromPbxFileReferenceSection(file); // PBXFileReference
335 this.removeFromPbxCopyfilesBuildPhase(file); // PBXFrameworksBuildPhase
336
337 return file;
338}
339
340pbxProject.prototype.removeFromPbxCopyfilesBuildPhase = function(file) {
341 var sources = this.pbxCopyfilesBuildPhaseObj(file.target);
342 for (i in sources.files) {
343 if (sources.files[i].comment == longComment(file)) {
344 sources.files.splice(i, 1);
345 break;
346 }
347 }
348}
349
350pbxProject.prototype.addStaticLibrary = function(path, opt) {
351 opt = opt || {};
352
353 var file;
354
355 if (opt.plugin) {
356 file = this.addPluginFile(path, opt);
357 if (!file) return false;
358 } else {
359 file = new pbxFile(path, opt);
360 if (this.hasFile(file.path)) return false;
361 }
362
363 file.uuid = this.generateUuid();
364 file.target = opt ? opt.target : undefined;
365
366 if (!opt.plugin) {
367 file.fileRef = this.generateUuid();
368 this.addToPbxFileReferenceSection(file); // PBXFileReference
369 }
370
371 this.addToPbxBuildFileSection(file); // PBXBuildFile
372 this.addToPbxFrameworksBuildPhase(file); // PBXFrameworksBuildPhase
373 this.addToLibrarySearchPaths(file); // make sure it gets built!
374
375 return file;
376}
377
378// helper addition functions
379pbxProject.prototype.addToPbxBuildFileSection = function(file) {
380 var commentKey = f("%s_comment", file.uuid);
381
382 this.pbxBuildFileSection()[file.uuid] = pbxBuildFileObj(file);
383 this.pbxBuildFileSection()[commentKey] = pbxBuildFileComment(file);
384}
385
386pbxProject.prototype.removeFromPbxBuildFileSection = function(file) {
387 var uuid;
388
389 for (uuid in this.pbxBuildFileSection()) {
390 if (this.pbxBuildFileSection()[uuid].fileRef_comment == file.basename) {
391 file.uuid = uuid;
392 delete this.pbxBuildFileSection()[uuid];
393 }
394 }
395 var commentKey = f("%s_comment", file.uuid);
396 delete this.pbxBuildFileSection()[commentKey];
397}
398
399pbxProject.prototype.addPbxGroup = function(filePathsArray, name, path, sourceTree) {
400 var groups = this.hash.project.objects['PBXGroup'],
401 pbxGroupUuid = this.generateUuid(),
402 commentKey = f("%s_comment", pbxGroupUuid),
403 pbxGroup = {
404 isa: 'PBXGroup',
405 children: [],
406 name: name,
407 path: path,
408 sourceTree: sourceTree ? sourceTree : '"<group>"'
409 },
410 fileReferenceSection = this.pbxFileReferenceSection(),
411 filePathToReference = {};
412
413 for (var key in fileReferenceSection) {
414 // only look for comments
415 if (!COMMENT_KEY.test(key)) continue;
416
417 var fileReferenceKey = key.split(COMMENT_KEY)[0],
418 fileReference = fileReferenceSection[fileReferenceKey];
419
420 filePathToReference[fileReference.path] = { fileRef: fileReferenceKey, basename: fileReferenceSection[key] };
421 }
422
423 for (var index = 0; index < filePathsArray.length; index++) {
424 var filePath = filePathsArray[index],
425 filePathQuoted = "\"" + filePath + "\"";
426 if (filePathToReference[filePath]) {
427 pbxGroup.children.push(pbxGroupChild(filePathToReference[filePath]));
428 continue;
429 } else if (filePathToReference[filePathQuoted]) {
430 pbxGroup.children.push(pbxGroupChild(filePathToReference[filePathQuoted]));
431 continue;
432 }
433
434 var file = new pbxFile(filePath);
435 file.uuid = this.generateUuid();
436 file.fileRef = this.generateUuid();
437 this.addToPbxFileReferenceSection(file); // PBXFileReference
438 this.addToPbxBuildFileSection(file); // PBXBuildFile
439 pbxGroup.children.push(pbxGroupChild(file));
440 }
441
442 if (groups) {
443 groups[pbxGroupUuid] = pbxGroup;
444 groups[commentKey] = name;
445 }
446
447 return { uuid: pbxGroupUuid, pbxGroup: pbxGroup };
448}
449
450pbxProject.prototype.removePbxGroup = function (groupName) {
451 var section = this.hash.project.objects['PBXGroup'],
452 key, itemKey;
453
454 for (key in section) {
455 // only look for comments
456 if (!COMMENT_KEY.test(key)) continue;
457
458 if (section[key] == groupName) {
459 itemKey = key.split(COMMENT_KEY)[0];
460 delete section[itemKey];
461 }
462 }
463}
464
465pbxProject.prototype.addToPbxProjectSection = function(target) {
466
467 var newTarget = {
468 value: target.uuid,
469 comment: pbxNativeTargetComment(target.pbxNativeTarget)
470 };
471
472 this.pbxProjectSection()[this.getFirstProject()['uuid']]['targets'].push(newTarget);
473}
474
475pbxProject.prototype.addToPbxNativeTargetSection = function(target) {
476 var commentKey = f("%s_comment", target.uuid);
477
478 this.pbxNativeTargetSection()[target.uuid] = target.pbxNativeTarget;
479 this.pbxNativeTargetSection()[commentKey] = target.pbxNativeTarget.name;
480}
481
482pbxProject.prototype.addToPbxFileReferenceSection = function(file) {
483 var commentKey = f("%s_comment", file.fileRef);
484
485 this.pbxFileReferenceSection()[file.fileRef] = pbxFileReferenceObj(file);
486 this.pbxFileReferenceSection()[commentKey] = pbxFileReferenceComment(file);
487}
488
489pbxProject.prototype.removeFromPbxFileReferenceSection = function(file) {
490
491 var i;
492 var refObj = pbxFileReferenceObj(file);
493 for (i in this.pbxFileReferenceSection()) {
494 if (this.pbxFileReferenceSection()[i].name == refObj.name ||
495 ('"' + this.pbxFileReferenceSection()[i].name + '"') == refObj.name ||
496 this.pbxFileReferenceSection()[i].path == refObj.path ||
497 ('"' + this.pbxFileReferenceSection()[i].path + '"') == refObj.path) {
498 file.fileRef = file.uuid = i;
499 delete this.pbxFileReferenceSection()[i];
500 break;
501 }
502 }
503 var commentKey = f("%s_comment", file.fileRef);
504 if (this.pbxFileReferenceSection()[commentKey] != undefined) {
505 delete this.pbxFileReferenceSection()[commentKey];
506 }
507
508 return file;
509}
510
511pbxProject.prototype.addToXcVersionGroupSection = function(file) {
512 if (!file.models || !file.currentModel) {
513 throw new Error("Cannot create a XCVersionGroup section from not a data model document file");
514 }
515
516 var commentKey = f("%s_comment", file.fileRef);
517
518 if (!this.xcVersionGroupSection()[file.fileRef]) {
519 this.xcVersionGroupSection()[file.fileRef] = {
520 isa: 'XCVersionGroup',
521 children: file.models.map(function (el) { return el.fileRef; }),
522 currentVersion: file.currentModel.fileRef,
523 name: path.basename(file.path),
524 path: file.path,
525 sourceTree: '"<group>"',
526 versionGroupType: 'wrapper.xcdatamodel'
527 };
528 this.xcVersionGroupSection()[commentKey] = path.basename(file.path);
529 }
530}
531
532pbxProject.prototype.addToPluginsPbxGroup = function(file) {
533 var pluginsGroup = this.pbxGroupByName('Plugins');
534 pluginsGroup.children.push(pbxGroupChild(file));
535}
536
537pbxProject.prototype.removeFromPluginsPbxGroup = function(file) {
538 var pluginsGroupChildren = this.pbxGroupByName('Plugins').children, i;
539 for (i in pluginsGroupChildren) {
540 if (pbxGroupChild(file).value == pluginsGroupChildren[i].value &&
541 pbxGroupChild(file).comment == pluginsGroupChildren[i].comment) {
542 pluginsGroupChildren.splice(i, 1);
543 break;
544 }
545 }
546}
547
548pbxProject.prototype.addToResourcesPbxGroup = function(file) {
549 var pluginsGroup = this.pbxGroupByName('Resources');
550 pluginsGroup.children.push(pbxGroupChild(file));
551}
552
553pbxProject.prototype.removeFromResourcesPbxGroup = function(file) {
554 var pluginsGroupChildren = this.pbxGroupByName('Resources').children, i;
555 for (i in pluginsGroupChildren) {
556 if (pbxGroupChild(file).value == pluginsGroupChildren[i].value &&
557 pbxGroupChild(file).comment == pluginsGroupChildren[i].comment) {
558 pluginsGroupChildren.splice(i, 1);
559 break;
560 }
561 }
562}
563
564pbxProject.prototype.addToFrameworksPbxGroup = function(file) {
565 var pluginsGroup = this.pbxGroupByName('Frameworks');
566 pluginsGroup.children.push(pbxGroupChild(file));
567}
568
569pbxProject.prototype.removeFromFrameworksPbxGroup = function(file) {
570 var pluginsGroupChildren = this.pbxGroupByName('Frameworks').children;
571
572 for (i in pluginsGroupChildren) {
573 if (pbxGroupChild(file).value == pluginsGroupChildren[i].value &&
574 pbxGroupChild(file).comment == pluginsGroupChildren[i].comment) {
575 pluginsGroupChildren.splice(i, 1);
576 break;
577 }
578 }
579}
580
581
582pbxProject.prototype.addToProductsPbxGroup = function(file) {
583 var productsGroup = this.pbxGroupByName('Products');
584 productsGroup.children.push(pbxGroupChild(file));
585}
586
587pbxProject.prototype.removeFromProductsPbxGroup = function(file) {
588 var productsGroupChildren = this.pbxGroupByName('Products').children, i;
589 for (i in productsGroupChildren) {
590 if (pbxGroupChild(file).value == productsGroupChildren[i].value &&
591 pbxGroupChild(file).comment == productsGroupChildren[i].comment) {
592 productsGroupChildren.splice(i, 1);
593 break;
594 }
595 }
596}
597
598pbxProject.prototype.addToPbxSourcesBuildPhase = function(file) {
599 var sources = this.pbxSourcesBuildPhaseObj(file.target);
600 sources.files.push(pbxBuildPhaseObj(file));
601}
602
603pbxProject.prototype.removeFromPbxSourcesBuildPhase = function(file) {
604
605 var sources = this.pbxSourcesBuildPhaseObj(file.target), i;
606 for (i in sources.files) {
607 if (sources.files[i].comment == longComment(file)) {
608 sources.files.splice(i, 1);
609 break;
610 }
611 }
612}
613
614pbxProject.prototype.addToPbxResourcesBuildPhase = function(file) {
615 var sources = this.pbxResourcesBuildPhaseObj(file.target);
616 sources.files.push(pbxBuildPhaseObj(file));
617}
618
619pbxProject.prototype.removeFromPbxResourcesBuildPhase = function(file) {
620 var sources = this.pbxResourcesBuildPhaseObj(file.target), i;
621
622 for (i in sources.files) {
623 if (sources.files[i].comment == longComment(file)) {
624 sources.files.splice(i, 1);
625 break;
626 }
627 }
628}
629
630pbxProject.prototype.addToPbxFrameworksBuildPhase = function(file) {
631 var sources = this.pbxFrameworksBuildPhaseObj(file.target);
632 sources.files.push(pbxBuildPhaseObj(file));
633}
634
635pbxProject.prototype.removeFromPbxFrameworksBuildPhase = function(file) {
636 var sources = this.pbxFrameworksBuildPhaseObj(file.target);
637 for (i in sources.files) {
638 if (sources.files[i].comment == longComment(file)) {
639 sources.files.splice(i, 1);
640 break;
641 }
642 }
643}
644
645pbxProject.prototype.addXCConfigurationList = function(configurationObjectsArray, defaultConfigurationName, comment) {
646 var pbxBuildConfigurationSection = this.pbxXCBuildConfigurationSection(),
647 pbxXCConfigurationListSection = this.pbxXCConfigurationList(),
648 xcConfigurationListUuid = this.generateUuid(),
649 commentKey = f("%s_comment", xcConfigurationListUuid),
650 xcConfigurationList = {
651 isa: 'XCConfigurationList',
652 buildConfigurations: [],
653 defaultConfigurationIsVisible: 0,
654 defaultConfigurationName: defaultConfigurationName
655 };
656
657 for (var index = 0; index < configurationObjectsArray.length; index++) {
658 var configuration = configurationObjectsArray[index],
659 configurationUuid = this.generateUuid(),
660 configurationCommentKey = f("%s_comment", configurationUuid);
661
662 pbxBuildConfigurationSection[configurationUuid] = configuration;
663 pbxBuildConfigurationSection[configurationCommentKey] = configuration.name;
664 xcConfigurationList.buildConfigurations.push({ value: configurationUuid, comment: configuration.name });
665 }
666
667 if (pbxXCConfigurationListSection) {
668 pbxXCConfigurationListSection[xcConfigurationListUuid] = xcConfigurationList;
669 pbxXCConfigurationListSection[commentKey] = comment;
670 }
671
672 return { uuid: xcConfigurationListUuid, xcConfigurationList: xcConfigurationList };
673}
674
675pbxProject.prototype.addTargetDependency = function(target, dependencyTargets) {
676 if (!target)
677 return undefined;
678
679 var nativeTargets = this.pbxNativeTargetSection();
680
681 if (typeof nativeTargets[target] == "undefined")
682 throw new Error("Invalid target: " + target);
683
684 for (var index = 0; index < dependencyTargets.length; index++) {
685 var dependencyTarget = dependencyTargets[index];
686 if (typeof nativeTargets[dependencyTarget] == "undefined")
687 throw new Error("Invalid target: " + dependencyTarget);
688 }
689
690 var pbxTargetDependency = 'PBXTargetDependency',
691 pbxContainerItemProxy = 'PBXContainerItemProxy',
692 pbxTargetDependencySection = this.hash.project.objects[pbxTargetDependency],
693 pbxContainerItemProxySection = this.hash.project.objects[pbxContainerItemProxy];
694
695 for (var index = 0; index < dependencyTargets.length; index++) {
696 var dependencyTargetUuid = dependencyTargets[index],
697 dependencyTargetCommentKey = f("%s_comment", dependencyTargetUuid),
698 targetDependencyUuid = this.generateUuid(),
699 targetDependencyCommentKey = f("%s_comment", targetDependencyUuid),
700 itemProxyUuid = this.generateUuid(),
701 itemProxyCommentKey = f("%s_comment", itemProxyUuid),
702 itemProxy = {
703 isa: pbxContainerItemProxy,
704 containerPortal: this.hash.project['rootObject'],
705 containerPortal_comment: this.hash.project['rootObject_comment'],
706 proxyType: 1,
707 remoteGlobalIDString: dependencyTargetUuid,
708 remoteInfo: nativeTargets[dependencyTargetUuid].name
709 },
710 targetDependency = {
711 isa: pbxTargetDependency,
712 target: dependencyTargetUuid,
713 target_comment: nativeTargets[dependencyTargetCommentKey],
714 targetProxy: itemProxyUuid,
715 targetProxy_comment: pbxContainerItemProxy
716 };
717
718 if (pbxContainerItemProxySection && pbxTargetDependencySection) {
719 pbxContainerItemProxySection[itemProxyUuid] = itemProxy;
720 pbxContainerItemProxySection[itemProxyCommentKey] = pbxContainerItemProxy;
721 pbxTargetDependencySection[targetDependencyUuid] = targetDependency;
722 pbxTargetDependencySection[targetDependencyCommentKey] = pbxTargetDependency;
723 nativeTargets[target].dependencies.push({ value: targetDependencyUuid, comment: pbxTargetDependency })
724 }
725 }
726
727 return { uuid: target, target: nativeTargets[target] };
728}
729
730pbxProject.prototype.addBuildPhase = function(filePathsArray, buildPhaseType, comment, target, folderType, subfolderPath) {
731 var buildPhaseSection,
732 fileReferenceSection = this.pbxFileReferenceSection(),
733 buildFileSection = this.pbxBuildFileSection(),
734 buildPhaseUuid = this.generateUuid(),
735 buildPhaseTargetUuid = target || this.getFirstTarget().uuid,
736 commentKey = f("%s_comment", buildPhaseUuid),
737 buildPhase = {
738 isa: buildPhaseType,
739 buildActionMask: 2147483647,
740 files: [],
741 runOnlyForDeploymentPostprocessing: 0
742 },
743 filePathToBuildFile = {};
744
745 if (buildPhaseType === 'PBXCopyFilesBuildPhase') {
746 buildPhase = pbxCopyFilesBuildPhaseObj(buildPhase, folderType, subfolderPath, comment);
747 }
748
749 if (!this.hash.project.objects[buildPhaseType]) {
750 this.hash.project.objects[buildPhaseType] = new Object();
751 }
752
753 if (!this.hash.project.objects[buildPhaseType][buildPhaseUuid]) {
754 this.hash.project.objects[buildPhaseType][buildPhaseUuid] = buildPhase;
755 this.hash.project.objects[buildPhaseType][commentKey] = comment;
756 }
757
758 if (this.hash.project.objects['PBXNativeTarget'][buildPhaseTargetUuid]['buildPhases']) {
759 this.hash.project.objects['PBXNativeTarget'][buildPhaseTargetUuid]['buildPhases'].push({
760 value: buildPhaseUuid,
761 comment: comment
762 })
763
764 }
765
766
767 for (var key in buildFileSection) {
768 // only look for comments
769 if (!COMMENT_KEY.test(key)) continue;
770
771 var buildFileKey = key.split(COMMENT_KEY)[0],
772 buildFile = buildFileSection[buildFileKey];
773 fileReference = fileReferenceSection[buildFile.fileRef];
774
775 if (!fileReference) continue;
776
777 var pbxFileObj = new pbxFile(fileReference.path);
778
779 filePathToBuildFile[fileReference.path] = { uuid: buildFileKey, basename: pbxFileObj.basename, group: pbxFileObj.group };
780 }
781
782 for (var index = 0; index < filePathsArray.length; index++) {
783 var filePath = filePathsArray[index],
784 filePathQuoted = "\"" + filePath + "\"",
785 file = new pbxFile(filePath);
786
787 if (filePathToBuildFile[filePath]) {
788 buildPhase.files.push(pbxBuildPhaseObj(filePathToBuildFile[filePath]));
789 continue;
790 } else if (filePathToBuildFile[filePathQuoted]) {
791 buildPhase.files.push(pbxBuildPhaseObj(filePathToBuildFile[filePathQuoted]));
792 continue;
793 }
794
795 file.uuid = this.generateUuid();
796 file.fileRef = this.generateUuid();
797 this.addToPbxFileReferenceSection(file); // PBXFileReference
798 this.addToPbxBuildFileSection(file); // PBXBuildFile
799 buildPhase.files.push(pbxBuildPhaseObj(file));
800 }
801
802 if (buildPhaseSection) {
803 buildPhaseSection[buildPhaseUuid] = buildPhase;
804 buildPhaseSection[commentKey] = comment;
805 }
806
807 return { uuid: buildPhaseUuid, buildPhase: buildPhase };
808}
809
810// helper access functions
811pbxProject.prototype.pbxProjectSection = function() {
812 return this.hash.project.objects['PBXProject'];
813}
814pbxProject.prototype.pbxBuildFileSection = function() {
815 return this.hash.project.objects['PBXBuildFile'];
816}
817
818pbxProject.prototype.pbxXCBuildConfigurationSection = function() {
819 return this.hash.project.objects['XCBuildConfiguration'];
820}
821
822pbxProject.prototype.pbxFileReferenceSection = function() {
823 return this.hash.project.objects['PBXFileReference'];
824}
825
826pbxProject.prototype.pbxNativeTargetSection = function() {
827 return this.hash.project.objects['PBXNativeTarget'];
828}
829
830pbxProject.prototype.xcVersionGroupSection = function () {
831 if (typeof this.hash.project.objects['XCVersionGroup'] !== 'object') {
832 this.hash.project.objects['XCVersionGroup'] = {};
833 }
834
835 return this.hash.project.objects['XCVersionGroup'];
836}
837
838pbxProject.prototype.pbxXCConfigurationList = function() {
839 return this.hash.project.objects['XCConfigurationList'];
840}
841
842pbxProject.prototype.pbxGroupByName = function(name) {
843 var groups = this.hash.project.objects['PBXGroup'],
844 key, groupKey;
845
846 for (key in groups) {
847 // only look for comments
848 if (!COMMENT_KEY.test(key)) continue;
849
850 if (groups[key] == name) {
851 groupKey = key.split(COMMENT_KEY)[0];
852 return groups[groupKey];
853 }
854 }
855
856 return null;
857}
858
859pbxProject.prototype.pbxTargetByName = function(name) {
860 return this.pbxItemByComment(name, 'PBXNativeTarget');
861}
862
863pbxProject.prototype.findTargetKey = function(name) {
864 var targets = this.hash.project.objects['PBXNativeTarget'];
865
866 for (var key in targets) {
867 // only look for comments
868 if (COMMENT_KEY.test(key)) continue;
869
870 var target = targets[key];
871 if (target.name === name) {
872 return key;
873 }
874 }
875
876 return null;
877}
878
879pbxProject.prototype.pbxItemByComment = function(name, pbxSectionName) {
880 var section = this.hash.project.objects[pbxSectionName],
881 key, itemKey;
882
883 for (key in section) {
884 // only look for comments
885 if (!COMMENT_KEY.test(key)) continue;
886
887 if (section[key] == name) {
888 itemKey = key.split(COMMENT_KEY)[0];
889 return section[itemKey];
890 }
891 }
892
893 return null;
894}
895
896pbxProject.prototype.pbxSourcesBuildPhaseObj = function(target) {
897 return this.buildPhaseObject('PBXSourcesBuildPhase', 'Sources', target);
898}
899
900pbxProject.prototype.pbxResourcesBuildPhaseObj = function(target) {
901 return this.buildPhaseObject('PBXResourcesBuildPhase', 'Resources', target);
902}
903
904pbxProject.prototype.pbxFrameworksBuildPhaseObj = function(target) {
905 return this.buildPhaseObject('PBXFrameworksBuildPhase', 'Frameworks', target);
906}
907
908// Find Build Phase from group/target
909pbxProject.prototype.buildPhase = function(group, target) {
910
911 if (!target)
912 return undefined;
913
914 var nativeTargets = this.pbxNativeTargetSection();
915 if (typeof nativeTargets[target] == "undefined")
916 throw new Error("Invalid target: " + target);
917
918 var nativeTarget = nativeTargets[target];
919 var buildPhases = nativeTarget.buildPhases;
920 for(var i in buildPhases)
921 {
922 var buildPhase = buildPhases[i];
923 if (buildPhase.comment==group)
924 return buildPhase.value + "_comment";
925 }
926 }
927
928pbxProject.prototype.buildPhaseObject = function(name, group, target) {
929 var section = this.hash.project.objects[name],
930 obj, sectionKey, key;
931 var buildPhase = this.buildPhase(group, target);
932
933 for (key in section) {
934
935 // only look for comments
936 if (!COMMENT_KEY.test(key)) continue;
937
938 // select the proper buildPhase
939 if (buildPhase && buildPhase!=key)
940 continue;
941 if (section[key] == group) {
942 sectionKey = key.split(COMMENT_KEY)[0];
943 return section[sectionKey];
944 }
945 }
946 return null;
947}
948
949pbxProject.prototype.addBuildProperty = function(prop, value, build_name) {
950 var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
951 key, configuration;
952
953 for (key in configurations){
954 configuration = configurations[key];
955 if (!build_name || configuration.name === build_name){
956 configuration.buildSettings[prop] = value;
957 }
958 }
959}
960
961pbxProject.prototype.removeBuildProperty = function(prop, build_name) {
962 var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
963 key, configuration;
964
965 for (key in configurations){
966 configuration = configurations[key];
967 if (configuration.buildSettings[prop] &&
968 !build_name || configuration.name === build_name){
969 delete configuration.buildSettings[prop];
970 }
971 }
972}
973
974/**
975 *
976 * @param prop {String}
977 * @param value {String|Array|Object|Number|Boolean}
978 * @param build {String} Release or Debug
979 */
980pbxProject.prototype.updateBuildProperty = function(prop, value, build) {
981 var configs = this.pbxXCBuildConfigurationSection();
982 for (var configName in configs) {
983 if (!COMMENT_KEY.test(configName)) {
984 var config = configs[configName];
985 if ( (build && config.name === build) || (!build) ) {
986 config.buildSettings[prop] = value;
987 }
988 }
989 }
990}
991
992pbxProject.prototype.updateProductName = function(name) {
993 this.updateBuildProperty('PRODUCT_NAME', '"' + name + '"');
994}
995
996pbxProject.prototype.removeFromFrameworkSearchPaths = function(file) {
997 var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
998 INHERITED = '"$(inherited)"',
999 SEARCH_PATHS = 'FRAMEWORK_SEARCH_PATHS',
1000 config, buildSettings, searchPaths;
1001 var new_path = searchPathForFile(file, this);
1002
1003 for (config in configurations) {
1004 buildSettings = configurations[config].buildSettings;
1005
1006 if (unquote(buildSettings['PRODUCT_NAME']) != this.productName)
1007 continue;
1008
1009 searchPaths = buildSettings[SEARCH_PATHS];
1010
1011 if (searchPaths) {
1012 var matches = searchPaths.filter(function(p) {
1013 return p.indexOf(new_path) > -1;
1014 });
1015 matches.forEach(function(m) {
1016 var idx = searchPaths.indexOf(m);
1017 searchPaths.splice(idx, 1);
1018 });
1019 }
1020
1021 }
1022}
1023
1024pbxProject.prototype.addToFrameworkSearchPaths = function(file) {
1025 var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
1026 INHERITED = '"$(inherited)"',
1027 config, buildSettings, searchPaths;
1028
1029 for (config in configurations) {
1030 buildSettings = configurations[config].buildSettings;
1031
1032 if (unquote(buildSettings['PRODUCT_NAME']) != this.productName)
1033 continue;
1034
1035 if (!buildSettings['FRAMEWORK_SEARCH_PATHS']
1036 || buildSettings['FRAMEWORK_SEARCH_PATHS'] === INHERITED) {
1037 buildSettings['FRAMEWORK_SEARCH_PATHS'] = [INHERITED];
1038 }
1039
1040 buildSettings['FRAMEWORK_SEARCH_PATHS'].push(searchPathForFile(file, this));
1041 }
1042}
1043
1044pbxProject.prototype.removeFromLibrarySearchPaths = function(file) {
1045 var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
1046 INHERITED = '"$(inherited)"',
1047 SEARCH_PATHS = 'LIBRARY_SEARCH_PATHS',
1048 config, buildSettings, searchPaths;
1049 var new_path = searchPathForFile(file, this);
1050
1051 for (config in configurations) {
1052 buildSettings = configurations[config].buildSettings;
1053
1054 if (unquote(buildSettings['PRODUCT_NAME']) != this.productName)
1055 continue;
1056
1057 searchPaths = buildSettings[SEARCH_PATHS];
1058
1059 if (searchPaths) {
1060 var matches = searchPaths.filter(function(p) {
1061 return p.indexOf(new_path) > -1;
1062 });
1063 matches.forEach(function(m) {
1064 var idx = searchPaths.indexOf(m);
1065 searchPaths.splice(idx, 1);
1066 });
1067 }
1068
1069 }
1070}
1071
1072pbxProject.prototype.addToLibrarySearchPaths = function(file) {
1073 var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
1074 INHERITED = '"$(inherited)"',
1075 config, buildSettings, searchPaths;
1076
1077 for (config in configurations) {
1078 buildSettings = configurations[config].buildSettings;
1079
1080 if (unquote(buildSettings['PRODUCT_NAME']) != this.productName)
1081 continue;
1082
1083 if (!buildSettings['LIBRARY_SEARCH_PATHS']
1084 || buildSettings['LIBRARY_SEARCH_PATHS'] === INHERITED) {
1085 buildSettings['LIBRARY_SEARCH_PATHS'] = [INHERITED];
1086 }
1087
1088 if (typeof file === 'string') {
1089 buildSettings['LIBRARY_SEARCH_PATHS'].push(file);
1090 } else {
1091 buildSettings['LIBRARY_SEARCH_PATHS'].push(searchPathForFile(file, this));
1092 }
1093 }
1094}
1095
1096pbxProject.prototype.removeFromHeaderSearchPaths = function(file) {
1097 var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
1098 INHERITED = '"$(inherited)"',
1099 SEARCH_PATHS = 'HEADER_SEARCH_PATHS',
1100 config, buildSettings, searchPaths;
1101 var new_path = searchPathForFile(file, this);
1102
1103 for (config in configurations) {
1104 buildSettings = configurations[config].buildSettings;
1105
1106 if (unquote(buildSettings['PRODUCT_NAME']) != this.productName)
1107 continue;
1108
1109 if (buildSettings[SEARCH_PATHS]) {
1110 var matches = buildSettings[SEARCH_PATHS].filter(function(p) {
1111 return p.indexOf(new_path) > -1;
1112 });
1113 matches.forEach(function(m) {
1114 var idx = buildSettings[SEARCH_PATHS].indexOf(m);
1115 buildSettings[SEARCH_PATHS].splice(idx, 1);
1116 });
1117 }
1118
1119 }
1120}
1121pbxProject.prototype.addToHeaderSearchPaths = function(file) {
1122 var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
1123 INHERITED = '"$(inherited)"',
1124 config, buildSettings, searchPaths;
1125
1126 for (config in configurations) {
1127 buildSettings = configurations[config].buildSettings;
1128
1129 if (unquote(buildSettings['PRODUCT_NAME']) != this.productName)
1130 continue;
1131
1132 if (!buildSettings['HEADER_SEARCH_PATHS']) {
1133 buildSettings['HEADER_SEARCH_PATHS'] = [INHERITED];
1134 }
1135
1136 if (typeof file === 'string') {
1137 buildSettings['HEADER_SEARCH_PATHS'].push(file);
1138 } else {
1139 buildSettings['HEADER_SEARCH_PATHS'].push(searchPathForFile(file, this));
1140 }
1141 }
1142}
1143
1144pbxProject.prototype.addToOtherLinkerFlags = function (flag) {
1145 var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
1146 INHERITED = '"$(inherited)"',
1147 OTHER_LDFLAGS = 'OTHER_LDFLAGS',
1148 config, buildSettings;
1149
1150 for (config in configurations) {
1151 buildSettings = configurations[config].buildSettings;
1152
1153 if (unquote(buildSettings['PRODUCT_NAME']) != this.productName)
1154 continue;
1155
1156 if (!buildSettings[OTHER_LDFLAGS]
1157 || buildSettings[OTHER_LDFLAGS] === INHERITED) {
1158 buildSettings[OTHER_LDFLAGS] = [INHERITED];
1159 }
1160
1161 buildSettings[OTHER_LDFLAGS].push(flag);
1162 }
1163}
1164
1165pbxProject.prototype.removeFromOtherLinkerFlags = function (flag) {
1166 var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
1167 OTHER_LDFLAGS = 'OTHER_LDFLAGS',
1168 config, buildSettings;
1169
1170 for (config in configurations) {
1171 buildSettings = configurations[config].buildSettings;
1172
1173 if (unquote(buildSettings['PRODUCT_NAME']) != this.productName) {
1174 continue;
1175 }
1176
1177 if (buildSettings[OTHER_LDFLAGS]) {
1178 var matches = buildSettings[OTHER_LDFLAGS].filter(function (p) {
1179 return p.indexOf(flag) > -1;
1180 });
1181 matches.forEach(function (m) {
1182 var idx = buildSettings[OTHER_LDFLAGS].indexOf(m);
1183 buildSettings[OTHER_LDFLAGS].splice(idx, 1);
1184 });
1185 }
1186 }
1187}
1188
1189pbxProject.prototype.addToBuildSettings = function (buildSetting, value) {
1190 var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
1191 config, buildSettings;
1192
1193 for (config in configurations) {
1194 buildSettings = configurations[config].buildSettings;
1195
1196 buildSettings[buildSetting] = value;
1197 }
1198}
1199
1200pbxProject.prototype.removeFromBuildSettings = function (buildSetting) {
1201 var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
1202 config, buildSettings;
1203
1204 for (config in configurations) {
1205 buildSettings = configurations[config].buildSettings;
1206
1207 if (buildSettings[buildSetting]) {
1208 delete buildSettings[buildSetting];
1209 }
1210 }
1211}
1212
1213// a JS getter. hmmm
1214pbxProject.prototype.__defineGetter__("productName", function() {
1215 var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
1216 config, productName;
1217
1218 for (config in configurations) {
1219 productName = configurations[config].buildSettings['PRODUCT_NAME'];
1220
1221 if (productName) {
1222 return unquote(productName);
1223 }
1224 }
1225});
1226
1227// check if file is present
1228pbxProject.prototype.hasFile = function(filePath) {
1229 var files = nonComments(this.pbxFileReferenceSection()),
1230 file, id;
1231 for (id in files) {
1232 file = files[id];
1233 if (file.path == filePath || file.path == ('"' + filePath + '"')) {
1234 return file;
1235 }
1236 }
1237
1238 return false;
1239}
1240
1241pbxProject.prototype.addTarget = function(name, type, subfolder) {
1242
1243 // Setup uuid and name of new target
1244 var targetUuid = this.generateUuid(),
1245 targetType = type,
1246 targetSubfolder = subfolder || name,
1247 targetName = name.trim();
1248
1249 // Check type against list of allowed target types
1250 if (!targetName) {
1251 throw new Error("Target name missing.");
1252 }
1253
1254 // Check type against list of allowed target types
1255 if (!targetType) {
1256 throw new Error("Target type missing.");
1257 }
1258
1259 // Check type against list of allowed target types
1260 if (!producttypeForTargettype(targetType)) {
1261 throw new Error("Target type invalid: " + targetType);
1262 }
1263
1264 // Build Configuration: Create
1265 var buildConfigurationsList = [
1266 {
1267 name: 'Debug',
1268 isa: 'XCBuildConfiguration',
1269 buildSettings: {
1270 GCC_PREPROCESSOR_DEFINITIONS: ['"DEBUG=1"', '"$(inherited)"'],
1271 INFOPLIST_FILE: '"' + path.join(targetSubfolder, targetSubfolder + '-Info.plist' + '"'),
1272 LD_RUNPATH_SEARCH_PATHS: '"$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"',
1273 PRODUCT_NAME: '"' + targetName + '"',
1274 SKIP_INSTALL: 'YES'
1275 }
1276 },
1277 {
1278 name: 'Release',
1279 isa: 'XCBuildConfiguration',
1280 buildSettings: {
1281 INFOPLIST_FILE: '"' + path.join(targetSubfolder, targetSubfolder + '-Info.plist' + '"'),
1282 LD_RUNPATH_SEARCH_PATHS: '"$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"',
1283 PRODUCT_NAME: '"' + targetName + '"',
1284 SKIP_INSTALL: 'YES'
1285 }
1286 }
1287 ];
1288
1289 // Build Configuration: Add
1290 var buildConfigurations = this.addXCConfigurationList(buildConfigurationsList, 'Release', 'Build configuration list for PBXNativeTarget "' + targetName +'"');
1291
1292 // Product: Create
1293 var productName = targetName,
1294 productType = producttypeForTargettype(targetType),
1295 productFileType = filetypeForProducttype(productType),
1296 productFile = this.addProductFile(productName, { group: 'Copy Files', 'target': targetUuid, 'explicitFileType': productFileType}),
1297 productFileName = productFile.basename;
1298
1299
1300 // Product: Add to build file list
1301 this.addToPbxBuildFileSection(productFile);
1302
1303 // Target: Create
1304 var target = {
1305 uuid: targetUuid,
1306 pbxNativeTarget: {
1307 isa: 'PBXNativeTarget',
1308 name: '"' + targetName + '"',
1309 productName: '"' + targetName + '"',
1310 productReference: productFile.fileRef,
1311 productType: '"' + producttypeForTargettype(targetType) + '"',
1312 buildConfigurationList: buildConfigurations.uuid,
1313 buildPhases: [],
1314 buildRules: [],
1315 dependencies: []
1316 }
1317 };
1318
1319 // Target: Add to PBXNativeTarget section
1320 this.addToPbxNativeTargetSection(target)
1321
1322 // Product: Embed (only for "extension"-type targets)
1323 if (targetType === 'app_extension') {
1324
1325 // Create CopyFiles phase in first target
1326 this.addBuildPhase([], 'PBXCopyFilesBuildPhase', 'Copy Files', this.getFirstTarget().uuid, targetType)
1327
1328 // Add product to CopyFiles phase
1329 this.addToPbxCopyfilesBuildPhase(productFile)
1330
1331 // this.addBuildPhaseToTarget(newPhase.buildPhase, this.getFirstTarget().uuid)
1332
1333 };
1334
1335 // Target: Add uuid to root project
1336 this.addToPbxProjectSection(target);
1337
1338 // Target: Add dependency for this target to first (main) target
1339 this.addTargetDependency(this.getFirstTarget().uuid, [target.uuid]);
1340
1341
1342 // Return target on success
1343 return target;
1344
1345};
1346
1347// helper recursive prop search+replace
1348function propReplace(obj, prop, value) {
1349 var o = {};
1350 for (var p in obj) {
1351 if (o.hasOwnProperty.call(obj, p)) {
1352 if (typeof obj[p] == 'object' && !Array.isArray(obj[p])) {
1353 propReplace(obj[p], prop, value);
1354 } else if (p == prop) {
1355 obj[p] = value;
1356 }
1357 }
1358 }
1359}
1360
1361// helper object creation functions
1362function pbxBuildFileObj(file) {
1363 var obj = Object.create(null);
1364
1365 obj.isa = 'PBXBuildFile';
1366 obj.fileRef = file.fileRef;
1367 obj.fileRef_comment = file.basename;
1368 if (file.settings) obj.settings = file.settings;
1369
1370 return obj;
1371}
1372
1373function pbxFileReferenceObj(file) {
1374 var fileObject = {
1375 isa: "PBXFileReference",
1376 name: "\"" + file.basename + "\"",
1377 path: "\"" + file.path.replace(/\\/g, '/') + "\"",
1378 sourceTree: file.sourceTree,
1379 fileEncoding: file.fileEncoding,
1380 lastKnownFileType: file.lastKnownFileType,
1381 explicitFileType: file.explicitFileType,
1382 includeInIndex: file.includeInIndex
1383 };
1384
1385 return fileObject;
1386}
1387
1388function pbxGroupChild(file) {
1389 var obj = Object.create(null);
1390
1391 obj.value = file.fileRef;
1392 obj.comment = file.basename;
1393
1394 return obj;
1395}
1396
1397function pbxBuildPhaseObj(file) {
1398 var obj = Object.create(null);
1399
1400 obj.value = file.uuid;
1401 obj.comment = longComment(file);
1402
1403 return obj;
1404}
1405
1406function pbxCopyFilesBuildPhaseObj(obj, folderType, subfolderPath, phaseName){
1407
1408 // Add additional properties for 'CopyFiles' build phase
1409 var DESTINATION_BY_TARGETTYPE = {
1410 application: 'wrapper',
1411 app_extension: 'plugins',
1412 bundle: 'wrapper',
1413 command_line_tool: 'wrapper',
1414 dynamic_library: 'products_directory',
1415 framework: 'shared_frameworks',
1416 static_library: 'products_directory',
1417 unit_test_bundle: 'wrapper',
1418 watch_app: 'wrapper',
1419 watch_extension: 'plugins'
1420 }
1421 var SUBFOLDERSPEC_BY_DESTINATION = {
1422 absolute_path: 0,
1423 executables: 6,
1424 frameworks: 10,
1425 java_resources: 15,
1426 plugins: 13,
1427 products_directory: 16,
1428 resources: 7,
1429 shared_frameworks: 11,
1430 shared_support: 12,
1431 wrapper: 1,
1432 xpc_services: 0
1433 }
1434
1435 obj.name = '"' + phaseName + '"';
1436 obj.dstPath = subfolderPath || '""';
1437 obj.dstSubfolderSpec = SUBFOLDERSPEC_BY_DESTINATION[DESTINATION_BY_TARGETTYPE[folderType]];
1438
1439 return obj;
1440}
1441
1442function pbxBuildFileComment(file) {
1443 return longComment(file);
1444}
1445
1446function pbxFileReferenceComment(file) {
1447 return file.basename || path.basename(file.path);
1448}
1449
1450function pbxNativeTargetComment(target) {
1451 return target.name;
1452}
1453
1454function longComment(file) {
1455 return f("%s in %s", file.basename, file.group);
1456}
1457
1458// respect <group> path
1459function correctForPluginsPath(file, project) {
1460 return correctForPath(file, project, 'Plugins');
1461}
1462
1463function correctForResourcesPath(file, project) {
1464 return correctForPath(file, project, 'Resources');
1465}
1466
1467function correctForFrameworksPath(file, project) {
1468 return correctForPath(file, project, 'Frameworks');
1469}
1470
1471function correctForPath(file, project, group) {
1472 var r_group_dir = new RegExp('^' + group + '[\\\\/]');
1473
1474 if (project.pbxGroupByName(group).path)
1475 file.path = file.path.replace(r_group_dir, '');
1476
1477 return file;
1478}
1479
1480function searchPathForFile(file, proj) {
1481 var plugins = proj.pbxGroupByName('Plugins'),
1482 pluginsPath = plugins ? plugins.path : null,
1483 fileDir = path.dirname(file.path);
1484
1485 if (fileDir == '.') {
1486 fileDir = '';
1487 } else {
1488 fileDir = '/' + fileDir;
1489 }
1490
1491 if (file.plugin && pluginsPath) {
1492 return '"\\"$(SRCROOT)/' + unquote(pluginsPath) + '\\""';
1493 } else if (file.customFramework && file.dirname) {
1494 return '"\\"' + file.dirname + '\\""';
1495 } else {
1496 return '"\\"$(SRCROOT)/' + proj.productName + fileDir + '\\""';
1497 }
1498}
1499
1500function nonComments(obj) {
1501 var keys = Object.keys(obj),
1502 newObj = {}, i = 0;
1503
1504 for (i; i < keys.length; i++) {
1505 if (!COMMENT_KEY.test(keys[i])) {
1506 newObj[keys[i]] = obj[keys[i]];
1507 }
1508 }
1509
1510 return newObj;
1511}
1512
1513function unquote(str) {
1514 if (str) return str.replace(/^"(.*)"$/, "$1");
1515}
1516
1517
1518function buildPhaseNameForIsa (isa) {
1519
1520 BUILDPHASENAME_BY_ISA = {
1521 PBXCopyFilesBuildPhase: 'Copy Files',
1522 PBXResourcesBuildPhase: 'Resources',
1523 PBXSourcesBuildPhase: 'Sources',
1524 PBXFrameworksBuildPhase: 'Frameworks'
1525 }
1526
1527 return BUILDPHASENAME_BY_ISA[isa]
1528}
1529
1530function producttypeForTargettype (targetType) {
1531
1532 PRODUCTTYPE_BY_TARGETTYPE = {
1533 application: 'com.apple.product-type.application',
1534 app_extension: 'com.apple.product-type.app-extension',
1535 bundle: 'com.apple.product-type.bundle',
1536 command_line_tool: 'com.apple.product-type.tool',
1537 dynamic_library: 'com.apple.product-type.library.dynamic',
1538 framework: 'com.apple.product-type.framework',
1539 static_library: 'com.apple.product-type.library.static',
1540 unit_test_bundle: 'com.apple.product-type.bundle.unit-test',
1541 watch_app: 'com.apple.product-type.application.watchapp',
1542 watch_extension: 'com.apple.product-type.watchkit-extension'
1543 };
1544
1545 return PRODUCTTYPE_BY_TARGETTYPE[targetType]
1546}
1547
1548function filetypeForProducttype (productType) {
1549
1550 FILETYPE_BY_PRODUCTTYPE = {
1551 'com.apple.product-type.application': '"wrapper.application"',
1552 'com.apple.product-type.app-extension': '"wrapper.app-extension"',
1553 'com.apple.product-type.bundle': '"wrapper.plug-in"',
1554 'com.apple.product-type.tool': '"compiled.mach-o.dylib"',
1555 'com.apple.product-type.library.dynamic': '"compiled.mach-o.dylib"',
1556 'com.apple.product-type.framework': '"wrapper.framework"',
1557 'com.apple.product-type.library.static': '"archive.ar"',
1558 'com.apple.product-type.bundle.unit-test': '"wrapper.cfbundle"',
1559 'com.apple.product-type.application.watchapp': '"wrapper.application"',
1560 'com.apple.product-type.watchkit-extension': '"wrapper.app-extension"'
1561 };
1562
1563 return FILETYPE_BY_PRODUCTTYPE[productType]
1564}
1565
1566pbxProject.prototype.getFirstProject = function() {
1567
1568 // Get pbxProject container
1569 var pbxProjectContainer = this.pbxProjectSection();
1570
1571 // Get first pbxProject UUID
1572 var firstProjectUuid = Object.keys(pbxProjectContainer)[0];
1573
1574 // Get first pbxProject
1575 var firstProject = pbxProjectContainer[firstProjectUuid];
1576
1577 return {
1578 uuid: firstProjectUuid,
1579 firstProject: firstProject
1580 }
1581}
1582
1583pbxProject.prototype.getFirstTarget = function() {
1584
1585 // Get first targets UUID
1586 var firstTargetUuid = this.getFirstProject()['firstProject']['targets'][0].value;
1587
1588 // Get first pbxNativeTarget
1589 var firstTarget = this.pbxNativeTargetSection()[firstTargetUuid];
1590
1591 return {
1592 uuid: firstTargetUuid,
1593 firstTarget: firstTarget
1594 }
1595}
1596
1597/*** NEW ***/
1598
1599pbxProject.prototype.addToPbxGroup = function (file, groupKey) {
1600 var group = this.getPBXGroupByKey(groupKey);
1601 if (group && group.children !== undefined) {
1602 if (typeof file === 'string') {
1603 //Group Key
1604 var childGroup = {
1605 value:file,
1606 comment: this.getPBXGroupByKey(file).name
1607 };
1608
1609 group.children.push(childGroup);
1610 }
1611 else {
1612 //File Object
1613 group.children.push(pbxGroupChild(file));
1614 }
1615 }
1616}
1617
1618pbxProject.prototype.removeFromPbxGroup = function (file, groupKey) {
1619 var group = this.getPBXGroupByKey(groupKey);
1620 if (group) {
1621 var groupChildren = group.children, i;
1622 for(i in groupChildren) {
1623 if(pbxGroupChild(file).value == groupChildren[i].value &&
1624 pbxGroupChild(file).comment == groupChildren[i].comment) {
1625 groupChildren.splice(i, 1);
1626 break;
1627 }
1628 }
1629 }
1630}
1631
1632pbxProject.prototype.getPBXGroupByKey = function(key) {
1633 return this.hash.project.objects['PBXGroup'][key];
1634};
1635
1636pbxProject.prototype.findPBXGroupKey = function(criteria) {
1637 var groups = this.hash.project.objects['PBXGroup'];
1638 var target;
1639
1640 for (var key in groups) {
1641 // only look for comments
1642 if (COMMENT_KEY.test(key)) continue;
1643
1644 var group = groups[key];
1645 if (criteria && criteria.path && criteria.name) {
1646 if (criteria.path === group.path && criteria.name === group.name) {
1647 target = key;
1648 break
1649 }
1650 }
1651 else if (criteria && criteria.path) {
1652 if (criteria.path === group.path) {
1653 target = key;
1654 break
1655 }
1656 }
1657 else if (criteria && criteria.name) {
1658 if (criteria.name === group.name) {
1659 target = key;
1660 break
1661 }
1662 }
1663 }
1664
1665 return target;
1666}
1667
1668pbxProject.prototype.pbxCreateGroup = function(name, pathName) {
1669
1670 //Create object
1671 var model = {
1672 isa:"PBXGroup",
1673 children: [],
1674 name: name,
1675 path: pathName,
1676 sourceTree: '"<group>"'
1677 };
1678 var key = this.generateUuid();
1679
1680 //Create comment
1681 var commendId = key + '_comment';
1682
1683 //add obj and commentObj to groups;
1684 var groups = this.hash.project.objects['PBXGroup'];
1685 groups[commendId] = name;
1686 groups[key] = model;
1687
1688 return key;
1689}
1690
1691
1692pbxProject.prototype.getPBXObject = function(name) {
1693 return this.hash.project.objects[name];
1694}
1695
1696
1697
1698pbxProject.prototype.addFile = function (path, group, opt) {
1699 var file = new pbxFile(path, opt);
1700
1701 // null is better for early errors
1702 if (this.hasFile(file.path)) return null;
1703
1704 file.fileRef = this.generateUuid();
1705
1706 this.addToPbxFileReferenceSection(file); // PBXFileReference
1707 this.addToPbxGroup(file, group); // PBXGroup
1708
1709 return file;
1710}
1711
1712pbxProject.prototype.removeFile = function (path, group, opt) {
1713 var file = new pbxFile(path, opt);
1714
1715 this.removeFromPbxFileReferenceSection(file); // PBXFileReference
1716 this.removeFromPbxGroup(file, group); // PBXGroup
1717
1718 return file;
1719}
1720
1721
1722
1723pbxProject.prototype.getBuildProperty = function(prop, build) {
1724 var target;
1725 var configs = this.pbxXCBuildConfigurationSection();
1726 for (var configName in configs) {
1727 if (!COMMENT_KEY.test(configName)) {
1728 var config = configs[configName];
1729 if ( (build && config.name === build) || (build === undefined) ) {
1730 if (config.buildSettings[prop] !== undefined) {
1731 target = config.buildSettings[prop];
1732 }
1733 }
1734 }
1735 }
1736 return target;
1737}
1738
1739pbxProject.prototype.getBuildConfigByName = function(name) {
1740 var target = {};
1741 var configs = this.pbxXCBuildConfigurationSection();
1742 for (var configName in configs) {
1743 if (!COMMENT_KEY.test(configName)) {
1744 var config = configs[configName];
1745 if (config.name === name) {
1746 target[configName] = config;
1747 }
1748 }
1749 }
1750 return target;
1751}
1752
1753pbxProject.prototype.addDataModelDocument = function(filePath, group, opt) {
1754 if (!group) {
1755 group = 'Resources';
1756 }
1757 if (!this.getPBXGroupByKey(group)) {
1758 group = this.findPBXGroupKey({ name: group });
1759 }
1760
1761 var file = new pbxFile(filePath, opt);
1762
1763 if (!file || this.hasFile(file.path)) return null;
1764
1765 file.fileRef = this.generateUuid();
1766 this.addToPbxGroup(file, group);
1767
1768 if (!file) return false;
1769
1770 file.target = opt ? opt.target : undefined;
1771 file.uuid = this.generateUuid();
1772
1773 this.addToPbxBuildFileSection(file);
1774 this.addToPbxSourcesBuildPhase(file);
1775
1776 file.models = [];
1777 var currentVersionName;
1778 var modelFiles = fs.readdirSync(file.path);
1779 for (var index in modelFiles) {
1780 var modelFileName = modelFiles[index];
1781 var modelFilePath = path.join(filePath, modelFileName);
1782
1783 if (modelFileName == '.xccurrentversion') {
1784 currentVersionName = plist.readFileSync(modelFilePath)._XCCurrentVersionName;
1785 continue;
1786 }
1787
1788 var modelFile = new pbxFile(modelFilePath);
1789 modelFile.fileRef = this.generateUuid();
1790
1791 this.addToPbxFileReferenceSection(modelFile);
1792
1793 file.models.push(modelFile);
1794
1795 if (currentVersionName && currentVersionName === modelFileName) {
1796 file.currentModel = modelFile;
1797 }
1798 }
1799
1800 if (!file.currentModel) {
1801 file.currentModel = file.models[0];
1802 }
1803
1804 this.addToXcVersionGroupSection(file);
1805
1806 return file;
1807}
1808
1809
1810module.exports = pbxProject;