<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mac</title>
    <description>The latest articles on DEV Community by Mac (@mcartoixa).</description>
    <link>https://dev.to/mcartoixa</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F377674%2F26fee8a8-43ee-4702-b963-e0f2a42b0500.png</url>
      <title>DEV Community: Mac</title>
      <link>https://dev.to/mcartoixa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mcartoixa"/>
    <language>en</language>
    <item>
      <title>The benefits of asynchronous communication</title>
      <dc:creator>Mac</dc:creator>
      <pubDate>Tue, 31 Mar 2026 04:39:00 +0000</pubDate>
      <link>https://dev.to/mcartoixa/the-benefits-of-asynchronous-communication-27g7</link>
      <guid>https://dev.to/mcartoixa/the-benefits-of-asynchronous-communication-27g7</guid>
      <description>&lt;p&gt;To me the main benefit of remote work (when done right) has always been the necessary focus on asynchronous communication. In these times when, &lt;a href="https://thehill.com/opinion/technology/5775420-remote-first-productivity-growth/" rel="noopener noreferrer"&gt;for some reason&lt;/a&gt;, remote gets a bad rap and &lt;a href="https://archieapp.co/blog/rto-companies-tracker/" rel="noopener noreferrer"&gt;companies left and right&lt;/a&gt; implement new Return To Office (RTO) policies, the old synchronous ways seem to get a big revival. "People need to talk to each other", "it's just common sense". The idea is simple: put everyone together in an open-plan office and collaboration will naturally improve. Right?&lt;/p&gt;

&lt;p&gt;Well, not quite, &lt;a href="https://github.com/mcartoixa/open-offices-bookmarks" rel="noopener noreferrer"&gt;according to overwhelming evidence&lt;/a&gt;. Asynchronous communication is more demanding and requires discipline. But it leads to better focus, better decisions, and better knowledge sharing, whether remote or in the office.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kill no flow&lt;/li&gt;
&lt;li&gt;Share knowledge, include everyone&lt;/li&gt;
&lt;li&gt;Have deeper interactions&lt;/li&gt;
&lt;li&gt;Make history&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Kill no flow
&lt;/h2&gt;

