Slide 1

Slide 1 text

ARTEM ZINNATULLIN, @ARTEM_ZIN Preparing Large Java project for Kotlin adoption

Slide 2

Slide 2 text

DEFINE “LARGE PROJECT”

Slide 3

Slide 3 text

DEFINE “LARGE PROJECT” •19106 text files •13561 Java files •698507 lines of Java code w/o comments (700K) $ git clone # clean repo state $ cloc repo/

Slide 4

Slide 4 text

DEFINE “LARGE PROJECT” •37990 Java files •63129167 lines of Java code (63M O_o) # After regular build

Slide 5

Slide 5 text

DEFINE “LARGE PROJECT” $ find . -type f -name 'build.gradle' | wc -l 709 modules

Slide 6

Slide 6 text

DEFINE “LARGE PROJECT” “Monorepo” (2 big android apps in same git repo) Last week: •54 people committed code •264 PRs landed on master •626 commits across different branches •~200 PRs open at the same time

Slide 7

Slide 7 text

BUILD SETUP

Slide 8

Slide 8 text

BUILD SETUP 2 build systems: - Gradle -Most of CI builds -Release builds -Source of truth for configuration -Buck -Local builds (better performance and resource consumption) -Unit tests on CI -Generates IntelliJ project

Slide 9

Slide 9 text

PLAN

Slide 10

Slide 10 text

PLAN • Build support (both Gradle and Buck) • IDE support for Kotlin • Static Analysis for Kotlin • Null safety for Java • Automatic code formatting for Kotlin • Kotlin Annotation Processing?

Slide 11

Slide 11 text

BUILD SUPPORT

Slide 12

Slide 12 text

BUILD SUPPORT: GRADLE Everything is good (almost) JetBrains maintains Kotlin Gradle Plugin

Slide 13

Slide 13 text

BUILD SUPPORT: GRADLE We’ve only faced 1 major build issue (already fixed) KT-15562 — Kotlin Compiler Daemon protocol violation in massively parallel builds https://youtrack.jetbrains.com/issue/KT-15562

Slide 14

Slide 14 text

BUILD SUPPORT: GRADLE https://youtrack.jetbrains.com/issue/KT-15562

Slide 15

Slide 15 text

BUILD SUPPORT: GRADLE • 99% OK • Use Kotlin EAPs, help JetBrains • Search & report bugs on https://youtrack.jetbrains.com/issues/KT • Kotlin is 100% open source, including Kotlin Gradle Plugin https://github.com/JetBrains/kotlin

Slide 16

Slide 16 text

IDE SUPPORT

Slide 17

Slide 17 text

IDE SUPPORT: INTELLIJ IDEA COMMUNITY EDITION 99% works because JetBrains Kotlin support is bundled into IJ CE since 2017. However, in our setup Buck generates IntelliJ project (not perfect) Broken source roots if module uses Kotlin

Slide 18

Slide 18 text

IDE SUPPORT: INTELLIJ IDEA COMMUNITY EDITION

Slide 19

Slide 19 text

IDE SUPPORT: INTELLIJ IDEA COMMUNITY EDITION

Slide 20

Slide 20 text

IDE SUPPORT: INTELLIJ IDEA COMMUNITY EDITION

Slide 21

Slide 21 text

So we wrote a tool in Kotlin to post- process .iml files for Kotlin modules after Buck IDE SUPPORT: INTELLIJ IDEA COMMUNITY EDITION

Slide 22

Slide 22 text

Generating project: finished in 25.5 sec Fixing .iml files generated by Buck... lyft/buck-iml-fixer finished successfully, patched 697 files, skipped 2 files, took 0.882897147 seconds IDE SUPPORT: INTELLIJ IDEA COMMUNITY EDITION

Slide 23

Slide 23 text

STATIC ANALYSIS

Slide 24

Slide 24 text

STATIC ANALYSIS: BEFORE • PMD • Simple checks • Only supports Java • Checkstyle • Simple style checker • Only supports Java • Android Lint • Very powerful framework for checks • Rich HTML/XML reports • Uses Universal AST • Supports Java • Supports Kotlin • XML • … • Arbitrary files

Slide 25

Slide 25 text

• PMD • Checkstyle • Android Lint • Adopt newest versions with better Kotlin support • Apply to non-android modules • Rewrite existing custom rules with UAST • New custom rules should only use UAST • New custom Kotlin specific rules • Error Prone • Fix performance issues (we didn’t expect any, but…) • Adopt uber/NullAway — null safety in Java code • Detekt • Apply all of the ones that matter STATIC ANALYSIS: AFTER

Slide 26

Slide 26 text

ERROR PRONE: NULLAWAY • Checks for null-safety violations in Java • null is 99% safe in Kotlin • But we have tons of Java code to interop with • That’s why we need null-safety in Java • Has reasonable defaults similar to Kotlin • Configurable • Runs as Error Prone Check • Can dive into library byte code to infer nullability

Slide 27

Slide 27 text

NULLAWAY static void log(@Nullable Object x) { System.out.println(x.toString()); } $ javac … error: [NullAway] dereferenced expression x is @Nullable System.out.println(x.toString()); https://github.com/uber/NullAway

Slide 28

Slide 28 text

