ionic
Version:
A tool for creating and developing Ionic Framework mobile apps.
315 lines (284 loc) • 13.6 kB
JavaScript
/*
*
* Copyright 2013 Anis Kadri
*
* Licensed 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.
*
*/
/* jshint laxcomma:true, sub:true */
var path = require('path')
, common = require('./common')
, fs = require('fs')
, glob = require('glob')
, xcode = require('xcode')
, plist = require('plist')
, shell = require('shelljs')
, semver = require('semver')
, events = require('cordova-common').events
, _ = require('underscore')
, CordovaError = require('cordova-common').CordovaError
, cachedProjectFiles = {}
;
function installHelper(type, obj, plugin_dir, project_dir, plugin_id, options, project) {
var srcFile = path.resolve(plugin_dir, obj.src);
var targetDir = path.resolve(project.plugins_dir, plugin_id, obj.targetDir || '');
var destFile = path.join(targetDir, path.basename(obj.src));
var project_ref;
var link = !!(options && options.link);
if (link) {
var trueSrc = fs.realpathSync(srcFile);
// Create a symlink in the expected place, so that uninstall can use it.
common.copyNewFile(plugin_dir, trueSrc, project_dir, destFile, link);
// Xcode won't save changes to a file if there is a symlink involved.
// Make the Xcode reference the file directly.
// Note: Can't use path.join() here since it collapses 'Plugins/..', and xcode
// library special-cases Plugins/ prefix.
project_ref = 'Plugins/' + fixPathSep(path.relative(fs.realpathSync(project.plugins_dir), trueSrc));
} else {
common.copyNewFile(plugin_dir, srcFile, project_dir, destFile, link);
project_ref = 'Plugins/' + fixPathSep(path.relative(project.plugins_dir, destFile));
}
if (type == 'header-file') {
project.xcode.addHeaderFile(project_ref);
} else if (obj.framework) {
var opt = { weak: obj.weak };
var project_relative = path.join(path.basename(project.xcode_path), project_ref);
project.xcode.addFramework(project_relative, opt);
project.xcode.addToLibrarySearchPaths({path:project_ref});
} else {
project.xcode.addSourceFile(project_ref, obj.compilerFlags ? {compilerFlags:obj.compilerFlags} : {});
}
}
function uninstallHelper(type, obj, project_dir, plugin_id, options, project) {
var targetDir = path.resolve(project.plugins_dir, plugin_id, obj.targetDir || '');
var destFile = path.join(targetDir, path.basename(obj.src));
var project_ref;
var link = !!(options && options.link);
if (link) {
var trueSrc = fs.readlinkSync(destFile);
project_ref = 'Plugins/' + fixPathSep(path.relative(fs.realpathSync(project.plugins_dir), trueSrc));
} else {
project_ref = 'Plugins/' + fixPathSep(path.relative(project.plugins_dir, destFile));
}
shell.rm('-rf', targetDir);
if (type == 'header-file') {
project.xcode.removeHeaderFile(project_ref);
} else if (obj.framework) {
var project_relative = path.join(path.basename(project.xcode_path), project_ref);
project.xcode.removeFramework(project_relative);
project.xcode.removeFromLibrarySearchPaths({path:project_ref});
} else {
project.xcode.removeSourceFile(project_ref);
}
}
// special handlers to add frameworks to the 'Embed Frameworks' build phase, needed for custom frameworks
// see CB-9517. should probably be moved to node-xcode.
var util = require('util');
function pbxBuildPhaseObj(file) {
var obj = Object.create(null);
obj.value = file.uuid;
obj.comment = longComment(file);
return obj;
}
function longComment(file) {
return util.format('%s in %s', file.basename, file.group);
}
xcode.project.prototype.pbxEmbedFrameworksBuildPhaseObj = function (target) {
return this.buildPhaseObject('PBXCopyFilesBuildPhase', 'Embed Frameworks', target);
};
xcode.project.prototype.addToPbxEmbedFrameworksBuildPhase = function (file) {
var sources = this.pbxEmbedFrameworksBuildPhaseObj(file.target);
if (sources) {
sources.files.push(pbxBuildPhaseObj(file));
}
};
xcode.project.prototype.removeFromPbxEmbedFrameworksBuildPhase = function (file) {
var sources = this.pbxEmbedFrameworksBuildPhaseObj(file.target);
if (sources) {
sources.files = _.reject(sources.files, function(file){
return file.comment === longComment(file);
});
}
};
// These frameworks are required by cordova-ios by default. We should never add/remove them.
var keep_these_frameworks = [
'MobileCoreServices.framework',
'CoreGraphics.framework',
'AssetsLibrary.framework'
];
module.exports = {
www_dir:function(project_dir) {
return path.join(project_dir, 'www');
},
package_name:function(project_dir) {
var plist_file = glob.sync(path.join(project_dir, '**', '*-Info.plist'))[0];
return plist.parse(fs.readFileSync(plist_file, 'utf8')).CFBundleIdentifier;
},
'source-file':{
install:function(obj, plugin_dir, project_dir, plugin_id, options, project) {
installHelper('source-file', obj, plugin_dir, project_dir, plugin_id, options, project);
},
uninstall:function(obj, project_dir, plugin_id, options, project) {
uninstallHelper('source-file', obj, project_dir, plugin_id, options, project);
}
},
'header-file':{
install:function(obj, plugin_dir, project_dir, plugin_id, options, project) {
installHelper('header-file', obj, plugin_dir, project_dir, plugin_id, options, project);
},
uninstall:function(obj, project_dir, plugin_id, options, project) {
uninstallHelper('header-file', obj, project_dir, plugin_id, options, project);
}
},
'resource-file':{
install:function(reobj, plugin_dir, project_dir, plugin_id, options, project) {
var src = reobj.src,
srcFile = path.resolve(plugin_dir, src),
destFile = path.resolve(project.resources_dir, path.basename(src));
if (!fs.existsSync(srcFile)) throw new Error('cannot find "' + srcFile + '" ios <resource-file>');
if (fs.existsSync(destFile)) throw new Error('target destination "' + destFile + '" already exists');
project.xcode.addResourceFile(path.join('Resources', path.basename(src)));
shell.cp('-R', srcFile, project.resources_dir);
},
uninstall:function(reobj, project_dir, plugin_id, options, project) {
var src = reobj.src,
destFile = path.resolve(project.resources_dir, path.basename(src));
project.xcode.removeResourceFile(path.join('Resources', path.basename(src)));
shell.rm('-rf', destFile);
}
},
'framework':{ // CB-5238 custom frameworks only
install:function(obj, plugin_dir, project_dir, plugin_id, options, project) {
var src = obj.src,
custom = obj.custom;
if (!custom) {
var keepFrameworks = keep_these_frameworks;
// CoreLocation dependency removed in cordova-ios@3.6.0.
if (semver.lt(project.cordovaVersion, '3.6.0-dev')) {
keepFrameworks = keepFrameworks.concat(['CoreLocation.framework']);
}
if (keepFrameworks.indexOf(src) < 0) {
project.xcode.addFramework(src, {weak: obj.weak});
project.frameworks[src] = (project.frameworks[src] || 0) + 1;
}
return;
}
var srcFile = path.resolve(plugin_dir, src),
targetDir = path.resolve(project.plugins_dir, plugin_id, path.basename(src));
if (!fs.existsSync(srcFile)) throw new Error('cannot find "' + srcFile + '" ios <framework>');
if (fs.existsSync(targetDir)) throw new Error('target destination "' + targetDir + '" already exists');
shell.mkdir('-p', path.dirname(targetDir));
shell.cp('-R', srcFile, path.dirname(targetDir)); // frameworks are directories
var project_relative = path.relative(project_dir, targetDir);
var pbxFile = project.xcode.addFramework(project_relative, {customFramework: true});
if (pbxFile) {
project.xcode.addToPbxEmbedFrameworksBuildPhase(pbxFile);
}
},
uninstall:function(obj, project_dir, plugin_id, options, project) {
var src = obj.src;
if (!obj.custom) {
var keepFrameworks = keep_these_frameworks;
// CoreLocation dependency removed in cordova-ios@3.6.0.
if (semver.lt(project.cordovaVersion, '3.6.0-dev')) {
keepFrameworks = keepFrameworks.concat(['CoreLocation.framework']);
}
if (keepFrameworks.indexOf(src) < 0) {
project.frameworks[src] -= (project.frameworks[src] || 1) - 1;
if (project.frameworks[src] < 1) {
// Only remove non-custom framework from xcode project
// if there is no references remains
project.xcode.removeFramework(src);
delete project.frameworks[src];
}
}
return;
}
var targetDir = path.resolve(project.plugins_dir, plugin_id, path.basename(src)),
pbxFile = project.xcode.removeFramework(targetDir, {customFramework: true});
if (pbxFile) {
project.xcode.removeFromPbxEmbedFrameworksBuildPhase(pbxFile);
}
shell.rm('-rf', targetDir);
}
},
'lib-file': {
install:function(obj, plugin_dir, project_dir, plugin_id, options) {
events.emit('verbose', 'lib-file.install is not supported for ios');
},
uninstall:function(obj, project_dir, plugin_id, options) {
events.emit('verbose', 'lib-file.uninstall is not supported for ios');
}
},
parseProjectFile:function(project_dir) {
// TODO: With ConfigKeeper introduced in config-changes.js
// there is now double caching of iOS project files.
// Remove the cache here when install can handle
// a list of plugins at once.
if (cachedProjectFiles[project_dir]) {
return cachedProjectFiles[project_dir];
}
// grab and parse pbxproj
// we don't want CordovaLib's xcode project
var project_files = glob.sync(path.join(project_dir, '*.xcodeproj', 'project.pbxproj'));
if (project_files.length === 0) {
throw new Error('does not appear to be an xcode project (no xcode project file)');
}
var pbxPath = project_files[0];
var xcodeproj = xcode.project(pbxPath);
xcodeproj.parseSync();
var xcBuildConfiguration = xcodeproj.pbxXCBuildConfigurationSection();
var plist_file_entry = _.find(xcBuildConfiguration, function (entry) { return entry.buildSettings && entry.buildSettings.INFOPLIST_FILE; });
var plist_file = path.join(project_dir, plist_file_entry.buildSettings.INFOPLIST_FILE.replace(/^"(.*)"$/g, '$1').replace(/\\&/g, '&'));
var config_file = path.join(path.dirname(plist_file), 'config.xml');
if (!fs.existsSync(plist_file) || !fs.existsSync(config_file)) {
throw new CordovaError('could not find -Info.plist file, or config.xml file.');
}
var frameworks_file = path.join(project_dir, 'frameworks.json');
var frameworks = {};
try {
frameworks = require(frameworks_file);
} catch (e) { }
var xcode_dir = path.dirname(plist_file);
var pluginsDir = path.resolve(xcode_dir, 'Plugins');
var resourcesDir = path.resolve(xcode_dir, 'Resources');
var cordovaVersion = fs.readFileSync(path.join(project_dir, 'CordovaLib', 'VERSION'), 'utf8').trim();
cachedProjectFiles[project_dir] = {
plugins_dir:pluginsDir,
resources_dir:resourcesDir,
xcode:xcodeproj,
xcode_path:xcode_dir,
pbx: pbxPath,
write: function () {
fs.writeFileSync(pbxPath, xcodeproj.writeSync());
if (Object.keys(this.frameworks).length === 0){
// If there is no framework references remain in the project, just remove this file
shell.rm('-rf', frameworks_file);
return;
}
fs.writeFileSync(frameworks_file, JSON.stringify(this.frameworks, null, 4));
},
cordovaVersion: cordovaVersion,
frameworks: frameworks
};
return cachedProjectFiles[project_dir];
},
purgeProjectFileCache:function(project_dir) {
delete cachedProjectFiles[project_dir];
}
};
var pathSepFix = new RegExp(path.sep.replace(/\\/,'\\\\'),'g');
function fixPathSep(file) {
return file.replace(pathSepFix, '/');
}