&lt;p&gt;Constant interruptions lead to &lt;a href="https://dl.acm.org/doi/10.1145/1357054.1357072" rel="noopener noreferrer"&gt;a higher workload, more stress, higher frustration, more time pressure, and effort&lt;/a&gt;. And they have &lt;a href="https://www.cogneurosociety.org/distractions_writing_foroughi/" rel="noopener noreferrer"&gt;a strong negative impact on work quality&lt;/a&gt;. It takes time and energy to get &lt;a href="https://oxford-review.com/blog-how-to-get-into-flow-at-work/" rel="noopener noreferrer"&gt;into a state of flow&lt;/a&gt;, and a very short interruption can ruin it all in an instant, requiring significant time to recover (also known as &lt;a href="https://en.wikipedia.org/wiki/Sune_Carlson" rel="noopener noreferrer"&gt;Carlson&lt;/a&gt;'s law). Faced with only the threat of interruptions, many people will tackle easier, shorter tasks instead of delving into the most complex ones.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.monkeyuser.com/2018/focus/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpqjlw100ed3d2ktgq3wx.png" alt="Focus" width="306" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Asynchronous communication allows everyone to manage their most precious resource more effectively: time. You can check your messages when you are done with your current task: no-one expects you to answer immediately. In exchange for this mutual respect, everyone has to make sure they check their messages regularly. On their own terms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Share knowledge, include everyone
&lt;/h2&gt;

&lt;p&gt;Synchronous communication is either very compartmentalized or very inefficient (and it can be both, of course). As teams grow, communication complexity increases dramatically (as described by &lt;a href="https://en.wikipedia.org/wiki/Metcalfe%27s_law" rel="noopener noreferrer"&gt;Metcalfe's law&lt;/a&gt;). In practice, this leads to a trade-off: either exclude people from conversations, or rely on more frequent all-hands meetings.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://getlighthouse.com/blog/developing-leaders-team-grows-big/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdev7adx1m2fywoe45s86.png" alt="Metcalfe's law" width="480" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most synchronous discussions have to happen in a silo: improvised meetings at the coffee machine, or even in the open space (commonly known as &lt;em&gt;interruptions&lt;/em&gt;). You will be de facto excluded if you happen to be in another meeting at that time, at home on a day off or just out for a 5-minute break. More meetings will be needed to keep everyone on the same page.&lt;/p&gt;

&lt;p&gt;Asynchronous communication can easily solve all those problems. If you work in the open by default, in public spaces accessible by anyone, then the whole team can be included at all times, if only to gain the knowledge that there is something going on. Of course not everyone will follow every discussion that happen in the public square, but everyone will be able to make their own choices of what to follow or not. Even if you take some days off, you will have the opportunity to catch up with what happened while you were away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Have deeper interactions
&lt;/h2&gt;

&lt;p&gt;Good async communication requires providing enough context upfront. The goal is to avoid unnecessary back-and-forth, since replies may not come immediately. As &lt;a href="https://sahillavingia.com/" rel="noopener noreferrer"&gt;Sahil Lavingia&lt;/a&gt;, founder at Gumroad, &lt;a href="https://x.com/shl/status/1222545251534925824" rel="noopener noreferrer"&gt;wrote about async&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All communication is thoughtful. Because nothing is urgent (unless the site is down), comments are made after mindful processing and never in real-time. There's no drama.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One immediate benefit is &lt;a href="https://en.wikipedia.org/wiki/Rubber_duck_debugging" rel="noopener noreferrer"&gt;the rubberducking effect&lt;/a&gt;: while it may cost me more time to write a comprehensible question than to interrupt the coworkers that happen to be next to me right now, in many cases I may not even need to send the message. The mere fact of synthesizing a proper context for my message can provide me with the clarity I need to answer my own question.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.smbc-comics.com/comic/the-rubber-duck-method" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl2cvypxd0uu1s0ubw1j7.png" alt="The " width="320" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More importantly, effective interruptions would require that all participants were able to mobilize all their knowledge on the spot, on demand, unprepared. This is obviously rarely the case, and improvised discussions very often lead to some variation of "I'm not sure" or "Let me check". Asynchronous communication avoids all of this: I can take the time to come back with a comprehensive answer everytime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make history
&lt;/h2&gt;

&lt;p&gt;Writing things down is &lt;a href="https://en.wikipedia.org/wiki/Recorded_history#Prehistory" rel="noopener noreferrer"&gt;what turns activity into history&lt;/a&gt;, and history into organizational learning. When communication is written and preserved, your organization gains the ability to learn from its past.&lt;/p&gt;

&lt;p&gt;In many synchronous companies, most of the organizational knowledge lives in the heads of a few senior employees that happened to be present when decisions were made (aka &lt;a href="https://en.wikipedia.org/wiki/Tribal_knowledge" rel="noopener noreferrer"&gt;tribal knowledge&lt;/a&gt;). But the older the knowledge, the more fuzzy it gets: it is just human nature.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dilbert.com/strip/2010-12-02" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv3xm1g6i8xtgeit3k1wa.png" alt="Tribal knowledge" width="640" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some companies may have the discipline to commit their discussions to writing, but this is always done after the facts, and usually in a hurry (think of meeting notes for instance). People can refer to those documents when they want to understand the context that led to a specific decision, which will be the first time most of them are thoroughly read and they will only find them obtuse and difficult to understand, often by lack of context.&lt;/p&gt;

&lt;p&gt;Asynchronous communication relies on written communication that is meant to be read every time (please do not do voice messages 🙃), and ambiguities rarely make it to the final documents.&lt;/p&gt;

&lt;p&gt;Async is not just a remote work practice, it is a better default for modern knowledge work. It requires effort and discipline, but the payoff is a calmer, more thoughtful, and an overall more effective way to collaborate.&lt;/p&gt;

</description>
      <category>organization</category>
      <category>communication</category>
      <category>asynchronous</category>
    </item>
    <item>
      <title>How to resize the scrollbars in Firefox</title>
      <dc:creator>Mac</dc:creator>
      <pubDate>Sat, 07 Feb 2026 06:39:00 +0000</pubDate>
      <link>https://dev.to/mcartoixa/how-to-resize-the-scrollbars-in-firefox-mfe</link>
      <guid>https://dev.to/mcartoixa/how-to-resize-the-scrollbars-in-firefox-mfe</guid>
      <description>&lt;p&gt;&lt;a href="https://discourse.gnome.org/t/whats-with-the-trend-of-tiny-and-hidden-scrollbars/18696" rel="noopener noreferrer"&gt;What’s with the trend of tiny and hidden scrollbars?&lt;/a&gt; You can hear some people of all stripes, presumably old enough to remember a time when accessibility concerns mattered to the point that they were solved at the OS level complain about the size of scrollbars nowadays (wherever you can still find them): on &lt;a href="https://learn.microsoft.com/en-us/answers/questions/4018586/why-is-the-scroll-bar-in-windows-11-so-narrow-in-w" rel="noopener noreferrer"&gt;Windows&lt;/a&gt;, on &lt;a href="https://forums.linuxmint.com/viewtopic.php?t=438290" rel="noopener noreferrer"&gt;Linux&lt;/a&gt;, on &lt;a href="https://apple.stackexchange.com/questions/467135/can-i-enlarge-the-system-scrollbar-size" rel="noopener noreferrer"&gt;MacOS&lt;/a&gt;, on &lt;a href="https://support.google.com/chrome/thread/335607960/scroll-bar-is-too-thin-it-s-hard-to-click-drag-it" rel="noopener noreferrer"&gt;Chrome&lt;/a&gt;, on &lt;a href="https://connect.mozilla.org/t5/ideas/scroll-bar-width-and-customization-options/idi-p/5320/page/6" rel="noopener noreferrer"&gt;Firefox&lt;/a&gt;...&lt;/p&gt;

&lt;p&gt;People are supposedly only using scroll wheels or trackpads nowadays, so scrollbars are seen as a distraction. Maybe it's just my age showing but I still find scrollbars useful sometimes, if only as visual indicators of where I stand in a large article or a big document. And I usually find them more efficient when I only want a fine tweak of my position in a page or, to the contrary, when I want to get back quickly to the top of an infinite scrolling page.&lt;/p&gt;

&lt;p&gt;Anyway if you think like me that you could use bigger scrollbars on Firefox, open &lt;a href="https://support.mozilla.org/en-US/kb/about-config-editor-firefox" rel="noopener noreferrer"&gt;the configuration editor&lt;/a&gt; (&lt;em&gt;about:config&lt;/em&gt;) and set the following values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;widget.non-native-theme.scrollbar.size.override 16
widget.non-native-theme.scrollbar.style 4
widget.non-native-theme.win.scrollbar.use-system-size false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Style 4 represents Windows 10 scrollbars, which IMHO are the most adequate for most situations, but &lt;a href="https://searchfox.org/firefox-main/rev/997d55938096e03a72bfedb57279d42a62cd1467/modules/libpref/init/StaticPrefList.yaml#19102" rel="noopener noreferrer"&gt;you can explore other options&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;No need to restart your browser. 😊&lt;/p&gt;

</description>
      <category>firefox</category>
      <category>windows</category>
      <category>ui</category>
    </item>
    <item>
      <title>GPG keys are no longer shared with development containers on Windows by default</title>
      <dc:creator>Mac</dc:creator>
      <pubDate>Fri, 16 Feb 2024 14:51:00 +0000</pubDate>
      <link>https://dev.to/mcartoixa/gpg-keys-are-no-longer-shared-with-development-containers-on-windows-by-default-54k6</link>
      <guid>https://dev.to/mcartoixa/gpg-keys-are-no-longer-shared-with-development-containers-on-windows-by-default-54k6</guid>
      <description>&lt;p&gt;If you are into the habit of &lt;a href="https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work" rel="noopener noreferrer"&gt;signing your Git commits&lt;/a&gt;, the configuration on Windows has always been pretty straightforward: install &lt;a href="https://www.gpg4win.org/" rel="noopener noreferrer"&gt;Gpg4win&lt;/a&gt;, set up your keys in the graphical interface and you’re pretty much done. And sharing those keys inside a development container with Visual Studio Code was even easier: &lt;a href="https://code.visualstudio.com/remote/advancedcontainers/sharing-git-credentials" rel="noopener noreferrer"&gt;nothing to do&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Well, that was the case until version 4.2 of Gpg4win. I have been banging my head for days now on why this wouldn’t work on a new machine of mine… As it turns out, the way keys are stored by Gpg4win by default has changed: &lt;a href="https://www.gpg4win.org/version4.2.html" rel="noopener noreferrer"&gt;it now uses &lt;strong&gt;keyboxd&lt;/strong&gt;&lt;/a&gt; and Visual Studio Code doesn’t know how to handle that. Yet?&lt;/p&gt;

&lt;p&gt;The old behaviour can simply be restored by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gpg-disable-keyboxd.bat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done. You may have to recreate your containers for the keys to be picked up in your container.&lt;/p&gt;

</description>
      <category>vscode</category>
      <category>gpg</category>
      <category>windows</category>
    </item>
    <item>
      <title>DoH must be set up for HTTPS navigation in private networks</title>
      <dc:creator>Mac</dc:creator>
      <pubDate>Sun, 04 Feb 2024 09:08:00 +0000</pubDate>
      <link>https://dev.to/mcartoixa/doh-must-be-set-up-for-https-navigation-in-private-networks-2o0l</link>
      <guid>https://dev.to/mcartoixa/doh-must-be-set-up-for-https-navigation-in-private-networks-2o0l</guid>
      <description>&lt;p&gt;I love computers. I always have. And as a professional developer I always was a little bit jealous of my fellow systems administrators who were in charge of dozens of servers and desktops (and sometimes much more). Active Directory was a misterious world where seemed to lie an unreachable and absolute power. So of course, as soon a I could I set up a decent homelab for myself: DIY servers, NAS, 10Gb network, Hyper-V VMs, Active Directory… I also plunged into the wild world of domotics, starting &lt;a href="https://www.domoticz.com/" rel="noopener noreferrer"&gt;with Domoticz&lt;/a&gt; and slowly migrating &lt;a href="https://www.home-assistant.io/" rel="noopener noreferrer"&gt;to Home Assistant&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Home automation applications have special DNS requirements, though, because an application that is installed on your phone must resolve to a different address whether you’re at home in your private network or out in the wild. My internal needs are easily fulfilled by the DNS Server which is linked to my Active Directory. In parallel, I bought the same domain name on a DNS provider that redirects every request to my private router, which dispatches requests to the proper server according to the specific TCP/IP port of the application. Works alright enough. &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9q7lkbq63snhrn2b3bb5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9q7lkbq63snhrn2b3bb5.png" alt="HTTP setup" width="713" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But surely nowadays the least we require is an HTTPS connection, especially if we are going to send passwords over the Internet to applications that could entirely control our homes. And the management of TCP/IP ports become a tedious affair to manage when the number of applications grow. So the plan is first to set up a private Certificate Authority that can generate certificates for my applications: I should be the only one using those applications, so I can simply distribute my own root certificates to the devices that will use it (and Active Directory makes it very easy for Windows systems). One benefit over &lt;a href="https://letsencrypt.org/" rel="noopener noreferrer"&gt;Let’s Encrypt&lt;/a&gt; for instance is that it will avoid publicizing the names I use for my applications (cf. &lt;a href="https://certificate.transparency.dev/" rel="noopener noreferrer"&gt;Certificate Transparency&lt;/a&gt;). A small Linux VM can easily be set up for this purpose only, that provides a &lt;a href="https://smallstep.com/docs/step-ca/" rel="noopener noreferrer"&gt;&lt;code&gt;step-ca&lt;/code&gt; server&lt;/a&gt;. Easy peasy. Then I set up a reverse proxy application that can handle TLS negociations and requests forwarding based on the domain name of the request. Seems straightforward enough. &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd8bd3pf1xxcsyzzlul7d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd8bd3pf1xxcsyzzlul7d.png" alt="HTTPS setup" width="703" height="676"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Except that it does not work. The reason being that modern browsers will use &lt;a href="https://en.wikipedia.org/wiki/DNS_over_HTTPS" rel="noopener noreferrer"&gt;DNS over HTTPS&lt;/a&gt; (DoH) to resolve your addresses. At least it is my experience, though I could not find a definitive reference for this behaviour. So unless your private DNS server is able to handle DoH a public server that can will be used instead. As a result your internal clients will cease to be fed with your private adresses and will try to use the public adresses that are sent to them (which in that case should be the public address of your router). I learned this the hard way…&lt;/p&gt;

&lt;p&gt;The solution here is to &lt;strong&gt;set up DoH internally&lt;/strong&gt; , which my DNS Server is currently unable to handle. But I could set up another small Linux VM running &lt;a href="https://dnsdist.org/" rel="noopener noreferrer"&gt;dns-dist&lt;/a&gt;, that can handle DoH (with certificates generated by my private Certificate Authority) and then defer actual name resolution to my private DNS server. The new DNS server should then be used all over the private network. For good measure I also deployed this new DNS Server address to all my Windows clients using the &lt;a href="https://learn.microsoft.com/en-us/powershell/module/dnsclient/add-dnsclientdohserveraddress" rel="noopener noreferrer"&gt;&lt;code&gt;Add-DnsClientDohServerAddress&lt;/code&gt; cmdlet&lt;/a&gt; in an Active Directory startup script. As I understand it, it will then be automatically used by every system that is in the same IP subnet as the new private DNS Server. It will be picked up by the browsers as well, so now internal clients can properly resolve private addresses! &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhavnbo919dyoz3ye3ety.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhavnbo919dyoz3ye3ety.png" alt="HTTPS setup" width="703" height="676"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>infrastructure</category>
      <category>dns</category>
    </item>
    <item>
      <title>apt commands fail (Connection timed out) in Debian based containers on Windows</title>
      <dc:creator>Mac</dc:creator>
      <pubDate>Sat, 03 Feb 2024 17:23:00 +0000</pubDate>
      <link>https://dev.to/mcartoixa/apt-commands-fail-connection-timed-out-in-debian-based-containers-on-windows-3d8l</link>
      <guid>https://dev.to/mcartoixa/apt-commands-fail-connection-timed-out-in-debian-based-containers-on-windows-3d8l</guid>
      <description>&lt;p&gt;The last few years have given me several opportunities to discover the Ruby language, and its ecosystem. I am very much a .NET guy, and I had wondered for a long time what the hype was all about. Now I can see how it inspired many modern platforms (like node.js or .NET &lt;del&gt;Core&lt;/del&gt; ), but boy is the operations side of it hard! I am maintaining this very blog &lt;a href="https://pages.github.com/" rel="noopener noreferrer"&gt;on GitHub Pages&lt;/a&gt;, which is based &lt;a href="https://jekyllrb.com/" rel="noopener noreferrer"&gt;on Jekyll&lt;/a&gt;, and even a simple usage like this requires &lt;a href="https://containers.dev/" rel="noopener noreferrer"&gt;a development container&lt;/a&gt;. Especially as a Windows guy, although &lt;a href="https://talk.jekyllrb.com/t/new-video-develop-jekyll-or-github-pages-using-docker-containers/7199" rel="noopener noreferrer"&gt;managing Ruby dependencies on Linux is a mess&lt;/a&gt; by itself.&lt;/p&gt;

&lt;p&gt;Anyway, I am not creating those containers every day, but I have had a lot of trouble lately trying to create new ones, that happen to be based on Debian images (Buster and Bullseye), like &lt;a href="https://hub.docker.com/layers/library/ruby/2.7.8/images/sha256-f4420f957a9b4fae91a8d8c7fab8ba43e5a76bd640c87cb2b20ef669039e1319?context=explore" rel="noopener noreferrer"&gt;ruby:2.7.8&lt;/a&gt; for instance. Part of the container creation involves running &lt;code&gt;apt-get update &amp;amp;&amp;amp; apt-get upgrade&lt;/code&gt; to have an image that is already up to date. These commands would invariably fail, preventing me (or VS Code for that matter) to create the container. The errors showed many &lt;code&gt;Connection timed out&lt;/code&gt; while trying to fetch packages from &lt;em&gt;deb.debian.org&lt;/em&gt; or &lt;em&gt;security.debian.org&lt;/em&gt;… And it happened with different Debian versions, on different (Windows) hosts.&lt;/p&gt;

&lt;p&gt;Part of the debugging process was trying to reach mirrors of the Debian repositories, &lt;a href="https://manpages.debian.org/testing/netselect-apt/netselect-apt.1.en.html" rel="noopener noreferrer"&gt;using &lt;code&gt;netselect-apt&lt;/code&gt;&lt;/a&gt;. But the same thing happened with the mirrors. And the first step of using &lt;code&gt;netselect-api&lt;/code&gt; was installing it, by downloading the package directly with &lt;code&gt;wget&lt;/code&gt; (can’t use &lt;code&gt;apt&lt;/code&gt; there 😁), thus proving that the container had proper access to Internet…&lt;/p&gt;

&lt;p&gt;Long story short, it seems that under my conditions (Debian running in a container in Docker Desktop on Windows using WSL), and for some reason, the connections established by &lt;code&gt;apt&lt;/code&gt; are quite unstable. The trick was to configure &lt;code&gt;apt&lt;/code&gt; so that it retries many times before giving up: I added &lt;code&gt;echo 'Acquire::Retries "100";' &amp;gt; /etc/apt/apt.conf.d/99custom&lt;/code&gt; as the first command in all my &lt;code&gt;Dockerfile&lt;/code&gt; descriptions.&lt;/p&gt;

&lt;p&gt;That’s it. I can create brand new Debian based development containers again. 🥳&lt;/p&gt;

</description>
      <category>docker</category>
      <category>windows</category>
      <category>debian</category>
    </item>
    <item>
      <title>Continuous Integration for .NET (Core)</title>
      <dc:creator>Mac</dc:creator>
      <pubDate>Thu, 28 Jan 2021 19:30:00 +0000</pubDate>
      <link>https://dev.to/mcartoixa/continuous-integration-for-net-core-1fk4</link>
      <guid>https://dev.to/mcartoixa/continuous-integration-for-net-core-1fk4</guid>
      <description>&lt;p&gt;A lot of work has been made in the last few years (almost 5 now) to revamp the whole .NET Platform and lead it confidently into this new decade. Going through &lt;a href="https://en.wikipedia.org/wiki/.NET_Core" rel="noopener noreferrer"&gt;.NET Core&lt;/a&gt;, .NET is now free, open-source and multiplatform. Sign of the times, the console addicts can now take advantage of &lt;a href="https://docs.microsoft.com/en-us/dotnet/core/tools/" rel="noopener noreferrer"&gt;the .NET CLI&lt;/a&gt; while everyone can enjoy the still evolving best parts of the eco-system: the C# language and MSBuild (amongst other things). So now is a good opportunity as ever to revisit &lt;a href="https://dev.to/mcartoixa/continuous-integration-for-the-net-framework-1hc6"&gt;my practices of Continuous Integration for the .NET Framework&lt;/a&gt; as well.&lt;/p&gt;

&lt;p&gt;This might look at first as easy as a combination of various &lt;code&gt;dotnet build&lt;/code&gt;, &lt;code&gt;dotnet test&lt;/code&gt;, &lt;code&gt;dotnet pack&lt;/code&gt; and/or &lt;code&gt;dotnet publish&lt;/code&gt; commands. This might work for some (and if it does then this is very fine), but to me this comes too close to breaking &lt;a href="https://dev.to/mcartoixa/my-take-on-continuous-integration-3gep#build-in-1-step"&gt;my &lt;em&gt;Build in 1 step&lt;/em&gt; rule&lt;/a&gt; and I think I will keep on basing my builds on… MSBuild:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;all those &lt;code&gt;dotnet&lt;/code&gt; commands have to be coordinated in some way, and I also want to be able to execute the build locally. If we are to create cross-platform scripts then our options become quite limited outside of MSBuild.&lt;/li&gt;
&lt;li&gt;MSBuild has the perfect logging infrastructure that allows to have both an understandable console output &lt;strong&gt;and&lt;/strong&gt; a complete file log that can prove very valuable when things go wrong. Oh, and &lt;a href="https://msbuildlog.com/" rel="noopener noreferrer"&gt;the Structured Log Viewer&lt;/a&gt; is just an amazing piece of software that has no equivalent that I know of on other technologies.&lt;/li&gt;
&lt;li&gt;most of the &lt;code&gt;dotnet&lt;/code&gt; commands are just wrappers around MSBuild targets anyway…&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A simple project
&lt;/h2&gt;

&lt;p&gt;If you want to see these principles in action please go and check &lt;a href="https://github.com/mcartoixa/Vigicrues.Client" rel="noopener noreferrer"&gt;Vigicrues.Client&lt;/a&gt;, a .NET wrapper for &lt;a href="https://www.vigicrues.gouv.fr/services/1/" rel="noopener noreferrer"&gt;the Vigicrues API&lt;/a&gt; which reports information about flooding hazards in France.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/mcartoixa" rel="noopener noreferrer"&gt;
        mcartoixa
      &lt;/a&gt; / &lt;a href="https://github.com/mcartoixa/Vigicrues.Client" rel="noopener noreferrer"&gt;
        Vigicrues.Client
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      .NET client for the Vigicrues API
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;What I need for Continuous Integration are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
A solution file (&lt;code&gt;Vigicrues.Client.sln&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
A build file (&lt;code&gt;Vigicrues.Client.proj&lt;/code&gt;), written in MSBuild.&lt;/li&gt;
&lt;li&gt;
A script file (&lt;code&gt;build.bat&lt;/code&gt;) that helps executing the build file locally.&lt;/li&gt;
&lt;li&gt;
A CI configuration file (&lt;code&gt;appveyor.yml&lt;/code&gt;). The rest of the project is either code of infrastructure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The solution
&lt;/h3&gt;

&lt;p&gt;Solutions are (&lt;a href="https://dev.to/mcartoixa/continuous-integration-for-the-net-framework-1hc6"&gt;still&lt;/a&gt;) &lt;a href="https://docs.microsoft.com/en-us/visualstudio/ide/" rel="noopener noreferrer"&gt;Visual Studio&lt;/a&gt; &lt;em&gt;speak&lt;/em&gt; for &lt;a href="https://docs.microsoft.com/en-us/visualstudio/get-started/tutorial-projects-solutions" rel="noopener noreferrer"&gt;a collection of related projects&lt;/a&gt;. You can load them (with Visual Studio), and you can also build them (with &lt;a href="https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild" rel="noopener noreferrer"&gt;MSBuild&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;In this context, solutions have 2 purposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;they are an entry point for developers to edit the code. Specifically the &lt;a href="https://github.com/mcartoixa/Vigicrues.Client/blob/v0.1/Vigicrues.Client-dev.sln" rel="noopener noreferrer"&gt;&lt;code&gt;Vigicrues.Client-dev.sln&lt;/code&gt;&lt;/a&gt; solution should be used for that.&lt;/li&gt;
&lt;li&gt;they are an entry point for the build scripts to generate a deployable package. This is &lt;a href="https://github.com/mcartoixa/Vigicrues.Client/blob/v0.1/Vigicrues.Client.sln" rel="noopener noreferrer"&gt;&lt;code&gt;Vigicrues.Client.sln&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the full .NET Framework we also had a solution dedicated to automated tests but this could not work here &lt;a href="https://github.com/Microsoft/vstest/issues/411" rel="noopener noreferrer"&gt;because of a bug in the test framework&lt;/a&gt;. More on that later.&lt;/p&gt;

&lt;p&gt;The main point here is that developers can still use their usual toolkit to develop (Visual Studio in this case).&lt;/p&gt;

&lt;h3&gt;
  
  
  The build file
&lt;/h3&gt;

&lt;p&gt;The architecture of the build is quite simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;execute the MSBuild equivalent of the various &lt;code&gt;dotnet&lt;/code&gt; commands on the &lt;a href="https://github.com/mcartoixa/Vigicrues.Client/blob/v0.1/Vigicrues.Client.sln" rel="noopener noreferrer"&gt;&lt;code&gt;Vigicrues.Client.sln&lt;/code&gt;&lt;/a&gt; solution.&lt;/li&gt;
&lt;li&gt;add a sprinkle of execution of various external dependencies and tools (like &lt;a href="https://github.com/AlDanial/cloc" rel="noopener noreferrer"&gt;the cloc utility&lt;/a&gt;) to make the whole thing more interesting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The gist of the &lt;a href="https://github.com/mcartoixa/Vigicrues.Client/blob/v0.1/Vigicrues.Client.proj" rel="noopener noreferrer"&gt;&lt;code&gt;Vigicrues.Client.proj&lt;/code&gt;&lt;/a&gt; is very simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;DefaultTargets=&lt;/span&gt;&lt;span class="s"&gt;"Rebuild"&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.microsoft.com/developer/msbuild/2003"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Projects&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Vigicrues.Client.sln"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;Import&lt;/span&gt; &lt;span class="na"&gt;Project=&lt;/span&gt;&lt;span class="s"&gt;"$(MSBuildProjectDirectory)\build\Common.targets"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We define a list of solutions to act on, and there is only one in this case. The rest is imported from another MSBuild file (&lt;a href="https://github.com/mcartoixa/Vigicrues.Client/blob/v0.1/build/Common.targets" rel="noopener noreferrer"&gt;&lt;code&gt;build\Common.targets&lt;/code&gt;&lt;/a&gt;) which is quite specific at this time but may evolve into a generic reusable build file over time. This is where define my own standard targets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Clean&lt;/em&gt;: cleans the build. 

&lt;ul&gt;
&lt;li&gt;This is usually a simple matter of deleting the &lt;code&gt;tmp\&lt;/code&gt; folder, as every other target generates its outputs there.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;em&gt;Compile&lt;/em&gt;: compiles the specified solutions.&lt;/li&gt;

&lt;li&gt;

&lt;em&gt;Test&lt;/em&gt;: compiles the tests and executes them. 

&lt;ul&gt;
&lt;li&gt;Also performs code coverage analysis and generates a human readable report about it.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;em&gt;Analyze&lt;/em&gt;: performs some analysis on the project. 

&lt;ul&gt;
&lt;li&gt;Right now it gathers statistics using &lt;a href="https://github.com/AlDanial/cloc" rel="noopener noreferrer"&gt;the cloc utility&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;em&gt;Package&lt;/em&gt;: generates a deployable package (in the &lt;code&gt;tmp\out\bin&lt;/code&gt; folder). In our case this will be a NuGet file (NuGet being &lt;a href="https://www.nuget.org/" rel="noopener noreferrer"&gt;the dependency manager of choice on the .NET platform&lt;/a&gt;).&lt;/li&gt;

&lt;li&gt;

&lt;em&gt;Build&lt;/em&gt;: shortcut for the combination of &lt;em&gt;Compile&lt;/em&gt;, &lt;em&gt;Test&lt;/em&gt; and &lt;em&gt;Analyze&lt;/em&gt;.&lt;/li&gt;

&lt;li&gt;

&lt;em&gt;Rebuild&lt;/em&gt;: shortcut for the combination of &lt;em&gt;Clean&lt;/em&gt; and &lt;em&gt;Build&lt;/em&gt;.&lt;/li&gt;

&lt;li&gt;

&lt;em&gt;Release&lt;/em&gt;: shortcut for the combination of &lt;em&gt;Clean&lt;/em&gt;, &lt;em&gt;Build&lt;/em&gt; and &lt;em&gt;Package&lt;/em&gt;.&lt;/li&gt;

&lt;/ul&gt;

&lt;h4&gt;
  
  
  Clean
&lt;/h4&gt;

&lt;p&gt;As planned the &lt;em&gt;Clean&lt;/em&gt; target is quite simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"Clean"&lt;/span&gt; &lt;span class="na"&gt;DependsOnTargets=&lt;/span&gt;&lt;span class="s"&gt;"CleanDirectories"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"CleanDirectories"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;RemoveDir&lt;/span&gt; &lt;span class="na"&gt;Directories=&lt;/span&gt;&lt;span class="s"&gt;"tmp\"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is actually a bit more to it to take &lt;a href="https://github.com/dotnet/sdk/issues/3485" rel="noopener noreferrer"&gt;bug #3485&lt;/a&gt; into account but this is a detail for this article.&lt;/p&gt;

&lt;h4&gt;
  
  
  Compile
&lt;/h4&gt;

&lt;p&gt;The &lt;em&gt;Compile&lt;/em&gt; target consists simply of calling MSBuild on the target solutions (instead of &lt;code&gt;dotnet build&lt;/code&gt; which actually does the same thing):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"Compile"&lt;/span&gt; &lt;span class="na"&gt;DependsOnTargets=&lt;/span&gt;&lt;span class="s"&gt;"CompileProject"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"CompileProject"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;_BaseOutputPath&amp;gt;&lt;/span&gt;tmp\bin\%(Projects.FileName)\&lt;span class="nt"&gt;&amp;lt;/_BaseOutputPath&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;_BaseIntermediateOutputPath&amp;gt;&lt;/span&gt;tmp\obj\bin\%(Projects.FileName)\&lt;span class="nt"&gt;&amp;lt;/_BaseIntermediateOutputPath&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;MSBuild&lt;/span&gt;
    &lt;span class="na"&gt;Projects=&lt;/span&gt;&lt;span class="s"&gt;"%(Projects.Identity)"&lt;/span&gt;
    &lt;span class="na"&gt;RebaseOutputs=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt;
    &lt;span class="na"&gt;Properties=&lt;/span&gt;&lt;span class="s"&gt;"Configuration=%(Projects.Configuration);Platform=%(Projects.Platform);BaseOutputPath=$(_BaseOutputPath);BaseIntermediateOutputPath=$(_BaseIntermediateOutputPath);%(Projects.Properties)"&lt;/span&gt;
    &lt;span class="na"&gt;Targets=&lt;/span&gt;&lt;span class="s"&gt;"Restore;Build"&lt;/span&gt;
  &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only trick here is to redirect the outputs (including intermediate files) into subfolders of the &lt;code&gt;tmp\&lt;/code&gt; folder. This is what made the &lt;em&gt;Clean&lt;/em&gt; target so easy to write.&lt;/p&gt;

&lt;h4&gt;
  
  
  Test
&lt;/h4&gt;

&lt;p&gt;At the core of the &lt;em&gt;Test&lt;/em&gt; target is another call to MSBuild (instead of &lt;code&gt;dotnet test&lt;/code&gt;), very much like above. Specificities include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;not using solutions here but finding projects which name end with &lt;code&gt;.Tests.csproj&lt;/code&gt;. This is partially because of &lt;a href="https://github.com/Microsoft/vstest/issues/411" rel="noopener noreferrer"&gt;bug #411&lt;/a&gt; which prevented the use of solutions in the execution of tests. It may have been fixed now, but in the meantime I got used to not having a dedicated solution for tests…&lt;/li&gt;
&lt;li&gt;not redirecting intermediates, because of &lt;a href="https://github.com/dotnet/sdk/issues/3485" rel="noopener noreferrer"&gt;bug #3485&lt;/a&gt; again.&lt;/li&gt;
&lt;li&gt;adding custom properties to the build (like &lt;code&gt;dotnet test&lt;/code&gt; would). For instance: 

&lt;ul&gt;
&lt;li&gt;I use &lt;a href="https://xunit.net/" rel="noopener noreferrer"&gt;xUnit&lt;/a&gt; for my tests, so I will configure &lt;a href="https://github.com/spekt/xunit.testlogger" rel="noopener noreferrer"&gt;the xUnit Test Logger&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;I can dynamically add another logger by using the &lt;code&gt;%VSTEST_LOGGER%&lt;/code&gt; environment variable (cf. the CI configuration file).&lt;/li&gt;
&lt;li&gt;I activate &lt;a href="https://docs.microsoft.com/en-us/dotnet/core/testing/unit-testing-code-coverage" rel="noopener noreferrer"&gt;code coverage collection&lt;/a&gt; (&lt;a href="https://github.com/coverlet-coverage/coverlet" rel="noopener noreferrer"&gt;Coverlet&lt;/a&gt; is already a dependency of my tests).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;lastly, generated XML reports are copied under the &lt;code&gt;tmp&amp;lt;/code&amp;gt; folder where every report is expected.&lt;/code&gt;
&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;And this gives someting like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"Test"&lt;/span&gt; &lt;span class="na"&gt;DependsOnTargets=&lt;/span&gt;&lt;span class="s"&gt;"TestProject"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;TestProjects&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"*\*.Tests.csproj"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"TestProject"&lt;/span&gt;
  &lt;span class="na"&gt;Outputs=&lt;/span&gt;&lt;span class="s"&gt;"tmp\tst\results\%(TestProjects.Filename)\TestResults.xml"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;_VsTestLoggers&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"xunit"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;_VsTestLoggers&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(VSTEST_LOGGER)' != ''"&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"$(VSTEST_LOGGER)"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;_BaseOutputPath&amp;gt;&lt;/span&gt;tmp\tst\bin\%(TestProjects.Filename)\&lt;span class="nt"&gt;&amp;lt;/_BaseOutputPath&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;_VSTestResultsPath&amp;gt;&lt;/span&gt;tmp\tst\results\%(TestProjects.Filename)\&lt;span class="nt"&gt;&amp;lt;/_VSTestResultsPath&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;_VsTestLogger&amp;gt;&lt;/span&gt;@(_VsTestLoggers-&amp;gt;'&lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;%(Identity)&lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;')&lt;span class="nt"&gt;&amp;lt;/_VsTestLogger&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;_TestProperties&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"IsTestProject=True"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;_TestProperties&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"VSTestNoLogo=True"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;_TestProperties&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"VSTestNoBuild=False"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;_TestProperties&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"VSTestBlame=True"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;_TestProperties&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"VSTestVerbosity=normal"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;_TestProperties&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"VSTestResultsDirectory=$(_VSTestResultsPath)"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;_TestProperties&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"VSTestTestAdapterPath=$(MSBuildProjectDirectory)"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;_TestProperties&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"VSTestCollect=XPlat Code Coverage"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;_TestProperties&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"VSTestLogger=$(_VsTestLogger)"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;RemoveDir&lt;/span&gt; &lt;span class="na"&gt;Directories=&lt;/span&gt;&lt;span class="s"&gt;"$(_VSTestResultsPath)"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;MSBuild&lt;/span&gt;
    &lt;span class="na"&gt;Projects=&lt;/span&gt;&lt;span class="s"&gt;"%(TestProjects.Identity)"&lt;/span&gt;
    &lt;span class="na"&gt;RebaseOutputs=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt;
    &lt;span class="na"&gt;Properties=&lt;/span&gt;&lt;span class="s"&gt;"Configuration=Release;BaseOutputPath=$(_BaseOutputPath);@(_TestProperties);%(Projects.Properties)"&lt;/span&gt;
    &lt;span class="na"&gt;Targets=&lt;/span&gt;&lt;span class="s"&gt;"Restore;VSTest"&lt;/span&gt;
  &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Copy&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"Exists('$(_VSTestResultsPath)TestResults.xml')"&lt;/span&gt; &lt;span class="na"&gt;SourceFiles=&lt;/span&gt;&lt;span class="s"&gt;"$(_VSTestResultsPath)TestResults.xml"&lt;/span&gt; &lt;span class="na"&gt;DestinationFiles=&lt;/span&gt;&lt;span class="s"&gt;"tmp\%(TestProjects.Filename)-xunit-results.xml"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hey, but what about the actual code coverage? It is collected but not exploited yet: we will use &lt;a href="https://danielpalme.github.io/ReportGenerator/" rel="noopener noreferrer"&gt;ReportGenerator&lt;/a&gt; for this. This is a NuGet dependency that we can define and restore in the project file itself by defining the right properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;RestoreGraphProjectInput&amp;gt;&lt;/span&gt;$(MSBuildProjectFullPath)&lt;span class="nt"&gt;&amp;lt;/RestoreGraphProjectInput&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;netstandard2.1&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;MSBuildProjectExtensionsPath&amp;gt;&lt;/span&gt;tmp\obj\&lt;span class="nt"&gt;&amp;lt;/MSBuildProjectExtensionsPath&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"ReportGenerator"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"4.8.4"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;Import&lt;/span&gt; &lt;span class="na"&gt;Project=&lt;/span&gt;&lt;span class="s"&gt;"$(MSBuildToolsPath)\NuGet.targets"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"Prepare"&lt;/span&gt; &lt;span class="na"&gt;DependsOnTargets=&lt;/span&gt;&lt;span class="s"&gt;"Restore"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;MakeDir&lt;/span&gt; &lt;span class="na"&gt;Directories=&lt;/span&gt;&lt;span class="s"&gt;"$(TmpOutputPath)"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;Import&lt;/span&gt; &lt;span class="na"&gt;Project=&lt;/span&gt;&lt;span class="s"&gt;"$(MSBuildProjectExtensionsPath)$(MSBuildProjectFile).nuget.g.props"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Import&lt;/span&gt; &lt;span class="na"&gt;Project=&lt;/span&gt;&lt;span class="s"&gt;"$(MSBuildProjectExtensionsPath)$(MSBuildProjectFile).nuget.g.targets"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can use &lt;em&gt;ReportGenerator&lt;/em&gt; on all the code coverage results and generate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a nice HTML report for humans to consume.&lt;/li&gt;
&lt;li&gt;a XML report, under the &lt;code&gt;tmp\&lt;/code&gt; directory along withy other reports.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"GenerateTestReports"&lt;/span&gt;
  &lt;span class="na"&gt;Returns=&lt;/span&gt;&lt;span class="s"&gt;"@(CoverageResults)"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;CoverageResults&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"tmp\tst\results\**\coverage.cobertura.xml"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"_GenerateTestReports"&lt;/span&gt;
  &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'@(CoverageResults)' != ''"&lt;/span&gt;
  &lt;span class="na"&gt;AfterTargets=&lt;/span&gt;&lt;span class="s"&gt;"GenerateTestReports"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ReportGenerator&lt;/span&gt; &lt;span class="na"&gt;ReportFiles=&lt;/span&gt;&lt;span class="s"&gt;"@(CoverageResults)"&lt;/span&gt; &lt;span class="na"&gt;TargetDirectory=&lt;/span&gt;&lt;span class="s"&gt;"tmp\tst\"&lt;/span&gt; &lt;span class="na"&gt;ReportTypes=&lt;/span&gt;&lt;span class="s"&gt;"HtmlInline;Cobertura"&lt;/span&gt; &lt;span class="na"&gt;VerbosityLevel=&lt;/span&gt;&lt;span class="s"&gt;"Info"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Move&lt;/span&gt; &lt;span class="na"&gt;SourceFiles=&lt;/span&gt;&lt;span class="s"&gt;"tmp\tst\Cobertura.xml"&lt;/span&gt; &lt;span class="na"&gt;DestinationFiles=&lt;/span&gt;&lt;span class="s"&gt;"tmp\tst\$(MSBuildProjectName)-cobertura-results.xml"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a side note you may notice that the tests results are not part of the output due to poor integration of the Test Platform with MSBuild (cf. &lt;a href="https://github.com/microsoft/vstest/issues/680" rel="noopener noreferrer"&gt;bug #680&lt;/a&gt;), but we are working on that.&lt;/p&gt;

&lt;h4&gt;
  
  
  Analyze
&lt;/h4&gt;

&lt;p&gt;This target is just a matter of executing &lt;a href="https://github.com/AlDanial/cloc" rel="noopener noreferrer"&gt;the cloc utility&lt;/a&gt;. The only trick is to execute the Perl script when not on Windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;  &lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"Analyze"&lt;/span&gt; &lt;span class="na"&gt;DependsOnTargets=&lt;/span&gt;&lt;span class="s"&gt;"CountLoc"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"CountLoc"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;ClocResultsFile&amp;gt;&lt;/span&gt;tmp\cloc-results.xml&lt;span class="nt"&gt;&amp;lt;/ClocResultsFile&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;_ClocCommand&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(OS)'=='Windows_NT'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;.tmp\cloc.exe&lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/_ClocCommand&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;_ClocCommand&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(_ClocCommand)'==''"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;perl &lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;.tmp/cloc.pl&lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/_ClocCommand&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Exec&lt;/span&gt;
      &lt;span class="na"&gt;Command=&lt;/span&gt;&lt;span class="s"&gt;"$(_ClocCommand) &amp;amp;quot;$(InputPath)&amp;amp;quot; --exclude-dir=.tmp,.vs,.vscode,bin,build,doc,lib,obj,tmp,GeneratedCode --exclude-ext=csproj,dbmdl,proj,sln,sqlproj,suo,user --3 --quiet --progress-rate=0 --xml --report_file=&amp;amp;quot;$(ClocResultsFile)&amp;amp;quot;"&lt;/span&gt;
      &lt;span class="na"&gt;YieldDuringToolExecution=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt;
      &lt;span class="na"&gt;IgnoreExitCode=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Package
&lt;/h4&gt;

&lt;p&gt;In the case of a library like we have, the &lt;em&gt;Package&lt;/em&gt; target is just calling the MSBuild equivalent of &lt;code&gt;dotnet pack&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"Package"&lt;/span&gt; &lt;span class="na"&gt;DependsOnTargets=&lt;/span&gt;&lt;span class="s"&gt;"Prepare;Project"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"PackageProject"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;_BaseOutputPath&amp;gt;&lt;/span&gt;tmp\pck\%(Projects.FileName)\&lt;span class="nt"&gt;&amp;lt;/_BaseOutputPath&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;_BaseIntermediateOutputPath&amp;gt;&lt;/span&gt;tmp\obj\bin\%(Projects.FileName)\&lt;span class="nt"&gt;&amp;lt;/_BaseIntermediateOutputPath&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;_PackageProperties&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"PackageOutputPath=tmp\out\bin"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;MSBuild&lt;/span&gt;
    &lt;span class="na"&gt;Projects=&lt;/span&gt;&lt;span class="s"&gt;"%(Projects.Identity)"&lt;/span&gt;
    &lt;span class="na"&gt;RebaseOutputs=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt;
    &lt;span class="na"&gt;Properties=&lt;/span&gt;&lt;span class="s"&gt;"Configuration=%(Projects.Configuration);Platform=%(Projects.Platform);BaseOutputPath=$(_BaseOutputPath);BaseIntermediateOutputPath=$(_BaseIntermediateOutputPath);@(_PackageProperties);%(Projects.Properties)"&lt;/span&gt;
    &lt;span class="na"&gt;Targets=&lt;/span&gt;&lt;span class="s"&gt;"Restore;Pack"&lt;/span&gt;
  &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will expect every final artefact to be generated in the &lt;code&gt;tmp\out\bin&lt;/code&gt; folder.&lt;/p&gt;

&lt;h3&gt;
  
  
  The script file
&lt;/h3&gt;

&lt;p&gt;There are actually 2 script files here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/mcartoixa/Vigicrues.Client/blob/v0.1/build.bat" rel="noopener noreferrer"&gt;&lt;code&gt;build.bat&lt;/code&gt;&lt;/a&gt; on Windows.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/mcartoixa/Vigicrues.Client/blob/v0.1/build.sh" rel="noopener noreferrer"&gt;&lt;code&gt;build.sh&lt;/code&gt;&lt;/a&gt; on Linux and MacOS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They both do the same thing on different platforms so I will detail only one of them. It is just a matter of interpreting command line parameters to create the right environment variables before executing the build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet.exe tool restore
dotnet.exe msbuild Vigicrue.Client.proj /nologo /t:Build /m /r /fl /flp:logfile=build.log;verbosity=%_VERBOSITY%;encoding=UTF-8 /nr:False /v:normal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One external dependency has to be installed prior to this execution though: &lt;a href="https://github.com/AlDanial/cloc" rel="noopener noreferrer"&gt;the cloc utility&lt;/a&gt;. This is done in various stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;version for this dependency is defined in the &lt;a href="https://github.com/mcartoixa/Vigicrues.Client/blob/v0.1/build/versions.env" rel="noopener noreferrer"&gt;&lt;code&gt;build\versions.env&lt;/code&gt;&lt;/a&gt; file.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_CLOC_VERSION=1.82
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;in the &lt;a href="https://github.com/mcartoixa/Vigicrues.Client/blob/v0.1/build/SetEnv.bat" rel="noopener noreferrer"&gt;&lt;code&gt;build\SetEnv.bat&lt;/code&gt;&lt;/a&gt; script versions are set as environment variables:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IF EXIST build\versions.env (
    FOR /F "eol=# tokens=1* delims==" %%i IN (build\versions.env) DO (
        SET "%%i=%%j"
        ECHO SET %%i=%%j
    )
    ECHO.
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;the right version of the tool is downloaded and extracted in the &lt;code&gt;.tmp&lt;/code&gt; folder (if necessary):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IF NOT EXIST "%CD%\.tmp\cloc.exe" GOTO SETENV_CLOC
FOR /F %%i IN ('"%CD%\.tmp\cloc.exe" --version') DO (
    IF "%%i"=="%_CLOC_VERSION%" GOTO END
)
:SETENV_CLOC
powershell.exe -NoLogo -NonInteractive -ExecutionPolicy ByPass -Command "&amp;amp; { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest https://github.com/AlDanial/cloc/releases/download/$Env:_CLOC_VERSION/cloc-$Env:_CLOC_VERSION.exe -OutFile .tmp\cloc.exe; }"
:END
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;build\SetEnv.bat&lt;/code&gt; is then simply called in the &lt;code&gt;build.bat&lt;/code&gt; script file. The same architecture could be used for other tools that cannot be retrieved with NuGet.&lt;/p&gt;

&lt;h3&gt;
  
  
  The CI configuration file
&lt;/h3&gt;

&lt;p&gt;I will use &lt;a href="https://www.appveyor.com/" rel="noopener noreferrer"&gt;AppVeyor&lt;/a&gt; as a platform, but as usual the configuration will be very simple because all the complexity has been handled above. I could very simply switch to any other tool with minimal reconfiguration. The &lt;a href="https://github.com/mcartoixa/Vigicrues.Client/blob/v0.1/appveyor.yml" rel="noopener noreferrer"&gt;&lt;code&gt;appveyor.yml&lt;/code&gt;&lt;/a&gt; can simply be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.1.{build}.0&lt;/span&gt;
&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Visual Studio &lt;/span&gt;&lt;span class="m"&gt;2019&lt;/span&gt;

&lt;span class="na"&gt;install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CALL build\SetEnv.bat&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet tool restore&lt;/span&gt;

&lt;span class="na"&gt;build_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet msbuild Vigicrues.Client.proj /nologo /t:Release /m /r /l:"C:\Program Files\AppVeyor\BuildAgent\dotnetcore\Appveyor.MSBuildLogger.dll" /fl /flp:logfile=build.log;verbosity=diagnostic;encoding=UTF-8 /nr:False /v:normal&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In pratice I will add a few tweaks though:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;upload the coverage results to &lt;a href="https://codecov.io/gh/mcartoixa/Vigicrues.Client" rel="noopener noreferrer"&gt;the Codecov platform&lt;/a&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet tool update Codecov.Tool --version 1.12.4&lt;/span&gt;
  &lt;span class="na"&gt;on_success&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet tool run codecov -f "tmp\*-cobertura-results.xml"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;use &lt;a href="https://github.com/spekt/appveyor.testlogger" rel="noopener noreferrer"&gt;the AppVeyor test logger&lt;/a&gt; to automatically report test results to the platform (remember the &lt;code&gt;%VSTEST_LOGGER%&lt;/code&gt; environment variable?):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;VSTEST_LOGGER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Appveyor&lt;/span&gt;
  &lt;span class="na"&gt;install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet add Vigicrues.Tests\Vigicrues.Tests.csproj package Appveyor.TestLogger --version 2.0.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Going further
&lt;/h2&gt;

&lt;p&gt;These scripts are still in early phase and they will evolve over time. In fact they might have already evolved at the time you read this post, but I guess I could still add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the ability to handle the packaging of native applications, which is a simple matter of translating &lt;code&gt;dotnet publish&lt;/code&gt; into MSBuild scripts.&lt;/li&gt;
&lt;li&gt;the ability to handle the packaging of web applications which whould also involve &lt;code&gt;dotnet publish&lt;/code&gt; and a small touch of &lt;a href="https://www.iis.net/downloads/microsoft/web-deploy" rel="noopener noreferrer"&gt;Web Deploy&lt;/a&gt; (I love this tool).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When everything is stable enough maybe I could extract most of the files in the &lt;code&gt;build\&lt;/code&gt; directory into a proper NuGet package and reuse them over all my projects. &lt;code&gt;dotnet make&lt;/code&gt;, anyone?&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1219923246373425152-434" src="https://platform.twitter.com/embed/Tweet.html?id=1219923246373425152"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1219923246373425152-434');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1219923246373425152&amp;amp;theme=dark"
  }



&lt;/p&gt;

</description>
      <category>ci</category>
      <category>dotnet</category>
      <category>msbuild</category>
    </item>
    <item>
      <title>How to host pgAdmin 4 on Windows Server</title>
      <dc:creator>Mac</dc:creator>
      <pubDate>Sun, 03 Jan 2021 06:53:00 +0000</pubDate>
      <link>https://dev.to/mcartoixa/how-to-host-pgadmin-4-on-windows-server-1e9b</link>
      <guid>https://dev.to/mcartoixa/how-to-host-pgadmin-4-on-windows-server-1e9b</guid>
      <description>&lt;p&gt;I just set out to create a PostgreSQL database server in my home lab, that I intend to use first with &lt;a href="https://www.home-assistant.io/" rel="noopener noreferrer"&gt;Home Assistant&lt;/a&gt;. And just because this is &lt;em&gt;my&lt;/em&gt; home lab, it is hosted in a Windows Server 2019 VM in Hyper-V. 😁 I then foolishly installed pgAdmin 4 on my computer with the intention to remotely manage this new server… I remember pgAdmin III as such a slick application with an installer weighing less than 15MB. 4 years later the pgAdmin 4 installer is &lt;strong&gt;10 times bigger&lt;/strong&gt; , a &lt;a href="https://flask.palletsprojects.com/" rel="noopener noreferrer"&gt;Flask application&lt;/a&gt; that runs in the browser (these are the times we live in 🤷🏻‍♂️) once the server has started, which can take a while.&lt;/p&gt;

&lt;p&gt;So I decided that this (otherwise fine) piece of bloatware belonged on a web server and not on my computer. This is not very difficult once you have got the recipe (and some experience on Windows of course).&lt;/p&gt;

&lt;h2&gt;
  
  
  Ingredients
&lt;/h2&gt;

&lt;p&gt;The ingredients are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a Windows Server machine. I used Windows Server 2019, but I am pretty sure the same applies to every version at least back to Windows Server 2008. Or Windows 8, for that matter.&lt;/li&gt;
&lt;li&gt;an instance of IIS. This can be &lt;a href="https://docs.microsoft.com/en-us/iis/web-hosting/web-server-for-shared-hosting/installing-the-web-server-role" rel="noopener noreferrer"&gt;installed as a role inside Windows Server&lt;/a&gt; (or as as feature in Windows 10).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.iis.net/downloads/microsoft/httpplatformhandler" rel="noopener noreferrer"&gt;HttpPlatformHandler&lt;/a&gt; which is a IIS Module that can be used to manage any process on the server (we will be using Python in this case) and act as a reverse proxy.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.pgadmin.org/download/pgadmin-4-windows/" rel="noopener noreferrer"&gt;pgAdmin 4&lt;/a&gt;: 

&lt;ul&gt;
&lt;li&gt;it can be installed anywhere, but &lt;code&gt;%ProgramFiles%&lt;/code&gt; is usually where programs go.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Recipe
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Create an application pool&lt;/li&gt;
&lt;li&gt;Create a website&lt;/li&gt;
&lt;li&gt;Open the firewall&lt;/li&gt;
&lt;li&gt;Configure the website&lt;/li&gt;
&lt;li&gt;Grant rights&lt;/li&gt;
&lt;li&gt;Start the website&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Create an application pool
&lt;/h3&gt;

&lt;p&gt;As a best practice you will need &lt;a href="https://docs.microsoft.com/en-us/iis/configuration/system.applicationhost/applicationpools/" rel="noopener noreferrer"&gt;a new Application Pool in IIS&lt;/a&gt;: this will allow us to isolate the application in a dedicated worker process with a dedicated identity. I named my pool &lt;em&gt;PgAdmin&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ferzfgcvt84wh9mgksp45.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ferzfgcvt84wh9mgksp45.png" alt="PgAdmin application pool" width="691" height="229"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a website
&lt;/h3&gt;

&lt;p&gt;Then we need a new website:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;select the Application Pool that we just created.&lt;/li&gt;
&lt;li&gt;create a new folder under &lt;code&gt;C:\inetpub&lt;/code&gt; called &lt;code&gt;pgadmin&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;configure your bindings as you wish: I configured my application to be accessible on port 3000. You will be able &lt;a href="https://docs.microsoft.com/en-us/iis/configuration/system.applicationhost/sites/site/bindings/binding" rel="noopener noreferrer"&gt;to change this later if necessary&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;do not start the website right away (there is still work to do).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqxjmoc1mz15fbrquv30j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqxjmoc1mz15fbrquv30j.png" alt="PgAdmin website" width="585" height="672"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Open the firewall
&lt;/h3&gt;

&lt;p&gt;Make sure your application is accessible by &lt;a href="https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-firewall/checklist-creating-inbound-firewall-rules" rel="noopener noreferrer"&gt;opening the Windows Firewall if necessary&lt;/a&gt; (on TCP port 3000 in my case).&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure the website
&lt;/h3&gt;

&lt;p&gt;Configure the website by adding the following configuration (cf. &lt;a href="https://docs.microsoft.com/en-us/visualstudio/python/configure-web-apps-for-iis-windows" rel="noopener noreferrer"&gt;Configure Python web apps for IIS&lt;/a&gt;) in a file called &lt;code&gt;web.config&lt;/code&gt; inside the &lt;code&gt;pgadmin&lt;/code&gt; folder that you just created (you can use Notepad):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;system.webServer&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;handlers&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;add&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"PythonHandler"&lt;/span&gt; &lt;span class="na"&gt;path=&lt;/span&gt;&lt;span class="s"&gt;"*"&lt;/span&gt; &lt;span class="na"&gt;verb=&lt;/span&gt;&lt;span class="s"&gt;"*"&lt;/span&gt; &lt;span class="na"&gt;modules=&lt;/span&gt;&lt;span class="s"&gt;"httpPlatformHandler"&lt;/span&gt; &lt;span class="na"&gt;resourceType=&lt;/span&gt;&lt;span class="s"&gt;"Unspecified"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/handlers&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;httpPlatform&lt;/span&gt;
       &lt;span class="na"&gt;processPath=&lt;/span&gt;&lt;span class="s"&gt;"%ProgramFiles%\pgAdmin 4\v4\runtime\python.exe"&lt;/span&gt;
       &lt;span class="na"&gt;arguments=&lt;/span&gt;&lt;span class="s"&gt;"&amp;amp;quot;%ProgramFiles%\pgAdmin 4\v4\web\pgAdmin4.py&amp;amp;quot;"&lt;/span&gt;
       &lt;span class="na"&gt;stdoutLogEnabled=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
       &lt;span class="na"&gt;stdoutLogFile=&lt;/span&gt;&lt;span class="s"&gt;"C:\inetpub\logs\pgadmin\pgAdmin4.log"&lt;/span&gt;
       &lt;span class="na"&gt;startupTimeLimit=&lt;/span&gt;&lt;span class="s"&gt;"60"&lt;/span&gt;
       &lt;span class="na"&gt;processesPerApplication=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;environmentVariables&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;environmentVariable&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"PGADMIN_INT_PORT"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"%HTTP_PLATFORM_PORT%"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;environmentVariable&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"PYTHONHOME"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"%ProgramFiles%\pgAdmin 4\v4\venv"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;environmentVariable&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"SERVER_MODE"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/environmentVariables&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/httpPlatform&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;modules&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;remove&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"WebDAVModule"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/modules&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/system.webServer&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is pretty straightforward but notably:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;em&gt;arguments&lt;/em&gt; attribute is quoted to account for the fact that the path contains spaces.&lt;/li&gt;
&lt;li&gt;the process outputs logs inside a folder called &lt;code&gt;C:\inetpub\logs\pgadmin&lt;/code&gt; that would have to be created beforehand.&lt;/li&gt;
&lt;li&gt;pgAdmin notoriously &lt;a href="https://www.pgadmin.org/docs/pgadmin4/development/server_deployment.html#requirements" rel="noopener noreferrer"&gt;handles a single process only&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;HttpPlatformHandler&lt;/em&gt; will automatically create a process using Python and assign it a dedicated port using the &lt;code&gt;HTTP_PLATFORM_PORT&lt;/code&gt; environment variable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Grant rights
&lt;/h3&gt;

&lt;p&gt;And now we simply have to give the proper rights to our application pool user named &lt;code&gt;IIS APPPOOL\PgAdmin&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Read &amp;amp; Execute&lt;/em&gt; on the folder &lt;code&gt;%ProgramFiles%\pgAdmin 4&lt;/code&gt; (and subfolders).&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Read &amp;amp; Execute&lt;/em&gt; on the folder &lt;code&gt;C:\inetpub\pgadmin&lt;/code&gt;. 

&lt;ul&gt;
&lt;li&gt;Create a subfolder called &lt;code&gt;pgAdmin&lt;/code&gt; and give &lt;em&gt;Full Control&lt;/em&gt; to the user. This where the application will store its data.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;em&gt;Full Control&lt;/em&gt; on the logging folder (&lt;code&gt;C:\inetpub\logs\pgadmin&lt;/code&gt; in my case).&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fov84ej7208h7m3vzzw6h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fov84ej7208h7m3vzzw6h.png" alt="PgAdmin application pool rights" width="363" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Start the website
&lt;/h3&gt;

&lt;p&gt;Now you can start the website. Enjoy!&lt;/p&gt;

&lt;p&gt;Note that the application will recycle automatically every 29 hours by default, which means that the next request will take much longer while the process is restarted. Not such a bad thing by itself, but obviously &lt;a href="https://docs.microsoft.com/en-us/iis/configuration/system.applicationhost/applicationpools/add/recycling/" rel="noopener noreferrer"&gt;this behaviour can be changed&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also note that the logging folder should be cleaned up from time to time…&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UPDATE 2021-05-29:&lt;/strong&gt; the website configuration has been udpated for version 5 of pgAdmin... 4! Here is the configuration for version 4:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;system.webServer&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;handlers&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;add&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"PythonHandler"&lt;/span&gt; &lt;span class="na"&gt;path=&lt;/span&gt;&lt;span class="s"&gt;"*"&lt;/span&gt; &lt;span class="na"&gt;verb=&lt;/span&gt;&lt;span class="s"&gt;"*"&lt;/span&gt; &lt;span class="na"&gt;modules=&lt;/span&gt;&lt;span class="s"&gt;"httpPlatformHandler"&lt;/span&gt; &lt;span class="na"&gt;resourceType=&lt;/span&gt;&lt;span class="s"&gt;"Unspecified"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/handlers&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;httpPlatform&lt;/span&gt;
       &lt;span class="na"&gt;processPath=&lt;/span&gt;&lt;span class="s"&gt;"%ProgramFiles%\pgAdmin 4\v4\runtime\python.exe"&lt;/span&gt;
       &lt;span class="na"&gt;arguments=&lt;/span&gt;&lt;span class="s"&gt;"&amp;amp;quot;%ProgramFiles%\pgAdmin 4\v4\web\pgAdmin4.py&amp;amp;quot;"&lt;/span&gt;
       &lt;span class="na"&gt;stdoutLogEnabled=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
       &lt;span class="na"&gt;stdoutLogFile=&lt;/span&gt;&lt;span class="s"&gt;"C:\inetpub\logs\pgadmin\pgAdmin4.log"&lt;/span&gt;
       &lt;span class="na"&gt;startupTimeLimit=&lt;/span&gt;&lt;span class="s"&gt;"60"&lt;/span&gt;
       &lt;span class="na"&gt;processesPerApplication=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;environmentVariables&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;environmentVariable&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"PGADMIN_INT_PORT"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"%HTTP_PLATFORM_PORT%"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;environmentVariable&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"PYTHONHOME"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"%ProgramFiles%\pgAdmin 4\v4\venv"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;environmentVariable&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"SERVER_MODE"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/environmentVariables&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/httpPlatform&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;modules&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;remove&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"WebDAVModule"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/modules&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/system.webServer&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>howto</category>
      <category>windows</category>
      <category>iis</category>
      <category>python</category>
    </item>
    <item>
      <title>Continuous Integration for Java</title>
      <dc:creator>Mac</dc:creator>
      <pubDate>Mon, 05 Oct 2020 05:19:00 +0000</pubDate>
      <link>https://dev.to/mcartoixa/continuous-integration-for-java-1127</link>
      <guid>https://dev.to/mcartoixa/continuous-integration-for-java-1127</guid>
      <description>&lt;p&gt;From what I understand &lt;a href="https://gradle.org/" rel="noopener noreferrer"&gt;Gradle&lt;/a&gt; or even &lt;a href="https://maven.apache.org/" rel="noopener noreferrer"&gt;Maven&lt;/a&gt; are the way to go for your build in Java. They seem to rely heavily on conventions, which is good. But if you need (or want) to add to these conventions (like I do with &lt;a href="https://dev.to/mcartoixa/my-take-on-continuous-integration-3gep"&gt;my practices&lt;/a&gt;) it seems that the only 2 ways are to either build your own plugins or to execute additional &lt;a href="https://ant.apache.org/" rel="noopener noreferrer"&gt;Apache Ant&lt;/a&gt; tasks and targets inside your build. So it seems to me that the good old &lt;a href="https://ant.apache.org/" rel="noopener noreferrer"&gt;Apache Ant&lt;/a&gt; is still alive and well.&lt;/p&gt;

&lt;h2&gt;
  
  
  The project
&lt;/h2&gt;

&lt;p&gt;As we will investigate in the next installment of this series I needed to create a build for a Salesforce project using the &lt;a href="https://developer.salesforce.com/tools/sfdxcli" rel="noopener noreferrer"&gt;Salesforce CLI&lt;/a&gt;. No build tool is truly native to this environment, and Salesforce is very keen on demonstrating how simple builds can be achieved &lt;a href="https://developer.salesforce.com/blogs/2019/05/continuous-integration-with-salesforce-dx.html" rel="noopener noreferrer"&gt;by batching command lines executions&lt;/a&gt;. I did not feel that this would allow me to create &lt;a href="https://dev.to/mcartoixa/my-take-on-continuous-integration-3gep"&gt;the kind of build that I wanted&lt;/a&gt;, and for reasons that I will detail when we get there I settled on using &lt;a href="https://ant.apache.org/" rel="noopener noreferrer"&gt;Apache Ant&lt;/a&gt; instead. In order to have proper control over the execution and the outputs I decided to encapsulate the &lt;a href="https://developer.salesforce.com/tools/sfdxcli" rel="noopener noreferrer"&gt;Salesforce CLI&lt;/a&gt; commands into proper &lt;a href="https://ant.apache.org/" rel="noopener noreferrer"&gt;Ant&lt;/a&gt; tasks (instead of using the &lt;a href="https://ant.apache.org/manual/Tasks/exec.html" rel="noopener noreferrer"&gt;&lt;code&gt;exec&lt;/code&gt;&lt;/a&gt; task). Hence the &lt;a href="https://github.com/mcartoixa/ant-sfdx" rel="noopener noreferrer"&gt;ant-sfdx&lt;/a&gt; project.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/mcartoixa" rel="noopener noreferrer"&gt;
        mcartoixa
      &lt;/a&gt; / &lt;a href="https://github.com/mcartoixa/ant-sfdx" rel="noopener noreferrer"&gt;
        ant-sfdx
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Ant tasks that encapsulate the Salesforce DX CLI
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The main elements of the project are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
The build file (&lt;code&gt;build.xml&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
The script file (&lt;code&gt;build.bat&lt;/code&gt;) that helps executing the build file locally.&lt;/li&gt;
&lt;li&gt;
The CI configuration file (&lt;code&gt;.travis.yml&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The build file
&lt;/h3&gt;

&lt;p&gt;Given that the goal is to create &lt;a href="https://ant.apache.org/" rel="noopener noreferrer"&gt;Apache Ant&lt;/a&gt; tasks, it seems natural that I used it for this project as well, if only to acquire more experience with it. I also settled on using &lt;a href="https://netbeans.org/" rel="noopener noreferrer"&gt;NetBeans&lt;/a&gt;, not because it is the best Java IDE around (I hope it is not) but because it provides an excellent support for &lt;a href="https://ant.apache.org/" rel="noopener noreferrer"&gt;Apache Ant&lt;/a&gt; and it can create a complete and extensible build based on &lt;a href="https://ant.apache.org/" rel="noopener noreferrer"&gt;Apache Ant&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As a matter of fact if I just import the build file provided by &lt;a href="https://netbeans.org/" rel="noopener noreferrer"&gt;NetBeans&lt;/a&gt; in my own &lt;code&gt;build.xml&lt;/code&gt; I automatically get all the targets I need to compile and test the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;import&lt;/span&gt; &lt;span class="na"&gt;file=&lt;/span&gt;&lt;span class="s"&gt;"nbproject/build-impl.xml"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My build provides the following targets (which should feel familiar by now):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;clean&lt;/em&gt;: automatically provided by &lt;a href="https://netbeans.org/" rel="noopener noreferrer"&gt;NetBeans&lt;/a&gt;. 

&lt;ul&gt;
&lt;li&gt;Extended to clean the &lt;code&gt;tmp\&lt;/code&gt; directory.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;em&gt;compile&lt;/em&gt;: automatically provided by &lt;a href="https://netbeans.org/" rel="noopener noreferrer"&gt;NetBeans&lt;/a&gt;.&lt;/li&gt;

&lt;li&gt;

&lt;em&gt;test&lt;/em&gt;: automatically provided by &lt;a href="https://netbeans.org/" rel="noopener noreferrer"&gt;NetBeans&lt;/a&gt;. 

&lt;ul&gt;
&lt;li&gt;Extended to include &lt;a href="https://www.jacoco.org/jacoco/" rel="noopener noreferrer"&gt;JaCoCo&lt;/a&gt; for code coverage measurement.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;em&gt;package&lt;/em&gt;: creates a package for the plugin which consists of the JAR file, &lt;a href="https://maven.apache.org/guides/introduction/introduction-to-the-pom.html" rel="noopener noreferrer"&gt;a POM for reference and deployment&lt;/a&gt; and a zip file for copy and paste deployment.&lt;/li&gt;

&lt;li&gt;

&lt;em&gt;build&lt;/em&gt;: shortcut for the combination of &lt;em&gt;compile&lt;/em&gt;, &lt;em&gt;test&lt;/em&gt; and &lt;em&gt;analyze&lt;/em&gt;.&lt;/li&gt;

&lt;li&gt;

&lt;em&gt;rebuild&lt;/em&gt;: shortcut for the combination of &lt;em&gt;clean&lt;/em&gt; and &lt;em&gt;build&lt;/em&gt;.&lt;/li&gt;

&lt;li&gt;

&lt;em&gt;release&lt;/em&gt;: shortcut for the combination of &lt;em&gt;clean&lt;/em&gt;, &lt;em&gt;build&lt;/em&gt;, and &lt;em&gt;package&lt;/em&gt;.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The build provided by &lt;a href="https://netbeans.org/" rel="noopener noreferrer"&gt;NetBeans&lt;/a&gt; contains empty targets that are meant to be overridden for extension. For instance the &lt;em&gt;-post-clean&lt;/em&gt; target is the perfect extension point to delete the &lt;code&gt;tmp\&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;target&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"-post-clean"&lt;/span&gt; &lt;span class="na"&gt;depends=&lt;/span&gt;&lt;span class="s"&gt;"clean.tmp"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;target&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"clean.tmp"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;delete&lt;/span&gt; &lt;span class="na"&gt;dir=&lt;/span&gt;&lt;span class="s"&gt;"./tmp"&lt;/span&gt; &lt;span class="na"&gt;includeemptydirs=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;quiet=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;failonerror=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/target&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To be able to find extensions points you can try and read the whole included file (1700+ lines of XML!), or you can use &lt;a href="https://netbeans.org/" rel="noopener noreferrer"&gt;NetBeans&lt;/a&gt; itself to navigate it: &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8mu0n9953pw00sk5qa70.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8mu0n9953pw00sk5qa70.png" alt="Ant Targets in NetBeans" width="197" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can also perform code analysis using &lt;a href="https://pmd.github.io/" rel="noopener noreferrer"&gt;PMD&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;target&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"analyze.pmd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;taskdef&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"pmd"&lt;/span&gt; &lt;span class="na"&gt;classname=&lt;/span&gt;&lt;span class="s"&gt;"net.sourceforge.pmd.ant.PMDTask"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;classpath&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;fileset&lt;/span&gt; &lt;span class="na"&gt;dir=&lt;/span&gt;&lt;span class="s"&gt;".tmp/pmd-bin-6.21.0/lib"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;include&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"**/*.jar"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/fileset&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/classpath&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/taskdef&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;pmd&lt;/span&gt; &lt;span class="na"&gt;failOnRuleViolation=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;cacheLocation=&lt;/span&gt;&lt;span class="s"&gt;"tmp/obj/pmd.cache"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;fileset&lt;/span&gt; &lt;span class="na"&gt;dir=&lt;/span&gt;&lt;span class="s"&gt;"src/main"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;include&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"**/*.java"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/fileset&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;sourceLanguage&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"java"&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"1.8"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ruleset&amp;gt;&lt;/span&gt;.ruleset.xml&lt;span class="nt"&gt;&amp;lt;/ruleset&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;formatter&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;toConsole=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;formatter&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"html"&lt;/span&gt; &lt;span class="na"&gt;toFile=&lt;/span&gt;&lt;span class="s"&gt;"tmp/pmd-results.html"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;formatter&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"xml"&lt;/span&gt; &lt;span class="na"&gt;toFile=&lt;/span&gt;&lt;span class="s"&gt;"tmp/pmd-results.xml"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/pmd&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/target&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;em&gt;package&lt;/em&gt; target has to be created entirely, but once you know what has to be done (cf. above) it is quite straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;target&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"package"&lt;/span&gt; &lt;span class="na"&gt;depends=&lt;/span&gt;&lt;span class="s"&gt;"jar,package.pom"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;copy&lt;/span&gt; &lt;span class="na"&gt;todir=&lt;/span&gt;&lt;span class="s"&gt;"tmp/out/bin/"&lt;/span&gt; &lt;span class="na"&gt;preservelastmodified=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;resources&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;file&lt;/span&gt; &lt;span class="na"&gt;file=&lt;/span&gt;&lt;span class="s"&gt;"tmp/bin/ant-sfdx.jar"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;file&lt;/span&gt; &lt;span class="na"&gt;file=&lt;/span&gt;&lt;span class="s"&gt;"ivy.xml"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/resources&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/copy&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;zip&lt;/span&gt; &lt;span class="na"&gt;destfile=&lt;/span&gt;&lt;span class="s"&gt;"tmp/out/bin/ant-sfdx.zip"&lt;/span&gt; &lt;span class="na"&gt;compress=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;level=&lt;/span&gt;&lt;span class="s"&gt;"9"&lt;/span&gt; &lt;span class="na"&gt;filesonly=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;resources&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;file&lt;/span&gt; &lt;span class="na"&gt;file=&lt;/span&gt;&lt;span class="s"&gt;"tmp/bin/ant-sfdx.jar"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;file&lt;/span&gt; &lt;span class="na"&gt;file=&lt;/span&gt;&lt;span class="s"&gt;"ivy.xml"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;file&lt;/span&gt; &lt;span class="na"&gt;file=&lt;/span&gt;&lt;span class="s"&gt;"${ivy.runtime.classpath}"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/resources&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;zipfileset&lt;/span&gt; &lt;span class="na"&gt;dir=&lt;/span&gt;&lt;span class="s"&gt;"docs"&lt;/span&gt; &lt;span class="na"&gt;prefix=&lt;/span&gt;&lt;span class="s"&gt;"docs"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/zip&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/target&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;target&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"package.pom"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ivy:makepom&lt;/span&gt; &lt;span class="na"&gt;ivyfile=&lt;/span&gt;&lt;span class="s"&gt;"ivy.xml"&lt;/span&gt; &lt;span class="na"&gt;pomfile=&lt;/span&gt;&lt;span class="s"&gt;"tmp/out/bin/pom.xml"&lt;/span&gt; &lt;span class="na"&gt;conf=&lt;/span&gt;&lt;span class="s"&gt;"default,compile"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;mapping&lt;/span&gt; &lt;span class="na"&gt;conf=&lt;/span&gt;&lt;span class="s"&gt;"default"&lt;/span&gt; &lt;span class="na"&gt;scope=&lt;/span&gt;&lt;span class="s"&gt;"default"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;mapping&lt;/span&gt; &lt;span class="na"&gt;conf=&lt;/span&gt;&lt;span class="s"&gt;"test"&lt;/span&gt; &lt;span class="na"&gt;scope=&lt;/span&gt;&lt;span class="s"&gt;"test"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;mapping&lt;/span&gt; &lt;span class="na"&gt;conf=&lt;/span&gt;&lt;span class="s"&gt;"compile"&lt;/span&gt; &lt;span class="na"&gt;scope=&lt;/span&gt;&lt;span class="s"&gt;"compile"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ivy:makepom&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/target&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you may see here there are references to &lt;a href="https://ant.apache.org/ivy/" rel="noopener noreferrer"&gt;Apache Ivy&lt;/a&gt;: it is the dependency manager of choice when using &lt;a href="https://ant.apache.org/" rel="noopener noreferrer"&gt;Apache Ant&lt;/a&gt;. I found its learning curve to be somewhat steep but well integrated technologies prove powerful in the end. The dependencies for the project are described in an XML file (of course!) &lt;a href="https://github.com/mcartoixa/ant-sfdx/blob/master/ivy.xml" rel="noopener noreferrer"&gt;&lt;code&gt;ivy.xml&lt;/code&gt;&lt;/a&gt;, and part of the build consists of retrieving these dependencies, updating the various CLASSPATHs associated with them and also updating the properties file that is the basis of the &lt;a href="https://netbeans.org/" rel="noopener noreferrer"&gt;NetBeans&lt;/a&gt; project so that it remains up to date when dependencies change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;target&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"prepare.dependencies"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ivy:retrieve&lt;/span&gt; &lt;span class="na"&gt;pattern=&lt;/span&gt;&lt;span class="s"&gt;"ivy/lib/[conf]/[artifact].[ext]"&lt;/span&gt; &lt;span class="na"&gt;log=&lt;/span&gt;&lt;span class="s"&gt;"quiet"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;pathconvert&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"ivy.compile.classpath"&lt;/span&gt; &lt;span class="na"&gt;dirsep=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt; &lt;span class="na"&gt;pathsep=&lt;/span&gt;&lt;span class="s"&gt;":"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;path&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;fileset&lt;/span&gt; &lt;span class="na"&gt;dir=&lt;/span&gt;&lt;span class="s"&gt;"${basedir}/ivy/lib/compile"&lt;/span&gt; &lt;span class="na"&gt;includes=&lt;/span&gt;&lt;span class="s"&gt;"**/*.jar"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/path&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;map&lt;/span&gt; &lt;span class="na"&gt;from=&lt;/span&gt;&lt;span class="s"&gt;"${basedir}${file.separator}"&lt;/span&gt; &lt;span class="na"&gt;to=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/pathconvert&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;pathconvert&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"ivy.test.classpath"&lt;/span&gt; &lt;span class="na"&gt;dirsep=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt; &lt;span class="na"&gt;pathsep=&lt;/span&gt;&lt;span class="s"&gt;":"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;path&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;fileset&lt;/span&gt; &lt;span class="na"&gt;dir=&lt;/span&gt;&lt;span class="s"&gt;"${basedir}/ivy/lib/test"&lt;/span&gt; &lt;span class="na"&gt;includes=&lt;/span&gt;&lt;span class="s"&gt;"**/*.jar"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/path&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;map&lt;/span&gt; &lt;span class="na"&gt;from=&lt;/span&gt;&lt;span class="s"&gt;"${basedir}${file.separator}"&lt;/span&gt; &lt;span class="na"&gt;to=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/pathconvert&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;uptodate&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"prepare.ivy.nbproject.isuptodate"&lt;/span&gt; &lt;span class="na"&gt;srcfile=&lt;/span&gt;&lt;span class="s"&gt;"ivy.xml"&lt;/span&gt; &lt;span class="na"&gt;targetfile=&lt;/span&gt;&lt;span class="s"&gt;"nbproject/project.properties"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;propertyfile&lt;/span&gt; &lt;span class="na"&gt;file=&lt;/span&gt;&lt;span class="s"&gt;"nbproject/project.properties"&lt;/span&gt; &lt;span class="na"&gt;unless:set=&lt;/span&gt;&lt;span class="s"&gt;"prepare.ivy.nbproject.isuptodate"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;entry&lt;/span&gt; &lt;span class="na"&gt;operation=&lt;/span&gt;&lt;span class="s"&gt;"="&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;"ivy.compile.classpath"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"${ivy.compile.classpath}"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;entry&lt;/span&gt; &lt;span class="na"&gt;operation=&lt;/span&gt;&lt;span class="s"&gt;"="&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;"ivy.test.classpath"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"${ivy.test.classpath}"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/propertyfile&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/target&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The script file
&lt;/h3&gt;

&lt;p&gt;At the core of the &lt;a href="https://github.com/mcartoixa/ant-sfdx/blob/master/build.bat" rel="noopener noreferrer"&gt;&lt;code&gt;build.bat&lt;/code&gt;&lt;/a&gt; script file lies simply the execution of &lt;a href="https://ant.apache.org/" rel="noopener noreferrer"&gt;Apache Ant&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CALL "%ANT_HOME%\bin\ant.bat" -noclasspath -nouserlib -noinput -lib "ivy\lib\test" -Dverbosity=%VERBOSITY% -f %PROJECT% %TARGET%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But before getting there we need to initialize the environment for our build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retrieving &lt;a href="https://ant.apache.org/ivy/" rel="noopener noreferrer"&gt;Apache Ivy&lt;/a&gt; dependencies that are necessary prior to lauching the build (including &lt;a href="https://ant.apache.org/ivy/" rel="noopener noreferrer"&gt;Apache Ivy&lt;/a&gt; itself).&lt;/li&gt;
&lt;li&gt;retrieving dependencies that cannot be retrieved with &lt;a href="https://ant.apache.org/ivy/" rel="noopener noreferrer"&gt;Apache Ivy&lt;/a&gt; (like &lt;a href="https://pmd.github.io/" rel="noopener noreferrer"&gt;PMD&lt;/a&gt;, or &lt;a href="https://ant.apache.org/" rel="noopener noreferrer"&gt;Apache Ant&lt;/a&gt; for instance).&lt;/li&gt;
&lt;li&gt;setting up the required environment variables (like &lt;code&gt;%ANT_HOME%&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Apache Ivy dependencies
&lt;/h4&gt;

&lt;p&gt;Everything &lt;a href="https://ant.apache.org/ivy/" rel="noopener noreferrer"&gt;Apache Ivy&lt;/a&gt; related is done right before executing &lt;a href="https://ant.apache.org/" rel="noopener noreferrer"&gt;Apache Ant&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;first download &lt;a href="https://ant.apache.org/ivy/" rel="noopener noreferrer"&gt;Apache Ivy&lt;/a&gt; itself if necessary.&lt;/li&gt;
&lt;li&gt;then execute it to retrieve the dependencies.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IF NOT EXIST ivy MKDIR ivy
PUSHD ivy
IF NOT EXIST ivy.jar (
    powershell.exe -NoLogo -NonInteractive -ExecutionPolicy ByPass -Command "&amp;amp; { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest https://repo1.maven.org/maven2/org/apache/ivy/ivy/$Env:_IVY_VERSION/ivy-$Env:_IVY_VERSION.jar -OutFile ivy.jar; }"
)
POPD
"%JAVA_HOME%\bin\java.exe" -jar ivy\ivy.jar -retrieve "ivy\lib\[conf]\[artifact].[ext]"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  General dependencies
&lt;/h4&gt;

&lt;p&gt;As for the rest of the initialization it happens inside the &lt;code&gt;build\&lt;/code&gt; directory. The versions for our dependencies are described in a &lt;code&gt;build\versions.env&lt;/code&gt; file, so that those definitions can be reused accross scripts (and platforms):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_ANT_VERSION=1.9.14
_CLOC_VERSION=1.82
_IVY_VERSION=2.5.0
_PMD_VERSION=6.21.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file can easily be read as environment variables inside the &lt;code&gt;build\SetEnv.bat&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IF EXIST build\versions.env (
    FOR /F "eol=# tokens=1* delims==" %%i IN (build\versions.env) DO (
        SET "%%i=%%j"
        ECHO SET %%i=%%j
    )
    ECHO.
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The shell equivalent is in the &lt;code&gt;build/.bashrc&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt; ./build/versions.env]&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="c"&gt;# xargs does not support the -d option on BSD (MacOS X)&lt;/span&gt;
    &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'^#'&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'^[[:space:]]*$'&lt;/span&gt; build/versions.env | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt; &lt;span class="s1"&gt;'\0'&lt;/span&gt; | xargs &lt;span class="nt"&gt;-0&lt;/span&gt; &lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'^#'&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'^[[:space:]]*$'&lt;/span&gt; build/versions.env | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt; &lt;span class="s1"&gt;'\0'&lt;/span&gt; | xargs &lt;span class="nt"&gt;-0&lt;/span&gt; &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="s2"&gt;%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;echo
&lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The right version of &lt;a href="https://ant.apache.org/" rel="noopener noreferrer"&gt;Apache Ant&lt;/a&gt; can then easily be installed locally (inside the &lt;code&gt;.tmp\&lt;/code&gt; folder, by convention) and the proper environment variable be set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SET ANT_HOME=%CD%\.tmp\apache-ant-%_ANT_VERSION%
IF NOT EXIST "%ANT_HOME%\bin\ant.bat" (
    IF NOT EXIST .tmp MKDIR .tmp
    powershell.exe -NoLogo -NonInteractive -ExecutionPolicy ByPass -Command "&amp;amp; { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest http://mirrors.ircam.fr/pub/apache//ant/binaries/apache-ant-$Env:_ANT_VERSION-bin.zip -OutFile .tmp\apache-ant-$Env:_ANT_VERSION-bin.zip; }"
    IF ERRORLEVEL 1 GOTO ERROR_ANT
    powershell.exe -NoLogo -NonInteractive -ExecutionPolicy ByPass -Command "Expand-Archive -Path .tmp\apache-ant-$Env:_ANT_VERSION-bin.zip -DestinationPath .tmp -Force"
    IF ERRORLEVEL 1 GOTO ERROR_ANT
)
ECHO SET ANT_HOME=%ANT_HOME%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A more traditional approach would have been to require everyone to install &lt;a href="https://ant.apache.org/" rel="noopener noreferrer"&gt;Apache Ant&lt;/a&gt; as a prerequisite but:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;this adds a lot of burden to developers in the form of a lot of dependencies to install prior to developping proper.&lt;/li&gt;
&lt;li&gt;part of the installation process is setting up a global environment variable (&lt;code&gt;%ANT_HOME%&lt;/code&gt;) because there is no way to automatically detect these installation paths.&lt;/li&gt;
&lt;li&gt;this makes the use of different versions (of &lt;a href="https://ant.apache.org/" rel="noopener noreferrer"&gt;Apache Ant&lt;/a&gt; for instance) in different projects, or even in different branches) very… tricky.&lt;/li&gt;
&lt;li&gt;a CI platform is a particular developer that cannot perform manual installations, and so I would have to make sure that the right versions of the different dependencies are already available there. Or I would have to install them and write those scripts anyway…&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The only real prerequisite for this project is thus Java 8. It has a proper installer and different versions can be installed on the same computer. All you have to do is use the registry (yes, I love the registry) to find where it has been installed and initialize the &lt;code&gt;%JAVA_HOME%&lt;/code&gt; environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SET JAVA_HOME=
FOR /F "tokens=1,2*" %%i IN ('REG QUERY "HKLM\SOFTWARE\JavaSoft\Java Development Kit\1.8" /V JavaHome') DO (
    IF "%%i"=="JavaHome" (
        SET "JAVA_HOME=%%k"
    )
)
IF "%PROCESSOR_ARCHITECTURE%"=="AMD64" (
    FOR /F "tokens=1,2*" %%i IN ('REG QUERY "HKLM\SOFTWARE\Wow6432Node\JavaSoft\Java Development Kit\1.8" /V JavaHome') DO (
        IF "%%i"=="JavaHome" (
            SET "JAVA_HOME=%%k"
        )
    )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And so this was more work here than we had seen previously in the equivalent sections, but in the end developers can just clone the repository and execute the build locally right away. Which is exactly what a CI platform does by the way…&lt;/p&gt;

&lt;h3&gt;
  
  
  The CI configuration file
&lt;/h3&gt;

&lt;p&gt;We solved every major problem previously so that this configuration should be a breeze, and I think it is. The configuration for &lt;a href="https://travis-ci.org/" rel="noopener noreferrer"&gt;Travis CI&lt;/a&gt; (yes, again) is simply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;. build/.bashrc&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;if [! -d ivy]; then mkdir ivy; fi&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;if [! -f ivy/ivy.jar]; then wget -nv -O ivy/ivy.jar https://repo1.maven.org/maven2/org/apache/ivy/ivy/$_IVY_VERSION/ivy-$_IVY_VERSION.jar; fi&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;$JAVA_HOME/bin/java -jar ivy/ivy.jar -retrieve "ivy/lib/[conf]/[artifact].[ext]"&lt;/span&gt;
&lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;$ANT_HOME/bin/ant -noclasspath -nouserlib -noinput -lib "ivy/lib/test" -logger org.apache.tools.ant.listener.AnsiColorLogger -f build.xml release&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also chose this project to try and create a configuration for &lt;a href="https://azure.microsoft.com/en-us/services/devops/pipelines/" rel="noopener noreferrer"&gt;Azure Pipelines&lt;/a&gt;, and here it is in the form of &lt;a href="https://github.com/mcartoixa/ant-sfdx/blob/master/azure-pipelines.yml" rel="noopener noreferrer"&gt;&lt;code&gt;azure-pipeline.yml&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;BatchScript@1&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build\SetEnv.bat&lt;/span&gt;
    &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/useCurrentJavaHome&lt;/span&gt;
    &lt;span class="na"&gt;modifyEnvironment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;True&lt;/span&gt;
    &lt;span class="na"&gt;workingFolder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(Build.Repository.LocalPath)&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;IF NOT EXIST ivy MKDIR ivy&lt;/span&gt;
    &lt;span class="s"&gt;PUSHD ivy&lt;/span&gt;
    &lt;span class="s"&gt;IF NOT EXIST ivy.jar (&lt;/span&gt;
        &lt;span class="s"&gt;powershell.exe -NoLogo -NonInteractive -ExecutionPolicy ByPass -Command "&amp;amp; { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest https://repo1.maven.org/maven2/org/apache/ivy/ivy/$Env:_IVY_VERSION/ivy-$Env:_IVY_VERSION.jar -OutFile ivy.jar; }"&lt;/span&gt;
        &lt;span class="s"&gt;IF ERRORLEVEL 1 GOTO END_ERROR&lt;/span&gt;
    &lt;span class="s"&gt;)&lt;/span&gt;
    &lt;span class="s"&gt;POPD&lt;/span&gt;
    &lt;span class="s"&gt;"%JAVA_HOME%\bin\java.exe" -jar ivy\ivy.jar -retrieve "ivy\lib\[conf]\[artifact].[ext]"&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ant@1&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;buildFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;build.xml'&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;-noclasspath -nouserlib -noinput -lib ivy/lib/test -Dverbosity=verbose&lt;/span&gt;
    &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;release&lt;/span&gt;
    &lt;span class="na"&gt;publishJUnitResults&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;testResultsFiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(Build.Repository.LocalPath)\tmp\obj\test\results\**\TEST-*.xml'&lt;/span&gt;
    &lt;span class="na"&gt;antHomeDirectory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(ANT_HOME)'&lt;/span&gt;
    &lt;span class="na"&gt;jdkVersionOption&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.8&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As promised, this shows I am not tied to a particular CI platform: I cannot imagine the pain it would have been to try a new CI platform if all the logic for the build had been located in these configuration files. It could even have been much shorter had I decided to include the &lt;a href="https://ant.apache.org/ivy/" rel="noopener noreferrer"&gt;Apache Ivy&lt;/a&gt; related commands in the &lt;code&gt;build\SetEnv.bat&lt;/code&gt; part. I am sure I must have had my reasons not to at the time…&lt;/p&gt;

&lt;p&gt;Just as a side note, I had to create and use a special &lt;code&gt;/useCurrentJavaHome&lt;/code&gt; argument to the &lt;code&gt;build\SetEnv.bat&lt;/code&gt; so that it would not override the &lt;code&gt;%JAVA_HOME%&lt;/code&gt; environment variable: registry installation detection would not work on &lt;a href="https://azure.microsoft.com/en-us/services/devops/pipelines/" rel="noopener noreferrer"&gt;Azure Pipelines&lt;/a&gt;. No wonder we need tools like Docker everywhere now when the registry is gone…&lt;/p&gt;

&lt;h2&gt;
  
  
  Ant as a build tool
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://ant.apache.org/" rel="noopener noreferrer"&gt;Apache Ant&lt;/a&gt; has a bad reputation and I can understand why. I think its main failures lie in the fact that it was the first of the next-gen build tools that promised:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tight integration with a specific ecosystem (Java in this case).&lt;/li&gt;
&lt;li&gt;multi-platform description (and execution).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And as it was the first it was inevitably the worst. Especially when it comes to understanding the core concepts, &lt;a href="https://ant.apache.org/manual/conceptstypeslist.html" rel="noopener noreferrer"&gt;the types to understand and use&lt;/a&gt; are far too numerous and too complex (I mean: &lt;a href="https://ant.apache.org/manual/Types/filelist.html" rel="noopener noreferrer"&gt;FileList&lt;/a&gt;s &lt;em&gt;and&lt;/em&gt; &lt;a href="https://ant.apache.org/manual/Types/fileset.html" rel="noopener noreferrer"&gt;FileSet&lt;/a&gt;s?…). And after all this time you would think something really should have been done about this (that would have required some amount of breaking changes along the way). But these concepts are key to understand, and cheap “solutions” like &lt;em&gt;ant-contrib&lt;/em&gt; (I don’t even want to link to this project) only help you make a mess of your builds.&lt;/p&gt;

&lt;p&gt;On the other hand, a lot of people seem to resent &lt;a href="https://ant.apache.org/" rel="noopener noreferrer"&gt;Apache Ant&lt;/a&gt; because of &lt;a href="https://blog.codinghorror.com/xml-the-angle-bracket-tax/" rel="noopener noreferrer"&gt;The Angle Bracket Tax&lt;/a&gt;. But like any language it is the developer’s responsibility to remember that he or she writes code for his or her fellow human colleagues. And I will take XML any day over JSON (or YAML), for both power and expressiveness, thank you very much.&lt;/p&gt;

&lt;p&gt;As I said in the beginning of this post, the Java world seems to rely heavily on conventions now with tools like &lt;a href="https://maven.apache.org/" rel="noopener noreferrer"&gt;Maven&lt;/a&gt; or &lt;a href="https://gradle.org/" rel="noopener noreferrer"&gt;Gradle&lt;/a&gt;, and this is very fine. But if you need to get further than those conventions &lt;a href="https://ant.apache.org/" rel="noopener noreferrer"&gt;Apache Ant&lt;/a&gt; will still take you a long way, that is usually more convenient than writing plugins for these tools.&lt;/p&gt;

&lt;p&gt;We will meet &lt;a href="https://ant.apache.org/" rel="noopener noreferrer"&gt;Apache Ant&lt;/a&gt; again soon.&lt;/p&gt;

</description>
      <category>ci</category>
      <category>java</category>
      <category>ant</category>
    </item>
    <item>
      <title>Continuous Integration for PHP</title>
      <dc:creator>Mac</dc:creator>
      <pubDate>Mon, 28 Sep 2020 05:59:00 +0000</pubDate>
      <link>https://dev.to/mcartoixa/continuous-integration-for-php-1nkc</link>
      <guid>https://dev.to/mcartoixa/continuous-integration-for-php-1nkc</guid>
      <description>&lt;p&gt;It has been a very long time since I have used PHP in any capacity for web development. And I am not planning doing so again any time soon (sorry). But I have had the opportunity to apply &lt;a href="https://dev.to/mcartoixa/my-take-on-continuous-integration-3gep"&gt;my practices&lt;/a&gt; on a very small, though still useful scale in PHP. From what I could gather it is indeed very possible to properly and continuously integrate PHP projects, provided you use the right tools for the job: a build tool and a dependency manager (optional but always recommended).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A simple project&lt;/li&gt;
&lt;li&gt;A more complete project&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A simple project
&lt;/h2&gt;

&lt;p&gt;Disclaimer: I do not condone illegal behaviour… But for research purposes I have created a custom search module for &lt;a href="https://www.synology.com/" rel="noopener noreferrer"&gt;Synology&lt;/a&gt; &lt;a href="https://www.synology.com/en-global/knowledgebase/DSM/help/DownloadStation/DownloadStation_desc" rel="noopener noreferrer"&gt;Download Manager&lt;/a&gt;, and as it happens this requires PHP. Hence the &lt;a href="https://github.com/mcartoixa/synology-dlm-rarbg" rel="noopener noreferrer"&gt;synology-dlm-rarbg&lt;/a&gt; project. No Laravel or Symfony involved here obviously but I want to believe that the concepts will remain the same on a more consequent project.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/mcartoixa" rel="noopener noreferrer"&gt;
        mcartoixa
      &lt;/a&gt; / &lt;a href="https://github.com/mcartoixa/synology-dlm-rarbg" rel="noopener noreferrer"&gt;
        synology-dlm-rarbg
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      RarBG search module for Synology Download Manager 
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The main elements of the project are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
The build file (&lt;code&gt;build.xml&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
The script file (&lt;code&gt;build.bat&lt;/code&gt;) that helps executing the build file locally.&lt;/li&gt;
&lt;li&gt;
The CI configuration file (&lt;code&gt;.travis.yml&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The build file
&lt;/h3&gt;

&lt;p&gt;Given my lack of recent experience with the technology I was a bit worried at first about the state of software development in PHP. But it turned out surprisingly pretty decent: you can find a fine dependency manager in &lt;a href="https://getcomposer.org/" rel="noopener noreferrer"&gt;Composer&lt;/a&gt; and a very decent build tool in &lt;a href="https://www.phing.info/" rel="noopener noreferrer"&gt;Phing&lt;/a&gt;. It is based on the old timer &lt;a href="https://ant.apache.org/" rel="noopener noreferrer"&gt;Apache Ant&lt;/a&gt; and so probably suffers from the same drawbacks (that I will cover in a subsequent post). But like the original: as long as you respect the tool (which involves understanding it…) you can go pretty far with it.&lt;/p&gt;

&lt;p&gt;So &lt;a href="https://www.phing.info/" rel="noopener noreferrer"&gt;Phing&lt;/a&gt; it is, and the &lt;code&gt;build.xml&lt;/code&gt; file defines the following targets (pretty much consistent with what we have already seen in &lt;a href="https://dev.to/mcartoixa/continuous-integration-for-the-net-framework-1hc6"&gt;.NET&lt;/a&gt;, &lt;a href="https://dev.to/mcartoixa/continuous-integration-for-node-js-3iak"&gt;node.js&lt;/a&gt; and &lt;a href="https://dev.to/mcartoixa/continuous-integration-for-ruby-4h56"&gt;Ruby&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;clean&lt;/em&gt;: cleans the build (the &lt;code&gt;tmp\&lt;/code&gt; directory).&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;build&lt;/em&gt;: runs &lt;a href="https://github.com/overtrue/phplint" rel="noopener noreferrer"&gt;PHPLint&lt;/a&gt; on the source code and copies the source code and the dependencies in the &lt;code&gt;tmp\out\bin&lt;/code&gt; folder.

&lt;ul&gt;
&lt;li&gt;If I had to do it today I would probably isolate the first action in an &lt;em&gt;analyze&lt;/em&gt; target. The second action would do well in the &lt;em&gt;package&lt;/em&gt; target. My own consistency evolves over time…&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;em&gt;test&lt;/em&gt;: executes the tests using &lt;a href="https://phpunit.de/" rel="noopener noreferrer"&gt;PHPUnit&lt;/a&gt;.&lt;/li&gt;

&lt;li&gt;

&lt;em&gt;package&lt;/em&gt;: creates a package for the search module (which is an archive of the source code and its dependencies).&lt;/li&gt;

&lt;li&gt;

&lt;em&gt;rebuild&lt;/em&gt;: shortcut for the combination of &lt;em&gt;clean&lt;/em&gt; and &lt;em&gt;build&lt;/em&gt;.&lt;/li&gt;

&lt;li&gt;

&lt;em&gt;release&lt;/em&gt;: shortcut for the combination of &lt;em&gt;clean&lt;/em&gt;, &lt;em&gt;build&lt;/em&gt;, and &lt;em&gt;package&lt;/em&gt;.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Before showing some code it appears that I may have over engineered this build a bit: I created a second build file &lt;a href="https://github.com/mcartoixa/synology-dlm-rarbg/blob/master/dlm/build.xml" rel="noopener noreferrer"&gt;&lt;code&gt;dlm\build.xml&lt;/code&gt;&lt;/a&gt; that is called by the first. Obviously I cannot clearly remember why right now but I think it had to do with the possibility of packaging multiple search modules in the same project. Anyway, how do you call another build file then? Simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;target&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"test"&lt;/span&gt; &lt;span class="na"&gt;depends=&lt;/span&gt;&lt;span class="s"&gt;"prepare.build"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;phing&lt;/span&gt; &lt;span class="na"&gt;phingfile=&lt;/span&gt;&lt;span class="s"&gt;"build.xml"&lt;/span&gt; &lt;span class="na"&gt;dir=&lt;/span&gt;&lt;span class="s"&gt;"./dlm"&lt;/span&gt; &lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;"test"&lt;/span&gt; &lt;span class="na"&gt;inheritAll=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;haltonfailure=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/target&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;em&gt;test&lt;/em&gt; target must execute &lt;a href="https://phpunit.de/" rel="noopener noreferrer"&gt;PHPUnit&lt;/a&gt; on the tests files, and it needs to have the dependencies properly downloaded before that (using &lt;a href="https://getcomposer.org/" rel="noopener noreferrer"&gt;Composer&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;target&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"test"&lt;/span&gt; &lt;span class="na"&gt;depends=&lt;/span&gt;&lt;span class="s"&gt;"test.prepare"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;phpunit&lt;/span&gt; &lt;span class="na"&gt;printsummary=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;bootstrap=&lt;/span&gt;&lt;span class="s"&gt;"vendor/autoload.php"&lt;/span&gt; &lt;span class="na"&gt;haltonerror=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;haltonfailure=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;formatter&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"plain"&lt;/span&gt; &lt;span class="na"&gt;usefile=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;batchtest&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;fileset&lt;/span&gt; &lt;span class="na"&gt;dir=&lt;/span&gt;&lt;span class="s"&gt;"./tests"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;include&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"**/*Test*.php"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/fileset&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/batchtest&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/phpunit&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/target&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;target&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"test.prepare"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;composer&lt;/span&gt; &lt;span class="na"&gt;command=&lt;/span&gt;&lt;span class="s"&gt;"update"&lt;/span&gt; &lt;span class="na"&gt;composer=&lt;/span&gt;&lt;span class="s"&gt;"bin/composer.phar"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;arg&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"-q"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;arg&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"-n"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/composer&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/target&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key in this project is to be able to package the search module in the format that is expected by the &lt;a href="https://www.synology.com/en-global/knowledgebase/DSM/help/DownloadStation/DownloadStation_desc" rel="noopener noreferrer"&gt;Download Manager&lt;/a&gt;, which is explained in &lt;a href="https://global.download.synology.com/download/Document/Software/DeveloperGuide/Package/DownloadStation/All/enu/DLM_Guide.pdf" rel="noopener noreferrer"&gt;a PDF document downloadable somewhere on the Synology website&lt;/a&gt;. I would never remember how, but now it has been described in &lt;a href="https://www.phing.info/" rel="noopener noreferrer"&gt;Phing&lt;/a&gt; it seems pretty straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;target&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"package"&lt;/span&gt; &lt;span class="na"&gt;depends=&lt;/span&gt;&lt;span class="s"&gt;"package.prepare,build"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;tar&lt;/span&gt; &lt;span class="na"&gt;destfile=&lt;/span&gt;&lt;span class="s"&gt;"tmp/out/bin/mcartoixa_rarbg.dlm"&lt;/span&gt; &lt;span class="na"&gt;compression=&lt;/span&gt;&lt;span class="s"&gt;"gzip"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;fileset&lt;/span&gt; &lt;span class="na"&gt;dir=&lt;/span&gt;&lt;span class="s"&gt;"tmp/bin/dlm"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;include&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;" **/**"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;exclude&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"composer.*"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/fileset&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/tar&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/target&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The script file
&lt;/h3&gt;

&lt;p&gt;The gist of the script file &lt;a href="https://github.com/mcartoixa/synology-dlm-rarbg/blob/master/build.bat" rel="noopener noreferrer"&gt;&lt;code&gt;build.bat&lt;/code&gt;&lt;/a&gt; is simply to install the dependencies, including &lt;a href="https://www.phing.info/" rel="noopener noreferrer"&gt;Phing&lt;/a&gt;, and then run the build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CALL .\bin\composer.bat install -q -n
CALL .\vendor\bin\phing.bat -f %PROJECT% %TARGET%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see &lt;a href="https://getcomposer.org/" rel="noopener noreferrer"&gt;Composer&lt;/a&gt; is itself stored in the repository as a &lt;a href="https://www.php.net/manual/en/book.phar.php" rel="noopener noreferrer"&gt;PHAR&lt;/a&gt; archive. I could have chosen &lt;a href="https://getcomposer.org/doc/faqs/how-to-install-composer-programmatically.md" rel="noopener noreferrer"&gt;to download it instead&lt;/a&gt;. The key here is that the repository is ready for development: nothing to configure.&lt;/p&gt;

&lt;h3&gt;
  
  
  The CI configuration file
&lt;/h3&gt;

&lt;p&gt;For whatever reason I have again chosen &lt;a href="https://travis-ci.org/" rel="noopener noreferrer"&gt;Travis CI&lt;/a&gt; as my Continuous Integration platform, which configuration is simply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;php bin/composer.phar install -n&lt;/span&gt;

&lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;vendor/bin/phing -f build.xml prepare.version release&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A more complete project
&lt;/h2&gt;

&lt;p&gt;Nothing much to be found here (as usual?), but I think proper foundations have been laid out above. As with every technology it is essential to delve into the build tool, understand it and use it to its full potential. For instance, if &lt;a href="https://www.phing.info/" rel="noopener noreferrer"&gt;Phing&lt;/a&gt; is anything like &lt;a href="https://ant.apache.org/" rel="noopener noreferrer"&gt;Apache Ant&lt;/a&gt; (and it looks like it is), understanding the core structures (like &lt;a href="https://www.phing.info/guide/chunkhtml/FileList.html" rel="noopener noreferrer"&gt;FileList&lt;/a&gt;s of &lt;a href="https://www.phing.info/guide/chunkhtml/FileSet.html" rel="noopener noreferrer"&gt;FileSet&lt;/a&gt;s) is very important.&lt;/p&gt;

&lt;p&gt;Also, I would tend to install every dependency locally instead of globally (as part of the build, including frameworks like &lt;a href="https://laravel.com/" rel="noopener noreferrer"&gt;Laravel&lt;/a&gt; for instance). It makes it easier to handle the &lt;code&gt;%PATH%&lt;/code&gt; consistently for everyone (including your Continuous Integration platform) and it makes it easier to use different versions on different projects (or even branches).&lt;/p&gt;

</description>
      <category>ci</category>
      <category>php</category>
      <category>phing</category>
    </item>
    <item>
      <title>Continuous Integration for Ruby</title>
      <dc:creator>Mac</dc:creator>
      <pubDate>Mon, 21 Sep 2020 07:57:00 +0000</pubDate>
      <link>https://dev.to/mcartoixa/continuous-integration-for-ruby-4h56</link>
      <guid>https://dev.to/mcartoixa/continuous-integration-for-ruby-4h56</guid>
      <description>&lt;p&gt;I have had to deal with a few projects in Ruby recently and I have tried to adapt &lt;a href="https://dev.to/mcartoixa/my-take-on-continuous-integration-3gep"&gt;my practices&lt;/a&gt; to this new environment. On a personal level this means less cognitive load when I have to come back to these projects months later, even more so because Ruby is far from my area of expertise. As it happens I have had quite some trouble implementing Continuous Integration because of (seemingly) conflicting conventions. But I have tried anyway:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A simple project&lt;/li&gt;
&lt;li&gt;A more complete project&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A simple project
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/mcartoixa/mcartoixa.github.io" rel="noopener noreferrer"&gt;My website (which includes my blog)&lt;/a&gt; is in fact based on Ruby, as it is a &lt;a href="https://jekyllrb.com/" rel="noopener noreferrer"&gt;Jekyll&lt;/a&gt; project hosted on &lt;a href="https://pages.github.com/" rel="noopener noreferrer"&gt;GitHub Pages&lt;/a&gt; (for now). Granted, it is not a proper Ruby project (as Ruby is not required on the target server for instance) but I find it is a good introduction to the concepts and the tools involved.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/mcartoixa" rel="noopener noreferrer"&gt;
        mcartoixa
      &lt;/a&gt; / &lt;a href="https://github.com/mcartoixa/mcartoixa.github.io" rel="noopener noreferrer"&gt;
        mcartoixa.github.io
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The main elements of the project are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
The build file (&lt;code&gt;Rakefile&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
The script file (&lt;code&gt;build.sh&lt;/code&gt;) that helps executing the build file locally.&lt;/li&gt;
&lt;li&gt;
The CI configuration file (&lt;code&gt;.travis.yml&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The build file
&lt;/h3&gt;

&lt;p&gt;I don’t think there is much choice here in the build tool to use in a Ruby environment, so &lt;a href="https://ruby.github.io/rake/" rel="noopener noreferrer"&gt;Rake&lt;/a&gt; it is. Here is the whole file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;PROJECT_NAME&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="s1"&gt;'www.mcartoixa.me'&lt;/span&gt;
&lt;span class="no"&gt;PROJECT_VERSION&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="s1"&gt;'0.0.0'&lt;/span&gt;

&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="s1"&gt;'build/common.rake'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alright, I cheated. I like to gather everything build related in a &lt;code&gt;build\&lt;/code&gt; folder and I did that here. But what we loose in straightforwardness we gain in consistency, because in &lt;a href="https://rubyonrails.org/" rel="noopener noreferrer"&gt;a Rails project&lt;/a&gt;, where many tasks and targets are imported, I find this actually brings more readability to the project.&lt;/p&gt;

&lt;p&gt;So, what’s in there? &lt;a href="https://github.com/mcartoixa/mcartoixa.github.io/blob/master/build/common.rake" rel="noopener noreferrer"&gt;The build file&lt;/a&gt; defines the following targets (ie &lt;em&gt;tasks&lt;/em&gt; in Rake speak), overall consistent with what we have seen in other technologies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;build:clean&lt;/em&gt;: cleans the build (the &lt;code&gt;tmp\&lt;/code&gt; directory).&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;build:compile&lt;/em&gt;: creates the static version of the website (using Jekyll), ready for deployment (in the &lt;code&gt;tmp\obj\bin\&lt;/code&gt; folder).&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;build:test&lt;/em&gt;: tests the project.

&lt;ul&gt;
&lt;li&gt;I want to check the links in my website using &lt;a href="https://github.com/gjtorikian/html-proofer" rel="noopener noreferrer"&gt;HTMLProofer&lt;/a&gt;. I could use &lt;a href="https://github.com/episource/jekyll-html-proofer" rel="noopener noreferrer"&gt;a Jekyll plugin&lt;/a&gt; for this, but testing links proves rather time consuming and error prone (more on that later) and I would rather keep it out of my development workflow.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;em&gt;build:analyze&lt;/em&gt;: checks deprecation and configuration issues (using Jekyll).&lt;/li&gt;

&lt;li&gt;

&lt;em&gt;build:package&lt;/em&gt;: creates a deployable archive of the static version of the website (in the &lt;code&gt;tmp\out\bin\​&lt;/code&gt; folder).

&lt;ul&gt;
&lt;li&gt;This is of little use right now as everything is handled by GitHub pages, including deployment, but this is partly a proof of concept. And I like the freedom of knowing I could host my website elsewhere at any moment.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;em&gt;build:build&lt;/em&gt;: shortcut for the combination of &lt;em&gt;build:compile&lt;/em&gt;, &lt;em&gt;build:test&lt;/em&gt; and &lt;em&gt;build:analyze&lt;/em&gt;.&lt;/li&gt;

&lt;li&gt;

&lt;em&gt;build:rebuild&lt;/em&gt;: shortcut for the combination of &lt;em&gt;build:clean&lt;/em&gt; and &lt;em&gt;build:build&lt;/em&gt;.&lt;/li&gt;

&lt;li&gt;

&lt;em&gt;build:release&lt;/em&gt;: shortcut for the combination of &lt;em&gt;build:clean&lt;/em&gt;, &lt;em&gt;build:build&lt;/em&gt;, and &lt;em&gt;build:package&lt;/em&gt;.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;What’s remarkable here is the use of namespaces in the tasks names. Again, this proves useful (and consistent) in &lt;a href="https://rubyonrails.org/" rel="noopener noreferrer"&gt;a Rails project&lt;/a&gt;. And so the description of the &lt;em&gt;build:compile&lt;/em&gt; task is simply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="s1"&gt;'build'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="s1"&gt;'compile'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'jekyll'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'build'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-dtmp/obj/bin/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'JEKYLL_ENV=production'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'--strict_front_matter'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;em&gt;build:test&lt;/em&gt; task is slightly more complicated, but is in fact mainly &lt;a href="https://github.com/gjtorikian/html-proofer" rel="noopener noreferrer"&gt;HTMLProofer&lt;/a&gt; configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'html-proofer'&lt;/span&gt;
&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="s1"&gt;'build'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="s1"&gt;'test'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="sx"&gt;%w[compile]&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="s1"&gt;'test'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;:allow_hash_href&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;:assume_extension&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;:check_favicon&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;:check_opengraph&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;:file_ignore&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sr"&gt;/\/blog\/software-craftsmanship\/20[01]\d\//&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# Do not check old blog posts&lt;/span&gt;
        &lt;span class="sr"&gt;/\/sections\//&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;:root_dir&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'tmp/obj/bin/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;:url_ignore&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'http://html5up.net'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'https://chrisbobbe.github.io/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# Included by the template&lt;/span&gt;
        &lt;span class="s1"&gt;'https://www.facebook.com/mathieu.cartoixa'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# 404 only when checked...&lt;/span&gt;
        &lt;span class="s1"&gt;'https://www.linkedin.com/in/cartoixa/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'https://www.linkedin.com/in/cartoixa/?trk=profile-badge'&lt;/span&gt; &lt;span class="c1"&gt;# 999 only when checked&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="no"&gt;HTMLProofer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check_directory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tmp/obj/bin/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then the &lt;em&gt;build:package&lt;/em&gt; task requires an external package for the creation of the archive: the default &lt;a href="https://ruby-doc.org/stdlib-trunk/libdoc/rake/rdoc/Rake/PackageTask.html" rel="noopener noreferrer"&gt;PackageTask&lt;/a&gt; does not seem to support well the archiving of files outside the root of the project. This gives something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'zip'&lt;/span&gt;
&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="s1"&gt;'build'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="s1"&gt;'package'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;PACKAGE_FILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'tmp/out/bin/www.mcartoixa.me-0.0.0.zip'&lt;/span&gt;
    &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="no"&gt;PACKAGE_FILE&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="sx"&gt;%w[build:compile]&lt;/span&gt;
    &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="no"&gt;PACKAGE_FILE&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;FileUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir_p&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tmp/out/bin/'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;Zip&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;PACKAGE_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Zip&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CREATE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;zf&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;FileList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tmp/obj/bin/**/*'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
          &lt;span class="n"&gt;zf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete_prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tmp/obj/bin/'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="s1"&gt;'build'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;PACKAGE_FILE&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="s1"&gt;'package'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="sx"&gt;%w[package:build]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please note here that one of the main features of a build tool is the ability to describe dependencies between targets. This is demonstrated here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;em&gt;build:package&lt;/em&gt; task depends on &lt;em&gt;build:​package:build&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;the &lt;em&gt;build:​package:build&lt;/em&gt; task depends on the rule to build &lt;em&gt;tmp/out/bin/&lt;a href="http://www.cartoixa.me-0.0.0.zip" rel="noopener noreferrer"&gt;www.cartoixa.me-0.0.0.zip&lt;/a&gt;&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;the rule to build &lt;em&gt;tmp/out/bin/&lt;a href="http://www.mcartoixa.me-0.0.0.zip" rel="noopener noreferrer"&gt;www.mcartoixa.me-0.0.0.zip&lt;/a&gt;&lt;/em&gt; depends on &lt;em&gt;build:compile&lt;/em&gt; (the static website needs to have been created before attempting to package it).This might seem a bit confusing to the newcomer but it is in fact very powerful (when used sensibly).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last thing I would like to add here is that &lt;a href="https://github.com/gjtorikian/html-proofer" rel="noopener noreferrer"&gt;HTMLProofer&lt;/a&gt; is a fine tool but:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I still have to find a way for it not to complain when I just added a new blog post that it in fact does not exist (yet).&lt;/li&gt;
&lt;li&gt;sometimes websites will fail because… reasons, and it should not prevent my own build from succeeding. I guess I should make sure that it only triggers warnings instead of errors.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The script file
&lt;/h3&gt;

&lt;p&gt;Having managed to run an instance of &lt;a href="https://www.redmine.org/" rel="noopener noreferrer"&gt;Redmine&lt;/a&gt; on Windows Server 2012 and IIS, I figured Ruby was a cross-platform technology. In reality I have since experienced that many dependencies that require native compilation are poorly (or not at all) maintained: running a Rails application with MariaDB and Redis connections is a nightmare (when possible at all).&lt;/p&gt;

&lt;p&gt;Disclaimer aside (no &lt;code&gt;build.bat&lt;/code&gt; then), the gist of the &lt;code&gt;build.sh&lt;/code&gt; file is simply (using &lt;a href="https://bundler.io/" rel="noopener noreferrer"&gt;Bundler&lt;/a&gt; here):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;bundle check &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;bundle clean
&lt;span class="k"&gt;else
  &lt;/span&gt;bundle &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--standalone&lt;/span&gt; &lt;span class="nt"&gt;--clean&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rake build:&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TASK&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The CI configuration file
&lt;/h3&gt;

&lt;p&gt;We need a CI platform that runs MacOS or Linux, so &lt;a href="https://travis-ci.org/" rel="noopener noreferrer"&gt;Travis CI&lt;/a&gt; will do just fine. Once again, having dealt with all the difficulties in the build file, the &lt;code&gt;.travis.yml&lt;/code&gt; is just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;before_install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gem install bundler&lt;/span&gt;
&lt;span class="na"&gt;install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle install --standalone --clean --jobs=3 --retry=3&lt;/span&gt;
&lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle exec rake build:release&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A more complete project
&lt;/h2&gt;

&lt;p&gt;This is where the trouble really begins. Having tried to apply the same principles to a more complex Rails project I came against difficulties that seem hard to overcome. This may be because of my lack of knowledge of the environment itself, but it might also be because of conflicting conventions. I have the sense for instance that everything is done to allow the developer to achieve everything (and anything) from the command line. This might mean many things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you don’t have to work in another directory, and in fact you should not. It seems quite a hassle (compared to Javascript for instance) to handle a copy of the source code elsewhere where you retrieve only production dependencies for packaging purposes.&lt;/li&gt;
&lt;li&gt;some tools have &lt;em&gt;memory&lt;/em&gt;. &lt;a href="https://bundler.io/" rel="noopener noreferrer"&gt;Bundler&lt;/a&gt; will remember your current environment (&lt;em&gt;development&lt;/em&gt;, &lt;em&gt;production&lt;/em&gt;) for instance.&lt;/li&gt;
&lt;li&gt;worse, the so called &lt;a href="https://bundler.io/v2.1/man/bundle-install.1.html#DEPLOYMENT-MODE" rel="noopener noreferrer"&gt;&lt;em&gt;Deployment Mode&lt;/em&gt;&lt;/a&gt; will explicitely screw up your development environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hard to &lt;a href="https://dev.to/mcartoixa/my-take-on-continuous-integration-3gep#build-in-1-step"&gt;run your build locally&lt;/a&gt; with these features!… This is not intended as a rant though, and if you have some insight on how to reconcile both worlds: please share.&lt;/p&gt;

&lt;p&gt;Though I cannot share a real and complete project here I can show Rakefile elements that can be of some use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how to run Ruby to check your source files syntax (this goes in the &lt;em&gt;build:compile&lt;/em&gt; target):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;SOURCE_FILES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;FileList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'.ruby-*'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'config.ru'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Gemfile*'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Rakefile'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'app/ **/*'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'bin/** /*'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'config/ **/*'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'db/** /*'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'lib/ **/*'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'public/** /*'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'vendor/**/*'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;fl&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;fl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exclude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^config\/app_parameters.yml$/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;fl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exclude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\.log$/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="no"&gt;RUBY_FILES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;FileList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SOURCE_FILES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ends_with?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'.rb'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;fl&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;fl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exclude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^vendor\//&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="s1"&gt;'build'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="s1"&gt;'.rb.log'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nb"&gt;proc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;tn&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;tn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\.rb\.log$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'.rb'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/tmp\/obj\//&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="no"&gt;FileUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir_p&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="s2"&gt;"-wc &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;source&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &amp;gt; &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="s1"&gt;'compile'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;RUBY_FILES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\.rb$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'.rb.log'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'tmp/obj/'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We define rules for &lt;code&gt;*.rb.log&lt;/code&gt; files that will be generated by running Ruby on the corresponding &lt;code&gt;*.rb&lt;/code&gt; file and storing the output. The task we define then depends on the generation of those files.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how to run &lt;a href="https://github.com/troessner/reek" rel="noopener noreferrer"&gt;Reek&lt;/a&gt; on your Ruby files (this creates a &lt;em&gt;reek&lt;/em&gt; target):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'reek/rake/task'&lt;/span&gt;
&lt;span class="no"&gt;Reek&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'.reek.yml'&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;source_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RUBY_FILES&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reek_opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'-s --force-exclusion --no-progress'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;how to run &lt;a href="https://rubocop.org/" rel="noopener noreferrer"&gt;Rubocop&lt;/a&gt; on your Ruby files (this creates a &lt;em&gt;rubocop&lt;/em&gt; target):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rubocop/rake_task'&lt;/span&gt;
&lt;span class="no"&gt;RuboCop&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RakeTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:rubocop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formatters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'clang'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'html'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'tmp/rubocop-results.html'&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;patterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RUBY_FILES&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As I wrote before, should you choose to use static analysis tools such as &lt;a href="https://github.com/troessner/reek" rel="noopener noreferrer"&gt;Reek&lt;/a&gt; and/or &lt;a href="https://rubocop.org/" rel="noopener noreferrer"&gt;Rubocop&lt;/a&gt; (and you should), you should do it as soon as possible in the life of your project in order to avoid to have hundreds of warnings to correct at once…&lt;/p&gt;

&lt;p&gt;Packaging proves the more challenging because when you are generating assets as part of the build (like minified Javascript files for instance), then you have to create &lt;a href="https://ruby-doc.org/stdlib-trunk/libdoc/rake/rdoc/Rake/FileList.html" rel="noopener noreferrer"&gt;file lists&lt;/a&gt; &lt;em&gt;after&lt;/em&gt; those files have been generated (&lt;a href="https://guides.rubyonrails.org/asset_pipeline.html#precompiling-assets" rel="noopener noreferrer"&gt;target &lt;em&gt;assets:precompile&lt;/em&gt; in a Rails project&lt;/a&gt;). This gives something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rake/packagetask'&lt;/span&gt;
&lt;span class="no"&gt;PACKAGED_FILES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;FileList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SOURCE_FILES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;fl&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;fl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exclude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^app\/assets\//&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="s1"&gt;'build'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="s1"&gt;'package'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;pt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PackageTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'my_application'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'0.0.0'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;need_tar_gz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;package_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'tmp/obj/bin/'&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;package_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;PACKAGED_FILES&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;package_dir_path&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;'/public/assets'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="sx"&gt;%w[assets:precompile]&lt;/span&gt;
    &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;package_dir_path&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;'/public/assets'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;package_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;package_files&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;FileList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'public/assets/**/*'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="s1"&gt;'build'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;package_dir_path&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;'/public/assets'&lt;/span&gt;
    &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="s1"&gt;'build'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="sx"&gt;%w[package]&lt;/span&gt;
    &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="s1"&gt;'build'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;FileUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir_p&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tmp/out/bin/'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;FileUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;package_dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tar_gz_file&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'tmp/out/bin/'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="s1"&gt;'package'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="sx"&gt;%w[package:build]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The whole build kind of works when specifying specific command lines in the CI configuration (again this cannot be part of the &lt;em&gt;local build&lt;/em&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--standalone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--deployment&lt;/span&gt;
&lt;span class="nv"&gt;RAILS_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test &lt;/span&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rake build:release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not ideal as we will be packaging with &lt;em&gt;test&lt;/em&gt; dependencies as well (things like &lt;a href="http://teamcapybara.github.io/capybara/" rel="noopener noreferrer"&gt;capybara&lt;/a&gt; or &lt;a href="https://relishapp.com/rspec/rspec-rails/docs" rel="noopener noreferrer"&gt;RSpec Rails&lt;/a&gt;). But hey: work in progress.&lt;/p&gt;

</description>
      <category>ci</category>
      <category>ruby</category>
      <category>rake</category>
    </item>
    <item>
      <title>Continuous Integration for node.js</title>
      <dc:creator>Mac</dc:creator>
      <pubDate>Mon, 14 Sep 2020 05:42:00 +0000</pubDate>
      <link>https://dev.to/mcartoixa/continuous-integration-for-node-js-3iak</link>
      <guid>https://dev.to/mcartoixa/continuous-integration-for-node-js-3iak</guid>
      <description>&lt;p&gt;Applying &lt;a href="https://dev.to/mcartoixa/my-take-on-continuous-integration-3gep"&gt;the same principles&lt;/a&gt; to node.js is interesting because this is an entirely different world from .NET (for the best and for the worst):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Javascript is interpreted: there is no compiler and no inherent notion of packaging.&lt;/li&gt;
&lt;li&gt;the platform is very fragmented: even the package manager seems to be up for grabs (&lt;a href="http://videos.ncrafts.io/video/223266261" rel="noopener noreferrer"&gt;stigmergy&lt;/a&gt;, anyone?).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And so in this this installment we will have a peak at how to implement Continuous Integration in a way that is both consistent with what we have seen before and specific to this platform:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A simple project&lt;/li&gt;
&lt;li&gt;A more complete project&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A simple project
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/mcartoixa/leaflet-binglayer" rel="noopener noreferrer"&gt;leaflet-binglayer&lt;/a&gt; was a plugin for &lt;a href="https://leafletjs.com/" rel="noopener noreferrer"&gt;Leaflet (“an open-source Javascript library for mobile-friendly interactive maps”)&lt;/a&gt;. Its goal was essentially to integrate &lt;a href="https://www.bing.com/maps/" rel="noopener noreferrer"&gt;Bing Maps&lt;/a&gt; layers in a way that did not consume &lt;a href="https://docs.microsoft.com/en-us/bingmaps/getting-started/bing-maps-dev-center-help/understanding-bing-maps-transactions" rel="noopener noreferrer"&gt;too many transactions&lt;/a&gt;. This is a browser library, and not a proper node.js project, but the principles and the toolkits are remarkably the same.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/mcartoixa" rel="noopener noreferrer"&gt;
        mcartoixa
      &lt;/a&gt; / &lt;a href="https://github.com/mcartoixa/leaflet-binglayer" rel="noopener noreferrer"&gt;
        leaflet-binglayer
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Yet another Leaflet plugin that handles Bing maps layers
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The main elements of the project are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
The build file (&lt;code&gt;gulpfile.js&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
The script file (&lt;code&gt;build.bat&lt;/code&gt;) that helps executing the build file locally.&lt;/li&gt;
&lt;li&gt;
The CI configuration file (&lt;code&gt;.travis.yml&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The build file
&lt;/h3&gt;

&lt;p&gt;I have chosen &lt;a href="https://gulpjs.com/" rel="noopener noreferrer"&gt;gulp.js&lt;/a&gt; for the build description, mainly because it is the only build tool that allows it to be pure Javascript. It also hardly knows how to do anything by itself, which is always a good thing in a build tool I believe. Other alternatives I know of seem lacking in at least one regard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://gruntjs.com/" rel="noopener noreferrer"&gt;grunt.js&lt;/a&gt; is a fine tool but the description is mainly JSON, which was only designed as a data interchange format and &lt;a href="https://justin.kelly.org.au/comments-in-json/" rel="noopener noreferrer"&gt;notoriously lacks comments&lt;/a&gt;…&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://webpack.js.org/" rel="noopener noreferrer"&gt;webpack&lt;/a&gt; is a very powerful &lt;em&gt;module bundler&lt;/em&gt; that has so many features that it can sometimes masquerade as a build tool. But once again the description is only JSON (did I mention that it &lt;a href="https://stackoverflow.com/questions/244777/can-comments-be-used-in-json" rel="noopener noreferrer"&gt;lacked comments&lt;/a&gt;?) and it makes complex scenarii convoluted and sometimes impossible. I like to keep it as a module bundler, which it does best, as part of a larger build described elsewhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Key elements of the build system are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you have access to native node.js functions, which allow for simple (as well as very complex) configurations:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;gulp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;clean&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;del&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;del&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;del&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tmp/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;you have access to a &lt;a href="https://gulpjs.com/plugins/" rel="noopener noreferrer"&gt;large library of plugins&lt;/a&gt; to configure common tools and scenarii (&lt;a href="https://eslint.org/" rel="noopener noreferrer"&gt;eslint&lt;/a&gt; in this case):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plugins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gulp-load-plugins&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt; &lt;span class="na"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nx"&gt;gulp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;analysis-eslint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;gulp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;src&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/**/*.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eslint&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eslint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eslint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;failAfterError&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consistency reduces my cognitive load, thus the targets defined in the build file (&lt;code&gt;gulpfile.js&lt;/code&gt;) are more or less the same &lt;a href="https://dev.to/mcartoixa/continuous-integration-for-the-net-framework-1hc6#the-build-file"&gt;as in .NET&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;clean&lt;/em&gt;: cleans the build (the &lt;code&gt;tmp\&lt;/code&gt; directory).&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;compile&lt;/em&gt;: “compiles” (ie minifies) the source code using &lt;a href="https://github.com/mishoo/UglifyJS#readme" rel="noopener noreferrer"&gt;UglifyJS&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;analysis&lt;/em&gt;: performs static analysis on the project with &lt;a href="https://eslint.org/" rel="noopener noreferrer"&gt;eslint&lt;/a&gt;, and then gathers statistics using &lt;a href="https://github.com/AlDanial/cloc" rel="noopener noreferrer"&gt;the cloc utility&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;package&lt;/em&gt;: simply copies the minified files to the &lt;code&gt;tmp\out\bin\&lt;/code&gt; folder.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;build&lt;/em&gt;: shortcut for the combination of &lt;em&gt;analysis&lt;/em&gt; and &lt;em&gt;compile&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;rebuild&lt;/em&gt;: shortcut for the combination of &lt;em&gt;clean&lt;/em&gt; and &lt;em&gt;build&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;release&lt;/em&gt;: shortcut for the combination of &lt;em&gt;clean&lt;/em&gt;, &lt;em&gt;build&lt;/em&gt;, and &lt;em&gt;package&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Automated testing is hard in this configuration (a plugin based on the visual integration of an external API) and so there is no test target (what are &lt;em&gt;your&lt;/em&gt; excuses?).&lt;/p&gt;

&lt;p&gt;And then as a convenience, for ease of use and discovery of our system, these targets are referenced in the main &lt;code&gt;package.json&lt;/code&gt; file, which will then be used as the main entry point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"analysis"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gulp analysis"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"clean"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gulp clean"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"compile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gulp compile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"package"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gulp package"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gulp build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rebuild"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gulp rebuild"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"release"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gulp release"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The script file
&lt;/h3&gt;

&lt;p&gt;Nothing new here, as the whole build has been described above and the script is just here to allow us to easily &lt;a href="https://dev.to/mcartoixa/my-take-on-continuous-integration-3gep#build-in-1-step"&gt;build locally&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CALL npm.cmd install --no-package-lock --no-shrinkwrap --loglevel info --cache .tmp\npm-cache
CALL npm.cmd run-script %TARGET% --loglevel %VERBOSITY%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well, almost nothing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First of all as our technology is cross platform and as our scripts are very simple, it is easy to make our build work on Windows &lt;strong&gt;and&lt;/strong&gt; MacOS for instance. Just add a simple &lt;code&gt;build.sh&lt;/code&gt; file (not forgetting &lt;a href="https://stackoverflow.com/questions/21691202/how-to-create-file-execute-mode-permissions-in-git-on-windows" rel="noopener noreferrer"&gt;the execute mode permissions&lt;/a&gt; of course):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-package-lock&lt;/span&gt; &lt;span class="nt"&gt;--no-shrinkwrap&lt;/span&gt; &lt;span class="nt"&gt;--loglevel&lt;/span&gt; info &lt;span class="nt"&gt;--cache&lt;/span&gt; .tmp/npm-cache
npm run-script &lt;span class="nv"&gt;$_TARGET&lt;/span&gt; &lt;span class="nt"&gt;--loglevel&lt;/span&gt; &lt;span class="nv"&gt;$_VERBOSITY&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Some external dependencies have to be installed prior to the build. For instance I like to gather statistics on my projects using &lt;a href="https://github.com/AlDanial/cloc" rel="noopener noreferrer"&gt;the cloc utility&lt;/a&gt;, which is much easier installed locally (this is a simple download) outside of the proper build.

&lt;ul&gt;
&lt;li&gt;By convention local installations go to the &lt;code&gt;.tmp\&lt;/code&gt; directory (notice the dot).&lt;/li&gt;
&lt;li&gt;In the &lt;code&gt;build\&lt;/code&gt; folder (by convention) I have a &lt;code&gt;SetEnv.bat&lt;/code&gt; script that will be called by other build scripts on Windows that can initialize the environment for the build (detect installation paths using the mighty Windows registry, initialize environement variables, warn of missing dependencies…) and (locally) install missing ones:
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IF NOT EXIST "%CD%\.tmp\cloc.exe" (
    IF NOT EXIST .tmp MKDIR .tmp
    powershell.exe -NoLogo -NonInteractive -ExecutionPolicy ByPass -Command "&amp;amp; { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest https://github.com/AlDanial/cloc/releases/download/v$Env:_CLOC_VERSION/cloc-$Env:_CLOC_VERSION.exe -OutFile .tmp\cloc.exe; }"
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;You can do the same for other platforms in a &lt;code&gt;.bashrc&lt;/code&gt; file (not forgetting &lt;a href="https://www.jerriepelser.com/blog/execute-permissions-with-git/" rel="noopener noreferrer"&gt;the execute mode permissions&lt;/a&gt;):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[!&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; .tmp]&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; .tmp&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;fi
if&lt;/span&gt; &lt;span class="o"&gt;[!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/.tmp/cloc.pl]&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;wget &lt;span class="nt"&gt;-nv&lt;/span&gt; &lt;span class="nt"&gt;--show-progress&lt;/span&gt; &lt;span class="nt"&gt;-O&lt;/span&gt; .tmp/cloc.pl https://github.com/AlDanial/cloc/releases/download/v&lt;span class="nv"&gt;$_CLOC_VERSION&lt;/span&gt;/cloc-&lt;span class="nv"&gt;$_CLOC_VERSION&lt;/span&gt;.pl
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Handling your external dependencies this way as much as possible can significantly reduce the need for documentation (in the &lt;em&gt;pre-requisite&lt;/em&gt; section) and cut down the time needed for a new developer to jump in your project. I know it saved &lt;em&gt;me&lt;/em&gt; much time when I got back to this project 2 years later…&lt;/p&gt;

&lt;h3&gt;
  
  
  The CI configuration file
&lt;/h3&gt;

&lt;p&gt;Having already handled all the difficulties our configuration file could hardly be more simple, which is the goal. For instance using &lt;a href="https://travis-ci.org/" rel="noopener noreferrer"&gt;Travis CI&lt;/a&gt;, the gist of the &lt;code&gt;.travis.yml&lt;/code&gt; is only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;. build/.bashrc&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm install --no-package-lock --no-shrinkwrap&lt;/span&gt;

&lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm run-script release --loglevel notice&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A more complete project
&lt;/h2&gt;

&lt;p&gt;Well, I do not think I have much to show you here. But every useful concept has been touched in the above (not so simple then) project. Simply know that I have been able to integrate this pipeline with success in other kinds of projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chrome extensions, which required JSON transformation of configuration files depending on the environment (to set up OAuth credentials for instance).&lt;/li&gt;
&lt;li&gt;proper node.js projects (backend and frontend). The main trick there is to be able to create &lt;a href="https://dev.to/mcartoixa/my-take-on-continuous-integration-3gep#create-deployable-packages"&gt;a deployable package&lt;/a&gt;: you will want to execute &lt;code&gt;npm install --production&lt;/code&gt; in a temporary folder, along with a copy of your backend source files, to be sure to package only the dependencies that are relevant to production. I leave it as an exercise for you, but remember you have the whole power of Javascript at your disposal (instead of YAML or JSON), so this should be fairly achievable (I did that using &lt;a href="https://gruntjs.com/" rel="noopener noreferrer"&gt;grunt.js&lt;/a&gt; a looong time ago).&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ci</category>
      <category>node</category>
      <category>gulp</category>
    </item>
    <item>
      <title>Continuous Integration for the .NET Framework</title>
      <dc:creator>Mac</dc:creator>
      <pubDate>Mon, 07 Sep 2020 05:59:00 +0000</pubDate>
      <link>https://dev.to/mcartoixa/continuous-integration-for-the-net-framework-1hc6</link>
      <guid>https://dev.to/mcartoixa/continuous-integration-for-the-net-framework-1hc6</guid>
      <description>&lt;p&gt;I realize writing this post that I have been practicing .NET development (C# in particular) since 2005 (15 years!). It is natural for &lt;a href="https://dev.to/mcartoixa/my-take-on-continuous-integration-3gep"&gt;my Continuous Integration practices&lt;/a&gt; to have been heavily influenced by this platform during all this time. The advent of .NET Core will be the occasion to revisit these, and also the subject of another blog post. This post shows what those years of maturation led to on the (soon legacy) .NET Framework platform.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A simple project&lt;/li&gt;
&lt;li&gt;A more complete project&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A simple project
&lt;/h2&gt;

&lt;p&gt;For a starter, let’s look at how the principles apply to a relatively simple (and largely unfinished) .NET Framework project: meet &lt;a href="https://github.com/mcartoixa/NetMonkey" rel="noopener noreferrer"&gt;NetMonkey&lt;/a&gt;, a .NET wrapper for the &lt;a href="https://mailchimp.com/developer/api/" rel="noopener noreferrer"&gt;MailChimp API&lt;/a&gt; (it was version 3.0 at the time). What you need are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
A solution file (&lt;code&gt;NetMonkey.sln&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
A build file (&lt;code&gt;NetMonkey.proj&lt;/code&gt;), written in MSBuild.&lt;/li&gt;
&lt;li&gt;
A script file (&lt;code&gt;build.bat&lt;/code&gt;) that helps executing the build file locally.&lt;/li&gt;
&lt;li&gt;
A CI configuration file (&lt;code&gt;appveyor.yml&lt;/code&gt;).The rest of the project is either code of infrastructure.&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/mcartoixa" rel="noopener noreferrer"&gt;
        mcartoixa
      &lt;/a&gt; / &lt;a href="https://github.com/mcartoixa/NetMonkey" rel="noopener noreferrer"&gt;
        NetMonkey
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A .NET wrapper for the MailChimp API v3.0.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  The solution
&lt;/h3&gt;

&lt;p&gt;Solutions are &lt;a href="https://docs.microsoft.com/en-us/visualstudio/ide/" rel="noopener noreferrer"&gt;Visual Studio&lt;/a&gt; &lt;em&gt;speak&lt;/em&gt; for &lt;a href="https://docs.microsoft.com/en-us/visualstudio/get-started/tutorial-projects-solutions" rel="noopener noreferrer"&gt;a collection of related projects&lt;/a&gt;. You can load them (with Visual Studio), and you can also build them (with &lt;a href="https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild" rel="noopener noreferrer"&gt;MSBuild&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;In this context, solutions have 2 purposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;they are an entry point for developers to edit the code.&lt;/li&gt;
&lt;li&gt;they are an entry point for the build scripts to generate a package. This is why there are actually 2 solutions in the project:

&lt;ul&gt;
&lt;li&gt;The main solution, &lt;a href="https://github.com/mcartoixa/NetMonkey/blob/master/NetMonkey.sln" rel="noopener noreferrer"&gt;&lt;code&gt;NetMonkey.sln&lt;/code&gt;&lt;/a&gt; is used to generate the library.&lt;/li&gt;
&lt;li&gt;The second solution &lt;a href="https://github.com/mcartoixa/NetMonkey/blob/master/NetMonkey.Tests.sln" rel="noopener noreferrer"&gt;&lt;code&gt;NetMonkey.Tests.sln&lt;/code&gt;&lt;/a&gt; also contains the associated unit tests. The main project is shared with the previous solution, so this is the one the developer will be encouraged to develop against (being more complete).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The main point here is that developers can still use their usual toolkit to develop (Visual Studio in this case).&lt;/p&gt;

&lt;h3&gt;
  
  
  The build file
&lt;/h3&gt;

&lt;p&gt;This is the crux of the build, written in &lt;a href="https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild" rel="noopener noreferrer"&gt;MSBuild&lt;/a&gt;. I might also have considered &lt;a href="https://cakebuild.net/" rel="noopener noreferrer"&gt;Cake&lt;/a&gt;, or even &lt;a href="https://psake.readthedocs.io/en/latest/" rel="noopener noreferrer"&gt;psake&lt;/a&gt; but for me nothing beats MSBuild. It is an acquired taste though, and it most certainly deserves its own dedicated blog post. Meanwhile the keypoints are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In .NET Framework the whole build system is based upon MSBuild, and project files are proper, editable MSBuild files (though notably solutions are not). In fact Visual Studio could be thought of as a visual editor for MSBuild projects. Using MSBuild means tighter integration with the .NET Framework platform.&lt;/li&gt;
&lt;li&gt;The logging capabilities of MSBuild are just amazing (yes, &lt;a href="https://github.com/KirillOsenkov/MSBuildStructuredLog" rel="noopener noreferrer"&gt;really&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;I don’t mind XML (yes, I’m that old).&lt;/li&gt;
&lt;li&gt;There were no real alternatives 15 years ago anyway; &lt;a href="http://nant.sourceforge.net/" rel="noopener noreferrer"&gt;NAnt&lt;/a&gt; was a step back IMHO. More on that another time…&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The gist of this file is deceptively simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.microsoft.com/developer/msbuild/2003"&lt;/span&gt; &lt;span class="na"&gt;DefaultTargets=&lt;/span&gt;&lt;span class="s"&gt;"Rebuild"&lt;/span&gt; &lt;span class="na"&gt;ToolsVersion=&lt;/span&gt;&lt;span class="s"&gt;"14.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Projects&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"NetMonkey.sln"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;Import&lt;/span&gt; &lt;span class="na"&gt;Project=&lt;/span&gt;&lt;span class="s"&gt;"$(MSBuildProjectDirectory)\packages\Isogeo.Build.*\tools\build\Isogeo.Common.targets"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It just says that the main solution is the &lt;code&gt;NetMonkey.sln&lt;/code&gt; file. As I adhere to my own conventions (as I easily tend to) I was able to abstract the essential part of the build in a library that can be shared accross many projects. The drawback is that after 15 years of trying to handle every situation (like web applications &lt;em&gt;and&lt;/em&gt; native applications for instance), the common library tends to become heavy and difficult to get into (see for yourself &lt;a href="https://github.com/isogeo/Isogeo.Build/blob/531d173efc326afceb013a6ff841e58ffcdaff25/files/build/Isogeo.Common.targets" rel="noopener noreferrer"&gt;https://github.com/isogeo/Isogeo.Build/blob/531d173efc326afceb013a6ff841e58ffcdaff25/files/build/Isogeo.Common.targets&lt;/a&gt;). But with a simple definition like the above it provides the following build targets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Clean&lt;/em&gt;: cleans the build.

&lt;ul&gt;
&lt;li&gt;This is usually a simple matter of deleting the &lt;code&gt;tmp\&lt;/code&gt; folder, as every other target generates its outputs there.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Compile&lt;/em&gt;: compiles the specified solutions.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Test&lt;/em&gt;: tests the project.

&lt;ul&gt;
&lt;li&gt;More specifically, if a solution exists that has the same name as the provided solution but with a &lt;code&gt;.Tests.sln&lt;/code&gt; suffix (like &lt;code&gt;NetMonkey.Tests.sln&lt;/code&gt; in this case) it compiles it and executes the tests.&lt;/li&gt;
&lt;li&gt;It also uses &lt;a href="https://github.com/OpenCover/opencover" rel="noopener noreferrer"&gt;OpenCover&lt;/a&gt; to check code coverage.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Analysis&lt;/em&gt;: performs static analysis on the project.

&lt;ul&gt;
&lt;li&gt;If &lt;a href="https://en.wikipedia.org/wiki/FxCop" rel="noopener noreferrer"&gt;FxCop&lt;/a&gt; is detected it performs a &lt;a href="https://docs.microsoft.com/en-us/visualstudio/code-quality/walkthrough-analyzing-managed-code-for-code-defects" rel="noopener noreferrer"&gt;legacy analysis&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;If a &lt;a href="https://www.sonarqube.org/" rel="noopener noreferrer"&gt;SonarQube&lt;/a&gt; configuration file is present it performs an analysis.&lt;/li&gt;
&lt;li&gt;It gathers statistics using &lt;a href="https://github.com/AlDanial/cloc" rel="noopener noreferrer"&gt;the cloc utility&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Document&lt;/em&gt;: generates documentation (in the &lt;code&gt;tmp\out\bin&lt;/code&gt; folder) for the project using &lt;a href="https://github.com/EWSoftware/SHFB" rel="noopener noreferrer"&gt;SHFB&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Package&lt;/em&gt;: generates a deployable package (in the &lt;code&gt;tmp\out\bin&lt;/code&gt; folder). Depending on the kind of solution being provided this can be:

&lt;ul&gt;
&lt;li&gt;A zip file.&lt;/li&gt;
&lt;li&gt;A NuGet file for libraries (NuGet being &lt;a href="https://www.nuget.org/" rel="noopener noreferrer"&gt;the dependency manager of choice on the .NET platform&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://www.iis.net/downloads/microsoft/web-deploy" rel="noopener noreferrer"&gt;Web Deploy&lt;/a&gt; package for web applications.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Build&lt;/em&gt;: shortcut for the combination of &lt;em&gt;Compile&lt;/em&gt; and &lt;em&gt;Test&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Rebuild&lt;/em&gt;: shortcut for the combination of &lt;em&gt;Clean&lt;/em&gt; and &lt;em&gt;Build&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Release&lt;/em&gt;: shortcut for the combination of &lt;em&gt;Clean&lt;/em&gt;, &lt;em&gt;Build&lt;/em&gt;, &lt;em&gt;Document&lt;/em&gt;, &lt;em&gt;Package&lt;/em&gt; and &lt;em&gt;Analysis&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Providing the same structure reduces my cognitive load when going from project to project: I always know where to start. Also note that most of the tools used generate reports (int the &lt;code&gt;tmp\&lt;/code&gt; folder), that can be sent to various platforms of choice (like &lt;a href="https://www.appveyor.com/docs/running-tests/#uploading-xml-test-results" rel="noopener noreferrer"&gt;AppVeyor&lt;/a&gt; or &lt;a href="https://docs.codecov.io/docs/about-the-codecov-bash-uploader" rel="noopener noreferrer"&gt;CodeCov&lt;/a&gt;…).&lt;/p&gt;
&lt;h3&gt;
  
  
  The script file
&lt;/h3&gt;

&lt;p&gt;The script file allows for easy local execution of the build file. What may not be easy is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MSBuild is usually not in your &lt;code&gt;%PATH%&lt;/code&gt;. I consider this a feature: it allows you to have multiple versions installed and dynamically choose the version you want at runtime, thanks to the &lt;a href="https://www.lifewire.com/windows-registry-2625992" rel="noopener noreferrer"&gt;Windows Registry&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;You can add advanced MSBuild arguments to improve your build. For instance &lt;a href="https://docs.microsoft.com/en-us/visualstudio/msbuild/obtaining-build-logs-with-msbuild" rel="noopener noreferrer"&gt;a complete log of the build&lt;/a&gt; can be generated for you to inspect in order to debug anything that might be wrong.&lt;/li&gt;
&lt;li&gt;You could need to check hard dependencies (like the .NET Framework itself!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the core of the batch file (cf. &lt;a href="https://github.com/mcartoixa/NetMonkey/blob/master/build.bat" rel="noopener noreferrer"&gt;https://github.com/mcartoixa/NetMonkey/blob/master/build.bat&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PUSHD .nuget
NuGet.exe restore "packages.config" -PackagesDirectory ..\packages
POPD
msbuild.exe %PROJECT% /nologo /t:%TARGET% /m:%NUMBER_OF_PROCESSORS% /p:GenerateDocumentation="%GENERATE_DOCUMENTATION%" /fl /flp:logfile=build.log;verbosity=%VERBOSITY%;encoding=UTF-8 /nr:False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Anyway, now you can test the build locally: &lt;code&gt;build.bat&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  The CI configuration file
&lt;/h3&gt;

&lt;p&gt;A looong time ago I used &lt;a href="https://ccnet.github.io/CruiseControl.NET" rel="noopener noreferrer"&gt;CruiseControl.NET&lt;/a&gt; to build my projects, and it served me well. Nowadays there are many options in the cloud that are much more practical, like &lt;a href="https://www.appveyor.com/" rel="noopener noreferrer"&gt;AppVeyor&lt;/a&gt; for instance which has provided Windows and .NET integration for many years now. It was inevitable that I made the switch at some point.&lt;/p&gt;

&lt;p&gt;But as my build is not tied to any CI platform, the transition was very simple. It keeps my YAML very simple (check for yourself &lt;a href="https://github.com/mcartoixa/NetMonkey/blob/master/appveyor.yml" rel="noopener noreferrer"&gt;https://github.com/mcartoixa/NetMonkey/blob/master/appveyor.yml&lt;/a&gt;), and I could easily switch again, to &lt;a href="https://azure.microsoft.com/en-us/services/devops/" rel="noopener noreferrer"&gt;Azure DevOps&lt;/a&gt; for instance.&lt;/p&gt;

&lt;p&gt;The configuration is the equivalent of &lt;code&gt;build.bat&lt;/code&gt; for the CI platform. It only adds the handling of packages (&lt;em&gt;artifacts&lt;/em&gt; in &lt;a href="https://www.appveyor.com/docs/packaging-artifacts/" rel="noopener noreferrer"&gt;AppVeyor speak&lt;/a&gt;) and releases. All the packages being output in a single folder (&lt;code&gt;tmp\ou\bin\&lt;/code&gt; by convention), the configuration is still very simple:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tmp\out\bin\*.nupkg&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NuGet&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tmp\out\bin\*.zip&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So it is mainly about releases.&lt;/p&gt;
&lt;h2&gt;
  
  
  A more complete project
&lt;/h2&gt;

&lt;p&gt;Meet &lt;a href="https://github.com/mcartoixa/GeoSIK" rel="noopener noreferrer"&gt;GeoSIK&lt;/a&gt;, a set of libraries that were destined to ease the development of &lt;a href="https://ogcapi.ogc.org/" rel="noopener noreferrer"&gt;OGC Web Services&lt;/a&gt; in .NET. It provided 11 libraries that were used to either implement those services or integrate them with external geospatial libraries (like &lt;a href="https://archive.codeplex.com/?p=projnet" rel="noopener noreferrer"&gt;ProjNet&lt;/a&gt; of &lt;a href="https://docs.microsoft.com/en-us/sql/relational-databases/spatial/spatial-data-sql-server" rel="noopener noreferrer"&gt;Sql Server Spatial Data types&lt;/a&gt;).&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/mcartoixa" rel="noopener noreferrer"&gt;
        mcartoixa
      &lt;/a&gt; / &lt;a href="https://github.com/mcartoixa/GeoSIK" rel="noopener noreferrer"&gt;
        GeoSIK
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      GeoSIK is a set of libraries that help develop OGC Web Services in .NET.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Even though this project is much more complex, its structure is the same. And if you understood the structure of the simple project above you should not have too many problems delving into this one now. The only major change is that to keep things simple it was decided to have all the libraries built by 1 single solution (instead of the 11 mandated by the principle &lt;em&gt;1 package, 1 solution&lt;/em&gt;). This required a specific packaging system, and so the &lt;a href="https://github.com/mcartoixa/GeoSIK/blob/master/GeoSik.proj" rel="noopener noreferrer"&gt;&lt;code&gt;GeoSIK.proj&lt;/code&gt;&lt;/a&gt; (&lt;a href="https://github.com/mcartoixa/GeoSIK/blob/master/GeoSik.proj" rel="noopener noreferrer"&gt;https://github.com/mcartoixa/GeoSIK/blob/master/GeoSik.proj&lt;/a&gt;) is a bit more complex.&lt;/p&gt;

&lt;p&gt;To me this shows that although it relies heavily on conventions, this build system is still quite adaptable. In fact the whole system can be adapted to other platforms.&lt;/p&gt;

</description>
      <category>ci</category>
      <category>dotnet</category>
      <category>msbuild</category>
    </item>
  </channel>
</rss>
