<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
<title type="text">Bloggure</title>
<generator uri="https://github.com/mojombo/jekyll">Jekyll</generator>
<link rel="self" type="application/atom+xml" href="http://www.bloggure.info/feed.xml" />
<link rel="alternate" type="text/html" href="http://www.bloggure.info" />
<updated>2026-01-27T15:31:15+01:00</updated>
<id>http://www.bloggure.info/</id>
<author>
  <name></name>
  <uri>http://www.bloggure.info/</uri>
  <email></email>
</author>


<entry>
  <title type="html"><![CDATA[iOS 26.2 Sheet: remove Liquid Glass for small detents]]></title>
  <link>http://www.bloggure.info/ios/swift/uikit/ios-sheetpresentationcontroller-background-effect</link>
  <id>http://www.bloggure.info/ios/swift/uikit/ios-sheetpresentationcontroller-background-effect</id>
  <published>2026-01-27T00:00:00+01:00</published>
  <updated>2026-01-27T00:00:00+01:00</updated>
  <author>
    <name></name>
    <uri>http://www.bloggure.info</uri>
    <email></email>
  </author>
  <content type="html">&lt;p&gt;I recently encountered an interesting issue with &lt;code&gt;UISheetPresentationController&lt;/code&gt; in iOS 26.2. When using a &lt;code&gt;.pageSheet&lt;/code&gt; presentation style with a detent of &lt;code&gt;.medium&lt;/code&gt; or smaller, iOS automatically applies the “liquid glass” background effect, completely ignoring any custom background settings.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;the-problem&quot;&gt;The Problem&lt;/h2&gt;

&lt;p&gt;In iOS 26.2, Apple introduced changes to the visual appearance of &lt;code&gt;.pageSheet&lt;/code&gt; presentations. When a page sheet does &lt;em&gt;not&lt;/em&gt; take up the entire screen (typically on larger devices or when content remains visible behind the sheet), iOS automatically applies a “liquid glass” background effect that overrides any custom background settings you may have configured.&lt;/p&gt;

&lt;p&gt;Interestingly, this issue only occurs when the sheet does &lt;em&gt;not&lt;/em&gt; occupy the full screen. When the sheet takes up the entire screen, the automatic liquid glass effect is not applied and custom backgrounds work as expected.&lt;/p&gt;

&lt;p&gt;This made my nice page sheet looks like the following :&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/post/sheet-262-issue.png&quot; alt=&quot;iOS 26.2 Sheet Issue&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;initial-attempts&quot;&gt;Initial Attempts&lt;/h2&gt;

&lt;p&gt;My first instinct was to try setting the background effect to &lt;code&gt;nil&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;if #available(iOS 26.1, *) {
    controller.sheetPresentationController?.backgroundEffect = nil
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Note that we need to guard the access to this property for 26.1 and later versions.&lt;/p&gt;

&lt;p&gt;However, this approach didn’t work - iOS still applied the liquid glass effect.&lt;/p&gt;

&lt;h2 id=&quot;the-solution&quot;&gt;The Solution&lt;/h2&gt;

&lt;p&gt;After some experimentation and research, I discovered the solution: using an empty &lt;code&gt;UIColorEffect()&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;if #available(iOS 26.1, *) {
    controller.sheetPresentationController?.backgroundEffect = UIColorEffect()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This effectively tells iOS to use a solid color background instead of the automatic liquid glass effect.&lt;/p&gt;

&lt;p&gt;And voilà, I’ve got the result I was expecting (and what was displayed prior to 26.2)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/post/sheet-262-fixed.png&quot; alt=&quot;iOS 26.2 Sheet Fixed&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;The automatic application of liquid glass effects in iOS 26.2 can be surprising, but with the &lt;code&gt;UIColorEffect()&lt;/code&gt; solution, you can regain control over your sheet’s appearance and maintain your desired visual design.&lt;/p&gt;


  &lt;p&gt;&lt;a href=&quot;http://www.bloggure.info/ios/swift/uikit/ios-sheetpresentationcontroller-background-effect&quot;&gt;iOS 26.2 Sheet: remove Liquid Glass for small detents&lt;/a&gt; was originally published by  at &lt;a href=&quot;http://www.bloggure.info&quot;&gt;Bloggure&lt;/a&gt; on January 27, 2026.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Streamlining Finances: Automating Bill Renaming with Apple Shortcuts]]></title>
  <link>http://www.bloggure.info/Shortcut_bill_renaming</link>
  <id>http://www.bloggure.info/Shortcut_bill_renaming</id>
  <published>2026-01-07T00:00:00+01:00</published>
  <updated>2026-01-07T00:00:00+01:00</updated>
  <author>
    <name></name>
    <uri>http://www.bloggure.info</uri>
    <email></email>
  </author>
  <content type="html">&lt;h2 id=&quot;the-problem-the-downloads-folder-chaos&quot;&gt;The Problem: The “Downloads” Folder Chaos&lt;/h2&gt;

&lt;p&gt;We all know the struggle. You download a PDF invoice from your utility provider or internet service, and the file is named something useless like &lt;code&gt;invoice_839204_export.pdf&lt;/code&gt;. These cryptic filenames make it nearly impossible to find specific bills later, clutter your file system, and create frustration when you’re trying to organize your finances.&lt;/p&gt;

&lt;p&gt;The goal is simple: automatically rename these files to a standard, searchable format like &lt;code&gt;YYYY-MM-DD_Company.pdf&lt;/code&gt; so you can easily find, sort, and manage your financial documents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common pain points:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Difficulty finding specific bills when you need them&lt;/li&gt;
  &lt;li&gt;Messy file organization that grows over time&lt;/li&gt;
  &lt;li&gt;Time wasted manually renaming files&lt;/li&gt;
  &lt;li&gt;Inconsistent naming conventions across different providers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-solution-apple-shortcuts&quot;&gt;The Solution: Apple Shortcuts&lt;/h2&gt;

&lt;p&gt;I chose Apple Shortcuts for this automation because it’s native to both macOS and iOS, completely free, and syncs seamlessly across all your Apple devices. Unlike complex Python scripts or third-party automation tools, Shortcuts provides a visual, user-friendly interface that makes it accessible to everyone - not just developers.&lt;/p&gt;

&lt;p&gt;The best part? You can trigger these automations from the Share Sheet, Quick Actions, or even set up folder-based automation, making it incredibly convenient for daily use.&lt;/p&gt;

&lt;h2 id=&quot;how-the-shortcut-works&quot;&gt;How the Shortcut Works&lt;/h2&gt;

