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

Automatisez_vos_workflows_en_Kotlin.pdf

mbonnin
February 14, 2020

 Automatisez_vos_workflows_en_Kotlin.pdf

mbonnin

February 14, 2020
Tweet

More Decks by mbonnin

Other Decks in Programming

Transcript

  1. Une journée (presque) classique 5 1. S’assigner un ticket jira

    2. Ouvrir une branche github 3. Coder... 4. Créer une pull request 5. Déplacer le ticket 6. Merger la pull request 7. Déplacer le ticket 8. Créér une alpha ◦ Incrémenter version ◦ Tag ◦ Compiler ◦ Push ◦ Archiver 9. Envoyer un message aux designers/product owners 10. Intégrer les retours 11. Revenir à l’étape 1. • Envoyer les traductions • Récupérer les traductions • Mettre à jour le changelog • Notifier la nouvelle version sur jira • Envoyer en beta • Mettre le changelog sur le play store. • Démarrer le rollout • Et aussi sur Amazon... ➡ Et tout nettoyer à la fin
  2. Mais on gagne: • En fiabilité • Reproducibilité • Auto-documentation

    • Fun • Et on peut gagner du temps (des fois) 8
  3. Solutions existantes 10 Fastlane (Ruby) Transifex cli (Ruby) after_success.sh (Bash)

    generate_docs.sh (Bash) Github hub (Go) build.gradle (Groovy) Scripts ad-hoc Outils tiers Système de compilation Outils génériques
  4. Pourquoi Kotlin? • Comment faire un tableau en bash ?

    • Est-ce qu’il faut échapper les ‘\’ ? • Comment faire une requete HTTP en ruby ? • Et parser un Json en go ? • Avec Kotlin, pas de changement de Contexte 11
  5. Mais aussi • Language Moderne • Un IDE au top

    (la plupart du temps!) • Un écosystème riche 12
  6. La boite à outils 13 • Outils ◦ Kscript ◦

    Clikt • Bibliothèques ◦ Process ◦ HTTP ◦ Json ◦ ... ➡ Exemple d’application à Dailymotion
  7. Scripting 15 • Pour des petits projets • Un seul

    fichier • Facile à mettre en place et à utiliser
  8. Avec le compilateur Kotlin $ echo ‘println("Hello ${args[0]}!")’ > hello.kts

    $ kotlinc -script hello.kts “DevFest Paris” Hello DevFest Paris! 16
  9. ✨ Avec Kscript ✨ • https://github.com/holgerbrandl/kscript • Mise en cache

    des compilations • Shebang • Gestion des dépendances • Distribution • Intégration IntelliJ 17
  10. Kscript - installation curl -s "https://get.sdkman.io" | bash # install

    sdkman source ~/.bash_profile # add sdkman to PATH sdk install kotlin # install Kotlin sdk install kscript # install Kscript touch hello.kts kscript --idea hello.kts # start the IDE 18
  11. Formats d’entrée • Fichier *.kts • Url • Stdin •

    Ou juste des paramètres ◦ kscript 'println(42.toString(16))' • Et meme un REPL ◦ kscript -i 19
  12. Kscript 20 // weekend.kts #!/usr/bin/env kscript @file:DependsOn("com.squareup.okhttp3:okhttp :4.3.1") import okhttp3.OkHttpClient

    import okhttp3.Request val weekend = isItTheWeekEndApiCall() if (weekend) { println("It is the weekend!") } else { println("Not yet :-|") }
  13. Kotlin - OkHttp 21 • https://square.github.io/okhttp/ • Développé par square

    • 100% Kotlin depuis la version 4 • API complète • Bientôt multiplateforme ?
  14. Kotlin - OkHttp 22 fun isItTheWeekEndApiCall(): Boolean { val request

    = Request.Builder() .get() .url("http://isitweekendyet.com/") .build() val response = OkHttpClient().newCall(request).execute() return response.body!!.string().toLowerCase().contains("yes") }
  15. Kotlin - OkHttp 23 fun isItTheWeekEndApiCall(): Boolean { val request

    = Request.Builder() .get() .header("User-Agent", "DevFest - 2020") .url("http://isitweekendyet.com/") .build() val response = OkHttpClient().newCall(request).execute() return response.body!!.string().toLowerCase().contains("yes") }
  16. Kotlin - OkHttp 24 fun isItTheWeekEndApiCall(): Boolean { val request

    = Request.Builder() .get() .header("User-Agent", "DevFest - 2020") .url("http://isitweekendyet.com/") .build() val response = OkHttpClient().newCall(request).execute() if (response.code() == 401) { // get credentials and retry } return response.body!!.string().toLowerCase().contains("yes") }
  17. Kscript 25 // weekend.kts #!/usr/bin/env kscript @file:DependsOn("com.squareup.okhttp3:okhttp :4.3.1") import okhttp3.OkHttpClient

    import okhttp3.Request val weekend = Request.Builder().get().url("http://isitweek endyet.com/").build().let { OkHttpClient().newCall(it) }.execute().body!!.string().toLowerCase().co ntains("yes") if (weekend) { println("It is the weekend!") } else { println("Not yet :-|") } $ chmod +x weekend.kts $ ./weekend.kts Not yet :-|
  18. Kscript - Distribution 29 # Inclusion des dépendances $ kscript

    --package weekend.kts $ ./weekend argument another-argument ou # Installation de kscript $ kscript --add-bootstrap-header weekend.kts
  19. • Rapide à mettre en place ◦ Generation de documentation

    ◦ Scripts d’installations ◦ Migrations vers le Kotlin Gradle DSL • JVM nécessaire • Pas vraiment prévu pour des projets à plusieurs fichiers. Kscript - Conclusion 31
  20. Paramètres 34 • Optionnels ou non. • Entier, flottant ou

    chaine de caractere ou Booleen. • Nommé ou positionnel. $ hello -d --count 1 --name “Paris”
  21. En bash 35 POSITIONAL=() while [[ $# -gt 0 ]]

    do key="$1" case $key in -e|--extension) EXTENSION="$2" shift # past argument shift # past value ;; --default) DEFAULT=YES shift # past argument ;; *) # unknown option POSITIONAL+=("$1") # save it in an array for later shift # past argument ;; esac done set -- "${POSITIONAL[@]}" # restore positional parameters
  22. En Kotlin 36 class Hello : CliktCommand() { val count

    by option().int().default(1) val name by option(help = "Votre nom").required() override fun run() { for (i in 1..count) { echo("Hello $name!") } } }
  23. Hello World 37 class Hello : CliktCommand() { val count

    by option().int().default(1) val name by option(help = "Votre nom").required() override fun run() { for (i in 1..count) { echo("Hello $name!") } } }
  24. Hello World 38 class Hello : CliktCommand() { val count

    by option().int().default(1) val name by option(help = "Votre nom").required() override fun run() { for (i in 1..count) { echo("Hello $name!") } } }
  25. Hello World 39 class Hello : CliktCommand() { val count

    by option().int().default(1) val name by option(help = "Votre nom").required() override fun run() { for (i in 1..count) { echo("Hello $name!") } } }
  26. Hello World 40 class Hello : CliktCommand() { val count

    by option().int().default(1) val name by option(help = "Votre nom").required() override fun run() { for (i in 1..count) { // No need to cast echo("Hello " + name.toString() + "!") } } }
  27. Main 41 fun main(args: Array<String>) = Hello().main(args) $ ./hello --count

    3 --name Paris Hello Paris! Hello Paris! Hello Paris!
  28. Validation 42 $ ./hello --count 3 Error: Missing option "--name".

    $ ./hello --count DevFest --name Paris Error: Invalid value for "--count": DevFest is not a valid integer $ ./hello --ooops Error: No such option "--ooops"
  29. Aide 43 $./hello -h Options: --count INT --name TEXT Votre

    nom -h, --help Show this message and exit
  30. Aide 44 class Hello : CliktCommand() { val count by

    option().int().default(1) val name by option(help = "Votre nom").required() override fun run() { for (i in 1..count) { echo("Hello $name!") } } }
  31. Aide 45 class Hello : CliktCommand() { val count by

    option(help = "Nombre de fois").int().default(1) val name by option(help = "Votre nom").required() override fun run() { for (i in 1..count) { echo("Hello $name!") } } }
  32. Aide 46 $./hello -h Options: --count INT Nombre de fois

    --name TEXT Votre nom -h, --help Show this message and exit
  33. Autocomplete! 47 $ _HELLO_COMPLETE=bash hello #!/usr/bin/env bash # Command completion

    for hello # Generated by Clikt [...] A lot of bash completion code [...] $ _HELLO_COMPLETE=bash hello > hello_complete $ source hello_complete
  34. KScript vs Clikt 50 • KScript ◦ Rapide à mettre

    en place ◦ Un seul fichier • Clikt ◦ Un peu de mise en place ◦ Pour des projets plus larges • Les deux sont complémentaires.
  35. En pratique: la commande buildTag 52 $ buildTag flavor=alpha build=debug

    versionCode=14500 ◦ Génère un aab signé + universal apk ◦ Exécute tous les test/lint ◦ Génère le Changelog ◦ Envoie sur appcenter ◦ Envoie un message sur slack
  36. Projet Gradle Application 53 plugins { application id("org.jetbrains.kotlin.jvm") } dependencies

    { implementation("org.jetbrains.kotlin:kotlin-stdlib:1.3.61") implementation("com.github.ajalt:clikt:2.4.0") } application { mainClassName = "com.dailymotion.buildTag.MainKt" applicationName = "buildTag" }
  37. Projet Gradle Application 54 plugins { application id("org.jetbrains.kotlin.jvm") } dependencies

    { implementation("org.jetbrains.kotlin:kotlin-stdlib:1.3.61") implementation("com.github.ajalt:clikt:2.4.0") } application { mainClassName = "com.dailymotion.buildTag.MainKt" applicationName = "buildTag" }
  38. La commande buildTag 55 // com.dailymotion.buildTag.Main.kt class BuildTag : CliktCommand(name

    = "buildTag", help = "Makes all tests and builds a release.") { [...] } fun main(args: Array<String>) = BuildTag().main(args)
  39. La commande buildTag 56 class BuildTag : CliktCommand(name = "buildTag",

    help = "Makes all tests and builds a release.") { val tag by argument(name = "tag", help = "The tag to build").long() val flavor by option(help = "The flavor to build. Will build all flavor if not specified.") .choice("alpha", "playStore") val build by option(help = "The buildType to build. Will build all buildTypes if not specified.") .choice("debug", "release") override fun run() { // Build the tag } }
  40. La commande buildTag 57 class BuildTag : CliktCommand(name = "buildTag",

    help = "Makes all tests and builds a release.") { val tag by argument(name = "tag", help = "The tag to build").long() val flavor by option(help = "The flavor to build. Will build all flavor if not specified.") .choice("alpha", "playStore") val build by option(help = "The buildType to build. Will build all buildTypes if not specified.") .choice("debug", "release") override fun run() { // Build the tag } }
  41. La commande buildTag 58 class BuildTag : CliktCommand(name = "buildTag",

    help = "Makes all tests and builds a release.") { val tag by argument(name = "tag", help = "The tag to build").long() val flavor by option(help = "The flavor to build. Will build all flavor if not specified.") .choice("alpha", "playStore") val build by option(help = "The buildType to build. Will build all buildTypes if not specified.") .choice("debug", "release") override fun run() { checkoutVersion(tag) executeGradle(flavor, build) computeChangelog() deploy(flavor, build, tag) sendSlack(tag) } }
  42. Processus: lire la sortie 61 val process = ProcessBuilder().command("gradle", "assembleProdDebug")

    .directory(File("projectDir")) .start() val output = process.inputStream.reader().readText() val needle = output.lines().find { ... }
  43. Processus: redirections 62 import java.io.File val processList = ProcessBuilder.startPipeline(listOf( ProcessBuilder().command("echo",

    """Hello |Paris""".trimMargin()), ProcessBuilder().command("grep", "Hello") )) val result = processList.get(1).inputStream.reader().readText()
  44. Encore mieux: GradleRunner 63 val result = GradleRunner.create() .withProjectDir(projectDir) .withArguments("assembleProdDebug")

    .build() Assert.assertEquals( TaskOutcome.SUCCESS, result.task(":assembleProdDebug")?.outcome )
  45. Reprenons 64 $ buildTag flavor=alpha build=debug versionCode=14500 ◦ Génère un

    aab signé + universal apk ✅ ◦ Exécute tous les test/lint ✅ ◦ Génère le Changelog ◦ Envoie sur appcenter et message Slack ⬦ OkHttp ✅ ⬦ Json ?
  46. Kotlinx.serialization 65 • Bibliothèque développée par Jetbrains • Plugin compilateur

    qui génére des parsers sans reflexion. • Compatible Json & protobuf • 100% Multiplatforme
  47. Kotlinx.serialization: mise en place 66 plugins { id("org.jetbrains.kotlin.jvm.plugin.serialization") } dependencies

    { implementation("org.jetbrains.kotlinx:kotlinx-serial ization-runtime:0.14.0") }
  48. Kotlinx.serialization: Avec des classes 67 @Serializable class PostData(val release_notes: String,

    val destination: String) val json = Json.stringify( PostData("new features and new bugs", "everyone") ) val data = Json.parse(PostData.serializer(), json)
  49. Kotlinx.serialization: Sans classes 68 // Serialization val json = JsonObject(

    mapOf( "destination_name" to JsonPrimitive("Everyone"), "release_notes" to JsonPrimitive("new features and new bugs") ) ).toString() // Deserialization val releaseNotes = Json.parse(json) .jsonObject .getPrimitive("release_notes") .content
  50. Requête AppCenter 69 val json = Json.stringify( PostData("new features and

    new bugs", "everyone") ) val release_url = Request.Builder() .header("Content-Type", "application/json") .header("Accept", "application/json") .header("X-API-Token", token_) .patch(RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json)) .url("https://api.appcenter.ms/${app}") .build() .let { OkHttpClient().newCall(it).execute().body()!!.string() }.let { Json.nonstrict.parse(ResultData.serializer(), it).release_url }
  51. buildTag en Action 70 martin@n550~$ ./bin/buildTag --flavor=demo 14500 Compiling version

    14500… Running lint… Running unit tests… Running connected tests… Generating universal apk… Uploading… Sending slack message… Done.
  52. Kotlin: Ecosystème 71 • Génération de code Swagger/Graphql • Ktor

    pour embarquer un serveur web • JGit pour s’intégrer avec git • Votre bibliothèque préférée • Et même Dagger si besoin !
  53. Le couteau suisse Git • startWork • PR • cleanLocal

    • cleanRemote • changelog • hotfix • branch Traductions • txPull • txPush • txPR Travis/Bitrise • trigger • encrypt • encryptFile Play Store metadatas • uploadWhatsNew • uploadListing • uploadScreenshots • generateScreenshots Play Store releases • beta • release Compilation • nightly • buildPR • buildTag 72
  54. Sur la machine de développement 4. Crééer une pull request

    5. Mettre à jour le ticket startWork {TICKET_ID} 1. Assigner un ticket 2. Crééer une branche 7. Garder un repo propre 3. Code 6. Merger la pull request pr cleanLocal 74 ‍
  55. Commandes réutilisables Git • startWork • PR • cleanLocal •

    cleanRemote • changelog • hotfix • branch Traductions • txPull • txPush • txPR Travis/Bitrise • trigger • encrypt • encryptFile Play Store metadatas • uploadWhatsNew • uploadListing • uploadScreenshots • generateScreenshots Play Store releases • beta • release Compilation • nightly • buildPR • buildTag 77
  56. Commandes réutilisables Git • startWork • PR • cleanLocal •

    cleanRemote • changelog • hotfix • branch Traductions • txPull • txPush • txPR Travis/Bitrise • trigger • encrypt • encryptFile Play Store metadatas • uploadWhatsNew • uploadListing • uploadScreenshots • generateScreenshots Play Store releases • beta • release Compilation • nightly • buildPR • buildTag 78
  57. Plusieurs niveaux 80 Lib • Méthodes unitaires ◦ Github ◦

    Travis ◦ Bitrise ◦ Transifex ◦ Google Play ◦ Etc… • Dépendance maven/gradle standard
  58. Plusieurs niveaux 81 Commandes builtin • playStore listing • playStore

    beta • playStore release • playStore screenshots • github pr • gitlab pr • transifex pull • transifex push • travis encrypt • ...
  59. 83 • Open source • Encore en gros développement •

    https://github.com/dailymotion/kinta • Ouvrez des ticket! Kinta
  60. 84 • Clikt et Kscript sont complémentaires. • HTTP, Json,

    Process, on peut tout faire en Kotlin ◦ Aussi du backend et du web. Le mot de la fin 5 mars à Bastille