UNPKG

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