Java

Intégration continue avec git, Maven et TeamCity

Quand on ne fait pas des PCB, il nous arrive de discuter d’intégration continue avec Brice. Il m’avait notamment parlé de TeamCity et cet outil avait piqué mon intérêt au vif, mais je n’avais pas pris le temps de tester. Récemment, j’ai lu un tutoriel sur Developpez.com sur « la mise en place d’une petite usine logicielle GitHub, CloudBees et Jenkins« . J’ai eu envie d’essayer de mettre en oeuvre une telle architecture, histoire de voir comment ça marche.

Pour cette expérimentation d’intégration continue, j’ai fait simple niveau code avec une application « hello-world » en Java. Ce projet Java se builde avec Maven. TeamCity permet de lancer automatiquement un build lorsqu’un commit est fait dans le repository Git. J’ai utilisé Eclipse Mars for Java Developers (installé avec Oomph) qui a l’avantage d’avoir des fonctionnalités intégrées pour s’interfacer avec Git et Maven. Le tout est installé dans un Xubuntu virtualisé avec VirtualBox.

Git

La partie Git n’a rien de bien sorcier. On installe Git, on crée son repository avec git init et voilà. On peut ensuite connecter Eclipse à ce repository grâce à EGit. La perspective Git est triviale à prendre en main et la documentation officielle est bien.

Git

Maven

J’avais écrit un article sur mes débuts avec Maven. Vous y trouverez les bases sur cet outil ainsi que la procédure d’installation. Ici, j’ai préféré utilisé le wizzard d’Eclipse plutôt que la ligne de commande pour créer mon projet Java. Dans File / New, on trouve Maven Project. J’ai créé mon projet « hello-world » puis je l’ai partagé dans Git en faisant un clic-droit sur le projet dans le Package Explorer et en choisissant Team / Share Project…. Il est possible de créer des Run Configurations ou d’utiliser la ligne de commande pour le build du projet.

Maven

L’étape suivante a été de personnaliser les entrepôts pour le déploiement. Si vous ne modifiez pas la configuration de votre projet ou la configuration globale de Maven, voici ce qu’il se passe si vous tentez de déployer :

$ mvn deploy
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building hello-world

(...)

[INFO] Building jar: /home/pgradot/Documents/git/hello-world/hello-world/target/hello-world-1.0.1-SNAPSHOT.jar
[INFO] [install:install {execution: default-install}]
[INFO] Installing /home/pgradot/Documents/git/hello-world/hello-world/target/hello-world-1.0.1-SNAPSHOT.jar to /home/pgradot/.m2/repository/com/gradot/autobuild/hello-world/1.0.1-SNAPSHOT/hello-world-1.0.1-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[ERROR] BUILD ERROR
[INFO] ------------------------------------------------------------------------
[INFO] Failed to configure plugin parameters for: org.apache.maven.plugins:maven-deploy-plugin:2.4

check that the following section of the pom.xml is present and correct:

<distributionManagement>
  <!-- use the following if you're not using a snapshot version. -->
  <repository>
    <id>repo</id>
    <name>Repository Name</name>
    <url>scp://host/path/to/repo</url>
  </repository>
  <!-- use the following if you ARE using a snapshot version. -->
  <snapshotRepository>
    <id>repo</id>
    <name>Repository Name</name>
    <url>scp://host/path/to/repo</url>
  </snapshotRepository>
</distributionManagement>

Cause: Class 'org.apache.maven.artifact.repository.ArtifactRepository' cannot be instantiated
[INFO] ------------------------------------------------------------------------
[INFO] For more information, run Maven with the -e switch
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2 seconds
[INFO] Finished at: Sat Oct 03 13:09:35 CEST 2015
[INFO] Final Memory: 12M/31M
[INFO] ------------------------------------------------------------------------

Je suis gentil et discipliné, j’ai donc rajouté la section demandée dans mon pom.xml et j’ai créé les dossiers correspondants :

