Maven One Phase by Ci Stage

Contenus

Avez-vous déjà essayé de lancer des phases Maven dans des stages/jobs différents d’une CI ? Si la réponse est oui, vous êtes surement tombé sur un problème un peu embêtant faisant que Maven relance les phases exécutées dans les jobs précédents malgré la présence des fichiers produit par la phase précédente. Voyons comment corriger cela !

Contexte

Imaginons que vous souhaitez lancer les phases de compile, test et package de Maven dans trois jobs séparés. C’est un cas légitime dans le cadre, par exemple d’un monorepo avec plusieurs modules dans différents langages. Dans ce genre de cas et pour plus de lisibilité, j’aime bien avoir un découpage fin de mon pipeline. Un stage Build exécute l’ensemble des stages de build, un stage de test exécute l’ensemble des jobs de test, …. L’optimisation de ce genre de pipeline est un autre sujet qu’on traitera dans un autre article. Avec Maven, lors du stage de test, celui-ci va relancer l’intégration de la phase de compile. Même si vous restaurez l’ensemble du dossier target contenant le résultat de la phase de compile dans le job de test avant de le lancer, Maven ne voudra rien savoir. Cela annule complétement l’intérêt de séparer les phases par jobs.

Pourquoi Maven agit ainsi ?

Le plugin maven-compiler-plugin utilise deux critères pour savoir s’il doit recompiler :

  • La liste des fichiers déjà traités dans une phase précédente (sources compilées lors de la phase de test, tests compilés lors de la phase de package)
  • Le timestamp des sources par rapport au .class produit

Ces deux critères peuvent poser problèmes lors de l’utilisation de maven dans une CI avec des agents docker.

Changement de chemin des fichiers déjà traités

Si votre CI, monte les sources de votre projet dans un conteneur docker avant l’exécution du job, il se peut que le chemin dans le conteneur ne soit pas le même que dans le conteneur de la phase précédente. J’ai eu ce cas dernièrement avec TFS. Le conteneur de Build qui lançait mvn compile avait les sources du projet montées sous le chemin /__w/<n>/s. n étant un numéro attribué par chaque agent de la CI. Le conteneur de Test avait donc un chemin /__w/<n>/s avec un n différent.

Maven qui compare les chemins absolus enregistrés dans target/maven-status/maven-compiler-plugin/**/inputFiles.lst avec les sources présentes dans /target, ne vas pas les trouver et va donc lancer une nouvelle compilation.

Pour résoudre ce problème, il faut modifier les chemins des sources dans les fichiers inputFiles.lst

1
2
3
4
5
6
7
8
# Exemple pour TFS. À adapter si votre CI est concernées par ce problème
COMPILE_STATUS_DIR="$(Build.SourceDirectory)/<modulename>/target/maven-status/maven-compiler-plugin/compile/default-compile"
sed -i "s|/__w/[0-9]*/s/|$(Build.SourceDirectory)/|g" "$COMPILE_STATUS_DIR/inputFiles.lst"

TEST_STATUS_DIR="$(Build.SourceDirectory)/<modulename>/target/maven-status/maven-compiler-plugin/compile/default-testCompile"
if [ -d "$TEST_STATUS_DIR" ]; then
  sed -i "s|/__w/[0-9]*/s/|$(Build.SourceDirectory)/|g" "$TEST_STATUS_DIR/inputFiles.lst"
fi

Changement du timestamp

Le second mécanisme voit Maven recompiler l’ensemble des .class si leur timestamp est plus ancien que les sources. Ceci est le cas lorsque vous restaurez les .class précédemment compilés directement dans le dossier du projet monté dans votre conteneur. Pour résoudre ce problème, il suffit de faire un touch sur les fichiers pour réinitialiser le timestamp des fichiers.

1
2
# Exemple pour TFS. À adapter avec les variables de votre CI.
find $(Build.SourceDirectory)/<modulename>/target -type f -exec touch {} +

Conclusion

Ce problème m’a bien pris la tête et je suis content d’avoir une solution. Ce n’est surement pas la plus propre des solutions, mais elle a le mérite de fonctionner. Je peux ainsi avoir une CI avec des jobs clairement séparés.

Mise à jour disponible

Une nouvelle version de ce site est disponible.