Problème
Vous développez un plugin Maven qui doit valider des trucs sur vos projets. Dans certains cas le plugin doit planter le build, dans d'autres cas, le plugin ne fait pas planter le build.
Ce plugin sert au premier cas, car la présence d'erreur arrête le build, dans ce cas, une erreur souhaitée lors d'un test ne fait plus planter le build :
<plugin>
<groupId>com.soebes.itf.jupiter.extension</groupId>
<artifactId>itf-maven-plugin</artifactId>
<version>0.13.1</version>
</plugin>
Merci à @Philou pour le lien (ça faisait longtemps) !
Un plugin Maven qui ne rebuild qu'en cas de changement.
Un outil permettant de savoir si votre code utilise des API obsolètes ou dépréciées en Java.
Le chemin est affecté à la propriété ${maven.multiModuleProjectDirectory}
à la condition qu'à côté du pom.xml
racine se trouve un dossier .mvn/
même s'il est vide !
Maven 4 s'approche tout doucement, boosté par l'arrivée bienvenue de Maven Daemon (dont la 1.0.0-m1 se base sur la alpha-3 de Maven 4).
Bref, le comportement de Maven change un peu sur les projets multimodules. @Animal & @Kysofer je vous recommande de lire cela à tête reposée.
Attention, Java 6 n'étant plus supportée les dépendances kotlin-stdlib-jdk7 et kotlin-stdlib-jdk8 n'existent plus (puisque c'est Java 8 la version minimale requise pour Kotlin à présent).
Il faut donc les remplacer par kotlin-stdlib. Une migration simple qui va poser de nombreux problèmes à certains, sans aucun doute.
Résumé
IntelliJ instrumente du code juste pour lui lorsqu'il exécute des TU.
Ce faisant, l'annotation @NotNull
apposée sur un paramètre produit un NullPointerException
lorsque c'est Java ou Maven (via Surefire lors des TU) qui exécute la fonction en lui passant un null
mais elle produit un IllegalArgumentException
lorsque c'est IntelliJ qui exécute la même fonction avec le même null
passé en paramètre.
Solution
Désactiver le paramètre "Add runtime assertions for not-null-annotated methods and parameters" dans le menu Setting > Build > Compiler
Ceci se fait en deux étapes :
1) Déclarer la property dans le plugin Surefire comme suit
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>LAST_SUREFIRE_VERSION</version>
<configuration>
<systemProperties>
<property>
<name>name-the-property-will-have-in-test</name>
<value>${my-property}</value>
</property>
</systemProperties>
</configuration>
</plugin>
2) La récupérer en tant que propriété système dans les TU
val property = System.getProperty("name-the-property-will-have-in-test")
Le plugin OWASP permettant la détection de vulnérabilités connues parmi les dépendances d'un projet Java/Kotlin/Scala/Groovy et c'est super utile !
Sauf qu'en entreprise, il est souvent impossible de récupérer la base de données des CVE du plugin à cause des proxy...
L'astuce consiste donc à installer une BDD des vulnérabilités sur le réseau et de modifier la configuration du plugin Maven de votre projet pour qu'il pointe vers celle-ci plutôt que vers celle en local (ie. H2DB).
La manipulation est la suivante :
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>dummy</groupId>
<artifactId>dummy</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>6.5.3</version>
<dependencies>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>1.4.6</version>
</dependency>
</dependencies>
<configuration>
<databaseDriverName>org.mariadb.jdbc.Driver</databaseDriverName>
<connectionString>jdbc:mariadb://my.cvedb.host/cvedb</connectionString>
<databaseUser>depscan</databaseUser>
<databasePassword>NotReallyMyDbPassword</databasePassword>
</configuration>
<executions>
<execution>
<goals>
<goal>update-only</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Attention à bien ajouter la dépendance dependency-check-core au runtime du build (pas au runtime de l'appli) si vous êtes à la version 6 ou plus du plugin.
Je me le note pour plus tard, comment ne copier que les dépendances en scope "compile" via le maven-dependency-plugin :
mvn dependency:copy-dependencies -DincludeScope=compile
Depuis Java 11, certains packages comme javax.xml.bind
ne font plus partie du JDK, ce faisant il faut les tirer en tant que dépendances soi-même.
Pour faire simple toutes les dépendances du type javax sont remplacées par des dépendances du type Jakarta, par exemple :
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
devient
<!-- Pour JEE 8 -->
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>2.3.3</version>
</dependency>
<!-- Pour JEE 9 -->
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>3.0.0</version>
</dependency>
Excellente idée ! Merci à @Philou pour le lien.
Maven crash beaucoup de logs lorsqu'il build, ce qui peut le ralentir beaucoup. Pour ne garder que les warnings et erreur, il suffit de créer une fonction Bash qui remplace Maven et qui contient la ligne suivante :
mvn clean install -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn
Remarque : d'intuition, je dirai que cela doit aussi marcher en mettant à jour la variable MAVEN_OPTS
La polygot maven extension permet de produit des pom en Yaml (j'avais fait un tuto ici en son temps) et maintenant en Kotlin. Vous savez que j'adore Kotlin et pourtant je vois dans cette opportunité une immondice sans nom pour ne pas dire la plus grande des saloperies !
Pourquoi ?
Parce que le DSL Kotlin permet de coder dans le pom dont le but est qu'il soit pourtant 100 % déclaratif. Car du code dans un pom n'aura jamais de TU, a un probabilité très haute de ne pas être portable et surtout, chaque évolution du code ne profitera ni à la communauté (car pas de plugin) et deviendra un risque pour l'entreprise qui va accentuer l'effet "copier-coller".
Non vraiment, si c'était pour faire de Maven la même horreur de maintenance que l'est Gradle, mais les performances en moins, je pense qu'il valait mieux s'abstenir.
Voici l'exemple qui m'a fait vomir deux fois (vous saurez apprécier le code à copier-coller dans chaque projet d'entreprise en début de pom n'est-ce pas ?) :
import java.io.File
// define a comparable data class to simplify handling versions
data class Version(val major: Int, val minor: Int, val patch: Int) : Comparable<Version> {
override fun compareTo(other: Version): Int =
compareValuesBy(this, other, Version::major, Version::minor, Version::patch)
override fun toString(): String = "$major.$minor.$patch"
}
// define a function to execute the git command and return its standard output
fun git(vararg args: String): String {
// use the basedir of the project as the command's working dir if it contains a '.git' subdir
// otherwise use the current working directory of this script if it contains a '.git' subdir
// if both conditions are false the result will be null; the git command will probably fail
val workingDir = basedir.takeIf { it.resolve(".git").exists() }
?: File(".").takeIf { it.resolve(".git").exists() }
// run the git command with the provided arguments
val process = ProcessBuilder()
.directory(workingDir)
.redirectErrorStream(true)
.command(listOf("git") + args)
.start()
// read the standard output completely as a String
val output = process.inputStream.bufferedReader().readText().trim()
// return the output if the exit value is 0 or throw an exception otherwise
if (process.waitFor() == 0) return output
else throw IllegalStateException(output)
}
val gitVersions by lazy {
// run the `git tag` command
git("tag")
// the returned list of tags is separated by newlines
.split("\n")
// filter out only tags that are versions (such as 1.231.15)
.filter { it.matches(Regex("[0-9]+\\.[0-9]+\\.[0-9]")) }
// the separate parts of each version are separated by dots,
// also parse each part as an int
.map { it.split('.').map { it.toInt() } }
// map each triple of numbers to an instance of the `Version` class
.map { (major, minor, patch) -> Version(major, minor, patch) }
// sort the list of versions
.sorted()
}
// the last release is always the tag with the highest version number
val lastRelease by lazy {
gitVersions.max()
}
// the next version is determined based on the git commit log
val nextVersion by lazy {
// use the lsat released version as the base
val baseVersion = lastRelease
// if there are no releases yet, we use the version 0.0.1
if (baseVersion == null) Version(0, 0, 1)
else {
// split the base version in each separate part using destructuring
val (major, minor, patch) = baseVersion
// create a separator to split each log message on (log messages are multiline)
val separator = "-".repeat(5) + "commit" + "-".repeat(5)
// get all log messages from the last release tag until the current HEAD
// for each commit the separator is printed + the full commit message
val logMessages = git("log", "--pretty=format:$separator%n%B", "$baseVersion..HEAD")
// split the output on each separator generated earlier
.split(separator)
// trim each message, removing excess newlines
.map { it.trim() }
// only keep non-empty messages
.filter { it.isNotEmpty() }
when {
// increment the major and reset the minor + patch if any
// message contains the words 'BREAKING CHANGE'
logMessages.any { it.contains("BREAKING CHANGE") } -> Version(major + 1, 0, 0)
// increment the minor and reset the patch if any message starts with 'feat'
logMessages.any { it.startsWith("feat") } -> Version(major, minor + 1, 0)
// increment the patch in all other cases
else -> Version(major, minor, patch + 1)
}
}
}
project {
// use the next version calculated above when defining our project id
id("nl.craftsmen.blog.kotlin:kotlin-rest-service:${nextVersion}")
dependencies {
compile("org.glassfish.jersey.inject:jersey-hk2:2.29")
compile("org.glassfish.jersey.containers:jersey-container-netty-http:2.29")
compile("org.glassfish.jersey.media:jersey-media-json-jackson:2.29")
runtime("ch.qos.logback:logback-classic:1.2.3")
}
properties {
"project.build.sourceEncoding" to "UTF-8"
"maven.compiler.source" to "11"
"maven.compiler.target" to "11"
}
distributionManagement {
repository("local") {
url(basedir.resolve("repo").toURI().toASCIIString())
}
}
build {
execute(id = "release", phase = "deploy") {
// create a new tag using the next version calculated above
git("tag", "-am", "Release $nextVersion", "$nextVersion")
// print some output
println("Tagged current HEAD as $nextVersion")
}
}
}
Bref, à patcher ASAP.
Remarque : je plains tout ceux qui sont passés sous Maven Wrapper et qui devront pour chaque projet et chaque branche de ces projets commiter la montée de version...
Encore une très bonne nouvelle pour Kotlin ! Le fait d'avoir une fondation à part, disposant d'un modèle économique clair et qui assure au langage sa survit et mieux encore son évolution est ce qu'il fallait faire.
Le parfait contre-exemple que je pourrais donner est ce qu'a fait la fondation Apache avec Maven dans le sens où Apache étant "anti-argent", la fondation a toujours refusé que des contributeurs majeurs de Maven mettent en place un modèle économique de financement de leurs contributions. 15 ans plus tard, le projet n'a qu'une mise à jour tous les 18 mois... Heureusement, des forks sont apparus comme Maven Daemon mais cela fragmente le marché.
Bref, je suis très contente pour Kotlin.
Incroyable, dès que j'ai un problème @Philou arrive avec une solution. Ici Maven Daemon qui propose ce que propose Gradle mais avec Maven (en rapport à mon post précédent).
Je teste ça dans la journée :D
Ça doit faire 4-5 ans que je me remets régulièrement à Gradle dans l'espoir de trouver une solution à un problème pourtant trivial : comment, avec Gradle, mutualiser un processus de build qui doit s'appliquer sur plusieurs projets qui n'ont rien à voir ?
Gradle confond la notion de projets et de modules, donc je dois clarifier ces deux définitions pour la suite :
- Un module, c'est ce qui va produire un jar au sein de votre projet Git et votre projet est composé de modules.
- Donc yn projet, c'est un répo Git et qui en général va produire une application et les modules la composent.
Dans toutes les docs de Gradle, un projet est un module, donc pas besoin d'héritage puisque le build.gradle à la racine va injecter sa conf dans les build.gradle des modules positionnés dans les sous-répertoires.
Sauf que ce genre de bidouilles, ça marche bien dans une startup ne concevant qu'un seul produit, mais imaginez que vous deviez proposer un socle de build gérant Java, Kotlin, Groovy et Scala et qui soit utilisé par 2000 personnes, sur plus de 100 applications et 200 libs, comment faites-vous avec Gradle ?
Permettez-moi de détailler, le processus de build standard inclut :
- La compilation en mode dev (sans optim) et en mode prod (avec optim).
- Les analyses de sécurité.
- Les analyses de code.
- La signature des jars.
- La production des jars des docs et des sources.
- Les analyses des dépendances.
- Etc.
Tout ce processus qui est agnostique du code lui-même DOIT être factorisé pour devenir un standard de l'entreprise et se pose en tant que quality-gate/build-breaker en cas de manquement.
Avec Maven facile, il suffit que le pom parent de votre projet hérite du pom sachant faire tout ça, mais comment faire avec Gradle ? Ce build.gradle "commun" ne peut pas être hérité car Gradle ne permet pas l'héritage et il ne peut pas lancer lui-même le build des 300 répos dont les produits ont des cycles de vie totalement indépendants.
C'est une vraie question car j'aimerai vraiment abandonner Maven (puisqu'il est beaucoup trop lent) au profit de Gradle, mais sans la capacité de mutualiser le processus de build entre deux projets indépendants, cela fait de Gradle un outil pour "amateurs", or sa percée sur le marché me pousse à croire qu'il doit bien exister une astuce mais que je n'ai encore jamais vue. Bref si quelqu'un a l'info...
Comme j'oublie toujours la commande, je me la note ici :
mvn versions:set -DnewVersion=1.2.3
Pour citer l'article.
mvn versions:display-dependency-updates
Scans a project’s dependencies and produces a report of those dependencies which have newer versions available.mvn versions:display-plugin-updates
Scans a project’s plugins and produces a report of those plugins which have newer versions available, taking care of Maven version prerequisites.mvn versions:display-property-updates
Scans a project and produces a report of those properties which are used to control artifact versions and which properties have newer versions available.