[记录点滴]Ionic编译过程的研究
[记录点滴]Ionic编译过程的研究
0x00 摘要
之前研究Ionic编译过程的笔记,发出来做个记录。当时是因为有些图片没有拷贝到应用中,所以需要调试编译过程。
0x01 入口
编译的入口在platforms\android\cordova,具体是以下脚本文件:
android_sdk_version build.bat clean lib loggingHelper.js run.bat
Api.js check_reqs clean.bat log node_modules version
build check_reqs.bat defaults.xml log.bat run version.bat
对应的命令就是ionic run/build/clean...
0x02 执行
以run为例,其会调用build下面的run函数 platforms\android\cordova\lib\run.js
build = require('./build'),
module.exports.run = function(runOptions) {
return Q()
...
return build.run.call(self, runOptions, resolvedTarget)
...
}
};
以build为例,build.bat会直接调用build文件,进行编译。
真正执行
build真正执行的命令在这里:
new Api().build(buildOpts)
./android/cordova/Api.js:Api.prototype.prepare = function (cordovaProject, prepareOptions) {
./android/cordova/Api.js: return require('./lib/prepare').prepare.call(this, cordovaProject, prepareOptions);
./android/cordova/Api.js: return require('./lib/prepare').clean.call(self, cleanOptions);
在Api.js中,build代码如下:
Api.prototype.build = function (buildOptions) {
var self = this;
return require('./lib/check_reqs').run()
.then(function () {
return require('./lib/build').run.call(self, buildOptions);
})
.then(function (buildResults) {
// Cast build result to array of build artifacts
return buildResults.apkPaths.map(function (apkPath) {
return {
buildType: buildResults.buildType,
buildMethod: buildResults.buildMethod,
path: apkPath,
type: 'apk'
};
});
});
};
Check
cordova\lib下面的代码 platforms\android\cordova\lib\check_reqs.js
做了各种check。
module.exports.check_all = function() {
var requirements = [
new Requirement('java', 'Java JDK'),
new Requirement('androidSdk', 'Android SDK'),
new Requirement('androidTarget', 'Android target'),
new Requirement('gradle', 'Gradle')
];
var checkFns = [
this.check_java,
this.check_android,
this.check_android_target,
this.check_gradle
];
}
选择builder
platforms\android\cordova\lib\build.js
会选择一个builder,然后调用其的build函数
var builders = require('./builders/builders');
module.exports.run = function(options, optResolvedTarget) {
var opts = parseOpts(options, optResolvedTarget, this.root);
var builder = builders.getBuilder(opts.buildMethod);
return builder.prepEnv(opts)
.then(function() {
if (opts.prepEnv) {
events.emit('verbose', 'Build file successfully prepared.');
return;
}
return builder.build(opts)
.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
};
});
});
};
cordova\lib\builders下面的函数 会调用具体的builder,比如ant还是gradle。
platforms\android\cordova\lib\builders\builder.js 具体做选什么builder。
var knownBuilders = {
ant: 'AntBuilder',
gradle: 'GradleBuilder',
none: 'GenericBuilder'
};
module.exports.getBuilder = function (builderType, projectRoot) {
if (!knownBuilders[builderType])
throw new CordovaError('Builder ' + builderType + ' is not supported.');
try {
var Builder = require('./' + knownBuilders[builderType]);
return new Builder(projectRoot);
} catch (err) {
throw new CordovaError('Failed to instantiate ' + knownBuilders[builderType] + ' builder: ' + err);
}
};
GradleBuilder
Gradle的编译调用在 platforms\android\cordova\lib\builders\GradleBuilder.js
GradleBuilder.prototype.build = function(opts) {
var wrapper = path.join(this.root, 'gradlew');
var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts);
return spawn(wrapper, args, {stdio: 'pipe'})
.progress(function (stdio){
if (stdio.stderr) {
var suppressThisLine = /^Picked up _JAVA_OPTIONS: /i.test(stdio.stderr.toString());
if (suppressThisLine) {
return;
}
process.stderr.write(stdio.stderr);
} else {
process.stdout.write(stdio.stdout);
}
}).catch(function (error) {
if (error.toString().indexOf('failed to find target with hash string') >= 0) {
return check_reqs.check_android_target(error).then(function() {
// If due to some odd reason - check_android_target succeeds
// we should still fail here.
return Q.reject(error);
});
}
return Q.reject(error);
});
};
0x03 排查拷贝文件
关键词的查找
{ platforms } ? find -type f | xargs grep -w "shell.cp"
./android/cordova/lib/builders/GradleBuilder.js: shell.cp('-f', pluginBuildGradle, path.join(this.root, subProjects[i], 'build.gradle'));
./android/cordova/lib/builders/GradleBuilder.js: shell.cp(path.join(wrapperDir, 'gradlew.bat'), self.root);
./android/cordova/lib/builders/GradleBuilder.js: shell.cp(path.join(wrapperDir, 'gradlew'), self.root);
./android/cordova/lib/builders/GradleBuilder.js: shell.cp('-r', path.join(wrapperDir, 'gradle', 'wrapper'), path.join(self.root, 'gradle'));
./android/cordova/lib/pluginHandlers.js: shell.cp('-Rf', src+'/*', dest);
./android/cordova/lib/pluginHandlers.js: shell.cp('-f', src, dest);
./android/cordova/lib/prepare.js: shell.cp('-f', locations.defaultConfigXml, locations.configXml);
./android/cordova/node_modules/cordova-common/src/FileUpdater.js: shell.cp("-f", sourceFullPath, targetFullPath);
./android/cordova/node_modules/cordova-common/src/FileUpdater.js: shell.cp("-f", sourceFullPath, targetFullPath);
./android/cordova/node_modules/cordova-common/src/FileUpdater.js: shell.cp("-f", sourceFullPath, targetFullPath);
{ platforms } ? find -type f | xargs grep -w mergeAndUpdateDir
./android/cordova/lib/prepare.js: FileUpdater.mergeAndUpdateDir(
./android/cordova/lib/prepare.js: // No source paths are specified, so mergeAndUpdateDir() will clear the target directory.
./android/cordova/lib/prepare.js: FileUpdater.mergeAndUpdateDir(
./android/cordova/node_modules/cordova-common/src/FileUpdater.js:function mergeAndUpdateDir(sourceDirs, targetDir, options, log) {
./android/cordova/node_modules/cordova-common/src/FileUpdater.js: mergeAndUpdateDir: mergeAndUpdateDir
第一步,看看是否拷贝文件正确,初步怀疑是在这里。因为这里都是js文件,所以可以用console.log()等函数打印log, 然后把编译过程输入到文件中看,比如ionic build android > log.txt, 命令执行结束之后,看log.txt文件中的log
./android/cordova/lib/prepare.js
module.exports.prepare = function (cordovaProject, options) {
var self = this;
var platformResourcesDir = path.relative(cordovaProject.root, path.join(this.locations.root, 'res'));
-------- 打印platformResourcesDir,看看这个数据是否正确
// Update own www dir with project's www assets and plugins' assets and js-files
return Q.when(updateWww(cordovaProject, this.locations))
.then(function () {
// update project according to config.xml changes.
return updateProjectAccordingTo(self._config, self.locations);
})
.then(function () {
------------- 在这里更新图标和启动界面的,所以以图标为例,看看updateIcons是否拷贝成功
updateIcons(cordovaProject, platformResourcesDir);
updateSplashes(cordovaProject, platformResourcesDir);
})
.then(function () {
events.emit('verbose', 'Prepared android project successfully');
});
};
第二步,实验添加log代码:
cordova\lib\run.js
module.exports.run = function(runOptions) {
console.log("================= cordova lib run =================");
}
cordova\lib\prepare.js
module.exports.prepare = function (cordovaProject) {
console.log("================= cordova lib prepare =================");
.....
var projectRoot = path.dirname(projectConfig.path);
var destination = path.join(platformRoot, 'res');
console.log("================= cordova lib handleIcons =================projectRoot: " + projectRoot);
console.log("================= cordova lib prepare =================destination: " + destination);
....
}
第三步,执行看看log
C:\>ionic prepare android
================= cordova lib prepare =================
================= cordova lib handleIcons =================projectRoot: C
================= cordova lib prepare =================destination: C:\platforms\android\res
Running command: "C:\Program Files\nodejs\node.exe"
add to body class: platform-android
will push strings array {"name":"lang","titles":["English (US)","English (UK)"],
"values":["en-us","en-gb"]}
android preferences file was successfully generated
C:\>ionic build android
================= cordova lib prepare =================
================= cordova lib handleIcons =================projectRoot:
BUILD SUCCESSFUL
Total time: 19.631 secs