[记录点滴]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

相关