[{"content":"","date":"20 octobre 2019","externalUrl":null,"permalink":"/categories/blog/","section":"Categories","summary":"","title":"Blog","type":"categories"},{"content":"","date":"20 octobre 2019","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"20 octobre 2019","externalUrl":null,"permalink":"/tags/hugo/","section":"Tags","summary":"","title":"Hugo","type":"tags"},{"content":"","date":"20 octobre 2019","externalUrl":null,"permalink":"/","section":"Jean-Christophe Gay","summary":"","title":"Jean-Christophe Gay","type":"page"},{"content":"","date":"20 octobre 2019","externalUrl":null,"permalink":"/tags/markdown/","section":"Tags","summary":"","title":"Markdown","type":"tags"},{"content":"Retour sur la migration d\u0026rsquo;un blog construit avec Pelican pour un site statique généré avec Hugo.\nÇa fait un moment que je n\u0026rsquo;ai pas posté ici. Pour le dépoussierage je reviens sur la migration du générateur de ce site.\nJ\u0026rsquo;utilisais Pelican depuis plusieurs années. Récemment j\u0026rsquo;ai réinstallé ma machine, j\u0026rsquo;avais bien entendu sauvegardé l\u0026rsquo;environnement virtuel qui me servait à générér mon site mais au moment de le restaurer je me suis dit que c\u0026rsquo;était le bon moment pour changer.\nJ\u0026rsquo;ai souvent eu des problèmes avec la gestion de mon environnement Python (pour tout dire ce n\u0026rsquo;est vraiment pas ma zone de confort, et je n\u0026rsquo;en touche que très rarement\u0026hellip;), que ce soit à cause des chemins vers mes installations de Python ou bien à cause des mises à jour de Pelican et de ses dépendances. Du coup je surveille ce qui se passe un peu dans l\u0026rsquo;univers des générateurs de site et je suis resté bloqué sur Hugo et Gatsby.\nLes deux semblent assez populaires et assez similaires en terme de fonctionalités. Comme je suis bien plus à l\u0026rsquo;aise avec Go qu\u0026rsquo;avec l\u0026rsquo;écosystème React le choix a été assez facile 😇.\nCe qui m\u0026rsquo;a encouragé à migrer est que pour utiliser Hugo il suffit d\u0026rsquo;utiliser un binaire tout en un. Pas besoin de gérer x dépendances 🚀. C\u0026rsquo;est donc plus simple de reproduire la génération de son site dans le temps.\nInstallation # Il a suffit de suivre le guide d\u0026rsquo;installation soit sur macOS un simple :\nbrew install hugo Puis de générer la structure du site (dans le dossier existant) avec :\nhugo new site jeanchristophegay.com Theme # J\u0026rsquo;ai choisi Beautiful Hugo pour le moment.\nL\u0026rsquo;installation a été rapide en suivant les simples étapes de la documentation.\nMigrer les articles # Les articles sont déplacés dans le dossier content/posts pour respecter la structure d\u0026rsquo;Hugo.\nLes images sont maintenant dans static/images pour ne pas devoir réécrire les URL.\nLes articles sont déjà rédigés en Markdown, il faut donc s\u0026rsquo;occuper de convertir les métadonnés et les quelques balises spécifiques à Pelican.\nLes métadonnées de Pelican ressemblaient à :\nTitle: De la couleur dans les logs Date: 2013-05-04 18:22 Category: java Tags: ansi console, grep console, jansi, log, log4j, log4j 2, logback -Slug: de-la-couleur-dans-les-logs -Author: Jean-Christophe Gay -lang: fr disqus_identifier: 213 http://jeanchristophegay.com/?p=213 Pour Hugo, il faut les transformer en :\n--- title: \u0026#34;De la couleur dans les logs\u0026#34; date: 2013-05-04T18:22:00+02:00 aliases: - /de-la-couleur-dans-les-logs/ slug: de-la-couleur-dans-les-logs categories: - java tags: - ansi console - grep console - jansi - log - log4j - log4j 2 - logback disqus_identifier: \u0026#34;213 http://jeanchristophegay.com/?p=213\u0026#34; --- Toutes les options possibles sont listées dans la documentation.\nL\u0026rsquo;attribut aliases est bien pratique pour conserver les anciennes URL générées par Pelican.\nPelican : https://jeanchristophegay.com/de-la-couleur-dans-les-logs/ Hugo : https://jeanchristophegay.com/posts/de-la-couleur-dans-les-logs/\nLa balises \u0026lt;!-- PELICAN_END_SUMMARY --\u0026gt; qui permettait de délimiter le résumé d\u0026rsquo;un article est remplacé par \u0026lt;!--more--\u0026gt;.\nPour le moment j\u0026rsquo;ai perdu la table des matières, apparemment certains thèmes permettent d\u0026rsquo;en générer une mais je n\u0026rsquo;ai pas encore essayé.\nPour les articles disponibles en plusieurs langues, pas besoin de metadonnées, le suffixe dans le nom de fichier est suffisant.\nConfiguration # Site # La configuration se déroule dans un fichier config.toml à la place de pelicanconf.py et publishconf.py. On y retrouve un peu le même genre d\u0026rsquo;options que dans Pelican.\nPour exemple voici à quoi ressemble le mien à ce jour :\nbaseURL = \u0026#34;https://jeanchristophegay.com/\u0026#34; languageCode = \u0026#34;fr-FR\u0026#34; title = \u0026#34;Jean-Christophe Gay\u0026#34; theme = \u0026#34;beautifulhugo\u0026#34; disqusShortname = \u0026#34;\u0026#34; googleAnalytics = \u0026#34;\u0026#34; enableEmoji = true enableGitInfo = true enableRobotsTXT = true DefaultContentLanguage = \u0026#34;fr\u0026#34; [languages] [languages.fr] weight = 1 [languages.en] weight = 2 [[menu.main]] name = \u0026#34;Home\u0026#34; url = \u0026#34;/\u0026#34; weight = 1 [[menu.main]] name = \u0026#34;Tags\u0026#34; url = \u0026#34;tags\u0026#34; weight = 3 [Params] subtitle = \u0026#34;Développeur\u0026#34; mainSections = [\u0026#34;post\u0026#34;,\u0026#34;posts\u0026#34;] logo = \u0026#34;https://www.gravatar.com/avatar/5cee6a7b8d17e4a0f367a5f0e237c694?s=200\u0026#34; dateFormat = \u0026#34;2 Jan 2006\u0026#34; rss = true comments = true readingTime = true wordCount = false socialShare = true delayDisqus = false showRelatedPosts = true [Author] name = \u0026#34;Jean-Christophe Gay\u0026#34; website = \u0026#34;jeanchristophegay.com\u0026#34; email = \u0026#34;contact@jeanchristophegay.com\u0026#34; github = \u0026#34;jcgay\u0026#34; gitlab = \u0026#34;jcgay\u0026#34; twitter = \u0026#34;jchristophegay\u0026#34; linkedin = \u0026#34;jcgay\u0026#34; Coloration syntaxique # Elle s\u0026rsquo;active dans la configuration :\npygmentsCodeFences = true pygmentsUseClasses = true Et il faut ensuite générer le style avec la commande :\nhugo gen chromastyles --style=trac \u0026gt; static/css/syntax.css J\u0026rsquo;ai activé le rendu côté serveur mais on peut aussi utilisé une version déportée dans le navigateur avec ce thème.\nC\u0026rsquo;est un point qu\u0026rsquo;il faut que je creuse encore un peu parce qu\u0026rsquo;en l\u0026rsquo;état le rendu n\u0026rsquo;est pas optimal, quand un bloc de code est un peu trop large on ne peut pas scrollé horizontalement\u0026hellip; 😅\nCommentaires # Les commentaires du blog étant hébergés sur Disqus, la migration s\u0026rsquo;est bien déroulée.\nIl a suffit d\u0026rsquo;utiliser les bons disqus_identifier pour chaque article. Par contre au départ j\u0026rsquo;avais utilisé l\u0026rsquo;option delayDisqus = true pour ne pas charger les commentaires par défaut (ça alourdi pas mal les pages de charger Disqus) mais cette option ne respecte pas par la suite les disqus_identifier définis\u0026hellip; Ce qui rendait la récupération des anciens commentaires impossibles.\nUn de ces jours je jetterai un oeil aux alternatives disponibles 😇.\nRedirections # La possibilité de définir des alias pour chaque article a été suffisant pour gérer la migration des URL des articles, les ressources statiques n\u0026rsquo;ont pas bougées. J\u0026rsquo;ai ajouté de nouvelles règles uniquement pour les flux RSS :\nRewriteRule feed/?$ index.xml [R=301,L] RewriteRule feeds/? index.xml [R=301,L] Prévisualisation # Pour voir le rendu de ce qu\u0026rsquo;on est en train d\u0026rsquo;écrire :\nhugo serve Le contenu se recharche bien entendu à chaud.\nGénération du site final # Le site prêt à déployer est disponible dans le dossier public après avoir lancé :\nhugo Conclusion # En 3 ou 4 heures j\u0026rsquo;ai réussi à avoir un rendu de mon blog à peu près équivalent au précédent.\nJe ne pensais pas que la migration se passerait aussi facilement 😎.\nIl reste 2 ou 3 réglages à faire mais je suis bien content de ne plus avoir à me compliquer la vie pour avoir un environnement stable Python pour générer ce site 🎇.\n","date":"20 octobre 2019","externalUrl":null,"permalink":"/posts/migration-pelican-hugo/","section":"Posts","summary":"Retour sur la migration d’un blog construit avec Pelican pour un site statique généré avec Hugo.\n","title":"Migration de Pelican vers Hugo","type":"posts"},{"content":"","date":"20 octobre 2019","externalUrl":null,"permalink":"/tags/pelican/","section":"Tags","summary":"","title":"Pelican","type":"tags"},{"content":"","date":"20 octobre 2019","externalUrl":null,"permalink":"/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"20 octobre 2019","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"2 septembre 2016","externalUrl":null,"permalink":"/categories/build/","section":"Categories","summary":"","title":"Build","type":"categories"},{"content":"","date":"2 septembre 2016","externalUrl":null,"permalink":"/tags/maven/","section":"Tags","summary":"","title":"Maven","type":"tags"},{"content":"Quand je travaille sur un projet Maven multi-modules (plusieurs dizaines) sur plusieurs branches en même temps, je me retrouve souvent avec des conflits à résoudre pour chaque merge sur les versions définies un peu partout dans les pom.xml\u0026hellip; Et c\u0026rsquo;est tout sauf marrant.\nTour d\u0026rsquo;horizon des solutions pour réduire le nombre de \u0026lt;version\u0026gt; déclarées dans tous ces pom.xml.\nNotre projet exemple de départ est donc constitué de deux modules :\n. ├── pom.xml ├── submodule-1 │ └── pom.xml └── submodule-2 └── pom.xml Le pom.xml du projet parent ressemble à :\n\u0026lt;project\u0026gt; \u0026lt;groupId\u0026gt;com.github.jcgay.example.version\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;parent-pom\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0-SNAPSHOT\u0026lt;/version\u0026gt; \u0026lt;packaging\u0026gt;pom\u0026lt;/packaging\u0026gt; \u0026lt;modules\u0026gt; \u0026lt;module\u0026gt;submodule-1\u0026lt;/module\u0026gt; \u0026lt;module\u0026gt;submodule-2\u0026lt;/module\u0026gt; \u0026lt;/modules\u0026gt; \u0026lt;/project\u0026gt; et ceux des modules :\n\u0026lt;project\u0026gt; \u0026lt;parent\u0026gt; \u0026lt;groupId\u0026gt;com.github.jcgay.example.version\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;parent-pom\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0-SNAPSHOT\u0026lt;/version\u0026gt; \u0026lt;/parent\u0026gt; \u0026lt;artifactId\u0026gt;submodule-1\u0026lt;/artifactId\u0026gt; \u0026lt;/project\u0026gt; La version du projet est répétée dans chaque pom.xml des modules, pour les changer ce n\u0026rsquo;est pas bien compliqué avec le plugin Versions. Par contre les merges entre branches sont vite pénibles, les conflits sont fréquents et généralement on ne peut pas se contenter de ne garder les changements que d\u0026rsquo;une des deux branches 😓.\nDéfinir la version dans une propriété # Première tentative, extraire la version dans une propriété Maven. Le pom parent devient donc :\n\u0026lt;project\u0026gt; \u0026lt;groupId\u0026gt;com.github.jcgay.example.version\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;parent-pom\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${my-version}\u0026lt;/version\u0026gt; \u0026lt;packaging\u0026gt;pom\u0026lt;/packaging\u0026gt; \u0026lt;properties\u0026gt; \u0026lt;my-version\u0026gt;1.0-SNAPSHOT\u0026lt;/my-version\u0026gt; \u0026lt;/properties\u0026gt; \u0026lt;modules\u0026gt; \u0026lt;module\u0026gt;submodule-1\u0026lt;/module\u0026gt; \u0026lt;module\u0026gt;submodule-2\u0026lt;/module\u0026gt; \u0026lt;/modules\u0026gt; \u0026lt;/project\u0026gt; et un module utilise également la propriété ${my-version} pour référencer son parent :\n\u0026lt;project\u0026gt; \u0026lt;parent\u0026gt; \u0026lt;groupId\u0026gt;com.github.jcgay.example.version\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;parent-pom\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${my-version}\u0026lt;/version\u0026gt; \u0026lt;/parent\u0026gt; \u0026lt;artifactId\u0026gt;submodule-2\u0026lt;/artifactId\u0026gt; \u0026lt;/project\u0026gt; Etonnement ça fonctionne 😲\nLa seule arnaque provient des messages d\u0026rsquo;avertissements affichés par Maven.\nSome problems were encountered while building the effective model for com.github.jcgay.example.version:submodule-1:jar:1.0-SNAPSHOT \u0026#39;version\u0026#39; contains an expression but should be a constant. @ com.github.jcgay.example.version:parent-pom:${my-version}, /Users/jcgay/dev/toolbox-examples/unique-maven-version/pom.xml, line 7, column 14 Some problems were encountered while building the effective model for com.github.jcgay.example.version:submodule-2:jar:1.0-SNAPSHOT \u0026#39;version\u0026#39; contains an expression but should be a constant. @ com.github.jcgay.example.version:parent-pom:${my-version}, /Users/jcgay/dev/toolbox-examples/unique-maven-version/pom.xml, line 7, column 14 Some problems were encountered while building the effective model for com.github.jcgay.example.version:parent-pom:pom:1.0-SNAPSHOT \u0026#39;version\u0026#39; contains an expression but should be a constant. @ com.github.jcgay.example.version:parent-pom:${my-version}, /Users/jcgay/dev/toolbox-examples/unique-maven-version/pom.xml, line 7, column 14 It is highly recommended to fix these problems because they threaten the stability of your build. For this reason, future Maven versions might no longer support building such malformed projects. En l\u0026rsquo;état ce n\u0026rsquo;est pas vraiment sérieux, et puis il y a un autre problème avec cette solution mais on va la détailler dans la seconde.\nContinuous delivery friendly version # Depuis Maven 3.2.1 il est possible d\u0026rsquo;utiliser des propriétés réservées pour éviter les messages d\u0026rsquo;avertissements précédents.\nSi on remplace notre propriété my-version de la solution précédente par une autre nommée revision, plus de messages qui font peur 🎆.\nSeulement utiliser des propriétés plutôt que des versions stables pose réellement un problème. Quand Maven installe dans le repository local ou déploie dans un repository distant nos artifacts, les pom.xml sont copiés en l\u0026rsquo;état. Si on a donc un projet qui dépend d\u0026rsquo;un de nos modules (comprendre qui n\u0026rsquo;est pas construit dans ce même projet multi-modules), la résolution des dépendances échouera car la propriété ${revision} n\u0026rsquo;existera pas dans ce projet extérieur.\nExemple avec ce projet tout simple qui n\u0026rsquo;est pas un module de notre parent-pom :\n\u0026lt;project\u0026gt; \u0026lt;groupId\u0026gt;com.github.jcgay.example.version\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;independant-project\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0-SNAPSHOT\u0026lt;/version\u0026gt; \u0026lt;dependencies\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.github.jcgay.example.version\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;submodule-1\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0-SNAPSHOT\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; \u0026lt;/project\u0026gt; Quand on lance un build :\n$\u0026gt; mvn test Scanning for projects... Using the MultiThreadedBuilder implementation with a thread count of 4 ------------------------------------------------------------------------ Building independant-project 1.0-SNAPSHOT ------------------------------------------------------------------------ Downloading: https://repo.maven.apache.org/maven2/com/github/jcgay/example/version/parent-pom/$%7Brevision%7D/parent-pom-$%7Brevision%7D.pom ------------------------------------------------------------------------ BUILD FAILURE ------------------------------------------------------------------------ Total time: 0.882 s (Wall Clock) Finished at: 2016-08-09T15:26:09+02:00 Final Memory: 12M/193M ------------------------------------------------------------------------ Failed to execute goal on project independant-project: Could not resolve dependencies for project com.github.jcgay.example.version:independant-project:jar:1.0-SNAPSHOT: Failed to collect dependencies at com.github.jcgay.example.version:submodule-1:jar:1.0-SNAPSHOT: Failed to read artifact descriptor for com.github.jcgay.example.version:submodule-1:jar:1.0-SNAPSHOT: Could not find artifact com.github.jcgay.example.version:parent-pom:pom:${revision} in central (https://repo.maven.apache.org/maven2) -\u0026gt; [Help 1] Et en effet le pom.xml présent dans mon repository local défini toujours ${revision} en tant que \u0026lt;version\u0026gt; du pom-parent.\nFiltrer les pom à l\u0026rsquo;installation/déploiement # Ce problème de propriétés non remplacées m\u0026rsquo;a rappelé un vieil article dans lequel l\u0026rsquo;auteur s\u0026rsquo;était amusé à filtrer (dans le sens maven-resources-plugin filtering) ses fichiers pom.xml et les installer à la place des originaux.\nJ\u0026rsquo;ai donc recherché s\u0026rsquo;il était possible de faire de même mais avec une extension Maven (la dose de XML nécessaire et le côté gros hack de la solution décrite m\u0026rsquo;a découragé\u0026hellip;). Et c\u0026rsquo;est effectivement faisable avec Aether (utilisé dans Maven depuis la version 3) via un org.eclipse.aether.impl.MetadataGenerator. On peut donc écrire un gros hack dans son langage préféré à la place 🤔.\nLe code est disponible sur GitHub. Il prend la forme d\u0026rsquo;une extension Maven qui se charge de transformer les artifacts de type pom à l\u0026rsquo;installation et au déploiement. Toutes les occurrences de ${revision} sont remplacées par l\u0026rsquo;actuelle version de l\u0026rsquo;artifact traité (c\u0026rsquo;est à dire la valeur résolue de ${revision}). Le problème de résolution de dépendance est dans ce cas résolu !\nOn peut l\u0026rsquo;utiliser via le mécanisme d\u0026rsquo;extensions de Maven en créant (modifiant) le fichier ${maven.multiModuleProjectDirectory}/.mvn/extensions.xml :\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;extensions\u0026gt; \u0026lt;extension\u0026gt; \u0026lt;groupId\u0026gt;fr.jcgay.maven.extension\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;unique-revision-maven-filtering\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;[latest-version]\u0026lt;/version\u0026gt; \u0026lt;/extension\u0026gt; \u0026lt;/extensions\u0026gt; ou bien en copiant directement le jar dans %M2_HOME%/lib/ext.\nConclusion # On a donc vu comment utiliser une propriété unique pour définir la version de tous les modules d\u0026rsquo;un projet. Clairement la fonctionnalité proposée par Maven (continuous delivery friendly version) ne semble pas prévue pour l\u0026rsquo;usage décrit ici, mais au moins je ne m\u0026rsquo;embête plus avec mes merge ! J\u0026rsquo;ai peut-être complètement raté une solution plus simple ? (dans ce cas je la veux bien 😉).\n","date":"2 septembre 2016","externalUrl":null,"permalink":"/posts/maven-unique-version-multi-modules-build/","section":"Posts","summary":"Quand je travaille sur un projet Maven multi-modules (plusieurs dizaines) sur plusieurs branches en même temps, je me retrouve souvent avec des conflits à résoudre pour chaque merge sur les versions définies un peu partout dans les pom.xml… Et c’est tout sauf marrant.\nTour d’horizon des solutions pour réduire le nombre de \u003cversion\u003e déclarées dans tous ces pom.xml.\n","title":"Utiliser une propriété unique comme version Maven","type":"posts"},{"content":"Sentry est un service qui permet la sauvegarde, analyse des erreurs qui surviennent dans vos applications.\nQuand vous ne pouvez pas analyser vos logs applicatifs facilement (application mobile ou installée directement sur les postes de vos utilisateurs) il est important de récupérer les erreurs qui se produisent pour les corriger. Pour cela on peut utiliser un service en ligne comme Sentry.\nLe service propose des intégrations pour toute sorte de langage, plate-forme. On suivra ici l\u0026rsquo;intégration au sein d\u0026rsquo;une JVM avec Logback.\nInstallation # Tout repose sur un client Java nommé raven pour envoyer des erreurs vers Sentry. Il suffit donc d\u0026rsquo;ajouter une dépendance à votre projet (par exemple avec Maven) :\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.getsentry.raven\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;raven-logback\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;7.3.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; Configuration # Il faut ajouter un nouvel appender à sa configuration Logback :\n\u0026lt;appender name=\u0026#34;Sentry\u0026#34; class=\u0026#34;com.getsentry.raven.logback.SentryAppender\u0026#34;\u0026gt; \u0026lt;dsn\u0026gt;https://\u0026lt;key\u0026gt;:\u0026lt;secret\u0026gt;@app.getsentry.com/\u0026lt;project\u0026gt;?options\u0026lt;/dsn\u0026gt; \u0026lt;/appender\u0026gt; \u0026lt;root level=\u0026#34;warn\u0026#34;\u0026gt; \u0026lt;appender-ref ref=\u0026#34;Sentry\u0026#34;/\u0026gt; \u0026lt;/root\u0026gt; Avec cet exemple, tous les messages loggés via Logback de niveau WARN ou ERROR seront envoyés à Sentry.\nC\u0026rsquo;est très bien on a maintenant nos erreurs qui sont enregistrées mais les informations remontées se limitent en gros aux messages loggés et stracktraces (si présente) associées.\nOn peut faire mieux en identifiant les utilisateurs victimes d\u0026rsquo;erreurs.\nIdentification # Les messages Sentry peuvent être enrichis à l\u0026rsquo;aide de SentryInterface. Une des interface fournie par défaut permet d\u0026rsquo;associer un utilisateur à chaque message : UserInterface.\nPour définir ces interfaces il faut pimper l\u0026rsquo;initialisation du client Raven à l\u0026rsquo;aide d\u0026rsquo;une factory personnalisée.\npackage fr.jcgay.github.sentry import java.net.InetAddress.getLocalHost import java.util.UUID import com.getsentry.raven.dsn.Dsn import com.getsentry.raven.event.EventBuilder import com.getsentry.raven.event.helper.EventBuilderHelper import com.getsentry.raven.event.interfaces.UserInterface import com.getsentry.raven.{DefaultRavenFactory, Raven} case class MyAppRavenFactory() extends DefaultRavenFactory { lazy val uuid: String = UUID.randomUUID.toString override def createRavenInstance(dsn: Dsn): Raven = { val raven = super.createRavenInstance(dsn) raven.addBuilderHelper(AddUser(\u0026#34;jcgay\u0026#34;, \u0026#34;contact@jeanchristophegay.com\u0026#34;, uuid)) raven } } case class AddUser(id: String, email: String, uuid: String) extends EventBuilderHelper { override def helpBuildingEvent(eventBuilder: EventBuilder): Unit = eventBuilder .withSentryInterface(new UserInterface(id, getLocalHost.getHostName, null, email)) .withTag(\u0026#34;session-uuid\u0026#34;, uuid) } On crée donc un utilisateur avec :\nnew UserInterface(id, getLocalHost.getHostName, null, email) Le 3ième argument est l\u0026rsquo;adresse IP de l\u0026rsquo;utilisateur. Utiliser null pour l\u0026rsquo;absence de valeur.\nOn peut aussi ajouter des informations taggées au sein d\u0026rsquo;un message. Ces données seront indexées par Sentry. On pourra donc les utiliser dans des recherches, etc.\nIci par exemple on génère un identifiant de session qui sera associé aux actions de l\u0026rsquo;utilisateur pour toute la durée de sa session.\nL\u0026rsquo;EventBuilder que l\u0026rsquo;on enrichi sera exécuté à chaque construction de message, attention donc à ce que l\u0026rsquo;on y met 😛.\nEn terme de configuration il faut enregistrer notre factory et la référencer dans le SentryAppender.\nExécuter quelque-part pendant l\u0026rsquo;initialisation de l\u0026rsquo;application :\nRavenFactory.registerFactory(MyAppRavenFactory()) Et dans le logback.xml :\n\u0026lt;appender name=\u0026#34;Sentry\u0026#34; class=\u0026#34;com.getsentry.raven.logback.SentryAppender\u0026#34;\u0026gt; \u0026lt;dsn\u0026gt;https://\u0026lt;key\u0026gt;:\u0026lt;secret\u0026gt;@app.getsentry.com/\u0026lt;project\u0026gt;?options\u0026lt;/dsn\u0026gt; \u0026lt;ravenFactory\u0026gt;fr.jcgay.github.sentry.MyAppRavenFactory\u0026lt;/ravenFactory\u0026gt; \u0026lt;/appender\u0026gt; \u0026lt;root level=\u0026#34;warn\u0026#34;\u0026gt; \u0026lt;appender-ref ref=\u0026#34;Sentry\u0026#34;/\u0026gt; \u0026lt;/root\u0026gt; Version de l\u0026rsquo;application # Une information importante comprise et utilisée par Sentry est la version de l\u0026rsquo;application. Elle est renseignable avec la balise \u0026lt;release\u0026gt; au niveau de la configuration du SentryAppender :\n\u0026lt;appender name=\u0026#34;Sentry\u0026#34; class=\u0026#34;com.getsentry.raven.logback.SentryAppender\u0026#34;\u0026gt; \u0026lt;dsn\u0026gt;https://\u0026lt;key\u0026gt;:\u0026lt;secret\u0026gt;@app.getsentry.com/\u0026lt;project\u0026gt;?options\u0026lt;/dsn\u0026gt; \u0026lt;ravenFactory\u0026gt;fr.jcgay.github.sentry.MyAppRavenFactory\u0026lt;/ravenFactory\u0026gt; \u0026lt;release\u0026gt;${project.version}\u0026lt;/release\u0026gt; \u0026lt;/appender\u0026gt; \u0026lt;root level=\u0026#34;warn\u0026#34;\u0026gt; \u0026lt;appender-ref ref=\u0026#34;Sentry\u0026#34;/\u0026gt; \u0026lt;/root\u0026gt; On peut par exemple utiliser la fonctionnalité de Filtering avec Maven pour injecter la version de l\u0026rsquo;application au moment du build.\nLe MDC # Le dernier moyen pour contextualiser ses messages est d\u0026rsquo;utiliser le MDC.\nPar défaut toutes les valeurs comprises dans le MDC sont envoyées à Sentry. Par contre elles ne sont pas indexées par défaut. Pour activer l\u0026rsquo;indexation sur une clef particulière il faut la déclarer en tant que tag :\nMDC.put(\u0026quot;Environment\u0026quot;, \u0026quot;Development\u0026quot;); \u0026lt;appender name=\u0026#34;Sentry\u0026#34; class=\u0026#34;com.getsentry.raven.logback.SentryAppender\u0026#34;\u0026gt; \u0026lt;dsn\u0026gt;https://\u0026lt;key\u0026gt;:\u0026lt;secret\u0026gt;@app.getsentry.com/\u0026lt;project\u0026gt;?options\u0026lt;/dsn\u0026gt; \u0026lt;ravenFactory\u0026gt;fr.jcgay.github.sentry.MyAppRavenFactory\u0026lt;/ravenFactory\u0026gt; \u0026lt;release\u0026gt;${project.version}\u0026lt;/release\u0026gt; \u0026lt;extraTag\u0026gt;Environment\u0026lt;/extraTag\u0026gt; \u0026lt;/appender\u0026gt; \u0026lt;root level=\u0026#34;warn\u0026#34;\u0026gt; \u0026lt;appender-ref ref=\u0026#34;Sentry\u0026#34;/\u0026gt; \u0026lt;/root\u0026gt; Conclusion # Une fois correctement configuré les messages récupérés dans Sentry permettent de surveiller les erreurs qui interviennent dans vos applications déployées un peu partout dans la nature.\nOn atteint assez vite les quotas (à la minute) dans le premier plan payant, il existe pas mal d\u0026rsquo;alternatives mais je n\u0026rsquo;en ai pas essayé 😱.\nLe code utilisé dans les exemples est disponible ici.\n","date":"15 juillet 2016","externalUrl":null,"permalink":"/posts/contextualiser-ses-evenements-sentry/","section":"Posts","summary":"Sentry est un service qui permet la sauvegarde, analyse des erreurs qui surviennent dans vos applications.\n","title":"Contextualiser ses événements Sentry","type":"posts"},{"content":"","date":"15 juillet 2016","externalUrl":null,"permalink":"/tags/logback/","section":"Tags","summary":"","title":"Logback","type":"tags"},{"content":"","date":"15 juillet 2016","externalUrl":null,"permalink":"/categories/logging/","section":"Categories","summary":"","title":"Logging","type":"categories"},{"content":"","date":"15 juillet 2016","externalUrl":null,"permalink":"/tags/sentry/","section":"Tags","summary":"","title":"Sentry","type":"tags"},{"content":"Comment fusionner de multiples dépôts Git en un seul ?\nWhat ?! # Récemment j\u0026rsquo;ai eu besoin de fusionner tout un tas de dépôts Git en un seul. Une collection d\u0026rsquo;applications et de socles métier répartis sur 6 dépôts. Bien sûr des dépendances binaires entre tout ce petit monde et toujours sur la dernière version en cours de développement. Observant que le cycle de vie des projets étaient toujours le même on a voulu simplifier nos processus (branches, gestion de dépendances, build, release, etc.) sans aller jusqu\u0026rsquo;à la pratique du mono dépôt (comme chez Google par exemple).\nMise en place # On veut bien entendu garder l\u0026rsquo;historique de tous nos projets, il n\u0026rsquo;est pas question de repartir de zéro 😅.\nIl existe un projet, git-merge-repos qui permet de faire le travail en essayant de préserver les branches et les tags.\nLe résultat attendu est un dépôt unique contenant tous les projets désirés :\nnew-repo ├── project-1 ├── project-2 ├── project-3 └── project-4 On peut utiliser deux stratégies pour faire ce merge :\nRéaliser un merge (avec n parents) et déplacer chaque projet dans son sous-dossier dans le commit de merge Réécrire l\u0026rsquo;histoire de chaque dépôt pour déplacer chaque contenu de commit dans un sous-dossier et réaliser le merge à la fin. J\u0026rsquo;ai d\u0026rsquo;abord testé avec la première option, le problème est qu\u0026rsquo;on ne voit plus l\u0026rsquo;historique d\u0026rsquo;une ressource après le commit de merge global.\nPar exemple :\n\u0026gt; git log -- project-1 ne retourne qu\u0026rsquo;une seule entrée, le commit de merge.\nLa solution 2 est donc vendue !\nPar contre comme on doit réécrire l\u0026rsquo;historique de chaque dépôt, il faut trouver un moment où une seule branche est en cours de développement (dans mon cas master).\nPour chaque dépôt :\n\u0026gt; git clone --mirror git@example.com:project-1.git \u0026gt; cd project-1 \u0026gt; git filter-branch --index-filter \\ \u0026#39;tab=$(printf \u0026#34;\\t\u0026#34;) \u0026amp;\u0026amp; git ls-files -s --error-unmatch . \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; [ $? != 0 ] || (git ls-files -s | sed \u0026#34;s-$tab\\\u0026#34;*-\u0026amp;project1/-\u0026#34; | GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info \u0026amp;\u0026amp; mv \u0026#34;$GIT_INDEX_FILE.new\u0026#34; \u0026#34;$GIT_INDEX_FILE\u0026#34;)\u0026#39; \\ --tag-name-filter cat \\ -- --all où project1 doit être remplacé par le sous-dossier dans lequel on veut transférer le projet.\nJ\u0026rsquo;en ai profité aussi pour renommer les tags, git-merge-repos s\u0026rsquo;occupe aussi de recréer les tags dans le dépôt final sur la base de leurs noms. Dans mon cas je ne le voulais pas vraiment, des tags sur différents dépôts portaient le même nom mais n\u0026rsquo;étaient pas forcément équivalents.\n\u0026gt; git tag | xargs -I '{}' sh -c 'git tag project-1-{} {} \u0026amp;\u0026amp; git tag -d {}' L\u0026rsquo;opération finale est le lancement la fusion :\n\u0026gt; git clone git@github.com:robinst/git-merge-repos.git \u0026gt; cd git-merge-repos \u0026gt; ./run.sh /absolute/path/to/project-1:. /absolute/path/to/project-2:. Le résultat se trouve dans un dossier merged à la racine de git-merge-repos.\nAvantages # L\u0026rsquo;intendance est plus facile (création de branches, merge, etc) On a aujourd\u0026rsquo;hui de vrais commits atomiques pour la réalisation d\u0026rsquo;une fonctionnalité. Avant on se retrouvait tout le temps avec des commits sur différents dépôts et ce n\u0026rsquo;était pas toujours facile à tracer ou pour investiguer sur des régressions La gestion des dépendances est plus simple, tout le code est dans le même dépôt Maintenant qu\u0026rsquo;on est capable de builder nos projets sans dépendances binaires sur des SNAPSHOT, on a mis en place le build de nos Pull Request avec GH Pull Request Builder pour Jenkins Dans la case inconvénient on peut noter :\nAllongement du temps de build, même si ce n\u0026rsquo;est pas totalement vrai, mais avant avec les dépôts séparés on ne reconstruisait pas forcément tous les projets pour valider nos changements On utilise Maven pour construire nos projets, notre projet multi-modules géant demande un peu plus de gymnastique intellectuelle pour lancer un build. Une fois qu\u0026rsquo;on a assimilé l\u0026rsquo;utilisation des options -pl et -am(d) c\u0026rsquo;est tout de même faisable 😇 ","date":"7 février 2016","externalUrl":null,"permalink":"/posts/merge-git-repositories/","section":"Posts","summary":"Comment fusionner de multiples dépôts Git en un seul ?\n","title":"Fusion de dépôts Git","type":"posts"},{"content":"","date":"7 février 2016","externalUrl":null,"permalink":"/tags/git/","section":"Tags","summary":"","title":"Git","type":"tags"},{"content":"","date":"7 février 2016","externalUrl":null,"permalink":"/categories/scm/","section":"Categories","summary":"","title":"Scm","type":"categories"},{"content":"","date":"31 janvier 2016","externalUrl":null,"permalink":"/tags/boot2docker/","section":"Tags","summary":"","title":"Boot2docker","type":"tags"},{"content":"","date":"31 janvier 2016","externalUrl":null,"permalink":"/categories/docker/","section":"Categories","summary":"","title":"Docker","type":"categories"},{"content":"","date":"31 janvier 2016","externalUrl":null,"permalink":"/tags/docker/","section":"Tags","summary":"","title":"Docker","type":"tags"},{"content":"Ça fait un moment qu\u0026rsquo;à chaque utilisation de la commande boot2docker j\u0026rsquo;ai un joli message d\u0026rsquo;avertissement :\nWARNING: The \u0026#39;boot2docker\u0026#39; command line interface (not to be confused with \u0026#39;boot2docker\u0026#39; the operating system) is officially deprecated. Please switch to Docker Machine (https://docs.docker.com/machine/) ASAP. Docker Toolbox (https://docker.com/toolbox) is the recommended install method. Je me suis dit qu\u0026rsquo;il était temps de finalement faire la migration et de tester quelques goodies !\nDésinstallation boot2docker # C\u0026rsquo;est la partie simple,\n\u0026gt; boot2docker delete \u0026gt; brew uninstall boot2docker Installation de docker-machine # Là encore brew nous sauve la vie,\n\u0026gt; brew install docker-machine docker-machine va nous permettre de provisionner docker dans des machines virtuelles (qu\u0026rsquo;elles soient locales ou dans le cloud) et il se charge de configurer le client docker pour parler avec ces machines.\nPour reproduire l\u0026rsquo;utilisation de boot2docker il faut créer un environnement en utilisant le driver virtualbox :\n\u0026gt; docker-machine create --driver virtualbox dev L\u0026rsquo;argument dev est le nom que de la machine, on peut y mettre ce qu\u0026rsquo;on veut.\nCreating CA: /Users/jcgay/.docker/machine/certs/ca.pem Creating client certificate: /Users/jcgay/.docker/machine/certs/cert.pem Running pre-create checks... (dev) Image cache directory does not exist, creating it at /Users/jcgay/.docker/machine/cache... (dev) No default Boot2Docker ISO found locally, downloading the latest release... (dev) Latest release for github.com/boot2docker/boot2docker is v1.9.1 (dev) (dev) Boot2Docker v1.9.1 has a known issue with AUFS. (dev) See here for more details: https://github.com/docker/docker/issues/18180 (dev) Consider specifying another storage driver (e.g. \u0026#39;overlay\u0026#39;) using \u0026#39;--engine-storage-driver\u0026#39; instead. (dev) (dev) Downloading /Users/jcgay/.docker/machine/cache/boot2docker.iso from https://github.com/boot2docker/boot2docker/releases/download/v1.9.1/boot2docker.iso... (dev) 0%....10%....20%....30%....40%....50%....60%....70%....80%....90%....100% Creating machine... (dev) Copying /Users/jcgay/.docker/machine/cache/boot2docker.iso to /Users/jcgay/.docker/machine/machines/dev/boot2docker.iso... (dev) Creating VirtualBox VM... (dev) Creating SSH key... (dev) Starting the VM... (dev) Waiting for an IP... Waiting for machine to be running, this may take a few minutes... Machine is running, waiting for SSH to be available... Detecting operating system of created instance... Detecting the provisioner... Provisioning with boot2docker... Copying certs to the local machine directory... Copying certs to the remote machine... Setting Docker configuration on the remote daemon... Checking connection to Docker... Docker is up and running! To see how to connect Docker to this machine, run: docker-machine env dev On peut bien entendu configurer un certains nombre d\u0026rsquo;options (CPU, mémoire, etc). La liste complète est disponible dans la documentation du driver VirtualBox.\nOn voit que docker-machine se charge de télécharger un ISO boot2docker, il nous indique aussi la commande à exécuter pour configurer le client docker.\n\u0026gt; docker-machine env dev export DOCKER_TLS_VERIFY=\u0026#34;1\u0026#34; export DOCKER_HOST=\u0026#34;tcp://192.168.99.100:2376\u0026#34; export DOCKER_CERT_PATH=\u0026#34;/Users/jcgay/.docker/machine/machines/dev\u0026#34; export DOCKER_MACHINE_NAME=\u0026#34;dev\u0026#34; # Run this command to configure your shell: # eval $(docker-machine env dev) Allez on finit donc par un :\n\u0026gt; eval $(docker-machine env dev) Et voilà docker fonctionne comme avant !\n\u0026gt; docker-machine stop dev Les volumes # Je dois certainement mal m\u0026rsquo;y prendre mais c\u0026rsquo;est vraiment la fonctionnalité qui me pose tout le temps problème, à chaque fois que j\u0026rsquo;essaie de démarrer un conteneur en mappant un volume sur ma machine, j\u0026rsquo;ai des problèmes de droit 🤔\nPar exemple avec mongo:\n\u0026gt; docker run -v /Users/jcgay/data/mongo:/data/db mongo [initandlisten] MongoDB starting : pid=1 port=27017 dbpath=/data/db 64-bit host=968af53e81ec [initandlisten] db version v3.2.1 [initandlisten] git version: a14d55980c2cdc565d4704a7e3ad37e4e535c1b2 [initandlisten] OpenSSL version: OpenSSL 1.0.1e 11 Feb 2013 [initandlisten] allocator: tcmalloc [initandlisten] modules: none [initandlisten] build environment: [initandlisten] distmod: debian71 [initandlisten] distarch: x86_64 [initandlisten] target_arch: x86_64 [initandlisten] options: {} [initandlisten] exception in initAndListen: 98 Unable to create/open lock file: /data/db/mongod.lock errno:13 Permission denied Is a mongod instance already running?, terminating ou encore mysql:\n\u0026gt; docker run -v /Users/jcgay/tmp/mysql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=plop mysql Initializing database [Warning] Setting lower_case_table_names=2 because file system for /var/lib/mysql/ is case insensitive [ERROR] InnoDB: Operating system error number 13 in a file operation. [ERROR] InnoDB: The error means mysqld does not have the access rights to the directory. [ERROR] InnoDB: Operating system error number 13 in a file operation. [ERROR] InnoDB: The error means mysqld does not have the access rights to the directory. [ERROR] InnoDB: Cannot open datafile \u0026#39;./ibdata1\u0026#39; [ERROR] InnoDB: Could not open or create the system tablespace. If you tried to add new data files to the system tablespace, and it failed here, you should now edit innodb_data_file_path in my.cnf back to what it was, and remove the new ibdata files InnoDB created in this failed attempt. InnoDB only wrote those files full of zeros, but did not yet use them in any way. But be careful: do not remove old data files which contain your precious data! [ERROR] InnoDB: InnoDB Database creation was aborted with error Cannot open a file. You may need to delete the ibdata1 file before trying to start up again. [ERROR] Plugin \u0026#39;InnoDB\u0026#39; init function returned error. [ERROR] Plugin \u0026#39;InnoDB\u0026#39; registration as a STORAGE ENGINE failed. [ERROR] Failed to initialize plugins. [ERROR] Aborting Apparemment le partage de volume ne fonctionne que si le partage de stockage avec VirtualBox se fait avec l\u0026rsquo;utilisateur root. Il y a un long thread ouvert chez boot2docker.\nActiver NFS pour le mapping de volumes # Le projet docker-machine-nfs permet de remplacer le partage VirtualBox par du NFS. En bonus, les performances de lecture/écriture devraient être meilleures.\nAprès installation, il faut activer NFS sur la machine désirée :\n\u0026gt; docker-machine-nfs dev [INFO] Configuration: - Machine Name: dev - Shared Folder: /Users - Force: false [INFO] machine presence ... OK [INFO] machine running ... OK [INFO] Lookup mandatory properties ... OK - Machine IP: 192.168.99.100 - Network ID: vboxnet4 - NFSHost IP: 192.168.99.1 [INFO] Configure NFS ... !!! Sudo will be necessary for editing /etc/exports !!! [INFO] Configure Docker Machine ... OK [INFO] Restart Docker Machine ... OK [INFO] Verify NFS mount ... OK -------------------------------------------- The docker-machine \u0026#39;dev\u0026#39; is now mounted with NFS! ENJOY high speed mounts :D -------------------------------------------- Bon et en fait chez moi, ce n\u0026rsquo;est toujours pas mieux 😱\n\u0026gt; docker run -v /Users/jcgay/tmp/mongo:/data/db mongo chown: changing ownership of `/data/db\u0026#39;: Operation not permitted \u0026gt; docker run -v /Users/jcgay/tmp/mysql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=plop mysql chown: changing ownership of \u0026#39;/var/lib/mysql/\u0026#39;: Operation not permitted On peut modifier la configuration du mapping pour changer les droits, voir le ticket #17)\n\u0026gt; docker-machine-nfs dev --force --nfs-config=\u0026quot;-alldirs -maproot=0\u0026quot; Et enfin ça fonctionne ! 🎆🚀\nDocker survitaminé # Maintenant que docker-machine s\u0026rsquo;occupe de tout, on peut tenter d\u0026rsquo;utiliser d\u0026rsquo;autres drivers. On peut essayer xhyve un hyperviseur natif pour OS X et ainsi se passer de VirtualBox.\n\u0026gt; brew install xhyve docker-machine-driver-xhyve Oui il faut être root\u0026hellip;\n\u0026gt; sudo chown root:wheel $(brew --prefix)/opt/docker-machine-driver-xhyve/bin/docker-machine-driver-xhyve \u0026gt; sudo chmod u+s $(brew --prefix)/opt/docker-machine-driver-xhyve/bin/docker-machine-driver-xhyve On crée la machine :\n\u0026gt; docker-machine create xhyve --driver xhyve --xhyve-experimental-nfs-share Toutes les options sont documentées sur la page officielle du driver.\nJe ne me suis pas amusé à comparer les performances des deux solutions mais pour le moment je n\u0026rsquo;ai pas rencontré de soucis avec cette dernière.\n","date":"31 janvier 2016","externalUrl":null,"permalink":"/posts/migrer-boot2docker-vers-docker-machine/","section":"Posts","summary":"Ça fait un moment qu’à chaque utilisation de la commande boot2docker j’ai un joli message d’avertissement :\nWARNING: The 'boot2docker' command line interface (not to be confused with 'boot2docker' the operating system) is officially deprecated. Please switch to Docker Machine (https://docs.docker.com/machine/) ASAP. Docker Toolbox (https://docker.com/toolbox) is the recommended install method. Je me suis dit qu’il était temps de finalement faire la migration et de tester quelques goodies !\n","title":"Migrer de boot2docker vers docker-machine","type":"posts"},{"content":"","date":"31 janvier 2016","externalUrl":null,"permalink":"/tags/virtualbox/","section":"Tags","summary":"","title":"Virtualbox","type":"tags"},{"content":"","date":"31 janvier 2016","externalUrl":null,"permalink":"/tags/xhyve/","section":"Tags","summary":"","title":"Xhyve","type":"tags"},{"content":"Mini guide pour déployer des binaires SNAPSHOT sur le repository OSS sonatype avec Travis CI.\nConfiguration de Maven # Il faut configurer Maven pour utiliser votre compte pour uploader des artifacts sur OSSRH.\nCréer un fichier etc/deploy-settings.xml et utiliser des variables pour référencer vos identifiants :\n\u0026lt;settings\u0026gt; \u0026lt;servers\u0026gt; \u0026lt;server\u0026gt; \u0026lt;id\u0026gt;ossrh\u0026lt;/id\u0026gt; \u0026lt;username\u0026gt;${env.OSSRH_USER}\u0026lt;/username\u0026gt; \u0026lt;password\u0026gt;${env.OSSRH_PASS}\u0026lt;/password\u0026gt; \u0026lt;/server\u0026gt; \u0026lt;/servers\u0026gt; \u0026lt;/settings\u0026gt; Ce fichier sera par la suite utilisé par mvn grâce à l\u0026rsquo;option --settings.\nIdentification # On a deux solutions pour stocker ses identifiants et les utiliser avec Travis CI.\nStockage dans les paramètres du repository # Dans les options de Travis CI https://travis-ci.org/user/repository/settings, on peut définir les variables d\u0026rsquo;environnements OSSRH_USER et OSSRH_PASS. Par défaut celles-ci seront masquées dans les logs.\nChiffrer ses identifiants # Il semble évident qu\u0026rsquo;on ne va pas sauvegarder ses identifiants en clair, Travis CI permet de chiffrer des données sensibles.\ngem install travis Et là on doit faire face à Ruby\u0026hellip; Si vous ne développez pas dans cet eco-système, il y a des chances que celà se passe mal à partir de maintenant :( (en tout cas avec OS X)\nDans mon cas j\u0026rsquo;ai eu le droit à l\u0026rsquo;erreur suivante :\n\u0026gt; gem install travis Fetching: multipart-post-2.0.0.gem (100%) ERROR: While executing gem ... (Gem::FilePermissionError) You don't have write permissions for the /Library/Ruby/Gems/2.0.0 directory. Le problème vient de la version installée par Apple, les utilisateurs de ruby utiliseront surement rvm, rbenv ou je ne sais quelle autre joyeuseté. Pour le commun des mortels qui ne veulent pas y passer une demi heure et qui n\u0026rsquo;ont pas envie de tout casser :\nbrew install ruby Relancer un shell et l\u0026rsquo;installation du gem travis devrait mieux se passer\u0026hellip;\nOn peut maintenant envoyer nos données importantes à travis :\ntravis encrypt --add -r jcgay/send-notification \u0026quot;OSSRH_USER='toto'\u0026quot; travis encrypt --add -r jcgay/send-notification \u0026quot;OSSRH_PASS='s3cret\u0026amp;'\u0026quot; Si les commandes sont lancées depuis le dossier qui contient le projet l\u0026rsquo;option -r n\u0026rsquo;est pas nécessaire et l\u0026rsquo;option --add ajoutera automatiquement les clefs cryptées dans le fichier .travis.yml.\njcgay/send-notification correspond bien entendu au projet pour lequel ont veut chiffrer des données.\nConfiguration de Travis CI # On veut déployer les SNAPSHOT sauf si Travis est en train de builder une pull-request. On peut pour cela utiliser la variable ${TRAVIS_PULL_REQUEST}.\nLa variable ${TRAVIS_TAG} permet de ne pas non plus déployer les releases (qui ne sont pas faites avec Travis dans mon cas).\nOn se retrouve donc avec les variables d\u0026rsquo;environnement cryptées au sein de .travis.yml :\nenv: global: - secure: GT8Hi5kgx2cLAGzh2ggZNTgiLm8J5fd64SYG3bkhgwZGbV/HRD8r8a1SYv1iQ72JqrDhlHQ7q8h17nDBFLjy99h/XKN4MacBu9CZhnPO7lXpuRefD3W/db6zWSk17a5DAKAt+1UOeyfohEkzx+JHcTfbvMg6hy+3DEPaVMUtV04= - secure: fQ4bSOcHWEURfsDQVhWA152GCiezQfh9YC45a2zae8oOk+6bIrHmrkJSyVYpUCLGdqO3Df+JTy6bfoqyJEsX7PkzGPZ10odSHw5qDqfTIolt7UympKtUvUNi6h5G6virGhBWjjs5REGjrgWdTlqKjNT2Ax/VlzNRskqaVAhEhl0= et une étape de plus dans la section after_success:\nafter_success: - \u0026quot;[[ ${TRAVIS_PULL_REQUEST} == 'false' ]] \u0026amp;\u0026amp; [[ ${TRAVIS_TAG} == '' ]] \u0026amp;\u0026amp; mvn deploy -DskipTests --settings etc/deploy-settings.xml\u0026quot; Et voilà tout roule !\n","date":"29 juillet 2015","externalUrl":null,"permalink":"/posts/deploy-snapshot-travis-ci/","section":"Posts","summary":"Mini guide pour déployer des binaires SNAPSHOT sur le repository OSS sonatype avec Travis CI.\n","title":"Déployer ses SNAPSHOT avec Travis CI","type":"posts"},{"content":"","date":"29 juillet 2015","externalUrl":null,"permalink":"/tags/snapshot/","section":"Tags","summary":"","title":"Snapshot","type":"tags"},{"content":"","date":"29 juillet 2015","externalUrl":null,"permalink":"/tags/sonatype-oss/","section":"Tags","summary":"","title":"Sonatype Oss","type":"tags"},{"content":"","date":"29 juillet 2015","externalUrl":null,"permalink":"/tags/travis-ci/","section":"Tags","summary":"","title":"Travis Ci","type":"tags"},{"content":"","date":"4 juin 2015","externalUrl":null,"permalink":"/tags/groovy/","section":"Tags","summary":"","title":"Groovy","type":"tags"},{"content":"","date":"4 juin 2015","externalUrl":null,"permalink":"/categories/java/","section":"Categories","summary":"","title":"Java","type":"categories"},{"content":"","date":"4 juin 2015","externalUrl":null,"permalink":"/tags/spock/","section":"Tags","summary":"","title":"Spock","type":"tags"},{"content":"","date":"4 juin 2015","externalUrl":null,"permalink":"/tags/test/","section":"Tags","summary":"","title":"Test","type":"tags"},{"content":"Spock est un framework de tests écrit sous la forme de spécifications pour des applications Java ou Groovy. Il offre une alternative tout en un au trio JUnit (TestNG), Mockito et AssertJ.\nMise en place # Maven # Il s\u0026rsquo;agit de dépendances à ajouter et d\u0026rsquo;un peu de configuration supplémentaire :\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.codehaus.groovy\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;groovy\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.4.3\u0026lt;/version\u0026gt; \u0026lt;scope\u0026gt;test\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.spockframework\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spock-core\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0-groovy-2.4\u0026lt;/version\u0026gt; \u0026lt;scope\u0026gt;test\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- (Optionnel) Nécessaire pour mocker des classes concrètes --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;cglib\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;cglib-nodep\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;3.1\u0026lt;/version\u0026gt; \u0026lt;scope\u0026gt;test\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;build\u0026gt; \u0026lt;plugins\u0026gt; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.codehaus.gmavenplus\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;gmavenplus-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.5\u0026lt;/version\u0026gt; \u0026lt;executions\u0026gt; \u0026lt;execution\u0026gt; \u0026lt;goals\u0026gt; \u0026lt;goal\u0026gt;testCompile\u0026lt;/goal\u0026gt; \u0026lt;/goals\u0026gt; \u0026lt;/execution\u0026gt; \u0026lt;/executions\u0026gt; \u0026lt;/plugin\u0026gt; \u0026lt;plugin\u0026gt; \u0026lt;artifactId\u0026gt;maven-surefire-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;includes\u0026gt; \u0026lt;include\u0026gt;**/*Spec.java\u0026lt;/include\u0026gt; \u0026lt;/includes\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/plugin\u0026gt; \u0026lt;/plugins\u0026gt; \u0026lt;/build\u0026gt; Et voilà on peut écrire des spécifications de type *Spec.groovy dans src/test/groovy.\nGradle # apply plugin: \u0026#39;java\u0026#39; apply plugin: \u0026#39;groovy\u0026#39; repositories { mavenCentral() } dependencies { testCompile group: \u0026#39;org.codehaus.groovy\u0026#39;, name: \u0026#39;groovy\u0026#39;, version: \u0026#39;2.4.3\u0026#39; testCompile group: \u0026#39;org.spockframework\u0026#39;, name:\u0026#39;spock-core\u0026#39;, version: \u0026#39;1.0-groovy-2.4\u0026#39; testRuntime group: \u0026#39;cglib\u0026#39;, name: \u0026#39;cglib-nodep\u0026#39;, version: \u0026#39;3.1\u0026#39; } Et voilà on peut écrire des spécifications de type *Spec.groovy dans src/test/groovy.\nSpécification # Squelette # Une spécification est une classe qui étends spock.lang.Specification. Elle est composée de champs, de méthodes de configuration (fixture) et enfin de méthodes pour décrire des fonctionnalités (feature).\nclass MyFirstSpec extends Specification { ClassUnderSpecification underTest = new ClassUnderSpecification() @Shared VeryExpensiveResource resource = new VeryExpensiveResource() def setup() {} def cleanup() {} def setupSpec() {} def cleanupSpec() {} def \u0026#34;pushing an element on the stack\u0026#34;() {} } Les champs ne sont pas partagés entre les méthodes de feature.\nPour partager un objet (par exemple un peu long à initialiser), il faut annoter le champ avec @Shared.\nEquivalence fixture avec les annotations JUnit :\nMéthode JUnit setup @Before cleanup @After setupSpec @BeforeClass cleanupSpec @AfterClass On peut de toute façon aussi utiliser les annotations JUnit directement.\nUne fonctionnalité est équivalente à une méthode annotée avec @Test dans JUnit.\nBlocs # Une fonctionnalité est structurée autour de blocs. Il en existe six : setup, when, then, expect, cleanup, et where. Il fait au minimum un bloc pour qu\u0026rsquo;une méthode soit considérée comme une fonctionnalité (et donc exécutée).\nChaque bloc joue un rôle dans l\u0026rsquo;expression d\u0026rsquo;un test et sa bonne utilisation est validée par la librairie quand c\u0026rsquo;est possible (ordre de déclaration, autorisation du contenu, etc).\nsetup, given # Il permet l\u0026rsquo;initialisation du test, given est un alias de setup.\nwhen, then # when contient le stimulus du test tandis que then permet d\u0026rsquo;écrire les assertions.\nwhen peut contenir n\u0026rsquo;importe quel type d\u0026rsquo;instructions contrairement au bloc then qui lui est limité aux : conditions, vérification des exceptions, interactions (avec les mocks), et définition de variables.\nexpect # Permet d\u0026rsquo;exprimer les bloc when et then en un seul. Dans la documentation officielle il est conseillé d\u0026rsquo;utiliser la forme when / then pour tester les méthodes avec des effets de bord et de garder la forme expect pour les fonctions pures.\ncleanup # Le bloc pour nettoyer les ressources utilisées lors d\u0026rsquo;un test. Son contenu est tout le temps exécuté même si une exception est levée dans une autre partie de la fonctionnalité.\nwhere # Toujours défini en dernier, on peut y écrire la génération des données pour exécuter des tests paramétrés.\nAssertions (conditions) # Les assertions avec Spock peuvent être remplacées par de simples expressions booléennes.\ngiven: def list = [] when: list.add(1) then: !list.isEmpty list.size() == 10 Si un test échoue, on a un affichage complet de l\u0026rsquo;état du test.\nCondition not satisfied: list.size() == 10 | | | [1] 1 false C\u0026rsquo;est plutôt pratique et lisible dans la majorité des cas. Quand ce ne l\u0026rsquo;est pas, on peut très bien continuer d\u0026rsquo;utiliser sa librairie d\u0026rsquo;assertions préférées.\nVérification des exceptions # Il existe divers solutions pour tester des exceptions dans les tests unitaires.\nAvec Spock on peut utiliser les conditions thrown(), notThrown et noExceptionThrown().\ndef \u0026#34;should not throw exception\u0026#34;() { when: [1].get(0) then: noExceptionThrown() } def \u0026#34;should not throw IndexOutOfBoundsException\u0026#34;() { when: [1].get(0) then: notThrown(IndexOutOfBoundsException) } def \u0026#34;should throw exception\u0026#34;() { when: [1].get(1) then: thrown(IndexOutOfBoundsException) } def \u0026#34;should throw exception with message\u0026#34;() { when: [1].get(1) then: IndexOutOfBoundsException e = thrown() e.message == \u0026#39;Index: 1, Size: 1\u0026#39; } Mock # Spock utilise sa propre librairie de mock qui se sert des possibilités du langage Groovy pour définir les interactions.\nComme avec Mockito les mocks sont permissifs par défaut (une interaction non définie ne fera pas échouer un test), par contre les interactions non définies ne retournent que les valeurs par défaut (0, null ou false).\nPrenons l\u0026rsquo;exemple d\u0026rsquo;une classe écoutant l\u0026rsquo;état d\u0026rsquo;un build qui chercherait à envoyer des notifications à la fin de celui-ci.\npublic class BuildListener { private final Notifier notification; public BuildListener(Notifier notification) { if (notification.checkCompatibility(System.getProperty(\u0026#34;os.name\u0026#34;))) { throw new IllegalArgumentException(\u0026#34;Notifier not compatible with current os\u0026#34;); } this.notification = notification; } public void onSuccess(BuildResult result) { if (notification.isReady()) { notification.send(\u0026#34;Build success !\u0026#34;); } } } Création # La définition d\u0026rsquo;un mock passe par l\u0026rsquo;utilisation de l\u0026rsquo;api Mock().\ndef notifier = Mock(Notifier) ou\nNotifier notifier = Mock() interaction # La spécification du comportement du mock se fait directement dans le bloc then.\nclass MockSpec extends Specification { Notifier notifier = Mock() BuildListener listener = new BuildListener(notifier) def \u0026#34;should send notification when build ends and notifier is ready\u0026#34;() { when: listener.onSuccess(new BuildResult()) then: 1 * notifier.isReady() \u0026gt;\u0026gt; true 1 * notifier.send(\u0026#34;Build success !\u0026#34;) } } Cela peut paraitre peu naturel au début de définir les interactions dans le bloc then. Mais c\u0026rsquo;est parce que l\u0026rsquo;api ne se limite pas à la définition du comportement, on a en plus la vérification que le mock a bien été appelé (l\u0026rsquo;équivalent d\u0026rsquo;un Mockito.verify()) !\nSi l\u0026rsquo;interaction avec le mock ne s\u0026rsquo;est pas passée comme prévu on a un message d\u0026rsquo;erreur :\nToo few invocations for: 1 * notifier.send(\u0026quot;Build success !\u0026quot;) (0 invocations) Unmatched invocations (ordered by similarity): 1 * notifier.isReady() On peut aussi ne pas vérifier les interactions (dans ce cas l\u0026rsquo;usage d\u0026rsquo;un Stub() est préférable). Le test précédent peut s\u0026rsquo;écrire :\ndef \u0026#34;should send notification when build ends\u0026#34;() { given: notifier.isReady() \u0026gt;\u0026gt; true when: listener.onSuccess(new BuildResult()) then: 1 * notifier.send(\u0026#34;Build success !\u0026#34;) } On peut déclarer le nombre d\u0026rsquo;appel attendu :\n1 * notifier.send(\u0026#34;Build success !\u0026#34;) // un appel 0 * notifier.send(\u0026#34;Build success !\u0026#34;) // pas d\u0026#39;appel (2..5) * notifier.send(\u0026#34;Build success !\u0026#34;) // entre deux et cinq appels (1.._) * notifier.send(\u0026#34;Build success !\u0026#34;) // au moins un appel (_..2) * notifier.send(\u0026#34;Build success !\u0026#34;) // deux appels au plus La vérification du/des arguments d\u0026rsquo;une méthode est lui aussi assez riche :\n1 * notifier.send(\u0026#34;a\u0026#34;) // argument vaut \u0026#34;a\u0026#34; 1 * notifier.send(!\u0026#34;a\u0026#34;) // argument différent de \u0026#34;a\u0026#34; 1 * notifier.send(_) // n\u0026#39;importe quel argument (dont null) 1 * notifier.send(!null) // argument non null 1 * notifier.send(_ as String) // argument de type String 1 * notifier.send({ it.startsWith \u0026#39;a\u0026#39; }) // argument commençant par \u0026#34;a\u0026#34; L\u0026rsquo;utilisation d\u0026rsquo;une closure dans le matching d\u0026rsquo;argument (comme le dernier exemple ci-dessus) peut permettre dans certains cas limites de reproduire le comportement d\u0026rsquo;un ArgumentCaptor de Mockito :\ndef \u0026#34;should send notification when build ends\u0026#34;() { def captured given: notifier.isReady() \u0026gt;\u0026gt; true when: listener.onSuccess(new BuildResult()) then: 1 * notifier.send(_) \u0026gt;\u0026gt; { arguments -\u0026gt; captured = arguments[0] } captured == \u0026#34;Build success !\u0026#34; } Pour retrouver l\u0026rsquo;équivalent du @InjectMocks de Mockito il existe une extension Spock (Spock Subjects-Collaborators Extension) qui permet d\u0026rsquo;injecter automatiquement des mocks dans l\u0026rsquo;objet que l\u0026rsquo;on souhaite tester.\nLes mocks doivent être annotés avec @Collaborator et le sujet du test avec @Subject.\nclass MockWithSubjectSpec extends Specification { @Collaborator Notifier notifier = Mock() @Subject BuildListener listener def \u0026#34;should send notification when build succeed\u0026#34;() { when: listener.onSuccess(new BuildResult()) then: 1 * notifier.isReady() \u0026gt;\u0026gt; true 1 * notifier.send(\u0026#34;Build success !\u0026#34;) } } Par défaut Spock ne vérifie pas l\u0026rsquo;ordre dans lequel les interactions sont invoquées. Si vraiment on en a besoin on peut définir les interactions dans des blocs then différents. Dans ce cas les mocks devront être appelés dans l\u0026rsquo;ordre dans lequel ils ont été déclarés.\ndef \u0026#34;should send notification when build ends\u0026#34;() { when: listener.onSuccess(new BuildResult()) then: 1 * notifier.isReady() \u0026gt;\u0026gt; true then: 1 * notifier.send(\u0026#34;Build success !\u0026#34;) } Définition retour des méthodes # Pour retourner une valeur dans la définition d\u0026rsquo;une interaction on utilise l\u0026rsquo;opérateur \u0026gt;\u0026gt;.\nnotifier.isReady() \u0026gt;\u0026gt; true L\u0026rsquo;opérateur \u0026gt;\u0026gt;\u0026gt; permet de définir des valeurs de retours différentes pour des appels successifs.\nnotifier.isReady() \u0026gt;\u0026gt;\u0026gt; [true, false, false, true] On peut également retourner une valeur après un calcul dépendant d\u0026rsquo;un argument.\nnotifier.checkCompatibility(_) \u0026gt;\u0026gt; { String os -\u0026gt; !os.startsWith(\u0026#34;win\u0026#34;) } Et enfin pour lever une exception :\nnotifier.isReady() \u0026gt;\u0026gt; { throw new NullPointerException() } Stub, Spy, Partial mock # Spock gère aussi tous ces autres type de mocks.\nUn stub peut-être créé avec l\u0026rsquo;api Stub(). La grande différence avec un Mock() est qu\u0026rsquo;on ne peut pas vérifié le comportement du stub (d\u0026rsquo;ailleurs une erreur se produira si l\u0026rsquo;on essaie). L\u0026rsquo;initialisation par défaut est aussi plus \u0026ldquo;intelligente\u0026rdquo; que celle des Mock().\nSi une interaction n\u0026rsquo;est pas définie et qu\u0026rsquo;un appel a lieu, la valeur retournée tentera de s\u0026rsquo;adapter au type de retour défini (une collection vide sera retournée par exemple).\nLes autres s\u0026rsquo;utilisent avec l\u0026rsquo;api Spy(). Tout est décrit dans la documentation\nTests paramétrés # Parfois on a besoin d\u0026rsquo;exécuter plusieurs fois le même test avec des données différentes.\nAvec JUnit on a le choix entre :\nles Parameterized tests, les theories, JUnitParams, junit-dataprovider Généralement j\u0026rsquo;utilise junit-dataprovider qui mime le comportement des DataProvider de TestNG mais avec Spock on va avoir accès à des solutions bien plus élégantes.\nData Tables # La déclaration des données se fait dans un bloc expect.\ndef \u0026#34;should return maximum of two numbers\u0026#34;() { expect: Math.max(a, b) == c where: a | b || c 1 | 2 || 2 2 | 1 || 2 1 | 0 || 1 0 | 0 || 0 } Le test sera exécuté pour chaque ligne du tableau, chaque colonne définissant une variable du test.\nLe double séparateur de colonne est optionnel, c\u0026rsquo;est du sucre syntaxique pour séparer les valeurs d\u0026rsquo;entrée et le résultat attendu.\nPar défaut le test est vu comme un seul, ce qui fait que ce n\u0026rsquo;est pas toujours facile de retrouver quelle ligne du tableau entraine un échec.\nPour améliorer la lisibilité du rapport de test, on peut utiliser l\u0026rsquo;annotation @Unroll. Elle permet de créer un rapport par ligne du tableau et d\u0026rsquo;injecter les valeurs du test dans le nom de la méthode.\nOn peut donc remplacer le nom du test précédent en utilisant #variable comme placeholder :\n@Unroll def \u0026#34;maximum of #a and #b should be #c\u0026#34;() et le rapport de l\u0026rsquo;exécution des tests ressemblera à :\nmaximum of 1 and 2 should be 2 maximum of 2 and 1 should be 2 maximum of 1 and 0 should be 1 maximum of 0 and 0 should be 0 Data Pipes # Les data pipes sont la seconde forme de data provider disponible.\ndef \u0026#34;even numbers\u0026#34;() { expect: a % 2 == 0 where: a \u0026lt;\u0026lt; [0, 2, 4, 6] } Le test sera exécuté quatre fois avec les valeurs 0, 2, 4, 6. Tout objet connu comme étant iterable par Groovy peut être utilisé dans un data pipe.\nTest génératifs # Spock-Genesis est une collection de générateurs pour écrire des tests génératifs.\n@Unroll def \u0026#34;should reverse #string\u0026#34;() { when: String reversed = string.reverse() then: \u0026#39;It maintains length\u0026#39; reversed.size() == string.size() and: \u0026#39;It is not destructive\u0026#39; reversed.reverse() == string where: string \u0026lt;\u0026lt; Gen.these(\u0026#39;\u0026#39;, \u0026#39;aba\u0026#39;, \u0026#39;aa\u0026#39;).then(Gen.string).take(10000) } Tout se passe à partir de spock.genesis.Gen, il y a des générateurs pour les String, int, long, double, Date, etc.\nLa documentation est succincte mais il y a des exemples d\u0026rsquo;utilisation ici.\nPour améliorer l\u0026rsquo;analyse statique dans l\u0026rsquo;IDE il est possible d\u0026rsquo;ajouter en paramètre de la méthode de test les types des variables nécessaires.\nDans l\u0026rsquo;exemple précédent, IntelliJ Idea se plaint de ne pas trouver les méthodes reverse et size pour la variable string (reconnu comme étant de type Object). Si on déclare un paramètre pour la méthode, plus de problème.\n@Unroll def \u0026#39;should reverse #string\u0026#39;(String string) Extensions # Spock possède un mécanisme permettant d\u0026rsquo;enrichir son cycle de vie. Les extensions disponibles par défaut sont listées dans la documentation.\nParmi les plus sympa, il y a :\n@IgnoreIf permet d\u0026rsquo;ignorer un test quand une condition n\u0026rsquo;est pas respectée, @Timeout pour faire échouer un test s\u0026rsquo;il ne s\u0026rsquo;est pas exécuté en un certain temps, @AutoCleanup appelle automatiquement une méthode sur un champ annoté pour libérer des resources à la fin de son cycle de vie, @RestoreSystemProperties rétabli les valeurs des propriétés systèmes quand elles ont été modifiées au cours d\u0026rsquo;un test. Et il en existe d\u0026rsquo;autres développés par la communauté :\nSpock Reports pour générer un rapport HTML des spécifications, @TempDirectory crée des dossiers temporaires, Spock Subjects-Collaborators Extension pour injecter des mocks dans la classe que l\u0026rsquo;on teste, Et bien d\u0026rsquo;autres\u0026hellip; Et finalement Spock est aussi compatible avec les @Rule de JUnit !!\nDocumentation # Spock met en avant le fait que les tests sont des spécifications qui peuvent-être documentées comme telles.\nLes blocs sont en fait des labels, on peut donc y associer une description :\nwhen: \u0026#34;a build notifies a listener when ending\u0026#34; listener.onSuccess(new BuildResult()) then: \u0026#34;the listener asks a notifier if it is ready\u0026#34; 1 * notifier.isReady() \u0026gt;\u0026gt; true then: \u0026#34;the listener asks the notifier to send a notification\u0026#34; 1 * notifier.send(\u0026#34;Build success !\u0026#34;) Un bloc peut être découpé en plusieurs étapes avec le label and:\ngiven: \u0026#34;something\u0026#34; // code and: \u0026#34;something else\u0026#34; // code when: then: Il existe aussi des extensions à fin documentaire :\n@Title, @Narrative définissent une spécification, @Issue indique qu\u0026rsquo;une fonctionnalité ou une spécification sont liées à un ticket, @Subject pour identifier la classe que l\u0026rsquo;on spécifie. Conclusion # Les tests écrits avec Spock sont globalement plus concis et lisibles. Les noms de méthodes sont de vrais phrases, les spécifications sont structurées autour de blocs bien définis (given, when, then) et le langage Groovy permet d\u0026rsquo;écrire moins de code.\nSpock vient avec son propre framework de mocks et ses assertions pensés pour l\u0026rsquo;outil. Ça évitera de se retrouver avec des tests écrits avec des librairies différentes et de devoir retenir plusieurs API. Rien n\u0026rsquo;empêche d\u0026rsquo;ailleurs d\u0026rsquo;utiliser Mockito (ou un autre) par exemple à la place des mocks livrés avec Spock.\nSpock est compatible avec de nombreux outils puisqu\u0026rsquo;il est accompagné d\u0026rsquo;un runner JUnit. Pas de soucis pour exécuter les spécifications depuis son IDE ou son outil de build préféré. Cela le rend aussi compatible avec les Rule JUnit !\nGroovy a une courbe d\u0026rsquo;apprentissage rapide pour un développeur Java. Le langage en est très proche et on peut se permettre d\u0026rsquo;en apprendre les spécificités et les concepts au fur et à mesure. Utiliser Groovy pour ses tests a encore plus de sens quand on maintient du code Java dans des versions un peu ancienne.\nMême si les exemples se sont concentrés sur du code Java, Spock fonctionne aussi très bien pour tester du Groovy 😇\nEncore ? # Le guide d\u0026rsquo;utilisation de Spock mérite une lecture attentive, ses mécanismes y sont très bien expliqués.\nLa présentation Smarter testing Java code with Spock Framework propose tout une série d\u0026rsquo;astuces pour encore améliorer son utilisation de Spock !\nEt pour finir les auteurs de l\u0026rsquo;outil maintiennent un projet d\u0026rsquo;exemple dans lequel on peut aller piocher de bonnes idées.\n","date":"4 juin 2015","externalUrl":null,"permalink":"/posts/tester-code-java-avec-groovy-spock/","section":"Posts","summary":"Spock est un framework de tests écrit sous la forme de spécifications pour des applications Java ou Groovy. Il offre une alternative tout en un au trio JUnit (TestNG), Mockito et AssertJ.\n","title":"Tester son code Java avec Groovy et Spock","type":"posts"},{"content":"","date":"13 mai 2015","externalUrl":null,"permalink":"/tags/anybar/","section":"Tags","summary":"","title":"Anybar","type":"tags"},{"content":"","date":"13 mai 2015","externalUrl":null,"permalink":"/tags/gradle/","section":"Tags","summary":"","title":"Gradle","type":"tags"},{"content":"","date":"13 mai 2015","externalUrl":null,"permalink":"/tags/growl/","section":"Tags","summary":"","title":"Growl","type":"tags"},{"content":"","date":"13 mai 2015","externalUrl":null,"permalink":"/tags/notification-center/","section":"Tags","summary":"","title":"Notification Center","type":"tags"},{"content":"Comme (trop ?) souvent avec les builds, le temps parait long\u0026hellip; Souvent lancé un peu en tâche de fond, j\u0026rsquo;aime bien avoir une notification quand ceux ci se terminent.\nGradle propose un plugin par défaut pour enrichir son build de notifications mais on verra comment avoir accès à une panoplie de nouveaux types de notifications !\nGradle Build Announcements # C\u0026rsquo;est le plugin disponible par défaut dans la distribution de Gradle. Il est lui même basé sur le plugin Announce qui permet d\u0026rsquo;envoyer des notifications personnalisées pendant un build.\nIl supporte 4 types de notifications :\ntwitter notify-send Snarl Growl Pour l\u0026rsquo;activer, c\u0026rsquo;est très simple il suffit de le déclarer dans build.gradle :\napply plugin: 'build-announcements' ou de le déclarer directement dans $HOME/.gradle/init.gradle pour que le plugin soit actif pour tous les projets :\nrootProject { apply plugin: 'build-announcements' } C\u0026rsquo;est vraiment très sympa d\u0026rsquo;avoir cette fonctionnalité disponible directement sans rien installer\u0026hellip; Par contre le choix d\u0026rsquo;applications est assez limité et les notifications ne sont pas très visuelles (l\u0026rsquo;icône est toujours la même quelque soit l\u0026rsquo;état du build par exemple).\nGradle notifier # Comme j\u0026rsquo;avais déjà mes notifications pour Maven avec maven-notifier, j\u0026rsquo;en ai profité pour les porter vers Gradle avec gradle-notifier !\nIl supporte l\u0026rsquo;envoi de notifications vers :\nNotifier Screenshot Growl, pour Windows et OS X. Snarl, pour Windows terminal-notifier, OS X notification center OS X (depuis Mavericks) notify-send pour Linux SystemTray depuis Java 6 Pushbullet Kdialog pour KDE notifu for Windows AnyBar pour OS X et Linux Toaster pour Windows 8 Notify since Java 6 BurntToast for Windows 10 Slack Installation # Le plugin peut être ajouté dans la phase d\u0026rsquo;initialisation ou directement au sein d\u0026rsquo;un projet.\nScript d\u0026rsquo;initialisation # Pa exemple, créer (ou modifier) le fichier $HOME/.gradle/init.gradle :\ninitscript { repositories { mavenCentral() } dependencies { classpath group: 'fr.jcgay', name: 'gradle-notifier', version: '1.1.0' } } rootProject { apply plugin: fr.jcgay.gradle.notifier.GradleNotifierPlugin } Script du projet # Dans le script build.gradle, ajouter :\nbuildscript { repositories { mavenCentral() } dependencies { classpath group: 'fr.jcgay', name: 'gradle-notifier', version: '1.1.0' } } apply plugin: 'fr.jcgay.gradle-notifier' ou en utilisant le mécanisme de plugin introduit depuis Gradle 2.1:\nplugins { id \u0026quot;fr.jcgay.gradle-notifier\u0026quot; version \u0026quot;1.1.0\u0026quot; } Configuration # Reposant sur send-notification, la configuration des applications et le choix de celle utilisée pour recevoir les notifications peut se faire dans le fichier $HOME/.send-notification (Voir les paramètres disponibles dans la documentation).\nOu bien l\u0026rsquo;outil est configurable directement au sein d\u0026rsquo;un script Gradle :\nnotifier { implementation = 'notificationcenter' threshold { time = 10 unit = java.util.concurrent.TimeUnit.SECONDS } growl { port = 23053 host = 'localhost' password = 'azerty123' } Et voilà plus moyen d\u0026rsquo;oublier son build au fin fond d\u0026rsquo;une console !\n","date":"13 mai 2015","externalUrl":null,"permalink":"/posts/gradle-desktop-notification/","section":"Posts","summary":"Comme (trop ?) souvent avec les builds, le temps parait long… Souvent lancé un peu en tâche de fond, j’aime bien avoir une notification quand ceux ci se terminent.\nGradle propose un plugin par défaut pour enrichir son build de notifications mais on verra comment avoir accès à une panoplie de nouveaux types de notifications !\n","title":"Notifications de bureau pour Gradle","type":"posts"},{"content":"","date":"13 mai 2015","externalUrl":null,"permalink":"/tags/notifu/","section":"Tags","summary":"","title":"Notifu","type":"tags"},{"content":"","date":"13 mai 2015","externalUrl":null,"permalink":"/tags/notify-send/","section":"Tags","summary":"","title":"Notify-Send","type":"tags"},{"content":"","date":"13 mai 2015","externalUrl":null,"permalink":"/tags/snarl/","section":"Tags","summary":"","title":"Snarl","type":"tags"},{"content":"Des outils pour mesurer en détail le temps de build de chaque exécution d\u0026rsquo;un build Maven.\nPar défaut Maven ne mesure que le temps par module et total du build sans donner de temps intermédiaire.\nIl existe heuresement des solutions pour obtenir les temps d\u0026rsquo;éxécutions de chaque étape de son build.\nmaven-profiler # Un outil que j\u0026rsquo;ai écris à l\u0026rsquo;époque car je n\u0026rsquo;en avais pas trouvé. Je m\u0026rsquo;en sers pour trouver quels étapes d\u0026rsquo;un build optimiser pour réduire son temps d\u0026rsquo;exécution.\nIl génère un rapport (HTML ou JSON) archivé dans le dossier .profiler au sein du projet.\nPour l\u0026rsquo;installer il suffit de suivre les instructions disponibles sur github.\nLe rapport est généré quand on lance un build avec la propriété système profile :\nmvn install -Dprofile Par défaut le rapport HTML est utilisé, pour avoir le modèle JSON :\nmvn install -Dprofile -DprofileFormat=JSON Exemple de rapport :\n{ \u0026#34;name\u0026#34;: \u0026#34;maven-profiler\u0026#34;, \u0026#34;goals\u0026#34;: \u0026#34;clean install\u0026#34;, \u0026#34;date\u0026#34;: \u0026#34;2015/01/17 15:28:49\u0026#34;, \u0026#34;parameters\u0026#34;: \u0026#34;{profile=true, profileFormat=JSON}\u0026#34;, \u0026#34;projects\u0026#34;: [{ \u0026#34;project\u0026#34;: \u0026#34;maven-profiler\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;6.793 s\u0026#34;, \u0026#34;mojos\u0026#34;: [{ \u0026#34;mojo\u0026#34;: \u0026#34;org.apache.maven.plugins:maven-surefire-plugin:2.18:test {execution: default-test}\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;2.512 s\u0026#34; }, { \u0026#34;mojo\u0026#34;: \u0026#34;org.apache.maven.plugins:maven-shade-plugin:2.3:shade {execution: default}\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;1.458 s\u0026#34; }, { \u0026#34;mojo\u0026#34;: \u0026#34;org.codehaus.gmavenplus:gmavenplus-plugin:1.2:testCompile {execution: default}\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;818.3 ms\u0026#34; }, { \u0026#34;mojo\u0026#34;: \u0026#34;org.apache.maven.plugins:maven-compiler-plugin:3.2:compile {execution: default-compile}\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;538.7 ms\u0026#34; }, { \u0026#34;mojo\u0026#34;: \u0026#34;org.apache.maven.plugins:maven-source-plugin:2.4:jar-no-fork {execution: attach-sources}\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;248.5 ms\u0026#34; }, { \u0026#34;mojo\u0026#34;: \u0026#34;org.apache.maven.plugins:maven-jar-plugin:2.5:jar {execution: default-jar}\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;238.2 ms\u0026#34; }, { \u0026#34;mojo\u0026#34;: \u0026#34;org.apache.maven.plugins:maven-resources-plugin:2.7:resources {execution: default-resources}\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;171.7 ms\u0026#34; }, { \u0026#34;mojo\u0026#34;: \u0026#34;org.codehaus.plexus:plexus-component-metadata:1.6:generate-metadata {execution: default}\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;170.5 ms\u0026#34; }, { \u0026#34;mojo\u0026#34;: \u0026#34;org.apache.maven.plugins:maven-enforcer-plugin:1.2:enforce {execution: enforce-maven}\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;126.0 ms\u0026#34; }, { \u0026#34;mojo\u0026#34;: \u0026#34;org.apache.maven.plugins:maven-clean-plugin:2.6.1:clean {execution: default-clean}\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;76.11 ms\u0026#34; }, { \u0026#34;mojo\u0026#34;: \u0026#34;org.apache.maven.plugins:maven-install-plugin:2.5.2:install {execution: default-install}\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;53.95 ms\u0026#34; }, { \u0026#34;mojo\u0026#34;: \u0026#34;org.apache.maven.plugins:maven-compiler-plugin:3.2:testCompile {execution: default-testCompile}\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;2.850 ms\u0026#34; }, { \u0026#34;mojo\u0026#34;: \u0026#34;org.apache.maven.plugins:maven-resources-plugin:2.7:testResources {execution: default-testResources}\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;2.518 ms\u0026#34; }] }] } maven-timeline # Ce plugin présente les temps de construction de chaque phase au sein d\u0026rsquo;un build sous la forme d\u0026rsquo;un timeline. Initialement développé par David Gageot, il en existe un fork maintenu par l\u0026rsquo;équipe de takari.\nPour l\u0026rsquo;utiliser il fait ajouter une extension dans le fichier pom.xml :\n\u0026lt;extension\u0026gt; \u0026lt;groupId\u0026gt;io.takari.maven\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;maven-timeline\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.6\u0026lt;/version\u0026gt; \u0026lt;/extension\u0026gt; Deux rapports sont générés, un en HTML et un en JSON.\n{ \u0026#34;start\u0026#34;: \u0026#34;2015-02-01T19:18:18.738Z\u0026#34;, \u0026#34;end\u0026#34;: \u0026#34;2015-02-01T19:18:21.770Z\u0026#34;, \u0026#34;dateTimeFormat\u0026#34;: \u0026#34;iso8601\u0026#34;, \u0026#34;events\u0026#34;: [ { \u0026#34;start\u0026#34;: \u0026#34;2015-02-01T19:18:20.940Z\u0026#34;, \u0026#34;end\u0026#34;: \u0026#34;2015-02-01T19:18:21.095Z\u0026#34;, \u0026#34;duration\u0026#34;: 2380, \u0026#34;description\u0026#34;: \u0026#34;com.github.jcgay.maven.color:maven-color-logback:package:jar:default-jar (2380 ms)\u0026#34;, \u0026#34;trackNum\u0026#34;: 1, \u0026#34;color\u0026#34;: \u0026#34;blue\u0026#34;, \u0026#34;durationEvent\u0026#34;: true }, { \u0026#34;start\u0026#34;: \u0026#34;2015-02-01T19:18:19.136Z\u0026#34;, \u0026#34;end\u0026#34;: \u0026#34;2015-02-01T19:18:19.545Z\u0026#34;, \u0026#34;duration\u0026#34;: 830, \u0026#34;description\u0026#34;: \u0026#34;com.github.jcgay.maven.color:maven-color-core:compile:compile:default-compile (830 ms)\u0026#34;, \u0026#34;trackNum\u0026#34;: 2, \u0026#34;color\u0026#34;: \u0026#34;blue\u0026#34;, \u0026#34;durationEvent\u0026#34;: true }, { \u0026#34;start\u0026#34;: \u0026#34;2015-02-01T19:18:19.562Z\u0026#34;, \u0026#34;end\u0026#34;: \u0026#34;2015-02-01T19:18:19.564Z\u0026#34;, \u0026#34;duration\u0026#34;: 849, \u0026#34;description\u0026#34;: \u0026#34;com.github.jcgay.maven.color:maven-color-agent:test-compile:testCompile:default-testCompile (849 ms)\u0026#34;, \u0026#34;trackNum\u0026#34;: 3, \u0026#34;color\u0026#34;: \u0026#34;blue\u0026#34;, \u0026#34;durationEvent\u0026#34;: true }, { \u0026#34;start\u0026#34;: \u0026#34;2015-02-01T19:18:19.677Z\u0026#34;, \u0026#34;end\u0026#34;: \u0026#34;2015-02-01T19:18:19.818Z\u0026#34;, \u0026#34;duration\u0026#34;: 1103, \u0026#34;description\u0026#34;: \u0026#34;com.github.jcgay.maven.color:maven-color-agent:package:jar:default-jar (1103 ms)\u0026#34;, \u0026#34;trackNum\u0026#34;: 3, \u0026#34;color\u0026#34;: \u0026#34;blue\u0026#34;, \u0026#34;durationEvent\u0026#34;: true }, ... { \u0026#34;start\u0026#34;: \u0026#34;2015-02-01T19:18:20.801Z\u0026#34;, \u0026#34;end\u0026#34;: \u0026#34;2015-02-01T19:18:21.549Z\u0026#34;, \u0026#34;duration\u0026#34;: 2834, \u0026#34;description\u0026#34;: \u0026#34;com.github.jcgay.maven.color:maven-color-log4j2:package:single:make-assembly (2834 ms)\u0026#34;, \u0026#34;trackNum\u0026#34;: 3, \u0026#34;color\u0026#34;: \u0026#34;blue\u0026#34;, \u0026#34;durationEvent\u0026#34;: true } ] } tesla-profiler # Un autre profiler qui affiche les temps de build dans la console.\nLe rapport est généré quand on lance un build avec la propriété système maven.profile :\nmvn install -Dmaven.profile Un exemple de résultat :\ncom.github.jcgay.maven:maven-profiler:2.2-SNAPSHOT clean 81ms org.apache.maven.plugins:maven-clean-plugin:2.6.1 (default-clean) 80ms validate 145ms org.apache.maven.plugins:maven-enforcer-plugin:1.2 (enforce-maven) 145ms process-resources 198ms org.apache.maven.plugins:maven-resources-plugin:2.7 (default-resources) 198ms compile 588ms org.apache.maven.plugins:maven-compiler-plugin:3.2 (default-compile) 588ms process-classes 209ms org.codehaus.plexus:plexus-component-metadata:1.6 (default) 209ms process-test-resources 3ms org.apache.maven.plugins:maven-resources-plugin:2.7 (default-testResources) 3ms test-compile 1s 2ms org.apache.maven.plugins:maven-compiler-plugin:3.2 (default-testCompile) 3ms org.codehaus.gmavenplus:gmavenplus-plugin:1.2 (default) 998ms test 2s 670ms org.apache.maven.plugins:maven-surefire-plugin:2.18 (default-test) 2s 669ms package 2s 778ms org.apache.maven.plugins:maven-jar-plugin:2.5 (default-jar) 250ms org.apache.maven.plugins:maven-source-plugin:2.4 (attach-sources) 270ms org.apache.maven.plugins:maven-shade-plugin:2.3 (default) 2s 258ms Bon profiling !\n","date":"1 février 2015","externalUrl":null,"permalink":"/posts/maven-profiler/","section":"Posts","summary":"Des outils pour mesurer en détail le temps de build de chaque exécution d’un build Maven.\n","title":"Mesurer son temps de build Maven","type":"posts"},{"content":"Liquibase est un outil très utile pour versionner sa base de données. La succession de refactoring à appliquer à une base peut-être décrite dans plusieurs formats de données (XML, YAML, JSON et SQL). Seulement il arrive qu\u0026rsquo;une migration soit \u0026ldquo;trop\u0026rdquo; complexe pour être décrite à partir des refactoring standards (ou alors tout simplement que l\u0026rsquo;on ne soit pas très à l\u0026rsquo;aise avec PL/SQL).\nDans ce cas on peut écrire un changement particulier en Java.\nImplémenter un CustomChange # Liquibase propose deux interfaces pour créer son refactoring personnel, CustomTaskChange et CustomSqlChange. La première permet d\u0026rsquo;appliquer des changements qui ne génèrent pas de SQL contrairement à la seconde.\nCette dernière est à privilégier quand c\u0026rsquo;est possible puisque cela permet de sauvegarder le SQL généré dans un fichier (via un dryRun par exemple).\npublic class MyCustomChangeThatGenerateSql implements CustomSqlChange { @Override public SqlStatement[] generateStatements(final Database database) throws CustomChangeException { List\u0026lt;SqlStatement\u0026gt; statements = new ArrayList\u0026lt;\u0026gt;(); JdbcConnection connection = (JdbcConnection) database.getConnection(); try { ResultSet persons = connection.createStatement().executeQuery(\u0026#34;select nom, prenom from person\u0026#34;); while (persons.next()) { // Write your complex logic in here ! // and generate SqlStatement(s) } } catch (DatabaseException | SQLException e) { throw new CustomChangeException(e); } SqlStatement insert = new InsertStatement(database.getDefaultCatalogName(), database.getDefaultSchemaName(), \u0026#34;person\u0026#34;) .addColumnValue(\u0026#34;nom\u0026#34;, \u0026#34;Gay\u0026#34;) .addColumnValue(\u0026#34;prenom\u0026#34;, \u0026#34;Jean-Christophe\u0026#34;); SqlStatement update = new UpdateStatement(database.getDefaultCatalogName(), database.getDefaultSchemaName(), \u0026#34;person\u0026#34;) .addNewColumnValue(\u0026#34;prenom\u0026#34;, \u0026#34;Jc\u0026#34;) .setWhereClause(String.format(\u0026#34;nom=\u0026#39;%s\u0026#39;\u0026#34;, Gay)) statements.add(insert); statements.add(update); return statements.toArray(new SqlStatement[statements.size()]); } @Override public String getConfirmationMessage() { return null; } @Override public void setUp() throws SetupException {} @Override public void setFileOpener(ResourceAccessor resourceAccessor) {} @Override public ValidationErrors validate(Database database) { return null; } } La principale logique se passe dans la méthode generateStatements. L\u0026rsquo;API de Liquibase nous donne accès à la connexion Jdbc et il suffit de générer des SqlStatement pour décrire son refactoring.\nLe reste des méthodes à implémenter permet de valider la configuration, accéder à des fichiers, etc. La Javadoc explique tout ça.\nConfigurer son changeset # L\u0026rsquo;élément customChange permet de déclarer la classe implémentant le refactoring.\n\u0026lt;changeSet id=\u0026#34;20140606-super-complex-change\u0026#34; author=\u0026#34;jc.gay\u0026#34;\u0026gt; \u0026lt;customChange class=\u0026#34;fr.jcgay.MyCustomChangeThatGenerateSql\u0026#34; /\u0026gt; \u0026lt;/changeSet\u0026gt; Et voilà on peut décrire des changements complexes dans son langage préféré !\n","date":"1 février 2015","externalUrl":null,"permalink":"/posts/liquibase-refactoring-change-java/","section":"Posts","summary":"Liquibase est un outil très utile pour versionner sa base de données. La succession de refactoring à appliquer à une base peut-être décrite dans plusieurs formats de données (XML, YAML, JSON et SQL). Seulement il arrive qu’une migration soit “trop” complexe pour être décrite à partir des refactoring standards (ou alors tout simplement que l’on ne soit pas très à l’aise avec PL/SQL).\nDans ce cas on peut écrire un changement particulier en Java.\n","title":"Ecrire une migration Liquibase en Java","type":"posts"},{"content":"","date":"1 février 2015","externalUrl":null,"permalink":"/tags/java/","section":"Tags","summary":"","title":"Java","type":"tags"},{"content":"","date":"1 février 2015","externalUrl":null,"permalink":"/tags/liquibase/","section":"Tags","summary":"","title":"Liquibase","type":"tags"},{"content":"","date":"26 octobre 2014","externalUrl":null,"permalink":"/tags/brew/","section":"Tags","summary":"","title":"Brew","type":"tags"},{"content":"","date":"26 octobre 2014","externalUrl":null,"permalink":"/tags/python/","section":"Tags","summary":"","title":"Python","type":"tags"},{"content":"J\u0026rsquo;ai récemment exécuté un brew cleanup pour nettoyer toutes les anciennes versions des paquets non utilisés. Depuis virtualenv ne fonctionne plus\u0026hellip; voilà comment j\u0026rsquo;ai pu m\u0026rsquo;en sortir.\nArrrghhhh # Après activation d\u0026rsquo;un environnement :\n. ~/virtualenvs/pelican/bin/activate En lançant par exemple la génération de mon blog avec Pelican :\ndyld: Library not loaded: @executable_path/../.Python Referenced from: /Users/jcgay/virtualenvs/pelican/bin/python Reason: image not found Mais pourquoi tout est cassé ? # Quand on crée un environnement avec virtualenv celui-ci crée des liens vers votre installation de Python. Par exemple à la racine d\u0026rsquo;un environnement isolé on a :\n.Python -\u0026gt; /usr/local/Cellar/python/2.7.6/Frameworks/Python.framework/Versions/2.7/Python En effectuant un brew cleanup les anciennes versions de Python sont supprimées. Les environnements utilisant ces versions deviennent inopérants.\nRéparation de l\u0026rsquo;environnement # On installe un find GNU :\nbrew install findutils On supprime tous les liens symboliques erronés :\ngfind ~/virtualenvs/pelican/ -type l -xtype l -delete On recrée l\u0026rsquo;environnement :\nvirtualenv ~/virtualenvs/pelican Et là nouvelle erreur\u0026hellip;\nzsh: /usr/local/bin/virtualenv: bad interpreter: /usr/local/Cellar/python/2.7.6/Frameworks/Python.framework/Ver: no such file or directory On réinstalle virtualenv :\npip uninstall virtualenv pip install virtualenv On recrée l\u0026rsquo;environnement :\nvirtualenv ~/virtualenvs/pelican Et voilà tout fonctionne à nouveau !\nSources # How to fix your virtualenv after an upgrade of Python with Homebrew\nBroken references in Virtualenvs\nTrouble using mkvirtualenv after installing OS X Mavericks\n","date":"26 octobre 2014","externalUrl":null,"permalink":"/posts/reparation-virtualenv-brew-cleanup/","section":"Posts","summary":"J’ai récemment exécuté un brew cleanup pour nettoyer toutes les anciennes versions des paquets non utilisés. Depuis virtualenv ne fonctionne plus… voilà comment j’ai pu m’en sortir.\n","title":"Réparation environnement Python après un brew cleanup","type":"posts"},{"content":"","date":"26 octobre 2014","externalUrl":null,"permalink":"/tags/virtualenv/","section":"Tags","summary":"","title":"Virtualenv","type":"tags"},{"content":"Java 8 est maintenant disponible depuis plusieurs semaines, voici un \u0026ldquo;petit\u0026rdquo; résumé des fonctionalités de Java 8 que j\u0026rsquo;ai testé à travers tous les articles sur lesquels je suis tombé.\nDefault methods sur les Interfaces # Java 8 utilise le mot clef default pour définir une implémentation par défaut au sein d\u0026rsquo;une interface.\nC\u0026rsquo;est utile pour les développeurs d\u0026rsquo;API, c\u0026rsquo;était même indispensable pour la plupart des nouveautés du langage sinon comment enrichir les interfaces comme java.util.Collection sans briser toutes les implémentations existantes ?\npublic interface A { default void printSomething() { System.out.println(\u0026#34;something\u0026#34;); } } public class Implementation implements A {} La classe Implementation n\u0026rsquo;a pas besoin de définir la méthode printSomething.\npublic class Examples { public static void main(String[] args) { new Implementation().printSomething(); } } Que ce passe t-il si deux interfaces implémentent la même méthode par défaut et qu\u0026rsquo;une classe implémente ces deux interfaces ?\npublic interface B { default void printSomething() { System.out.println(\u0026#34;something else\u0026#34;); } } public class DoesNotCompile implements A,B {} Et bien le code précédent ne compile pas:\nclass DoesNotCompile inherits unrelated defaults for printSomething() from types A and B On est obligé de surcharger la méthode dans l\u0026rsquo;implémentation pour résoudre le conflit,\npublic class ItCompiles implements A,B { @Override public void printSomething() { System.out.printf(\u0026#34;I can print what I want !\u0026#34;); } } Pour faire référence à une des méthodes par défaut :\npublic class ItCompilesToo implements A,B { @Override public void printSomething() { B.super.printSomething(); } } Je pense que cette fonctionalité va être très utile pour les développeurs de librairies, on va pouvoir oublier les fameuses classes abstraites dont il fallait hériter pour ne pas risquer de tout casser lors de mises à jour d\u0026rsquo;interfaces.\nExpressions Lambda # Surement une des évolutions les plus attendues, une expression lambda est une sorte de méthode \u0026ldquo;anonyme\u0026rdquo;. Jusqu\u0026rsquo;à aujourd\u0026rsquo;hui en Java pour simuler ce comportement on utilisait une classe anonyme qui implémentait une interface avec une seule méthode abstraite (SAM).\nList\u0026lt;Integer\u0026gt; numbers = asList(10, 1, 1000, 100); Collections.sort(numbers, new Comparator\u0026lt;Integer\u0026gt;() { @Override public int compare(Integer a, Integer b) { return a.compareTo(b); } }); peut-etre remplacé par :\nCollections.sort(numbers, (a, b) -\u0026gt; a.compareTo(b)); Le compilateur est capable de trouver les types des paramètres, pas besoin de les préciser et la méthode tenant sur une ligne, on peut également omettre les {} et le return.\nAccès variable locale # Contrairement à une classe anonyme, une expression lambda peut accéder aux variables locales même si celles ci ne sont pas déclarées comme étant final.\nInteger n = 4; Function\u0026lt;Integer, Integer\u0026gt; modulo = (Integer a) -\u0026gt; a % n; assert modulo.apply(8) == 0; mais en fait il faut qu\u0026rsquo;elles le soient, le code suivant ne compile pas :\nInteger n = 4; Function\u0026lt;Integer, Integer\u0026gt; modulo = (Integer a) -\u0026gt; a % n; n = 8; avec comme erreur :\nlocal variables referenced from a lambda expression must be final or effectively final Membre et variable static # Contrairement aux variables locales, les membres de classe sont accessibles en écriture.\npublic class Lambda { private static int staticNumber; private int number; public void write() { Function\u0026lt;Integer, Integer\u0026gt; function = (Integer a) -\u0026gt; staticNumber = a; Function\u0026lt;Integer, Integer\u0026gt; function2 = (Integer a) -\u0026gt; number = a; } } Functional Interfaces # Les expressions lambda ne sont en fait pas si \u0026ldquo;anonymes\u0026rdquo; que ça. Elles correspondent en fait à des types spécifiés par des interfaces avec exactement une méthode abstraite. C\u0026rsquo;est pour ça que les classes anonymes implémentant une interface de type SAM peuvent être remplacées par des expressions lambda.\nJava 8 propose l\u0026rsquo;annotation @FunctionalInterface pour s\u0026rsquo;assurer qu\u0026rsquo;une interface ne déclare qu\u0026rsquo;une seule méthode abstraite.\n@FunctionalInterface public interface Function\u0026lt;T, R\u0026gt; { R apply(T t); } Si une deuxième méthode abstraite est ajoutée la compilation échoue :\nFunction is not a functional interface, multiple non-overriding abstract methods found in interface Lambda # On peut ajouter des méthodes par defaut sur une @FunctionalInterface.\n@FunctionalInterface public interface A { default void printSomething() { System.out.println(\u0026#34;something\u0026#34;); } void print(); } mais on ne peut pas accéder à ces méthodes depuis une lambda.\nA doesNotCompile = () -\u0026gt; printSomething(); Function, Predicate, etc # De nombreuses @FunctionalInterface sont disponibles par défaut. D\u0026rsquo;anciennes interfaces de l\u0026rsquo;API ont été migrées, Comparable et Runnable par exemple. Si vous connaissez déjà Guava vous ne serez pas complètement perdu.\nFunction # Une Function prend un argument et retourne un résultat.\nFunction\u0026lt;Integer, String\u0026gt; toString = n -\u0026gt; String.valueOf(n); Function\u0026lt;String, Integer\u0026gt; toInteger = s -\u0026gt; Integer.valueOf(s); assert \u0026#34;4\u0026#34;.equals(toString.apply(4)); assert toInteger.apply(\u0026#34;4\u0026#34;) == 4; assert \u0026#34;4\u0026#34;.equals(toString.compose(toInteger).apply(\u0026#34;4\u0026#34;)); assert toString.andThen(toInteger).apply(4) == 4; La méthode compose applique la fonction toInteger puis la fonction toString alors que la méthode andThen applique toString puis toInteger.\nBiFunction # C\u0026rsquo;est un spécialisation d\u0026rsquo;une Function qui prend deux arguments et retourne un résultat.\nBiFunction\u0026lt;Integer, String, String\u0026gt; concat = (Integer i, String s) -\u0026gt; s + \u0026#34;: \u0026#34; + i; assert \u0026#34;un: 1\u0026#34;.equals(concat.apply(1, \u0026#34;un\u0026#34;)); UnaryOperator # C\u0026rsquo;est une Function qui prend un argument et retourne un résultat du même type. Par exemple la fonction identity retourne toujours la valeur passée en argument.\nassert UnaryOperator.identity().apply(\u0026#34;un\u0026#34;).equals(\u0026#34;un\u0026#34;); BinaryOperator # Un BinaryOperator est une spécialisation d\u0026rsquo;une BiFunction dont les paramètres et le résultat partagent le même type.\nBinaryOperator\u0026lt;String\u0026gt; concatString = (s1, s2) -\u0026gt; s1.concat(\u0026#34;: \u0026#34;).concat(s2); assert \u0026#34;un: 1\u0026#34;.equals(concatString.apply(\u0026#34;un\u0026#34;, \u0026#34;1\u0026#34;)); Predicate # Un Predicate prend un argument et retourne un booléen.\nPredicate\u0026lt;String\u0026gt; isEmpty = s -\u0026gt; s == null || s.isEmpty(); Predicate\u0026lt;String\u0026gt; isTrimmed = s -\u0026gt; s.equals(s.trim()); assert isEmpty.test(null) == true; assert isEmpty.test(\u0026#34;\u0026#34;) == true; assert isEmpty.test(\u0026#34;not empty\u0026#34;) == false; assert isEmpty.negate().and(isTrimmed).test(\u0026#34;not empty\u0026#34;) == true; assert isEmpty.negate().and(isTrimmed).test(\u0026#34; not empty \u0026#34;) == false; assert isEmpty.or(isTrimmed).test(\u0026#34;\u0026#34;) == true; assert isEmpty.or(isTrimmed).test(\u0026#34;not empty\u0026#34;) == true; assert Predicate.isEqual(\u0026#34;hello\u0026#34;).test(\u0026#34;hello\u0026#34;) == true; negate inverse un prédicat, and et or permettent de chaîner des prédicats selon l\u0026rsquo;opérateur logique, La méthode statique isEqual teste l\u0026rsquo;égalité de deux objets selon Object#equals BiPredicate # Un BiPredicate prend deux arguments et retourne un booléen.\nSupplier # Un Supplier ne prend pas d\u0026rsquo;argument et produit un résultat.\nSupplier\u0026lt;String\u0026gt; emptyString = () -\u0026gt; \u0026#34;\u0026#34;; assert \u0026#34;\u0026#34;.equals(emptyString.get()); Consumer # Un Consumer prend un argument mais ne retourne pas de résultat.\nConsumer\u0026lt;String\u0026gt; print = s -\u0026gt; System.out.println(s); Consumer\u0026lt;String\u0026gt; hello = s -\u0026gt; System.out.printf(\u0026#34;Hello %s !\u0026#34;, s); print.accept(\u0026#34;something\u0026#34;); // something print.andThen(hello).accept(\u0026#34;JC\u0026#34;); // JC Hello JC ! La méthode andThen permet de chaîner les consommateurs.\nComparator # Les Comparator sont devenus des FunctionalInterface.\nComparator\u0026lt;Integer\u0026gt; ascending = (a, b) -\u0026gt; a.compareTo(b); assert ascending.compare(10, 1) \u0026gt; 0; assert ascending.reversed().compare(10, 1) \u0026lt; 0; Sans grande surprise reversed inverse la comparaison.\nIl existe des méthodes par défaut pour appliquer une Function avant le Comparator dans les différents thenComparating.\nA retenir # Ça fait beaucoup d\u0026rsquo;interfaces fonctionnelles (et je n\u0026rsquo;ai même pas lister les versions spécifiques pour les types primitifs), ce qu\u0026rsquo;il faut retenir :\ns\u0026rsquo;il n\u0026rsquo;y a pas de retour, on utilise un Consumer, s\u0026rsquo;il faut retourner un booléen, on utilise un Predicate, s\u0026rsquo;il faut produire un numérique primitif (int, double, long), on utilise un (Type)ToIntFunction, (Type)ToDoubleFunction, (Type)ToLongFunction, s\u0026rsquo;il faut retourner une valeur sans prendre d\u0026rsquo;argument, c\u0026rsquo;est un Supplier, si le seul argument de la fonction est un int, double, long on utilise un Int(something), Double(something), Long(Something), si la fonction prend deux arguments, c\u0026rsquo;est une Bi(something), si la fonction prend deux arguments de même type, c\u0026rsquo;est un BinaryOperator, si une fonction retourne une valeur de même type que son unique argument, c\u0026rsquo;est un UnaryOperator, si une fonction prend en argument un type primitif et un autre type sans retourner de valeur, c\u0026rsquo;est un Obj(Int|Double|Long)Consumer, sinon c\u0026rsquo;est une Function. Surcharge de méthode # On peut surcharger une méthode utilisant une FunctionalInterface en paramètre par une autre plus spécifique (la manière classique d\u0026rsquo;overloading).\nPar exemple, un BinaryOperator est une spécialisation d\u0026rsquo;une BiFunction,\npublic class A { public void is(BiFunction\u0026lt;Boolean, Boolean, Boolean\u0026gt; function) { System.out.println(\u0026#34;bifunction\u0026#34;); } public void is(BinaryOperator\u0026lt;Boolean\u0026gt; function) { System.out.println(\u0026#34;binaryoperator\u0026#34;); } } le code suivant affichera \u0026quot;binaryoperator\u0026quot;\nnew A().is((a, b) -\u0026gt; true); Par contre le compilateur ne peut pas toujours trouvé \u0026ldquo;un type le plus spécifique\u0026rdquo;. Dans le cas où les types en questions n\u0026rsquo;ont pas de relation, seul le nombre d\u0026rsquo;arguments de la FunctionalInterface permet de différencier les méthodes surchargées.\n@FunctionalInterface public interface StringPredicate { boolean test(String string); } public class A { public void anotherMethod(Predicate\u0026lt;String\u0026gt; predicate) { System.out.println(\u0026#34;predicate\u0026#34;); } public void anotherMethod(StringPredicate predicate) { System.out.println(\u0026#34;string predicate\u0026#34;); } } Le code suivant ne compile pas\nnew A().anotherMethod(input -\u0026gt; true); reference to anotherMethod is ambiguous both method anotherMethod(java.util.function.Predicate\u0026lt;java.lang.String\u0026gt;) in fr.jcgay.example.java.overload.A and method anotherMethod(fr.jcgay.example.java.overload.StringPredicate) in fr.jcgay.example.java.overload.A match Même si c\u0026rsquo;est techniquement possible, je ne suis pas sûr que ce soit une super idée de surcharger de telles méthodes, dans le premier cas par exemple la résolution est bien moins claire lors de la manipulation de l\u0026rsquo;expression lambda que lors de cas classiques de surcharge où l\u0026rsquo;on manipule directement des types.\nRéférences de méthodes # Java 8 introduit le mot clef :: pour extraire des références de méthodes. C\u0026rsquo;est utile pour remplacer des expressions lambda qui se contentent de faire appel à une méthode qui existe déjà.\nOn peut remplacer :\nFunction\u0026lt;Integer, String\u0026gt; toString = n -\u0026gt; String.valueOf(n); par :\nFunction\u0026lt;Integer, String\u0026gt; toString = String::valueOf; On peut faire référence à des méthodes statiques mais aussi directement depuis une instance,\nString hello = new String(\u0026#34;hello\u0026#34;); Predicate\u0026lt;String\u0026gt; startsWith = hello::startsWith; assert startsWith.test(\u0026#34;he\u0026#34;) == true; et aux constructeurs,\nSupplier\u0026lt;String\u0026gt; newString = String::new; et aussi aux méthodes d\u0026rsquo;instance sans référence d\u0026rsquo;objet en particulier :\nList\u0026lt;String\u0026gt; names = Arrays.asList(\u0026#34;Barbara\u0026#34;, \u0026#34;James\u0026#34;, \u0026#34;Mary\u0026#34;, \u0026#34;John\u0026#34;); Collections.sort(names, String::compareToIgnoreCase); Fonctionnement # C\u0026rsquo;est bien magique mais comment on détermine qu\u0026rsquo;une méthode existante est compatible avec une FunctionalInterface ?\nstatique # La plus simple à se représenter, il suffit que la dite méthode ait bien la même signature que la méthode unique de l\u0026rsquo;interface désirée.\nPar exemple, on peut écrire notre Function toString précédente,\nFunction\u0026lt;Integer, String\u0026gt; toString = new Function\u0026lt;Integer, String\u0026gt;() { @Override public String apply(Integer integer) { return String.valueOf(integer); } }; String#valueOf(Integer) est bien utilisable pour implémenter Function\u0026lt;Integer, String\u0026gt;#apply(Integer), et remplacer une expression lambda par une référence de méthode statique.\ninstance # Ce type de référence permet de capturer une instance qui sera utilisée lors de l\u0026rsquo;évaluation de l\u0026rsquo;expression lambda.\nAinsi notre exemple du début revient à écrire :\nString hello = new String(\u0026#34;hello\u0026#34;); Predicate\u0026lt;String\u0026gt; startsWith = new Predicate\u0026lt;String\u0026gt;() { @Override public boolean test(String s) { return hello.startsWith(s); } }; instance arbitraire # Ce sont les plus difficiles à se représenter au début. Ces références permettent de pointer des méthodes sur des instances qui seront découvertes à l\u0026rsquo;exécution. En fait celà veut dire que l\u0026rsquo;instance en question sera le premier argument de l\u0026rsquo;expression lambda et le reste des paramètres seront ceux passés à la méthode en référence.\nReprenons l\u0026rsquo;exemple de la comparaison de chaînes de caractères,\nBiFunction\u0026lt;String, String, Integer\u0026gt; concat = new BiFunction\u0026lt;String, String, Integer\u0026gt;() { @Override public Integer apply(String self, String argument) { return self.compareToIgnoreCase(argument); } }; Optional # C\u0026rsquo;est un conteneur pour une valeur qui peut être null. Ce conteneur possède deux états, contenant une valeur ou ne contenant rien.\nRetourner un Optional à la place d\u0026rsquo;un null permet d\u0026rsquo;obliger le traitement du cas \u0026ldquo;pas de valeur de retour\u0026rdquo;.\nOptional\u0026lt;String\u0026gt; hello = Optional.of(\u0026#34;hello\u0026#34;); assert hello.isPresent() == true; assert \u0026#34;hello\u0026#34;.equals(hello.get()); Si la valeur contenu est null,\nOptional\u0026lt;Object\u0026gt; absent = Optional.ofNullable(null); assert absent.isPresent() == false; absent.get() // throws java.util.NoSuchElementException: No value present La classe Optional regorge de méthodes utilitaires pour transformer/filtrer la valeur qu\u0026rsquo;il contient, pour retourner une valeur par défaut et également un orElseThrow qui pour le coup n\u0026rsquo;existe pas dans Guava.\nStream # Un Stream est une séquence d\u0026rsquo;éléments sur laquelle on peut effectuer des opérations. Un Stream se compose d\u0026rsquo;une source (un tableau, une collection, etc), de zéro ou plusieurs opérations intermédiaires (transformation du Stream en un autre via filter par exemple) et d\u0026rsquo;une opération terminale (qui produit le résultat).\nLes calculs ne sont effectués qu\u0026rsquo;à l\u0026rsquo;initialisation de l\u0026rsquo;opération finale et la source est consommée que si c\u0026rsquo;est nécessaire.\nOn peut créer un Stream à partir d\u0026rsquo;une collection en utilisant la méthode stream(),\nList\u0026lt;Person\u0026gt; persons = asList( new Person(\u0026#34;John\u0026#34;, \u0026#34;Doe\u0026#34;, 30), new Person(\u0026#34;Jane\u0026#34;, \u0026#34;Doe\u0026#34;, 20), new Person(\u0026#34;Jim\u0026#34;, \u0026#34;Smith\u0026#34;, 15) ); assert persons.stream().count() == 3; En utilisant IntStream, DoubleStream, LongStream pour créer des Stream numériques,\nassert IntStream.range(0, 10).sum() == 45; Ou encore avec différentes méthodes utilitaires sur la classe Stream,\nStream.of(\u0026#34;a\u0026#34;, \u0026#34;b\u0026#34;, \u0026#34;c\u0026#34;).forEach(System.out::println); Stream.builder().add(\u0026#34;a\u0026#34;).add(\u0026#34;b\u0026#34;).add(\u0026#34;c\u0026#34;) .build() .forEach(System.out::println); Un Stream peut-être infini, dans ce cas il faut avoir une opération stoppante,\nRandom random = new Random(); Stream.generate(() -\u0026gt; random.nextInt()) .limit(10) .forEach(System.out::println); new Random().ints() .limit(10) .forEach(System.out::println); En gros il y a des Stream un peu partout :)\nforEach # Effectue une opération sur chacun des éléments en utilisant un Consumer. C\u0026rsquo;est une opération terminale qui consomme le Stream. On ne peut pas appeler d\u0026rsquo;autres opérations après un forEach.\npersons.stream() .forEach(p -\u0026gt; System.out.println(p.getLastName() + \u0026#34; \u0026#34; + p.getFirstName())); // Doe John // Doe Jane // Smith Jim filter # Accepte un Predicate pour filtrer les éléments. C\u0026rsquo;est une opération intermédiaire qui nous permet donc de chaîner d\u0026rsquo;autres opérations à sa suite.\npersons.stream() .filter(p -\u0026gt; p.getLastName().startsWith(\u0026#34;D\u0026#34;)) .forEach(System.out::println); // Person{firstName=\u0026#39;John\u0026#39;, lastName=\u0026#39;Doe\u0026#39;, age=30} // Person{firstName=\u0026#39;Jane\u0026#39;, lastName=\u0026#39;Doe\u0026#39;, age=20} sorted # Une opération intermédiaire qui permet de trier les éléments à l\u0026rsquo;aide d\u0026rsquo;un Comparable.\npersons.stream() .sorted((p1, p2) -\u0026gt; p1.getFirstName().compareTo(p2.getFirstName())) .forEach(System.out::println); // Person{firstName=\u0026#39;Jane\u0026#39;, lastName=\u0026#39;Doe\u0026#39;, age=20} // Person{firstName=\u0026#39;Jim\u0026#39;, lastName=\u0026#39;Smith\u0026#39;, age=15} // Person{firstName=\u0026#39;John\u0026#39;, lastName=\u0026#39;Doe\u0026#39;, age=30} map # Applique une Function sur les éléments, c\u0026rsquo;est également une opération intermédiaire.\npersons.stream() .map(Person::getAge) .sorted() .forEach(System.out::println); // 15 // 20 // 30 allMatch, anyMatch, noneMatch # Plusieurs méthodes permettant de vérifier que zéro/un/des éléments vérifient un Predicate. Toutes ces opérations sont terminales.\nassert persons.stream() .allMatch(p -\u0026gt; p.getFirstName().startsWith(\u0026#34;J\u0026#34;)) == true; assert persons.stream() .noneMatch(p -\u0026gt; p.getAge() == 35) == true; assert persons.stream() .anyMatch(p -\u0026gt; \u0026#34;Doe\u0026#34;.equals(p.getLastName())) == true; count # Une opération terminale qui retourne le nombre d\u0026rsquo;élément dans un Stream.\nassert persons.stream() .filter(p -\u0026gt; p.getAge() \u0026gt;= 20) .count() == 2; sum # Retourne la somme de tous les éléments.\nassert IntStream.rangeClosed(1, 10).sum() == 55; reduce # C\u0026rsquo;est une opération terminale qui réduit les éléments du Stream avec un BinaryOperator.\nOn obtient au final le résultat sous la forme d\u0026rsquo;un Optional,\nOn va créer un monstre en fusionnant nos personnes,\npersons.stream() .reduce((p1, p2) -\u0026gt; new Person(p1.getFirstName(), p2.getLastName(), p1.getAge() + p2.getAge())) .ifPresent(System.out::println); // Person{firstName=\u0026#39;John\u0026#39;, lastName=\u0026#39;Smith\u0026#39;, age=65} On peut additionner les âges de nos personnes,\nassert persons.stream() .map(Person::getAge) .reduce(0, Integer::sum) == 65; L\u0026rsquo;argument idendity est l\u0026rsquo;élément identité de la réduction, il ne doit avoir aucun effet sur la fonction d\u0026rsquo;accumulation (accumulator dans la signature de reduce). C\u0026rsquo;est le cas ici puisque x + 0 = x.\nEt on peut encore écrire le reduce sous la forme\nassert persons.stream() .reduce(0, (result, person) -\u0026gt; result + person.getAge(), (a, b) -\u0026gt; a + b ) == 65; L\u0026rsquo;accumulator définit l\u0026rsquo;opération effectué sur chacun des éléments et retient ce résultat tandis que le combiner décrit l\u0026rsquo;opération effectuée pour combiner deux résultats de la fonction accumulator.\nCette fois ci identity ne doit pas avoir d\u0026rsquo;effet sur le combiner.\nLa fonction combiner doit être associative pour assurer la cohérence du résultat lors du traitement du Stream en parallèle.\ncollect # Permet de réunir tous les éléments en utilisant un Collector. C\u0026rsquo;est bien entendu une opération terminale.\npersons.stream() .map(Person::getLastName) .collect(Collectors.toList()); // [Doe, Doe, Smith] La classe Collectors regorge d\u0026rsquo;implémentations de Collector pour faciliter l\u0026rsquo;opération.\nOn peut en avoir un très bon aperçu dans la présentation de José Paumard : Java 8 Streams \u0026amp; Collectors : patterns, performances, parallélisation\nconcat # On peut concaténer des Stream.\nIntStream.concat( IntStream.range(0, 4), IntStream.range(4, 6) ).forEach(System.out::print); // 012345 findFirst, findAny # Des opérations terminales qui permettent de retourner le premier élément du Stream s\u0026rsquo;il existe en tant qu\u0026rsquo;Optional,\npersons.stream() .findFirst() .ifPresent(System.out::print); // Person{firstName=\u0026#39;John\u0026#39;, lastName=\u0026#39;Doe\u0026#39;, age=30} ou encore n\u0026rsquo;importe quel élément,\npersons.stream() .parallel() .findAny() .ifPresent(System.out::print); Le résultat de findAny n\u0026rsquo;est pas constant, le résultat peut varier.\nflatMap # C\u0026rsquo;est une opération intermédiraire qui permet de mettre à plat un Stream.\nOn peut par exemple transformer un Stream\u0026lt;List\u0026lt;Person\u0026gt;\u0026gt; en Stream\u0026lt;Person\u0026gt; via une Function\u0026lt;List\u0026lt;Person\u0026gt;, Stream\u0026lt;Person\u0026gt;\u0026gt; :\nStream.\u0026lt;List\u0026lt;Person\u0026gt;\u0026gt;builder() .add(asList(new Person(\u0026#34;John\u0026#34;, \u0026#34;Doe\u0026#34;, 30), new Person(\u0026#34;Jane\u0026#34;, \u0026#34;Doe\u0026#34;, 20))) .add(asList(new Person(\u0026#34;Jim\u0026#34;, \u0026#34;Smith\u0026#34;, 15))) .build() .flatMap(persons -\u0026gt; persons.stream()) .filter(person -\u0026gt; \u0026#34;Doe\u0026#34;.equals(person.getLastName())) .forEach(System.out::println); // Person{firstName=\u0026#39;John\u0026#39;, lastName=\u0026#39;Doe\u0026#39;, age=30} // Person{firstName=\u0026#39;Jane\u0026#39;, lastName=\u0026#39;Doe\u0026#39;, age=20} limit, skip # Permet de se déplacer ou de bloquer le nombre d\u0026rsquo;éléments d\u0026rsquo;un Stream. Ce sont des opérations intermédiaires.\npersons.stream() .limit(2) .skip(1) .findFirst() .ifPresent(System.out::print); // Person{firstName=\u0026#39;Jane\u0026#39;, lastName=\u0026#39;Doe\u0026#39;, age=20} min, max # Des opérations terminales pour trouver le minimum ou le maximum (en tant qu\u0026rsquo;Optional) d\u0026rsquo;un Stream s\u0026rsquo;ils existent à l\u0026rsquo;aide d\u0026rsquo;un Comparator.\nassert IntStream.rangeClosed(1, 10) .min() .getAsInt() == 1; assert IntStream.rangeClosed(1, 10) .max() .getAsInt() == 10; peek # Une méthode utile pour débugger entre les opérations efféctuées sur un Stream. C\u0026rsquo;est une opération intermédiaire qui exécute un Consumer sur chacun des éléments.\npersons.stream() .filter(p -\u0026gt; \u0026#34;Doe\u0026#34;.equals(p.getLastName())) .peek(System.out::println) .filter(p -\u0026gt; p.getAge() \u0026lt; 25) .peek(System.out::println) .collect(Collectors.toSet()); // Person{firstName=\u0026#39;John\u0026#39;, lastName=\u0026#39;Doe\u0026#39;, age=30} // Person{firstName=\u0026#39;Jane\u0026#39;, lastName=\u0026#39;Doe\u0026#39;, age=20} // Person{firstName=\u0026#39;Jane\u0026#39;, lastName=\u0026#39;Doe\u0026#39;, age=20} Parallel Stream # Par défaut les opérations effectuées sur un Stream sont séquentielles. Pour paralléliser les processus il suffit d\u0026rsquo;utiliser la méthode parallel.\nOn peut changer l\u0026rsquo;état d\u0026rsquo;un Stream au cours de son utilisation en utilisant sequential et parallel.\nBien ententu il ne s\u0026rsquo;agit pas d\u0026rsquo;utiliser parallel sur chaque Stream en espèrant un temps de traitement moins long (ça peut même être le contraire). Il conviendra donc de s\u0026rsquo;assurer de la pertinence de son utilisation.\nMap # Les Map ne sont pas compatibles avec les Stream. Elles ont quand même le droit à leur lot de nouvelles fonctionalités.\nputIfAbsent # Associe une valeur avec une clef seulement si la clef n\u0026rsquo;existe pas encore ou si la valeur associée à la clef vaut null.\nMap\u0026lt;String, String\u0026gt; map = new HashMap\u0026lt;\u0026gt;(); assert map.putIfAbsent(\u0026#34;key\u0026#34;, null) == null; assert map.get(\u0026#34;key\u0026#34;) == null; assert map.putIfAbsent(\u0026#34;key\u0026#34;, \u0026#34;value\u0026#34;) == null; assert map.get(\u0026#34;key\u0026#34;).equals(\u0026#34;value\u0026#34;); assert map.putIfAbsent(\u0026#34;key\u0026#34;, \u0026#34;new-value\u0026#34;).equals(\u0026#34;value\u0026#34;); assert map.get(\u0026#34;key\u0026#34;).equals(\u0026#34;value\u0026#34;); forEach # Utilise un BiConsumer pour consommer tous les couples d\u0026rsquo;une Map.\nMap\u0026lt;String, Integer\u0026gt; map = new HashMap\u0026lt;\u0026gt;(); map.put(\u0026#34;un\u0026#34;, 1); map.put(\u0026#34;deux\u0026#34;, 2); map.put(\u0026#34;trois\u0026#34;, 3); map.forEach((key, value) -\u0026gt; System.out.printf(\u0026#34;%s(%d) \u0026#34;, key, value)); // trois(3) un(1) deux(2) computeIf # Permet d\u0026rsquo;appliquer une BiFunction sur les valeurs de la map.\ncompute # Si la Function retourne null, le couple est supprimé de la map.\nassert map.compute(\u0026#34;un\u0026#34;, (key, value) -\u0026gt; null) == null; assert map.compute(\u0026#34;deux\u0026#34;, (key, value) -\u0026gt; value == null ? null : value + 2) == 4; // trois(3) deux(4) computeIfAbsent # Tente la création d\u0026rsquo;une valeur pour une clef si celle-ci n\u0026rsquo;est associée à aucune valeur (ou si elle est null) en utilisant une Function.\nassert map.computeIfAbsent(\u0026#34;un\u0026#34;, key -\u0026gt; null) == 1; assert map.computeIfAbsent(\u0026#34;quatre\u0026#34;, key -\u0026gt; 4) == 4; // trois(3) quatre(4) un(1) deux(2) computeIfPresent # Tente de mettre à jour la valeur associée à une clef si la valeur en question est non-null avec une BiFunction. Si la Function retourne null, le couple est supprimé de la Map.\nMap\u0026lt;String, Integer\u0026gt; map = new HashMap\u0026lt;\u0026gt;(); map.put(\u0026#34;un\u0026#34;, 1); map.put(\u0026#34;deux\u0026#34;, 2); map.put(\u0026#34;trois\u0026#34;, 3); map.put(\u0026#34;quatre\u0026#34;, null); assert map.computeIfPresent(\u0026#34;trois\u0026#34;, (key, value) -\u0026gt; value + 5) == 8; assert map.computeIfPresent(\u0026#34;un\u0026#34;, (key, value) -\u0026gt; null) == null; assert map.computeIfPresent(\u0026#34;quatre\u0026#34;, (key, value) -\u0026gt; value + 5) == null; // trois(8) quatre(null) deux(2) remove # Supprime l\u0026rsquo;Entry pour la clef en argument si et seulement si la valeur associée à cette clef est égale à celle en argument.\nassert map.remove(\u0026#34;un\u0026#34;, 1) == true; assert map.remove(\u0026#34;deux\u0026#34;, 3) == false; // trois(3) deux(2) getOrDefault # Retourne une valeur par défaut si aucune valeur n\u0026rsquo;est associée à la clef en argument.\nassert map.getOrDefault(\u0026#34;un\u0026#34;, -1) == 1; assert map.getOrDefault(\u0026#34;six\u0026#34;, -1) == -1; merge # Associe la valeur non-null en argument à une clef si cette clef n\u0026rsquo;est pas déjà associée à une valeur (ou si la valeur est null), sinon applique la BiFunction sur la valeur existante.\nMap\u0026lt;Integer, String\u0026gt; map = new HashMap\u0026lt;\u0026gt;(); map.put(1, \u0026#34;un\u0026#34;); map.put(2, \u0026#34;deux\u0026#34;); assert map.merge(1, \u0026#34; exemple\u0026#34;, String::concat).equals(\u0026#34;un exemple\u0026#34;); assert map.merge(2, \u0026#34;exemples\u0026#34;, (key, value) -\u0026gt; null) == null; // 1(un exemple) replace, replaceAll # Remplace une valeur associée à une clef si cette valeur est égale à celle en argument.\nMap\u0026lt;String, Integer\u0026gt; map = new HashMap\u0026lt;\u0026gt;(); map.put(\u0026#34;un\u0026#34;, 1); map.put(\u0026#34;deux\u0026#34;, 2); assert map.replace(\u0026#34;un\u0026#34;, 1, 5) == true; assert map.replace(\u0026#34;deux\u0026#34;, 3, 5) == false; // un(5) deux(2) Remplace toutes les valeurs en appliquant la BiFunction en paramètre\nMap\u0026lt;String, Integer\u0026gt; map = new HashMap\u0026lt;\u0026gt;(); map.put(\u0026#34;un\u0026#34;, 1); map.put(\u0026#34;deux\u0026#34;, 2); map.replaceAll((key, value) -\u0026gt; \u0026#34;un\u0026#34;.equals(key) ? value + 1 : value + 2); assert map.get(\u0026#34;un\u0026#34;) == 2; assert map.get(\u0026#34;deux\u0026#34;) == 4; Date # Une toute nouvelle API pour les dates est disponible dans le package java.time. Elle s\u0026rsquo;inspire de Joda-Time et toutes les classes de cette API sont enfin immuables et threadsafe !\nLocalTime # Cette classe représente une heure au sein d\u0026rsquo;une journée sans information de timezone, c\u0026rsquo;est l\u0026rsquo;heure qu\u0026rsquo;on affiche sur une horloge.\nLocalTime time1 = LocalTime.of(13, 37, 26); LocalTime time2 = LocalTime.of(15, 47, 54); assert time1.isBefore(time2) == true; LocalTime time3 = time1.plusMinutes(5); assert ChronoUnit.HOURS.between(time1, time2) == 2; assert ChronoUnit.MINUTES.between(time1, time3) == 5; time1.format(DateTimeFormatter.ISO_LOCAL_TIME); // 13:37:26 time3.format(DateTimeFormatter.ofPattern(\u0026#34;Ha\u0026#34;)); // 13PM DateTimeFormatter formatter = DateTimeFormatter .ofLocalizedTime(FormatStyle.SHORT) .withLocale(Locale.FRANCE); LocalTime time4 = LocalTime.parse(\u0026#34;17:17\u0026#34;, formatter); assert time4.getHour() == 17; assert time4.getMinute() == 17; LocalDate # Représente une date sans information d\u0026rsquo;heures, c\u0026rsquo;est ce qu\u0026rsquo;on utilise pour une date de naissance.\nLocalDate today = LocalDate.now(); LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS); LocalDate yesterday = today.minusDays(1); LocalDate birthday = LocalDate.of(2014, Month.DECEMBER, 18); assert birthday.getDayOfWeek() == DayOfWeek.THURSDAY; DateTimeFormatter formatter = DateTimeFormatter .ofLocalizedDate(FormatStyle.LONG) .withLocale(Locale.FRANCE); LocalDate date = LocalDate.parse(\u0026#34;14 juillet 2014\u0026#34;, formatter); assert date.getDayOfMonth() == 14; assert date.getMonth() == Month.JULY; assert date.getYear() == 2014; LocalDateTime # Et voilà une date avec des heures mais toujours sans timezone. L\u0026rsquo;API reste toujours très similaire aux Local* précédents.\nLocalDateTime time = LocalDateTime.of(2014, Month.DECEMBER, 25, 12, 10); assert time.get(ChronoField.YEAR) == 2014; time.format(DateTimeFormatter.ofPattern(\u0026#34;dd/MM/yyyy HH:mm\u0026#34;)); // 25/12/2014 12:10 ZonedDateTime # La version ultime avec une timezone en plus.\nZonedDateTime today = ZonedDateTime.now(ZoneId.of(\u0026#34;Europe/Paris\u0026#34;)); System.out.println(today.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)); // 2014-04-21T20:26:53.275+02:00[Europe/Paris] ZoneId # Cette classe représente les timezone manipulables dans l\u0026rsquo;API.\nZoneId.getAvailableZoneIds(); // [Asia/Aden, America/Cuiaba, Etc/GMT+9, Etc/GMT+8, Africa/Nairobi, America/Marigot... ZoneId paris = ZoneId.of(\u0026#34;Europe/Paris\u0026#34;); paris.getDisplayName(TextStyle.FULL, Locale.FRENCH); // Heure d\u0026#39;Europe centrale paris.getDisplayName(TextStyle.SHORT, Locale.FRENCH); // CET Instant # Représente un instant au cours du temps.\nInstant.parse(\u0026#34;2007-12-03T10:15:30.00Z\u0026#34;); On peut récupérer cet objet un peu partout depuis l\u0026rsquo;API, par exemple\nInstant now = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant(); // 2014-04-21T19:27:26.759Z Et il permet de créer un bon vieux java.util.Date pour la compatibilité.\nDate.from(now) Clock # Permet l\u0026rsquo;accès à l\u0026rsquo;Instant courant.\nInstant now = Clock.system(ZoneId.of(\u0026#34;Europe/Paris\u0026#34;)).instant(); // 2014-04-21T19:36:37.786Z long millis = Clock.systemDefaultZone().millis(); // 1398109144935 Duration # Représente une quantité de temps comme 42 secondes.\nDuration twentySeconds = Duration.parse(\u0026#34;PT20S\u0026#34;); assert twentySeconds.getSeconds() == 20; Duration oneMinuteTwentySeconds = Duration.ofMinutes(1).plus(twentySeconds); assert oneMinuteTwentySeconds.getSeconds() == 80; LocalTime time1 = LocalTime.of(21, 30, 17); LocalTime time2 = LocalTime.of(22, 40, 37); Duration duration = Duration.between(time1, time2); // PT1H10M20S assert duration.get(ChronoUnit.SECONDS) == 3600 + 600 + 20; Du sucre # StringJoiner # On peut enfin concaténer des String séparées par un délimiteur sans utiliser de librairies.\nString join = new StringJoiner(\u0026#34;,\u0026#34;) .add(\u0026#34;a\u0026#34;) .add(\u0026#34;b\u0026#34;) .add(\u0026#34;c\u0026#34;) .toString(); assert join.equals(\u0026#34;a,b,c\u0026#34;); String join = String.join(\u0026#34;,\u0026#34;, asList(\u0026#34;a\u0026#34;, \u0026#34;b\u0026#34;, \u0026#34;c\u0026#34;)); assert join.equals(\u0026#34;a,b,c\u0026#34;); Calendar.Builder # La mise en place du pattern Builder pour la classe Calendar.\nDes exemples sont disponibles dans l\u0026rsquo;article http://marxsoftware.blogspot.fr/2013/05/jdk-8-calendar-builder.html.\nBase64 # Plus besoin non plus d\u0026rsquo;utiliser une librairie pour encoder des données en Base64.\nBase64.getEncoder().encode(\u0026#34;JC\u0026#34;.getBytes()); //SkM= Annotations # Les annotations peuvent être déclarées sur les utilisations des types.\nInstantiation d\u0026rsquo;une classe :\nnew @Interned MyObject(); Cast :\nmyString = (@NonNull String) str; Implémentation d\u0026rsquo;une interface :\nclass UnmodifiableList\u0026lt;T\u0026gt; implements @Readonly List\u0026lt;@Readonly T\u0026gt; { ... } Déclaration des Exceptions:\nvoid monitorTemperature() throws @Critical TemperatureException { ... } Java 8 introduit la possibilité de rendre une annotation @Repeatable.\nVous avez déjà certainement vu le schéma d\u0026rsquo;utilisation suivant :\nune annotation d\u0026rsquo;un certain type, au hasard @JoinColumn, une seconde prenant comme paramètre un tableau d\u0026rsquo;éléments du premier type, @JoinColumns. Et on écrivait ensuite\n@JoinColumns({ @JoinColumn(name=\u0026#34;ADDR_ID\u0026#34;, referencedColumnName=\u0026#34;ID\u0026#34;), @JoinColumn(name=\u0026#34;ADDR_ZIP\u0026#34;, referencedColumnName=\u0026#34;ZIP\u0026#34;) }) public Address getAddress() { return address; } On peut maintenant se contenter de\n@JoinColumn(name=\u0026#34;ADDR_ID\u0026#34;, referencedColumnName=\u0026#34;ID\u0026#34;), @JoinColumn(name=\u0026#34;ADDR_ZIP\u0026#34;, referencedColumnName=\u0026#34;ZIP\u0026#34;) public Address getAddress() { return address; } si l\u0026#39;annotation `@JoinColumn` est annotée avec `@Repeatable(JoinColumns.class)`. Création # Il faut continuer de créer deux types.\npublic @interface Descriptions { Description[] value(); } @Repeatable(Descriptions.class) public @interface Description { String value(); } @Description pourra être répétée dans utiliser @Descriptions.\n@Description(\u0026#34;toto\u0026#34;) @Description(\u0026#34;tata\u0026#34;) public class Repeat {} En fait c\u0026rsquo;est le compilateur qui utilisera @Descriptions de manière transparente pour l\u0026rsquo;utilisateur.\nLecture # Description[] byType = Repeat.class.getAnnotationsByType(Description.class); assert byType.length == 2; Description withoutType = Repeat.class.getAnnotation(Description.class); assert withoutType == null; Descriptions byContainer = Repeat.class.getAnnotation(Descriptions.class); assert byContainer != null; assert byContainer.value().length == 2; On voit qu\u0026rsquo;il faut utiliser Class#getAnnotationsByType pour lire une annotation @Repeatable et que le compilateur a bien ajouté une annotation @Descriptions pour englober les @Description.\nNashorn # C\u0026rsquo;est un moteur Javascript executé au sein de la JVM ! Jusque là était disponible Rhino mais Nashorn est une nouvelle implémentation basée sur invokedynamic.\nIl est possible d\u0026rsquo;interpréter du js en ligne de commande avec l\u0026rsquo;utilitaire jjs disponible dans le jdk.\njjs jjs\u0026gt; print(\u0026quot;hello\u0026quot;); hello Mais on peut surtout embarquer le moteur dans ses programmes Java.\nScriptEngineManager engineManager = new ScriptEngineManager(); ScriptEngine engine = engineManager.getEngineByName(\u0026#34;nashorn\u0026#34;); engine.eval(\u0026#34;function sum(a, b) { return a + b; }\u0026#34;); assert (int) engine.eval(\u0026#34;sum(1, 2);\u0026#34;) == 3; Il existe de nombreuses possibilités d\u0026rsquo;intéraction entre votre code Java et le js évalué. L\u0026rsquo;article Oracle Nashorn: A next-generation JavaScript engine for the JVM présente toutes ces fonctionalités.\njdeps # Un analyseur de dépendances fait son apparition dans les outils du JDK.\njdeps -v Examples.class Examples.class -\u0026gt; /Library/Java/JavaVirtualMachines/jdk1.8.0_05.jdk/Contents/Home/jre/lib/rt.jar fr.jcgay.example.java.Strings.Examples -\u0026gt; java.lang.AssertionError fr.jcgay.example.java.Strings.Examples -\u0026gt; java.lang.CharSequence fr.jcgay.example.java.Strings.Examples -\u0026gt; java.lang.Class fr.jcgay.example.java.Strings.Examples -\u0026gt; java.lang.Iterable fr.jcgay.example.java.Strings.Examples -\u0026gt; java.lang.Object fr.jcgay.example.java.Strings.Examples -\u0026gt; java.lang.String fr.jcgay.example.java.Strings.Examples -\u0026gt; java.util.Arrays fr.jcgay.example.java.Strings.Examples -\u0026gt; java.util.List fr.jcgay.example.java.Strings.Examples -\u0026gt; java.util.StringJoiner Direction l\u0026rsquo;article http://marxsoftware.blogspot.fr/2014/03/jdeps.html pour en savoir plus.\nProfils # Des sous-ensembles de l\u0026rsquo;API ont été définis et permettent de réduire la taille de la JVM : guide.\nJavadoc # La section \u0026ldquo;Method Summary\u0026rdquo; a évoluée légèrement en regroupant maintenant les méthodes par type (Exemple).\nJavadoc method summary Un outil de validation a fait son apparition, doclint, malheureusement activé par défaut. Il y a peu de chance que votre documentation soit valide\u0026hellip; Tout est très bien expliqué ici\nMetaspace # La suppression de la Permanent Generation (PermGen) laisse place au Metaspace.\nJava 8 from PermGen to Metaspace\nMetaspace in Java 8\nSources, pour aller plus loin # Understanding Method References\nJava 8 tutorial\n10 features in Java 8 you haven\u0026rsquo;t heard of\nAnnotations Basics\n50 nouvelles choses que l\u0026rsquo;on peut faire avec Java 8\nJava time\nJava 8 features\n","date":"4 mai 2014","externalUrl":null,"permalink":"/posts/java8-lambda-stream/","section":"Posts","summary":"Java 8 est maintenant disponible depuis plusieurs semaines, voici un “petit” résumé des fonctionalités de Java 8 que j’ai testé à travers tous les articles sur lesquels je suis tombé.\n","title":"A la découverte de Java 8","type":"posts"},{"content":"","date":"22 mars 2014","externalUrl":null,"permalink":"/tags/build/","section":"Tags","summary":"","title":"Build","type":"tags"},{"content":"Avec Maven, je me suis souvent retrouvé un peu seul devant un POM en me demandant ce que pouvait bien exécuter mon build sans devoir aller lister toutes les exécutions déclarées. Depuis Maven 3 il est possible de prévoir ce qu\u0026rsquo;un build va exécuter au sein de son cycle de vie. Etrangement il n\u0026rsquo;y a rien qui permet d\u0026rsquo;afficher facilement ce plan de build calculé. Voici le buildplan-maven-plugin.\nConfiguration # Pour utiliser le plugin facilement, déclarer son groupId au sein de la configuration de Maven (ie settings.xml) :\n\u0026lt;pluginGroups\u0026gt; \u0026lt;pluginGroup\u0026gt;fr.jcgay.maven.plugins\u0026lt;/pluginGroup\u0026gt; \u0026lt;/pluginGroups\u0026gt; Afficher le plan du build # Il est possible de lister toutes les exécutions de goals qui font parties du build. La simulation se déroule jusqu\u0026rsquo;à la phase deploy.\n\u0026gt; mvn buildplan:list [INFO] --- buildplan-maven-plugin:1.0:list (default-cli) @ buildplan-maven-plugin --- [INFO] -------------------------------------------------------------------------------------------------------------------- [INFO] PLUGIN | PHASE | ID | GOAL [INFO] -------------------------------------------------------------------------------------------------------------------- [INFO] maven-plugin-plugin | generate-sources | help-goal | helpmojo [INFO] maven-plugin-plugin | process-classes | default-descriptor | descriptor [INFO] maven-resources-plugin | process-resources | default-resources | resources [INFO] maven-compiler-plugin | compile | default-compile | compile [INFO] maven-plugin-plugin | process-classes | mojo-descriptor | descriptor [INFO] maven-resources-plugin | process-test-resources | default-testResources | testResources [INFO] maven-compiler-plugin | test-compile | default-testCompile | testCompile [INFO] maven-surefire-plugin | test | default-test | test [INFO] animal-sniffer-maven-plugin | process-classes | check-signature | check [INFO] maven-jar-plugin | package | default-jar | jar [INFO] maven-plugin-plugin | package | default-addPluginArtifactMetadata | addPluginArtifactMetadata [INFO] maven-source-plugin | package | attach-sources | jar-no-fork [INFO] license-maven-plugin | verify | default | check [INFO] maven-install-plugin | install | default-install | install [INFO] maven-deploy-plugin | deploy | default-deploy | deploy Lister les phases du plan # Une vue par phase au sein du build est disponible. On peut ainsi voir rapidement quels plugins seront exécutés au sein d\u0026rsquo;une phase.\n\u0026gt; mvn buildplan:list-phase [INFO] --- buildplan-maven-plugin:1.0:list-phase (default-cli) @ buildplan-maven-plugin --- [INFO] install ------------------------------------------------------------------------------------------ [INFO] + maven-install-plugin | default-install | install [INFO] generate-sources --------------------------------------------------------------------------------- [INFO] + maven-plugin-plugin | help-goal | helpmojo [INFO] process-resources -------------------------------------------------------------------------------- [INFO] + maven-resources-plugin | default-resources | resources [INFO] test --------------------------------------------------------------------------------------------- [INFO] + maven-surefire-plugin | default-test | test [INFO] test-compile ------------------------------------------------------------------------------------- [INFO] + maven-compiler-plugin | default-testCompile | testCompile [INFO] process-test-resources --------------------------------------------------------------------------- [INFO] + maven-resources-plugin | default-testResources | testResources [INFO] verify ------------------------------------------------------------------------------------------- [INFO] + license-maven-plugin | default | check [INFO] process-classes ---------------------------------------------------------------------------------- [INFO] + maven-plugin-plugin | default-descriptor | descriptor [INFO] + maven-plugin-plugin | mojo-descriptor | descriptor [INFO] + animal-sniffer-maven-plugin | check-signature | check [INFO] compile ------------------------------------------------------------------------------------------ [INFO] + maven-compiler-plugin | default-compile | compile [INFO] package ------------------------------------------------------------------------------------------ [INFO] + maven-jar-plugin | default-jar | jar [INFO] + maven-plugin-plugin | default-addPluginArtifactMetadata | addPluginArtifactMetadata [INFO] + maven-source-plugin | attach-sources | jar-no-fork [INFO] deploy ------------------------------------------------------------------------------------------- [INFO] + maven-deploy-plugin | default-deploy | deploy Pour filtrer la description d\u0026rsquo;une seule phase, utiliser le paramètre buildplan.phase :\nmvn buildplan:list-phase -Dbuildplan.phase=test Lister les plugins # On peut grouper les exécutions par plugin.\n\u0026gt; mvn buildplan:list-plugin [INFO] --- buildplan-maven-plugin:1.0:list-plugin (default-cli) @ buildplan-maven-plugin --- [INFO] maven-deploy-plugin ------------------------------------------------------------------------- [INFO] + deploy | default-deploy | deploy [INFO] maven-source-plugin ------------------------------------------------------------------------- [INFO] + package | attach-sources | jar-no-fork [INFO] license-maven-plugin ------------------------------------------------------------------------ [INFO] + verify | default | check [INFO] maven-resources-plugin ---------------------------------------------------------------------- [INFO] + process-resources | default-resources | resources [INFO] + process-test-resources | default-testResources | testResources [INFO] maven-plugin-plugin ------------------------------------------------------------------------- [INFO] + generate-sources | help-goal | helpmojo [INFO] + process-classes | default-descriptor | descriptor [INFO] + process-classes | mojo-descriptor | descriptor [INFO] + package | default-addPluginArtifactMetadata | addPluginArtifactMetadata [INFO] maven-jar-plugin ---------------------------------------------------------------------------- [INFO] + package | default-jar | jar [INFO] animal-sniffer-maven-plugin ----------------------------------------------------------------- [INFO] + process-classes | check-signature | check [INFO] maven-surefire-plugin ----------------------------------------------------------------------- [INFO] + test | default-test | test [INFO] maven-compiler-plugin ----------------------------------------------------------------------- [INFO] + compile | default-compile | compile [INFO] + test-compile | default-testCompile | testCompile [INFO] maven-install-plugin ------------------------------------------------------------------------ [INFO] + install | default-install | install Le paramètre buildplan.plugin permet de filtrer un plugin en particulier :\nmvn buildplan:list-plugin -Dbuildplan.plugin=maven-compiler-plugin Et voilà comment on peut explorer ce que va exécuter Maven pendant un build !\n","date":"22 mars 2014","externalUrl":null,"permalink":"/posts/maven-build-plan/","section":"Posts","summary":"Avec Maven, je me suis souvent retrouvé un peu seul devant un POM en me demandant ce que pouvait bien exécuter mon build sans devoir aller lister toutes les exécutions déclarées. Depuis Maven 3 il est possible de prévoir ce qu’un build va exécuter au sein de son cycle de vie. Etrangement il n’y a rien qui permet d’afficher facilement ce plan de build calculé. Voici le buildplan-maven-plugin.\n","title":"Le cycle de vie de son build Maven","type":"posts"},{"content":"","date":"22 mars 2014","externalUrl":null,"permalink":"/tags/plan/","section":"Tags","summary":"","title":"Plan","type":"tags"},{"content":"","date":"17 mars 2014","externalUrl":null,"permalink":"/tags/elegant/","section":"Tags","summary":"","title":"Elegant","type":"tags"},{"content":"Retour sur la migration d\u0026rsquo;un blog construit avec WordPress pour un site statique généré avec Pelican.\nMais pourquoi ? # Tout simplement pour avoir quelque chose de plus léger\u0026hellip; J\u0026rsquo;avais volontairement gardé mon installation de WordPress minimaliste mais l\u0026rsquo;outil prends du poids par nature (en tout cas pour des fonctionalités que je n\u0026rsquo;utilisais pas). Peu de plugins étaient installés mais les temps de chargement ne me convenaient pas (hébergement mutualisé pour du PHP).\nComme j\u0026rsquo;écrivais déjà mes textes en Markdown pour les convertir en HTML et les coller dans l\u0026rsquo;éditeur de WordPress, j\u0026rsquo;ai sauté le pas pour un générateur de contenu statique.\nPelican # Pelican est un générateur de site statique écrit en Python. Il supporte Markdown, AsciiDoc et reStructuredText.\nIl en existe une pléthore, les plus connus étant certainement Jekyll et Octopress.\nJ\u0026rsquo;ai choisi Pelican car il propose une fonctionalité multilangue, ce qu\u0026rsquo;il est possible de faire avec les autres mais pas de manière native.\nInstallation # Pour l\u0026rsquo;utiliser avec OS X :\nbrew install python pip install virtualenv ## Install Pelican mkdir -p ~/virtualenvs/pelican virtualenv ~/virtualenvs/pelican cd ~/virtualenvs/pelican . bin/activate pip install pelican pip install Markdown pip install beautifulsoup4 pip install typogrify pip install webassets pip install cssmin Sinon l\u0026rsquo;installation est détaillée ici.\nActiver l\u0026rsquo;environnement Pelican # . ~/virtualenvs/pelican/bin/activate Quitter l\u0026rsquo;environnement Pelican # deactivate Créer un site # Il suffit de lancer pelican-quickstart et de suivre les instructions.\nWelcome to pelican-quickstart v3.3.0. This script will help you create a new Pelican-based website. Please answer the following questions so this script can generate the files needed by Pelican. \u0026gt; Where do you want to create your new web site? [.] blog \u0026gt; What will be the title of this web site? Jean-Christophe Gay \u0026gt; Who will be the author of this web site? Jean-Christophe Gay \u0026gt; What will be the default language of this web site? [en] fr \u0026gt; Do you want to specify a URL prefix? e.g., http://example.com (Y/n) Y \u0026gt; What is your URL prefix? (see above example; no trailing slash) http://jeanchristophegay.com \u0026gt; Do you want to enable article pagination? (Y/n) Y \u0026gt; How many articles per page do you want? [10] \u0026gt; Do you want to generate a Fabfile/Makefile to automate generation and publishing? (Y/n) Y \u0026gt; Do you want an auto-reload \u0026amp; simpleHTTP script to assist with theme and site development? (Y/n) Y \u0026gt; Do you want to upload your website using FTP? (y/N) N \u0026gt; Do you want to upload your website using SSH? (y/N) N \u0026gt; Do you want to upload your website using Dropbox? (y/N) N \u0026gt; Do you want to upload your website using S3? (y/N) N \u0026gt; Do you want to upload your website using Rackspace Cloud Files? (y/N) N Done. Your new project is available at /Users/jcgay/blog Les source générées:\nblog ├── Makefile ├── content │ ├── (pages) ├── develop_server.sh ├── fabfile.py ├── output ├── pelicanconf.py └── publishconf.py 2 directories, 5 files Ajouter du contenu # Les articles doivent être ajoutés dans le dosser content. Il est possible d\u0026rsquo;extraire le contenu de WordPress pour l\u0026rsquo;importer dans Pelican : http://docs.getpelican.com/en/3.3.0/importer.html#import\nJe ne l\u0026rsquo;ai pas testé car j\u0026rsquo;avais déjà des versions Markdown de mes articles. Je me suis contenté d\u0026rsquo;ajouter les métadonnées nécessaires à Pelican dans mes fichiers .md.\nPour qu\u0026rsquo;un article soit indexé comme tel par Pelican, il faut ajouter les métadonnées:\nTitle: git-p4 au secours de Perforce Date: 2012-03-18 18:10 Category: Scm Tags: git, git-p4, perforce, p4 Slug: git-p4-au-secours-de-perforce Author: Jean-Christophe Gay lang: fr La propriété slug permettra de conserver les mêmes URLs que celles utilisées par WordPress.\nChoisir un thème # Les thèmes sont disponibles dans le repository https://github.com/getpelican/pelican-themes.\nPour le moment j\u0026rsquo;ai choisi le thème elegant.\nCloner le répository des thèmes Pelican pour pouvoir les utiliser par la suite.\nLes plugins # Pelican est extensible par l\u0026rsquo;intermédiaire de plugins. Ils sont répertoriés dans le repository https://github.com/getpelican/pelican-plugins. Comme pour les thèmes il suffit de cloner le repository dans l\u0026rsquo;emplacement de son choix.\nLa liste des plugins que j\u0026rsquo;ai activé:\nsitemap : génère un fichier sitemap.xml pour l\u0026rsquo;indexation. tipue_search : moteur de recherche au sein du site sans utiliser un service tiers. extract_toc : extait une table des matières du contenu d\u0026rsquo;un article. optimize_images : compresse les images jpeg et png. related_posts : affiche une liste d\u0026rsquo;article en relation avec l\u0026rsquo;article lu. summary : défini un résumé de l\u0026rsquo;article en utilisant le tag \u0026lt;!-- PELICAN_END_SUMMARY --\u0026gt; dans les articles. assets : permet de minifier/compiler les resources statiques. Configuration # La configuration est séparée en deux fichiers: pelicanconf.py et publishconf.py. Le second surcharge le premier avec les paramètres utilisés lors de la génération du site de production.\nTous les paramètres sont documentés ici.\nJe n\u0026rsquo;ai pas rencontré de difficultés particulières, deux petites astuces quand même.\nPour générer des URLs identiques à celles que j\u0026rsquo;avais avec WordPress :\nARTICLE_URL = '{slug}' ARTICLE_SAVE_AS = '{slug}/index.html' Pour chaque article, un dossier est créé avec la valeur de la métadonnée slug contenant un fichier index.html.\nIl est possible de copier des fichiers avec la propriété EXTRA_PATH_METADATA :\nEXTRA_PATH_METADATA = { 'extra/humans.txt': {'path': 'humans.txt'}, 'extra/robots.txt': {'path': 'robots.txt'}, 'extra/.htaccess': {'path': '.htaccess'}, } Les commentaires # Si vous utilisez le système de commentaires de WordPress, il faut les migrer vers un service externe, par exemple Disqus. C\u0026rsquo;est déjà ce que j\u0026rsquo;utilisais mais sinon il suffit d\u0026rsquo;installer le plugin WordPress.\nPour chacun des articles, ajouter l\u0026rsquo;identifiant de chaque fil de discussion :\ndisqus_identifier: 7 http://jeanchristophegay.com/?p=7 C\u0026rsquo;est supporté par le thème elegant.\nRègles de redirection # Il faut réécrire les URLs qui vont disparaitre pour ne pas perdre toutes vos URLs qui se baladent dans la nature.\n\u0026lt;IfModule mod_rewrite.c\u0026gt; RewriteEngine On RewriteBase / RewriteRule feed/?$ feeds/rss.xml [R=301,L] RewriteRule wp-content/uploads/2013/03/Capture-d’écran-2013-03-09-à-11.24.20-300x182.png images/p4merge-mini.png [R=301,L] RewriteRule wp-content/uploads/2013/03/Capture-d’écran-2013-03-09-à-11.24.20.png images/p4merge.png [R=301,L] # Redirect wp-content/uploads/2012/03/git-p4.png to images/git-p4.png RewriteRule wp-content/uploads/[0-2][0-9][0-9][0-9]/[0-1][0-9]/([^/]+) images/$1 [R=301,L] # Redirect 2012/04 to archives.html#2012 RewriteRule ([0-2][0-9][0-9][0-9])/[0-1][0-9]/? archives.html#$1 [R=301,NE,L] # Redirect category/slug/ to categories.html#slug-ref RewriteRule category/([^/]+)/? categories.html#$1-ref [R=301,NE,L] # Redirect tag/slug/ to tags.html#slug-ref RewriteRule tag/([^/]+)/? tags.html#$1-ref [R=301,NE,L] # Redirect author/jcgay/ to / RewriteRule author/jcgay/? / [R=301,L] \u0026lt;/IfModule\u0026gt; Tester/Publier son site # Pour générer le site, lancer make html \u0026amp;\u0026amp; make serve.\nLe contenu est généré dans le dossier output et accessible via http://localhost:8000.\nLe contenu final est disponible en utilisant make publish. Il ne reste qu\u0026rsquo;à uploader le contenu en ligne.\nConclusion # Je suis plutôt satisfait du résultat, tout le site est généré à partir d\u0026rsquo;un simple repository git, celà permet de versionner correctement le contenu du site et j\u0026rsquo;ai les fonctionalités nécessaires à la publication de quelques articles de temps en temps. Mission Accomplie !\n","date":"17 mars 2014","externalUrl":null,"permalink":"/posts/migration-wordpress-pelican/","section":"Posts","summary":"Retour sur la migration d’un blog construit avec WordPress pour un site statique généré avec Pelican.\n","title":"Migration de WordPress vers Pelican","type":"posts"},{"content":"","date":"17 mars 2014","externalUrl":null,"permalink":"/tags/wordpress/","section":"Tags","summary":"","title":"Wordpress","type":"tags"},{"content":"Mini guide pour démarrer son environnement de développement Scala pour IntelliJ Idea sous OS X.\nInstallation SBT et Scala # Avec le gestionnaire de paquet Homebrew :\nbrew install scala --with-docs brew install sbt Pour passer des options supplémentaires à la JVM lors de l\u0026rsquo;utilisation de SBT, celà se passe via $SBT_OPTS ou dans le fichier ~/.sbtconfig. Utiliser les paramètres \u0026quot;-XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256M\u0026quot; pour commencer.\nConfiguration IntelliJ Idea # Direction la gestion des plugins et installer le plugin Scala disponible dans les plugins maintenus par JetBrains.\nLe reste de la configuration se déroulera lors de la première création d\u0026rsquo;un projet.\nCréation d\u0026rsquo;un projet (à la main) # Dans Idea, Create New Project puis choisir Scala Module :\nChoisir un nom de projet Utiliser /usr/local/opt/scala/idea en tant que Scala Home Cocher Make global libraries Le projet est prêt !\nLes avertissements ne sont pas activés par défaut à la compilation, l\u0026rsquo;IDE vous prévient quand même qu\u0026rsquo;il faut activer certaines options pour avoir le détail des avertissements avec des messages de type : scala: there were 1 feature warning(s); re-run with -feature for details\nIl suffit d\u0026rsquo;aller les options du projet, Open module settings \u0026gt; Facets \u0026gt; Scala et de cocher les avertissements désirés ou de spécifier l\u0026rsquo;option voulu dans le champ Additional compiler options.\nCréation d\u0026rsquo;un projet (SBT) # Structure du build # La structure d\u0026rsquo;un projet est assez classique pour un langage de la JVM : mkdir -p src/{main,test}/{scala,java,resources}\nL\u0026rsquo;outil de build majeur dans l\u0026rsquo;univers scala est SBT, le descripteur du projet prend la forme d\u0026rsquo;un fichier build.sbt:\nscalaVersion := \u0026quot;2.10.3\u0026quot; name := \u0026quot;mon-projet-scala\u0026quot; version := \u0026quot;1.0\u0026quot; scalacOptions ++= Seq(\u0026quot;-unchecked\u0026quot;, \u0026quot;-deprecation\u0026quot;, \u0026quot;-feature\u0026quot;) Les options ajoutées à scalac permettent d\u0026rsquo;afficher les avertissements les plus courants lors de la compilation.\nLes lignes vides ne sont pas une blague, c\u0026rsquo;est vraiment nécessaire à SBT pour détecter la fin d\u0026rsquo;une propriété de configuration…\nGénération des fichiers projets IntelliJ Idea # Dans sa version actuelle (12.1.6), Idea n\u0026rsquo;intègre pas encore SBT, on ne peut donc pas importer un projet à partir de son descripteur de build comme il est possible de le faire avec Maven ou Gradle.\nIl faut donc générer la configuration du projet à l\u0026rsquo;aide du plugin sbt-idea.\nCréer le fichier ~/.sbt/0.13/plugins/build.sbt pour déclarer le plugin de manière globale. Le plugin sera utilisable sur tous les projets sans devoir le déclarer dans chaque build.sbt. 0.13 correspondant à la version de SBT utilisée, ajouter :\naddSbtPlugin(\u0026quot;com.github.mpeltonen\u0026quot; % \u0026quot;sbt-idea\u0026quot; % \u0026quot;1.5.1\u0026quot;) A la racine du projet exécuter sbt gen-idea pour générer toute la configuration.\nLe projet peut maintenant est ouvert dans IntelliJ Idea.\nIl se peut que le compilateur scala soit configuré un peu n\u0026rsquo;importe comment, dans ce cas il faut aller dans les options du projet pour sélectionner un compiler library correct (celui créer en tant que global library par exemple) et supprimer ceux qui sont incorrects.\nMais, ça ne fonctionne pas les scripts ?! # Tous les exemples présentés pour apprendre le langage sont souvent des scripts.\nSi vos scripts sont écrits dans des fichiers .scala, Idea essaie de les compiler et échoue :\nexpected class or object definition println(\u0026quot;hello world\u0026quot;) ^ La solution consiste à renommer le script avec l\u0026rsquo;extension .sc, ce qui le transforme en scala worksheet (source).\nEt voilà !\n","date":"13 octobre 2013","externalUrl":null,"permalink":"/posts/configurer-idea-pour-developper-en-scala-sous-os-x/","section":"Posts","summary":"Mini guide pour démarrer son environnement de développement Scala pour IntelliJ Idea sous OS X.\n","title":"Configurer Idea pour développer en Scala sous OS X","type":"posts"},{"content":"","date":"13 octobre 2013","externalUrl":null,"permalink":"/tags/intellij-idea/","section":"Tags","summary":"","title":"Intellij Idea","type":"tags"},{"content":"","date":"13 octobre 2013","externalUrl":null,"permalink":"/tags/os-x/","section":"Tags","summary":"","title":"Os X","type":"tags"},{"content":"","date":"13 octobre 2013","externalUrl":null,"permalink":"/tags/sbt/","section":"Tags","summary":"","title":"Sbt","type":"tags"},{"content":"","date":"13 octobre 2013","externalUrl":null,"permalink":"/categories/scala/","section":"Categories","summary":"","title":"Scala","type":"categories"},{"content":"","date":"13 octobre 2013","externalUrl":null,"permalink":"/tags/scala/","section":"Tags","summary":"","title":"Scala","type":"tags"},{"content":"","date":"17 août 2013","externalUrl":null,"permalink":"/tags/github/","section":"Tags","summary":"","title":"Github","type":"tags"},{"content":"Tout fier de son projet hébergé sur GitHub, il existe un mécanisme de publication de site statique, les GitHub Pages. Petit tour de piste pour configurer le déploiement de site Maven sur cet espace.\nThe Maven way # Le maven-site-plugin permet de déployer les sites via différents protocoles configurables avec des providers wagon. Coup de chance il existe une section pour l\u0026rsquo;utilisation des gh-pages: Deploying your Maven site to GitHub\u0026rsquo;s gh-pages.\nMalheureusement je n\u0026rsquo;ai pas réussi à le faire fonctionner… Je suis tombé sur ce ticket qui donne des pistes (enfin pas sûr que ça fonctionne au final) mais qui indique surtout qu\u0026rsquo;il faut utiliser Maven SCM Publish Plugin.\nPar contre ce n\u0026rsquo;est pas intégré à la fonctionalité de reporting de Maven, pas de mvn site-deploy out of the box.\nJe ne l\u0026rsquo;ai pas testé, je me suis plutôt rabattu sur le plugin maven développé par GitHub qui avait l\u0026rsquo;air de répondre à mes attentes.\nPlugin GitHub # Dans le repository officiel de GitHub est disponible le Site Plugin.\nUn petit peu de configuration :\n\u0026lt;plugin\u0026gt; \u0026lt;artifactId\u0026gt;maven-site-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;3.3\u0026lt;/version\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;skipDeploy\u0026gt;true\u0026lt;/skipDeploy\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/plugin\u0026gt; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;com.github.github\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;site-maven-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;0.8\u0026lt;/version\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;message\u0026gt;Creating site for ${project.version}\u0026lt;/message\u0026gt; \u0026lt;repositoryOwner\u0026gt;_\u0026lt;/repositoryOwner\u0026gt; \u0026lt;repositoryName\u0026gt;_\u0026lt;/repositoryName\u0026gt; \u0026lt;server\u0026gt;github\u0026lt;/server\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;executions\u0026gt; \u0026lt;execution\u0026gt; \u0026lt;phase\u0026gt;site-deploy\u0026lt;/phase\u0026gt; \u0026lt;goals\u0026gt; \u0026lt;goal\u0026gt;site\u0026lt;/goal\u0026gt; \u0026lt;/goals\u0026gt; \u0026lt;/execution\u0026gt; \u0026lt;/executions\u0026gt; \u0026lt;/plugin\u0026gt; avec\nrepositoryOwner: nom d\u0026rsquo;utilisateur,\nrepositoryName: nom du repository,\nserver: identifiant du serveur dans settings.xml\n\u0026lt;servers\u0026gt; \u0026lt;server\u0026gt; \u0026lt;id\u0026gt;github\u0026lt;/id\u0026gt; \u0026lt;username\u0026gt;GitHubLogin\u0026lt;/username\u0026gt; \u0026lt;password\u0026gt;GitHubPassw0rd\u0026lt;/password\u0026gt; \u0026lt;/server\u0026gt; \u0026lt;/servers\u0026gt; Et voilà mvn site-deploy génère et publie le site sur vos gh-pages (peut prendre une dizaine de minutes avant qu\u0026rsquo;il ne soit disponible).\nSi vous ne l\u0026rsquo;avez pas encore fait, pensez à utiliser le skin maven-fluido-skin qui modernise le rendu de votre site !\n","date":"17 août 2013","externalUrl":null,"permalink":"/posts/publier-son-site-maven-sur-github-pages/","section":"Posts","summary":"Tout fier de son projet hébergé sur GitHub, il existe un mécanisme de publication de site statique, les GitHub Pages. Petit tour de piste pour configurer le déploiement de site Maven sur cet espace.\n","title":"Publier son site Maven sur GitHub Pages","type":"posts"},{"content":"","date":"18 juin 2013","externalUrl":null,"permalink":"/tags/assertj/","section":"Tags","summary":"","title":"Assertj","type":"tags"},{"content":"","date":"18 juin 2013","externalUrl":null,"permalink":"/tags/catch-exception/","section":"Tags","summary":"","title":"Catch-Exception","type":"tags"},{"content":"","date":"18 juin 2013","externalUrl":null,"permalink":"/tags/junit/","section":"Tags","summary":"","title":"Junit","type":"tags"},{"content":"Il existe différentes solutions pour écrire des assertions sur les exceptions. Le choix de l’une ou l’autre se fait généralement selon les tests que l’on souhaite écrire\u0026hellip; tour d’horizon des différents choix possibles.\n@Test(expected) avec JUnit # @Test(expected = IndexOutOfBoundsException.class) public void should_throw_exception() throws Exception { asList(0, 1).get(2); } Si le test échoue le message d\u0026rsquo;erreur est :\njava.lang.AssertionError: Expected exception: java.lang.IndexOutOfBoundsException C’est la méthode la plus simple, le code est compact et le but du test est clair. On ne peut pas effectuer d’autres assertions au sein du test (celui-ci s’arrête dès que l’exception attendue est levée), c’est un bien mais aussi un mal\u0026hellip; Le message de l’exception n\u0026rsquo;est pas testable, ce qui est pourtant bien pratique.\n@Rule ExpectedException avec JUnit # @Rule public ExpectedException thrownException = ExpectedException.none(); @Test public void should_throw_exception() throws Exception { thrownException.expect(IndexOutOfBoundsException.class); thrownException.expectMessage(containsString(\u0026#34;2\u0026#34;)); asList(0, 1).get(2); } Si le test échoue le message d\u0026rsquo;erreur est :\njava.lang.AssertionError: Expected test to throw an instance of java.lang.IndexOutOfBoundsException JUnit possède de nombreuses règles (@Rule) utilisables dans les tests. Celle-ci est dédiée aux exceptions. Cette solution permet, contrairement à la première, de réaliser des assertions plus précises sur l’exception levée. On peut vérifier le message, la présence ou non de cause, etc. En utilisant des matchers Hamcrest le message est testable de manière fine.\nJe trouve dommage de devoir écrire ses assertions avant l’appel au code testé, l’ordre de lecture se retrouve inversé mais on s’en accommode assez bien.\n@Test(expectedExceptions) avec TestNG # @Test(expectedExceptions = IndexOutOfBoundsException.class, expectedExceptionsMessageRegExp = \u0026#34;2\u0026#34;) public void should_throw_exception() throws Exception { asList(0, 1).get(2); } Si le test échoue le message d\u0026rsquo;erreur est :\nMethod TestNGExpectedTest.should_fail_because_no_exception_is_thrown()[...] should have thrown an exception of class java.lang.IndexOutOfBoundsException Quand le message ne correspond pas :\norg.testng.TestException: The exception was thrown with the wrong message: expected \u0026quot;2\u0026quot; but got \u0026quot;3\u0026quot; Cette méthode avec TestNG ressemble beaucoup à celle de JUnit sauf que l’on peut tester le message ! Pratique et suffisant dans la majorité des cas. On utilise une expression régulière pour valider le message.\nEtrangement expectedExceptions permet de renseigner un tableau d’Exception attendues, pas sûr que celà serve la lecture et la robustesse du test\u0026hellip;\ntry / fail / catch # @Test public void should_trow_exception() throws Exception { try { asList(0, 1).get(2); failBecauseExceptionWasNotThrown(IndexOutOfBoundsException.class); } catch (IndexOutOfBoundsException e) { assertThat(e).hasNoCause() .hasMessage(\u0026#34;2\u0026#34;); } } Si le test échoue le message d\u0026rsquo;erreur est :\njava.lang.AssertionError: Expected IndexOutOfBoundsException to be thrown Quand les autres solutions ne suffisent pas, par exemple pour tester un type personnalisé avec des données spécifiques ou pour vérifier la cause d’une exception, on peut toujours se reposer sur cette solution. Le test produit est assez verbeux mais le plus gros problème à mon sens est l\u0026rsquo;oubli du fail(). Si les tests sont écrits après le code de production, il est malheureusement assez facile de l\u0026rsquo;oublier au sein du bloc try.\ncatch-exception # @Test public void should_throw_exception() throws Exception { catchException(asList(0, 1)).get(2); assertThat(caughtException()) .isInstanceOf(IndexOutOfBoundsException.class) .hasMessage(\u0026#34;2\u0026#34;) .hasNoCause(); } Si le test échoue le message d\u0026rsquo;erreur est :\njava.lang.AssertionError: Expecting actual not to be null catch-exception permet également de tester l’exception levée de manière très précise mais possède l’avantage de ne pas devoir écrire ses assertions dans le bloc catch. Plus de soucis d’oubli du fail() (caughtException() retourne null si aucune exception n\u0026rsquo;est levée). Le code est également moins verbeux que le try / catch / fail.\nL’outil possède d’autres variantes d’écriture documentées sur son site. La forme présentée ici est celle que je préfère, puisque malheureusement la version BDD repose sur l’utilisation de FEST-assert 1.4.\nConclusion # Avec TestNG, j’utilise expectedExceptions quand le type de l’exception et son message sont suffisants pour mon test. Sinon avec JUnit ou pour des tests plus précis, j’utilise catch-exception avec AssertJ.\n","date":"18 juin 2013","externalUrl":null,"permalink":"/posts/les-exceptions-dans-les-tests-unitaires/","section":"Posts","summary":"Il existe différentes solutions pour écrire des assertions sur les exceptions. Le choix de l’une ou l’autre se fait généralement selon les tests que l’on souhaite écrire… tour d’horizon des différents choix possibles.\n","title":"Les exceptions dans les tests unitaires","type":"posts"},{"content":"","date":"18 juin 2013","externalUrl":null,"permalink":"/tags/testng/","section":"Tags","summary":"","title":"Testng","type":"tags"},{"content":"","date":"4 mai 2013","externalUrl":null,"permalink":"/tags/ansi-console/","section":"Tags","summary":"","title":"Ansi Console","type":"tags"},{"content":"Dans la quête de couleur, je me suis intéressé aux logs des applications Java. En fait il est très simple de configurer les différents frameworks de logs pour éclairer son quotidien.\nLogback # L’implémentation ConsoleAppender expose une propriété withJansi qui permet d’activer la coloration des logs. Il suffit ensuite d’utiliser un des pattern disponibles dans PatternLayout (celui configuré par défaut) pour customiser ses logs :\n%red, %boldRed, etc %highlight Ces mots clefs sont censés encapsuler d’autres expressions. La couleur sera appliquée sur l’expression contenue entre parenthèses.\n[%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg %n %highlight est une conversion qui permet de séparer les différents niveaux de logs. Une erreur sera affichée en rouge et en gras, un warn en rouge et un info en bleu.\n\u0026lt;configuration\u0026gt; \u0026lt;appender name=\u0026#34;STDOUT\u0026#34; class=\u0026#34;ch.qos.logback.core.ConsoleAppender\u0026#34;\u0026gt; \u0026lt;withJansi\u0026gt;true\u0026lt;/withJansi\u0026gt; \u0026lt;encoder\u0026gt; \u0026lt;pattern\u0026gt;%d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %logger{36} - %msg%n\u0026lt;/pattern\u0026gt; \u0026lt;/encoder\u0026gt; \u0026lt;/appender\u0026gt; \u0026lt;root level=\u0026#34;trace\u0026#34;\u0026gt; \u0026lt;appender-ref ref=\u0026#34;STDOUT\u0026#34; /\u0026gt; \u0026lt;/root\u0026gt; \u0026lt;/configuration\u0026gt; Sous Windows il est nécessaire d’ajouter la librairie Jansi à l’exécution de l\u0026rsquo;application.\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.fusesource.jansi\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jansi\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.10\u0026lt;/version\u0026gt; \u0026lt;scope\u0026gt;runtime\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; Documentation officielle : http://logback.qos.ch/manual/layouts.html#coloring\nLog4j 2.0 # Dans la nouvelle version de Log4j, le pattern %highlight est également disponible dans l’implémentation PatternLayout. Les couleurs par défaut sont différentes de celles de Logback mais il est possible de les surcharger :\n%highlight{%d [%t] %-5level: %msg%n%throwable}{FATAL=white, ERROR=red, WARN=blue, INFO=black, DEBUG=green, TRACE=blue} voir même d’utiliser les mêmes codes couleurs que Logback :\n%highlight{%d [%t] %-5level: %msg%n%throwable}{STYLE=Logback} Le deuxième pattern disponible est %style{pattern}{ANSI style}. Il permet de choisir une couleur pour une portion de texte. La liste complète des couleurs est disponible dans la documentation de Log4j 2.\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;configuration status=\u0026#34;OFF\u0026#34;\u0026gt; \u0026lt;appenders\u0026gt; \u0026lt;Console name=\u0026#34;Console\u0026#34; target=\u0026#34;SYSTEM_OUT\u0026#34;\u0026gt; \u0026lt;PatternLayout pattern=\u0026#34;%d{HH:mm:ss.SSS} [%t] %highlight{%-5level} %logger{36} - %msg%n\u0026#34;/\u0026gt; \u0026lt;/Console\u0026gt; \u0026lt;/appenders\u0026gt; \u0026lt;loggers\u0026gt; \u0026lt;root level=\u0026#34;trace\u0026#34;\u0026gt; \u0026lt;appender-ref ref=\u0026#34;Console\u0026#34;/\u0026gt; \u0026lt;/root\u0026gt; \u0026lt;/loggers\u0026gt; \u0026lt;/configuration\u0026gt; Sous Windows il est nécessaire d’ajouter la librairie Jansi à l’exécution de l\u0026rsquo;application.\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.fusesource.jansi\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jansi\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.10\u0026lt;/version\u0026gt; \u0026lt;scope\u0026gt;runtime\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; Log4j # Pas de solution officielle disponible pour le bon vieux Log4j\u0026hellip; J’ai écrit un Appender et un Layout (logj4-color) qui permettent d’utiliser le pattern #highlight pour coloriser les logs. Les couleurs utilisées sont :\nFATAL, rouge ERROR, rouge WARN, jaune INFO, gras L\u0026rsquo;appender AnsiConsoleAppender doit être utilisé sous Windows. Sinon le ConsoleAppender suffit.\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34; ?\u0026gt; \u0026lt;!DOCTYPE log4j:configuration SYSTEM \u0026#34;log4j.dtd\u0026#34;\u0026gt; \u0026lt;log4j:configuration xmlns:log4j=\u0026#34;http://jakarta.apache.org/log4j/\u0026#34;\u0026gt; \u0026lt;appender name=\u0026#34;console\u0026#34; class=\u0026#34;com.github.jcgay.log4j.color.appender.AnsiConsoleAppender\u0026#34;\u0026gt; \u0026lt;param name=\u0026#34;Target\u0026#34; value=\u0026#34;System.out\u0026#34;/\u0026gt; \u0026lt;layout class=\u0026#34;com.github.jcgay.log4j.color.layout.ColorEnhancedPatternLayout\u0026#34;\u0026gt; \u0026lt;param name=\u0026#34;ConversionPattern\u0026#34; value=\u0026#34;#highlight(%-5p) %c{1} - %m%n\u0026#34;/\u0026gt; \u0026lt;/layout\u0026gt; \u0026lt;/appender\u0026gt; \u0026lt;root\u0026gt; \u0026lt;priority value =\u0026#34;trace\u0026#34; /\u0026gt; \u0026lt;appender-ref ref=\u0026#34;console\u0026#34; /\u0026gt; \u0026lt;/root\u0026gt; \u0026lt;/log4j:configuration\u0026gt; Il suffit d’ajouter la librairie log4j-color à l’exécution de l’application.\n\u0026lt;project\u0026gt; ... \u0026lt;repositories\u0026gt; \u0026lt;repository\u0026gt; \u0026lt;id\u0026gt;jcgay-releases\u0026lt;/id\u0026gt; \u0026lt;url\u0026gt;https://repository-jcgay.forge.cloudbees.com/release/\u0026lt;/url\u0026gt; \u0026lt;/repository\u0026gt; \u0026lt;/repositories\u0026gt; ... \u0026lt;dependencies\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.github.jcgay.log4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;log4j-color\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;0.1\u0026lt;/version\u0026gt; \u0026lt;scope\u0026gt;runtime\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; ... \u0026lt;/project\u0026gt; Plugins pour les IDE # Pour profiter de toutes ces couleurs dans son IDE préféré, il faudra utiliser des plugins pour améliorer les consoles disponibles : IntelliJ Idea : Grep Console Eclipse : Ansi Console\n","date":"4 mai 2013","externalUrl":null,"permalink":"/posts/de-la-couleur-dans-les-logs/","section":"Posts","summary":"Dans la quête de couleur, je me suis intéressé aux logs des applications Java. En fait il est très simple de configurer les différents frameworks de logs pour éclairer son quotidien.\n","title":"De la couleur dans les logs","type":"posts"},{"content":"","date":"4 mai 2013","externalUrl":null,"permalink":"/tags/grep-console/","section":"Tags","summary":"","title":"Grep Console","type":"tags"},{"content":"","date":"4 mai 2013","externalUrl":null,"permalink":"/tags/jansi/","section":"Tags","summary":"","title":"Jansi","type":"tags"},{"content":"","date":"4 mai 2013","externalUrl":null,"permalink":"/tags/log/","section":"Tags","summary":"","title":"Log","type":"tags"},{"content":"","date":"4 mai 2013","externalUrl":null,"permalink":"/tags/log4j/","section":"Tags","summary":"","title":"Log4j","type":"tags"},{"content":"","date":"4 mai 2013","externalUrl":null,"permalink":"/tags/log4j-2/","section":"Tags","summary":"","title":"Log4j 2","type":"tags"},{"content":"","date":"14 avril 2013","externalUrl":null,"permalink":"/tags/bash/","section":"Tags","summary":"","title":"Bash","type":"tags"},{"content":"Maven, out of the box n’est pas l’outil le plus sexy du monde\u0026hellip; Utilisé dans un IDE, il saura se faire oublié mais il mérite mieux lors de son usage en ligne de commande.\nComplétion # Il existe des scripts de complétion automatique des commandes Maven.\nLes utilisateurs de bash pourront compter sur :\nmaven-bash-completion, une représentation statique de plugins/goals/options. Celà fonctionne très bien, malheureusement il faut que chaque plugin soit décrit pour pouvoir être reconnu. Un second script maintenu dans le paquet ubuntu de maven. Il fonctionne sur la même base que le précédent. Si vous utilisez zsh, deux solutions sont disponibles :\nle plugin mvn au sein du framework oh-my-zsh s’occupe de la complétion. En cadeau on a également tout une collection d\u0026rsquo;alias très pratique. dans zsh-completions, un script permet de découvrir les goals de manière automatique à la première utilisation d\u0026rsquo;un plugin (mvn plugin:\u0026lt;TAB\u0026gt;). La première exécution est un peu plus longue mais après la complétion est instantanée (Merci à Thomas Queste qui en a parlé dans les commentaires). Notifications de bureau # Avoir un build rapide c’est bien\u0026hellip; seulement il arrive que ce ne soit pas du tout le cas. Rester devant sa console pour un build qui dure plusieurs minutes n’a pas vraiment d’intérêt\u0026hellip; Pouvoir être notifié du résultat à la fin de son build devient alors indispensable.\nmaven-notifier # J’ai développé une extension Maven qui permet d’envoyer une notification à la fin d’un build : maven-notifier. Il fonctionne avec tous les systèmes d\u0026rsquo;exploitation.\nPar défaut, l\u0026rsquo;extension tentera de trouver un notifier disponible sur la machine depuis cette liste d\u0026rsquo;outils.\nIl est possible de configurer le système de notification utilisé et bien d\u0026rsquo;autres choses (format des messages, etc) en créant un fichier maven-notifier.properties.\nNotifier Capture d\u0026rsquo;écran Growl, disponible pour Windows et OS X. maven-notifier-growl Snarl, disponible pour Windows maven-notifier-snarl terminal-notifier, OS X notification center OS X (depuis Mavericks) notify-send pour Linux SystemTray depuis Java 6 Pushbullet maven-notifier-pushbullet Kdialog utilisable avec KDE maven-notifier-kdialog notifu disponible pour Windows maven-notifier-notifu AnyBar pour OS X et Linux maven-notifier-anybar Toaster pour Windows 8 Notify since Java 6 BurntToast for Windows 10 Slack Les scripts # On peut aussi se contenter de wrapper la commande mvn pour récupérer son statut et envoyer une notification.\nGrowl via growlnotify : Intégration de Growl avec Maven, maven-growl notification center via terminal-notifier : maven-notification-center notify-send : Intégration de notify-send avec Maven. Couleurs # La sortie console de Maven est toute terne. Il n’est pas toujours facile d’y retrouver les messages d’erreurs et c’est encore pire pour les messages d’avertissement\u0026hellip;\nmaven-color # maven-color ajoute de la couleur dans les logs de Maven.\nLes dernières versions de Maven (depuis la version 3.1.x) utilisent SLF4J en interne, cela veut dire que l\u0026rsquo;on peut choisir l\u0026rsquo;implémentation de log que l\u0026rsquo;on veut !\nCet article très complet explique comment avoir une console en couleur avec Log4j 2 ou Logback. C\u0026rsquo;est la technique utilisée par maven-color\nPour les versions plus anciennes (entre la 3.0.x et la 3.1.0), j’ai utilisé Jansi et remplacé les loggers utilisés en interne par Maven avec un agent. Cela fonctionne avec les goals interactifs, par contre c’est beaucoup moins facile à mettre en place et bien moins résistant aux changements d’implémentation au sein de Maven et du plugin Surefire\u0026hellip; maven-color.\nLes scripts # On peut ajouter un peu de couleur pour y voir plus clair :\nmvn-in-colors mvnc maven-antsy-color Ces scripts sont très biens et faciles à mettre en place, malheureusement ils ne fonctionnent pas avec les goals interactifs. N’espérez pas utiliser maven-release-plugin ou maven-archetype-plugin en mode interactif, les messages de demande de saisis n’apparaitront pas.\nTout en un (pour OS X) # Comme c\u0026rsquo;était dommage de réinstaller toutes ces extensions à la main pour chaque version de Maven, j\u0026rsquo;ai créé une formule pour brew.\nbrew tap jcgay/jcgay brew install maven-deluxe Celà installera la dernière version de Maven, maven-notifier, maven-color et maven-profiler.\nConclusion # Xzibit Thanks Xzibit for pimping my Maven !\n","date":"14 avril 2013","externalUrl":null,"permalink":"/posts/maven-completion-couleurs-et-notifications/","section":"Posts","summary":"Maven, out of the box n’est pas l’outil le plus sexy du monde… Utilisé dans un IDE, il saura se faire oublié mais il mérite mieux lors de son usage en ligne de commande.\n","title":"Maven: complétion, couleurs et notifications de bureau","type":"posts"},{"content":"","date":"14 avril 2013","externalUrl":null,"permalink":"/tags/oh-my-zsh/","section":"Tags","summary":"","title":"Oh-My-Zsh","type":"tags"},{"content":"","date":"14 avril 2013","externalUrl":null,"permalink":"/tags/pushbullet/","section":"Tags","summary":"","title":"Pushbullet","type":"tags"},{"content":"","date":"14 avril 2013","externalUrl":null,"permalink":"/tags/terminal-notifier/","section":"Tags","summary":"","title":"Terminal-Notifier","type":"tags"},{"content":"","date":"14 avril 2013","externalUrl":null,"permalink":"/tags/toaster/","section":"Tags","summary":"","title":"Toaster","type":"tags"},{"content":"","date":"14 avril 2013","externalUrl":null,"permalink":"/tags/zsh/","section":"Tags","summary":"","title":"Zsh","type":"tags"},{"content":"","date":"9 mars 2013","externalUrl":null,"permalink":"/tags/difftool/","section":"Tags","summary":"","title":"Difftool","type":"tags"},{"content":"Quelque soit le gestionnaire de sources que l’on utilise, vient toujours le moment où des conflits apparaissent lors des merge. Dans ces cas là, chacun sa technique, personnellement, que ce soit pour l’affichage d’un diff ou un merge, je préfère les outils graphiques. J’ai longtemps utilisé les outils de tortoise sous Windows, mais à force de papillonner entre différents OS, j’ai cherché un outil disponible sur toutes les plateformes. Et je me suis finalement arrêté sur P4Merge\u0026hellip;\nMalgré un rendez-vous manqué avec Perforce (git-p4), l’outil de merge disponible au sein de la distribution de Perforce est plutôt réussi (une fois que l’on a compris le mécanisme de sélection à l’aide des trois coquillages).\nInstallation # Télécharger une distribution Perforce : http://www.perforce.com/downloads/complete_list\nCopier p4merge dans /Applications\nDirection /usr/local/bin, créer un script nommé p4merge avec le contenu :\n#!/bin/sh /Applications/p4merge.app/Contents/Resources/launchp4merge \u0026quot;$@\u0026quot; Rendre le script exécutable : chmod +x p4merge\nConfiguration de git # [merge] conflictStyle = diff3 tool = p4merge [mergetool] keepBackup = false keepTemporaries = false prompt = false Utilisation # Et voilà, git difftool et git mergetool utiliseront maintenant P4Merge ! ","date":"9 mars 2013","externalUrl":null,"permalink":"/posts/git-mergetool-avec-p4merge-sous-mac-os/","section":"Posts","summary":"Quelque soit le gestionnaire de sources que l’on utilise, vient toujours le moment où des conflits apparaissent lors des merge. Dans ces cas là, chacun sa technique, personnellement, que ce soit pour l’affichage d’un diff ou un merge, je préfère les outils graphiques. J’ai longtemps utilisé les outils de tortoise sous Windows, mais à force de papillonner entre différents OS, j’ai cherché un outil disponible sur toutes les plateformes. Et je me suis finalement arrêté sur P4Merge…\n","title":"Git mergetool avec P4Merge sous MacOS","type":"posts"},{"content":"","date":"9 mars 2013","externalUrl":null,"permalink":"/tags/mergetool/","section":"Tags","summary":"","title":"Mergetool","type":"tags"},{"content":"","date":"9 mars 2013","externalUrl":null,"permalink":"/tags/p4merge/","section":"Tags","summary":"","title":"P4merge","type":"tags"},{"content":"","date":"2 février 2013","externalUrl":null,"permalink":"/tags/jrebel/","section":"Tags","summary":"","title":"Jrebel","type":"tags"},{"content":"Quand on développe des applications web Java EE, on passe généralement son temps à redéployer son application et redémarrer son conteneur\u0026hellip; Heureusement on peut compter depuis un moment sur JRebel pour grandement augmenter sa productivité lors des développements en permettant le rechargement à chaud des modifications de façon plus poussé que le Hot Swap intégré à la JVM.\nFonctionnement # JRebel est un agent java qui va surveiller les modifications de notre application et les appliquées au sein de la JVM à chaud. Pour celà, l’agent va scanner le classpath à la recherche de fichiers rebel.xml décrivant les ressources à surveiller.\nInstallation # La documentation de JRebel met l’accent sur l’intégration avec les IDE (Eclipse, IntelliJ Idea, Netbeans, MyEclipse) et est très bien faite : JRebel IDE Plugins Personnellement j’utilise rarement les fonctionnalités de déploiement intégrés dans les IDE (peut-être à cause de wtp dans Eclipse\u0026hellip;) et préfère utilisé un serveur embedded (dans un plugin Maven par exemple) ou même directement le serveur d’application installé sur mon poste.\nOn a donc besoin de télécharger une archive de JRebel, disponible ici : Télécharger JRebel\nUne fois dezippé, il faut exécuter la configuration de JRebel via bin/jrebel-config.{cmd|sh} et suivre les instructions de l’assistant. Pour ajouter l’agent JRebel à la JVM : -noverify -javaagent:$JREBEL_HOME/jrebel.jar\nConfiguration de votre application # Il faut maintenant s’intéresser à la génération des fichiers de configuration rebel.xml. Il existe un plugin Maven pour faire le travail à notre place : http://zeroturnaround.com/software/jrebel/maven/\n\u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.zeroturnaround\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jrebel-maven-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.1.3\u0026lt;/version\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;classpath\u0026gt; \u0026lt;resources\u0026gt; \u0026lt;resource\u0026gt; \u0026lt;includes\u0026gt; \u0026lt;include\u0026gt;com/github/jcgay/**\u0026lt;/include\u0026gt; \u0026lt;/includes\u0026gt; \u0026lt;/resource\u0026gt; \u0026lt;resource\u0026gt; \u0026lt;directory\u0026gt;src/main/resources\u0026lt;/directory\u0026gt; \u0026lt;/resource\u0026gt; \u0026lt;/resources\u0026gt; \u0026lt;/classpath\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;executions\u0026gt; \u0026lt;execution\u0026gt; \u0026lt;id\u0026gt;generate-rebel-xml\u0026lt;/id\u0026gt; \u0026lt;phase\u0026gt;generate-resources\u0026lt;/phase\u0026gt; \u0026lt;goals\u0026gt; \u0026lt;goal\u0026gt;generate\u0026lt;/goal\u0026gt; \u0026lt;/goals\u0026gt; \u0026lt;/execution\u0026gt; \u0026lt;/executions\u0026gt; \u0026lt;/plugin\u0026gt; Le plugin se base sur le type de votre module pour générer la configuration par défaut (pour par exemple intégrer src/main/webapp aux ressources surveillées). Ce comportement peut-être surchargé si par exemple votre webapp utilise le type maven jar en utilisant war au sein de la configuration du plugin. Le plugin fonctionne très bien au sein des projets multi-module. On peut filtrer avec des include/exclude les ressources à intégrer dans le rechargement de JRebel.\nLes chemins générés au sein de la configuration sont absolus par défaut (donc spécifique à votre environnement), il est possible de variabiliser les chemins générés pour les rendre relatifs. Je n’ai jamais utilisé la fonctionalité (je ne package pas la configuration JRebel dans mes artifacts finaux) mais c’est toujours bon à savoir.\nToutes les possibilités de configuration du rebel.xml sont décrites ici : documentation rebel.xml\nVérification du fonctionnement # Si l’agent est correctement configuré, on obtient au lancement de la JVM :\n[2013-01-25 16:16:04] ############################################################# [2013-01-25 16:16:04] [2013-01-25 16:16:04] JRebel 5.1.1 (201211271929) [2013-01-25 16:16:04] (c) Copyright ZeroTurnaround OU, Estonia, Tartu. [2013-01-25 16:16:04] [2013-01-25 16:16:04] Over the last 30 days JRebel prevented [2013-01-25 16:16:04] at least 95 redeploys/restarts saving you about 3.9 hours. [2013-01-25 16:16:04] [2013-01-25 16:16:04] This product is licensed to ****** [2013-01-25 16:16:04] [2013-01-25 16:16:04] The following plugins are disabled at the moment: [2013-01-25 16:16:04] * Click plugin (set -Drebel.click_plugin=true to enable) [2013-01-25 16:16:04] * JRuby Plugin (set -Drebel.jruby_plugin=true to enable) [2013-01-25 16:16:04] * Jersey plugin (set -Drebel.jersey_plugin=true to enable) [2013-01-25 16:16:04] * Oracle ADF Core plugin (set -Drebel.adf_core_plugin=true to enable) [2013-01-25 16:16:04] * Oracle ADF Faces plugin (set -Drebel.adf_faces_plugin=true to enable) [2013-01-25 16:16:04] * RESTlet plugin (set -Drebel.restlet_plugin=true to enable) [2013-01-25 16:16:04] * Seam-Wicket plugin (set -Drebel.seam_wicket_plugin=true to enable) [2013-01-25 16:16:04] * Spring Data Plugin (set -Drebel.spring_data_plugin=true to enable) [2013-01-25 16:16:04] * WebObjects plugin (set -Drebel.webobjects_plugin=true to enable) [2013-01-25 16:16:04] [2013-01-25 16:16:04] ############################################################# Puis les ressources qui sont surveillées :\n[2013-01-25 16:16:06] JRebel: Directory '/home/jcgay/dev/app/target/classes' will be monitored for changes. [2013-01-25 16:16:06] JRebel: Directory '/home/jcgay/dev/app/src/main/webapp' will be monitored for changes. [2013-01-25 16:16:06] JRebel: Directory '/home/jcgay/dev/app/src/main/resources will be monitored for changes. Les classes seront maintenant rechargées dès qu’elles sont compilées depuis l’IDE (pour peu que celui ci soit bien configuré pour compiler les classes au même endroit que Maven).\nLes ressources sont rechargées après un hit sur le serveur d\u0026rsquo;application. Pour chaque ressource rechargée, une entrée est loggée :\n[2012-12-20 11:33:56] JRebel: Reloading class 'com.github.jcgay.app.Person'. Notes # La génération de la configuration via le plugin Maven est liée à la phase “generate-resources”. Il est possible que le reste du build écrase cette configuration (par exemple de l’overlay de war, ou du tissage d’aspect à partir du build d’un war dans ses modules). Génaralement dans ce cas je change la phase d’exécution du plugin Maven pour une phase postérieure à celle qui pose problème\u0026hellip; Attention à bien cocher “I use Maven to build my application” dans l’assistant de configuration de JRebel, sinon vous verrez surement le monitoring de plugin JRebel correctement s’activé mais pas celui des ressources de l’application. Dans IntelliJ Idea, pour que la compilation se fasse bien dans le même répertoire que Maven, il faut cocher “Use Maven output directories”. Toujours dans Idea, si l\u0026rsquo;on utilise le debugger, celui si se retrouvera désynchronisé dès la prise en compte d\u0026rsquo;un rechargement à chaud. Dans ce cas il faut installer le plugin JRebel, et ajouter la propriété -Drebel.debugger_integration=true au lancement de la JVM. ","date":"2 février 2013","externalUrl":null,"permalink":"/posts/jrebel-redeploiement-a-chaud/","section":"Posts","summary":"Quand on développe des applications web Java EE, on passe généralement son temps à redéployer son application et redémarrer son conteneur… Heureusement on peut compter depuis un moment sur JRebel pour grandement augmenter sa productivité lors des développements en permettant le rechargement à chaud des modifications de façon plus poussé que le Hot Swap intégré à la JVM.\n","title":"JRebel, le redéploiement c'est à chaud !","type":"posts"},{"content":"","date":"15 juillet 2012","externalUrl":null,"permalink":"/tags/eclipse/","section":"Tags","summary":"","title":"Eclipse","type":"tags"},{"content":"","date":"15 juillet 2012","externalUrl":null,"permalink":"/categories/integration-continue/","section":"Categories","summary":"","title":"Integration Continue","type":"categories"},{"content":"","date":"15 juillet 2012","externalUrl":null,"permalink":"/tags/jenkins/","section":"Tags","summary":"","title":"Jenkins","type":"tags"},{"content":"","date":"15 juillet 2012","externalUrl":null,"permalink":"/tags/jenkins-control-plugin/","section":"Tags","summary":"","title":"Jenkins Control Plugin","type":"tags"},{"content":"Se tenir informer de l’état de son (ses) build(s) est primordial dès lors que l’on utilise un environnement d’intégration continue. Quand on a de la chance on a un buildwall dans son espace de travail au milieu des développeurs\u0026hellip; sinon c’est un peu du chacun pour sa peau.\nMa solution préférée (différente des mails et autre moyen de notifications) consiste à monitorer Jenkins depuis son IDE.\nEclipse # Il existe un connecteur mylyn pour Jenkins (et aussi Hudson). Celui-ci permet de suivre le statut des jobs que l’on choisit et bien plus encore.\nInstallation # Pré requis : Le plugin Mylyn doit être installé. Pour la dernière version d’Eclipse (Juno), le connecteur est disponible dans le répository mylyn. Direction Help \u0026gt; Install new software... \u0026gt; Mylyn for Eclipse Juno \u0026gt; Mylyn Integrations \u0026gt; Mylyn Build Connector: Hudson/Jenkins pour dérouler le processus d\u0026rsquo;installation et redémarrage d\u0026rsquo;Eclipse.\nConfiguration # Une nouvelle entrée est disponible dans les préférences d\u0026rsquo;Eclipse : Eclipse \u0026gt; Preferences \u0026gt; Mylyn \u0026gt; Builds\nOn peut :\nRafraichir le statut des builds toutes les X minutes ou seulement lorsque la vue \u0026quot;Build\u0026quot; est active. Changer le type de notification disponible, par défaut un changement de statut du build ouvrira une popup dans dans la fenêtre d’Eclipse. Il suffit ensuite d\u0026rsquo;ajouter une instance Jenkins :\nAfficher la vue Mylyn \u0026gt; Builds, on obtient l\u0026rsquo;écran suivant : On clique sur create a build server, sélectionner Hudson (support Jenkins), Renseigner les informations nécessaires, puis cliquer sur Validate pour vérifier que la connection s\u0026rsquo;effectue correctement, Sélectionner les jobs à surveiller : Les jobs sélectionnés sont maintenant disponible dans la vue Builds. Utilisation # Exemple de notification pour être prévenu de l\u0026rsquo;état du build :\nEn double cliquant sur un job dans la vue Builds, on obtient une fiche Mylyn :\nParmis les fonctionalités sympa, on peut :\nLancer un build, Obtenir le contenu de la console Jenkins dans la vue console d’Eclipse, Charger les résultats des tests dans la vue JUnit. C’est très pratique quand des tests ont échoués, ceux-ci se retrouvent directement liés aux sources dans le workspace, et plein d’autres choses dont je me sert moins souvent\u0026hellip; Ces actions sont aussi disponibles directement dans la vue Builds, quand on sélectionne un job !\nMalheureusement pour le moment, le plugin n\u0026rsquo;est plus compatible avec les dernières versions de Jenkins\u0026hellip; Le bug est ouvert ici.\nIntelliJ Idea # Installation # Télécharger le plugin \u0026quot;Jenkins Control Plugin\u0026quot; depuis le répository des plugins.\nConfiguration # Après redémarrage d’Idea, une nouvelle entrée \u0026quot;Jenkins Control Plugin\u0026quot; est disponible dans les settings.\nUtilisation # On a accès dans la vue Jenkins Browser à tous les jobs de l\u0026rsquo;instance, les fonctionalités sont plus réduites que celles du plugin eclipse, on peut :\nVoir l\u0026rsquo;état des builds, Lancer un build, Ouvrir la page du job Jenkins dans votre navigateur. Voilà, plus d\u0026rsquo;excuses possibles pour ne pas avoir vu que son commit a planté le build :)\n","date":"15 juillet 2012","externalUrl":null,"permalink":"/posts/jenkins-dans-son-ide-favoris/","section":"Posts","summary":"Se tenir informer de l’état de son (ses) build(s) est primordial dès lors que l’on utilise un environnement d’intégration continue. Quand on a de la chance on a un buildwall dans son espace de travail au milieu des développeurs… sinon c’est un peu du chacun pour sa peau.\nMa solution préférée (différente des mails et autre moyen de notifications) consiste à monitorer Jenkins depuis son IDE.\n","title":"Jenkins dans son IDE favoris","type":"posts"},{"content":"","date":"15 juillet 2012","externalUrl":null,"permalink":"/tags/mylyn/","section":"Tags","summary":"","title":"Mylyn","type":"tags"},{"content":"","date":"9 mai 2012","externalUrl":null,"permalink":"/tags/profile/","section":"Tags","summary":"","title":"Profile","type":"tags"},{"content":"","date":"9 mai 2012","externalUrl":null,"permalink":"/tags/spring/","section":"Tags","summary":"","title":"Spring","type":"tags"},{"content":"Une des fonctionnalités apportées par la version 3.1 de Spring est la gestion des profils.\nRécemment, pour une application web, j’ai eu besoin de définir des comportements spécifiques dans mon environnement de développement pour ne pas interroger des services externes, inaccessible depuis mon poste.\nLe besoin est donc simple, charger des implémentations de service différentes selon l’environnement d’exécution de l’application. Et c’est là qu’entre en jeux les profils ! Ceux ci permettent de charger des beans différents dans le contexte Spring selon le(s) profil(s) configurés.\nConfiguration XML # Un fichier par profil # Il est possible de spécifier un profil dès la balise racine, celà implique que tout le contenu du fichier ne sera chargé que si le profil correspondant est activé.\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;beans xmlns=\u0026#34;http://www.springframework.org/schema/beans\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation=\u0026#34;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd\u0026#34; profile=\u0026#34;a\u0026#34;\u0026gt; \u0026lt;bean class=\u0026#34;com.github.jcgay.example.spring.BeanA\u0026#34; /\u0026gt; \u0026lt;/beans\u0026gt; Le bean n’est chargé que si le profile “a” est actif. Les beans définis en dehors de ce fichier de configuration seront accessibles dans tous les cas.\nBlocs au sein d\u0026rsquo;un même fichier # On peut également définir des blocs de profils au sein d’un même fichier de configuration.\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;beans xmlns=\u0026#34;http://www.springframework.org/schema/beans\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation=\u0026#34;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd\u0026#34;\u0026gt; \u0026lt;bean class=\u0026#34;com.github.jcgay.example.spring.BeanA\u0026#34; /\u0026gt; \u0026lt;beans profile=\u0026#34;b\u0026#34;\u0026gt; \u0026lt;bean class=\u0026#34;com.github.jcgay.example.spring.BeanB\u0026#34; /\u0026gt; \u0026lt;/beans\u0026gt; \u0026lt;/beans\u0026gt; Le bean A est chargé dans tous les cas, le B seulement quand le profil “b” est actif.\nMutualisation de configuration pour plusieurs profils # Pour ne pas répéter la configuration de beans dans plusieurs profils, on peut déclarer des blocs valides pour ces différents profils.\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;beans xmlns=\u0026#34;http://www.springframework.org/schema/beans\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation=\u0026#34;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd\u0026#34;\u0026gt; \u0026lt;beans profile=\u0026#34;a,b\u0026#34;\u0026gt; \u0026lt;bean class=\u0026#34;com.github.jcgay.example.spring.BeanA\u0026#34; /\u0026gt; \u0026lt;bean class=\u0026#34;com.github.jcgay.example.spring.BeanB\u0026#34; /\u0026gt; \u0026lt;/beans\u0026gt; \u0026lt;beans profile=\u0026#34;a\u0026#34;\u0026gt; \u0026lt;bean class=\u0026#34;com.github.jcgay.example.spring.BeanC\u0026#34; /\u0026gt; \u0026lt;/beans\u0026gt; \u0026lt;beans profile=\u0026#34;b\u0026#34;\u0026gt; \u0026lt;bean class=\u0026#34;com.github.jcgay.example.spring.BeanD\u0026#34; /\u0026gt; \u0026lt;/beans\u0026gt; \u0026lt;/beans\u0026gt; Le profil \u0026ldquo;a\u0026rdquo; contient les beans A,B et C tandis que le profil \u0026ldquo;b\u0026rdquo; comprends les beans A,B et D. La déclaration de A et B est mutualisée.\nConfiguration par annotation # On peut également définir des profils via les annotations Spring. L’annotation @Profile peut être utilisé sur les différents stéréotypes disponibles (@Service, @Component, @Configuration, etc).\nAu niveau @Configuration # @Configuration @Profile(\u0026#34;a\u0026#34;) public class ProfileA { @Bean public BeanA beanA() { return new BeanA(); } } Tous les beans définis dans cette classe sont accessibles quand le profil \u0026ldquo;a\u0026rdquo; correspondant est actif.\nComme avec le modèle XML, il est possible de mutualiser la définition des profils.\n@Configuration @Profile({\u0026#34;a\u0026#34;, \u0026#34;b\u0026#34;}) public class MultipleProfiles { @Bean public BeanA beanA() { return new BeanA(); } @Bean public BeanB beanB() { return new BeanB(); } } On peut annoter directement un composant Spring sans passer par des @Configuration.\n@Component @Profile(\u0026#34;e\u0026#34;) public class BeanE { } Et enfin on peut utiliser @Profile comme meta-annotation et donc créer ses propres annotations au lieu de répeter le nom du profil un peu partout.\n@Profile(\u0026#34;a\u0026#34;) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface CustomProfile { } Et voilà on peut utiliser @CustomProfile pour construire le profil \u0026ldquo;a\u0026rdquo;.\nActivation d\u0026rsquo;un profil # L’option -Dspring.profiles.active=a en ligne de commande active le profil \u0026ldquo;a\u0026rdquo;. Pour activer plusieurs profils il faut les séparer par des virgules.\nOu alors, en variable d\u0026rsquo;environnement : export spring.profiles.active=a Dans le cadre d\u0026rsquo;une application web, le choix du profil peut être passé en paramètre de la servlet :\n\u0026lt;servlet\u0026gt; \u0026lt;servlet-name\u0026gt;dispatcher\u0026lt;/servlet-name\u0026gt; \u0026lt;servlet-class\u0026gt;org.springframework.web.servlet.DispatcherServlet\u0026lt;/servlet-class\u0026gt; \u0026lt;init-param\u0026gt; \u0026lt;param-name\u0026gt;spring.profiles.active\u0026lt;/param-name\u0026gt; \u0026lt;param-value\u0026gt;a\u0026lt;/param-value\u0026gt; \u0026lt;/init-param\u0026gt; \u0026lt;/servlet\u0026gt; ou en paramètre du listener :\n\u0026lt;context-param\u0026gt; \u0026lt;param-name\u0026gt;contextConfigLocation\u0026lt;/param-name\u0026gt; \u0026lt;param-value\u0026gt;/WEB-INF/applicationContext*.xml\u0026lt;/param-value\u0026gt; \u0026lt;/context-param\u0026gt; \u0026lt;context-param\u0026gt; \u0026lt;param-name\u0026gt;spring.profiles.active\u0026lt;/param-name\u0026gt; \u0026lt;param-value\u0026gt;a\u0026lt;/param-value\u0026gt; \u0026lt;/context-param\u0026gt; \u0026lt;listener\u0026gt; \u0026lt;listener-class\u0026gt;org.springframework.web.context.ContextLoaderListener\u0026lt;/listener-class\u0026gt; \u0026lt;/listener\u0026gt; Définir un profil par défaut # La clef spring.profiles.default permet de choisir un profil qui sera activé automatiquement si aucun autre n\u0026rsquo;a été défini. Pratique si par exemple on veut que l\u0026rsquo;application charge une configuration type \u0026ldquo;production\u0026rdquo; si aucun profil n\u0026rsquo;est spécifié.\nPour finir, pour les utilisateurs IntelliJ IDEA, il y a une fonctionnalité plutôt sympa qui permet de filtrer la configuration Spring selon le profil actif : http://blogs.jetbrains.com/idea/2011/04/new-in-105-spring-31-bean-definition-profiles/\n","date":"9 mai 2012","externalUrl":null,"permalink":"/posts/spring-et-les-profils/","section":"Posts","summary":"Une des fonctionnalités apportées par la version 3.1 de Spring est la gestion des profils.\nRécemment, pour une application web, j’ai eu besoin de définir des comportements spécifiques dans mon environnement de développement pour ne pas interroger des services externes, inaccessible depuis mon poste.\nLe besoin est donc simple, charger des implémentations de service différentes selon l’environnement d’exécution de l’application. Et c’est là qu’entre en jeux les profils ! Ceux ci permettent de charger des beans différents dans le contexte Spring selon le(s) profil(s) configurés.\n","title":"Spring et les profils","type":"posts"},{"content":"","date":"18 mars 2012","externalUrl":null,"permalink":"/tags/git-p4/","section":"Tags","summary":"","title":"Git-P4","type":"tags"},{"content":"A la manière d’un git-svn, il est possible d’utiliser git pour se synchroniser avec un dépôt Perforce (ouf). On peut profiter de la souplesse de git en local et continuer de travailler avec ses collègues sur le dépot central Perforce.\nPrincipe de fonctionnement # Git-p4 permet de cloner un dépot Perforce dans un repository git.\nLe script va parcourir chaque révision des fichiers et peupler un répository git via la commande p4 print. On peut importer la totalité de l’historique, enfin si on a le temps surtout, le premier import peut-être assez long en fonction de la taille du dépot Perforce.\nLa phase un peu plus compliquée est le report des changements dans Perforce (à cause des spécificités de ce SCM). Il faut pour celà passer par un workspace Perforce différent de l’emplacement du répository git.\nLes commits git sont alors reportés par le script dans le workspace Perforce via des git diff/format-patch… | git apply puis p4 submit s\u0026rsquo;occupe de transférer les changements dans Perforce.\nGit-p4 utilise git-notes sur chaque commit pour repérer ceux déjà synchroniser avec Perforce.\nInstallation # git # Après l\u0026rsquo;installation classique des binaires, on ajoute quelques propriétés spécifiques à git-p4 dans le fichier de configuration .gitconfig :\n[git-p4] detectRename=true detectCopy=true Celà permettra à git-p4 de détecter les fichiers déplacés lors du submit dans Perforce (le copy ne semble pas utilisé\u0026hellip;).\nperforce # Après l\u0026rsquo;installation des binaires, exposer la variable d’environnement P4CONFIG.\nPar la suite j’utilise un fichier .p4config pour configurer les accès Perforce.\npython 2.x # git-p4 est écrit en python, installation.\ngit-p4 # Télécharger le script Pour windows, créer le fichier git-p4.bat avec le contenu suivant:\n@python \u0026quot;%~d0%~p0git-p4\u0026quot; %*\nCopier ces deux fichiers dans le répertoire d’installation de git /libexec/git-core/\nVérification de l\u0026rsquo;installation # git --version p4 help python --version git p4 help Utilisation # Structure d’un projet # Le dossier git contiendra le repository git, et p4 le workspace Perforce.\nContenu du fichier .p4config :\nP4USER=nom d’utilisateur P4PASSWD=mot de passe P4CHARSET=encoding du serveur P4COMMANDLINE=encoding du CLI P4CLIENT=nom du workspace Création du client Perforce # Dans le dossier p4, utiliser la commande p4 client pour créer un nouveau workspace. Les données du fichier .p4config sont automatiquement utilisées, il suffit donc seulement de renseigner le mapping vers le projet.\nPenser à créer l’arborescence complète du mapping côté client avant de tenter votre premier commit.\nImport initial du projet # Dans le dossier git, utiliser la commande git p4 clone //depot/chemin/projet . (attention au point à la fin de la commande).\nPar défaut seule la dernière révision sera importée, pour importer l’historique complet il suffit de suffixer le chemin du dépôt par @all.\nVoilà on peut maintenant travailler dans le répository git !\nMise à jour incrémentale # Une fois le premier import effectué, git p4 rebase permet de récupérer les dernières changelist depuis Perforce.\nPour ceux qui préfèrent voir les changements distants avant de les intégrer dans les commit locaux git p4 sync puis git rebase p4/master\nReport des changements locaux dans Perforce # Penser à toujours faire un git p4 rebase avant de soumettre les changements locaux dans Perforce sinon les possibles conflits devront être résolus depuis le workspace Perforce, et c\u0026rsquo;est quand même plus simple de les gérer dans git. En oubliant l\u0026rsquo;update avant le submit il est également possible de se retrouver avec des changelists Perforce non importées dans git (pas cool).\ngit p4 submit permet de reporter les changements sur le serveur Perforce.\nUtiliser l’option --auto pour que Perforce ne demande pas de modifier le commentaire des commits. Il utilisera automatiquement ceux de git.\nPrécautions # Comme lorsque l’on travaille uniquement avec git, ne pas réécrire l’historique de changements déjà synchroniser avec Perforce, Penser à toujours rebaser les modifications Perforce sur les changements locaux de git avant de tenter un submit, Éviter les merge entre branches locales, de toute façon il faudra rebaser les modifications au final. Celà pourrait amener à régler les mêmes conflits plusieurs fois. Workflow # git p4 clone Modifier des fichiers git commit Modifier des fichiers git commit git p4 rebase git p4 submit Pour aller plus loin # Utiliser le même répository git pour gérer plusieurs branches du dépot Perforce (Pour le moment j’utilise un repository git par branche), Repasser sur le script maintenu sur le repository git officiel, j’ai eu des problèmes sous windows avec ce dernier il y a quelques mois quand j’ai commencé à utiliser git-p4. ","date":"18 mars 2012","externalUrl":null,"permalink":"/posts/git-p4-au-secours-de-perforce/","section":"Posts","summary":"A la manière d’un git-svn, il est possible d’utiliser git pour se synchroniser avec un dépôt Perforce (ouf). On peut profiter de la souplesse de git en local et continuer de travailler avec ses collègues sur le dépot central Perforce.\n","title":"git-p4 au secours de Perforce","type":"posts"},{"content":"","date":"18 mars 2012","externalUrl":null,"permalink":"/tags/p4/","section":"Tags","summary":"","title":"P4","type":"tags"},{"content":"","date":"18 mars 2012","externalUrl":null,"permalink":"/tags/perforce/","section":"Tags","summary":"","title":"Perforce","type":"tags"}]