Slide 1

Slide 1 text

Automatisez vos workflows en Kotlin

Slide 2

Slide 2 text

@MartinBonnin 2

Slide 3

Slide 3 text

Dailymotion 3 ● Sprints 2 semaines ● Release à la fin de chaque sprint ● Feature flags ● Product Owner, Designers ● Jira partout!

Slide 4

Slide 4 text

Environment 4 Jira Github Slack Bitrise Transifex Play console Appcenter

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Tout Automatiser !! 6

Slide 7

Slide 7 text

Automation: https://xkcd.com/1319/ Aussi: https://xkcd.com/612/ 7

Slide 8

Slide 8 text

Avantages 8 ● Reproductibilité ● Fiabilité ● Auto-documentation ● Bon pour le moral ● Gain de temps (des fois)

Slide 9

Slide 9 text

Solutions existantes 9 ● Hub ○ go ● Play-publisher ○ gradle plugin ● Client transifex ○ python ● Fastlane ○ Ruby ● Bitrise cli ○ go

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

Tout automatiser en Kotlin 12

Slide 13

Slide 13 text

Kotlin 13 ● Pas de changement de contexte ● Un IDE au top (la plupart du temps!) ● Un écosystème riche

Slide 14

Slide 14 text

Boite à outils 14 ● Outils ○ Clikt (cli) ○ Kscript (~bash) ○ Servlets (cloud) ● Bibliothèques ○ Gradle Tooling ○ Moshi ○ OkHttp ○ Ktor/NanoHTTPd ○ … ➡ Exemple d’application à Dailymotion

Slide 15

Slide 15 text

Clikt 15

Slide 16

Slide 16 text

Clikt 16 ● Interpreteur ligne de commande ● 100% Kotlin, bientot multiplatforme ● Parse les arguments ● Il existe aussi kotlinx.cli (1.3.60)

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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)

Slide 19

Slide 19 text

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) = Hello().main(args) Hello world

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

Plugin gradle “application” 21 ● apply plugin: 'application' ● Crée un jar ● Spécifie la classe ‘Main’ ● Génère des scripts de démarrage

Slide 22

Slide 22 text

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 { manifest { attributes("Main-Class" to "com.dailymotion.kinta.MainKt") } from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }) } Plugin Application

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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) { BuildTag().main(args) } La commande BuildTag

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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 ?

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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) Bibliothèque: Moshi

Slide 32

Slide 32 text

32 Autres bibliothèques ● Retrofit ● Gson ● Kotlinx.serialization ● Jgit ● ... ● Et aussi ProcessBuilder

Slide 33

Slide 33 text

● 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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

Récuperation des traductions: txPR 35 ● txPR ○ Récupère les traductions dans transifex ○ Ouvre une pull request avec les nouvelles traductions

Slide 36

Slide 36 text

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) { TxPR().main(args) } Commande txPR

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

38 Oauth

Slide 39

Slide 39 text

39 Bibliothèque: NanoHTTPD ● Serveur HTTP léger pour JVM ● Simple ● Remplacer par ktor ?

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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/

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

● 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

Slide 45

Slide 45 text

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) { MainCommand() .subcommands(BuildTag(), TxPR()) .main(args) } Sous-commandes

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

47 martin@n550~/git/kinta$ _KINTA_COMPLETE=bash kinta > ~/.kinta.completion martin@n550~/git/kinta$ source ~/.kinta.completion martin@n550~/git/kinta$ kinta buildTag txPR BONUS: Bash Autocompletion

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

Kscript 49

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

52 Hello World!

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

54 Debugger

Slide 55

Slide 55 text

55 Dépendences @file:DependsOn(“com.squareup. okhttp3:okhttp:4.2.1”)

Slide 56

Slide 56 text

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!

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

Servlets 59

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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.") } }

Slide 62

Slide 62 text

62 apply plugin: 'com.google.cloud.tools.appengine-standard' apply plugin: 'war' appengine { deploy { projectId = 'your-project-id' stopPreviousVersion = true promote = true } } AppEngine Plugin

Slide 63

Slide 63 text

63 main net.mbonnin.appengine.MainServlet main /* web.xml

Slide 64

Slide 64 text

Executer le serveur 64 ● Créér le projet AppEngine sur la console(!) ● Executer localement ○ ./gradlew appengineRun ● Déployer sur appengine ○ ./gradlew appengineDeploy

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

66 Monitoring

Slide 67

Slide 67 text

Conclusion 67 ● Les 3 outils sont complémentaires ● IDE complet ● Cli, Script, Cloud, on peut tout faire en kotlin ○ Meme des sites web!

Slide 68

Slide 68 text

Merci! Questions? @MartinBonnin 68

Slide 69

Slide 69 text

Resources 69 ● https://github.com/ajalt/clikt ● https://github.com/holgerbrandl/kscript ● https://github.com/martinbonnin/ktsify ● https://github.com/paug/video-tools/blob/ma ster/kscripts/am_youtube_tool.kts ● https://github.com/martinbonnin/AppReview sToSlack/blob/master/build.gradle

Slide 70

Slide 70 text

Credits ● Template from SlidesCarnival ● Spider from Vecteezy ● Cat dream by Damian R ● Keyboard cat by Cassandra Leigh Gotto 70

Slide 71

Slide 71 text

DEMO 71