添加平台支持,其实本质是利用各平台SDK环境创建原生工程文件。所以,所有函数也是围绕这一主题展开。
入口当然是platform函数
module.exports = function platform(command, targets, callback) {
.....
};
先不看具体源码,再看其他几个函数
(1)module.exports.supports = function(project_root, name, callback) {
这个函数主要执行以下几步:
//检查传入参数
//判断平台是否支持
//获得顶层目录下platforms.js中parser参数指定的各平台解析文件
//检查各平台依赖的SDK是否存在
/**
* Check Platform Support.
*
* Options:
*
* - {String} `name` of the platform to test.
* - {Function} `callback` is triggered with the answer.
* - {Error} `e` null when a platform is supported otherwise describes error.
*/
module.exports.supports = function(project_root, name, callback) {
// required parameters
if (!name) throw new Error('requires a platform name parameter');
if (!callback) throw new Error('requires a callback parameter');
// check if platform exists
var platform = platforms[name];
if (!platform) {
callback(new Error(util.format('"%s" platform does not exist', name)));
return;
}
// look up platform meta-data parser
var platformParser = platforms[name].parser;
if (!platformParser) {
callback(new Error(util.format('"%s" platform parser does not exist', name)));
return;
}
// check for platform support
platformParser.check_requirements(project_root, function(e) {
// typecast String to Error
e = (typeof e == 'string') ? new Error(e) : e;
// typecast false Boolean to null
e = (e) ? e : null;
callback(e);
});
};
(2)function call_into_create(target, projectRoot, cfg, id, version, callback, end) {
这个函数调用各个移动开发平台SDK环境中提供的创建原生工程文件的方法,创建工程
function call_into_create(target, projectRoot, cfg, id, version, callback, end) {
var output = path.join(projectRoot, 'platforms', target);
// Check if output directory already exists.
if (fs.existsSync(output)) {
var err = new Error('Platform "' + target + '" already exists at "' + output + '"');
if (callback) callback(err);
else throw err;
} else {
// Make sure we have minimum requirements to work with specified platform
events.emit('log', 'Checking if platform "' + target + '" passes minimum requirements...');
module.exports.supports(projectRoot, target, function(err) {
if (err) {
if (callback) callback(err);
else throw err;
} else {
// Create a platform app using the ./bin/create scripts that exist in each repo.
// Run platform's create script
var bin = path.join(cordova_util.libDirectory, target, id, version, 'bin', 'create');
//调用不同平台下创建工程命令
if(target == 'wp7') bin = path.join(cordova_util.libDirectory, 'wp', id, version, 'wp7', 'bin', 'create');
if(target == 'wp8') bin = path.join(cordova_util.libDirectory, 'wp', id, version, 'wp8', 'bin', 'create');
var args = (target=='ios') ? '--arc' : '';
var pkg = cfg.packageName().replace(/[^\w.]/g,'_');
var name = cfg.name();
var command = util.format('"%s" %s "%s" "%s" "%s"', bin, args, output, pkg, name);
events.emit('log', 'Running bin/create for platform "' + target + '" with command: "' + command + '" (output to follow)');
shell.exec(command, {silent:true,async:true}, function(code, create_output) {
events.emit('log', create_output);
if (code > 0) {
var err = new Error('An error occured during creation of ' + target + ' sub-project. ' + create_output);
if (callback) callback(err);
else throw err;
} else {
require('../cordova').prepare(target, function(err) {
if (err) {
if (callback) callback(err);
else throw err;
} else {
createOverrides(projectRoot, target);
end(); //platform add is done by now.
// Install all currently installed plugins into this new platform.
var plugins_dir = path.join(projectRoot, 'plugins');
var plugins = cordova_util.findPlugins(plugins_dir);
var parser = new platforms[target].parser(output);
plugins && plugins.forEach(function(plugin) {
events.emit('log', 'Installing plugin "' + plugin + '" following successful platform add of ' + target);
plugman.install(target, output, path.basename(plugin), plugins_dir, { www_dir: parser.staging_dir() });
});
}
});
}
});
}
});
}
}
(3)最会回到platform函数
module.exports = function platform(command, targets, callback) {
var projectRoot = cordova_util.isCordova(process.cwd());
//检查是否在根目录下存在.cordova目录,如果存在此目录才是cordova项目,返回此绝对路径;否则返回false
if (!projectRoot) {
var err = new Error('Current working directory is not a Cordova-based project.');
if (callback) callback(err);
else throw err;
return;
}
var hooks = new hooker(projectRoot);
if (arguments.length === 0) command = 'ls';//参数为空,默认为ls
if (targets) {
if (!(targets instanceof Array)) targets = [targets];//遍历targets对象是否是系统支持平台参数
targets.forEach(function(t) {
if (!(t in platforms)) {
var err = new Error('Platform "' + t + '" not recognized as core cordova platform.');
if (callback) return callback(err);
else throw err;
}
});
} else {
if (command == 'add' || command == 'rm') {
var err = new Error('You need to qualify `add` or `remove` with one or more platforms!');
if (callback) return callback(err);
else throw err;
}
}
var xml = cordova_util.projectConfig(projectRoot);//根目录/www/config.xml
var cfg = new cordova_util.config_parser(xml);//解析xml文件
var opts = {
platforms:targets
};
switch(command) {
case 'add':
var end = n(targets.length, function() {
hooks.fire('after_platform_add', opts, function(err) {
if (err) {
if (callback) callback(err);
else throw err;
} else {
if (callback) callback();
}
});
});
hooks.fire('before_platform_add', opts, function(err) {
if (err) {
if (callback) callback(err);
else throw err;
} else {
var config_json = config.read(projectRoot);
targets.forEach(function(t) {
lazy_load.based_on_config(projectRoot, t, function(err) {
if (err) {
if (callback) callback(err);
else throw err;
} else {
if (config_json.lib && config_json.lib[t]) {
call_into_create(t, projectRoot, cfg, config_json.lib[t].id, config_json.lib[t].version, callback, end);
} else {
call_into_create(t, projectRoot, cfg, 'cordova', platforms[t].version, callback, end);
}
}
});
});
}
});
break;
case 'rm':
case 'remove':
var end = n(targets.length, function() {
hooks.fire('after_platform_rm', opts, function(err) {
if (err) {
if (callback) callback(err);
else throw err;
} else {
if (callback) callback();
}
});
});
hooks.fire('before_platform_rm', opts, function(err) {
if (err) {
if (callback) callback(err);
else throw err;
} else {
targets.forEach(function(target) {
shell.rm('-rf', path.join(projectRoot, 'platforms', target));
shell.rm('-rf', path.join(cordova_util.appDir(projectRoot), 'merges', target));
var plugins_json = path.join(projectRoot, 'plugins', target + '.json');
if (fs.existsSync(plugins_json)) shell.rm(plugins_json);
end();
});
}
});
break;
case 'ls':
case 'list':
default:
var platforms_on_fs = cordova_util.listPlatforms(projectRoot);
hooks.fire('before_platform_ls', function(err) {
if (err) {
if (callback) callback(err);
else throw err;
} else {
events.emit('results', (platforms_on_fs.length ? platforms_on_fs : 'No platforms added. Use `cordova platform add <platform>`.'));
hooks.fire('after_platform_ls', function(err) {
if (err) {
if (callback) callback(err);
else throw err;
}
});
}
});
break;
}
};
结构比较简单,参数检查,然后根据不同参数执行不同动作,需要唯一说明的是,在switch函数中出现的
var end = n(targets.length, function() {
hooks.fire('after_platform_add', opts, function(err) {
if (err) {
if (callback) callback(err);
else throw err;
} else {
if (callback) callback();
}
});
});
这个函数是使用的node的nCallbacks插件,https://npmjs.org/package/ncallbacks
官方文档解释是function that executes n times,在上面这段代码中意思就是,end函数只能被执行targets.length次,调用超过targets.length次再调用end,其内部函数也不会再被执行,其实就是限制次数,避免当平台列表中为空后还误执行