Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Develop your CI tools

Develop your CI tools

Nowadays, setting up a basic unit and syntax testing pipeline for your mobile app has never been easier! That’s a great starting point, but what comes next? We can leverage the CI pipeline to do so much more! Things like automatic code generation and advanced static analysis—all with an aim to ensuring the reliability and performance of your applications.

In this talk, we’ll take a look at several tools and techniques that you can use to improve your quality of life as a developer, largely by letting the CI do the hard work for you.

Xavier Gouchet

April 25, 2022
Tweet

More Decks by Xavier Gouchet

Other Decks in Programming

Transcript

  1. Small team ▫ 3 engineers ▫ Lots of features ▫

    Lots of bug fixes ▫ Lots of improvements ▫ Small bandwidth Unsplash photo by @marvelous
  2. Delegating to the CI ▫ Runs automatically ▫ Strict and

    thorough ▫ Great way to offload the PR Review
  3. Code Review Pyramid Credit: Gunnar Morling (CC-BY-SA 4.0) CODE STYLE

    TESTS IMPLEMENTATION API SEMANTICS DOCUMENTATION
  4. What is Detekt? ▫ Kotlin Static Analysis ▫ Code smells

    ▫ Performance Issues ▫ Code complexity ▫ Documentation Missing ▫ …
  5. How does Detekt work ▫ Visitor pattern over the Syntax

    Tree (PSI) ▫ With Type Resolution ▫ Report issues on specific patterns
  6. Custom Rule class MyCustomRule : Rule() { override val issue:

    Issue = Issue( javaClass.simpleName, Severity.CodeSmell, "A description of the issue…", Debt.TWENTY_MINS ) }
  7. Custom Rule class MyCustomRule : Rule() { override val issue:

    Issue = Issue( javaClass.simpleName, Severity.CodeSmell, "A description of the issue…", Debt.TWENTY_MINS ) }
  8. Custom Rule class MyCustomRule : Rule() { override val issue:

    Issue = Issue( javaClass.simpleName, Severity.CodeSmell, "A description of the issue…", Debt.TWENTY_MINS ) }
  9. A few examples of our custom rules ▫ Forbid throwing

    exceptions ▫ Forbid using check or require ▫ Forbid usage of !! ▫ Flag unsafe third party methods ▫ Flag TODO comments without a Jira ticket
  10. Custom Rule class MyCustomRule( config: Config ) : Rule(config) {

    private val enableFoo: Boolean by config(defaultValue = true) //… }
  11. Custom Rule class MyCustomRule( config: Config ) : Rule(config) {

    private val enableFoo: Boolean by config(defaultValue = true) //… }
  12. Custom Rule class MyCustomRule( config: Config ) : Rule(config) {

    private val enableFoo: Boolean by config(defaultValue = true) //… }
  13. What is KSP? ▫ Lightweight Compiler Plugin ▫ Similar to

    (K)APT but faster ▫ Source code is read-only ▫ Code generation
  14. How does KSP work ▫ Called at compile time ▫

    Processor can query files and/or declarations ▫ Can generate new code (but not modify existing one)
  15. Custom Processor class MyCustomSymbolProcessor( val codeGenerator: CodeGenerator, val logger: KSPLogger

    ) : SymbolProcessor { override fun process(resolver: Resolver): List<KSAnnotated> { return emptyList() } }
  16. Custom Processor class MyCustomSymbolProcessor( val codeGenerator: CodeGenerator, val logger: KSPLogger

    ) : SymbolProcessor { override fun process(resolver: Resolver): List<KSAnnotated> { return emptyList() } }
  17. Custom Processor class MyCustomSymbolProcessor( val codeGenerator: CodeGenerator, val logger: KSPLogger

    ) : SymbolProcessor { override fun process(resolver: Resolver): List<KSAnnotated> { return emptyList() } }
  18. Custom Processor override fun process(resolver: Resolver): List<KSAnnotated> { val deferred

    = mutableListOf<KSAnnotated>() resolver.getSymbolsWithAnnotation("com.example.MyAnnotation") .forEach { if (!generateSomething(it)) deferred.add(it) } return deferred }
  19. Custom Processor override fun process(resolver: Resolver): List<KSAnnotated> { val deferred

    = mutableListOf<KSAnnotated>() resolver.getSymbolsWithAnnotation("com.example.MyAnnotation") .forEach { if (!generateSomething(it)) deferred.add(it) } return deferred }
  20. Custom Processor override fun process(resolver: Resolver): List<KSAnnotated> { val deferred

    = mutableListOf<KSAnnotated>() resolver.getSymbolsWithAnnotation("com.example.MyAnnotation") .forEach { if (!generateSomething(it)) deferred.add(it) } return deferred }
  21. Custom Processor override fun process(resolver: Resolver): List<KSAnnotated> { val deferred

    = mutableListOf<KSAnnotated>() resolver.getSymbolsWithAnnotation("com.example.MyAnnotation") .forEach { if (!generateSomething(it)) deferred.add(it) } return deferred }
  22. Custom Processor override fun process(resolver: Resolver): List<KSAnnotated> { val deferred

    = mutableListOf<KSAnnotated>() resolver.getSymbolsWithAnnotation("com.example.MyAnnotation") .forEach { if (!generateSomething(it)) deferred.add(it) } return deferred }
  23. NoOpDataWriter.kt internal class NoOpDataWriter<T : Any> : DataWriter<T> { public

    override fun write(element: T) { } public override fun write(data: List<T>) { } }
  24. KotlinPoet val file = FileSpec.builder("", "Greeter") .addType( TypeSpec.classBuilder("Greeter") .addFunction( FunSpec.builder("greet")

    .addParameter("name", String::class) .addStatement("println(%P)", "Hello, \$name").build() ).build() ).build()
  25. KotlinPoet val file = FileSpec.builder("", "Greeter") .addType( TypeSpec.classBuilder("Greeter") .addFunction( FunSpec.builder("greet")

    .addParameter("name", String::class) .addStatement("println(%P)", "Hello, \$name").build() ).build() ).build()
  26. KotlinPoet val file = FileSpec.builder("", "Greeter") .addType( TypeSpec.classBuilder("Greeter") .addFunction( FunSpec.builder("greet")

    .addParameter("name", String::class) .addStatement("println(%P)", "Hello, \$name").build() ).build() ).build()
  27. KotlinPoet val file = FileSpec.builder("", "Greeter") .addType( TypeSpec.classBuilder("Greeter") .addFunction( FunSpec.builder("greet")

    .addParameter("name", String::class) .addStatement("println(%P)", "Hello, \$name").build() ).build() ).build()
  28. How does Gradle work ▫ Project object created by Gradle

    ▫ Plugin adds extension/tasks to it
  29. Custom Task open class MyCustomTask : DefaultTask() { init {

    group = "MyCompany" description = "Description of the task" } @TaskAction fun applyTask() { } }
  30. Custom Task open class MyCustomTask : DefaultTask() { init {

    group = "MyCompany" description = "Description of the task" } @TaskAction fun applyTask() { } }
  31. Custom Task open class MyCustomTask : DefaultTask() { init {

    group = "MyCompany" description = "Description of the task" } @TaskAction fun applyTask() { } }
  32. Custom Plugin class MyCustomPlugin : Plugin<Project> { override fun apply(target:

    Project) { val myExtension = target.extensions .create("myExtension", MyCustomExtension::class.java) val myTask = target.tasks .create("myTask", MyCustomTask::class.java) myTask.extension = myExtension } }
  33. Custom Plugin class MyCustomPlugin : Plugin<Project> { override fun apply(target:

    Project) { val myExtension = target.extensions .create("myExtension", MyCustomExtension::class.java) val myTask = target.tasks .create("myTask", MyCustomTask::class.java) myTask.extension = myExtension } }
  34. Custom Plugin class MyCustomPlugin : Plugin<Project> { override fun apply(target:

    Project) { val myExtension = target.extensions .create("myExtension", MyCustomExtension::class.java) val myTask = target.tasks .create("myTask", MyCustomTask::class.java) myTask.extension = myExtension } }
  35. Custom Plugin class MyCustomPlugin : Plugin<Project> { override fun apply(target:

    Project) { val myExtension = target.extensions .create("myExtension", MyCustomExtension::class.java) val myTask = target.tasks .create("myTask", MyCustomTask::class.java) myTask.extension = myExtension } }
  36. Custom Plugin class MyCustomPlugin : Plugin<Project> { override fun apply(target:

    Project) { val myExtension = target.extensions .create("myExtension", MyCustomExtension::class.java) val myTask = target.tasks .create("myTask", MyCustomTask::class.java) myTask.extension = myExtension } }
  37. An example of custom Gradle Tasks ▫ Compute dependencies size

    and OSS Licenses ▫ Keep track of API Surface ▫ Generate Data Class from JSON Schema ▫ From a remote git repository
  38. Custom Plugin override fun apply(target: Project) { // ... target.afterEvaluate

    { p -> p.tasks.withType(KotlinCompile::class.java) { dependsOn(myTask) } } }
  39. What is it? ▫ Plugin getting access to Kotlin source

    code / IR ▫ Can manipulate the code before compilation into JVM/JS/… ▫ Similar to Aspect Oriented Programming
  40. Compiler Plugin ▫ Kotlin Compiler Plugin ▫ Gradle Plugin /

    Maven Plugin ▫ IDE Plugin (icons / syntax)
  41. More info… ▫ https:/ /www.youtube.com/watch?v=w-GMlaziIyo ▫ https:/ /arrow-kt.io/docs/meta/ ▫ https:/

    /medium.com/@heyitsmohit/writing-kotlin-compiler-pl ugin-with-arrow-meta-cf7b3689aa3e
  42. PSI Viewer ▫ IDE Plugin for Intellij Idea and Android

    Studio ▫ Displays the actual PSI for the open tab
  43. “ Your Detekt rules, KSP, Gradle scripts are still code.

    Keep them as clean, maintainable and trustworthy as your production code
  44. Credits Special thanks to all the people who made and

    released these awesome resources for free: ▫ Presentation template by SlidesCarnival ▫ Photographs by Unsplash 79