blob: c14f28b8e33d7a0488b7dec6e955692d61ce6011 [file] [log] [blame]
/**
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
'License'); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
var util = require('util'),
f = util.format,
EventEmitter = require('events').EventEmitter,
path = require('path'),
uuid = require('uuid'),
fork = require('child_process').fork,
pbxWriter = require('./pbxWriter'),
pbxFile = require('./pbxFile'),
fs = require('fs'),
parser = require('./parser/pbxproj'),
plist = require('simple-plist'),
COMMENT_KEY = /_comment$/
function pbxProject(filename) {
if (!(this instanceof pbxProject))
return new pbxProject(filename);
this.filepath = path.resolve(filename)
}
util.inherits(pbxProject, EventEmitter)
pbxProject.prototype.parse = function(cb) {
var worker = fork(__dirname + '/parseJob.js', [this.filepath])
worker.on('message', function(msg) {
if (msg.name == 'SyntaxError' || msg.code) {
this.emit('error', msg);
} else {
this.hash = msg;
this.emit('end', null, msg)
}
}.bind(this));
if (cb) {
this.on('error', cb);
this.on('end', cb);
}
return this;
}
pbxProject.prototype.parseSync = function() {
var file_contents = fs.readFileSync(this.filepath, 'utf-8');
this.hash = parser.parse(file_contents);
return this;
}
pbxProject.prototype.writeSync = function() {
this.writer = new pbxWriter(this.hash);
return this.writer.writeSync();
}
pbxProject.prototype.allUuids = function() {
var sections = this.hash.project.objects,
uuids = [],
section;
for (key in sections) {
section = sections[key]
uuids = uuids.concat(Object.keys(section))
}
uuids = uuids.filter(function(str) {
return !COMMENT_KEY.test(str) && str.length == 24;
});
return uuids;
}
pbxProject.prototype.generateUuid = function() {
var id = uuid.v4()
.replace(/-/g, '')
.substr(0, 24)
.toUpperCase()
if (this.allUuids().indexOf(id) >= 0) {
return this.generateUuid();
} else {
return id;
}
}
pbxProject.prototype.addPluginFile = function(path, opt) {
var file = new pbxFile(path, opt);
file.plugin = true; // durr
correctForPluginsPath(file, this);
// null is better for early errors
if (this.hasFile(file.path)) return null;
file.fileRef = this.generateUuid();
this.addToPbxFileReferenceSection(file); // PBXFileReference
this.addToPluginsPbxGroup(file); // PBXGroup
return file;
}
pbxProject.prototype.removePluginFile = function(path, opt) {
var file = new pbxFile(path, opt);
correctForPluginsPath(file, this);
this.removeFromPbxFileReferenceSection(file); // PBXFileReference
this.removeFromPluginsPbxGroup(file); // PBXGroup
return file;
}
pbxProject.prototype.addProductFile = function(targetPath, opt) {
var file = new pbxFile(targetPath, opt);
file.includeInIndex = 0;
file.fileRef = this.generateUuid();
file.target = opt ? opt.target : undefined;
file.group = opt ? opt.group : undefined;
file.uuid = this.generateUuid();
file.path = file.basename;
this.addToPbxFileReferenceSection(file);
this.addToProductsPbxGroup(file); // PBXGroup
return file;
}
pbxProject.prototype.removeProductFile = function(path, opt) {
var file = new pbxFile(path, opt);
this.removeFromProductsPbxGroup(file); // PBXGroup
return file;
}
/**
*
* @param path {String}
* @param opt {Object} see pbxFile for avail options
* @param group {String} group key
* @returns {Object} file; see pbxFile
*/
pbxProject.prototype.addSourceFile = function (path, opt, group) {
var file;
if (group) {
file = this.addFile(path, group, opt);
}
else {
file = this.addPluginFile(path, opt);
}
if (!file) return false;
file.target = opt ? opt.target : undefined;
file.uuid = this.generateUuid();
this.addToPbxBuildFileSection(file); // PBXBuildFile
this.addToPbxSourcesBuildPhase(file); // PBXSourcesBuildPhase
return file;
}
/**
*
* @param path {String}
* @param opt {Object} see pbxFile for avail options
* @param group {String} group key
* @returns {Object} file; see pbxFile
*/
pbxProject.prototype.removeSourceFile = function (path, opt, group) {
var file;
if (group) {
file = this.removeFile(path, group, opt);
}
else {
file = this.removePluginFile(path, opt);
}
file.target = opt ? opt.target : undefined;
this.removeFromPbxBuildFileSection(file); // PBXBuildFile
this.removeFromPbxSourcesBuildPhase(file); // PBXSourcesBuildPhase
return file;
}
/**
*
* @param path {String}
* @param opt {Object} see pbxFile for avail options
* @param group {String} group key
* @returns {Object} file; see pbxFile
*/
pbxProject.prototype.addHeaderFile = function (path, opt, group) {
if (group) {
return this.addFile(path, group, opt);
}
else {
return this.addPluginFile(path, opt);
}
}
/**
*
* @param path {String}
* @param opt {Object} see pbxFile for avail options
* @param group {String} group key
* @returns {Object} file; see pbxFile
*/
pbxProject.prototype.removeHeaderFile = function (path, opt, group) {
if (group) {
return this.removeFile(path, group, opt);
}
else {
return this.removePluginFile(path, opt);
}
}
/**
*
* @param path {String}
* @param opt {Object} see pbxFile for avail options
* @param group {String} group key
* @returns {Object} file; see pbxFile
*/
pbxProject.prototype.addResourceFile = function(path, opt, group) {
opt = opt || {};
var file;
if (opt.plugin) {
file = this.addPluginFile(path, opt);
if (!file) return false;
} else {
file = new pbxFile(path, opt);
if (this.hasFile(file.path)) return false;
}
file.uuid = this.generateUuid();
file.target = opt ? opt.target : undefined;
if (!opt.plugin) {
correctForResourcesPath(file, this);
file.fileRef = this.generateUuid();
}
if (!opt.variantGroup) {
this.addToPbxBuildFileSection(file); // PBXBuildFile
this.addToPbxResourcesBuildPhase(file); // PBXResourcesBuildPhase
}
if (!opt.plugin) {
this.addToPbxFileReferenceSection(file); // PBXFileReference
if (group) {
if (this.getPBXGroupByKey(group)) {
this.addToPbxGroup(file, group); //Group other than Resources (i.e. 'splash')
}
else if (this.getPBXVariantGroupByKey(group)) {
this.addToPbxVariantGroup(file, group); // PBXVariantGroup
}
}
else {
this.addToResourcesPbxGroup(file); // PBXGroup
}
}
return file;
}
/**
*
* @param path {String}
* @param opt {Object} see pbxFile for avail options
* @param group {String} group key
* @returns {Object} file; see pbxFile
*/
pbxProject.prototype.removeResourceFile = function(path, opt, group) {
var file = new pbxFile(path, opt);
file.target = opt ? opt.target : undefined;
correctForResourcesPath(file, this);
this.removeFromPbxBuildFileSection(file); // PBXBuildFile
this.removeFromPbxFileReferenceSection(file); // PBXFileReference
if (group) {
if (this.getPBXGroupByKey(group)) {
this.removeFromPbxGroup(file, group); //Group other than Resources (i.e. 'splash')
}
else if (this.getPBXVariantGroupByKey(group)) {
this.removeFromPbxVariantGroup(file, group); // PBXVariantGroup
}
}
else {
this.removeFromResourcesPbxGroup(file); // PBXGroup
}
this.removeFromPbxResourcesBuildPhase(file); // PBXResourcesBuildPhase
return file;
}
pbxProject.prototype.addFramework = function(fpath, opt) {
var customFramework = opt && opt.customFramework == true;
var link = !opt || (opt.link == undefined || opt.link); //defaults to true if not specified
var embed = opt && opt.embed; //defaults to false if not specified
if (opt) {
delete opt.embed;
}
var file = new pbxFile(fpath, opt);
file.uuid = this.generateUuid();
file.fileRef = this.generateUuid();
file.target = opt ? opt.target : undefined;
if (this.hasFile(file.path)) return false;
this.addToPbxBuildFileSection(file); // PBXBuildFile
this.addToPbxFileReferenceSection(file); // PBXFileReference
this.addToFrameworksPbxGroup(file); // PBXGroup
if (link) {
this.addToPbxFrameworksBuildPhase(file); // PBXFrameworksBuildPhase
}
if (customFramework) {
this.addToFrameworkSearchPaths(file);
if (embed) {
opt.embed = embed;
var embeddedFile = new pbxFile(fpath, opt);
embeddedFile.uuid = this.generateUuid();
embeddedFile.fileRef = file.fileRef;
//keeping a separate PBXBuildFile entry for Embed Frameworks
this.addToPbxBuildFileSection(embeddedFile); // PBXBuildFile
this.addToPbxEmbedFrameworksBuildPhase(embeddedFile); // PBXCopyFilesBuildPhase
return embeddedFile;
}
}
return file;
}
pbxProject.prototype.removeFramework = function(fpath, opt) {
var embed = opt && opt.embed;
if (opt) {
delete opt.embed;
}
var file = new pbxFile(fpath, opt);
file.target = opt ? opt.target : undefined;
this.removeFromPbxBuildFileSection(file); // PBXBuildFile
this.removeFromPbxFileReferenceSection(file); // PBXFileReference
this.removeFromFrameworksPbxGroup(file); // PBXGroup
this.removeFromPbxFrameworksBuildPhase(file); // PBXFrameworksBuildPhase
if (opt && opt.customFramework) {
this.removeFromFrameworkSearchPaths(file);
}
opt = opt || {};
opt.embed = true;
var embeddedFile = new pbxFile(fpath, opt);
embeddedFile.fileRef = file.fileRef;
this.removeFromPbxBuildFileSection(embeddedFile); // PBXBuildFile
this.removeFromPbxEmbedFrameworksBuildPhase(embeddedFile); // PBXCopyFilesBuildPhase
return file;
}
pbxProject.prototype.addCopyfile = function(fpath, opt) {
var file = new pbxFile(fpath, opt);
// catch duplicates
if (this.hasFile(file.path)) {
file = this.hasFile(file.path);
}
file.fileRef = file.uuid = this.generateUuid();
file.target = opt ? opt.target : undefined;
this.addToPbxBuildFileSection(file); // PBXBuildFile
this.addToPbxFileReferenceSection(file); // PBXFileReference
this.addToPbxCopyfilesBuildPhase(file); // PBXCopyFilesBuildPhase
return file;
}
pbxProject.prototype.pbxCopyfilesBuildPhaseObj = function(target) {
return this.buildPhaseObject('PBXCopyFilesBuildPhase', 'Copy Files', target);
}
pbxProject.prototype.addToPbxCopyfilesBuildPhase = function(file) {
var sources = this.buildPhaseObject('PBXCopyFilesBuildPhase', 'Copy Files', file.target);
sources.files.push(pbxBuildPhaseObj(file));
}
pbxProject.prototype.removeCopyfile = function(fpath, opt) {
var file = new pbxFile(fpath, opt);
file.target = opt ? opt.target : undefined;
this.removeFromPbxBuildFileSection(file); // PBXBuildFile
this.removeFromPbxFileReferenceSection(file); // PBXFileReference
this.removeFromPbxCopyfilesBuildPhase(file); // PBXFrameworksBuildPhase
return file;
}
pbxProject.prototype.removeFromPbxCopyfilesBuildPhase = function(file) {
var sources = this.pbxCopyfilesBuildPhaseObj(file.target);
for (i in sources.files) {
if (sources.files[i].comment == longComment(file)) {
sources.files.splice(i, 1);
break;
}
}
}
pbxProject.prototype.addStaticLibrary = function(path, opt) {
opt = opt || {};
var file;
if (opt.plugin) {
file = this.addPluginFile(path, opt);
if (!file) return false;
} else {
file = new pbxFile(path, opt);
if (this.hasFile(file.path)) return false;
}
file.uuid = this.generateUuid();
file.target = opt ? opt.target : undefined;
if (!opt.plugin) {
file.fileRef = this.generateUuid();
this.addToPbxFileReferenceSection(file); // PBXFileReference
}
this.addToPbxBuildFileSection(file); // PBXBuildFile
this.addToPbxFrameworksBuildPhase(file); // PBXFrameworksBuildPhase
this.addToLibrarySearchPaths(file); // make sure it gets built!
return file;
}
// helper addition functions
pbxProject.prototype.addToPbxBuildFileSection = function(file) {
var commentKey = f("%s_comment", file.uuid);
this.pbxBuildFileSection()[file.uuid] = pbxBuildFileObj(file);
this.pbxBuildFileSection()[commentKey] = pbxBuildFileComment(file);
}
pbxProject.prototype.removeFromPbxBuildFileSection = function(file) {
var uuid;
for (uuid in this.pbxBuildFileSection()) {
if (this.pbxBuildFileSection()[uuid].fileRef_comment == file.basename) {
file.uuid = uuid;
delete this.pbxBuildFileSection()[uuid];
}
}
var commentKey = f("%s_comment", file.uuid);
delete this.pbxBuildFileSection()[commentKey];
}
pbxProject.prototype.addPbxGroup = function(filePathsArray, name, path, sourceTree) {
var groups = this.hash.project.objects['PBXGroup'],
pbxGroupUuid = this.generateUuid(),
commentKey = f("%s_comment", pbxGroupUuid),
pbxGroup = {
isa: 'PBXGroup',
children: [],
name: name,
path: path,
sourceTree: sourceTree ? sourceTree : '"<group>"'
},
fileReferenceSection = this.pbxFileReferenceSection(),
filePathToReference = {};
for (var key in fileReferenceSection) {
// only look for comments
if (!COMMENT_KEY.test(key)) continue;
var fileReferenceKey = key.split(COMMENT_KEY)[0],
fileReference = fileReferenceSection[fileReferenceKey];
filePathToReference[fileReference.path] = { fileRef: fileReferenceKey, basename: fileReferenceSection[key] };
}
for (var index = 0; index < filePathsArray.length; index++) {
var filePath = filePathsArray[index],
filePathQuoted = "\"" + filePath + "\"";
if (filePathToReference[filePath]) {
pbxGroup.children.push(pbxGroupChild(filePathToReference[filePath]));
continue;
} else if (filePathToReference[filePathQuoted]) {
pbxGroup.children.push(pbxGroupChild(filePathToReference[filePathQuoted]));
continue;
}
var file = new pbxFile(filePath);
file.uuid = this.generateUuid();
file.fileRef = this.generateUuid();
this.addToPbxFileReferenceSection(file); // PBXFileReference
this.addToPbxBuildFileSection(file); // PBXBuildFile
pbxGroup.children.push(pbxGroupChild(file));
}
if (groups) {
groups[pbxGroupUuid] = pbxGroup;
groups[commentKey] = name;
}
return { uuid: pbxGroupUuid, pbxGroup: pbxGroup };
}
pbxProject.prototype.removePbxGroup = function (groupName) {
var section = this.hash.project.objects['PBXGroup'],
key, itemKey;
for (key in section) {
// only look for comments
if (!COMMENT_KEY.test(key)) continue;
if (section[key] == groupName) {
itemKey = key.split(COMMENT_KEY)[0];
delete section[itemKey];
}
}
}
pbxProject.prototype.addToPbxProjectSection = function(target) {
var newTarget = {
value: target.uuid,
comment: pbxNativeTargetComment(target.pbxNativeTarget)
};
this.pbxProjectSection()[this.getFirstProject()['uuid']]['targets'].push(newTarget);
}
pbxProject.prototype.addToPbxNativeTargetSection = function(target) {
var commentKey = f("%s_comment", target.uuid);
this.pbxNativeTargetSection()[target.uuid] = target.pbxNativeTarget;
this.pbxNativeTargetSection()[commentKey] = target.pbxNativeTarget.name;
}
pbxProject.prototype.addToPbxFileReferenceSection = function(file) {
var commentKey = f("%s_comment", file.fileRef);
this.pbxFileReferenceSection()[file.fileRef] = pbxFileReferenceObj(file);
this.pbxFileReferenceSection()[commentKey] = pbxFileReferenceComment(file);
}
pbxProject.prototype.removeFromPbxFileReferenceSection = function(file) {
var i;
var refObj = pbxFileReferenceObj(file);
for (i in this.pbxFileReferenceSection()) {
if (this.pbxFileReferenceSection()[i].name == refObj.name ||
('"' + this.pbxFileReferenceSection()[i].name + '"') == refObj.name ||
this.pbxFileReferenceSection()[i].path == refObj.path ||
('"' + this.pbxFileReferenceSection()[i].path + '"') == refObj.path) {
file.fileRef = file.uuid = i;
delete this.pbxFileReferenceSection()[i];
break;
}
}
var commentKey = f("%s_comment", file.fileRef);
if (this.pbxFileReferenceSection()[commentKey] != undefined) {
delete this.pbxFileReferenceSection()[commentKey];
}
return file;
}
pbxProject.prototype.addToXcVersionGroupSection = function(file) {
if (!file.models || !file.currentModel) {
throw new Error("Cannot create a XCVersionGroup section from not a data model document file");
}
var commentKey = f("%s_comment", file.fileRef);
if (!this.xcVersionGroupSection()[file.fileRef]) {
this.xcVersionGroupSection()[file.fileRef] = {
isa: 'XCVersionGroup',
children: file.models.map(function (el) { return el.fileRef; }),
currentVersion: file.currentModel.fileRef,
name: path.basename(file.path),
path: file.path,
sourceTree: '"<group>"',
versionGroupType: 'wrapper.xcdatamodel'
};
this.xcVersionGroupSection()[commentKey] = path.basename(file.path);
}
}
pbxProject.prototype.addToPluginsPbxGroup = function(file) {
var pluginsGroup = this.pbxGroupByName('Plugins');
if (!pluginsGroup) {
this.addPbxGroup([file.path], 'Plugins');
} else {
pluginsGroup.children.push(pbxGroupChild(file));
}
}
pbxProject.prototype.removeFromPluginsPbxGroup = function(file) {
if (!this.pbxGroupByName('Plugins')) {
return null;
}
var pluginsGroupChildren = this.pbxGroupByName('Plugins').children, i;
for (i in pluginsGroupChildren) {
if (pbxGroupChild(file).value == pluginsGroupChildren[i].value &&
pbxGroupChild(file).comment == pluginsGroupChildren[i].comment) {
pluginsGroupChildren.splice(i, 1);
break;
}
}
}
pbxProject.prototype.addToResourcesPbxGroup = function(file) {
var pluginsGroup = this.pbxGroupByName('Resources');
if (!pluginsGroup) {
this.addPbxGroup([file.path], 'Resources');
} else {
pluginsGroup.children.push(pbxGroupChild(file));
}
}
pbxProject.prototype.removeFromResourcesPbxGroup = function(file) {
if (!this.pbxGroupByName('Resources')) {
return null;
}
var pluginsGroupChildren = this.pbxGroupByName('Resources').children, i;
for (i in pluginsGroupChildren) {
if (pbxGroupChild(file).value == pluginsGroupChildren[i].value &&
pbxGroupChild(file).comment == pluginsGroupChildren[i].comment) {
pluginsGroupChildren.splice(i, 1);
break;
}
}
}
pbxProject.prototype.addToFrameworksPbxGroup = function(file) {
var pluginsGroup = this.pbxGroupByName('Frameworks');
if (!pluginsGroup) {
this.addPbxGroup([file.path], 'Frameworks');
} else {
pluginsGroup.children.push(pbxGroupChild(file));
}
}
pbxProject.prototype.removeFromFrameworksPbxGroup = function(file) {
if (!this.pbxGroupByName('Frameworks')) {
return null;
}
var pluginsGroupChildren = this.pbxGroupByName('Frameworks').children;
for (i in pluginsGroupChildren) {
if (pbxGroupChild(file).value == pluginsGroupChildren[i].value &&
pbxGroupChild(file).comment == pluginsGroupChildren[i].comment) {
pluginsGroupChildren.splice(i, 1);
break;
}
}
}
pbxProject.prototype.addToPbxEmbedFrameworksBuildPhase = function (file) {
var sources = this.pbxEmbedFrameworksBuildPhaseObj(file.target);
if (sources) {
sources.files.push(pbxBuildPhaseObj(file));
}
}
pbxProject.prototype.removeFromPbxEmbedFrameworksBuildPhase = function (file) {
var sources = this.pbxEmbedFrameworksBuildPhaseObj(file.target);
if (sources) {
var files = [];
for (i in sources.files) {
if (sources.files[i].comment != longComment(file)) {
files.push(sources.files[i]);
}
}
sources.files = files;
}
}
pbxProject.prototype.addToProductsPbxGroup = function(file) {
var productsGroup = this.pbxGroupByName('Products');
if (!productsGroup) {
this.addPbxGroup([file.path], 'Products');
} else {
productsGroup.children.push(pbxGroupChild(file));
}
}
pbxProject.prototype.removeFromProductsPbxGroup = function(file) {
if (!this.pbxGroupByName('Products')) {
return null;
}
var productsGroupChildren = this.pbxGroupByName('Products').children, i;
for (i in productsGroupChildren) {
if (pbxGroupChild(file).value == productsGroupChildren[i].value &&
pbxGroupChild(file).comment == productsGroupChildren[i].comment) {
productsGroupChildren.splice(i, 1);
break;
}
}
}
pbxProject.prototype.addToPbxSourcesBuildPhase = function(file) {
var sources = this.pbxSourcesBuildPhaseObj(file.target);
sources.files.push(pbxBuildPhaseObj(file));
}
pbxProject.prototype.removeFromPbxSourcesBuildPhase = function(file) {
var sources = this.pbxSourcesBuildPhaseObj(file.target), i;
for (i in sources.files) {
if (sources.files[i].comment == longComment(file)) {
sources.files.splice(i, 1);
break;
}
}
}
pbxProject.prototype.addToPbxResourcesBuildPhase = function(file) {
var sources = this.pbxResourcesBuildPhaseObj(file.target);
sources.files.push(pbxBuildPhaseObj(file));
}
pbxProject.prototype.removeFromPbxResourcesBuildPhase = function(file) {
var sources = this.pbxResourcesBuildPhaseObj(file.target), i;
for (i in sources.files) {
if (sources.files[i].comment == longComment(file)) {
sources.files.splice(i, 1);
break;
}
}
}
pbxProject.prototype.addToPbxFrameworksBuildPhase = function(file) {
var sources = this.pbxFrameworksBuildPhaseObj(file.target);
sources.files.push(pbxBuildPhaseObj(file));
}
pbxProject.prototype.removeFromPbxFrameworksBuildPhase = function(file) {
var sources = this.pbxFrameworksBuildPhaseObj(file.target);
for (i in sources.files) {
if (sources.files[i].comment == longComment(file)) {
sources.files.splice(i, 1);
break;
}
}
}
pbxProject.prototype.addXCConfigurationList = function(configurationObjectsArray, defaultConfigurationName, comment) {
var pbxBuildConfigurationSection = this.pbxXCBuildConfigurationSection(),
pbxXCConfigurationListSection = this.pbxXCConfigurationList(),
xcConfigurationListUuid = this.generateUuid(),
commentKey = f("%s_comment", xcConfigurationListUuid),
xcConfigurationList = {
isa: 'XCConfigurationList',
buildConfigurations: [],
defaultConfigurationIsVisible: 0,
defaultConfigurationName: defaultConfigurationName
};
for (var index = 0; index < configurationObjectsArray.length; index++) {
var configuration = configurationObjectsArray[index],
configurationUuid = this.generateUuid(),
configurationCommentKey = f("%s_comment", configurationUuid);
pbxBuildConfigurationSection[configurationUuid] = configuration;
pbxBuildConfigurationSection[configurationCommentKey] = configuration.name;
xcConfigurationList.buildConfigurations.push({ value: configurationUuid, comment: configuration.name });
}
if (pbxXCConfigurationListSection) {
pbxXCConfigurationListSection[xcConfigurationListUuid] = xcConfigurationList;
pbxXCConfigurationListSection[commentKey] = comment;
}
return { uuid: xcConfigurationListUuid, xcConfigurationList: xcConfigurationList };
}
pbxProject.prototype.addTargetDependency = function(target, dependencyTargets) {
if (!target)
return undefined;
var nativeTargets = this.pbxNativeTargetSection();
if (typeof nativeTargets[target] == "undefined")
throw new Error("Invalid target: " + target);
for (var index = 0; index < dependencyTargets.length; index++) {
var dependencyTarget = dependencyTargets[index];
if (typeof nativeTargets[dependencyTarget] == "undefined")
throw new Error("Invalid target: " + dependencyTarget);
}
var pbxTargetDependency = 'PBXTargetDependency',
pbxContainerItemProxy = 'PBXContainerItemProxy',
pbxTargetDependencySection = this.hash.project.objects[pbxTargetDependency],
pbxContainerItemProxySection = this.hash.project.objects[pbxContainerItemProxy];
for (var index = 0; index < dependencyTargets.length; index++) {
var dependencyTargetUuid = dependencyTargets[index],
dependencyTargetCommentKey = f("%s_comment", dependencyTargetUuid),
targetDependencyUuid = this.generateUuid(),
targetDependencyCommentKey = f("%s_comment", targetDependencyUuid),
itemProxyUuid = this.generateUuid(),
itemProxyCommentKey = f("%s_comment", itemProxyUuid),
itemProxy = {
isa: pbxContainerItemProxy,
containerPortal: this.hash.project['rootObject'],
containerPortal_comment: this.hash.project['rootObject_comment'],
proxyType: 1,
remoteGlobalIDString: dependencyTargetUuid,
remoteInfo: nativeTargets[dependencyTargetUuid].name
},
targetDependency = {
isa: pbxTargetDependency,
target: dependencyTargetUuid,
target_comment: nativeTargets[dependencyTargetCommentKey],
targetProxy: itemProxyUuid,
targetProxy_comment: pbxContainerItemProxy
};
if (pbxContainerItemProxySection && pbxTargetDependencySection) {
pbxContainerItemProxySection[itemProxyUuid] = itemProxy;
pbxContainerItemProxySection[itemProxyCommentKey] = pbxContainerItemProxy;
pbxTargetDependencySection[targetDependencyUuid] = targetDependency;
pbxTargetDependencySection[targetDependencyCommentKey] = pbxTargetDependency;
nativeTargets[target].dependencies.push({ value: targetDependencyUuid, comment: pbxTargetDependency })
}
}
return { uuid: target, target: nativeTargets[target] };
}
pbxProject.prototype.addBuildPhase = function(filePathsArray, buildPhaseType, comment, target, optionsOrFolderType, subfolderPath) {
var buildPhaseSection,
fileReferenceSection = this.pbxFileReferenceSection(),
buildFileSection = this.pbxBuildFileSection(),
buildPhaseUuid = this.generateUuid(),
buildPhaseTargetUuid = target || this.getFirstTarget().uuid,
commentKey = f("%s_comment", buildPhaseUuid),
buildPhase = {
isa: buildPhaseType,
buildActionMask: 2147483647,
files: [],
runOnlyForDeploymentPostprocessing: 0
},
filePathToBuildFile = {};
if (buildPhaseType === 'PBXCopyFilesBuildPhase') {
buildPhase = pbxCopyFilesBuildPhaseObj(buildPhase, optionsOrFolderType, subfolderPath, comment);
} else if (buildPhaseType === 'PBXShellScriptBuildPhase') {
buildPhase = pbxShellScriptBuildPhaseObj(buildPhase, optionsOrFolderType, comment)
}
if (!this.hash.project.objects[buildPhaseType]) {
this.hash.project.objects[buildPhaseType] = new Object();
}
if (!this.hash.project.objects[buildPhaseType][buildPhaseUuid]) {
this.hash.project.objects[buildPhaseType][buildPhaseUuid] = buildPhase;
this.hash.project.objects[buildPhaseType][commentKey] = comment;
}
if (this.hash.project.objects['PBXNativeTarget'][buildPhaseTargetUuid]['buildPhases']) {
this.hash.project.objects['PBXNativeTarget'][buildPhaseTargetUuid]['buildPhases'].push({
value: buildPhaseUuid,
comment: comment
})
}
for (var key in buildFileSection) {
// only look for comments
if (!COMMENT_KEY.test(key)) continue;
var buildFileKey = key.split(COMMENT_KEY)[0],
buildFile = buildFileSection[buildFileKey];
fileReference = fileReferenceSection[buildFile.fileRef];
if (!fileReference) continue;
var pbxFileObj = new pbxFile(fileReference.path);
filePathToBuildFile[fileReference.path] = { uuid: buildFileKey, basename: pbxFileObj.basename, group: pbxFileObj.group };
}
for (var index = 0; index < filePathsArray.length; index++) {
var filePath = filePathsArray[index],
filePathQuoted = "\"" + filePath + "\"",
file = new pbxFile(filePath);
if (filePathToBuildFile[filePath]) {
buildPhase.files.push(pbxBuildPhaseObj(filePathToBuildFile[filePath]));
continue;
} else if (filePathToBuildFile[filePathQuoted]) {
buildPhase.files.push(pbxBuildPhaseObj(filePathToBuildFile[filePathQuoted]));
continue;
}
file.uuid = this.generateUuid();
file.fileRef = this.generateUuid();
this.addToPbxFileReferenceSection(file); // PBXFileReference
this.addToPbxBuildFileSection(file); // PBXBuildFile
buildPhase.files.push(pbxBuildPhaseObj(file));
}
if (buildPhaseSection) {
buildPhaseSection[buildPhaseUuid] = buildPhase;
buildPhaseSection[commentKey] = comment;
}
return { uuid: buildPhaseUuid, buildPhase: buildPhase };
}
// helper access functions
pbxProject.prototype.pbxProjectSection = function() {
return this.hash.project.objects['PBXProject'];
}
pbxProject.prototype.pbxBuildFileSection = function() {
return this.hash.project.objects['PBXBuildFile'];
}
pbxProject.prototype.pbxXCBuildConfigurationSection = function() {
return this.hash.project.objects['XCBuildConfiguration'];
}
pbxProject.prototype.pbxFileReferenceSection = function() {
return this.hash.project.objects['PBXFileReference'];
}
pbxProject.prototype.pbxNativeTargetSection = function() {
return this.hash.project.objects['PBXNativeTarget'];
}
pbxProject.prototype.xcVersionGroupSection = function () {
if (typeof this.hash.project.objects['XCVersionGroup'] !== 'object') {
this.hash.project.objects['XCVersionGroup'] = {};
}
return this.hash.project.objects['XCVersionGroup'];
}
pbxProject.prototype.pbxXCConfigurationList = function() {
return this.hash.project.objects['XCConfigurationList'];
}
pbxProject.prototype.pbxGroupByName = function(name) {
var groups = this.hash.project.objects['PBXGroup'],
key, groupKey;
for (key in groups) {
// only look for comments
if (!COMMENT_KEY.test(key)) continue;
if (groups[key] == name) {
groupKey = key.split(COMMENT_KEY)[0];
return groups[groupKey];
}
}
return null;
}
pbxProject.prototype.pbxTargetByName = function(name) {
return this.pbxItemByComment(name, 'PBXNativeTarget');
}
pbxProject.prototype.findTargetKey = function(name) {
var targets = this.hash.project.objects['PBXNativeTarget'];
for (var key in targets) {
// only look for comments
if (COMMENT_KEY.test(key)) continue;
var target = targets[key];
if (target.name === name) {
return key;
}
}
return null;
}
pbxProject.prototype.pbxItemByComment = function(name, pbxSectionName) {
var section = this.hash.project.objects[pbxSectionName],
key, itemKey;
for (key in section) {
// only look for comments
if (!COMMENT_KEY.test(key)) continue;
if (section[key] == name) {
itemKey = key.split(COMMENT_KEY)[0];
return section[itemKey];
}
}
return null;
}
pbxProject.prototype.pbxSourcesBuildPhaseObj = function(target) {
return this.buildPhaseObject('PBXSourcesBuildPhase', 'Sources', target);
}
pbxProject.prototype.pbxResourcesBuildPhaseObj = function(target) {
return this.buildPhaseObject('PBXResourcesBuildPhase', 'Resources', target);
}
pbxProject.prototype.pbxFrameworksBuildPhaseObj = function(target) {
return this.buildPhaseObject('PBXFrameworksBuildPhase', 'Frameworks', target);
}
pbxProject.prototype.pbxEmbedFrameworksBuildPhaseObj = function (target) {
return this.buildPhaseObject('PBXCopyFilesBuildPhase', 'Embed Frameworks', target);
};
// Find Build Phase from group/target
pbxProject.prototype.buildPhase = function(group, target) {
if (!target)
return undefined;
var nativeTargets = this.pbxNativeTargetSection();
if (typeof nativeTargets[target] == "undefined")
throw new Error("Invalid target: " + target);
var nativeTarget = nativeTargets[target];
var buildPhases = nativeTarget.buildPhases;
for(var i in buildPhases)
{
var buildPhase = buildPhases[i];
if (buildPhase.comment==group)
return buildPhase.value + "_comment";
}
}
pbxProject.prototype.buildPhaseObject = function(name, group, target) {
var section = this.hash.project.objects[name],
obj, sectionKey, key;
var buildPhase = this.buildPhase(group, target);
for (key in section) {
// only look for comments
if (!COMMENT_KEY.test(key)) continue;
// select the proper buildPhase
if (buildPhase && buildPhase!=key)
continue;
if (section[key] == group) {
sectionKey = key.split(COMMENT_KEY)[0];
return section[sectionKey];
}
}
return null;
}
pbxProject.prototype.addBuildProperty = function(prop, value, build_name) {
var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
key, configuration;
for (key in configurations){
configuration = configurations[key];
if (!build_name || configuration.name === build_name){
configuration.buildSettings[prop] = value;
}
}
}
pbxProject.prototype.removeBuildProperty = function(prop, build_name) {
var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
key, configuration;
for (key in configurations){
configuration = configurations[key];
if (configuration.buildSettings[prop] &&
!build_name || configuration.name === build_name){
delete configuration.buildSettings[prop];
}
}
}
/**
*
* @param prop {String}
* @param value {String|Array|Object|Number|Boolean}
* @param build {String} Release or Debug
*/
pbxProject.prototype.updateBuildProperty = function(prop, value, build) {
var configs = this.pbxXCBuildConfigurationSection();
for (var configName in configs) {
if (!COMMENT_KEY.test(configName)) {
var config = configs[configName];
if ( (build && config.name === build) || (!build) ) {
config.buildSettings[prop] = value;
}
}
}
}
pbxProject.prototype.updateProductName = function(name) {
this.updateBuildProperty('PRODUCT_NAME', '"' + name + '"');
}
pbxProject.prototype.removeFromFrameworkSearchPaths = function(file) {
var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
INHERITED = '"$(inherited)"',
SEARCH_PATHS = 'FRAMEWORK_SEARCH_PATHS',
config, buildSettings, searchPaths;
var new_path = searchPathForFile(file, this);
for (config in configurations) {
buildSettings = configurations[config].buildSettings;
if (unquote(buildSettings['PRODUCT_NAME']) != this.productName)
continue;
searchPaths = buildSettings[SEARCH_PATHS];
if (searchPaths && Array.isArray(searchPaths)) {
var matches = searchPaths.filter(function(p) {
return p.indexOf(new_path) > -1;
});
matches.forEach(function(m) {
var idx = searchPaths.indexOf(m);
searchPaths.splice(idx, 1);
});
}
}
}
pbxProject.prototype.addToFrameworkSearchPaths = function(file) {
var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
INHERITED = '"$(inherited)"',
config, buildSettings, searchPaths;
for (config in configurations) {
buildSettings = configurations[config].buildSettings;
if (unquote(buildSettings['PRODUCT_NAME']) != this.productName)
continue;
if (!buildSettings['FRAMEWORK_SEARCH_PATHS']
|| buildSettings['FRAMEWORK_SEARCH_PATHS'] === INHERITED) {
buildSettings['FRAMEWORK_SEARCH_PATHS'] = [INHERITED];
}
buildSettings['FRAMEWORK_SEARCH_PATHS'].push(searchPathForFile(file, this));
}
}
pbxProject.prototype.removeFromLibrarySearchPaths = function(file) {
var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
INHERITED = '"$(inherited)"',
SEARCH_PATHS = 'LIBRARY_SEARCH_PATHS',
config, buildSettings, searchPaths;
var new_path = searchPathForFile(file, this);
for (config in configurations) {
buildSettings = configurations[config].buildSettings;
if (unquote(buildSettings['PRODUCT_NAME']) != this.productName)
continue;
searchPaths = buildSettings[SEARCH_PATHS];
if (searchPaths && Array.isArray(searchPaths)) {
var matches = searchPaths.filter(function(p) {
return p.indexOf(new_path) > -1;
});
matches.forEach(function(m) {
var idx = searchPaths.indexOf(m);
searchPaths.splice(idx, 1);
});
}
}
}
pbxProject.prototype.addToLibrarySearchPaths = function(file) {
var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
INHERITED = '"$(inherited)"',
config, buildSettings, searchPaths;
for (config in configurations) {
buildSettings = configurations[config].buildSettings;
if (unquote(buildSettings['PRODUCT_NAME']) != this.productName)
continue;
if (!buildSettings['LIBRARY_SEARCH_PATHS']
|| buildSettings['LIBRARY_SEARCH_PATHS'] === INHERITED) {
buildSettings['LIBRARY_SEARCH_PATHS'] = [INHERITED];
}
if (typeof file === 'string') {
buildSettings['LIBRARY_SEARCH_PATHS'].push(file);
} else {
buildSettings['LIBRARY_SEARCH_PATHS'].push(searchPathForFile(file, this));
}
}
}
pbxProject.prototype.removeFromHeaderSearchPaths = function(file) {
var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
INHERITED = '"$(inherited)"',
SEARCH_PATHS = 'HEADER_SEARCH_PATHS',
config, buildSettings, searchPaths;
var new_path = searchPathForFile(file, this);
for (config in configurations) {
buildSettings = configurations[config].buildSettings;
if (unquote(buildSettings['PRODUCT_NAME']) != this.productName)
continue;
if (buildSettings[SEARCH_PATHS]) {
var matches = buildSettings[SEARCH_PATHS].filter(function(p) {
return p.indexOf(new_path) > -1;
});
matches.forEach(function(m) {
var idx = buildSettings[SEARCH_PATHS].indexOf(m);
buildSettings[SEARCH_PATHS].splice(idx, 1);
});
}
}
}
pbxProject.prototype.addToHeaderSearchPaths = function(file) {
var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
INHERITED = '"$(inherited)"',
config, buildSettings, searchPaths;
for (config in configurations) {
buildSettings = configurations[config].buildSettings;
if (unquote(buildSettings['PRODUCT_NAME']) != this.productName)
continue;
if (!buildSettings['HEADER_SEARCH_PATHS']) {
buildSettings['HEADER_SEARCH_PATHS'] = [INHERITED];
}
if (typeof file === 'string') {
buildSettings['HEADER_SEARCH_PATHS'].push(file);
} else {
buildSettings['HEADER_SEARCH_PATHS'].push(searchPathForFile(file, this));
}
}
}
pbxProject.prototype.addToOtherLinkerFlags = function (flag) {
var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
INHERITED = '"$(inherited)"',
OTHER_LDFLAGS = 'OTHER_LDFLAGS',
config, buildSettings;
for (config in configurations) {
buildSettings = configurations[config].buildSettings;
if (unquote(buildSettings['PRODUCT_NAME']) != this.productName)
continue;
if (!buildSettings[OTHER_LDFLAGS]
|| buildSettings[OTHER_LDFLAGS] === INHERITED) {
buildSettings[OTHER_LDFLAGS] = [INHERITED];
}
buildSettings[OTHER_LDFLAGS].push(flag);
}
}
pbxProject.prototype.removeFromOtherLinkerFlags = function (flag) {
var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
OTHER_LDFLAGS = 'OTHER_LDFLAGS',
config, buildSettings;
for (config in configurations) {
buildSettings = configurations[config].buildSettings;
if (unquote(buildSettings['PRODUCT_NAME']) != this.productName) {
continue;
}
if (buildSettings[OTHER_LDFLAGS]) {
var matches = buildSettings[OTHER_LDFLAGS].filter(function (p) {
return p.indexOf(flag) > -1;
});
matches.forEach(function (m) {
var idx = buildSettings[OTHER_LDFLAGS].indexOf(m);
buildSettings[OTHER_LDFLAGS].splice(idx, 1);
});
}
}
}
pbxProject.prototype.addToBuildSettings = function (buildSetting, value) {
var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
config, buildSettings;
for (config in configurations) {
buildSettings = configurations[config].buildSettings;
buildSettings[buildSetting] = value;
}
}
pbxProject.prototype.removeFromBuildSettings = function (buildSetting) {
var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
config, buildSettings;
for (config in configurations) {
buildSettings = configurations[config].buildSettings;
if (buildSettings[buildSetting]) {
delete buildSettings[buildSetting];
}
}
}
// a JS getter. hmmm
pbxProject.prototype.__defineGetter__("productName", function() {
var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
config, productName;
for (config in configurations) {
productName = configurations[config].buildSettings['PRODUCT_NAME'];
if (productName) {
return unquote(productName);
}
}
});
// check if file is present
pbxProject.prototype.hasFile = function(filePath) {
var files = nonComments(this.pbxFileReferenceSection()),
file, id;
for (id in files) {
file = files[id];
if (file.path == filePath || file.path == ('"' + filePath + '"')) {
return file;
}
}
return false;
}
pbxProject.prototype.addTarget = function(name, type, subfolder) {
// Setup uuid and name of new target
var targetUuid = this.generateUuid(),
targetType = type,
targetSubfolder = subfolder || name,
targetName = name.trim();
// Check type against list of allowed target types
if (!targetName) {
throw new Error("Target name missing.");
}
// Check type against list of allowed target types
if (!targetType) {
throw new Error("Target type missing.");
}
// Check type against list of allowed target types
if (!producttypeForTargettype(targetType)) {
throw new Error("Target type invalid: " + targetType);
}
// Build Configuration: Create
var buildConfigurationsList = [
{
name: 'Debug',
isa: 'XCBuildConfiguration',
buildSettings: {
GCC_PREPROCESSOR_DEFINITIONS: ['"DEBUG=1"', '"$(inherited)"'],
INFOPLIST_FILE: '"' + path.join(targetSubfolder, targetSubfolder + '-Info.plist' + '"'),
LD_RUNPATH_SEARCH_PATHS: '"$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"',
PRODUCT_NAME: '"' + targetName + '"',
SKIP_INSTALL: 'YES'
}
},
{
name: 'Release',
isa: 'XCBuildConfiguration',
buildSettings: {
INFOPLIST_FILE: '"' + path.join(targetSubfolder, targetSubfolder + '-Info.plist' + '"'),
LD_RUNPATH_SEARCH_PATHS: '"$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"',
PRODUCT_NAME: '"' + targetName + '"',
SKIP_INSTALL: 'YES'
}
}
];
// Build Configuration: Add
var buildConfigurations = this.addXCConfigurationList(buildConfigurationsList, 'Release', 'Build configuration list for PBXNativeTarget "' + targetName +'"');
// Product: Create
var productName = targetName,
productType = producttypeForTargettype(targetType),
productFileType = filetypeForProducttype(productType),
productFile = this.addProductFile(productName, { group: 'Copy Files', 'target': targetUuid, 'explicitFileType': productFileType}),
productFileName = productFile.basename;
// Product: Add to build file list
this.addToPbxBuildFileSection(productFile);
// Target: Create
var target = {
uuid: targetUuid,
pbxNativeTarget: {
isa: 'PBXNativeTarget',
name: '"' + targetName + '"',
productName: '"' + targetName + '"',
productReference: productFile.fileRef,
productType: '"' + producttypeForTargettype(targetType) + '"',
buildConfigurationList: buildConfigurations.uuid,
buildPhases: [],
buildRules: [],
dependencies: []
}
};
// Target: Add to PBXNativeTarget section
this.addToPbxNativeTargetSection(target)
// Product: Embed (only for "extension"-type targets)
if (targetType === 'app_extension') {
// Create CopyFiles phase in first target
this.addBuildPhase([], 'PBXCopyFilesBuildPhase', 'Copy Files', this.getFirstTarget().uuid, targetType)
// Add product to CopyFiles phase
this.addToPbxCopyfilesBuildPhase(productFile)
// this.addBuildPhaseToTarget(newPhase.buildPhase, this.getFirstTarget().uuid)
};
// Target: Add uuid to root project
this.addToPbxProjectSection(target);
// Target: Add dependency for this target to first (main) target
this.addTargetDependency(this.getFirstTarget().uuid, [target.uuid]);
// Return target on success
return target;
};
// helper recursive prop search+replace
function propReplace(obj, prop, value) {
var o = {};
for (var p in obj) {
if (o.hasOwnProperty.call(obj, p)) {
if (typeof obj[p] == 'object' && !Array.isArray(obj[p])) {
propReplace(obj[p], prop, value);
} else if (p == prop) {
obj[p] = value;
}
}
}
}
// helper object creation functions
function pbxBuildFileObj(file) {
var obj = Object.create(null);
obj.isa = 'PBXBuildFile';
obj.fileRef = file.fileRef;
obj.fileRef_comment = file.basename;
if (file.settings) obj.settings = file.settings;
return obj;
}
function pbxFileReferenceObj(file) {
var fileObject = {
isa: "PBXFileReference",
name: "\"" + file.basename + "\"",
path: "\"" + file.path.replace(/\\/g, '/') + "\"",
sourceTree: file.sourceTree,
fileEncoding: file.fileEncoding,
lastKnownFileType: file.lastKnownFileType,
explicitFileType: file.explicitFileType,
includeInIndex: file.includeInIndex
};
return fileObject;
}
function pbxGroupChild(file) {
var obj = Object.create(null);
obj.value = file.fileRef;
obj.comment = file.basename;
return obj;
}
function pbxBuildPhaseObj(file) {
var obj = Object.create(null);
obj.value = file.uuid;
obj.comment = longComment(file);
return obj;
}
function pbxCopyFilesBuildPhaseObj(obj, folderType, subfolderPath, phaseName) {
// Add additional properties for 'CopyFiles' build phase
var DESTINATION_BY_TARGETTYPE = {
application: 'wrapper',
app_extension: 'plugins',
bundle: 'wrapper',
command_line_tool: 'wrapper',
dynamic_library: 'products_directory',
framework: 'shared_frameworks',
frameworks: 'frameworks',
static_library: 'products_directory',
unit_test_bundle: 'wrapper',
watch_app: 'wrapper',
watch_extension: 'plugins'
}
var SUBFOLDERSPEC_BY_DESTINATION = {
absolute_path: 0,
executables: 6,
frameworks: 10,
java_resources: 15,
plugins: 13,
products_directory: 16,
resources: 7,
shared_frameworks: 11,
shared_support: 12,
wrapper: 1,
xpc_services: 0
}
obj.name = '"' + phaseName + '"';
obj.dstPath = subfolderPath || '""';
obj.dstSubfolderSpec = SUBFOLDERSPEC_BY_DESTINATION[DESTINATION_BY_TARGETTYPE[folderType]];
return obj;
}
function pbxShellScriptBuildPhaseObj(obj, options, phaseName) {
obj.name = '"' + phaseName + '"';
obj.inputPaths = options.inputPaths || [];
obj.outputPaths = options.outputPaths || [];
obj.shellPath = options.shellPath;
obj.shellScript = '"' + options.shellScript.replace(/"/g, '\\"') + '"';
return obj;
}
function pbxBuildFileComment(file) {
return longComment(file);
}
function pbxFileReferenceComment(file) {
return file.basename || path.basename(file.path);
}
function pbxNativeTargetComment(target) {
return target.name;
}
function longComment(file) {
return f("%s in %s", file.basename, file.group);
}
// respect <group> path
function correctForPluginsPath(file, project) {
return correctForPath(file, project, 'Plugins');
}
function correctForResourcesPath(file, project) {
return correctForPath(file, project, 'Resources');
}
function correctForFrameworksPath(file, project) {
return correctForPath(file, project, 'Frameworks');
}
function correctForPath(file, project, group) {
var r_group_dir = new RegExp('^' + group + '[\\\\/]');
if (project.pbxGroupByName(group).path)
file.path = file.path.replace(r_group_dir, '');
return file;
}
function searchPathForFile(file, proj) {
var plugins = proj.pbxGroupByName('Plugins'),
pluginsPath = plugins ? plugins.path : null,
fileDir = path.dirname(file.path);
if (fileDir == '.') {
fileDir = '';
} else {
fileDir = '/' + fileDir;
}
if (file.plugin && pluginsPath) {
return '"\\"$(SRCROOT)/' + unquote(pluginsPath) + '\\""';
} else if (file.customFramework && file.dirname) {
return '"\\"' + file.dirname + '\\""';
} else {
return '"\\"$(SRCROOT)/' + proj.productName + fileDir + '\\""';
}
}
function nonComments(obj) {
var keys = Object.keys(obj),
newObj = {}, i = 0;
for (i; i < keys.length; i++) {
if (!COMMENT_KEY.test(keys[i])) {
newObj[keys[i]] = obj[keys[i]];
}
}
return newObj;
}
function unquote(str) {
if (str) return str.replace(/^"(.*)"$/, "$1");
}
function buildPhaseNameForIsa (isa) {
BUILDPHASENAME_BY_ISA = {
PBXCopyFilesBuildPhase: 'Copy Files',
PBXResourcesBuildPhase: 'Resources',
PBXSourcesBuildPhase: 'Sources',
PBXFrameworksBuildPhase: 'Frameworks'
}
return BUILDPHASENAME_BY_ISA[isa]
}
function producttypeForTargettype (targetType) {
PRODUCTTYPE_BY_TARGETTYPE = {
application: 'com.apple.product-type.application',
app_extension: 'com.apple.product-type.app-extension',
bundle: 'com.apple.product-type.bundle',
command_line_tool: 'com.apple.product-type.tool',
dynamic_library: 'com.apple.product-type.library.dynamic',
framework: 'com.apple.product-type.framework',
static_library: 'com.apple.product-type.library.static',
unit_test_bundle: 'com.apple.product-type.bundle.unit-test',
watch_app: 'com.apple.product-type.application.watchapp',
watch_extension: 'com.apple.product-type.watchkit-extension'
};
return PRODUCTTYPE_BY_TARGETTYPE[targetType]
}
function filetypeForProducttype (productType) {
FILETYPE_BY_PRODUCTTYPE = {
'com.apple.product-type.application': '"wrapper.application"',
'com.apple.product-type.app-extension': '"wrapper.app-extension"',
'com.apple.product-type.bundle': '"wrapper.plug-in"',
'com.apple.product-type.tool': '"compiled.mach-o.dylib"',
'com.apple.product-type.library.dynamic': '"compiled.mach-o.dylib"',
'com.apple.product-type.framework': '"wrapper.framework"',
'com.apple.product-type.library.static': '"archive.ar"',
'com.apple.product-type.bundle.unit-test': '"wrapper.cfbundle"',
'com.apple.product-type.application.watchapp': '"wrapper.application"',
'com.apple.product-type.watchkit-extension': '"wrapper.app-extension"'
};
return FILETYPE_BY_PRODUCTTYPE[productType]
}
pbxProject.prototype.getFirstProject = function() {
// Get pbxProject container
var pbxProjectContainer = this.pbxProjectSection();
// Get first pbxProject UUID
var firstProjectUuid = Object.keys(pbxProjectContainer)[0];
// Get first pbxProject
var firstProject = pbxProjectContainer[firstProjectUuid];
return {
uuid: firstProjectUuid,
firstProject: firstProject
}
}
pbxProject.prototype.getFirstTarget = function() {
// Get first targets UUID
var firstTargetUuid = this.getFirstProject()['firstProject']['targets'][0].value;
// Get first pbxNativeTarget
var firstTarget = this.pbxNativeTargetSection()[firstTargetUuid];
return {
uuid: firstTargetUuid,
firstTarget: firstTarget
}
}
/*** NEW ***/
pbxProject.prototype.addToPbxGroupType = function (file, groupKey, groupType) {
var group = this.getPBXGroupByKeyAndType(groupKey, groupType);
if (group && group.children !== undefined) {
if (typeof file === 'string') {
//Group Key
var childGroup = {
value:file,
};
if (this.getPBXGroupByKey(file)) {
childGroup.comment = this.getPBXGroupByKey(file).name;
}
else if (this.getPBXVariantGroupByKey(file)) {
childGroup.comment = this.getPBXVariantGroupByKey(file).name;
}
group.children.push(childGroup);
}
else {
//File Object
group.children.push(pbxGroupChild(file));
}
}
}
pbxProject.prototype.addToPbxVariantGroup = function (file, groupKey) {
this.addToPbxGroupType(file, groupKey, 'PBXVariantGroup');
}
pbxProject.prototype.addToPbxGroup = function (file, groupKey) {
this.addToPbxGroupType(file, groupKey, 'PBXGroup');
}
pbxProject.prototype.pbxCreateGroupWithType = function(name, pathName, groupType) {
//Create object
var model = {
isa: '"' + groupType + '"',
children: [],
name: name,
sourceTree: '"<group>"'
};
if (pathName) model.path = pathName;
var key = this.generateUuid();
//Create comment
var commendId = key + '_comment';
//add obj and commentObj to groups;
var groups = this.hash.project.objects[groupType];
if (!groups) {
groups = this.hash.project.objects[groupType] = new Object();
}
groups[commendId] = name;
groups[key] = model;
return key;
}
pbxProject.prototype.pbxCreateVariantGroup = function(name) {
return this.pbxCreateGroupWithType(name, undefined, 'PBXVariantGroup')
}
pbxProject.prototype.pbxCreateGroup = function(name, pathName) {
return this.pbxCreateGroupWithType(name, pathName, 'PBXGroup');
}
pbxProject.prototype.removeFromPbxGroupAndType = function (file, groupKey, groupType) {
var group = this.getPBXGroupByKeyAndType(groupKey, groupType);
if (group) {
var groupChildren = group.children, i;
for(i in groupChildren) {
if(pbxGroupChild(file).value == groupChildren[i].value &&
pbxGroupChild(file).comment == groupChildren[i].comment) {
groupChildren.splice(i, 1);
break;
}
}
}
}
pbxProject.prototype.removeFromPbxGroup = function (file, groupKey) {
this.removeFromPbxGroupAndType(file, groupKey, 'PBXGroup');
}
pbxProject.prototype.removeFromPbxVariantGroup = function (file, groupKey) {
this.removeFromPbxGroupAndType(file, groupKey, 'PBXVariantGroup');
}
pbxProject.prototype.getPBXGroupByKeyAndType = function(key, groupType) {
return this.hash.project.objects[groupType][key];
};
pbxProject.prototype.getPBXGroupByKey = function(key) {
return this.hash.project.objects['PBXGroup'][key];
};
pbxProject.prototype.getPBXVariantGroupByKey = function(key) {
return this.hash.project.objects['PBXVariantGroup'][key];
};
pbxProject.prototype.findPBXGroupKeyAndType = function(criteria, groupType) {
var groups = this.hash.project.objects[groupType];
var target;
for (var key in groups) {
// only look for comments
if (COMMENT_KEY.test(key)) continue;
var group = groups[key];
if (criteria && criteria.path && criteria.name) {
if (criteria.path === group.path && criteria.name === group.name) {
target = key;
break
}
}
else if (criteria && criteria.path) {
if (criteria.path === group.path) {
target = key;
break
}
}
else if (criteria && criteria.name) {
if (criteria.name === group.name) {
target = key;
break
}
}
}
return target;
}
pbxProject.prototype.findPBXGroupKey = function(criteria) {
return this.findPBXGroupKeyAndType(criteria, 'PBXGroup');
}
pbxProject.prototype.findPBXVariantGroupKey = function(criteria) {
return this.findPBXGroupKeyAndType(criteria, 'PBXVariantGroup');
}
pbxProject.prototype.addLocalizationVariantGroup = function(name) {
var groupKey = this.pbxCreateVariantGroup(name);
var resourceGroupKey = this.findPBXGroupKey({name: 'Resources'});
this.addToPbxGroup(groupKey, resourceGroupKey);
var localizationVariantGroup = {
uuid: this.generateUuid(),
fileRef: groupKey,
basename: name
}
this.addToPbxBuildFileSection(localizationVariantGroup); // PBXBuildFile
this.addToPbxResourcesBuildPhase(localizationVariantGroup); //PBXResourcesBuildPhase
return localizationVariantGroup;
};
pbxProject.prototype.addKnownRegion = function (name) {
if (!this.pbxProjectSection()[this.getFirstProject()['uuid']]['knownRegions']) {
this.pbxProjectSection()[this.getFirstProject()['uuid']]['knownRegions'] = [];
}
if (!this.hasKnownRegion(name)) {
this.pbxProjectSection()[this.getFirstProject()['uuid']]['knownRegions'].push(name);
}
}
pbxProject.prototype.removeKnownRegion = function (name) {
var regions = this.pbxProjectSection()[this.getFirstProject()['uuid']]['knownRegions'];
if (regions) {
for (var i = 0; i < regions.length; i++) {
if (regions[i] === name) {
regions.splice(i, 1);
break;
}
}
this.pbxProjectSection()[this.getFirstProject()['uuid']]['knownRegions'] = regions;
}
}
pbxProject.prototype.hasKnownRegion = function (name) {
var regions = this.pbxProjectSection()[this.getFirstProject()['uuid']]['knownRegions'];
if (regions) {
for (var i in regions) {
if (regions[i] === name) {
return true;
}
}
}
return false;
}
pbxProject.prototype.getPBXObject = function(name) {
return this.hash.project.objects[name];
}
pbxProject.prototype.addFile = function (path, group, opt) {
var file = new pbxFile(path, opt);
// null is better for early errors
if (this.hasFile(file.path)) return null;
file.fileRef = this.generateUuid();
this.addToPbxFileReferenceSection(file); // PBXFileReference
if (this.getPBXGroupByKey(group)) {
this.addToPbxGroup(file, group); // PBXGroup
}
else if (this.getPBXVariantGroupByKey(group)) {
this.addToPbxVariantGroup(file, group); // PBXVariantGroup
}
return file;
}
pbxProject.prototype.removeFile = function (path, group, opt) {
var file = new pbxFile(path, opt);
this.removeFromPbxFileReferenceSection(file); // PBXFileReference
if (this.getPBXGroupByKey(group)) {
this.removeFromPbxGroup(file, group); // PBXGroup
}
else if (this.getPBXVariantGroupByKey(group)) {
this.removeFromPbxVariantGroup(file, group); // PBXVariantGroup
}
return file;
}
pbxProject.prototype.getBuildProperty = function(prop, build) {
var target;
var configs = this.pbxXCBuildConfigurationSection();
for (var configName in configs) {
if (!COMMENT_KEY.test(configName)) {
var config = configs[configName];
if ( (build && config.name === build) || (build === undefined) ) {
if (config.buildSettings[prop] !== undefined) {
target = config.buildSettings[prop];
}
}
}
}
return target;
}
pbxProject.prototype.getBuildConfigByName = function(name) {
var target = {};
var configs = this.pbxXCBuildConfigurationSection();
for (var configName in configs) {
if (!COMMENT_KEY.test(configName)) {
var config = configs[configName];
if (config.name === name) {
target[configName] = config;
}
}
}
return target;
}
pbxProject.prototype.addDataModelDocument = function(filePath, group, opt) {
if (!group) {
group = 'Resources';
}
if (!this.getPBXGroupByKey(group)) {
group = this.findPBXGroupKey({ name: group });
}
var file = new pbxFile(filePath, opt);
if (!file || this.hasFile(file.path)) return null;
file.fileRef = this.generateUuid();
this.addToPbxGroup(file, group);
if (!file) return false;
file.target = opt ? opt.target : undefined;
file.uuid = this.generateUuid();
this.addToPbxBuildFileSection(file);
this.addToPbxSourcesBuildPhase(file);
file.models = [];
var currentVersionName;
var modelFiles = fs.readdirSync(file.path);
for (var index in modelFiles) {
var modelFileName = modelFiles[index];
var modelFilePath = path.join(filePath, modelFileName);
if (modelFileName == '.xccurrentversion') {
currentVersionName = plist.readFileSync(modelFilePath)._XCCurrentVersionName;
continue;
}
var modelFile = new pbxFile(modelFilePath);
modelFile.fileRef = this.generateUuid();
this.addToPbxFileReferenceSection(modelFile);
file.models.push(modelFile);
if (currentVersionName && currentVersionName === modelFileName) {
file.currentModel = modelFile;
}
}
if (!file.currentModel) {
file.currentModel = file.models[0];
}
this.addToXcVersionGroupSection(file);
return file;
}
pbxProject.prototype.addTargetAttribute = function(prop, value, target) {
var attributes = this.getFirstProject()['firstProject']['attributes'];
if (attributes['TargetAttributes'] === undefined) {
attributes['TargetAttributes'] = {};
}
target = target || this.getFirstTarget();
if (attributes['TargetAttributes'][target.uuid] === undefined) {
attributes['TargetAttributes'][target.uuid] = {};
}
attributes['TargetAttributes'][target.uuid][prop] = value;
}
pbxProject.prototype.removeTargetAttribute = function(prop, target) {
var attributes = this.getFirstProject()['firstProject']['attributes'];
target = target || this.getFirstTarget();
if (attributes['TargetAttributes'] &&
attributes['TargetAttributes'][target.uuid]) {
delete attributes['TargetAttributes'][target.uuid][prop];
}
}
module.exports = pbxProject;