BUCK 与 RetroLambda 兼容性解决方案
aori59oe
9年前
<p> </p> <p>从最初 OkBuck 发布时宣称 BUCK 与 RetroLambda 不兼容只能忍痛割爱(lambda),到 BUCK 维护者之一联系我声称 BUCK 可以编译 Java 8 结果遇到编译错误未解,到昨晚终于成功让 BUCK 与 RetroLambda 出双入对,时隔大半年终于臻至完美,怎一个爽字了得!如果你还不了解什么是 BUCK,可以参考我的两篇文章 <a href="/misc/goto?guid=4959672249709908449" rel="nofollow,noindex">OkBuck, underneath the hood</a> , <a href="http://www.open-open.com/lib/view/open1457672622203.html">手把手OkBuck教程:应用到AndroidTDDBootStrap项目(续)</a> ,以及 <a href="/misc/goto?guid=4959641676959213040" rel="nofollow,noindex">BUCK 官方文档</a> 。</p> <h2>BUCK 编译 Java 8</h2> <p>这一点 BUCK 确实已经支持了,只需要在 java_library 和 android_library 这两种 rule 中加入以下配置即可:</p> <pre> source = '8', target = '8', </pre> <p>然而就是这一步中出现的拦路虎,挡住了我们前进的脚步半年之久,简而言之,纯 Java library module 这样做没有任何问题,但是 Android library module 的编译却报告了错误:</p> <pre> com.sun.tools.javac.code.Symbol$CompletionFailure: class file for java.lang.invoke.MethodType not found. </pre> <p>错误日志很明显对不对?然而,搜索出来的结果绝大部分都是指向了 <a href="/misc/goto?guid=4959672249840214436" rel="nofollow,noindex">gradle-retrolambda 的一个 issue</a> ,而这个 issue 的解决方案外部看来和这个问题没有任何联系。无奈之下只能再次向 BUCK 维护者求助:</p> <p><img src="https://simg.open-open.com/show/efc4219a293f03b691c15ab4cc5fe0dc.jpg"></p> <p>但这个帅小伙却从此失去了音信 :(</p> <p>好了帅小伙的故事先暂时到这里,我们继续。</p> <p>对于 Java library module,BUCK 能够成功使用 javac 编译出 java 8 的字节码,但是我们怎么把 RetroLambda 集成到 BUCK 的构建过程中呢?还是这个帅小伙出的主意(其实是 BUCK 的有些文档严重缺乏,只能自己看源码或者他们出主意): postprocess_classes_commands 。</p> <h2>BUCK 调用 RetroLambda</h2> <p>RetroLambda 只是一个命令行工具,大家通常使用的可能是另一个 gradle 插件: <a href="/misc/goto?guid=4959672249924869378" rel="nofollow,noindex">gradle-retrolambda</a> ,利用上面提到的 postprocess_classes_commands 参数,我们可以在 java_library 和 android_library 这两种 rule 中加一个 class 编译完成之后的 hook,BUCK 会执行 postprocess_classes_commands 参数的命令,并把本次编译的 class 路径作为参数传入。所以我们就可以在这里执行 RetroLambda 程序把 java 8 的字节码编译为 java 6 的字节码了。这里因为需要为 shell 脚本传入参数,所以我们需要把命令封装到一个脚本文件中,脚本文件的内容如下:</p> <pre> java \ -Dretrolambda.inputDir=$1 \ -Dretrolambda.classpath=$1 \ -jar ./retrolambda-2.3.0.jar </pre> <p>这里 RetroLambda 会直接覆盖 BUCK 编译生成的 class 文件,命令执行完毕之后,BUCK 会继续打包的后续步骤。</p> <p>这个版本的脚本文件编译 Java library module 时成功了,但是编译 Android library module 时却失败了,报了上节提到的错误。</p> <p>也就是这个错误,困扰了我们半年之久。当初的困境感兴趣的朋友可以查看这个 <a href="/misc/goto?guid=4959672250013938482" rel="nofollow,noindex">Github issue</a> 。</p> <h2>class file for java.lang.invoke.MethodType not found 问题的解决</h2> <p>这个问题之前 RetroLambda 也遇见过,虽然他们的解决方法看上去和我们遇见的问题没有关系,但它毕竟解决了,所以深挖肯定有门路。我在之前的文章中曾总结过各种问题的解决思路,这次的问题还是通过“差异分析法”解决的。</p> <p>RetroLambda 可以把同样的代码先编译为 java 8 的字节码,BUCK 的却不可以,但他们用的至少都是相同的 javac 程序吧?那问题肯定就出现了 <strong>编译选项</strong> 上。通过给两种方式加上日志输出选项,我拿到了它们各自的编译选项,这里,我以我发布到 <a href="/misc/goto?guid=4959672250102432114" rel="nofollow,noindex">Github 的 demo 工程</a> 为例。</p> <p>执行 buck build -v 10 app/:src_release ,在控制台看到了一段红色的信息,就是出错的命令,错误就是上面提到的 class file for java.lang.invoke.MethodType not found ,编译选项如下:</p> <pre> javac \ -source 8 -target 8 \ -sourcepath -g \ -bootclasspath \ /Users/piasy/tools/android-sdk/platforms/android-23/android.jar:\ /Users/piasy/tools/android-sdk/platforms/android-23/optional/org.apache.http.legacy.jar \ -verbose \ -d /Users/piasy/src/BuckJava8RetroLambdaDemo/buck-out/bin/app/lib__src_release__classes \ -classpath /Users/piasy/src/BuckJava8RetroLambdaDemo/buck-out/bin/app/__src_release#dummy_r_dot_java_rdotjava_bin__:\ /Users/piasy/src/BuckJava8RetroLambdaDemo/buck-out/gen/.okbuck/AFFB34D18189F4D10144A341628B7C81/aar__animated-vector-drawable-23.3.0.aar#aar_prebuilt_jar.jar:\ /Users/piasy/src/BuckJava8RetroLambdaDemo/buck-out/gen/.okbuck/AFFB34D18189F4D10144A341628B7C81/aar__appcompat-v7-23.3.0.aar#aar_prebuilt_jar.jar:\ /Users/piasy/src/BuckJava8RetroLambdaDemo/buck-out/gen/.okbuck/AFFB34D18189F4D10144A341628B7C81/aar__support-v4-23.3.0.aar#aar_prebuilt_jar.jar:\ /Users/piasy/src/BuckJava8RetroLambdaDemo/buck-out/gen/.okbuck/AFFB34D18189F4D10144A341628B7C81/aar__support-vector-drawable-23.3.0.aar#aar_prebuilt_jar.jar:\ /Users/piasy/src/BuckJava8RetroLambdaDemo/buck-out/gen/.okbuck/AFFB34D18189F4D10144A341628B7C81/jar__support-annotations-23.3.0.jar.jar:\ /Users/piasy/src/BuckJava8RetroLambdaDemo/buck-out/gen/app/lib__build_config_release__output/build_config_release.jar \ @buck-out/gen/app/__src_release__srcs </pre> <p>执行 ./gradlew :app:compileDebugJavaWithJavac --debug ,查看输出找到编译选项:</p> <pre> javac \ -d /Users/piasy/src/BuckJava8RetroLambdaDemo/app/build/retrolambda/debug \ -g -encoding UTF-8 \ -bootclasspath /Users/piasy/tools/android-sdk/platforms/android-23/android.jar \ -sourcepath /Users/piasy/src/BuckJava8RetroLambdaDemo/app/build/tmp/compileDebugJavaWithJavac/emptySourcePathRef \ -classpath /Users/piasy/src/BuckJava8RetroLambdaDemo/app/build/intermediates/exploded-aar/com.android.support/support-v4/23.3.0/jars/classes.jar:\ /Users/piasy/tools/android-sdk/extras/android/m2repository/com/android/support/support-annotations/23.3.0/support-annotations-23.3.0.jar:\ /Users/piasy/src/BuckJava8RetroLambdaDemo/app/build/intermediates/exploded-aar/com.android.support/support-vector-drawable/23.3.0/jars/classes.jar:\ /Users/piasy/src/BuckJava8RetroLambdaDemo/app/build/intermediates/exploded-aar/com.android.support/appcompat-v7/23.3.0/jars/classes.jar:\ /Users/piasy/src/BuckJava8RetroLambdaDemo/app/build/intermediates/exploded-aar/com.android.support/animated-vector-drawable/23.3.0/jars/classes.jar:\ /Users/piasy/src/BuckJava8RetroLambdaDemo/app/build/intermediates/exploded-aar/com.android.support/support-v4/23.3.0/jars/libs/internal_impl-23.3.0.jar:\ /Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/rt.jar \ /Users/piasy/src/BuckJava8RetroLambdaDemo/app/src/main/java/com/github/piasy/buck/retrolambda/demo/MainActivity.java \ /Users/piasy/src/BuckJava8RetroLambdaDemo/app/build/generated/source/r/debug/android/support/v7/appcompat/R.java \ /Users/piasy/src/BuckJava8RetroLambdaDemo/app/build/generated/source/r/debug/com/github/piasy/buck/retrolambda/demo/R.java \ /Users/piasy/src/BuckJava8RetroLambdaDemo/app/build/generated/source/buildConfig/debug/com/github/piasy/buck/retrolambda/demo/BuildConfig.java \ -XDuseUnsharedTable=true </pre> <p>差异最大的是两部分,一个是 classpath 选项,一个是最后一部分,BUCK 是一个 @ 加一个文件路径,gradle 则是多个文件名,而查看 BUCK 命令中的那个文件内容, <em>基本上</em> 就是 gradle 命令中传入的那几个文件名。那么很可能就是 classpath 部分了(当然,真实情况是我逐一替换了所有不同的部分,不幸的是最后才轮到 classpath )。</p> <p>有趣的是,gradle 和 BUCK 都会先把 aar 文件解压开来,然后把其中的 jar 包加入到 classpath 中,而 gradle 的 classpath 相较于 BUCK 的多了一个非常可疑的对象: jre/lib/rt.jar ,gotcha!不用试了,一看就是它了。</p> <p>当然我还是 google 了一下它的渊源,详见 <a href="/misc/goto?guid=4959672250184578450" rel="nofollow,noindex">这篇文章</a> ,惭愧的是我正如这篇文章开头所描述的那帮程序员一样,连 rt.jar 为何方神圣都不知道,罪过罪过。解压开来之后确实发现 java.lang.invoke.MethodType 这个 class 端坐其中。</p> <p>通过修改 BUCK 的源码,把 rt.jar 加入到 javac 的默认 classpath 中,进一步的测试也验证了问题的症结所在,就是 classpath 少了 rt.jar!</p> <h2>完整的示例</h2> <p>示例工程代码可以在 <a href="/misc/goto?guid=4959672250102432114" rel="nofollow,noindex">Github 获取</a> 。</p> <p>上上节中的 RetroLambda 脚本对 Java library module 的 BUCK 与 RetroLambda 联编可以通过,但对 Android library module 却不行,因为还有许多 jar 包需要加入到 classpath 中,包括 android.jar。而这一点很好解决,把我们在上节中拿到的 BUCK 编译的 javac classpath 加入到 RetroLambda 执行的 classpath 中即可,完整的 RetroLambda 脚本如下:</p> <pre> java \ -Dretrolambda.inputDir=$1 \ -Dretrolambda.classpath=$1:\ /Users/piasy/tools/android-sdk/platforms/android-23/android.jar:\ \ /Users/piasy/tools/android-sdk/platforms/android-23/optional/org.apache.http.legacy.jar:\ ./buck-out/bin/app/__src_release#dummy_r_dot_java_rdotjava_bin__:\ ./buck-out/gen/.okbuck/AFFB34D18189F4D10144A341628B7C81/aar__animated-vector-drawable-23.3.0.aar#aar_prebuilt_jar.jar:\ ./buck-out/gen/.okbuck/AFFB34D18189F4D10144A341628B7C81/aar__appcompat-v7-23.3.0.aar#aar_prebuilt_jar.jar:\ ./buck-out/gen/.okbuck/AFFB34D18189F4D10144A341628B7C81/aar__support-v4-23.3.0.aar#aar_prebuilt_jar.jar:\ ./buck-out/gen/.okbuck/AFFB34D18189F4D10144A341628B7C81/aar__support-vector-drawable-23.3.0.aar#aar_prebuilt_jar.jar:\ ./buck-out/gen/.okbuck/AFFB34D18189F4D10144A341628B7C81/jar__support-annotations-23.3.0.jar.jar:\ ./buck-out/gen/app/lib__build_config_release__output/build_config_release.jar \ -jar ./retrolambda-2.3.0.jar </pre> <p>运行修改过后的 BUCK 编译,成功!</p> <h2>后续工作</h2> <p>这次对 BUCK 的修改量很少,只有一行代码,但却解决了困扰我们长达半年的一个问题,这段心酸岁月终于过去了。修改内容已经提交 <a href="/misc/goto?guid=4959672250283431404" rel="nofollow,noindex">pr</a> 到 BUCK repo,希望能够尽快合入主干,而这个 RetroLambda 的脚本也是有可能自动生成的,所以也加入到了 OkBuck 的开发日程中,近日就将完成。届时欢迎大家使用,尽情体验 lambda + BUCK 的畅快淋漓!</p> <p><span style="line-height:1.6">来自: </span>http://blog.piasy.com/2016/05/03/BUCK-With-RetroLambda/<span style="line-height:1.6"> </span></p>