<distributionManagement>
	<!-- The following repositories will be used during the 'deploy' phase. -->
	<repository>
		<id>gradot-repository</id>
		<name>Gradot's Maven Repository</name>
		<url>file:///home/pgradot/Documents/maven/gradot-repository</url>
	</repository>
	<snapshotRepository>
		<id>gradot-repository-snapshots</id>
		<name>Gradot's Maven Repository for snapshots</name>
		<url>file:///home/pgradot/Documents/maven/gradot-repository-snaphots</url>
	</snapshotRepository>
</distributionManagement>

TeamCity

A ce stade, j’avais donc un projet Java versionné sous Git et pouvant être buildé avec Maven, qui va déposer les artefacts au bon endroit. Il ne me restait plus qu’à mettre en place TeamCity pour automatiser le build Maven quand un commit était fait dans Git.

TeamCity (la dernière version à ce jour est la 9.1) est à télécharger sur le site l’éditeur, JetBrains, au format tar.gz. Il suffit de décompresser l’archive à l’endroit souhaité puis de lancer le serveur avec la commande suivante :

./bin/runAll.sh start

Tout se passe ensuite par navigateur à l’adresse http://localhost:8111/. Lors du premier accès, vous devrez configurer quelques trucs et créer le compte administrateur avant d’arriver sur la page principale de TeamCity. TeamCity est un outil assez touffu, ses possibilités semblent gigantesques. On est un peu perdu la première fois mais j’ai réussi assez facilement à faire ce que je voulais, sans avoir à me taper la documentation, à part pour l’installation et le lancement.

J’ai commencé par aller dans le menu Administration (en haut à droite), d’où on peut créer de nouveaux projets, et j’ai créé mon projet « hello-world ». Depuis la page du projet, j’ai ensuite cliqué sur le bouton Create build configuration. Après l’avoir nommé, on m’a demandé à quel VCS je souhaitais connecter cette build configuration. J’ai choisi mon dépôt Git :

teamCity - git

Une fois cette configuration créée, on peut cliquer dessus pour la modifier. Dans le menu de gauche, j’ai cliqué sur Build Steps, j’ai choisi Auto detect build steps, et, ô ! magie, il a trouvé Maven ! J’ai modifié la cible pour utiliser clean deploy à la place de clean test. La dernière étape est de configurer TeamCity pour lancer ce build automatiquement. En dessous de Build Steps, on trouve Trigger. J’ai choisi VCS et TeamCity ajoute tout seul un trigger sur check-in.

C’est tout. Oui, ce n’était pas bien compliqué. Maintenant, quand je commit avec Git, TeamCity se déclenche et un nouvel artefact est disponible dans mes entrepôts Maven. On peut voir l’avancement dans le menu Agent :

teamcity - building

La page d’accueil montre les projets, on peut cliquer dessus pour voir les historiques de builds, on peut cliquer sur chaque build pour voir les tests JUint passés par Maven, etc :

teamcity - build details

teamcity - maven build result

Je suis séduis par cette expérimentation et notamment par TeamCity. Brice m’avait bien dit que c’était très rapide de faire son build automatique, il avait raison. Le plus long a en fait été d’avoir un projet mavenisé avec les cibles de déploiement correctement configurées. L’automatisation s’est faite presque tout seule 🙂


update-alternatives

Hier, j’ai voulu installé Eclipse sous Xubuntu avec le nouvel installateur Oomph. Au lancement de l’installateur, j’ai eu un message d’erreur car je ne disposais que de Java 7 et que l’installateur avait besoin de Java 8. Je me suis alors dit qu’il était temps de mettre en oeuvre la technique des update-alternatives dont m’avait parlé mon administrateur système préféré.

État des lieux de Java

Tout d’abord, commençons par regarder ce vers quoi pointe la commande java :

$ ls -la $(which java)
 lrwxrwxrwx 1 root root 22 sept. 20 19:05 /usr/bin/java -> /etc/alternatives/java

