# Ma CI avec <br/> SBT et GitLab (-CI) Kévin Rauscher • [`@tomahna`](https://twitter.com/tomahna) --- ## Agenda 1. Qu'attend-on d'une CI <!-- .element: class="fragment" --> 2. GitLab & GitLab CI <!-- .element: class="fragment" --> 3. SBT et ses plugins <!-- .element: class="fragment" --> --- ## Qu'attend-on d'une CI -- #### Simple #### Notifications claires <!-- .element: class="fragment" data-fragment-index="1" --> ![](/blog/slide/sbt-gitlab/gitlab_pipeline_failed.png) <!-- .element: class="fragment" data-fragment-index="1"--> #### Rapide & Scalable <!-- .element: class="fragment" data-fragment-index="2"--> ![](/blog/slide/sbt-gitlab/gitlab_pipeline_success.png) <!-- .element: class="fragment" data-fragment-index="2"--> #### Polyvalente <!-- .element: class="fragment" data-fragment-index="3"--> --- ## Gitlab ![](/blog/slide/sbt-gitlab/gitlab_logo.svg) -- > The leading integrated product for the entire software development lifecycle. -- ![](/blog/slide/sbt-gitlab/gitlab.jpg) -- ## Gitlab-CI -- ### Tache ![](/blog/slide/sbt-gitlab/tache.gif) -- ### Tache ```yaml hello: # <-- création de la tache "test" script: # <-- corps de la tache - echo "Hello PSUG" ``` -- ### Stage ![](/blog/slide/sbt-gitlab/stage.jpg) -- ### Stage ```yaml stages: recette deploy-recette: stage: recette script: - export ENV=recette - ./deploy.sh ``` -- ### Pipeline Définis dans un fichier .gitlab-ci.yml ![](/blog/slide/sbt-gitlab/gitlab_pipeline.png) -- ### Pipeline - Les stages s'exécutent séquentiellement <!-- .element: class="fragment" --> - Les taches s'exécutent en parallel <!-- .element: class="fragment" --> -- ## Et alors ça marche ? -- #### Simple Yaml + Shell = Peu de subtilités <!-- .element: class="fragment" --> #### Notifications claires <!-- .element: class="fragment" --> Bien intégré avec Gitlab <!-- .element: class="fragment" --> #### Rapide et Scalable <!-- .element: class="fragment" --> oui via installation de runners sur les machines de dev <!-- .element: class="fragment" --> #### Polyvalente <!-- .element: class="fragment" --> Bonne intégration avec docker <!-- .element: class="fragment" --> -- ### MAIS GitLab-CI => GitLab <!-- .element: class="fragment" --> -- ## Tests -- ### Tests unitaires ```yaml # .gitlab-ci.yml test: script: - sbt test ``` <!-- .element: class="fragment" --> ![](/blog/slide/sbt-gitlab/gitlab_pipeline_1.png) <!-- .element: class="fragment" --> -- ### Et la sbt télécharge les dépendances ![](/blog/slide/sbt-gitlab/dead_waiting.jpg) <!-- .element: class="fragment" --> -- ### Coursier ```scala addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-RC13") ``` <!-- .element: class="fragment" --> ![](/blog/slide/sbt-gitlab/coursier.gif) <!-- .element: class="fragment" --> -- ### Code Format -- #### Exemple ```scala object FormatMe { List(number) match { case head :: Nil if head % 2 == 0 => "number is even" case head :: Nil => "number is not even" case Nil => "List is empty" } function(arg1, arg2(arg3(arg4, arg5, "arg6") , arg7 + arg8), arg9.select(1, 2, 3, 4, 5, 6)) } ``` <!-- .element: class="fragment" --> -- ### ScalaRiform (Unopinionated) ```scala object FormatMe { List(number) match { case head :: Nil if head % 2 == 0 => "number is even" case head :: Nil => "number is not even" case Nil => "List is empty" } function(arg1, arg2(arg3(arg4, arg5, "arg6") , arg7 + arg8), arg9.select(1, 2, 3, 4, 5, 6)) } ``` <!-- .element: class="fragment" --> -- ### ScalaFmt (Opinionated) ```scala object FormatMe { List(number) match { case head :: Nil if head % 2 == 0 => "number is even" case head :: Nil => "number is not even" case Nil => "List is empty" } function(arg1, arg2(arg3(arg4, arg5, "arg6"), arg7 + arg8), arg9.select(1, 2, 3, 4, 5, 6)) } ``` <!-- .element: class="fragment" --> #### Besoin d'un formatage spécifique <=> besoin de refactoring <!-- .element: class="fragment" --> -- ### ScalaFmt & Gitlab ```scala // plugins.sbt addSbtPlugin("com.lucidchart" % "sbt-scalafmt-coursier" % "1.10") ``` <!-- .element: class="fragment" --> ```yaml # .gitlab-ci.yml format: script: - sbt scalafmt::test ``` <!-- .element: class="fragment" --> ![](/blog/slide/sbt-gitlab/gitlab_pipeline_2.png) <!-- .element: class="fragment" --> -- ### Version des dépendances ```scala // plugins.sbt addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.3.3") ``` <!-- .element: class="fragment" --> ```yaml # .gitlab-ci.yml update: script: - sbt "set dependencyUpdatesFailBuild := true" dependencyUpdates allow_failure: true ``` <!-- .element: class="fragment" --> --- ## Code Style et Qualité -- ### Scala Compiler ```scala scalacOptions ++= Seq( "-Yrangepos", "-Xlint", "-deprecation", "-feature", "-unchecked", "-Yno-adapted-args", "-Ywarn-dead-code", "-Ywarn-numeric-widen", "-Ywarn-value-discard", "-Xfuture", //"-Xfatal-warnings", si possible "-Ywarn-unused", "-Ywarn-unused-import", "-Ydelambdafy:method", ) ``` <!-- .element: class="fragment" --> -- ### ScapeGoat 116 check implementés <!-- .element: class="fragment" --> ```scala addSbtPlugin("com.sksamuel.scapegoat" %% "sbt-scapegoat" % "1.0.4") ``` <!-- .element: class="fragment" --> #### Unused Method Parameter <!-- .element: class="fragment" --> ```scala def func(a:Int, b:Int) = { val c = a+1; c } ``` <!-- .element: class="fragment" --> #### Filter Dot Size <!-- .element: class="fragment" --> ```scala val a = Seq(1,2,3); val b = a.filter{ _ > 1 }.length ``` <!-- .element: class="fragment" --> -- ### WartRemover ```scala //plugins.sbt addSbtPlugin("org.wartremover" % "sbt-wartremover" % "2.2.1") ``` <!-- .element: class="fragment" --> ```scala //build.sbt wartremoverErrors ++= Warts.unsafe ``` <!-- .element: class="fragment" --> ``` [error] /tmp/src/main/scala/test/Foo.scala:2: [wartremover:Null] null is disabled [error] val foo = null [error] ^ [error] one error found [error] (compile:compileIncremental) Compilation failed ``` <!-- .element: class="fragment" --> -- ### Other plugins * ScalaStyle * ScalaLinter --- ## Versioning -- ### SBT Release ```scala releaseProcess := Seq[ReleaseStep]( checkSnapshotDependencies, inquireVersions, runClean, runTest, setReleaseVersion, commitReleaseVersion, tagRelease, publishArtifacts, setNextVersion, commitNextVersion, pushChanges ) ``` -- ### SBT Release ```scala releaseProcess := Seq[ReleaseStep]( checkSnapshotDependencies, inquireVersions, // <--- Etape manuelle runClean, runTest, setReleaseVersion, commitReleaseVersion, tagRelease, publishArtifacts, setNextVersion, commitNextVersion, pushChanges ) ``` -- ### SBT Release ```scala releaseProcess := Seq[ReleaseStep]( checkSnapshotDependencies, inquireVersions, runClean, runTest, setReleaseVersion, commitReleaseVersion, tagRelease, publishArtifacts, setNextVersion, commitNextVersion, pushChanges // <--- Et si ça échoue ? ) ``` -- ### Mais au fait c'est quoi une version ? -- ### Version Un point dans le temps * Arbitraire <!-- .element: class="fragment" --> * Unique <!-- .element: class="fragment" --> * Immutable (idéalement) <!-- .element: class="fragment" --> -- ### SBT Dynver ```scala addSbtPlugin("com.dwijnand" % "sbt-dynver" % "2.0.0") ``` ![](/blog/slide/sbt-gitlab/dynver.png) --- ## Packaging et publication -- ### SBT Native Packager * Universal (zip, tgz, ...) * Debian * Rpm * Docker * Windows (pour les plus téméraires) -- ### Packaging avec Gitlab ```yaml stages: - test - package format: ... test: ... debian: stage: package only: master # N'execute la tache que sur master script: - sbt debian:packageBin artifacts: # Permet de télécharger les artefactes paths: - target/*.deb ``` -- ![](/blog/slide/sbt-gitlab/gitlab_pipeline_3.png) -- ### Publication * Sonatype * Nexus * S3 (via sbt-s3-resolver/fm-sbt-s3-resolver) --- ## Conclusion #### SBT Complexe mais complet <!-- .element: class="fragment" --> #### Autobootstrap <!-- .element: class="fragment" --> #### Self contained repos <!-- .element: class="fragment" --> --- ## Merci ! Des questions ?