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

Automatisez vos workflows en Kotlin

mbonnin
October 19, 2019

Automatisez vos workflows en Kotlin

Ce talk est un retour d'expérience sur la manière dont nous utilisons kotlin à toutes les étapes du développement de notre app Android chez Dailymotion:

* sur les machines de dev, pour la gestion des pull requests, connection jira et déploiement avec un outil en ligne de commande utilisant Clikt.

* sur les serveurs de CI avec un runner kotlin qui execute la compilation, tests et envoi des artifacts.

* sur un servlet Google Cloud pour héberger nos artifacts et Kdoc.

* sur un cron pour monitorer les commentaires utilisateurs du Google Play.

* enfin, de manière générale, pour remplacer bash et perl par du kotlin partout grâce à kscript!

Le but du talk n'est pas d'aller dans le détail de chaque technologie mais plus de montrer l'étendue des possibles. L'écosystème kotlin rend accessible un grand nombre de tâches.

mbonnin

October 19, 2019
Tweet

More Decks by mbonnin

Other Decks in Programming

Transcript

  1. Dailymotion 3 • Sprints 2 semaines • Release à la

    fin de chaque sprint • Feature flags • Product Owner, Designers • Jira partout!
  2. Workflows (simplifiés) 5 1. S’assigner un ticket jira 2. Ouvrir

    une branche github 3. Coder... 4. Créér 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 ◦ Push 9. Envoyer un message aux designers/product owners 10. Intégrer les retours 11. Revenir à l’étape 1. • Envoyer les nouvelles chaines de caractères • Récupérer les traductions • Vérifier 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
  3. Solutions existantes 9 • Hub ◦ go • Play-publisher ◦

    gradle plugin • Client transifex ◦ python • Fastlane ◦ Ruby • Bitrise cli ◦ go
  4. 10 Syndrome /bin/bash #!/bin/bash my_array=(item0 item1 "item2") # guillemets ou

    pas guillemets ? unset my_array[1] echo ${my_array[@]} # index '@' ? my_array += "item 3" # les espaces !! if [ ${#ArrayName[@]} = 0 ] # or is it if [[ "${#ArrayName[@]}" == 0]] ? then # ne pas oublier ! echo "yeaaa, j'aime bash <3" fi
  5. Changements de contexte 11 • Réapprendre la syntaxe • Réapprendre

    les outils • Réapprendre les bibliothèques ◦ Comment faire une requête HTTP ⬦ curl ⬦ urllib ⬦ net/http Perte de productivité & Frustration
  6. Kotlin 13 • Pas de changement de contexte • Un

    IDE au top (la plupart du temps!) • Un écosystème riche
  7. Boite à outils 14 • Outils ◦ Clikt (cli) ◦

    Kscript (~bash) ◦ Servlets (cloud) • Bibliothèques ◦ Gradle Tooling ◦ Moshi ◦ OkHttp ◦ Ktor/NanoHTTPd ◦ … ➡ Exemple d’application à Dailymotion
  8. Clikt 16 • Interpreteur ligne de commande • 100% Kotlin,

    bientot multiplatforme • Parse les arguments • Il existe aussi kotlinx.cli (1.3.60)
  9. Arguments 17 • Ils peuvent etre optionels ou non. •

    Entier, flottant ou chaine de caractere. • Avoir un nom ou pas. • Dans quel ordre ? ◦ release rollout=0.5 changelog=whatsNew app-release.aab ◦ release app-release.aab rollout=0.5 app
  10. Clickt: Hello world 18 • Un programme qui dit “Bonjour”

    un certain nombre de fois. • 2 parametres: ◦ Count ⬦ Entier (facultatif, 1 par défaut) ◦ Name ⬦ Chaine de caractères à rentrer par l’utilisateur (obligatoire)
  11. 19 class Hello : CliktCommand() { val count: Int by

    option(help = "Nombre de fois") .int().default(1) val name: String by option(help = "Votre nom") .prompt("Qui êtes vous?") override fun run() { for (i in 1..count) { echo("Hello $name!") } } } fun main(args: Array<String>) = Hello().main(args) Hello world
  12. 20 $ ./hello --help Usage: hello [OPTIONS] Options: --count INT

    Nombre de fois --name TEXT Votre nom -h, --help Show this message and exit $ ./hello --count=3 Votre nom: KotlinEverywhere Hello KotlinEverywhere! Hello KotlinEverywhere! Hello KotlinEverywhere! $ ./hello --whoops Usage: hello [OPTIONS] Error: no such option: "--whoops". Hello world
  13. Plugin gradle “application” 21 • apply plugin: 'application' • Crée

    un jar • Spécifie la classe ‘Main’ • Génère des scripts de démarrage
  14. 22 plugins { application kotlin("jvm") version "1.3.31" } application {

    mainClassName = "com.dailymotion.kinta.MainKt" } # you can also create a fat jar if needed tasks.withType<Jar> { manifest { attributes("Main-Class" to "com.dailymotion.kinta.MainKt") } from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }) } Plugin Application
  15. Chez dailymotion: Kinta 23 • Le couteau suisse en ligne

    de commande • 26 commandes pour la release, le test, la publication, la maintenance, etc.. • S’execute sur la machine de dev et/ou sur la ci
  16. Construction d’une version: buildTag 24 • buildTag flavorType=alpha buildType=debug 14500

    ◦ Génère un aab signé + universal apk + alphas ◦ Exécute tous les test/vérifications ◦ Envoie sur appcenter
  17. 25 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 flavorType by option(help = "The flavor to build. Will build all flavor if not specified.") .choice("alpha", "playStore") val buildType by option(help = "The buildType to build. Will build all buildTypes if not specified.") .choice("debug", "release") override fun run() { TagBuild(Gradle(), tag, flavorType, buildType) .execute() } } fun main(args: Array<String>) { BuildTag().main(args) } La commande BuildTag
  18. 26 Bibliothèque: Gradle Tooling API • Execution de taches gradle

    • Redirection de l’entrée standard • Récupération des résultats de chaque tache et de ses dépendances
  19. 27 class Gradle { fun executeTasks(vararg tasks: String) { val

    gradleConnector = GradleConnector.newConnector().forProjectDirectory(File("." )) val connection = gradleConnector.connect() val result = connection.newBuild().forTasks(*tasks) .withArguments("--stacktrace") .run() connection.close() } } Bibliothèque: Gradle Tooling API
  20. 28 Bibliothèque: OkHttp • Le client http par défaut sur

    Android • Développé par square • 100% kotlin depuis la version 4 • API complète • Bientôt multiplateforme ?
  21. 29 //build.gradle implementation("com.squareup.okhttp3:okhttp:4.2.1") //AppCenter.kt val request = Request.Builder() .header("Content-Type", "application/json")

    .header("Accept", "application/json") .header("X-API-Token", appCenterToken) .post(RequestBody.create(null, ByteArray(0))) .url("https://api.appcenter.ms/v0.1/apps/” + ”Dailymotion-Organization/$appId/release_uploa ds") .build() val response = okHttpClient.newCall(request).execute() Bibliothèque: OkHttp
  22. 30 Bibliothèque: Moshi • Un parser json conçu pour kotlin

    • Développé par square • Annotation processor pour génération des parsers • Ou reflexion
  23. 31 //build.gradle implementation("com.squareup.moshi:moshi:1.8.0") //AppCenter.kt @JsonClass(generateAdapter = true) class ReleaseData(val release_url:

    String) val moshi = Moshi.Builder().build() val releaseData = moshi.adapter(ReleaseData::class.java).fromJson(response) // Also without annotation processing val releaseData = moshi.adapter(Any::class.java).fromJson(response) (releaseData as? Map<String, Any>) Bibliothèque: Moshi
  24. • Création des artifacts avec Gradle Tooling • Envoi sur

    AppCenter avec OkHttp • Récupération du lien dans la réponse avec Moshi • Envoi du lien sur slack avec OkHttp 33 Au final
  25. 34 martin@n550~/git/kinta$ ./bin/buildTag -h Usage: buildTag [OPTIONS] tag Makes verifications,

    builds a aab/apk and uploads to appcenter. Options: --flavor-type [demo|full] Filter the build targets on a particular flavor type --build-type [debug|release] Filter the build targets on a particular build type -h, --help Show this message and exit Arguments: tag The tag to process martin@n550~/git/kinta$ ./bin/buildTag --flavor-type=demo 14500 Compiling version 14500… Running lint… Running unit tests… Running connected tests… Generating universal apk… Uploading… Done. Execution
  26. Récuperation des traductions: txPR 35 • txPR ◦ Récupère les

    traductions dans transifex ◦ Ouvre une pull request avec les nouvelles traductions
  27. 36 class TxPR: CliktCommand(name = "txPR", help = "Pulls the

    latest translations from transifex and open a Pull Request") { override fun run() { createTransifexPR() } } fun main(args: Array<String>) { TxPR().main(args) } Commande txPR
  28. Authentification Github 37 • Authentification du developpeur ◦ Kinta est

    un client Oauth • Oauth ◦ https://github.com/login/oauth/authorize ◦ Login de l’utilisateur ◦ Redirection: https://localhost:666/callback?code=$code ◦ Echange du code contre un token • Ouverture d’un Navigateur ◦ xdg-open/open • WebServer: NanoHttpD/Ktor
  29. 40 private fun acquireOauthToken(): String { val url = "https://github.com/login/oauth/authorize?client_id=XYZ&scope=repo&state=$state"

    var token: String? = null Log.d("acquiring oauth token") val server = object : NanoHTTPD(666) { override fun serve(session: IHTTPSession): Response { val code = session.parms["code"] val token = exchangeCode(code, state) synchronized(lock) { token = json.getString("access_token") lock.notify() } return newFixedLengthResponse("yay, you've been authorized !") } } server.start() openBrowser(url) synchronized(lock) { while (token == null) { lock.wait(1000) } } server.stop() return token!! } Bibliothèque: NanoHTTPD
  30. Ouvrir une Pull Request en GraphQL 41 • GraphQL ◦

    Language fortement typé de description d’API ◦ Le client requete uniquement les données dont il a besoin • Github GraphQL api ◦ https://developer.github.com/v4/explorer/
  31. 42 Bibliothèque: Apollo-android • Client GrapqhQL • Fonctionne aussi sans

    Android • Plugin gradle • Génère les modèles automatiquement • Vérification du type à la compilation
  32. 43 # src/main/graphql/com/dailymotion/kinta/github.graphql mutation($input: CreatePullRequestInput!) { createPullRequest(input: $input) { pullRequest

    { number } } } // src/main/kotlin/com/dailymotion/TxPR.kt // Apollo-Android will generate the models automatically val input = CreatePullRequestInput( repositoryId = "MDEwOlJlcG9zaXRvcnkyMTI4NzY5NjA=", baseRefName = "master", headRefName = "feature", title = "Pull request automatique!" ) val response = apolloClient.mutate(CreatePullRequestMutation(input)).toDeferred().aw ait() val prNumber = response.data().pullRequest.number Bibliothèque: Apollo-android
  33. • Récupération des traductions transifex avec OkHttp • Commit des

    nouvelles traductions avec Jgit • Authentification Oauth sur github • Création de la pull request avec Apollo-android 44 Au final
  34. 45 class BuildTag : CliktCommand(name = "buildTag", help = "Makes

    verifications, builds a aab/apk and uploads to appcenter.") { [skip] } class TxPR: CliktCommand(name = "txPR", help = "Pulls the latest translations from transifex and open a Pull Request") { [skip] } class MainCommand: CliktCommand(name = "kinta") { override fun run() { } } fun main(args: Array<String>) { MainCommand() .subcommands(BuildTag(), TxPR()) .main(args) } Sous-commandes
  35. 46 $ kinta Usage: kinta [OPTIONS] COMMAND [ARGS]... Options: -h,

    --help Show this message and exit Commands: BuildTag txPR $ kinta buildTag Usage: kinta buildTag [OPTIONS] tag Error: Missing argument "tag". Sous-commandes
  36. Chez dailymotion 48 • startWork • PR • txPull •

    txPush • txPR • nightly • branch • hotfix • uploadWhatsNew • uploadListing • uploadScreenshots • generateScreenshots • trigger • buildPR • buildTag • beta • release • cleanLocal • cleanRemote ➡ S’execute aussi sur la CI
  37. Kscript 50 • Compilation à la volée de scripts kotlin

    • ~scripts bash ou perl ◦ kscript hello.kts • Meme sans fichier: ◦ kscript 'println("hello world")' • Support de IntelliJ • Support des dépendances
  38. 51 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 Installation
  39. 53 martin@n550:~/dev$ chmod +x hello.kts martin@n550:~/dev$ ./hello.kts Hello from Kotlin!

    Shebang • #!/usr/bin/env kscript • La première ligne permet de spécifier l’interpreteur • Rend le fichier executable
  40. 56 Example: Ktsify • Migration groovy ➡ Kotlin DSL •

    Process simple mais répétitif: ◦ build.gradle ➡ build.gradle.kts ◦ ‘ ➡ “ ◦ implementation “dep” ➡ implementation(“dep”) ◦ apply plugin: plugin ➡ apply(plugin = plugin) ◦ Etc… • 15 modules • Automatisation avec des regex • Pas de mauvais Ctrl-C Ctrl-V!
  41. 57 Exemples d’utilisation • Envoi des videos d’Android Makers •

    Utilisation de l’api Youtube • Automatisation de la description et des metadonnées • OkHttp • Moshi
  42. Clikt vs KScript 58 • Clikt ◦ Un peu de

    mise en place ◦ Projets complexes ◦ Gradle magic ✨ • KScript ◦ Installation rapide ◦ Pas de generation de code ⬦ Pas de apollo ni moshi-codegen
  43. Servlet 60 • API pour serveur web • Peut tourner

    en local ou dans le cloud • Se distribue comme fichier WAR • Permet de déployer du code dans le cloud facilement • Google AppEngine supporte les servlets
  44. 61 API Servlet class MainServlet : HttpServlet() { override fun

    service(req: HttpServletRequest, resp: HttpServletResponse) { System.out.println("pathInfo=${req.pathInfo}") resp.status = 200 resp.writer.write("Hello World.") } }
  45. 62 apply plugin: 'com.google.cloud.tools.appengine-standard' apply plugin: 'war' appengine { deploy

    { projectId = 'your-project-id' stopPreviousVersion = true promote = true } } AppEngine Plugin
  46. 63 <!--WEB-INF/web.xml--> <?xml version="1.0" encoding="utf-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee

    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <servlet> <servlet-name>main</servlet-name> <servlet-class>net.mbonnin.appengine.MainServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>main</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app> web.xml
  47. Executer le serveur 64 • Créér le projet AppEngine sur

    la console(!) • Executer localement ◦ ./gradlew appengineRun • Déployer sur appengine ◦ ./gradlew appengineDeploy
  48. Example: monitoring des avis sur l’app 65 • Cron executé

    toutes les 15 minutes sur l’url /cron • Récupération des reviews avec les API Google/Apple • Stack habituelle: moshi, kotlin, etc... • Traduction avec l’API Google Translate • Envoi sur slack
  49. Conclusion 67 • Les 3 outils sont complémentaires • IDE

    complet • Cli, Script, Cloud, on peut tout faire en kotlin ◦ Meme des sites web!
  50. Credits • Template from SlidesCarnival • Spider from Vecteezy •

    Cat dream by Damian R • Keyboard cat by Cassandra Leigh Gotto 70