function updateSplashes(cordovaProject, platformResourcesDir) { var resources = cordovaProject.projectConfig.getSplashScreens('android'); // if there are "splash" elements in config.xml if (resources.length === 0) { events.emit('verbose', 'This app does not have splash screens defined'); return; } var resourceMap = mapImageResources(cordovaProject.root, platformResourcesDir, 'drawable', 'screen.png'); var hadMdpi = false; resources.forEach(function (resource) { if (!resource.density) { return; } if (resource.density == 'mdpi') { hadMdpi = true; } var targetPath = getImageResourcePath( platformResourcesDir, 'drawable', resource.density, 'screen.png', path.basename(resource.src)); resourceMap[targetPath] = resource.src; }); // There's no "default" drawable, so assume default == mdpi. if (!hadMdpi && resources.defaultResource) { var targetPath = getImageResourcePath( platformResourcesDir, 'drawable', 'mdpi', 'screen.png', path.basename(resources.defaultResource.src)); resourceMap[targetPath] = resources.defaultResource.src; } events.emit('verbose', 'Updating splash screens at ' + platformResourcesDir); FileUpdater.updatePaths( resourceMap, { rootDir: cordovaProject.root }, logFileOp); }
amazon_fireos_parser.prototype.handleSplashes = function (config) { var resources = config.getSplashScreens('android'); var destfilepath; // if there are "splash" elements in config.xml if (resources.length > 0) { var densities = this.deleteDefaultResource('screen.png'); events.emit('verbose', 'splash screens: ' + JSON.stringify(resources)); var res = path.join(this.path, 'res'); if (resources.defaultResource) { destfilepath = path.join(res, 'drawable', 'screen.png'); events.emit('verbose', 'copying splash icon from ' + resources.defaultResource.src + ' to ' + destfilepath); shell.cp('-f', resources.defaultResource.src, destfilepath); } for (var i = 0; i < densities.length; i++) { var density = densities[i]; var resource = resources.getByDensity(density); if (resource) { // copy splash screens. destfilepath = path.join(res, 'drawable-' + density, 'screen.png'); events.emit('verbose', 'copying splash icon from ' + resource.src + ' to ' + destfilepath); shell.cp('-f', resource.src, destfilepath); } } } };
ios_parser.prototype.update_build_settings = function(config) { var targetDevice = parseTargetDevicePreference(config.getPreference('target-device', 'ios')); var deploymentTarget = config.getPreference('deployment-target', 'ios'); // no build settings provided, we don't need to parse and update .pbxproj file if (!targetDevice && !deploymentTarget) { return Q(); } var proj = new xcode.project(this.pbxproj); try { proj.parseSync(); } catch (err) { return Q.reject(new Error('An error occured during parsing of project.pbxproj. Start weeping. Output: ' + err)); } if (targetDevice) { events.emit('verbose', 'Set TARGETED_DEVICE_FAMILY to ' + targetDevice + '.'); proj.updateBuildProperty('TARGETED_DEVICE_FAMILY', targetDevice); } if (deploymentTarget) { events.emit('verbose', 'Set IPHONEOS_DEPLOYMENT_TARGET to "' + deploymentTarget + '".'); proj.updateBuildProperty('IPHONEOS_DEPLOYMENT_TARGET', deploymentTarget); } fs.writeFileSync(this.pbxproj, proj.writeSync(), 'utf-8'); return Q(); };
function handleError(error) { if (error.code === 'ENOENT') { events.emit('warn', 'Platform does not support ' + this.script); } else { events.emit('warn', 'An unexpected error has occured while running ' + this.script + ' with code ' + error.code + ': ' + error); } }
/** * Updates project structure and AndroidManifest according to project's configuration. * * @param {ConfigParser} platformConfig A project's configuration that will * be used to update project * @param {Object} locations A map of locations for this platform */ function updateProjectAccordingTo(platformConfig, locations) { // Update app name by editing res/values/strings.xml var name = platformConfig.name(); var strings = xmlHelpers.parseElementtreeSync(locations.strings); strings.find('string[@name="app_name"]').text = name.replace(/\'/g, '\\\''); fs.writeFileSync(locations.strings, strings.write({indent: 4}), 'utf-8'); events.emit('verbose', 'Wrote out android application name "' + name + '" to ' + locations.strings); // Java packages cannot support dashes var pkg = (platformConfig.android_packageName() || platformConfig.packageName()).replace(/-/g, '_'); var manifest = new AndroidManifest(locations.manifest); var orig_pkg = manifest.getPackageId(); manifest.getActivity() .setOrientation(platformConfig.getPreference('orientation')) .setLaunchMode(findAndroidLaunchModePreference(platformConfig)); manifest.setVersionName(platformConfig.version()) .setVersionCode(platformConfig.android_versionCode() || default_versionCode(platformConfig.version())) .setPackageId(pkg) .setMinSdkVersion(platformConfig.getPreference('android-minSdkVersion', 'android')) .setMaxSdkVersion(platformConfig.getPreference('android-maxSdkVersion', 'android')) .setTargetSdkVersion(platformConfig.getPreference('android-targetSdkVersion', 'android')) .write(); var javaPattern = path.join(locations.root, 'src', orig_pkg.replace(/\./g, '/'), '*.java'); var java_files = shell.ls(javaPattern).filter(function(f) { return shell.grep(/extends\s+CordovaActivity/g, f); }); if (java_files.length === 0) { throw new CordovaError('No Java files found that extend CordovaActivity.'); } else if(java_files.length > 1) { events.emit('log', 'Multiple candidate Java files that extend CordovaActivity found. Guessing at the first one, ' + java_files[0]); } var destFile = path.join(locations.root, 'src', pkg.replace(/\./g, '/'), path.basename(java_files[0])); shell.mkdir('-p', path.dirname(destFile)); shell.sed(/package [\w\.]*;/, 'package ' + pkg + ';', java_files[0]).to(destFile); events.emit('verbose', 'Wrote out Android package name "' + pkg + '" to ' + destFile); if (orig_pkg !== pkg) { // If package was name changed we need to remove old java with main activity shell.rm('-Rf',java_files[0]); // remove any empty directories var currentDir = path.dirname(java_files[0]); var sourcesRoot = path.resolve(locations.root, 'src'); while(currentDir !== sourcesRoot) { if(fs.existsSync(currentDir) && fs.readdirSync(currentDir).length === 0) { fs.rmdirSync(currentDir); currentDir = path.resolve(currentDir, '..'); } else { break; } } } }
.then(function(pluginInfo){ // save to config.xml if(saveToConfigXmlOn(config_json, opts)){ var src = parseSource(target, opts); var attributes = { name: pluginInfo.id }; if (src) { attributes.spec = src; } else { var ver = '~' + pluginInfo.version; // Scoped packages need to have the package-spec along with the version var parsedSpec = pluginSpec.parse(target); if (parsedSpec.scope) { attributes.spec = parsedSpec.package + '@' + ver; } else { attributes.spec = ver; } } xml = cordova_util.projectConfig(projectRoot); cfg = new ConfigParser(xml); cfg.removePlugin(pluginInfo.id); cfg.addPlugin(attributes, opts.cli_variables); cfg.write(); events.emit('results', 'Saved plugin info for "' + pluginInfo.id + '" to config.xml'); } });
install:function(obj, plugin, project, options) { var src = obj.src; if (!src) throw new CordovaError(generateAttributeError('src', 'framework', plugin.id)); events.emit('verbose', 'Installing Android library: ' + src); var parentDir = obj.parent ? path.resolve(project.projectDir, obj.parent) : project.projectDir; var subDir; if (obj.custom) { var subRelativeDir = project.getCustomSubprojectRelativeDir(plugin.id, src); copyNewFile(plugin.dir, src, project.projectDir, subRelativeDir, !!(options && options.link)); subDir = path.resolve(project.projectDir, subRelativeDir); } else { // WEEX_HOOK obj.type = 'gradleReference'; //obj.type = 'sys'; subDir = src; } if (obj.type == 'gradleReference') { project.addGradleReference(parentDir, subDir); } else if (obj.type == 'sys') { project.addSystemLibrary(parentDir, subDir); } else { project.addSubProject(parentDir, subDir); } },
.then(null, function(err) { if (/timed out/.exec('' + err)) { // adb kill-server doesn't seem to do the trick. // Could probably find a x-platform version of killall, but I'm not actually // sure that this scenario even happens on non-OSX machines. events.emit('verbose', 'adb timed out while detecting device/emulator architecture. Killing adb and trying again.'); return spawn('killall', ['adb']) .then(function() { return helper() .then(null, function() { // The double kill is sadly often necessary, at least on mac. events.emit('warn', 'adb timed out a second time while detecting device/emulator architecture. Killing adb and trying again.'); return spawn('killall', ['adb']) .then(function() { return helper() .then(null, function() { return Q.reject(new CordovaError('adb timed out a third time while detecting device/emulator architecture. Try unplugging & replugging the device.')); }); }); }); }, function() { // For non-killall OS's. return Q.reject(err); }); } throw err; });
module.exports.getUninstaller = function(type) { if (handlers[type] && handlers[type].uninstall) { return handlers[type].uninstall; } events.emit('verbose', '<' + type + '> is not supported for android plugins'); };
install:function(obj, plugin_dir, project_dir, plugin_id, options) { var src = obj.src; var custom = obj.custom; if (!src) throw new CordovaError('src not specified in <framework> for plugin ' + plugin_id); events.emit('verbose', 'Installing Android library: ' + src); var parent = obj.parent; var parentDir = parent ? path.resolve(project_dir, parent) : project_dir; var subDir; if (custom) { var subRelativeDir = module.exports.getCustomSubprojectRelativeDir(plugin_id, project_dir, src); common.copyNewFile(plugin_dir, src, project_dir, subRelativeDir); subDir = path.resolve(project_dir, subRelativeDir); } else { var sdk_dir = module.exports.getProjectSdkDir(project_dir); subDir = path.resolve(sdk_dir, src); } var projectConfig = module.exports.parseProjectFile(project_dir); var type = obj.type; if (type == 'gradleReference') { //add reference to build.gradle projectConfig.addGradleReference(parentDir, subDir); } else { projectConfig.addSubProject(parentDir, subDir); } },
module.exports.getInstaller = function (type) { if (handlers[type] && handlers[type].install) { return handlers[type].install; } events.emit('warn', '<' + type + '> is not supported for iOS plugins'); };
function getFailedRequirements(reqs, pluginMap, platformMap, cordovaVersion) { var failed = []; for (var req in reqs) { if(reqs.hasOwnProperty(req) && typeof req === 'string' && semver.validRange(reqs[req])) { var badInstalledVersion = null; var trimmedReq = req.trim(); if(pluginMap[trimmedReq] && !semver.satisfies(pluginMap[trimmedReq], reqs[req])) { badInstalledVersion = pluginMap[req]; } else if(trimmedReq === 'cordova' && !semver.satisfies(cordovaVersion, reqs[req])) { badInstalledVersion = cordovaVersion; } else if(trimmedReq.indexOf('cordova-') === 0) { // Might be a platform constraint var platform = trimmedReq.substring(8); if(platformMap[platform] && !semver.satisfies(platformMap[platform], reqs[req])) { badInstalledVersion = platformMap[platform]; } } if(badInstalledVersion) { failed.push({ dependency: trimmedReq, installed: badInstalledVersion.trim(), required: reqs[req].trim() }); } } else { events.emit('verbose', 'Ignoring invalid plugin dependency constraint ' + req + ':' + reqs[req]); } } return failed; }
uninstall:function(obj, plugin, project, options) { var src = obj.src; if (!src) throw new CordovaError(generateAttributeError('src', 'framework', plugin.id)); events.emit('verbose', 'Uninstalling Android library: ' + src); var parentDir = obj.parent ? path.resolve(project.projectDir, obj.parent) : project.projectDir; var subDir; if (obj.custom) { var subRelativeDir = project.getCustomSubprojectRelativeDir(plugin.id, src); removeFile(project.projectDir, subRelativeDir); subDir = path.resolve(project.projectDir, subRelativeDir); // If it's the last framework in the plugin, remove the parent directory. var parDir = path.dirname(subDir); if (fs.existsSync(parDir) && fs.readdirSync(parDir).length === 0) { fs.rmdirSync(parDir); } } else { // WEEX_HOOK obj.type = 'gradleReference'; //obj.type = 'sys'; subDir = src; } if (obj.type == 'gradleReference') { project.removeGradleReference(parentDir, subDir); } else if (obj.type == 'sys') { project.removeSystemLibrary(parentDir, subDir); } else { project.removeSubProject(parentDir, subDir); } }
function listUnmetRequirements(name, failedRequirements) { events.emit('warn', 'Unmet project requirements for latest version of ' + name + ':'); failedRequirements.forEach(function(req) { events.emit('warn', ' ' + req.dependency + ' (' + req.installed + ' in project, ' + req.required + ' required)'); }); }
/** * Cleans all files from the platform 'www' directory. */ function cleanWww(projectRoot, locations) { var targetDir = path.relative(projectRoot, locations.www); events.emit('verbose', 'Cleaning ' + targetDir); // No source paths are specified, so mergeAndUpdateDir() will clear the target directory. FileUpdater.mergeAndUpdateDir( [], targetDir, { rootDir: projectRoot, all: true }, logFileOp); }
/** * Ensures that plugins, installed with previous versions of CLI (<5.4.0) are * readded to platform correctly. Also triggers regeneration of * cordova_plugins.js file. * * @param {String} platform Platform name to check for installed plugins * @param {String} projectRoot A current cordova project location * @param {Object} [options] Options that will be passed to * PlatformApi.pluginAdd/Remove. This object will be extended with plugin * variables, used to install the plugin initially (picked from "old" * plugins/<platform>.json) * * @return {Promise} Promise that'll be fulfilled if all the * plugins reinstalled properly. */ function restoreMissingPluginsForPlatform(platform, projectRoot, options) { events.emit('verbose', 'Checking for any plugins added to the project that have not been installed in ' + platform + ' platform'); // Flow: // 1. Compare <platform>.json file in <project>/plugins ("old") and platforms/<platform> ("new") // 2. If there is any differences - merge "old" one into "new" // 3. Reinstall plugins that are missing and was merged on previous step var oldPlatformJson = PlatformJson.load(path.join(projectRoot, 'plugins'), platform); var platformJson = PlatformJson.load(path.join(projectRoot, 'platforms', platform), platform); var missingPlugins = Object.keys(oldPlatformJson.root.installed_plugins) .concat(Object.keys(oldPlatformJson.root.dependent_plugins)) .reduce(function (result, candidate) { if (!platformJson.isPluginInstalled(candidate)) result.push({name: candidate, // Note: isPluginInstalled is actually returns not a boolean, // but object which corresponds to this particular plugin variables: oldPlatformJson.isPluginInstalled(candidate)}); return result; }, []); if (missingPlugins.length === 0) { events.emit('verbose', 'No differences found between plugins added to project and installed in ' + platform + ' platform. Continuing...'); return Q.resolve(); } var api = platforms.getPlatformApi(platform); var provider = new PluginInfoProvider(); return missingPlugins.reduce(function (promise, plugin) { return promise.then(function () { var pluginOptions = options || {}; pluginOptions.variables = plugin.variables; pluginOptions.usePlatformWww = true; events.emit('verbose', 'Reinstalling missing plugin ' + plugin.name + ' in ' + platform + ' platform'); var pluginInfo = provider.get(path.join(projectRoot, 'plugins', plugin.name)); return api.removePlugin(pluginInfo, pluginOptions) .then(function () { return api.addPlugin(pluginInfo, pluginOptions); }); }); }, Q()); }
amazon_fireos_parser.prototype.findAndroidLaunchModePreference = function(config) { var launchMode = config.getPreference('AndroidLaunchMode'); if (!launchMode) { // Return a default value return 'singleTop'; } var expectedValues = ['standard', 'singleTop', 'singleTask', 'singleInstance']; var valid = expectedValues.indexOf(launchMode) !== -1; if (!valid) { events.emit('warn', 'Unrecognized value for AndroidLaunchMode preference: ' + launchMode); events.emit('warn', ' Expected values are: ' + expectedValues.join(', ')); // Note: warn, but leave the launch mode as developer wanted, in case the list of options changes in the future } return launchMode; };
// Converts cordova specific representation of target device to XCode value function parseTargetDevicePreference(value) { if (!value) return null; var map = { 'universal': '"1,2"', 'handset': '"1"', 'tablet': '"2"'}; if (map[value.toLowerCase()]) { return map[value.toLowerCase()]; } events.emit('warn', 'Unknown target-device preference value: "' + value + '".'); return null; }
.then(function() { var apkPaths = builder.findOutputApks(opts.buildType, opts.arch); events.emit('log', 'Built the following apk(s): \n\t' + apkPaths.join('\n\t')); return { apkPaths: apkPaths, buildType: opts.buildType, buildMethod: opts.buildMethod }; });
platformSplashScreens.forEach(function(item) { var splash = splashScreens.getBySize(item.width, item.height); if (splash){ var src = path.join(appRoot, splash.src), dest = path.join(platformRoot, destSplashFolder, item.dest); events.emit('verbose', 'Copying splash from ' + src + ' to ' + dest); shell.cp('-f', src, dest); } });
platformIcons.forEach(function (item) { var icon = icons.getBySize(item.width, item.height) || icons.getDefault(); if (icon){ var src = path.join(appRoot, icon.src), dest = path.join(platformRoot, destIconsFolder, item.dest); events.emit('verbose', 'Copying icon from ' + src + ' to ' + dest); shell.cp('-f', src, dest); } });
/** * Updates platform 'www' directory by replacing it with contents of * 'platform_www' and app www. Also copies project's overrides' folder into * the platform 'www' folder * * @param {Object} cordovaProject An object which describes cordova project. * @param {Object} destinations An object that contains destination * paths for www files. */ function updateWww(cordovaProject, destinations) { var sourceDirs = [ path.relative(cordovaProject.root, cordovaProject.locations.www), path.relative(cordovaProject.root, destinations.platformWww) ]; // If project contains 'merges' for our platform, use them as another overrides var merges_path = path.join(cordovaProject.root, 'merges', 'android'); if (fs.existsSync(merges_path)) { events.emit('verbose', 'Found "merges/android" folder. Copying its contents into the android project.'); sourceDirs.push(path.join('merges', 'android')); } var targetDir = path.relative(cordovaProject.root, destinations.www); events.emit( 'verbose', 'Merging and updating files from [' + sourceDirs.join(', ') + '] to ' + targetDir); FileUpdater.mergeAndUpdateDir( sourceDirs, targetDir, { rootDir: cordovaProject.root }, logFileOp); }
function determinePluginTarget(projectRoot, cfg, target, fetchOptions) { var parsedSpec = pluginSpec.parse(target); var id = parsedSpec.package || target; // CB-10975 We need to resolve relative path to plugin dir from app's root before checking whether if it exists var maybeDir = cordova_util.fixRelativePath(id); if (parsedSpec.version || cordova_util.isUrl(id) || cordova_util.isDirectory(maybeDir)) { return Q(target); } // If no version is specified, retrieve the version (or source) from config.xml events.emit('verbose', 'No version specified for ' + parsedSpec.package + ', retrieving version from config.xml'); var ver = getVersionFromConfigFile(id, cfg); if (cordova_util.isUrl(ver) || cordova_util.isDirectory(ver) || pluginSpec.parse(ver).scope) { return Q(ver); } // If version exists in config.xml, use that if (ver) { return Q(id + '@' + ver); } // If no version is given at all and we are fetching from npm, we // can attempt to use the Cordova dependencies the plugin lists in // their package.json var shouldUseNpmInfo = !fetchOptions.searchpath && !fetchOptions.noregistry; events.emit('verbose', 'No version for ' + parsedSpec.package + ' saved in config.xml'); if(shouldUseNpmInfo) { events.emit('verbose', 'Attempting to use npm info for ' + parsedSpec.package + ' to choose a compatible release'); } else { events.emit('verbose', 'Not checking npm info for ' + parsedSpec.package + ' because searchpath or noregistry flag was given'); } return (shouldUseNpmInfo ? registry.info([id]) .then(function(pluginInfo) { return getFetchVersion(projectRoot, pluginInfo, pkgJson.version); }) : Q(null)) .then(function(fetchVersion) { return fetchVersion ? (id + '@' + fetchVersion) : target; }); }
/** * Updates config files in project based on app's config.xml and config munge, * generated by plugins. * * @param {ConfigParser} sourceConfig A project's configuration that will * be merged into platform's config.xml * @param {ConfigChanges} configMunger An initialized ConfigChanges instance * for this platform. * @param {Object} locations A map of locations for this platform * * @return {ConfigParser} An instance of ConfigParser, that * represents current project's configuration. When returned, the * configuration is already dumped to appropriate config.xml file. */ function updateConfigFilesFrom(sourceConfig, configMunger, locations) { events.emit('verbose', 'Generating platform-specific config.xml from defaults for android at ' + locations.configXml); // First cleanup current config and merge project's one into own // Overwrite platform config.xml with defaults.xml. shell.cp('-f', locations.defaultConfigXml, locations.configXml); // Then apply config changes from global munge to all config files // in project (including project's config) configMunger.reapply_global_munge().save_all(); events.emit('verbose', 'Merging project\'s config.xml into platform-specific android config.xml'); // Merge changes from app's config.xml into platform's one var config = new ConfigParser(locations.configXml); xmlHelpers.mergeXml(sourceConfig.doc.getroot(), config.doc.getroot(), 'android', /*clobber=*/true); config.write(); return config; }
.then(null, function() { // The double kill is sadly often necessary, at least on mac. events.emit('warn', 'adb timed out a second time while detecting device/emulator architecture. Killing adb and trying again.'); return spawn('killall', ['adb']) .then(function() { return helper() .then(null, function() { return Q.reject(new CordovaError('adb timed out a third time while detecting device/emulator architecture. Try unplugging & replugging the device.')); }); }); });
amazon_fireos_parser.prototype.copyImage = function(src, density, name) { var destFolder = path.join(this.path, 'res', (density ? 'drawable-': 'drawable') + density); var destFilePath = path.join(destFolder, name); // default template does not have default asset for this density if (!fs.existsSync(destFolder)) { fs.mkdirSync(destFolder); } events.emit('verbose', 'copying image from ' + src + ' to ' + destFilePath); shell.cp('-f', src, destFilePath); };
function cleanIcons(projectRoot, projectConfig, platformResourcesDir) { var icons = projectConfig.getIcons('android'); if (icons.length > 0) { var resourceMap = mapImageResources(projectRoot, platformResourcesDir, 'mipmap', 'icon.png'); events.emit('verbose', 'Cleaning icons at ' + platformResourcesDir); // No source paths are specified in the map, so updatePaths() will delete the target files. FileUpdater.updatePaths( resourceMap, { rootDir: projectRoot, all: true }, logFileOp); } }
}).then(function(){ //remove plugin from config.xml if(saveToConfigXmlOn(config_json, opts)){ events.emit('log', 'Removing plugin ' + target + ' from config.xml file...'); var configPath = cordova_util.projectConfig(projectRoot); if(fs.existsSync(configPath)){//should not happen with real life but needed for tests var configXml = new ConfigParser(configPath); configXml.removePlugin(target); configXml.write(); } } })
return promise.then(function () { var pluginOptions = options || {}; pluginOptions.variables = plugin.variables; pluginOptions.usePlatformWww = true; events.emit('verbose', 'Reinstalling missing plugin ' + plugin.name + ' in ' + platform + ' platform'); var pluginInfo = provider.get(path.join(projectRoot, 'plugins', plugin.name)); return api.removePlugin(pluginInfo, pluginOptions) .then(function () { return api.addPlugin(pluginInfo, pluginOptions); }); });
uninstall:function(obj, project_dir, plugin_id, options, project_file) { events.emit('verbose', 'wp8 framework uninstall :: ' + plugin_id ); var src = obj.src; var isCustom = obj.custom; if(isCustom) { var dest = path.join('plugins', plugin_id); common.removeFile(project_dir, dest); } project_file.removeReference(src); }