美文网首页Android增量编译
Freeline迁移记录2-修复classpath崩溃

Freeline迁移记录2-修复classpath崩溃

作者: RetroX | 来源:发表于2017-10-03 14:35 被阅读47次

    github链接

    新版迁移过程中修复Classpath问题

    Freeline踩坑日记

    追查表象的蛛丝马迹

    结果前一部分的修改

    基本上做到了编译的通过

    然后在增量的时候 报出了错误

    类似这样子的错误

    /Users/jichenyang/AndroidStudioProjects/ONE/app/src/main/java/com/liuzh/one/activity/MovieActivity.java:3: error: package android.annotation does not exist
    import android.annotation.SuppressLint;
                             ^
    /Users/jichenyang/AndroidStudioProjects/ONE/app/src/main/java/com/liuzh/one/activity/MovieActivity.java:4: error: package android.content does not exist
    import android.content.Context;
                          ^
    /Users/jichenyang/AndroidStudioProjects/ONE/app/src/main/java/com/liuzh/one/activity/MovieActivity.java:5: error: package android.content does not exist
    import android.content.Intent;
                          ^
    /Users/jichenyang/AndroidStudioProjects/ONE/app/src/main/java/com/liuzh/one/activity/MovieActivity.java:6: error: package android.os does not exist
    import android.os.Build;
                     ^
    /Users/jichenyang/AndroidStudioProjects/ONE/app/src/main/java/com/liuzh/one/activity/MovieActivity.java:7: error: package android.support.annotation does not exist
    import android.support.annotation.RequiresApi;
                                     ^
    ...
    

    很显然是classpath的缺失

    于是查阅Freeline的log , 也分析了Freeline的增量构建流程

    这里几段Python代码是运行了增量javac

    javacargs = self._generate_java_compile_args(extra_javac_args_enabled=extra_javac_args_enabled)
    
            self.debug('javac exec: ' + ' '.join(javacargs))
            output, err, code = cexec(javacargs, callback=None)
    

    于是追查javac的命令 查看它的传入的classpath

     /Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/bin/javac 
     -encoding UTF-8 -g -target 1.7 -source 1.7 
     -cp 
     /Users/jichenyang/AndroidStudioProjects/ONE/app/build/freeline/app/classes:
     /Users/jichenyang/AndroidStudioProjects/ONE/app/build/intermediates/classes/debug:
     /Users/jichenyang/android-sdk-mac_x86/platforms/android-25/android.jar 
     /Users/jichenyang/AndroidStudioProjects/ONE/app/src/main/java/com/liuzh/one/activity/MovieActivity.java 
     /Users/jichenyang/AndroidStudioProjects/ONE/app/build/freeline/app/backup/com/liuzh/one/R.java -d /Users/jichenyang/AndroidStudioProjects/ONE/app/build/freeline/app/classes
    

    通过观察这几个目录可以发现的是 这些classpath并不能满足需要

    缺少了一些第三方库的依赖

    根据:“控制变量法” 我切换到了稳定版的Gradle Plugin跑了一遍 查看它的log

    PS:大家看长度就可以~

    javac exec: /Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/bin/javac 
    -encoding UTF-8 -g -target 1.7 -source 1.7 -cp 
    /Users/jichenyang/AndroidStudioProjects/ONE/app/build/freeline/app/classes:
    /Users/jichenyang/AndroidStudioProjects/ONE/app/build/intermediates/classes/debug:
    /Users/jichenyang/android-sdk-mac_x86/platforms/android-25/android.jar:
    /Users/jichenyang/.gradle/caches/modules-2/files-2.1/com.google.code.gson/gson/2.6.1/b9d63507329a7178e026fc334f87587ee5070ac5/gson-2.6.1.jar:
    /Users/jichenyang/.gradle/caches/modules-2/files-2.1/com.squareup.retrofit2/retrofit/2.2.0/41e67dba73c3347e4503761642c39d0e06ca1f2/retrofit-2.2.0.jar:
    /Users/jichenyang/.android/build-cache/edb8a7ac2177cd2946b58128ce36f08bc9b38f89/output/jars/classes.jar:/Users/jichenyang/.android/build-cache/1533df315d42d7f4ff00b9f8b7de5c201256284e/output/jars/classes.jar:/Users/jichenyang/.android/build-cache/6aefc3619c39e6e4ce3118e695f213d148de053e/output/jars/classes.jar:/Users/jichenyang/.gradle/caches/modules-2/files-2.1/com.squareup.picasso/picasso/2.5.2/7446d06ec8d4f7ffcc53f1da37c95f200dcb9387/picasso-2.5.2.jar:/Users/jichenyang/.android/build-cache/dffbaa523b9008a1896eec5b373120bb1c6de17c/output/jars/classes.jar:/Users/jichenyang/.android/build-cache/4aaff6c08351f15c642176ed10e6377844d7046c/output/jars/classes.jar:/Users/jichenyang/.android/build-cache/b20f53c3c1cdc7f0f1b4d0dbb35408b94ce38903/output/jars/classes.jar:/Users/jichenyang/.android/build-cache/ba30abeac3972b144fefa6124554f582fa60df5c/output/jars/classes.jar:/Users/jichenyang/.android/build-cache/d4cdfb05df67f451bb9aba52161bc413bc43c9ea/output/jars/classes.jar:/Users/jichenyang/.gradle/caches/modules-2/files-2.1/com.squareup.okio/okio/1.11.0/840897fcd7223a8143f1d9b6f69714e7be34fd50/okio-1.11.0.jar:/Users/jichenyang/.android/build-cache/3936d4bae4a86548d80c6de52bbd92ee7a284855/output/jars/classes.jar:/Users/jichenyang/.gradle/caches/modules-2/files-2.1/com.squareup.okhttp3/okhttp/3.6.0/69edde9fc4b01c9fd51d25b83428837478c27254/okhttp-3.6.0.jar:/Users/jichenyang/.android/build-cache/f159250e4dfb4e3d4150d31319640f34f7ff3388/output/jars/classes.jar:/Users/jichenyang/.android/build-cache/64331bf8b484ce0281624b5b84622ed7a08fbba2/output/jars/classes.jar:/Users/jichenyang/.gradle/caches/modules-2/files-2.1/com.squareup.retrofit2/converter-gson/2.0.2/f8d87f15b94b8d74e7ccf61d7eedb558811cdb30/converter-gson-2.0.2.jar:/Users/jichenyang/.android/build-cache/11cf9470b93863a1118d758231db813ac8dbc99c/output/jars/classes.jar:/Users/jichenyang/.android/build-cache/28277db1bdc4921dded9cbd9bc2991b9198a9093/output/jars/classes.jar:/Users/jichenyang/android-sdk-mac_x86/extras/android/m2repository/com/android/support/support-annotations/25.3.1/support-annotations-25.3.1.jar:/Users/jichenyang/.android/build-cache/837dc24219f090432e4c44c4b5da23f93591bb19/output/jars/classes.jar: /Users/jichenyang/AndroidStudioProjects/ONE/app/src/main/java/com/liuzh/one/activity/MovieActivity.java -d /Users/jichenyang/AndroidStudioProjects/ONE/app/build/freeline/app/classes
    

    相比之下大概是什么呢:

    正常情况下 应该还囊括到Gradle编译过去中释放aar产生的jar包依赖 加入classpath

    然而在AS3.0中 这部分依赖并没有被加入进来

    Freeline的源码中有一段注释可以很清楚的表面Classpath的来源

        def fill_classpaths(self):
            # classpaths:
            # 1. patch classes
            # 2. dependent modules' patch classes
            # 3. android.jar
            # 4. third party jars
            # 5. generated classes in build directory
            ...
    

    发现表象查源头

    问:为什么这部分Classpath丢失了

    查看Freeline增量编译Javac部分Classpath添加的方法

        def fill_classpaths(self):
            # classpaths:
            # 1. patch classes
            # 2. dependent modules' patch classes
            # 3. android.jar
            # 4. third party jars
            # 5. generated classes in build directory
            patch_classes_cache_dir = self._finder.get_patch_classes_cache_dir()
            self._classpaths.append(patch_classes_cache_dir)
            self._classpaths.append(self._finder.get_dst_classes_dir())
            for module in self._module_info['local_module_dep']:
                finder = GradleDirectoryFinder(module, self._module_dir_map[module], self._cache_dir)
                self._classpaths.append(finder.get_patch_classes_cache_dir())
    
            # add main module classes dir to classpath to generate databinding files
            main_module_name = self._config['main_project_name']
            if self._name != main_module_name and self._is_databinding_enabled:
                finder = GradleDirectoryFinder(main_module_name, self._module_dir_map[main_module_name], self._cache_dir,
                                               config=self._config)
                self._classpaths.append(finder.get_dst_classes_dir())
    
            self._classpaths.append(os.path.join(self._config['compile_sdk_directory'], 'android.jar'))
            #重点来了 (下面)
            self._classpaths.extend(self._module_info['dep_jar_path'])
    
            # remove existing same-name class in build directory
            srcdirs = self._config['project_source_sets'][self._name]['main_src_directory']
    ...
    

    注释写的很清晰 顾名思义

    可以用Debug的方法 一句一句的跑 然后在Debugger里面看Classpath的变量值

    self._classpaths.extend(self._module_info['dep_jar_path'])这句话就是添加第三方依赖的操作

    然后这条线就到了下一个工序 —> self._module_info['dep_jar_path']是哪里来的?

    通过调试+全局搜索的骚操作

    定位在了

    def get_project_info(config):
        Logger.debug("collecting project info, please wait a while...")
        project_info = {}
        if 'modules' in config:
            modules = config['modules']
        else:
            modules = get_all_modules(os.getcwd())
    
    #从这个json文件中取出来了依赖路径
        jar_dependencies_path = os.path.join(config['build_cache_dir'], 'jar_dependencies.json')
        jar_dependencies = []
        if os.path.exists(jar_dependencies_path):
            jar_dependencies = load_json_cache(jar_dependencies_path)
    
        for module in modules:
            if module['name'] in config['project_source_sets']:
                module_info = {}
                module_info['name'] = module['name']
                module_info['path'] = module['path']
                module_info['relative_dir'] = module['path']
                #把他们放进去
                module_info['dep_jar_path'] = jar_dependencies
                module_info['packagename'] = get_package_name(
                    config['project_source_sets'][module['name']]['main_manifest_path'])
    
    

    看上面代码的注释部分 就可以知道 这些依赖是从一个json里面取出来的

    于是我们去翻这个json文件

    [
        "/Users/jichenyang/.gradle/caches/modules-2/files-2.1/com.google.code.gson/gson/2.6.1/b9d63507329a7178e026fc334f87587ee5070ac5/gson-2.6.1.jar",
        "/Users/jichenyang/.gradle/caches/modules-2/files-2.1/com.squareup.retrofit2/retrofit/2.2.0/41e67dba73c3347e4503761642c39d0e06ca1f2/retrofit-2.2.0.jar",
        "/Users/jichenyang/.android/build-cache/edb8a7ac2177cd2946b58128ce36f08bc9b38f89/output/jars/classes.jar",
        "/Users/jichenyang/.android/build-cache/1533df315d42d7f4ff00b9f8b7de5c201256284e/output/jars/classes.jar",
        "/Users/jichenyang/.android/build-cache/6aefc3619c39e6e4ce3118e695f213d148de053e/output/jars/classes.jar",
        "/Users/jichenyang/.gradle/caches/modules-2/files-2.1/com.squareup.picasso/picasso/2.5.2/7446d06ec8d4f7ffcc53f1da37c95f200dcb9387/picasso-2.5.2.jar",
        "/Users/jichenyang/.android/build-cache/dffbaa523b9008a1896eec5b373120bb1c6de17c/output/jars/classes.jar",
        "/Users/jichenyang/.android/build-cache/4aaff6c08351f15c642176ed10e6377844d7046c/output/jars/classes.jar",
        "/Users/jichenyang/.android/build-cache/b20f53c3c1cdc7f0f1b4d0dbb35408b94ce38903/output/jars/classes.jar",
        "/Users/jichenyang/.android/build-cache/ba30abeac3972b144fefa6124554f582fa60df5c/output/jars/classes.jar",
        "/Users/jichenyang/.android/build-cache/d4cdfb05df67f451bb9aba52161bc413bc43c9ea/output/jars/classes.jar",
        "/Users/jichenyang/.gradle/caches/modules-2/files-2.1/com.squareup.okio/okio/1.11.0/840897fcd7223a8143f1d9b6f69714e7be34fd50/okio-1.11.0.jar",
        "/Users/jichenyang/.android/build-cache/3936d4bae4a86548d80c6de52bbd92ee7a284855/output/jars/classes.jar",
        "/Users/jichenyang/.gradle/caches/modules-2/files-2.1/com.squareup.okhttp3/okhttp/3.6.0/69edde9fc4b01c9fd51d25b83428837478c27254/okhttp-3.6.0.jar",
        "/Users/jichenyang/.android/build-cache/f159250e4dfb4e3d4150d31319640f34f7ff3388/output/jars/classes.jar",
        "/Users/jichenyang/.android/build-cache/64331bf8b484ce0281624b5b84622ed7a08fbba2/output/jars/classes.jar",
        "/Users/jichenyang/.gradle/caches/modules-2/files-2.1/com.squareup.retrofit2/converter-gson/2.0.2/f8d87f15b94b8d74e7ccf61d7eedb558811cdb30/converter-gson-2.0.2.jar",
        "/Users/jichenyang/.android/build-cache/11cf9470b93863a1118d758231db813ac8dbc99c/output/jars/classes.jar",
        "/Users/jichenyang/.android/build-cache/28277db1bdc4921dded9cbd9bc2991b9198a9093/output/jars/classes.jar",
        "/Users/jichenyang/android-sdk-mac_x86/extras/android/m2repository/com/android/support/support-annotations/25.3.1/support-annotations-25.3.1.jar",
        "/Users/jichenyang/.android/build-cache/837dc24219f090432e4c44c4b5da23f93591bb19/output/jars/classes.jar",
        ""
    ]
    

    很显然了已经 (对比之下 3.0的环境下 这个json的数组是空的)

    测试的时候一个比较hack的操作是 我把2.3.3环境下跑出的json文件内容 复制到3.0环境下

    然后增量javac成功

    这也就证明了javac环节的问题 同时消去了我的一个担忧:是不是Dexmerge的时候会烂

    穷追不舍 修复问题

    那么

    为什么这个json文件会是空的?

    为什么2.3的时候这个json文件可以被赋值?

    这个json文件的数据是在什么时候被存入的?

    带着问题 我们继续追查源码

    一招骚操作 我在Freeline源码里面全局搜索

    Command + shift + F

    就搜它: "jar_dependencies.json"

    果然 它还在Freeline的Gradle Plugin里面出现了

    Freeline的取出jar的逻辑是:

    把处理jar的Task取出来

    然后遍历他的inputs,取出那些jar包的地址 放在json里面

    String manifest_path = project.android.sourceSets.main.manifest.srcFile.path
                        if (getMinSdkVersion(variant.mergedFlavor, manifest_path) < 21 && multiDexEnabled) {
                            classesProcessTask = project.tasks.findByName("transformClassesWithJarMergingFor${variant.name.capitalize()}")
                            multiDexListTask = project.tasks.findByName("transformClassesWithMultidexlistFor${variant.name.capitalize()}")
                        } else {
                            classesProcessTask = project.tasks.findByName("transformClassesWithDexFor${variant.name.capitalize()}")
                        }
    
    project.task(hackClassesBeforeDex) << {
                        def jarDependencies = []
      //就是这里偷出jar依赖地址
                        classesProcessTask.inputs.files.files.each { f ->
                            if (f.isDirectory()) {
                                f.eachFileRecurse(FileType.FILES) { file ->
                                    backUpClass(backupMap, file as File, backUpDirPath as String, modules.values())
                                    FreelineInjector.inject(excludeHackClasses, file as File, modules.values())
                                    if (file.path.endsWith(".jar")) {
                                        jarDependencies.add(file.path)
                                    }
                                }
                            } else {
                                backUpClass(backupMap, f as File, backUpDirPath as String, modules.values())
                                FreelineInjector.inject(excludeHackClasses, f as File, modules.values())
                                if (f.path.endsWith(".jar")) {
                                    jarDependencies.add(f.path)
                                }
                            }
                        }
    
                        if (preDexTask == null) {
                            def providedConf = project.configurations.findByName("provided")
    //                        providedConf.setCanBeResolved(true) //适配3.0 但是这里不行
                            if (providedConf) {
                                def providedJars = providedConf.asPath.split(File.pathSeparator)
                                jarDependencies.addAll(providedJars)
                            }
    
                            jarDependencies.addAll(addtionalJars)
                            // add all additional jars to final jar dependencies
                            def json = new JsonBuilder(jarDependencies).toPrettyString()
                            project.logger.info(json)
                            FreelineUtils.saveJson(json, FreelineUtils.joinPath(FreelineUtils.getBuildCacheDir(project.buildDir.absolutePath), "jar_dependencies.json"), true);
                        }
                    }
    

    这是它之前的代码

    那为什么无法兼容新版3.0呢?

    因为"transformClassesWithJarMergingFor${variant.name.capitalize()}"这个task在3.0上...

    不存在了...

    不存在了...

    果然

    classesProcessTask = project.tasks.findByName("transformClassesWithDexBuilderFor${variant.name.capitalize()}")
    

    在Plugin中增加对3.0的判断 然后替换成这个task就可以了

    很幸运的是...这个task的相关从inputs里面取jar相关的api都没有变...

    没有给我太大的折磨

    改完相关代码 install到MavenLocal

    跑一下增量 OK!

    如果有问题的话....那估计是3.0自带的问题

    坑还是要慢慢踩

    相关文章

      网友评论

        本文标题:Freeline迁移记录2-修复classpath崩溃

        本文链接:https://www.haomeiwen.com/subject/nvffyxtx.html