Le principe d’update-alternatives est de ne pas faire de lien direct vers la vraie implémentation mais un lien vers une alternative, qui est dans /etc/alternatives qui pointe à son tour vers le programme réellement chargé de la commande. Regardons les alternatives connues du système des alternatives pour java :

$ update-alternatives --list java
/usr/lib/jvm/java-7-openjdk-i386/jre/bin/java

Il n’y a pour l’instant qu’un seul programme connu pour la commande java et c’est sans surprise un Java 7.

Installation d’une nouvelle alternative pour Java

Personnellement, j’ai choisi d’utiliser le JDK 8 d’Oracle et j’ai récupéré la dernière version disponible sur le site officiel. J’ai décompressé l’archive tar.gz dans /usr/local :

$ ls
bin/ etc/ games/ include/ jdk1.8.0_60/ lib/ man@ sbin/ share/ src/

Pour installer une nouvelle alternative, il faut utiliser une ligne de commande de la forme suivante :

update-alternatives --install <link> <name> <path> <priority>

<link> est l’endroit où placer le lien qui pointera vers /etc/alternatives, <name> est la commande pour laquelle on installe une alternative et <path> est le chemin vers le programme concret. <priority> sert pour les groupes de liens et quand le mode automatique est utilisé. Plus la priorité est élevée, plus l’alternative aura une priorité forte. Pour installer une nouvelle alternative vers mon JDK 8, j’ai utilisé la commande suivante :

$ sudo update-alternatives --install /usr/bin/java java
    /usr/local/jdk1.8.0_60/bin/java 3000
update-alternatives: using /usr/local/jdk1.8.0_60/bin/java 
    to provide /usr/bin/java (java) in auto mode

C’est maintenant Java 8 qui est utilisé par défaut car la nouvelle alternative a une priorité plus forte que l’alternative déjà disponible :

$ which java
/usr/bin/java
pgradot@pgradot-xubuntu:~$ java -version
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) Client VM (build 25.60-b23, mixed mode)

A titre d’exemple, voici ce qui se passe pour une installation avec une priorité à 1000 au lieu de 3000 :

$ sudo update-alternatives --install /usr/bin/java java
    /usr/local/jdk1.8.0_60/bin/java 1000
pgradot@pgradot-xubuntu:~/Documents/git/PyTweet$ java -version
java version "1.7.0_79"
OpenJDK Runtime Environment (IcedTea 2.5.6) (7u79-2.5.6-0ubuntu1.14.04.1)
OpenJDK Client VM (build 24.79-b02, mixed mode, sharing)

Changer d’alternative

Il est simple de passer d’une alternative à une autre et il est donc pratique de garder plusieurs versions installées sur le système et de changer la cible concrète de la commande au besoin. Cela est utile quand un projet particulier réclame une version différente que la version utilisée généralement, que ce soit Java ou autre.

On commence par lister les alternatives pour la commande souhaitée :

$ sudo update-alternatives --list java
[sudo] password for pgradot:
/usr/lib/jvm/java-7-openjdk-i386/jre/bin/java
/usr/local/jdk1.8.0_60/bin/java

Il suffit ensuite d’utiliser la commande --set :

$ sudo update-alternatives --set java
    /usr/lib/jvm/java-7-openjdk-i386/jre/bin/java
update-alternatives: using /usr/lib/jvm/java-7-openjdk-i386/jre/bin/java
    to provide /usr/bin/java (java) in manual mode
$ java -version
java version "1.7.0_79"
OpenJDK Runtime Environment (IcedTea 2.5.6) (7u79-2.5.6-0ubuntu1.14.04.1)
OpenJDK Client VM (build 24.79-b02, mixed mode, sharing)

Il est pratique de lister les alternatives avant d’utiliser --set pour pouvoir copier-coller le chemin souhaité. Il n’est pas possible d’utiliser un programme qui n’a pas encore été ajouté comme alternative :

