# 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" -->
![](assets/gitlab_pipeline_failed.png) <!-- .element: class="fragment" data-fragment-index="1"-->
#### Rapide & Scalable <!-- .element: class="fragment" data-fragment-index="2"-->
![](assets/gitlab_pipeline_success.png) <!-- .element: class="fragment" data-fragment-index="2"-->
#### Polyvalente <!-- .element: class="fragment" data-fragment-index="3"-->
---
## Gitlab
![](assets/gitlab_logo.svg)
--
> The leading integrated product for the entire software development lifecycle.
--
![](assets/gitlab.jpg)
--
## Gitlab-CI
--
### Tache
![](assets/tache.gif)
--
### Tache
```yaml
hello: # <-- création de la tache "test"
script: # <-- corps de la tache
- echo "Hello PSUG"
```
--
### Stage
![](assets/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
![](assets/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" -->
![](assets/gitlab_pipeline_1.png)
<!-- .element: class="fragment" -->
--
### Et la sbt télécharge les dépendances
![](assets/dead_waiting.jpg) <!-- .element: class="fragment" -->
--
### Coursier
```scala
addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-RC13")
```
<!-- .element: class="fragment" -->
![](assets/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" -->
![](assets/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")
```
![](assets/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
```
--
![](assets/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 ?