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 !
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")
}
}
}
Imaginez, vous êtes en prestation pour une grosse boite et la cellule d'architecture de votre client active tout un tas de plugins Maven sans comprendre comment fonctionnent ces plugins et ni Maven d'une manière générale.
Vous souhaitez donc bloquer l'héritage de la configuration de ces plugins pour ne laisser que les vôtres. Évidemment vous êtes quand même obligé d'hériter du pom parent provenant de la cellule d'architecture pour récupérer quelques properties bien utiles.
Solution :
1) Vous créez un pom abstrait à la racine de votre projet.
2) Dans ce pom vous ajoutez plugin par plugin (ici avec ANT) la désactivation <inherited>false</inherited>
:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.2</version>
<inherited>false</inherited>
...
</plugin>
</plugins>
</build>
...
</project>
3) Vous créez un second pom abstrait, enfant du premier et duquel héritera tous vos modules.
Et voilà, les modules n'auront plus connaissance des plugins du super-parent.
Edit la conclusion est fausse. Si l'on veut que le module A soit présent au runtime comme au compile-time, il faudra l'ajouter explicitement.
Les <optional>true</optional>
ne sont utiles que si l'on se sert de frameworks comme Spring qui passent leur temps à faire de la détection de chargement au runtime.
Je recherchais un moyen de reproduire avec Maven le comportement du implementation
de Gradle, j'en parlais ici. Pour ceux qui ne connaîtraient pas l'idée est la suivante :
- Un module B tire en tant que dépendance un module A.
- Si un module C tire à son tour le module B en tant que dépendance, et puisque tout est transitif par défaut dans Maven, alors les classes de C pourront importer les classes de A
Ceci est dangereux car rien ne lie explicitement C à A et il devient impossible de supprimer B en tant que dépendance dans le pom de C.
La solution, déclarer le module A en tant que dépendance optionnelle de B de cette manière :
<dependency>
<groupId>com.a</groupId>
<artifactId>module-a</artifactId>
<version>1.0.0</version>
<optional>true</optional>
</dependency>
Si C tire B, alors les classes de A ne seront plus visiblent par C dans le classpath mais elles resteront présentent au runtime.
Mes amis.. à vos pom !
Introduction
Comme vous le savez sûrement, il existe une légende sur internet congrue à l'arrivée de Maven 3 : il est possible d'écrire des pom en Yaml. Eh bien sachez que la chose n'est en rien une légende.
En effet, les versions Maven 3.x et supérieures recherchent - avant de lire le pom.xml - un fichier spécifique dans ./mvn/extensions.xml. Ce fichier va charger des extensions à Maven lui permettant de faire plus de choses.
L'idée est donc de demander à Maven de charger l'extension polyglot-yaml qui va ajouter à Maven la capacité d'interpréter du Yaml.
Mise en oeuvre
- À la racine de votre projet créez le répertoire ./mvn/
- Créez dans ce répertoire le fichier extensions.xml dont le contenu est le suivant :
<?xml version="1.0" encoding="UTF-8"?> <extensions> <extension> <groupId>io.takari.polyglot</groupId> <artifactId>polyglot-yaml</artifactId> <version>0.2.1</version> </extension> </extensions>
- Créez un fichier pom.yml à la racine de votre projet
- A titre d'exemple, voici un modèle de pom parent en Yaml :
modelEncoding: 'UTF-8'
modelVersion: '4.0.0'
groupId: 'com.enterprise.fivestars.fs'
artifactId: 'fs-project'
version: '1.0.0-SNAPSHOT'
packaging: 'pom'
name: '. ${project.artifactId} [${project.version}]'
properties:
## Project encoding
project.encoding: 'UTF-8'
project.build.sourceEncoding: '${project.encoding}'
project.reporting.outputEncoding: '${project.encoding}'
## Maven compiler
maven.compiler.source: '1.8'
maven.compiler.target: '${maven.compiler.source}'
## Kotlin compiler
kotlin.compiler.jvmTarget: '${maven.compiler.source}'
kotlin.source.directory: '${project.basedir}/src/main/kt'
kotlin.test.directory: '${project.basedir}/src/test/kt'
kotlin.version: '1.1.51'
## Loggers
logback.version: '1.1.7'
slf4j.version: '1.7.22'
modules:
- fs-domain
- fs-main
- fs-persistence
dependencyManagement:
dependencies:
## Project dependencies
- { groupId: '${project.groupId}' , artifactId: 'fs-project' , version: '${project.version}' }
- { groupId: '${project.groupId}' , artifactId: 'fs-domain' , version: '${project.version}' }
- { groupId: '${project.groupId}' , artifactId: 'fs-persistence' , version: '${project.version}' }
## Database - Driver & embedded DB
- { groupId: 'com.h2database' , artifactId: 'h2' , version: '1.4.195' }
## Database - Database migration
- { groupId: 'org.flywaydb' , artifactId: 'flyway-core' , version: '4.2.0' }
## Database - Connection Pool
- { groupId: 'com.zaxxer' , artifactId: 'HikariCP' , version: '2.7.2' }
## Database - Persistence framework
- { groupId: 'org.javalite' , artifactId: 'activejdbc' , version: '1.4.13.j7' }
## Dependencies Injection
- { groupId: 'org.codejargon.feather' , artifactId: 'feather' , version: '1.0' }
## Kotlin - JVM & Collection compliance
- { groupId: 'org.jetbrains.kotlin' , artifactId: 'kotlin-stdlib' , version: '${kotlin.version}' }
- { groupId: 'org.jetbrains.kotlin' , artifactId: 'kotlin-stdlib-jre8' , version: '${kotlin.version}' }
## Logger - Logging framework facad
- { groupId: 'org.slf4j' , artifactId: 'slf4j-api' , version: '${slf4j.version}' }
## Logger - Logging framework
- { groupId: 'ch.qos.logback' , artifactId: 'logback-classic' , version: '${logback.version}' }
## Testing dependencies
- { groupId: 'org.assertj' , artifactId: 'assertj-core' , version: '3.8.0' }
- { groupId: 'org.jetbrains.kotlin' , artifactId: 'kotlin-test' , version: '${kotlin.version}' }
- { groupId: 'org.junit.platform' , artifactId: 'junit-platform-runner' , version: '1.0.1' }
- { groupId: 'org.mockito' , artifactId: 'mockito-core' , version: '2.12.0' }
build:
pluginManagement:
plugins:
## Compiler - Java (Prevent its execution because of Kotlin)
- artifactId: 'maven-compiler-plugin'
groupId: 'org.apache.maven.plugins'
version: '3.6.2'
configuration:
compilerArgs: {arg: '-Werror'}
encoding: '${project.build.sourceEncoding}'
fork: true
debug: false
optimize: true
showDeprecation: true
showWarnings: true
source: '${maven.compiler.source}'
target: '${maven.compiler.target}'
executions:
- goals:
id: 'default-compile'
phase: 'none'
- goals:
id: 'default-testCompile'
phase: 'none'
- goals: ['compile']
id: 'java-compile'
phase: 'compile'
- goals: ['testCompile']
id: 'java-test-compile'
phase: 'compile'
## Compiler - Kotlin
- artifactId: 'kotlin-maven-plugin'
groupId: 'org.jetbrains.kotlin'
version: '${kotlin.version}'
configuration:
nowarn: false
jvmTarget: '${kotlin.compiler.jvmTarget}'
executions:
- goals: ['compile']
id: 'compile'
phase: 'compile'
configuration:
sourceDirs: [
'${kotlin.source.directory}'
]
- goals: ['test-compile']
id: 'test-compile'
phase: 'test-compile'
configuration:
sourceDirs: [
'${kotlin.test.directory}'
]
## ActiveJDBC - Enrich entities bytecode
- artifactId: 'activejdbc-instrumentation'
groupId: 'org.javalite'
version: '1.4.13.j7'
executions:
- goals: ['instrument']
id: 'enrich-entities'
phase: 'process-classes'
## Quality - JaCoCo (Code coverage)
- artifactId: 'jacoco-maven-plugin'
groupId: 'org.jacoco'
version: '0.7.9'
executions:
- goals:
id: 'prepare-agent'
inherited: true
- goals:
id: 'report'
inherited: true
phase: 'prepare-package'
Et la même chose avec un pom enfant :
modelEncoding: 'UTF-8'
modelVersion: '4.0.0'
parent:
groupId: 'com.enterprise.fivestars.fs'
artifactId: 'fs-project'
version: '1.0.0-SNAPSHOT'
relativePath: '../pom.yml'
artifactId: 'fs-persistence'
packaging: 'jar'
name: '${project.artifactId}'
dependencies:
## Kotlin - JVM & Collection compliance
- { groupId: 'org.jetbrains.kotlin' , artifactId: 'kotlin-stdlib' , scope: 'compile' }
- { groupId: 'org.jetbrains.kotlin' , artifactId: 'kotlin-stdlib-jre8' , scope: 'compile' }
## Database - Database migration
- { groupId: 'org.flywaydb' , artifactId: 'flyway-core' , scope: 'compile' }
## Database - Driver & embedded DB
- { groupId: 'com.h2database' , artifactId: 'h2' , scope: 'compile' }
## Database - Connection Pool
- { groupId: 'com.zaxxer' , artifactId: 'HikariCP' , scope: 'compile' }
## Logger - Logging framework facade
- { groupId: 'org.slf4j' , artifactId: 'slf4j-api' , scope: 'compile' }
## Logger - Logging framework
- { groupId: 'ch.qos.logback' , artifactId: 'logback-classic' , scope: 'compile' }
## Testing dependencies
- { groupId: 'org.assertj' , artifactId: 'assertj-core' , scope: 'test' }
- { groupId: 'org.jetbrains.kotlin' , artifactId: 'kotlin-test' , scope: 'test' }
- { groupId: 'org.junit.platform' , artifactId: 'junit-platform-runner' , scope: 'test' }
- { groupId: 'org.mockito' , artifactId: 'mockito-core' , scope: 'test' }
build:
plugins:
- { groupId: 'org.jetbrains.kotlin' , artifactId: 'kotlin-maven-plugin' }
- { groupId: 'org.apache.maven.plugins' , artifactId: 'maven-compiler-plugin' }
- { groupId: 'org.jacoco' , artifactId: 'jacoco-maven-plugin' }
Et en prime, vous savez à présent compiler du Kotlin avec Maven.
Créer son propre "packaging type" pour Maven.