$ sudo update-alternatives --set java /usr/local/jdk1.8.0_60/bin/jav
update-alternatives: error: alternative /usr/local/jdk1.8.0_60/bin/jav
    for java not registered; not setting

L’erreur dit bien que l’alternative n’est pas connue et non que le programme n’existe pas.

Supprimer une alternative

Rien de bien compliqué pour supprimer une alternative. Si c’est l’alternative est actuellement utilisé, le gestionnaire des alternatives se rabat sur une autre alternative en se basant sur les priorités :

$ sudo update-alternatives --remove java /usr/local/jdk1.8.0_60/bin/java
update-alternatives: removing manually selected
    alternative - switching java to auto mode
update-alternatives: using /usr/lib/jvm/java-7-openjdk-i386/jre/bin/java
    to provide /usr/bin/java (java) in auto mode

C’est aussi l’un des avantages du système des alternatives. Il sera charge de mettre à jour les différents liens quand des alternatives sont modifiées.


ObjectAid : sources Java vers diagrammes de classes

Ceci est mon 100e article ! \o/

Au début de cette année, j’avais passé plusieurs heures à chercher sur Internet un outil permettant de gérer des diagrammes de classes à partir d’un projet Java existant. Il n’y avait pas beaucoup de critères :

  • être gratuit,
  • être simple,
  • s’intégrer dans Eclipse.

J’avais essayé des tas de mots-clés dans Google et je n’avais rien trouvé. Vraiment rien. J’avais essayé quelques usines à gaz d’Eclipse Modeling sans arriver à un résultat concluant. J’avais alors laissé tomber. La semaine passée, j’ai cherché à faire l’inverse : créer des diagrammes de classes pour pouvoir ensuite générer du code Java. J’ai fais une recherche dans Google et le premier résultat était une discussion stackoverflow expliquant comment… générer des diagrammes de classes à partir de sources existantes. Oui, je suis très sérieux.

C’est ainsi que j’ai découvert ObjectAid. Il ne permet pas de créer de diagrammes de classes pour générer ensuite du code ; il analyse le code existant et l’afficher sous forme de diagrammes de classes. Il est disponible via le Marketplace d’Eclipse. En fait, j’ai vu cet outil lors de mes recherches de début d’année mais j’avais crû qu’il n’était pas gratuit. Il existe en effet des licences payantes mais elles donnent accès à des fonctionnalités supplémentaires. Les fonctionnalités de base sont bien gratuites. Voici la description de l’outil donné par le site officiel :

The ObjectAid UML Explorer is an agile and lightweight code visualization tool for the Eclipse IDE. It shows your Java source code and libraries in live UML class and sequence diagrams that automatically update as your code changes.

L’utilisation de l’outil est triviale. Un tutoriel est disponible ici.

Dans le meilleur des mondes, on devrait faire des diagrammes de classes lors de la phase de conception, écrire le code (ou encore mieux : le générer) et continuer à garder une cohérence entre les diagrammes et le code au fur et à mesure que le projet avance et qu’on fait du refactoring ou que l’on repense certains modules. Dans les faits, on ne fait pas souvent les diagrammes de classes originels et on les garde encore moins souvent à jour. ObjectAid ne permet pas de répondre à la première problématique mais répond complètement à la seconde puisque ses diagrammes se mettent à jour quand on modifie les fichiers sources associés.

Je suis bien content d’avoir trouvé cet outil, il sera très pratique pour donner une meilleure vision d’ensemble de bibliothèques ou de projets, en montrant de manière graphique les liens entre les classes. Juste pour l’exemple, j’ai fait un petit projet avec 3 classes, dont 2 implémentent une interface :

objectaid - project

Et voici le diagramme montrant l’interface et les 2 classes d’implémentation (je n’ai pas ajouté la 3e classe au digramme car je suis libre de mettre les classes que je souhaite, je ne suis pas obligé de mettre tout le projet Eclipse !) :

objectaid - diagramme classes


Things every Java developer must know about Exception handling

