blob: 32196e35fcbfa067e9d1476538698c91bf223cd5 [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 fs = require('fs');
var path = require('path');
var shell = require('shelljs');
var events = require('cordova-common').events;
var CordovaError = require('cordova-common').CordovaError;
// These frameworks are required by cordova-osx by default. We should never add/remove them.
var keep_these_frameworks = [
'MobileCoreServices.framework',
'CoreGraphics.framework',
'AssetsLibrary.framework'
];
var handlers = {
'source-file': {
install: function (obj, plugin, project, options) {
installHelper('source-file', obj, plugin.dir, project.projectDir, plugin.id, options, project);
},
uninstall: function (obj, plugin, project, options) {
uninstallHelper('source-file', obj, project.projectDir, plugin.id, options, project);
}
},
'header-file': {
install: function (obj, plugin, project, options) {
installHelper('header-file', obj, plugin.dir, project.projectDir, plugin.id, options, project);
},
uninstall: function (obj, plugin, project, options) {
uninstallHelper('header-file', obj, project.projectDir, plugin.id, options, project);
}
},
'resource-file': {
install: function (obj, plugin, project, options) {
var src = obj.src;
var srcFile = path.resolve(plugin.dir, src);
var destFile = path.resolve(project.resources_dir, path.basename(src));
if (!fs.existsSync(srcFile)) throw new CordovaError('cannot find "' + srcFile + '" osx <resource-file>');
if (fs.existsSync(destFile)) throw new CordovaError('target destination "' + destFile + '" already exists');
project.xcode.addResourceFile(path.join('Resources', path.basename(src)));
shell.cp('-R', srcFile, project.resources_dir);
},
uninstall: function (obj, plugin, project, options) {
var src = obj.src;
var 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, project, options) {
var src = obj.src;
var custom = obj.custom;
if (!custom) {
if (keep_these_frameworks.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);
var targetDir = path.resolve(project.plugins_dir, plugin.id, path.basename(src));
if (!fs.existsSync(srcFile)) throw new CordovaError('cannot find "' + srcFile + '" osx <framework>');
if (fs.existsSync(targetDir)) throw new CordovaError('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.projectDir, targetDir);
var pbxFile = project.xcode.addFramework(project_relative, { customFramework: true });
if (pbxFile) {
project.xcode.addToPbxEmbedFrameworksBuildPhase(pbxFile);
}
},
uninstall: function (obj, plugin, project, options) {
var src = obj.src;
if (!obj.custom) {
if (keep_these_frameworks.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));
var pbxFile = project.xcode.removeFramework(targetDir, { customFramework: true });
if (pbxFile) {
project.xcode.removeFromPbxEmbedFrameworksBuildPhase(pbxFile);
}
shell.rm('-rf', targetDir);
}
},
'lib-file': {
install: function (obj, plugin, project, options) {
events.emit('verbose', 'lib-file.install is not supported for osx');
},
uninstall: function (obj, plugin, project, options) {
events.emit('verbose', 'lib-file.uninstall is not supported for osx');
}
},
asset: {
install: function (obj, plugin, project, options) {
if (!obj.src) {
throw new CordovaError('<asset> tag without required "src" attribute. plugin=' + plugin.dir);
}
if (!obj.target) {
throw new CordovaError('<asset> tag without required "target" attribute');
}
var www = options.usePlatformWww ? project.platformWww : project.www;
copyFile(plugin.dir, obj.src, www, obj.target);
},
uninstall: function (obj, plugin, project, options) {
var target = obj.target;
if (!target) {
throw new Error('<asset> tag without required "target" attribute');
}
var www = options.usePlatformWww ? project.platformWww : project.www;
removeFile(www, target);
removeFileF(path.resolve(project.www, 'plugins', plugin.id));
}
},
'js-module': {
install: function (obj, plugin, project, options) {
// Copy the plugin's files into the www directory.
var moduleSource = path.resolve(plugin.dir, obj.src);
var moduleName = plugin.id + '.' + (obj.name || path.parse(obj.src).name);
// Read in the file, prepend the cordova.define, and write it back out.
var scriptContent = fs.readFileSync(moduleSource, 'utf-8').replace(/^\ufeff/, ''); // Window BOM
if (moduleSource.match(/.*\.json$/)) {
scriptContent = 'module.exports = ' + scriptContent;
}
scriptContent = 'cordova.define("' + moduleName + '", function(require, exports, module) {\n' + scriptContent + '\n});\n';
var www = options.usePlatformWww ? project.platformWww : project.www;
var moduleDestination = path.resolve(www, 'plugins', plugin.id, obj.src);
shell.mkdir('-p', path.dirname(moduleDestination));
fs.writeFileSync(moduleDestination, scriptContent, 'utf-8');
},
uninstall: function (obj, plugin, project, options) {
var pluginRelativePath = path.join('plugins', plugin.id, obj.src);
var www = options.usePlatformWww ? project.platformWww : project.www;
removeFileAndParents(www, pluginRelativePath);
}
}
};
module.exports.getInstaller = function (type) {
if (handlers[type] && handlers[type].install) {
return handlers[type].install;
}
events.emit('warn', '<' + type + '> is not supported for osx plugins');
};
module.exports.getUninstaller = function (type) {
if (handlers[type] && handlers[type].uninstall) {
return handlers[type].uninstall;
}
events.emit('warn', '<' + type + '> is not supported for osx plugins');
};
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.
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 {
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);
}
}
var pathSepFix = new RegExp(path.sep.replace(/\\/, '\\\\'), 'g');
function fixPathSep (file) {
return file.replace(pathSepFix, '/');
}
function copyFile (plugin_dir, src, project_dir, dest, link) {
src = path.resolve(plugin_dir, src);
if (!fs.existsSync(src)) throw new CordovaError('"' + src + '" not found!');
// check that src path is inside plugin directory
var real_path = fs.realpathSync(src);
var real_plugin_path = fs.realpathSync(plugin_dir);
if (real_path.indexOf(real_plugin_path) !== 0) { throw new CordovaError('"' + src + '" not located within plugin!'); }
dest = path.resolve(project_dir, dest);
// check that dest path is located in project directory
if (dest.indexOf(project_dir) !== 0) { throw new CordovaError('"' + dest + '" not located within project!'); }
shell.mkdir('-p', path.dirname(dest));
if (link) {
fs.symlinkSync(path.relative(path.dirname(dest), src), dest);
} else if (fs.statSync(src).isDirectory()) {
// XXX shelljs decides to create a directory when -R|-r is used which sucks. http://goo.gl/nbsjq
shell.cp('-Rf', src + '/*', dest);
} else {
shell.cp('-f', src, dest);
}
}
// Same as copy file but throws error if target exists
function copyNewFile (plugin_dir, src, project_dir, dest, link) {
var target_path = path.resolve(project_dir, dest);
if (fs.existsSync(target_path)) { throw new CordovaError('"' + target_path + '" already exists!'); }
copyFile(plugin_dir, src, project_dir, dest, !!link);
}
// checks if file exists and then deletes. Error if doesn't exist
function removeFile (project_dir, src) {
var file = path.resolve(project_dir, src);
shell.rm('-Rf', file);
}
// deletes file/directory without checking
function removeFileF (file) {
shell.rm('-Rf', file);
}
function removeFileAndParents (baseDir, destFile, stopper) {
stopper = stopper || '.';
var file = path.resolve(baseDir, destFile);
if (!fs.existsSync(file)) return;
removeFileF(file);
// check if directory is empty
var curDir = path.dirname(file);
while (curDir !== path.resolve(baseDir, stopper)) {
if (fs.existsSync(curDir) && fs.readdirSync(curDir).length === 0) {
fs.rmdirSync(curDir);
curDir = path.resolve(curDir, '..');
} else {
// directory not empty...do nothing
break;
}
}
}