&lt;p&gt;Before you download the shortcut, here is the logic behind the workflow. Breaking it down helps if you want to customize it later.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Input:&lt;/strong&gt; The shortcut accepts a PDF or an image file (via the Share Sheet or Quick Actions).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Text Extraction:&lt;/strong&gt; It extracts the text from the image, and asks ChatGPT or other AI models to extract vendor and date from the document&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Formatting:&lt;/strong&gt; It standardizes the date to ISO format (&lt;code&gt;YYYY-MM-DD&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Renaming:&lt;/strong&gt; It combines the variables into a new filename string.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Saving:&lt;/strong&gt; It changes the file name in-place with the new one&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;the-logic-breakdown&quot;&gt;The Logic Breakdown&lt;/h3&gt;

&lt;p&gt;The shortcut uses a combination of Apple’s built-in actions and AI-powered text extraction:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Text Extraction:&lt;/strong&gt; Uses OCR (Optical Character Recognition) to read text from PDFs and images&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;AI Processing:&lt;/strong&gt; Leverages ChatGPT to intelligently identify vendor names and dates&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Date Standardization:&lt;/strong&gt; Converts various date formats to consistent ISO format&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Batch Processing:&lt;/strong&gt; Can handle multiple files at once for efficiency&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Pro Tip:&lt;/strong&gt; The AI component makes this shortcut smarter than traditional regex-based solutions. It can handle different invoice formats and layouts automatically.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;get-the-shortcut&quot;&gt;Get the Shortcut&lt;/h2&gt;

&lt;p&gt;You can download the shortcut directly to your library using the link below:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://www.icloud.com/shortcuts/df9854a70cc34f9285f21b805518cd79&quot;&gt; 📥 Download: Rename Bills Shortcut&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Note: You might need to allow untrusted shortcuts in settings if you haven’t already, though Apple has changed how this works in recent iOS versions.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Requirements:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;iOS 16+ or macOS Ventura+&lt;/li&gt;
  &lt;li&gt;Apple Shortcuts app installed&lt;/li&gt;
  &lt;li&gt;Internet connection for AI processing (or local/offline alternative)&lt;/li&gt;
  &lt;li&gt;ChatGPT API key (free tier available)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Privacy Note:&lt;/strong&gt; If you’re concerned about privacy or prefer offline processing, you can modify this shortcut to use local model or private cloud models instead of ChatGPT. The shortcut can be adapted to use either approach based on your preferences.&lt;/p&gt;

&lt;h2 id=&quot;how-to-set-it-up&quot;&gt;How to Set It Up&lt;/h2&gt;

&lt;p&gt;Once you have installed the shortcut, you need to configure a few variables to match your preferences:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;ChatGPT API Key:&lt;/strong&gt; Add your ChatGPT API key in the shortcut settings for AI processing&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Naming Convention:&lt;/strong&gt; If you prefer &lt;code&gt;Company_Date&lt;/code&gt; instead of &lt;code&gt;Date_Company&lt;/code&gt;, you can drag and drop the variables in the “Set Name” action.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Date Format:&lt;/strong&gt; The shortcut uses ISO format (YYYY-MM-DD) by default, but you can modify the date formatting action if you prefer a different format.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;File Types:&lt;/strong&gt; By default, it handles PDFs and images (PNG, JPG). You can add more file types in the input action.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;/images/post/shortcut-editor.png&quot; alt=&quot;Apple Shortcuts Editor showing the Rename Bills workflow with actions for getting text from PDF, formatting date, and saving file&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configuration Tips:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Test with a few sample files first&lt;/li&gt;
  &lt;li&gt;Adjust the AI prompt if you need different extraction behavior&lt;/li&gt;
  &lt;li&gt;Consider adding error handling for edge cases&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;usage-guide&quot;&gt;Usage Guide&lt;/h2&gt;

&lt;p&gt;Here is how to use it in daily life:&lt;/p&gt;

&lt;h3 id=&quot;on-macos&quot;&gt;On macOS&lt;/h3&gt;
&lt;ol&gt;
  &lt;li&gt;Right-click a PDF or image file -&amp;gt; &lt;strong&gt;Quick Actions&lt;/strong&gt; -&amp;gt; Select &lt;strong&gt;Auto rename Bill&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;For batch processing: Select multiple files -&amp;gt; Right-click -&amp;gt; &lt;strong&gt;Quick Actions&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Auto rename Bill&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;on-ios--ipados&quot;&gt;On iOS / iPadOS&lt;/h3&gt;
&lt;ol&gt;
  &lt;li&gt;Open the PDF or image -&amp;gt; Tap the &lt;strong&gt;Share&lt;/strong&gt; icon -&amp;gt; Scroll down and tap &lt;strong&gt;Auto rename Bill&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;For batch processing: Select multiple files in Files app -&amp;gt; Tap Share -&amp;gt; &lt;strong&gt;Auto rename Bill&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;advanced-usage&quot;&gt;Advanced Usage&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Folder Automation:&lt;/strong&gt; Set up a folder action in macOS to automatically process new files added to a specific folder&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Siri Integration:&lt;/strong&gt; Add the shortcut to Siri for voice-activated renaming&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Batch Processing:&lt;/strong&gt; Process multiple files at once to save time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pro Tip:&lt;/strong&gt; Create a dedicated “Bills to Process” folder and set up a folder action to automatically rename files when they’re added.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Automation doesn’t have to be complicated code. With Apple Shortcuts, you can save yourself the 30 seconds it takes to rename a file—which adds up over a lifetime of paying bills.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time Savings Calculation:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;30 seconds per bill × 12 bills/month × 12 months/year = 6 minutes/year&lt;/li&gt;
  &lt;li&gt;Over 10 years = 1 hour saved&lt;/li&gt;
  &lt;li&gt;Over a lifetime = Significant time savings!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This shortcut demonstrates how AI-powered automation can make everyday tasks easier. The combination of OCR, AI text extraction, and Apple’s automation framework creates a powerful tool that’s accessible to everyone.&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://www.bloggure.info/Shortcut_bill_renaming&quot;&gt;Streamlining Finances: Automating Bill Renaming with Apple Shortcuts&lt;/a&gt; was originally published by  at &lt;a href=&quot;http://www.bloggure.info&quot;&gt;Bloggure&lt;/a&gt; on January 07, 2026.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Tuist build test schemes]]></title>
  <link>http://www.bloggure.info/Tuist_build_test_schemes</link>
  <id>http://www.bloggure.info/Tuist_build_test_schemes</id>
  <published>2025-05-22T00:00:00+02:00</published>
  <updated>2025-05-22T00:00:00+02:00</updated>
  <author>
    <name></name>
    <uri>http://www.bloggure.info</uri>
    <email></email>
  </author>
  <content type="html">&lt;p&gt;Tuist build test schemes&lt;/p&gt;

&lt;p&gt;I use tuist to build most of my iOS projects nowadays. And like every good software engineer I test the code that I deliver. I want to ensure that everything works according to what I expect is coming to my app.&lt;/p&gt;

&lt;h3 id=&quot;xcode-and-schemes&quot;&gt;Xcode and schemes&lt;/h3&gt;

&lt;p&gt;XCode is a fun IDE, it is slow, has his own temper when it comes to find references, refactor or even launch app.
One of the things that is really strange when you’re using this IDE for the first time is that it does only build the active scheme.
So, if your project has, let’s say, 10 schemes, and you refactor a method to add an argument. You won’t notice you have a build error until your build all schemes…&lt;/p&gt;

&lt;h3 id=&quot;build-errors&quot;&gt;Build errors&lt;/h3&gt;

&lt;p&gt;I tend to have a lot of build errors during refactoring, but this is globally ok, I use them to pave the way to my final implementation.
One of the thing that bothers me is that I don’t always catch all errors, and I need the CI to bail out because of a missing argument / protocol conformance method…
This is not a cool thing in my daily process, I am interrupted to fix a build error that should never happen.&lt;/p&gt;

&lt;h3 id=&quot;build-tests-target-automatically&quot;&gt;Build tests target automatically&lt;/h3&gt;

&lt;p&gt;As I am a tuist user, I want to be able to build all my tests target easily to prevent pushing something that does not even build (even before I can say that my tests are passing).&lt;/p&gt;

&lt;h4 id=&quot;command-line-fu&quot;&gt;Command line-fu&lt;/h4&gt;

&lt;p&gt;At first I tried a basic thing, hoping that it would work (spoiler alert, it does not):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt; tuist build &lt;span class=&quot;s1&quot;&gt;&apos;*Tests&apos;&lt;/span&gt;
 &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h4 id=&quot;scripting-to-the-rescue&quot;&gt;Scripting to the rescue&lt;/h4&gt;

&lt;p&gt;It seems that it is not supported out of the box by tuist, running schemes by using a wildcard operator to filter them. However, it is rather easy to do using basic bash scripting, so here is my basic solution&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;SCHEMES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;xcodebuild &lt;span class=&quot;nt&quot;&gt;-workspace&lt;/span&gt; MyProject.xcworkspace &lt;span class=&quot;nt&quot;&gt;-list&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;Tests | &lt;span class=&quot;nb&quot;&gt;awk&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{print $1}&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$SCHEMES&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | &lt;span class=&quot;k&quot;&gt;while &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;IFS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;read&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; s&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do
   &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Building &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$s&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
   tuist build &lt;span class=&quot;nv&quot;&gt;$s&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I save this file under &lt;code&gt;buildTests.sh&lt;/code&gt;, run a &lt;code&gt;chmod +x buildTests.sh&lt;/code&gt; and then I can build my tests targets, in sequence, exiting on first failure. This is perfect for my use case, whenever I refactor too much, or I want to check that tests are still building, I can run this.&lt;/p&gt;

&lt;h3 id=&quot;downside&quot;&gt;Downside&lt;/h3&gt;

&lt;p&gt;Script execution is rather slow, as there is not any parallelism involved, everything is building from scratch.
It might be nice to run everything in parallel, but so far so good, it is ok for my use case !&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://www.bloggure.info/Tuist_build_test_schemes&quot;&gt;Tuist build test schemes&lt;/a&gt; was originally published by  at &lt;a href=&quot;http://www.bloggure.info&quot;&gt;Bloggure&lt;/a&gt; on May 22, 2025.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Swift module registration]]></title>
  <link>http://www.bloggure.info/Module_Registration_Swift</link>
  <id>http://www.bloggure.info/Module_Registration_Swift</id>
  <published>2025-04-28T00:00:00+02:00</published>
  <updated>2025-04-28T00:00:00+02:00</updated>
  <author>
    <name></name>
    <uri>http://www.bloggure.info</uri>
    <email></email>
  </author>
  <content type="html">&lt;p&gt;Swift module registration&lt;/p&gt;

&lt;p&gt;Let’s modularize our Swift code!&lt;/p&gt;

&lt;p&gt;I am using tuist for a while now in my projects. This tool was initially bring into the project’s scope to ease day to day developer operations : no more fiddling with conflicts in xcodeproject files !&lt;/p&gt;

&lt;h3 id=&quot;tuist-&quot;&gt;Tuist ?&lt;/h3&gt;

&lt;p&gt;What is tuist by the way ?&lt;/p&gt;

&lt;p&gt;Have you ever booted an iOS project, from a few years things have evolved a bit.&lt;/p&gt;

&lt;p&gt;Before 2020 I’d say we were doomed to using xcworkspace for defining complex projects.&lt;/p&gt;

&lt;p&gt;Cocoapod and Carthage were the cool kids on the block, allowing to download libraries and use them in our beautiful apps.&lt;/p&gt;

&lt;p&gt;After 2020, and still now, Swift development have changed to massively using SPM: the built in dependency management tool baked in the swift toolchain. The process is straightforward and easy to set up, a few clicks in XCode and voila! The other way of using SPM is by using a Package.swift file to describe what you want to achieve.&lt;/p&gt;

&lt;p&gt;The swift file works very well but lacks from a lot of advanced features available in XCode projects. Or if it is possible, it is not convenient to do.&lt;/p&gt;

&lt;p&gt;This is where tuist fills the gap, like Gradle in the Java ecosystem did a few years ago. We can finally use Swift to describe our Swift projects. This means no more copasting things over and over and the dreaded xcodeproj is no more : you’ll only ever have to merge swift files !&lt;/p&gt;

&lt;h2 id=&quot;code-modularity-appears-&quot;&gt;Code modularity appears !&lt;/h2&gt;

&lt;p&gt;It also came in handy for bringing modularity into our codebase. We managed to split dependencies and make modules per feature or functional scope.&lt;/p&gt;

&lt;p&gt;This comes at a cost, having to register our different modules to our main app. When I’m talking about registering, I’m considering handling dependencies, routing and navigation. This can represent a lot of modules, a lot of useless lines in our codebase.&lt;/p&gt;

&lt;p&gt;To be clearer, our main app contains authentication logic as well as basic http interactions, our modules are dumb as they depend on this external provider to be able to issue any http call.&lt;/p&gt;

&lt;h2 id=&quot;first-implementation&quot;&gt;First implementation&lt;/h2&gt;

&lt;p&gt;Every of our module was being added to our ResourceLoader class that would call the necessary initializers to set things in motion like the following&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot; data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;LoggingModule&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;err&quot;&gt;…&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;With our ResourceLoader implementing a protocol exposing everything that was useful for our submodules, protocol which was made available through a simple used-everywhere dependency.&lt;/p&gt;

&lt;h2 id=&quot;auto-registration-for-the-win-&quot;&gt;Auto-registration for the win !&lt;/h2&gt;

&lt;p&gt;We can do module autoregistration by scanning embedded frameworks. As we can do with IoC, the idea is to let every module declare itself to the main consumer. No more long &lt;code&gt;register&lt;/code&gt; method in our application launch, and new modules are automatically discovered at launch time !&lt;/p&gt;

&lt;p&gt;Let’s illustrate this with some code, we declare a protocol that will allow consumer to call registration for our module.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot; data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;protocol&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ModuleRegistration&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This is our base building block for registration, every module implements &lt;code&gt;ModuleRegistration&lt;/code&gt; to contribute to the main app.&lt;/p&gt;

&lt;p&gt;In every module project, we then set a class that will be responsible for registration. It will implement this specific protocol and be populated by its full name into &lt;code&gt;Info.plist&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot; data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;kd&quot;&gt;@objc&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyFrameworkRegistration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ModuleRegistration&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// inject into the global context our dependencies&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// for instance we can do&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// Container.register(MyLogging.self, MyLoggingImpl.self)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Please notice important things in this class. It needs to be annotated &lt;code&gt;@objc&lt;/code&gt;  and inherit from &lt;code&gt;NSObject&lt;/code&gt; to be discoverable later on.&lt;/p&gt;

&lt;p&gt;The magic being this is that we will declare this class to be our &lt;code&gt;NSPrincipalClass&lt;/code&gt;  in the &lt;code&gt;Info.plist&lt;/code&gt; of our framework.&lt;/p&gt;

&lt;p&gt;To do so, we add the final touch by enriching the &lt;code&gt;Info.plist&lt;/code&gt; of our module using Tuist project’s description :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot; data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;MyFramework&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;destinations&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;iOS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;product&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;framework&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;bundleId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;org.9h41.ios.modules&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;deploymentTargets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;iOS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;16.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;infoPlist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;extendingDefault&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
             &lt;span class=&quot;s&quot;&gt;&quot;NSPrincipalClass&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;MyFramework.MyFrameworkRegistration&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;sources&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MyFramework/Sources/**&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;main-app-implementation&quot;&gt;Main app implementation&lt;/h2&gt;

&lt;p&gt;Then in our main app, we scan for &lt;code&gt;NSPrincipalClass&lt;/code&gt; in all of our included bundles , one just need to call the &lt;code&gt;Modules.autoregister()&lt;/code&gt; method in application startup.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot; data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Modules&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;autoregister&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;kt&quot;&gt;Bundle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;allFrameworks&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;compactMap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(\&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;principalClass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
              &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;forEach&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clazz&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;
                  &lt;span class=&quot;nf&quot;&gt;autoregister&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;clazz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clazz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
              &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;autoregister&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;clazz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AnyClass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;instance&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clazz&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;alloc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instance&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as?&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ModuleRegistration&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Doing registration for &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clazz&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Our main app will then scan all included bundles, look for &lt;code&gt;NSPrincipalClass&lt;/code&gt;, test if it matches the protocol requirement and then calls the registration function.&lt;/p&gt;

&lt;p&gt;The declared dependencies in our frameworks are now properly registered inside our app context, dependency injection system or anything you can imagine.&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://www.bloggure.info/Module_Registration_Swift&quot;&gt;Swift module registration&lt;/a&gt; was originally published by  at &lt;a href=&quot;http://www.bloggure.info&quot;&gt;Bloggure&lt;/a&gt; on April 28, 2025.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Swift macro : @VisibleForTesting]]></title>
  <link>http://www.bloggure.info/Swift_VisibleForTesting_Macro</link>
  <id>http://www.bloggure.info/Swift_VisibleForTesting_Macro</id>
  <published>2024-04-19T00:00:00+02:00</published>
  <updated>2024-04-19T00:00:00+02:00</updated>
  <author>
    <name></name>
    <uri>http://www.bloggure.info</uri>
    <email></email>
  </author>
  <content type="html">&lt;p&gt;As an ancient Java developer, I’ve learned to use a set of annotations to bring meta programming in my projects.&lt;/p&gt;

&lt;p&gt;Meta programming can be considered as an orthogonal thing to your code, you can inject code to log things, to wrap execution in a transaction or simply provide some context to your fellow developer.&lt;/p&gt;

&lt;p&gt;One of my favorite context providing annotation at this time was brought by Google-Annotations package : &lt;a href=&quot;https://guava.dev/releases/19.0/api/docs/com/google/common/annotations/VisibleForTesting.html&quot;&gt;&lt;code&gt;@VisibleForTesting&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Its goal is rather simple: provide the context that the visibility of the variable / method is not as restricted as it should be, but this is for the sake of testing.&lt;/p&gt;

&lt;p&gt;When going back to my loved XCode (just kidding), I miss these kind of useful information.&lt;/p&gt;

&lt;p&gt;Of course you can add a comment, that maybe someone will read if he begins to wonder why the visibility is too important.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot; data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// this method should be private but we want to access it from unit test code  &lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;doStuff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can also play with the deprecation annotation to trigger a warning (one more to add and parse with your eyes…)&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot; data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;kd&quot;&gt;@available&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deprecated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;This is visible for testing&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  
&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;myState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;State&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;But one thing I was really missing is the ability to really set the proper visibility on my fields while keeping the testability.&lt;/p&gt;

&lt;p&gt;Recently, &lt;a href=&quot;https://www.swift.org/blog/swift-5.9-released/&quot;&gt;Swift 5.9 had added Macro support&lt;/a&gt;. Macros can be seen as ways to generate code based on specific instructions (this brings back old Java-apt memories).&lt;/p&gt;

&lt;h1 id=&quot;macro-types&quot;&gt;Macro types&lt;/h1&gt;

&lt;p&gt;There are multiples macro types, whether they can attach to fields, and depending on what they can do:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Providing accessors&lt;/li&gt;
  &lt;li&gt;Generating code alongside the annotated field&lt;/li&gt;
  &lt;li&gt;Generating code “in-place”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are two ways of calling macros :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Attached ones with &lt;code&gt;@MacroName&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Freeform ones with &lt;code&gt;#MacroName&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I will not enter the details of each type and implementation, you will see more on this later here and can scout on GitHub repositories for inspiration.&lt;/p&gt;

&lt;p&gt;Attached macros are written with a leading @ and can generate code alongside some of our declaration. This allowed me to introduce my own &lt;code&gt;@VisibleForTesting&lt;/code&gt; for swift implementation.&lt;/p&gt;

&lt;p&gt;The idea behind this is really simple, generate specific code with public visibility that wraps call to the “non-exposed” real method.&lt;/p&gt;

&lt;p&gt;This way we get the best of both worlds, we keep our fields private, we tell our colleagues that this field is available for testing and we are able to test it properly.&lt;/p&gt;

&lt;h1 id=&quot;what-does-it-look-like-&quot;&gt;What does it look like ?&lt;/h1&gt;

&lt;p&gt;To use this library, you need to add an SPM dependency on this repository: &lt;a href=&quot;https://github.com/CedricGatay/SwiftMacroUtils&quot;&gt;https://github.com/CedricGatay/SwiftMacroUtils&lt;/a&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot; data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;https://github.com/CedricGatay/SwiftMacroUtils&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;branch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;main&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Then, let’s say you want to give access for testing to the name var of the Dog struct to your testing code, you simply need to do the following&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot; data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Dog&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;@VisibleForTesting&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Under the hood, the macro will generate a public accessor that you will be able to use in your tests&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot; data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;__test_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newValue&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The same goes for &lt;code&gt;let&lt;/code&gt;, &lt;code&gt;func&lt;/code&gt;, and &lt;code&gt;init&lt;/code&gt; . The only specific thing to keep in mind that if you annotate a &lt;code&gt;class&lt;/code&gt; initializer, you need to mark it as &lt;code&gt;required&lt;/code&gt;, otherwise the build will fail (but a nice comment will tell you why).&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://www.bloggure.info/Swift_VisibleForTesting_Macro&quot;&gt;Swift macro : @VisibleForTesting&lt;/a&gt; was originally published by  at &lt;a href=&quot;http://www.bloggure.info&quot;&gt;Bloggure&lt;/a&gt; on April 19, 2024.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Dark Mode iOS Snapshot]]></title>
  <link>http://www.bloggure.info/iOS-DarkModeBackground-App-Switcher</link>
  <id>http://www.bloggure.info/iOS-DarkModeBackground-App-Switcher</id>
  <published>2021-01-10T00:00:00+01:00</published>
  <updated>2021-01-10T00:00:00+01:00</updated>
  <author>
    <name></name>
    <uri>http://www.bloggure.info</uri>
    <email></email>
  </author>
  <content type="html">&lt;h1 id=&quot;dark-mode-support&quot;&gt;Dark mode support&lt;/h1&gt;

&lt;p&gt;As you might know, iOS supports Dark mode since iOS 12. 
It is pretty straightforward to implement it by using dynamic colors. Either system provided ones or adjust them using trait collections.&lt;/p&gt;

&lt;h2 id=&quot;dynamically-switch&quot;&gt;Dynamically switch&lt;/h2&gt;

&lt;p&gt;At times, we can not rely on default colors but we have to listen for &lt;code&gt;traitCollection&lt;/code&gt; changes in order to do what is appropriate for adapting the UI to the mode being active.&lt;/p&gt;

&lt;p&gt;It is easy to do by checking current &lt;code&gt;traitCollection&lt;/code&gt; :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot; data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;traitCollectionDidChange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;previousTraitCollection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UITraitCollection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;traitCollectionDidChange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;previousTraitCollection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;userInterfaceStyle&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;traitCollection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userInterfaceStyle&lt;/span&gt; 
    &lt;span class=&quot;c1&quot;&gt;// Either .unspecified, .light, or .dark&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
 &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;app-switcher-tricks&quot;&gt;App switcher tricks&lt;/h2&gt;

&lt;p&gt;One thing that I was not aware is that iOS switches back and forth
between active mode and the complementary when the app goes to the background :&lt;/p&gt;

&lt;p&gt;To have proper representation in application switcher, if the trait collection is changed, the OS takes a screenshot of your app using  &lt;code&gt;light&lt;/code&gt; and &lt;code&gt;dark&lt;/code&gt; schemes.&lt;/p&gt;

&lt;h2 id=&quot;consequences&quot;&gt;Consequences&lt;/h2&gt;

&lt;p&gt;This can lead to subtle problems, in my case, I was loading a specific stylesheet for MapBox, that, when loaded, was registering / refreshing elements on map. Thus, when the app was put to the background, a lot of things were happening, without a clear explanation (and the app was sometimes crashing due to the very fast switching between map stylesheets).&lt;/p&gt;

&lt;p&gt;The workaround is rather simple : if the app is in the background we prevent loading the stylesheet, the trade-off is acceptable : the app can display an 
invalid preview in application switcher, but, it is nicer on CPU / Network.&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://www.bloggure.info/iOS-DarkModeBackground-App-Switcher&quot;&gt;Dark Mode iOS Snapshot&lt;/a&gt; was originally published by  at &lt;a href=&quot;http://www.bloggure.info&quot;&gt;Bloggure&lt;/a&gt; on January 10, 2021.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Git-gone]]></title>
  <link>http://www.bloggure.info/git-gone</link>
  <id>http://www.bloggure.info/git-gone</id>
  <published>2020-12-31T00:00:00+01:00</published>
  <updated>2020-12-31T00:00:00+01:00</updated>
  <author>
    <name></name>
    <uri>http://www.bloggure.info</uri>
    <email></email>
  </author>
  <content type="html">&lt;h1 id=&quot;git-daily-usage&quot;&gt;Git daily usage&lt;/h1&gt;

&lt;p&gt;If you’re like most developers nowadays you make an extensive usage of &lt;code&gt;git&lt;/code&gt; across a large number of repositories.&lt;/p&gt;

&lt;p&gt;The now-classic branching model that consists in using a branch for every fix / feature / experiment before 
creating a merge request is perfect for collaborating efficiently. 
However it comes with one downside, local branches can quickly become messy and removing stale branches is not that easy&lt;/p&gt;

&lt;h2 id=&quot;removing-remote-branches&quot;&gt;Removing remote branches&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;git&lt;/code&gt; is a really well designed tool and removing remote references from your local repository is easily done by&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;git remote prune origin&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This will remove local references to non existing branches, however it is not useful to remember this command, we can configure &lt;code&gt;git&lt;/code&gt; so that it
prunes automatically on fetching :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;git config &lt;span class=&quot;nt&quot;&gt;--global&lt;/span&gt; fetch.prune &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;removing-local-branches&quot;&gt;Removing local branches&lt;/h2&gt;

&lt;p&gt;This is where things can be quite messy, as local branches might have been merged, rebased, or squashed, and “classic” commands
allowing to detected merged branches are not always working as they ought.&lt;/p&gt;

&lt;p&gt;If you search how to clean up local branches, you might find a lot of commands involving &lt;code&gt;git branch --merged&lt;/code&gt; with &lt;code&gt;grep&lt;/code&gt; and &lt;code&gt;xargs&lt;/code&gt;
all the way.&lt;/p&gt;

&lt;p&gt;I found out a little neat tool to do so, called &lt;a href=&quot;https://github.com/lunaryorn/git-gone&quot;&gt;&lt;code&gt;git-gone&lt;/code&gt; (github/lunaryorn/git-gone)&lt;/a&gt;, written in Rust 🧡&lt;/p&gt;

&lt;h3 id=&quot;install-it&quot;&gt;Install it&lt;/h3&gt;

&lt;p&gt;As all rust project, it is packaged and easy to install using cargo&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot; data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;n&quot;&gt;cargo&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;git&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gone&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;list-branches-to-prune&quot;&gt;List branches to prune&lt;/h3&gt;

&lt;p&gt;Before doing anything silly, we can list the branches that are candidate to removal (the &lt;code&gt;-f&lt;/code&gt; flag forces fetching remote)&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot; data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gone&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;feature&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asciidoc&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;fix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imageCompress&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;pruneem-all&quot;&gt;Prune’em all&lt;/h3&gt;

&lt;p&gt;And finally, you can remove them (the &lt;code&gt;-f&lt;/code&gt; flag is still here to sync with remote)&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot; data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gone&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prune&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;Deleted&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;feature&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;asciidoc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;restore&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;checkout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;feature&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asciidoc&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f007a&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;Deleted&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;imageCompress&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;restore&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;checkout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imageCompress&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;af523b&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;code&gt;git-gone&lt;/code&gt; is even clever enough to give you the command to restore a branch if change your mind 
(as long as you did not &lt;code&gt;git gc&lt;/code&gt; the revision is still available locally, so you can restore it)&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://www.bloggure.info/git-gone&quot;&gt;Git-gone&lt;/a&gt; was originally published by  at &lt;a href=&quot;http://www.bloggure.info&quot;&gt;Bloggure&lt;/a&gt; on December 31, 2020.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[macOS tools checklist]]></title>
  <link>http://www.bloggure.info/mac-checklist</link>
  <id>http://www.bloggure.info/mac-checklist</id>
  <published>2020-12-06T00:00:00+01:00</published>
  <updated>2020-12-06T00:00:00+01:00</updated>
  <author>
    <name></name>
    <uri>http://www.bloggure.info</uri>
    <email></email>
  </author>
  <content type="html">&lt;h1 id=&quot;welcome-to-the-mac-world&quot;&gt;Welcome to the mac world&lt;/h1&gt;

&lt;p&gt;Wether you’re new to macOS or an user coming from different machines, the tools we tend to use is very important to be productive.&lt;/p&gt;

&lt;p&gt;Here you’ll find my list and what I install on a fresh machine to get started.&lt;/p&gt;

&lt;h2 id=&quot;system-tools&quot;&gt;System tools&lt;/h2&gt;

&lt;p&gt;First and foremost, I start with &lt;a href=&quot;https://brew.sh&quot;&gt;&lt;code&gt;brew&lt;/code&gt;&lt;/a&gt; to handle all my packages, but instead of doing so manually, I use my &lt;a href=&quot;https://github.com/CedricGatay/dotfiles.git&quot;&gt;&lt;code&gt;dotfiles&lt;/code&gt;&lt;/a&gt; and my “magic”
install script that does all the heavy lifting for me.&lt;/p&gt;

&lt;h2 id=&quot;keyboard-tools&quot;&gt;Keyboard tools&lt;/h2&gt;

&lt;p&gt;I use a TypeMatrix with a Colemak layout and also the internal keyboard of my laptop with its default layout.
Colemak layout is available on a fresh macOS install but it is far from perfect as there are a lot of missing dead keys 
(to type accented letters mainly), so I start by installing the layout provided on this page: &lt;a href=&quot;https://colemak.com/Mac&quot;&gt;Colemak mac&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To easily switch between the two and get almost the same feeling, I use &lt;a href=&quot;http://karabiner-elements.pqrs.org/&quot;&gt;Karabiner Elements&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;everyday-tools&quot;&gt;Everyday tools&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://sdkman.io&quot;&gt;sdkman&lt;/a&gt;: to manage installation of various sdk (mainly Java based)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://iterm2.com&quot;&gt;iTerm&lt;/a&gt;: nice terminal app with profiles&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/tmux/tmux/wiki&quot;&gt;tmux&lt;/a&gt;: multi terminal in one window, switching terminal with a keystroke&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.zsh.org&quot;&gt;zsh&lt;/a&gt;: Z-Shell&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://starship.rs&quot;&gt;starship&lt;/a&gt;: fast shell prompt&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/BurntSushi/ripgrep&quot;&gt;ripgrep&lt;/a&gt;: faster &lt;code&gt;grep&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/sharkdp/bat&quot;&gt;bat&lt;/a&gt;: nice &lt;code&gt;cat&lt;/code&gt; alternative (with paging / highlighting)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/ogham/exa&quot;&gt;exa&lt;/a&gt;: replacement for &lt;code&gt;ls&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.alfredapp.com&quot;&gt;Alfred&lt;/a&gt;: Spotlight with more features, this is my main app launcher / switcher&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://go.setapp.com/invite/cgatay&quot;&gt;SetApp&lt;/a&gt;: App subscription service, use many tools from this (BetterTouchTool, iStat, BarTender…)&lt;/li&gt;
&lt;/ul&gt;

  &lt;p&gt;&lt;a href=&quot;http://www.bloggure.info/mac-checklist&quot;&gt;macOS tools checklist&lt;/a&gt; was originally published by  at &lt;a href=&quot;http://www.bloggure.info&quot;&gt;Bloggure&lt;/a&gt; on December 06, 2020.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[SwiftUI UIKit Interop]]></title>
  <link>http://www.bloggure.info/SwiftUI-UIKit_Interop</link>
  <id>http://www.bloggure.info/SwiftUI-UIKit_Interop</id>
  <published>2020-12-03T00:00:00+01:00</published>
  <updated>2020-12-03T00:00:00+01:00</updated>
  <author>
    <name></name>
    <uri>http://www.bloggure.info</uri>
    <email></email>
  </author>
  <content type="html">&lt;p&gt;Now that iOS13 is available for more than year, we can start to set it as a lower bound for our deployments.&lt;/p&gt;

&lt;p&gt;This allows us to play with all sugar that Apple put in it, one of the biggest thing is the arrival of SwiftUI to supercede UIKit (write cross platform UIs and so on).&lt;/p&gt;

&lt;h2 id=&quot;bridging-the-two-worlds&quot;&gt;Bridging the two worlds&lt;/h2&gt;

&lt;h3 id=&quot;using-swiftui-from-uikit&quot;&gt;Using SwiftUI from UIKit&lt;/h3&gt;

&lt;p&gt;The first thing we might try to do, is embedding a SwiftUI View inside our &lt;code&gt;UIViewController&lt;/code&gt; based application. To do so, Apple gives us &lt;code&gt;UIHostingViewController&lt;/code&gt; which is a simple bridging controller, straightforward to use.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot; data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyNewView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;I&apos;m in SwiftUI&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyNewViewViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIHostingViewController&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;rootView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyNewView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;uiviewcontroller-in-swiftui&quot;&gt;UIViewController in SwiftUI&lt;/h3&gt;

&lt;p&gt;However, at times we still need to reuse our good old &lt;code&gt;UIViewController&lt;/code&gt;, either because we can not afford a full SwiftUI rewrite, so we want to keep old code and migrate pieces by pieces or because we are using something not yet adapted to SwiftUI.&lt;/p&gt;

&lt;p&gt;In my case it was using the camera to scan a QRCode.&lt;/p&gt;

&lt;h4 id=&quot;non-elegant-solution&quot;&gt;Non elegant solution&lt;/h4&gt;

&lt;p&gt;I faced a few issue with examples I found out, most of them are adding an extension to the &lt;code&gt;UIViewController&lt;/code&gt; that makes it conform to &lt;code&gt;UIViewControllerRepresentable&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot; data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;cancellable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Cancellable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// classic stuff&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIViewControllerRepresentable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;typealias&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIViewControllerType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyViewController&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;makeUIViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIViewControllerRepresentableContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;MyViewController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIViewControllerType&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// don&apos;t .init() please, class instance is already available&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;updateUIViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIViewControllerType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIViewControllerRepresentableContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;MyViewController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;dismantleUIViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;uiViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIViewControllerType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coordinator&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Coordinator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;uiViewController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cancellable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cancel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;MyViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I find this not easy to read, as we’re doing weird thing by returning  &lt;code&gt;self&lt;/code&gt; from a function named &lt;code&gt;makeUIViewController&lt;/code&gt;.
Some example are telling to return &lt;code&gt;MyViewController()&lt;/code&gt; instead of &lt;code&gt;self&lt;/code&gt;. Please don’t do this otherwise you’re creating the &lt;code&gt;UIViewController&lt;/code&gt; twice for each call !&lt;/p&gt;

&lt;p&gt;I also had a leak when using  &lt;code&gt;Combine&lt;/code&gt; (more to come about this great framework), my  &lt;code&gt;Cancellable&lt;/code&gt;s were never freed, leading in a memory cycle that kept the &lt;code&gt;UIViewController&lt;/code&gt; living even though it was no longer presented.&lt;/p&gt;

&lt;p&gt;It is important to do proper house keeping in the &lt;code&gt;dismantleUIViewController&lt;/code&gt; method if you don’t want to use too much memory and slow down your app.&lt;/p&gt;

&lt;h4 id=&quot;nice-looking-way-of-doing&quot;&gt;Nice looking way of doing&lt;/h4&gt;

&lt;p&gt;You will find a working example below, basically we need to implement a &lt;code&gt;UIViewControllerRepresentable&lt;/code&gt; struct to represent be the container for our &lt;code&gt;UIViewController&lt;/code&gt; in the SwiftUI world.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot; data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;cancellable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Cancellable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// classic stuff&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyGreatView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIViewControllerRepresentable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;typealias&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIViewControllerType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyViewController&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;makeUIViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIViewControllerRepresentableContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;MyGreatView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIViewControllerType&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;MyViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;updateUIViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIViewControllerType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIViewControllerRepresentableContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;MyGreatView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;dismantleUIViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;uiViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;UIViewControllerType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coordinator&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Coordinator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;uiViewController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cancellable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cancel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MyView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;MyGreatView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

  &lt;p&gt;&lt;a href=&quot;http://www.bloggure.info/SwiftUI-UIKit_Interop&quot;&gt;SwiftUI UIKit Interop&lt;/a&gt; was originally published by  at &lt;a href=&quot;http://www.bloggure.info&quot;&gt;Bloggure&lt;/a&gt; on December 03, 2020.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[XCode unit test and inheritance]]></title>
  <link>http://www.bloggure.info/XCodeUnitTestInheritance</link>
  <id>http://www.bloggure.info/XCodeUnitTestInheritance</id>
  <published>2020-12-02T00:00:00+01:00</published>
  <updated>2020-12-02T00:00:00+01:00</updated>
  <author>
    <name></name>
    <uri>http://www.bloggure.info</uri>
    <email></email>
  </author>
  <content type="html">&lt;p&gt;As I was working on an iOS project, I added unit tests to ensure things are not behaving badly (and will not).&lt;/p&gt;

&lt;p&gt;During the process, a common pattern showed up and a few fields were required, I came up with the idea to basically create a &lt;code&gt;BaseTest&lt;/code&gt; for my tests, so that everything is unified.&lt;/p&gt;

&lt;h2 id=&quot;base-idea&quot;&gt;Base idea&lt;/h2&gt;

&lt;p&gt;I came up with something like the following for my tests&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot; data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;BaseTest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCTestCase&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setUp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;LoginTest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;BaseTest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;LoginMiddleware&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;LoginAction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_loginIsWorking&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;It worked very well within XCode, I could run the tests by hitting the 🔹 in the gutter.&lt;/p&gt;

&lt;h2 id=&quot;bad-things-happen&quot;&gt;Bad things happen&lt;/h2&gt;

&lt;p&gt;The thing that I discovered later (thanks to the CI feedback), is that XCode was not properly discovering my tests as it should.
At first I blamed the fact that my new tests were not at the top level of my sources folder (and the other ones were), but it was easy to check that this was not the problem at all.&lt;/p&gt;

&lt;p&gt;Then, I blamed &lt;code&gt;fastlane&lt;/code&gt; and thought that I’ve missed something in my test target configuration or something, but in fact, the problem was similar when using classical &lt;code&gt;CMD + U&lt;/code&gt; key combo.&lt;/p&gt;

&lt;p&gt;XCode was simply not discovering my test.&lt;/p&gt;

&lt;h2 id=&quot;workaround&quot;&gt;Workaround&lt;/h2&gt;
&lt;p&gt;Inheritance is often misused, in this case, I think it is relevant, but I applied classical way of working around this. I changed my &lt;code&gt;BaseTest&lt;/code&gt; to a &lt;code&gt;BaseHelper&lt;/code&gt; instead, to which the test delegates the calls.&lt;/p&gt;

&lt;p&gt;With this, the test class properly inherits &lt;code&gt;XCTestCase&lt;/code&gt; and is discovered as expected (even in subfolders).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot; data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;BaseHelper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;LoginTest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCTestCase&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;helper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;BaseHelper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;LoginMiddleware&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;LoginAction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;!&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setUp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;helper&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_loginIsWorking&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The nice thing in this solution is that the &lt;code&gt;setUp&lt;/code&gt; call is no longer magical !&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://www.bloggure.info/XCodeUnitTestInheritance&quot;&gt;XCode unit test and inheritance&lt;/a&gt; was originally published by  at &lt;a href=&quot;http://www.bloggure.info&quot;&gt;Bloggure&lt;/a&gt; on December 02, 2020.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Bloggure revival]]></title>
  <link>http://www.bloggure.info/Revival</link>
  <id>http://www.bloggure.info/Revival</id>
  <published>2020-12-01T00:00:00+01:00</published>
  <updated>2020-12-01T00:00:00+01:00</updated>
  <author>
    <name></name>
    <uri>http://www.bloggure.info</uri>
    <email></email>
  </author>
  <content type="html">&lt;h1 id=&quot;its-been-a-long-time&quot;&gt;It’s been a long time&lt;/h1&gt;

&lt;p&gt;It’s been a very long time since last post (5 years…).&lt;/p&gt;

&lt;p&gt;I am convinced that blogging is useful, at least for my present self, and for my future self that tends to lose track of important things.&lt;/p&gt;

&lt;h2 id=&quot;what-will-this-be-about-&quot;&gt;What will this be about ?&lt;/h2&gt;

&lt;p&gt;In the &lt;em&gt;past&lt;/em&gt; I blogged a lot about Java / Maven and so on, my days have evolved to another languages, so I guess
there will be less JVM things (but may be a few Kotlin) and more Swift / Rust on the other end.&lt;/p&gt;

&lt;h2 id=&quot;technical-stack&quot;&gt;Technical stack&lt;/h2&gt;

&lt;p&gt;In a blog reboot, we (I mean dev) like to follow the latest trend and migrate our blog system to latest hype stack.
I will avoid this and focus on writing instead, so the site stays in its 15’ shape:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://jekyllrb.com/&quot;&gt;jekyll&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://pages.github.com&quot;&gt;github-pages&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;markdown (I think &lt;code&gt;jenkins-asciidoc&lt;/code&gt; is around the corner though)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://actions.github.com&quot;&gt;github actions&lt;/a&gt; for building&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sole new thing is that I’m trying to write using &lt;a href=&quot;https://gitpod.io&quot;&gt;gitpod.io&lt;/a&gt; to get a distraction free environment.&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://www.bloggure.info/Revival&quot;&gt;Bloggure revival&lt;/a&gt; was originally published by  at &lt;a href=&quot;http://www.bloggure.info&quot;&gt;Bloggure&lt;/a&gt; on December 01, 2020.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Firebase + XCode]]></title>
  <link>http://www.bloggure.info/FirebasePListXCode</link>
  <id>http://www.bloggure.info/FirebasePListXCode</id>
  <published>2018-03-13T00:00:00+01:00</published>
  <updated>2018-03-13T00:00:00+01:00</updated>
  <author>
    <name></name>
    <uri>http://www.bloggure.info</uri>
    <email></email>
  </author>
  <content type="html">&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Il est courant maintenant d&amp;#8217;utiliser Firebase dans les applications mobiles.
Que ce soit pour la partie statistiques, les notifications ou le reporting des &lt;em&gt;crashes&lt;/em&gt;.
L&amp;#8217;intégration est très facile, mais peut devenir compliquée dès lors qu&amp;#8217;on manipule des &lt;em&gt;target&lt;/em&gt; différentes avec des configurations différentes.
Cet article va vous détailler une façon simple mais efficace de gérer ces différences.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;multiples-fichiers-plist&quot;&gt;Multiples fichiers &lt;code&gt;plist&lt;/code&gt;&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Les fichiers &lt;code&gt;plist&lt;/code&gt; sont des fichiers XML de configuration pour XCode.
Lors de la création d&amp;#8217;un projet dans Firebase, il est possible de récupérer une version générée contenant les bonnes variables pour votre projet.
Le problème qui peut se produire est lorsqu&amp;#8217;on génère plusieurs livrables avec des configurations qui doivent être différentes grace aux &lt;em&gt;target&lt;/em&gt;.
Il n&amp;#8217;est pas possible, ni confortable, de gérer le cas de plusieurs fichiers &lt;code&gt;plist&lt;/code&gt; dans les sources.
La redéfinition du nom du fichier à faire utiliser par Firebase ne marche pas à tous les coups et il a tendance à aller lire la valeur par défaut &lt;code&gt;GoogleService-Info.plist&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;L&amp;#8217;idée est donc d&amp;#8217;utiliser un script au build qui se chargera de configurer correctement le fichier &lt;code&gt;plist&lt;/code&gt; qui sera inclu dans le livrable.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;première-étape-variabilisation&quot;&gt;Première étape : variabilisation&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Pour être capable de générer le fichier &lt;code&gt;plist&lt;/code&gt; correctement, il est nécessaire de le variabiliser.
Dans mon cas, j&amp;#8217;ai identifié les valeurs suivantes qui devaient être variabilisées:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;GOOGLE_APP_ID&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;BUNDLE_ID&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CLIENT_ID&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;REVERSED_CLIENT_ID&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;J&amp;#8217;ai donc ajouté des variables à la configuration de mon build pour représenter ces valeurs qui doivent être personnalisées.
J&amp;#8217;utilise des fichiers &lt;code&gt;.xcconfig&lt;/code&gt; mais ceci fonctionne également avec l&amp;#8217;ajout manuel (dans &lt;code&gt;Build Settings &amp;gt; + &amp;gt; Add User-Defined settings&lt;/code&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Unresolved directive in 2018-03-16-FirebasePListXCode.adoc - include::site/static/lightbox.adoc[]&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;En plus de ça, j&amp;#8217;ai modifié le fichier &lt;code&gt;GoogleService-Info.plist&lt;/code&gt; téléchargé sur Firebase pour enlever les éléments qui allaient être remplacés à terme.
Plus exactement, j&amp;#8217;ai remplacé les valeurs par des chaînes du type &lt;code&gt;WILL BE REPLACED AT BUILD TIME&lt;/code&gt;, qui me permettent facilement de me rendre compte d&amp;#8217;un oubli de configuration.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;deuxième-étape-génération-du-fichier&quot;&gt;Deuxième étape : génération du fichier&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Une fois le fichier et l&amp;#8217;environnement préparé, il ne reste qu&amp;#8217;à générer la version finale.
Pour se faire, je me base sur le système de &lt;em&gt;build&lt;/em&gt; de XCode qui permet de définir simplement des étapes.
Les variables définies dans la configuration du projet sont en effet disponibles simplement lors de l&amp;#8217;exécution des étapes de &lt;em&gt;build&lt;/em&gt;.
En utilisant l&amp;#8217;outil &lt;code&gt;defaults&lt;/code&gt; inclu dans macOS pour la manipulation des fichiers &lt;code&gt;plist&lt;/code&gt;, il est donc facile de définir les valeurs attendues dans le fichier.
J&amp;#8217;ai ainsi rajouté une étape &lt;em&gt;shell&lt;/em&gt; script correspondant à ceci dans le processus de &lt;em&gt;build&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;defaults write &quot;${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist&quot; GOOGLE_APP_ID ${GOOGLE_APP_ID}
defaults write &quot;${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist&quot; BUNDLE_ID ${PKG_IDENTIFIER}
defaults write &quot;${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist&quot; CLIENT_ID ${CLIENT_ID}
defaults write &quot;${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist&quot; REVERSED_CLIENT_ID ${REVERSED_CLIENT_ID}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Enfin, il faut ordonner cette étape de &lt;em&gt;build&lt;/em&gt; après celle de copie des ressources, pour effectuer la modification uniquement dans le fichier binaire de l&amp;#8217;application et non dans les sources du projet.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;bénéfices&quot;&gt;Bénéfices&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Il est donc facile de disposer de configuration différente de Firebase entre le &lt;em&gt;debug&lt;/em&gt; et la &lt;em&gt;release&lt;/em&gt; par exemple.
Ainsi vous pouvez tester l&amp;#8217;envoi de notification, par exemple, sans risquer de polluer des utilisateurs de production, ou activer le &lt;em&gt;crash reporting&lt;/em&gt; uniquement en production par exemple.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
  &lt;p&gt;&lt;a href=&quot;http://www.bloggure.info/FirebasePListXCode&quot;&gt;Firebase + XCode&lt;/a&gt; was originally published by  at &lt;a href=&quot;http://www.bloggure.info&quot;&gt;Bloggure&lt;/a&gt; on March 13, 2018.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Jenkins Workflow Plugin]]></title>
  <link>http://www.bloggure.info/JenkinsWorkflow</link>
  <id>http://www.bloggure.info/JenkinsWorkflow</id>
  <published>2016-01-20T00:00:00+01:00</published>
  <updated>2016-01-20T00:00:00+01:00</updated>
  <author>
    <name></name>
    <uri>http://www.bloggure.info</uri>
    <email></email>
  </author>
  <content type="html">&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Un des points génants lors de l&amp;#8217;utilisation de Jenkins est le coté volatile de la configuration des jobs de builds.
Il est souvent nécessaire de jouer de click-click pour faire la configuration des jobs sur Jenkins et de se reposer sur un plugin permettant de versionner,
autant que possible, les configurations utilisées.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Mais, une fois que vous aurez lu cet article, vous vous rendrez compte que c&amp;#8217;est le passé.
Attention toutefois, cet article parle de Jenkins, de Docker et de Groovy, n&amp;#8217;ayez pas peur, tout est &lt;em&gt;presque&lt;/em&gt; trop simple&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;prérequis-jenkins&quot;&gt;Prérequis Jenkins&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;jenkins-avec-accès-à-docker&quot;&gt;Jenkins avec accès à Docker&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nous avons l&amp;#8217;habitude d&amp;#8217;utiliser un Jenkins lancé dans un container depuis quelques temps.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nous utilisons l&amp;#8217;image maintenue par &lt;a href=&quot;https://agileek.github.io/&quot;&gt;Michael Bitard&lt;/a&gt; &lt;a href=&quot;https://hub.docker.com/r/agileek/docker-jenkins/&quot;&gt;&lt;code&gt;agileek/docker-jenkins&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nous lançons cette image en lui fournissant de quoi exécuter le binaire docker client sans soucis :&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;docker run
       -d --restart=&quot;always&quot; --name jenkins
       -u $(id -u):$(getent group docker | cut -d: -f3) # &lt;b class=&quot;conum&quot;&gt;(1)&lt;/b&gt;
       -p 8080:8080
       -v /var/jenkins_home:/var/jenkins_home # &lt;b class=&quot;conum&quot;&gt;(2)&lt;/b&gt;
       -v $(which docker):/usr/bin/docker # &lt;b class=&quot;conum&quot;&gt;(3)&lt;/b&gt;
       -v /var/run/docker.sock:/var/run/docker.sock # &lt;b class=&quot;conum&quot;&gt;(4)&lt;/b&gt;
       -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1:/lib/x86_64-linux-gnu/libapparmor.so.1 # &lt;b class=&quot;conum&quot;&gt;(5)&lt;/b&gt;
       agileek/docker-jenkins # &lt;b class=&quot;conum&quot;&gt;(6)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Le container est lancé avec l&amp;#8217;utilisateur courant et le groupe &lt;code&gt;docker&lt;/code&gt; pour pouvoir accéder au &lt;code&gt;docker.sock&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pour éviter les incohérences de chemin, le chemin racine du jenkins est le même en dehors et dans le container&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Le binaire docker du système est fourni dans l&amp;#8217;image&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Le socket docker est également fourni pour que le client puisse &quot;parler&quot; au démon&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;La bibliotheque apparmor est nécessaire pour le bon fonctionnement de docker client&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;jenkins-workflow-plugin&quot;&gt;Jenkins workflow plugin&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Pour utiliser la suite des éléments, vous aurez besoin des plugins gérant la notion de &lt;em&gt;workflow&lt;/em&gt; dans Jenkins :&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Unresolved directive in 2016-01-20-JenkinsWorkflow.adoc - include::site/static/lightbox.adoc[]&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ensuite, il nous est possible de créer un job de construction de type &lt;em&gt;workflow&lt;/em&gt; :&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Unresolved directive in 2016-01-20-JenkinsWorkflow.adoc - include::site/static/lightbox.adoc[]&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;premier-job-workflow&quot;&gt;Premier job &lt;em&gt;Workflow&lt;/em&gt;&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ensuite, c&amp;#8217;est là que la magie opère, plutôt que de devoir sélectionner les n-items voulus et remplir chaque étape du build, nous pouvons maintenant le décrire en utilisant du code !
Ainsi, en copiant/collant le script suivant dans la partie idoine, vous devriez avoir un job bien configuré qui marche, du premier coup !&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;def m2Repo = &apos;-v /var/jenkins_home/.m2:/home/jenkins/.m2&apos; //  # &lt;b class=&quot;conum&quot;&gt;(1)&lt;/b&gt;
def timezone = &apos;-e TZ=Europe/Paris&apos; // # &lt;b class=&quot;conum&quot;&gt;(2)&lt;/b&gt;
docker.image(&quot;codetroopers/jenkins-slave-jdk8-restx&quot;)
    .inside(&quot;${m2Repo} ${timezone}&quot;){ //  # &lt;b class=&quot;conum&quot;&gt;(3)&lt;/b&gt;
    git branch: &apos;master&apos;, url: &apos;https://github.com/code-troopers/jenkins-workflow-demo-repo.git&apos; // # &lt;b class=&quot;conum&quot;&gt;(4)&lt;/b&gt;
    sh &quot;MAVEN_OPTS=-Dfile.encoding=UTF-8 mvn clean install -B -Ppackage&quot; // # &lt;b class=&quot;conum&quot;&gt;(5)&lt;/b&gt;
    step([$class: &apos;ArtifactArchiver&apos;, artifacts: &apos;srv/target/dependency/webapp-runner.jar, srv/target/*.war, run.sh&apos;]) // # &lt;b class=&quot;conum&quot;&gt;(6)&lt;/b&gt;
    step([$class: &apos;JUnitResultArchiver&apos;, testResults: &apos;**/target/surefire-reports/TEST-*.xml&apos;]) // # &lt;b class=&quot;conum&quot;&gt;(7)&lt;/b&gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Partage du dépôt Maven local (pour gagner en temps de build)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Export de la timezone (pour les tests unitaires de l&amp;#8217;exemple)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Démarrage du conteneur de build avec la bonne timezone ainsi que le dépôt partagé&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Clonage des sources&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Lancement du build (en forçant l&amp;#8217;UTF-8)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Archivage des produits du build&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Archivage des résultats des tests&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Comme vous pouvez le voir, le script est relativement parlant et permet en plus de s&amp;#8217;affranchir du clickodrome de configuration dans l&amp;#8217;interface de Jenkins !&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Il est intéressant de noter que l&amp;#8217;image Docker qui sert au build est une image personnalisée. Ce n&amp;#8217;est pas parce qu&amp;#8217;elle inclut un quelconque fonctionnement permettant de builder