Je n’avais encore jamais reblogué, mais cet article est tellement bien écrit, il me semble être le candidat idéal pour un premier reblog !

10K-LOC

Exceptions are one of the most misunderstood (and misused) features of the Java programming language. This article describes the absolute minimum every Java developer must know about exceptions. It assumes that the reader is somewhat familiar with Java.

Historical Perspective

Back in the heyday of the « C » programming language, it was customary to return values such as -1 or NULL from functions to indicate errors. You can easily see why this isn’t a great idea – developers had to check and track possible return values and their meanings: a return value of 2 might indicate « host is down » error in library A, whereas in library B, it could mean « illegal filename ».

Attempts were made to standardize error checking by expecting functions to set a global variable with a defined value.

deleteme

James Gosling and other designers of the language felt that this approach would go against the design goals of Java. They wanted:

  1. a cleaner, robust and portable approach
  2. built in language support for error checking…

Voir l’article original 1 470 mots de plus


Java : try / catch / finally et return

Voici un code simple en Java :

package com.gradot.blog;

public class ExceptionAndReturn {
	public static final int NORMAL = 0;
	public static final int EXCEPTION = 42;
	public static final int FINALLY = 66;

	public static void main(String[] args) {
		System.out.println("foo returned " + foo());
	}

	public static int foo() {
		try {
			System.out.println("Try");
			Object o = null;
			o.getClass();
			return NORMAL;
		} catch (NullPointerException e) {
			System.out.println("Catch");
			return EXCEPTION;
		} finally {
			System.out.println("Finally");
			return FINALLY;
		}
	}
}

De manière tout à fait évidente, l’appel à o.getClass(); va provoquer une exception et on va entrer dans le bloc catch. Que va alors afficher ce code ? Quelle valeur est renvoyée ? Exécutons et regardons :

Try
Catch
Finally
foo returned 66

Le passage dans les 3 blocs de gestion d’exception est attendu. Le retour de la valeur 66 est peut-être moins évident mais en même temps c’est logique : on termine par un return FINALLY;.

Modifions légèrement la méthode foo() pour que ne survienne pas l’exception :

public static int foo() {
	try {
		System.out.println("Try");
		Object o = new Object();
		o.getClass();
		return NORMAL;
	} catch (NullPointerException e) {
		System.out.println("Catch");
		return EXCEPTION;
	} finally {
		System.out.println("Finally");
		return FINALLY;
	}
}

On ne rentre plus dans le bloc catch mais on a bien sûr le même problème : la valeur retournée dans le bloc finally écrase celle donnée par un éventuel précédent return. On obtient en console :
Try
Finally
foo returned 66

Revenons à une version du code provoquant une exception mais essayons de la relancer dans le catch plutôt que de retourner un code d’erreur :

public static int foo() {
	try {
		System.out.println("Try");
		Object o = null;
		o.getClass();
		return NORMAL;
	} catch (NullPointerException e) {
		System.out.println("Catch");
		throw e;
	} finally {
		System.out.println("Finally");
		return FINALLY;
	}
}

Quel affichage d’après vous ? Et bien, de manière un peu surprenante (bien que vous deviez sentir le piège), le return du bloc finally annule et remplace le throw du bloc catch. En console :

Try
Catch
Finally
foo returned 66

Tentons carrément de supprimer le bloc catch :

public static int foo() {
	try {
		System.out.println("Try");
		Object o = null;
		o.getClass();
		return NORMAL;
	} finally {
		System.out.println("Finally");
		return FINALLY;
	}
}

catch ou pas, le résultat est le même, le finally est exécuté et l’exception n’est pas remontée à l’appelant.

La conclusion est toute simple : ne mettez pas de return dans un finally. Vous masqueriez les précédents returns et vous bloqueriez la levée d’exception ! Vous avez eu chaud ? Pas tant que ça : si vous mettez les codes ci-dessus dans Eclipse (ou si vous le compiler manuellement), vous verrez un warning sur l’ensemble du bloc finally :

finally block does not complete normally