Freeline适配kotlin-2

kotlin增量实现

Posted by Retrox on August 27, 2017

Freeline适配kotlin-2 源码修改

在上一部分我们梳理了java增量的逻辑

整体来讲就是:

  • 扫描变化的java文件
  • 对它们进行单独的javac编译
  • 然后打包成增量dex
  • merge进去

其实现在大家基本上已经有思路了

javac换成kotlinc就ok了嘛

磨刀霍霍向牛羊

打开我心爱的pycharm

1.增加对kotlin修改的扫描

self._changed_files[module_name] = {'libs': [], 'assets': [], 'res': [], 'src': [], 'manifest': [],
                                    'config': [], 'so': [], 'cpp': [], 'kotlin': []}
# kotlin占坑

现在保存修改文件状态的map里面增加kotlin字段 准备放置修改的kotlin的文件路径

然后我们修改扫描代码文件修改的方法

# scan src
src_dirs = self._config['project_source_sets'][module_name]['main_src_directory']
for src_dir in src_dirs:
    if os.path.exists(src_dir):
        for dirpath, dirnames, files in os.walk(src_dir):
            if re.findall(r'[/\\+]androidTest[/\\+]', dirpath) or '/.' in dirpath:
                continue
            for fn in files:
              # 之前只有对java后缀名文件的检查
                if fn.endswith('java'):
                    if fn.endswith('package-info.java') or fn.endswith('BuildConfig.java'):
                        continue
                    fpath = os.path.join(dirpath, fn)
                    if self.__check_changes(module_name, fpath, module_cache):
                        self._changed_files[module_name]['src'].append(fpath)
                # 添加kotlin的增量检查
                elif fn.endswith('kt'):
                    fpath = os.path.join(dirpath, fn)
                    if self.__check_changes(module_name, fpath, module_cache):
                        self._changed_files[module_name]['kotlin'].append(fpath)

check_changes这个方法我们在上一节就已经介绍了 先检查修改时间在检查md5

这些操作后 我们修改一个kotlin文件 然后跑一遍freeline的调试模式

{
    "build_info": {
        "last_clean_build_time": 1503822329.0,
        "is_root_config_changed": false
    },
    "projects": {
        "app": {
            "src": [],
            "so": [],
            "assets": [],
            "kotlin": [
                "/Users/retrox/AndroidStudioProjects/One/app/src/main/java/com/twtstudio/one/view/VActivity.kt"
            ],
            "libs": [],
            "res": [],
            "config": [],
            "cpp": [],
            "manifest": []
        }
    }
}

在kotlin数组里面已经出现了我们修改的文件了

我们下一步要做的就是:对它进行kotlinc

对kt文件单独增量编译

流程类似于对java的增量

新建一个TaskCommand类用于对kotlin的增量编译

class GradleIncKotlincCommand(IncKotlincCommand):
    def __init__(self, module_name, invoker):
        IncKotlincCommand.__init__(self, module_name, invoker)

    def execute(self):
        self._invoker.check_r_md5()  # check if R.java has changed
        # self._invoker.check_other_modules_resources()
        should_run_kotlinc_task = self._invoker.check_kotlinc_task()
        if not should_run_kotlinc_task:
            self.debug('no need to execute kotlinc ')
            return

        self.debug('start to execute kotlinc command...')
        self._invoker.append_r_file()
        self._invoker.fill_classpaths()
        self._invoker.clean_dex_cache()
        self._invoker.run_kotlinc_task()

其实和java增量没有什么区别 因为没有加入kotlin的复杂特性支持(如kapt)所以某种角度看比java增量还简单

然后我们看看run_kotlinc_task这个方法

#运行增量kotlinc
def run_kotlinc_task(self):
    kotlincargs = self._generate_kotlin_compile_args()
    self.debug('kotlinc exec: ' + ' '.join(kotlincargs))
    output, err, code = cexec(kotlincargs, callback=None)

    if code != 0:
        raise FreelineException('incremental kotlinc compile failed.', '{}\n{}'.format(output, err))
    

这个方法只贴了核心代码 当然还有一些R文件的增量什么的需要处理下(我只展示和kotlin相关的部分)

到了_generate_kotlin_compile_args这个方法

#kotlin增量命令的生成 暂时直接kotlinc了
def _generate_kotlin_compile_args(self, extra_javac_args_enabled=False):
    # javacargs = [self._javac]
    # test environment kotlinc
    kotlincargs = ['kotlinc']
    arguments = []
    arguments.append('-cp')
    # todo  适配classpath
    self._classpaths.append('${projectDir}/app/build/tmp/kotlin-classes/debug')
    arguments.append(os.pathsep.join(self._classpaths))

    for fpath in self._changed_files['kotlin']:
        arguments.append(fpath)

    arguments.append('-d')
    arguments.append(self._finder.get_patch_classes_cache_dir())

    kotlincargs.extend(arguments)
    return kotlincargs

kotlin的classpath要增加一些 因为kotlin的字节码文件是单独存放的 需要把那些字节码文件也纳入到classpath中来

然后把kotlin增量的操作添加到freeline执行的操作链上

class GradleCompileCommand(CompileCommand):
    def __init__(self, module, invoker):
        self._module = module
        CompileCommand.__init__(self, 'gradle_{}_compile_command'.format(module), invoker)

    def _setup(self):
        # 加一个kotlin即可
        self.add_command(GradleIncKotlincCommand(self._module, self._invoker))
        self.add_command(GradleIncJavacCommand(self._module, self._invoker))
        self.add_command(GradleIncDexCommand(self._module, self._invoker))

其实就已经很清晰了 增量kotlinc然后把字节码存放到freeline文件夹 然后再统一dex打增量包 推送到手机

Dex增量打包

Dex增量工具会自己把字节码文件夹的生成字节码打包 而我们适配kotlin要做的是

设置标志位 为什么呢?

因为之前增量dex的标志位只针对了java的情况 修改kotlin并不能触发标志位的变化

所以只改kotlin的话 dex是拒接增量打包推送的

所以我们要小小的修改下

def _mark_changed_flag(self):
    info = self._changed_files.values()
    cache_dir = self._config['build_cache_dir']
    for bundle in info:
        if not android_tools.is_src_changed(cache_dir) and len(bundle['src']) > 0:
            android_tools.mark_src_changed(cache_dir)
        if not android_tools.is_res_changed(cache_dir) and len(bundle['res']) > 0:
            android_tools.mark_res_changed(cache_dir)
        # kotlin增量flag
        if not android_tools.is_src_changed(cache_dir) and len(bundle['kotlin']) > 0:
            android_tools.mark_src_changed(cache_dir)

然后修改下kotlin 跑下freeline 成功增量!

freeline兼容适配之路还有很长要走

不如kapt kotlin和java混写的相互依赖什么的 还有各种蜜汁问题

不过一个优秀的增量工具就是这样子一步步走下来的

希望这篇文章可以对你有所启发