en utilisant le plugin Workflow. Elle sert de base uniquement car elle met à disposition la partie &lt;code&gt;npm&lt;/code&gt; nécessaire au build de la partie &lt;em&gt;frontend&lt;/em&gt; de l&amp;#8217;application RestX.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;grouper-les-étapes&quot;&gt;Grouper les étapes&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Le plugin workflow permet en plus de grouper les différentes étapes d&amp;#8217;un build pour permettre, par exemple, de le lancer sur plusieurs environnement différents.
Ici nous ajoutons simplement un nom de groupe pour notre étape de build.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;stage &apos;build&apos; // # &lt;b class=&quot;conum&quot;&gt;(1)&lt;/b&gt;
    def m2Repo = &apos;-v /var/jenkins_home/.m2:/home/jenkins/.m2&apos;
    def timezone = &apos;-e TZ=Europe/Paris&apos;
    docker.image(&quot;codetroopers/jenkins-slave-jdk8-restx&quot;).inside(&quot;${m2Repo} ${timezone}&quot;){
        git branch: &apos;master&apos;, url: &apos;https://github.com/code-troopers/jenkins-workflow-demo-repo.git&apos;
        sh &quot;MAVEN_OPTS=-Dfile.encoding=UTF-8 mvn clean install -B -Ppackage&quot;
        step([$class: &apos;ArtifactArchiver&apos;, artifacts: &apos;srv/target/dependency/webapp-runner.jar, srv/target/*.war, run.sh&apos;])
        step([$class: &apos;JUnitResultArchiver&apos;, testResults: &apos;**/target/surefire-reports/TEST-*.xml&apos;])
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Étape nommée pour l&amp;#8217;exécution de la construction de l&amp;#8217;application&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;mettre-de-côté-les-fichiers-pour-plus-tard&quot;&gt;Mettre de côté les fichiers pour plus tard&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;La notion de &lt;em&gt;stash&lt;/em&gt; bien connue des utilisateurs de git est également présente.
Elle permet de mettre de côté des fichiers pour les réutiliser à une étape ultérieure du &lt;em&gt;workflow&lt;/em&gt; de build.
Ceci permet d&amp;#8217;éviter l&amp;#8217;archivage de produits du build pour des raisons &quot;techniques&quot;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;stage &apos;build&apos;
    def m2Repo = &apos;-v /var/jenkins_home/.m2:/home/jenkins/.m2&apos;
    def timezone = &apos;-e TZ=Europe/Paris&apos;
    docker.image(&quot;codetroopers/jenkins-slave-jdk8-restx&quot;).inside(&quot;${m2Repo} ${timezone}&quot;){
        git branch: &apos;master&apos;, url: &apos;https://github.com/code-troopers/jenkins-workflow-demo-repo.git&apos;
        sh &quot;MAVEN_OPTS=-Dfile.encoding=UTF-8 mvn clean install -B -Ppackage&quot;
        step([$class: &apos;ArtifactArchiver&apos;, artifacts: &apos;srv/target/dependency/webapp-runner.jar, srv/target/*.war, run.sh&apos;])
        step([$class: &apos;JUnitResultArchiver&apos;, testResults: &apos;**/target/surefire-reports/TEST-*.xml&apos;])
        stash includes: &apos;run.sh,srv/target/dependency/webapp-runner.jar,srv/target/*.war,Dockerfile&apos;, name: &apos;dockerBuild&apos; // # &lt;b class=&quot;conum&quot;&gt;(1)&lt;/b&gt;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Enregistrement d&amp;#8217;une liste de fichiers associée à un nom pour une utilisation ultérieure&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;étape-de-construction-dune-image-docker&quot;&gt;Étape de construction d&amp;#8217;une image Docker&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;stage &apos;docker&apos; // # &lt;b class=&quot;conum&quot;&gt;(1)&lt;/b&gt;
node{ // # &lt;b class=&quot;conum&quot;&gt;(2)&lt;/b&gt;
  ws{ // # &lt;b class=&quot;conum&quot;&gt;(3)&lt;/b&gt;
    unstash &apos;dockerBuild&apos; // # &lt;b class=&quot;conum&quot;&gt;(4)&lt;/b&gt;
    docker.build(&quot;codetroopers/jenkins-workflow-demo:${env.BUILD_ID}&quot;) // # &lt;b class=&quot;conum&quot;&gt;(5)&lt;/b&gt;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Création d&amp;#8217;une nouvelle étape&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Permet de distinguer un ensemble d&amp;#8217;opération de build (peut accepter les labels pour restreindre sur des noeuds)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Déclenche la création d&amp;#8217;un nouveau &lt;em&gt;workspace&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Récupère les fichiers mis de côté sous le nom &lt;code&gt;dockerBuild&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Construction d&amp;#8217;une image docker avec pour tag le numéro de build en cours (&lt;code&gt;$BUILD_ID&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;workflow-et-gestion-multibranche&quot;&gt;Workflow et gestion multibranche&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Dans nos façons de fonctionner qui sont maintenant devenues habituelles, nous utilisons de façon intensives les branches pour isoler nos développements.
Un des points fastidieux est de configurer un nouveau job Jenkins pour chaque branche afin de valider son bon fonctionnement et ne pas se rendre compte trop tard d&amp;#8217;un build au rouge.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Le plugin &apos;Workflow Multibranch&apos; simplifie de façon drastique ce genre de cas, il suffit de rajouter un descripteur de build dans les sources.
Le fichier correspondant est tout simplement appelé &lt;code&gt;Jenkinsfile&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;stage &apos;build&apos;
    def m2Repo = &apos;-v /var/jenkins_home/.m2:/home/jenkins/.m2&apos;
    def timezone = &apos;-e TZ=Europe/Paris&apos;
    docker.image(&quot;codetroopers/jenkins-slave-jdk8-restx&quot;).inside(&quot;${m2Repo} ${timezone}&quot;){
        checkout scm // # &lt;b class=&quot;conum&quot;&gt;(1)&lt;/b&gt;
        sh &quot;MAVEN_OPTS=-Dfile.encoding=UTF-8 mvn clean install -B -Ppackage&quot;
        step([$class: &apos;ArtifactArchiver&apos;, artifacts: &apos;srv/target/dependency/webapp-runner.jar, srv/target/*.war, run.sh&apos;])
        step([$class: &apos;JUnitResultArchiver&apos;, testResults: &apos;**/target/surefire-reports/TEST-*.xml&apos;])
        stash includes: &apos;run.sh,srv/target/dependency/webapp-runner.jar,srv/target/*.war,Dockerfile&apos;, name: &apos;dockerBuild&apos;
    }

stage &apos;docker&apos;
node{
  ws{
    unstash &apos;dockerBuild&apos;
    docker.build(&quot;codetroopers/jenkins-workflow-demo:${env.BUILD_ID}&quot;)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Il faut bien entendu remplacer l&amp;#8217;endroit où nous faisions le git clone pour qu&amp;#8217;il soit dynamique par rapport à ce qu&amp;#8217;on
construit. Le terme &lt;code&gt;checkout scm&lt;/code&gt; permet de s&amp;#8217;assurer de ce fonctionnement.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;L&amp;#8217;intérêt est que chaque branche qui sera buildée n&amp;#8217;aura pas son historique mélangé avec une autre (là où les jobs de validation de Pull Request ont tendance à tout mélanger).
De plus, un changement dans le process de build sera directement versionné.
Il n&amp;#8217;y aura donc pas besoin de penser à éditer le job lors du merge sur &lt;code&gt;master&lt;/code&gt; (on a tous vécu ce genre de situation énervante) !&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;attendre-une-confirmation-utilisateur&quot;&gt;Attendre une confirmation utilisateur&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Un des points intéressant de ce plugin est qu&amp;#8217;il permet la mise en pause des constructions.
Ainsi, il est possible de mettre en pause une construction correspondant à une livraison et de lui faire attendre une validation manuelle par exemple.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;stage &apos;build&apos;
    def m2Repo = &apos;-v /var/jenkins_home/.m2:/home/jenkins/.m2&apos;
    def timezone = &apos;-e TZ=Europe/Paris&apos;
    docker.image(&quot;codetroopers/jenkins-slave-jdk8-restx&quot;).inside(&quot;${m2Repo} ${timezone}&quot;){
        git branch: &apos;master&apos;, url: &apos;https://github.com/code-troopers/jenkins-workflow-demo-repo.git&apos;
        sh &quot;MAVEN_OPTS=-Dfile.encoding=UTF-8 mvn clean install -B -Ppackage&quot;
        step([$class: &apos;ArtifactArchiver&apos;, artifacts: &apos;srv/target/dependency/webapp-runner.jar, srv/target/*.war, run.sh&apos;])
        step([$class: &apos;JUnitResultArchiver&apos;, testResults: &apos;**/target/surefire-reports/TEST-*.xml&apos;])
        stash includes: &apos;run.sh,srv/target/dependency/webapp-runner.jar,srv/target/*.war,Dockerfile&apos;, name: &apos;dockerBuild&apos;
    }

stage &apos;docker&apos;
node{
    ws{
        unstash &apos;dockerBuild&apos;
        def built = docker.build(&quot;codetroopers/jenkins-workflow-demo:${env.BUILD_ID}&quot;)
        input &apos;Is everything ok ? Run app ?&apos; // # &lt;b class=&quot;conum&quot;&gt;(1)&lt;/b&gt;
        echo &quot;We can run the docker-compose up here&quot;
        def outcome = input message: &apos;We can even have parameters to answer this question&apos;, parameters: [ // # &lt;b class=&quot;conum&quot;&gt;(2)&lt;/b&gt;
            [name: &apos;myChoice&apos;, description: &apos;My choice&apos;, choices: &apos;Choice 1\nChoice 2\nChoice 3&apos;, $class: &apos;ChoiceParameterDefinition&apos;]
        ]
        echo &quot;You have chosen ${outcome}&quot; // # &lt;b class=&quot;conum&quot;&gt;(3)&lt;/b&gt;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;input&lt;/code&gt; met en pause la construction et permet de continuer ou interrompre celle-ci&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Il est également possible de permettre à l&amp;#8217;utilisateur de faire un choix&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ici la valeur sélectionnée par l&amp;#8217;utilisateur est écrite dans la sortie du build.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;J&amp;#8217;espère que cet article vous donnera l&amp;#8217;envie d&amp;#8217;essayer de rationnaliser un peu plus la configuration de vos job Jenkins en les stockant dans votre SCM&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
  &lt;p&gt;&lt;a href=&quot;http://www.bloggure.info/JenkinsWorkflow&quot;&gt;Jenkins Workflow Plugin&lt;/a&gt; was originally published by  at &lt;a href=&quot;http://www.bloggure.info&quot;&gt;Bloggure&lt;/a&gt; on January 20, 2016.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Avoid Proxy Headaches in Docker with redsocks]]></title>
  <link>http://www.bloggure.info/redsocks-avoid-docker-proxy-headaches</link>
  <id>http://www.bloggure.info/redsocks-avoid-docker-proxy-headaches</id>
  <published>2015-11-16T00:00:00+01:00</published>
  <updated>2015-11-16T00:00:00+01:00</updated>
  <author>
    <name></name>
    <uri>http://www.bloggure.info</uri>
    <email></email>
  </author>
  <content type="html">&lt;h1 id=&quot;proxying-with-docker&quot;&gt;Proxying with Docker&lt;/h1&gt;

&lt;p&gt;When using docker under a corporate proxy, it can be cumbersome to have a working networking in all containers. You often end up being blocked by specific network access which does not seem to be properly forwarded to the proper proxy. For example when using &lt;code&gt;apt&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;classic-way-of-doing&quot;&gt;Classic way of doing&lt;/h2&gt;

&lt;p&gt;There is a &lt;a href=&quot;https://docs.docker.com/engine/articles/systemd/#http-proxy&quot;&gt;documented way&lt;/a&gt; of using a proxy, by adding command-line switches to your docker deamon. However, it does not seem to work everytime and could require exporting additional settings to your in-container applications (in my experience though).&lt;/p&gt;

&lt;h2 id=&quot;why-not-using-docker&quot;&gt;Why not using docker&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/ncarlier/&quot;&gt;Nicolas&lt;/a&gt; pointed me an image he created to help with the setup of a corporate proxy. It uses &lt;a href=&quot;http://darkk.net.ru/redsocks/&quot;&gt;redsocks&lt;/a&gt; under the hood that listen to the docker socket and automatically add the glue to do the forwarding through the proxy.&lt;/p&gt;

&lt;p&gt;Easy proxying in docker is just one command away ! (fill in the blank of your proxy ip and port)&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;docker run \
       --restart=always \
       --privileged=true \
       --net=host \
       -d ncarlier/redsocks \
       $PROXY_IP $PROXY_PORT
&lt;/code&gt;&lt;/pre&gt;

  &lt;p&gt;&lt;a href=&quot;http://www.bloggure.info/redsocks-avoid-docker-proxy-headaches&quot;&gt;Avoid Proxy Headaches in Docker with redsocks&lt;/a&gt; was originally published by  at &lt;a href=&quot;http://www.bloggure.info&quot;&gt;Bloggure&lt;/a&gt; on November 16, 2015.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Faster ssh multi hop]]></title>
  <link>http://www.bloggure.info/faster-ssh-multi-hop</link>
  <id>http://www.bloggure.info/faster-ssh-multi-hop</id>
  <published>2015-09-07T00:00:00+02:00</published>
  <updated>2015-09-07T00:00:00+02:00</updated>
  <author>
    <name></name>
    <uri>http://www.bloggure.info</uri>
    <email></email>
  </author>
  <content type="html">&lt;h1 id=&quot;multi-hop&quot;&gt;Multi Hop&lt;/h1&gt;

&lt;p&gt;It is often required that, for security reason, you have to hop through a SSH gateway to access other machines. While this is perfectly fine and simple to do, it is often cumbersome to open a new session.
However, with a small script you can speed up your access to machines even with such a restriction in place.&lt;/p&gt;

&lt;h2 id=&quot;classical-way-of-hoping&quot;&gt;Classical way of hop’ing&lt;/h2&gt;

&lt;p&gt;Let’s say our gateway is named &lt;code&gt;gateway&lt;/code&gt; and our target host &lt;code&gt;myAppHost&lt;/code&gt; the classical way of doing it would be :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;ssh gateway
you@gateway &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;hostname
&lt;/span&gt;gateway.my.tld
you@gateway &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ssh myAppHost
you@myAppHost &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;hostname
&lt;/span&gt;myAppHost.my.tld
   &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;faster-way-of-hoping&quot;&gt;Faster way of hop’ing&lt;/h2&gt;

&lt;p&gt;A quicker way of doing this is to specify the ssh command directly, there is one thing to tell ssh though: allocating a TTY even if it does not seem to be connected to one. 
In fact, the command supplied to ssh is not supposed to be interactive, that is why you need to give this hint to SSH :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;ssh &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; gateway ssh myAppHost
you@myAppHost &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;hostname
&lt;/span&gt;myAppHost.my.tld
   &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;script-this-&quot;&gt;Script this !&lt;/h2&gt;
&lt;p&gt;The script is really simple, and only consists in the following&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/sh&lt;/span&gt;
ssh &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; gateway ssh &lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt;
   &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Save this in your path and give it the run permission then you are all set (mine is named &lt;code&gt;gssh&lt;/code&gt;). All you have to do to connect is now a simple &lt;code&gt;gssh myAppHost&lt;/code&gt;&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://www.bloggure.info/faster-ssh-multi-hop&quot;&gt;Faster ssh multi hop&lt;/a&gt; was originally published by  at &lt;a href=&quot;http://www.bloggure.info&quot;&gt;Bloggure&lt;/a&gt; on September 07, 2015.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[How to get all failing tests for a multimodule Maven project]]></title>
  <link>http://www.bloggure.info/MavenFailTestAtEnd</link>
  <id>http://www.bloggure.info/MavenFailTestAtEnd</id>
  <published>2015-09-03T00:00:00+02:00</published>
  <updated>2015-09-03T00:00:00+02:00</updated>
  <author>
    <name></name>
    <uri>http://www.bloggure.info</uri>
    <email></email>
  </author>
  <content type="html">&lt;h1 id=&quot;maven-testing&quot;&gt;Maven testing&lt;/h1&gt;

&lt;p&gt;One of the bothering thing being a contractor is that you often happen to work on a project with a skip tests flag set on all developers computer.&lt;/p&gt;

&lt;p&gt;One of the thing I tend to do when on such project is enabling tests and trying to fix as much as possible (often the fixes are easy to do).&lt;/p&gt;

&lt;h2 id=&quot;multi-module-testing&quot;&gt;Multi module testing&lt;/h2&gt;
&lt;p&gt;By design, surefire plugin make the build fail if there is a test failure. 
While this is ok in single module, when working with multi-module project it can be nice to run all tests on all modules regardless of the failures happening in some modules.&lt;/p&gt;

&lt;p&gt;Maven is a great tool and allows such a behavior very easily, it allows two command line switches for that :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;--fail-at-end&lt;/code&gt; : will fail the build at the end if there is test failures&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;--fail-never&lt;/code&gt; : will never fail the build, even if there is test failures&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;flags-behavior-differences&quot;&gt;Flags behavior differences&lt;/h2&gt;
&lt;p&gt;There is one thing to understand when using the &lt;code&gt;--fail-at-end&lt;/code&gt; flag, it will fail the build at end for a module with test failure but it will also prevents building of dependent modules.&lt;/p&gt;

&lt;p&gt;With a small example it become obvious. Let’s say that we have a multi project containing the following :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;core : containing model objects and services&lt;/li&gt;
  &lt;li&gt;web : containing web views for browser access&lt;/li&gt;
  &lt;li&gt;javafx : containing desktop application classes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is straightforward to see that &lt;code&gt;web&lt;/code&gt; and &lt;code&gt;javafx&lt;/code&gt; modules will depends on the &lt;code&gt;core&lt;/code&gt; module.&lt;/p&gt;

&lt;h3 id=&quot;fail-at-end&quot;&gt;fail-at-end&lt;/h3&gt;
&lt;p&gt;If using the &lt;code&gt;--fail-at-end&lt;/code&gt; flag, a test failure in the core module will prevent building the &lt;code&gt;web&lt;/code&gt; and &lt;code&gt;javafx&lt;/code&gt; module completely : you will not be able to track tests failure before fixing the ones from &lt;code&gt;core&lt;/code&gt; (at least on a single build command).&lt;/p&gt;

&lt;h3 id=&quot;fail-never&quot;&gt;fail-never&lt;/h3&gt;
&lt;p&gt;If using the &lt;code&gt;--fail-never&lt;/code&gt; flag, a test failure in the &lt;code&gt;core&lt;/code&gt; module will be reported but the build and tests of the &lt;code&gt;web&lt;/code&gt; and &lt;code&gt;javafx&lt;/code&gt; modules will be built and their respective tests errors will also be reported.&lt;/p&gt;

&lt;h2 id=&quot;tired-of-typing&quot;&gt;Tired of typing&lt;/h2&gt;
&lt;p&gt;If you find that typing &lt;code&gt;--fail-at-end&lt;/code&gt; is too long, remember yourself it short alias : &lt;code&gt;-fae&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The same is also available for the &lt;code&gt;--fail-never&lt;/code&gt; flag with : &lt;code&gt;-fn&lt;/code&gt;.&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://www.bloggure.info/MavenFailTestAtEnd&quot;&gt;How to get all failing tests for a multimodule Maven project&lt;/a&gt; was originally published by  at &lt;a href=&quot;http://www.bloggure.info&quot;&gt;Bloggure&lt;/a&gt; on September 03, 2015.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Auto apply a keymap using udev when a Typematrix is plugged]]></title>
  <link>http://www.bloggure.info/AutoApplyKeymapUdev</link>
  <id>http://www.bloggure.info/AutoApplyKeymapUdev</id>
  <published>2015-09-01T00:00:00+02:00</published>
  <updated>2015-09-01T00:00:00+02:00</updated>
  <author>
    <name></name>
    <uri>http://www.bloggure.info</uri>
    <email></email>
  </author>
  <content type="html">&lt;h1 id=&quot;switching-keymap&quot;&gt;Switching keymap&lt;/h1&gt;

&lt;p&gt;As some of you might know, I am now using a Typematrix 2030 on a daily basis. When I switched to this great keyboard I also adopted a new layout on it : Colemak.&lt;/p&gt;

&lt;p&gt;However, to be able to pair with others not using a Colemak mapping, I did not set the default mapping to Colemak but I instead use an udev rule to set the input method to Colemak only for the Typematrix.&lt;/p&gt;

&lt;h2 id=&quot;write-a-keymap-switch-script&quot;&gt;Write a keymap switch script&lt;/h2&gt;

&lt;p&gt;Thanks to &lt;a href=&quot;https://twitter.com/BitardMichael&quot;&gt;@BitardMichael&lt;/a&gt; tips and existing scripts I ended up with the following script saved in &lt;code&gt;/usr/local/bin/set_typematrix_colemak_mapping&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;One of the tricky part was having a way of executing the script only when the keyboard is ready, without blocking udev’s job (or the keyboard is not yet visible by the X system). The workaround I found was scheduling the execution of the job with the simple &lt;code&gt;at&lt;/code&gt; command.
&lt;script src=&quot;https://gist.github.com/1673456ec94a1f6d8d0d set_typematrix_colemak_mapping.js&quot;&gt;&lt;/script&gt;&lt;/p&gt;

&lt;h2 id=&quot;tell-udev-to-run-the-script-on-keyboard-detection&quot;&gt;Tell udev to run the script on keyboard detection&lt;/h2&gt;
&lt;p&gt;The process is really easy, all you need to do is to add the following to a new file : &lt;code&gt;/lib/udev/rules.d/85-typematrix.rules&lt;/code&gt;&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/1673456ec94a1f6d8d0d 85-typematrix.rules.js&quot;&gt;&lt;/script&gt;

&lt;h2 id=&quot;adapting-it-to-your-use-case&quot;&gt;Adapting it to your use case&lt;/h2&gt;
&lt;p&gt;If you are using another keyboard than a Typematrix you will need to adapt the udev rule with the proper Vendor / Product IDs (you can grab them with &lt;code&gt;lsusb&lt;/code&gt;). 
For the &lt;code&gt;xinput&lt;/code&gt; part, you will need to adjust the grep to match your hardware.&lt;/p&gt;

&lt;p&gt;Of course the same goes for your layout : colemak / dvorak / bépo …&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://www.bloggure.info/AutoApplyKeymapUdev&quot;&gt;Auto apply a keymap using udev when a Typematrix is plugged&lt;/a&gt; was originally published by  at &lt;a href=&quot;http://www.bloggure.info&quot;&gt;Bloggure&lt;/a&gt; on September 01, 2015.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Debug Failsafe Integration Test easily]]></title>
  <link>http://www.bloggure.info/DebugMavenFailsafe</link>
  <id>http://www.bloggure.info/DebugMavenFailsafe</id>
  <published>2015-04-13T00:00:00+02:00</published>
  <updated>2015-04-13T00:00:00+02:00</updated>
  <author>
    <name></name>
    <uri>http://www.bloggure.info</uri>
    <email></email>
  </author>
  <content type="html">&lt;h1 id=&quot;arquillian-testing&quot;&gt;Arquillian testing&lt;/h1&gt;

&lt;p&gt;Arquillian testing is very convenient to get a full environment deployed on an application server and test quickly. 
However sometimes we need to debug what’s inside.&lt;/p&gt;

&lt;h2 id=&quot;launch-maven-in-debug-mode&quot;&gt;Launch Maven in Debug mode&lt;/h2&gt;
&lt;p&gt;One way of doing it can be launching Maven in debug mode with the &lt;code&gt;mvnDebug&lt;/code&gt; command instead of the classical &lt;code&gt;mvn&lt;/code&gt;.
It will open a JPDA debugger on port 8000 by default, you then just need to connect to it with your preferred IDE.
But, if there is forked processes, you won’t be able to debug inside them.&lt;/p&gt;

&lt;h2 id=&quot;launch-failsafe-integration-tests-in-debug-mode&quot;&gt;Launch Failsafe Integration Tests in Debug mode&lt;/h2&gt;
&lt;p&gt;When using Arquillian, there is a high probability that Arquillian is running in a forked process (with embedded containers).&lt;/p&gt;

&lt;p&gt;There is an easy way to tell &lt;code&gt;maven-failsafe-plugin&lt;/code&gt; to wait for a debugger when starting : use the &lt;code&gt;-Dmaven.failsafe.debug&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;You can even specify the options as you would in &lt;code&gt;$JAVA_OPTS&lt;/code&gt; to change ports / wait…&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://www.bloggure.info/DebugMavenFailsafe&quot;&gt;Debug Failsafe Integration Test easily&lt;/a&gt; was originally published by  at &lt;a href=&quot;http://www.bloggure.info&quot;&gt;Bloggure&lt;/a&gt; on April 13, 2015.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Rescue a VPS upgrade with chroot]]></title>
  <link>http://www.bloggure.info/rescue-a-vps-with-chroot</link>
  <id>http://www.bloggure.info/rescue-a-vps-with-chroot</id>
  <published>2014-09-22T00:00:00+02:00</published>
  <updated>2014-09-22T00:00:00+02:00</updated>
  <author>
    <name></name>
    <uri>http://www.bloggure.info</uri>
    <email></email>
  </author>
  <content type="html">&lt;h1 id=&quot;oops&quot;&gt;Oops&lt;/h1&gt;
&lt;p&gt;A few days ago, I finally decided to upgrade the installed Linux on my dedicated box hosted at &lt;a href=&quot;http://www.kimsufi.com&quot;&gt;OVH&lt;/a&gt;. As with every task you tend to postpone until it is too late, it failed miserably, Murphy’s law I am looking at you right now !&lt;/p&gt;

&lt;p&gt;During the install process, the machine hanged (I am not sure whether the machine is faulty or not, it seems to shut down under heavy load). Fortunately for me, OVH is kind enough to send a tech resets the machine, and if it does not ping when booted, the tech reboots the machine in rescue mode.&lt;/p&gt;

&lt;h1 id=&quot;rescue-mode&quot;&gt;Rescue mode&lt;/h1&gt;
&lt;p&gt;The rescue mode is a lightweight Linux booted on the machine so you can run &lt;code&gt;fsck&lt;/code&gt;and other commands to rescue your filesystem (you can even &lt;code&gt;scp&lt;/code&gt; files to backup before a reinstall for example.&lt;/p&gt;

&lt;p&gt;My problem was simple, I needed to allow &lt;code&gt;dpkg&lt;/code&gt; to finish its job in order for me to get a working machine. Then I think of the good old powerful &lt;code&gt;chroot&lt;/code&gt; command and I remembered how powerful Unixes are !&lt;/p&gt;

&lt;h1 id=&quot;mounting-filesystems&quot;&gt;Mounting filesystems&lt;/h1&gt;
&lt;p&gt;As you should know, &lt;code&gt;chroot&lt;/code&gt; is a command allowing to change the root of a file system, litterally jailing it into a directory (popular things of today like pico containers like Docker are improved versions of chroot).&lt;/p&gt;

&lt;p&gt;The partition layout of my machine is the following :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;sda1 contains the root filesystem&lt;/li&gt;
  &lt;li&gt;sda2 contains home directories&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I issued the following commands to “jail” the rescue mode into my machine :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;mount /dev/sda1 /mnt
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;mount /dev/sda2 /mnt/home
    
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;mount &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bind&lt;/span&gt; /proc /mnt/proc
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;mount &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bind&lt;/span&gt; /dev /mnt/dev
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;mount &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bind&lt;/span&gt; /dev/pts /mnt/dev/pts
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;mount &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bind&lt;/span&gt; /sys /mnt/sys 
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;chroot&lt;/span&gt; /mnt/
    &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;From then, I was on my machine, with my files and so on, so I have been able to run &lt;code&gt;dpkg&lt;/code&gt; to finish the install :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;dpkg &lt;span class=&quot;nt&quot;&gt;--configure&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--a&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Finally, &lt;code&gt;dpkg&lt;/code&gt; sent me back to a working prompt and I have been able to reboot the machine (do not forget to change boot disk in OVH manager to &lt;code&gt;hd&lt;/code&gt; or &lt;code&gt;netboot&lt;/code&gt; to prevent rebooting in rescue mode).&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://www.bloggure.info/rescue-a-vps-with-chroot&quot;&gt;Rescue a VPS upgrade with chroot&lt;/a&gt; was originally published by  at &lt;a href=&quot;http://www.bloggure.info&quot;&gt;Bloggure&lt;/a&gt; on September 22, 2014.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Résoudre les conflits de dépendances]]></title>
  <link>http://www.bloggure.info/MavenShade</link>
  <id>http://www.bloggure.info/MavenShade</id>
  <published>2014-09-01T00:00:00+02:00</published>
  <updated>2014-09-01T00:00:00+02:00</updated>
  <author>
    <name></name>
    <uri>http://www.bloggure.info</uri>
    <email></email>
  </author>
  <content type="html">&lt;p&gt;Lors de nos développements, nous reposons beaucoup sur des projets externes qui nous fournissent énormément de services
utiles. Dans un récent projet, nous avons eu besoin de faire fonctionner Neo4j conjointement à ElasticSearch. Jusqu’ici,
aucun soucis n’est à déplorer, mais nous avions une exigence particulière : il fallait que l’application puisse
démarrer automatiquement un serveur Neo4J ainsi qu’un serveur ElasticSearch sur les postes de développements (ainsi que
pour les tests d’intégration).&lt;/p&gt;

&lt;h2 id=&quot;problème-existant&quot;&gt;Problème existant&lt;/h2&gt;

&lt;p&gt;Les deux outils que nous utilisons se basent sur Apache Lucene pour toute la partie indexation et accès aux données.
Mais, et c’est là que le problème se situe, ils n’utilisent pas les mêmes versions de Lucene.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;!-- Extrait du pom d&apos;ElasticSearch --&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.apache.lucene&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;lucene-core&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;4.9.0&amp;lt;/version&amp;gt;
    &amp;lt;scope&amp;gt;compile&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;hr /&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;!-- Extrait du pom de Neo4j --&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.apache.lucene&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;lucene-core&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;3.6.2&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;ElasticSearch utilise la version 4.9.0, alors que Neo4J utilise la version 3.6.2. Ainsi, en fonction du bon vouloir du
&lt;code&gt;Classloader&lt;/code&gt; qui sera utilisé par l’application, il se peut qu’ElasticSearch ou Neo4J refuse de fonctionner. La
difficulté pour comprendre et détecter le problème est qu’il se manifeste souvent par un obscur &lt;code&gt;NoClassDefFoundError&lt;/code&gt;
ou &lt;code&gt;NoSuchMethodError&lt;/code&gt; qui n’est pas des plus explicites (d’autant plus lorsque notre IDE nous montre une version qui
contient ledit symbole non trouvé).&lt;/p&gt;

&lt;h2 id=&quot;solution-de-contournement&quot;&gt;Solution de contournement&lt;/h2&gt;

&lt;p&gt;Le conflit est assez simple à contourner une fois qu’on a compris ce qui se passe. En fait, il y a deux classes portant
le même nom dans les classes chargées, par exemple &lt;code&gt;org.lucene.MaClass&lt;/code&gt;, l’une effaçant l’autre aux yeux du
&lt;code&gt;ClassLoader&lt;/code&gt;.
Une pratique courante est de renommer (ou relocate) le package de base d’une bibliothèque utilisée et de l’inclure dans
le fichier de package du projet, le plugin &lt;code&gt;maven-shade&lt;/code&gt; est conçu dans cette optique.
Le choix fait est de renommer la dépendance Lucene dans Neo4J pour notre part, ainsi, nous avons forké le projet et
configuré le plugin &lt;code&gt;shade&lt;/code&gt; pour qu’il inclue le contenu de la dépendance d’Apache Lucene et qu’il fasse le renommage de
&lt;code&gt;org.apache.lucene&lt;/code&gt; en &lt;code&gt;shaded.org.apache.lucene&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; &amp;lt;plugin&amp;gt;
        &amp;lt;groupId&amp;gt;org.apache.maven.plugins&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;maven-shade-plugin&amp;lt;/artifactId&amp;gt;
        &amp;lt;version&amp;gt;2.2&amp;lt;/version&amp;gt;
        &amp;lt;executions&amp;gt;
            &amp;lt;execution&amp;gt;
                &amp;lt;phase&amp;gt;package&amp;lt;/phase&amp;gt;
                &amp;lt;goals&amp;gt;
                    &amp;lt;goal&amp;gt;shade&amp;lt;/goal&amp;gt;
                &amp;lt;/goals&amp;gt;
                &amp;lt;configuration&amp;gt;
                    &amp;lt;createDependencyReducedPom&amp;gt;true&amp;lt;/createDependencyReducedPom&amp;gt;

                    &amp;lt;artifactSet&amp;gt;
                        &amp;lt;includes&amp;gt;
                            &amp;lt;include&amp;gt;org.apache.lucene:*&amp;lt;/include&amp;gt;
                        &amp;lt;/includes&amp;gt;
                    &amp;lt;/artifactSet&amp;gt;
                    &amp;lt;relocations&amp;gt;
                        &amp;lt;relocation&amp;gt;
                            &amp;lt;pattern&amp;gt;org.apache.lucene&amp;lt;/pattern&amp;gt;
                            &amp;lt;shadedPattern&amp;gt;shaded.org.apache.lucene&amp;lt;/shadedPattern&amp;gt;
                        &amp;lt;/relocation&amp;gt;
                    &amp;lt;/relocations&amp;gt;
                &amp;lt;/configuration&amp;gt;
            &amp;lt;/execution&amp;gt;
        &amp;lt;/executions&amp;gt;
  &amp;lt;/plugin&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;déploiement-et-nommage&quot;&gt;Déploiement et nommage&lt;/h2&gt;

&lt;p&gt;Pour ne pas polluer les dépôts, le numéro de version modifié a été postfixé par &lt;code&gt;-shaded&lt;/code&gt;. Le déploiement a été fait sur
un dépôt Maven qui est en fait un simple repository Github.
Le commit correspondant à cette modification &lt;a href=&quot;https://github.com/CedricGatay/neo4j/commit/452f58fca69ffe3b1d0eab6261b8342f8da95889&quot;&gt;est consultable
ici&lt;/a&gt;.&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://www.bloggure.info/MavenShade&quot;&gt;Résoudre les conflits de dépendances&lt;/a&gt; was originally published by  at &lt;a href=&quot;http://www.bloggure.info&quot;&gt;Bloggure&lt;/a&gt; on September 01, 2014.&lt;/p&gt;</content>
</entry>

</feed>