Median build time (Docker, Oracle JDK 8 1.8.0_161, Error Prone replaces JDK's javac) • baseline: 4.173 sec • Error Prone with default settings: 14.922 sec, 3.57x slower • Error Prone with most expensive checks disabled: 14.680 sec, 3.51x slower • Error Prone with all checks disabled: 10.358 sec, 2.48x slower ⚠ https://github.com/artem-zinnatullin/error-prone-performance ERROR PRONE: PERFORMANCE ISSUES

Slide 29

Slide 29 text

Max "Used size" memory (Docker, Oracle JDK 8 1.8.0_161, Error Prone replaces JDK's javac) • baseline: 0.89 GB • Error Prone with default settings: 2.3 GB, 2.58x memory used • Error Prone with most expensive checks disabled: 2.08 GB, 2.3x memory used • Error Prone with all checks disabled: N/A (haven't measured yet) Looks like Parallel GC doesn't play well with Error Prone and max memory usage spikes significantly, please see below results with G1 GC on JDK 9. ERROR PRONE: PERFORMANCE ISSUES https://github.com/artem-zinnatullin/error-prone-performance

Slide 30

Slide 30 text

ERROR PRONE: PERFORMANCE ISSUES https://github.com/artem-zinnatullin/error-prone-performance

Slide 31

Slide 31 text

• Error Prone is designed to run as compiler or a plugin to compiler • Build systems with incremental compilation like Gradle can avoid re-checking • Has a loooot checks that run by default, including Google-specific • Do you use Guava? • Do you use Protobuf? • Do you use Thruth? • Some checks are just complicated and slow • etc • etc ERROR PRONE: PERFORMANCE ISSUES

Slide 32

Slide 32 text

-Disabling all checks still didn’t fix the issue, hmm -What do? -Chat with some people first to make sure you’re not insane -Create reproducible, isolated sample (thx Docker) -Run on different machines -Report to upstream! ERROR PRONE: PERFORMANCE ISSUES https://github.com/artem-zinnatullin/error-prone-performance

Slide 33

Slide 33 text

https://github.com/google/error-prone/issues/994 ERROR PRONE: PERFORMANCE ISSUES

Slide 34

Slide 34 text

https://github.com/tbroyer/gradle-errorprone-plugin/pull/68 Sad truth: the problem was in the ErrorProne Gradle Plugin It was creating a new ClassLoader to load Error Prone on each compilation thus JIT was unable to reuse optimized code ERROR PRONE: PERFORMANCE ISSUES

Slide 35

Slide 35 text

https://github.com/tbroyer/gradle-errorprone-plugin/pull/68 ERROR PRONE: PERFORMANCE ISSUES

Slide 36

Slide 36 text

https://github.com/tbroyer/gradle-errorprone-plugin/pull/68 ERROR PRONE: PERFORMANCE ISSUES, ~166% WIN

Slide 37

Slide 37 text

• Warnings → Errors, no one looks at warnings • Exclude-list setup to enable on per-module basis + all new modules by default • Enable for all core modules to ease adoption • Configure common lateinit field methods ie onCreate(), onBindViews(), etc • Write docs/nullability.md • Small internal talk so everyone is on the same page NULL AWAY: INTEGRATION STRATEGY

Slide 38

Slide 38 text

CODE FOR MATTING { }

Slide 39

Slide 39 text

• be configurable/flexible • be consistent with IDE • be executable from command line • be fast • be isolated from local environment CODE FORMATTING Ideally it should

Slide 40

Slide 40 text

So we’ve one! CODE FORMATTING built*

Slide 41

Slide 41 text

•Downloads separate version of IntelliJ CE (once) •Removes some default plugins to start faster •Installs some plugins to format more languages •Sets custom idea.vmoptions optimized for short start/exit use-case •Sets custom idea.properties to isolate from existing IntellIJs •Uses code style settings from target project https://github.com/lyft/we-will-open-source-it-soon CODE FORMATTING

Slide 42

Slide 42 text

https://github.com/lyft/we-will-open-source-it-soon Integration: •git pre-commit hook •only formats files that are about to be committed • CODE FORMATTING

Slide 43

Slide 43 text

➜ instant-android git:(az/test) ✗ git commit -am 'Increase cpu limits' Formatting files... IntelliJ IDEA 2018.1.4, build IC-181.5087.20 Formatter Formatting frameworks/work-manager/src/…/java/com/…/workmanager/WorkerData.kt…OK Formatting frameworks/common-result/build.gradle...OK Formatting frameworks/rx/src/main/java/me/lyft/android/rx/RxBinder.java...OK Formatting gradle.properties...OK Formatting Dockerfile...Skipped, not supported. 4 file(s) formatted. Took 12 seconds. [az/test cc6ea59887] Increase cpu limits 5 files changed, 15 insertions(+), 2 deletions(-) CODE FORMATTING

Slide 44

Slide 44 text

https://github.com/lyft/we-will-open-source-it-soon Problems — Performance •Typically takes ~6 seconds •Actual formatting takes less than a second •Potential fix — switch to AOT JVM (Java 10/11) •Potential fix — patch IntelliJ CE to optimize formatting entrypoint CODE FORMATTING

Slide 45

Slide 45 text

RESULTS

Slide 46

Slide 46 text

RESULTS - 303 .kt files - 18k LOC - 56 modules use Kotlin

Slide 47

Slide 47 text

TODO

Slide 48

Slide 48 text

Most of remaining challenges are about humans •Docs docs docs •Restrict Kotlin sdtlib with custom Lint rules •KAPT? •Some Buck patches Artem Zinnatullin, @artem_zin TODO