<?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: Gabriel Nagy</title>
    <description>The latest articles on DEV Community by Gabriel Nagy (@gabuscus).</description>
    <link>https://dev.to/gabuscus</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%2F638632%2F1b431da3-01d2-4fea-b768-b2aa6341b3b8.png</url>
      <title>DEV Community: Gabriel Nagy</title>
      <link>https://dev.to/gabuscus</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gabuscus"/>
    <language>en</language>
    <item>
      <title>Faster module tests with Facter 4 and rspec-puppet</title>
      <dc:creator>Gabriel Nagy</dc:creator>
      <pubDate>Sat, 22 Jan 2022 10:03:16 +0000</pubDate>
      <link>https://dev.to/puppet/faster-module-tests-with-facter-4-and-rspec-puppet-561e</link>
      <guid>https://dev.to/puppet/faster-module-tests-with-facter-4-and-rspec-puppet-561e</guid>
      <description>&lt;p&gt;The latest major version of &lt;a href="https://github.com/puppetlabs/facter" rel="noopener noreferrer"&gt;Facter&lt;/a&gt;—Puppet's tool for collecting system information—&lt;a href="https://puppet.com/blog/facter-4-back-to-the-roots/" rel="noopener noreferrer"&gt;has been out&lt;/a&gt; for some time now. However, we've been hard at work fixing bugs ever since. The fact that Facter has to be able to run on a variety of operating systems and architectures makes maintenance quite a challenging ordeal.&lt;/p&gt;

&lt;p&gt;Returning to using Ruby as the language of choice (Facter 3 was &lt;a href="https://github.com/puppetlabs/facter/tree/3.x" rel="noopener noreferrer"&gt;written in C++&lt;/a&gt;) meant that we'd have a lot more freedom in structuring and writing the code, mostly at the expense of the added run time. This isn't an issue for how most people use Facter, for example Puppet loads &lt;a href="https://github.com/puppetlabs/puppet/blob/a5a77602a275f1106be9e6a810c5878d08998299/lib/puppet/indirector/facts/facter.rb#L39" rel="noopener noreferrer"&gt;all available facts&lt;/a&gt; at once when it runs, so a few seconds of added run time won't make a difference with Puppet runs that already take minutes. This was something we took into account when making the decision to return to Ruby with Facter 4 (initially Facter was written in Ruby, but was then rewritten in C++ for improved performance). What we didn't take into account were the intricacies of how Facter interacts with other downstream projects that we weren't aware of, such as the &lt;a href="https://rspec-puppet.com/" rel="noopener noreferrer"&gt;rspec-puppet&lt;/a&gt; test framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is &lt;code&gt;rspec-puppet&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;Well, RSpec is a well-known test framework for Ruby, and Puppet is... well, you probably already know or you wouldn't be here reading these words.&lt;/p&gt;

&lt;p&gt;rspec-puppet is the tool of choice when writing unit tests for Puppet modules.  It provides a helpful syntax for interacting with Puppet catalogs in an RSpec way. Since it's unfeasible to acceptance test Puppet functionality on dozens of operating systems and versions, rspec-puppet circumvents this by making Facter &lt;em&gt;trick&lt;/em&gt; Puppet into thinking it runs on different OS configurations.&lt;/p&gt;

&lt;p&gt;Puppet gets almost all of its system-related information using facts, so for example if you're running some Linux and you're curious to see how your Puppet manifest would behave on macOS, in most cases it's enough to feed Facter a bunch of macOS facts. Of course, the underlying implementation is more complicated than that, especially for Windows, but in a nutshell this is how rspec-puppet works.&lt;/p&gt;

&lt;p&gt;For each test, rspec-puppet &lt;a href="https://github.com/puppetlabs/rspec-puppet/blob/84a9cfe48257e09061bda565189766cebd03d426/lib/rspec-puppet/support.rb#L482" rel="noopener noreferrer"&gt;stubs the fake facts&lt;/a&gt; using the &lt;a href="https://puppet.com/docs/puppet/latest/custom_facts.html" rel="noopener noreferrer"&gt;custom facts API&lt;/a&gt;. The &lt;em&gt;fake&lt;/em&gt; facts it gets from &lt;a href="https://github.com/voxpupuli/facterdb" rel="noopener noreferrer"&gt;facterdb&lt;/a&gt;, which is a gem that contains "dummy" facts for a variety of operating systems and Facter versions—basically lots of files containing &lt;code&gt;facter --json&lt;/code&gt; output. The resulting information is then fed to Puppet for catalog compilation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting slower and slower...
&lt;/h2&gt;

&lt;p&gt;Over time, &lt;a href="https://tickets.puppetlabs.com/browse/FACT-3039" rel="noopener noreferrer"&gt;people have been noticing&lt;/a&gt; that module tests running with Facter 4 were much slower than Facter 2. Module tests have skipped Facter 3 altogether because even though Facter 3 ships a compatible Ruby module, in the end it's C++ code that's extremely incompatible with the Ruby ecosystem (you can't &lt;code&gt;gem install&lt;/code&gt; it like you would any other gem).&lt;/p&gt;

&lt;p&gt;After some investigation it turned out that Facter 4 was evaluating underlying core facts even though they had been overridden by custom facts. The first instinct would be to classify this as a bug, but a closer look showed us that this functionality was intended in order to be fully-compatible with Facter 3.  And because modules never ran tests with Facter 3 this wasn't a problem until now.&lt;/p&gt;

&lt;p&gt;Overriding a core fact, similar to how rspec-puppet does:&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;Facter&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="ss"&gt;:ipaddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;weight: &lt;/span&gt;&lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;setcode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;'1.1.1.1'&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;will return the correct value (1.1.1.1) but it will cause Facter to also load the core &lt;code&gt;ipaddress&lt;/code&gt; fact, ultimately resolving all networking facts, which means executing system commands. &lt;/p&gt;

&lt;p&gt;Assuming our custom &lt;code&gt;ipaddress&lt;/code&gt; fact is defined inside the &lt;code&gt;custom_facts&lt;/code&gt; directory, here's what gets called when we attempt to resolve the fact:&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="nv"&gt;$ FACTERLIB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;/custom_facts strace &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-eexecve&lt;/span&gt; facter ipaddress
&lt;span class="o"&gt;[&lt;/span&gt;pid 1777063] execve&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/usr/bin/ip"&lt;/span&gt;, &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ip"&lt;/span&gt;, &lt;span class="s2"&gt;"-o"&lt;/span&gt;, &lt;span class="s2"&gt;"link"&lt;/span&gt;, &lt;span class="s2"&gt;"show"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;, 0x563bf6b7acf0 /&lt;span class="k"&gt;*&lt;/span&gt; 110 vars &lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 0
&lt;span class="o"&gt;[&lt;/span&gt;pid 1777067] execve&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/usr/bin/ip"&lt;/span&gt;, &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ip"&lt;/span&gt;, &lt;span class="s2"&gt;"link"&lt;/span&gt;, &lt;span class="s2"&gt;"show"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;, 0x563bf64eafa0 /&lt;span class="k"&gt;*&lt;/span&gt; 110 vars &lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 0
&lt;span class="o"&gt;[&lt;/span&gt;pid 1777068] execve&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/usr/bin/dhcpcd"&lt;/span&gt;, &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"/usr/bin/dhcpcd"&lt;/span&gt;, &lt;span class="s2"&gt;"-U"&lt;/span&gt;, &lt;span class="s2"&gt;"lo"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;, 0x563bf6aa57b0 /&lt;span class="k"&gt;*&lt;/span&gt; 110 vars &lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 0
&lt;span class="c"&gt;# ... dhcpcd is called for every network interface on the system&lt;/span&gt;
&lt;span class="c"&gt;# I removed the other calls for brevity&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;pid 1777078] execve&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/usr/bin/ip"&lt;/span&gt;, &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ip"&lt;/span&gt;, &lt;span class="s2"&gt;"route"&lt;/span&gt;, &lt;span class="s2"&gt;"show"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;, 0x563bf6aba230 /&lt;span class="k"&gt;*&lt;/span&gt; 110 vars &lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 0
&lt;span class="o"&gt;[&lt;/span&gt;pid 1777079] execve&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/usr/bin/ip"&lt;/span&gt;, &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ip"&lt;/span&gt;, &lt;span class="s2"&gt;"-6"&lt;/span&gt;, &lt;span class="s2"&gt;"route"&lt;/span&gt;, &lt;span class="s2"&gt;"show"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;, 0x563bf6ae37d0 /&lt;span class="k"&gt;*&lt;/span&gt; 110 vars &lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Doing this hundreds or even thousands of times per test suite definitely adds to the increase in run time, and it's redundant since the resolved facts are never needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing things without breaking more things
&lt;/h2&gt;

&lt;p&gt;Modifying Facter's behavior—undocumented as it was—was a no-go from the start,  as we &lt;a href="https://tickets.puppetlabs.com/browse/FACT-3000" rel="noopener noreferrer"&gt;found out before&lt;/a&gt; (Facter is a good example of &lt;a href="https://www.hyrumslaw.com/" rel="noopener noreferrer"&gt;Hyrum's law&lt;/a&gt; in action). So, we had to think of other ways to improve performance.&lt;/p&gt;

&lt;p&gt;We started by decoupling Puppet from Facter as much as we could, introducing the possibility of having &lt;a href="https://tickets.puppetlabs.com/browse/PUP-11216" rel="noopener noreferrer"&gt;multiple Facter backends&lt;/a&gt;. While Puppet would &lt;a href="https://github.com/puppetlabs/puppet/blob/a5a77602a275f1106be9e6a810c5878d08998299/lib/puppet/runtime.rb#L21" rel="noopener noreferrer"&gt;use&lt;/a&gt; the &lt;a href="https://github.com/puppetlabs/puppet/blob/a5a77602a275f1106be9e6a810c5878d08998299/lib/puppet/facter_impl.rb" rel="noopener noreferrer"&gt;default Facter implementation&lt;/a&gt; when running on its own, external users would be able to define and pass their own Facter implementation when initializing Puppet, similar to how puppetserver configures Puppet to use its JRuby-compliant HTTP client.&lt;/p&gt;

&lt;p&gt;To avoid breaking the Facter API, we ended up &lt;a href="https://github.com/puppetlabs/rspec-puppet/blob/046340805ba8877e34af22be7756ee972da84f9b/lib/rspec-puppet/facter_impl.rb" rel="noopener noreferrer"&gt;implementing an overcomplicated way of interacting with a hash&lt;/a&gt;. Using our dumb Facter backend, custom facts were now simply added to a hash, and querying them would just produce them from the hash if available:&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FacterTestImpl&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
    &lt;span class="vi"&gt;@facts&lt;/span&gt; &lt;span class="o"&gt;=&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;def&lt;/span&gt; &lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fact_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@facts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;fact_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&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;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&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="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s1"&gt;'Facter.add expects a block'&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="nb"&gt;block_given?&lt;/span&gt;
    &lt;span class="vi"&gt;@facts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;instance_eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="o"&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;With our custom implementation we bypassed Facter altogether. This managed to bring us back to Facter 2 speeds, which behaved similarly by just returning the custom fact's value without resolving any additional facts.&lt;/p&gt;

&lt;p&gt;Of course, there may be downsides to this approach, as Facter code paths will no longer be executed by rspec-puppet. In the past there have been occasions where we merged Facter work that passed our CI, but ended up failing in module tests, so switching to this implementation will get rid of this level of testing. I'd argue that it wasn't a module's business to validate Facter itself, but it was a good safety net for us as maintainers.&lt;/p&gt;

&lt;p&gt;And because performance improvements mean nothing without showing the numbers, here's how test times have changed for the &lt;a href="https://github.com/puppetlabs/puppet-nginx" rel="noopener noreferrer"&gt;puppet-nginx&lt;/a&gt; module:&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%2Fvwhqmqiit3i2n0x3n318.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%2Fvwhqmqiit3i2n0x3n318.png" width="800" height="293"&gt;&lt;/a&gt;&lt;br&gt;Running &lt;code&gt;rake parallel_spec&lt;/code&gt; on the module using Puppet 7 / Facter 4 took around 47 minutes with the original rspec-puppet implementation.
  &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%2Fjg65snejczql6wj99ym2.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%2Fjg65snejczql6wj99ym2.png" width="800" height="284"&gt;&lt;/a&gt;&lt;br&gt;We managed to shave off around 11 minutes off the test run by using the custom Facter implementation in rspec-puppet.
  &lt;/p&gt;

&lt;p&gt;One thing I haven't mentioned is that running the same tests with Puppet 6 takes &lt;a href="https://github.com/GabrielNagy/puppet-nginx/runs/4149964576" rel="noopener noreferrer"&gt;a total of 25 minutes&lt;/a&gt;, so there's more to improve in Puppet itself as well.  However, from a Facter standpoint it's impossible to make the tests any faster, unless Ruby itself improves hash access speed 😜.&lt;/p&gt;

&lt;p&gt;This new functionality is opt-in and configurable by setting the &lt;a href="https://github.com/puppetlabs/rspec-puppet#facter_implementation" rel="noopener noreferrer"&gt;&lt;code&gt;facter_implementation&lt;/code&gt;&lt;/a&gt; RSpec option in your &lt;code&gt;spec_helper.rb&lt;/code&gt;.&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;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&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;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;facter_implementation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:rspec&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It was first &lt;a href="https://github.com/puppetlabs/rspec-puppet/pull/16" rel="noopener noreferrer"&gt;made available&lt;/a&gt; in rspec-puppet &lt;a href="https://rubygems.org/gems/rspec-puppet/versions/2.11.0" rel="noopener noreferrer"&gt;2.11.0&lt;/a&gt;, with an additional &lt;a href="https://github.com/puppetlabs/rspec-puppet/pull/19" rel="noopener noreferrer"&gt;bugfix&lt;/a&gt; that was released in rspec-puppet &lt;a href="https://rubygems.org/gems/rspec-puppet/versions/2.11.1" rel="noopener noreferrer"&gt;2.11.1&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;The investigation and work surrounding this improvement has spanned many months and was the product of multiple Puppet employees and community members, namely &lt;a href="https://github.com/gimmyxd" rel="noopener noreferrer"&gt;Gimmy&lt;/a&gt;, &lt;a href="https://github.com/joshcooper" rel="noopener noreferrer"&gt;Josh Cooper&lt;/a&gt;, &lt;a href="https://github.com/ekohl" rel="noopener noreferrer"&gt;Ewoud Kohl van Wijngaarden&lt;/a&gt; and &lt;a href="https://github.com/bastelfreak" rel="noopener noreferrer"&gt;Tim Meusel&lt;/a&gt;. Thanks to everyone who contributed!&lt;/p&gt;

</description>
      <category>puppet</category>
      <category>ruby</category>
      <category>rspec</category>
      <category>testing</category>
    </item>
    <item>
      <title>Windows, Ruby and Long Paths</title>
      <dc:creator>Gabriel Nagy</dc:creator>
      <pubDate>Mon, 07 Jun 2021 12:58:30 +0000</pubDate>
      <link>https://dev.to/puppet/windows-ruby-and-long-paths-3jag</link>
      <guid>https://dev.to/puppet/windows-ruby-and-long-paths-3jag</guid>
      <description>&lt;p&gt;Over the past few months, we've had a long-standing issue related to Puppet on Windows resurface (&lt;a href="https://github.com/puppetlabs/Puppet.Dsc/issues/144" rel="noopener noreferrer"&gt;puppetlabs/Puppet.Dsc#144&lt;/a&gt;). More specifically, Puppet modules with long file paths could not be installed on Windows due to a limitation in the Windows operating system. In short, if a file path surpasses 260 characters, it's open season: the path has to be referred to in a different format, first-party apps like Notepad or File Explorer start to behave erratically, and there's no guarantee what works and what doesn't anymore.&lt;/p&gt;

&lt;p&gt;Buckle up, as we're about to go on a perilous journey where we'll encounter and modify old code, graft resources onto Windows executables, build Ruby with the help of renowned triple-A videogame Hitman&lt;sup&gt;TM&lt;/sup&gt; (I'm 100% serious) and generally have a good time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: The Windows
&lt;/h2&gt;

&lt;p&gt;In theory, Windows's NTFS filesystem supports a maximum of an approximate 32767 characters in a file path. However, there's also a hard limit of 260 characters, &lt;code&gt;MAX_PATH&lt;/code&gt;, which is enforced in all Win32 API file management functions.&lt;/p&gt;

&lt;p&gt;A short Google search for "&lt;a href="https://www.google.com/search?q=windows+long+file+paths" rel="noopener noreferrer"&gt;windows long file paths&lt;/a&gt;" shows that this issue is frequently hit by developers and regular users alike. Since 260 characters is really &lt;em&gt;not&lt;/em&gt; that much for a file path, the limit can be hit easily. Off the top of my head I remember seeing the error when installing the boost libraries on Windows, fortunately I was able to work around it by specifying a shorter install path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bypassing the &lt;code&gt;MAX_PATH&lt;/code&gt; limitation
&lt;/h3&gt;

&lt;p&gt;If there's one thing I've grown to appreciate from Microsoft, it's their extensive API documentation (I'm looking at you, Apple 👀). For this long path problem they've put together a nice document&lt;sup id="fnref1"&gt;1&lt;/sup&gt; detailing how to work around &lt;code&gt;MAX_PATH&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The two options are as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;specify long paths using the extended-length format (e.g. &lt;code&gt;\\?\D:\very long path&lt;/code&gt;)

&lt;ul&gt;
&lt;li&gt;this format does not support relative paths&lt;/li&gt;
&lt;li&gt;it would also require extensive refactoring throughout any software that decides to implement long path support&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;disable the limitation by changing a registry value (needs at least Windows 10, version 1607)

&lt;ul&gt;
&lt;li&gt;doing this will remove the limitation in the Win32 functions, and will enable them to work with long paths without the extended prefix&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The second options comes with a catch, which we failed to notice when we first investigated the problem. In Microsoft's article, just under registry example lies an application manifest:&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;application&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"urn:schemas-microsoft-com:asm.v3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;windowsSettings&lt;/span&gt; &lt;span class="na"&gt;xmlns:ws2=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.microsoft.com/SMI/2016/WindowsSettings"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ws2:longPathAware&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/ws2:longPathAware&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/windowsSettings&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/application&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What we thought needs to happen:&lt;/strong&gt; by conflating other (wrong) sources with Microsoft's official documentation, we came to the (wrong) conclusion that the &lt;code&gt;MAX_PATH&lt;/code&gt; limitation is globally controlled by the registry key, and on a per-application basis through the application manifest. This. Is. Not. True.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What actually needs to happen:&lt;/strong&gt; &lt;em&gt;in addition to&lt;/em&gt; having the registry key set, the application that wants to use long paths &lt;strong&gt;NEEDS&lt;/strong&gt; to embed that application manifest at build time. Microsoft devs are playing it extra safe here, showing how much they care about backwards compatibility. And in a way they're right, who knows how much of the third-party software out there makes wrong assumptions about this limitation so it would only make sense for it to be an opt-in feature.&lt;/p&gt;

&lt;p&gt;This definitely explained why most of the things did not Just Work&lt;sup&gt;TM&lt;/sup&gt; after setting the registry key, but it also gave us hope. Well, we still needed to figure out what the hell an application manifest was, how to embed it in the executable, and if possible to automate all this without the help of the Visual Studio GUI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Application Manifests! What do they know? Do they know things?? Let's find out!
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;"When in doubt, check what the Python folks &lt;a href="https://github.com/python/cpython/commit/19ab0fd456a79fa1fdfdb543ac423723667cb2d0" rel="noopener noreferrer"&gt;do&lt;/a&gt;." -anonymous proverb&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Quoting from the Windows documentation, application manifests are XML files that describe and identify the shared and private side-by-side assemblies that an application should bind to at run time.&lt;sup id="fnref2"&gt;2&lt;/sup&gt; Got it? Me neither, and this was as far as I was willing to go reading documentation 😆. I left it at "put manifest in executable file" for simplicity.&lt;/p&gt;

&lt;p&gt;During my time at Puppet I've grown to be wary with everything regarding Windows development. It couldn't possibly be that hammering an XML file in an executable would magically get rid of the long path limitation &lt;small&gt;&lt;em&gt;(It does)&lt;/em&gt;&lt;/small&gt;. There must be more to it &lt;small&gt;&lt;em&gt;(There wasn't)&lt;/em&gt;&lt;/small&gt;. Other, more complicated theories were running through my head, among them a far-fetched one suggesting that the manifest was somehow read by Microsoft's compiler which in turn optimized the Win32 function calls, making this solution impossible to use with different compilers. I also presumed that this manfest thing is C# specific, hence not applicable to Ruby which is written in C.&lt;/p&gt;

&lt;p&gt;From the comments in the related Ruby bug&lt;sup id="fnref3"&gt;3&lt;/sup&gt; I knew that Python had this feature, so I started digging through their source code fully expecting to see path manipulation with the &lt;code&gt;\\?\&lt;/code&gt; prefix for every Win32 API call. Adding to the fact that the Ruby issue was like 5 years old at this point, I was sure that this long paths fix would be a massive effort and likely impossible without knowing the ins and outs of the Ruby C implementation.&lt;/p&gt;

&lt;p&gt;I started going through the Python codebase, but all I could find was &lt;a href="https://github.com/python/cpython/commit/19ab0fd456a79fa1fdfdb543ac423723667cb2d0" rel="noopener noreferrer"&gt;the addition of&lt;/a&gt; the &lt;code&gt;longPathAware&lt;/code&gt; manifest key, which kind of dismantled all my preconceptions about how application manifests work. Maybe having the manifest was indeed enough.&lt;/p&gt;

&lt;p&gt;From another useful Microsoft document,&lt;sup id="fnref4"&gt;4&lt;/sup&gt; I found that a manifest can be embedded into an executable using the following syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mt.exe -manifest MyApp.exe.manifest -outputresource:MyApp.exe;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To do this for a library, replace the &lt;code&gt;1&lt;/code&gt; at the end with &lt;code&gt;2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As a quick sanity check, I opened up Visual Studio, created a new C project in which I simply called &lt;a href="https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createdirectoryw" rel="noopener noreferrer"&gt;&lt;code&gt;CreateDirectory&lt;/code&gt;&lt;/a&gt; with a long path. The call errored out as the path was too long, but after including the manifest it worked. Granted, it took me &lt;em&gt;way&lt;/em&gt; too long to find out how to include a manifest through the VS user interface, but hey, it worked!&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: The Ruby
&lt;/h2&gt;

&lt;p&gt;Coming from a Linux background, I definitely did &lt;em&gt;not&lt;/em&gt; expect to have so much fun compiling Ruby on Windows (it's been a few weeks since I've done this so the bad memories have mostly gone away).&lt;/p&gt;

&lt;h3&gt;
  
  
  Building this thing
&lt;/h3&gt;

&lt;p&gt;Ruby on Windows can be compiled with both MinGW/GCC and Visual C++, and I decided to start with the latter as I already had the Visual C++ compiler installed.&lt;/p&gt;

&lt;p&gt;Building Ruby from the git source requires an extra set of commands like &lt;code&gt;bison&lt;/code&gt;, &lt;code&gt;patch&lt;/code&gt; and &lt;code&gt;sed&lt;/code&gt;. To get these on Windows I installed Cygwin and &lt;a href="https://community.chocolatey.org/packages/cyg-get" rel="noopener noreferrer"&gt;cyg-get&lt;/a&gt;, and made sure to have the Cygwin bin path properly set. After that it was just a matter of calling &lt;code&gt;cyg-get&lt;/code&gt; to install each required package.&lt;/p&gt;

&lt;p&gt;Afterwards, Ruby can be built by executing the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;win32\configure.bat
nmake
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately my first Ruby build failed fast with a &lt;code&gt;ucrtbase.dll&lt;/code&gt; error somewhere &lt;a href="https://github.com/ruby/ruby/blob/44cff500a0ad565952e84935bc98523c36a91b06/win32/win32.c#L2612" rel="noopener noreferrer"&gt;here&lt;/a&gt;.  I still have no idea what this code does, I assume it searches for a function in my &lt;code&gt;ucrtbase.dll&lt;/code&gt;. I blamed it on the fact that I'm running Windows builds from the Dev Channel which may have newer versions of DLLs, and I started my search for the perfect &lt;code&gt;ucrtbase.dll&lt;/code&gt;. This is a good moment to plug &lt;a href="https://www.voidtools.com/downloads/" rel="noopener noreferrer"&gt;Everything&lt;/a&gt;, an awesome search tool for Windows:&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%2F2qajlnnoj91vzq0muiog.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%2F2qajlnnoj91vzq0muiog.png" alt="Everything ucrtbase.dll" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I kid you not, I ended up taking the &lt;code&gt;ucrtbase.dll&lt;/code&gt; from my Hitman installation, copied it to the Ruby directory and prepended the path to the &lt;code&gt;LIB&lt;/code&gt; environment variable. Thanks to Agent 47 I was able to successfully build Ruby with Visual C++.&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%2Fxy4lh76nj0c80ztzc4cu.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%2Fxy4lh76nj0c80ztzc4cu.png" width="457" height="457"&gt;&lt;/a&gt;&lt;br&gt;Look at that unsettling smile! Also, did you know that Agent 47 is &lt;a href="https://hitman.fandom.com/wiki/Agent_47" rel="noopener noreferrer"&gt;partly Romanian&lt;/a&gt;?
  &lt;/p&gt;

&lt;h3&gt;
  
  
  Where to put the manifest: The Visual C++ version
&lt;/h3&gt;

&lt;p&gt;The good part is that I found a spot in the Makefile where &lt;code&gt;mt.exe&lt;/code&gt; &lt;a href="https://github.com/ruby/ruby/blob/93be7a4c6bda8269b3d82ce4cfde6b2bde3b9718/win32/Makefile.sub#L307-L310" rel="noopener noreferrer"&gt;is called&lt;/a&gt;.  The bad part is that &lt;code&gt;mt.exe&lt;/code&gt; is a tool only provided in the Visual C++ toolchain, and the Makefile was Visual C++-specific, so this would only fix half of the problem. At Puppet, we vendor our own Ruby, but we compile it with MinGW/GCC so we wouldn't be able to benefit from the Visual C++ changes.&lt;/p&gt;

&lt;p&gt;Either way, it was late at night and I just wanted to get that manifest inside the Ruby executable, so I started desecrating the Makefile to make it behave the way I wanted. I ended up embedding the manifest into &lt;em&gt;EVERY&lt;/em&gt; executable and library generated by the compiler (including all native extensions), so I might have gone a bit overboard with that. On the bright side, I was able to confirm that long paths now worked!&lt;/p&gt;

&lt;p&gt;Still, it was a piece of ugly code, I shared it in &lt;a href="https://bugs.ruby-lang.org/issues/12551#note-8" rel="noopener noreferrer"&gt;a comment&lt;/a&gt; on the original Ruby ticket, and to my surprise just a few hours later &lt;a href="https://github.com/nobu" rel="noopener noreferrer"&gt;&lt;strong&gt;nobu&lt;/strong&gt;&lt;/a&gt; responded with a cleaner solution which I validated, and looked something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/win32/Makefile.sub b/win32/Makefile.sub
index c88ae6f9d1..22198aa358 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/win32/Makefile.sub
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/win32/Makefile.sub
&lt;/span&gt;&lt;span class="p"&gt;@@ -305,9 +305,10 @@&lt;/span&gt; XCFLAGS = -DRUBY_EXPORT $(INCFLAGS) $(XCFLAGS)
 !if $(MSC_VER) &amp;gt;= 1400
 # Prevents VC++ 2005 (cl ver 14) warnings
 MANIFESTTOOL = mt -nologo
&lt;span class="gd"&gt;-LDSHARED_0 = @if exist $(@).manifest $(MINIRUBY) -run -e wait_writable -- -n 10 $@
-LDSHARED_1 = @if exist $(@).manifest $(MANIFESTTOOL) -manifest $(@).manifest -outputresource:$(@);2
-LDSHARED_2 = @if exist $(@).manifest @$(RM) $(@:/=\).manifest
&lt;/span&gt;&lt;span class="gi"&gt;+LDSHARED_0 = $(Q)$(MINIRUBY) -run -e wait_writable -- -n 10 $@
+LDSHARED_1 = $(Q)if exist $(@).manifest (set MANIFEST=$(@).manifest) else (set MANIFEST=$(win_srcdir)/ruby.manifest) &amp;amp;&amp;amp; \
+            call $(MANIFESTTOOL) -manifest ^%MANIFEST% -outputresource:$(@);2
+LDSHARED_2 = $(Q)@$(RM) $(@:/=\).manifest
&lt;/span&gt; !endif
 CPPFLAGS = $(DEFS) $(ARCHDEFS) $(CPPFLAGS)
 !if "$(USE_RUBYGEMS)" == "no"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;small&gt;&lt;em&gt;If you look closely you can see that &lt;code&gt;mt&lt;/code&gt; is called with &lt;code&gt;2&lt;/code&gt; which means it only works for libraries. After some debugging I extended the Makefile with additional commands to make it work for executables as well, but the changes got a bit more complicated than I wanted, so I'll skip over them; especially since the final fix is completely different.&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;The remaining issue was to make it also work with GCC.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where to put the manifest: The GCC version
&lt;/h3&gt;

&lt;p&gt;After some Google searching I found out that the MinGW toolchain provides &lt;a href="https://sourceware.org/binutils/docs/binutils/windres.html" rel="noopener noreferrer"&gt;&lt;code&gt;windres&lt;/code&gt;&lt;/a&gt;, a tool that can manipulate &lt;a href="https://en.wikipedia.org/wiki/Resource_(Windows)" rel="noopener noreferrer"&gt;Windows resources&lt;/a&gt;. What are Windows resources you might ask? Well, various things that can be embedded into an application, like icons, cursors, fonts, and... application manifests!&lt;/p&gt;

&lt;p&gt;I was able to find &lt;a href="https://github.com/ruby/ruby/blob/44cff500a0ad565952e84935bc98523c36a91b06/cygwin/GNUmakefile.in#L49-L51" rel="noopener noreferrer"&gt;usage of &lt;code&gt;windres&lt;/code&gt;&lt;/a&gt; inside the Ruby Cygwin Makefile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nl"&gt;%.res.@OBJEXT@&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;%.rc&lt;/span&gt;
    &lt;span class="p"&gt;$(&lt;/span&gt;ECHO&lt;span class="p"&gt;)&lt;/span&gt; compiling &lt;span class="nv"&gt;$@&lt;/span&gt;
    &lt;span class="p"&gt;$(&lt;/span&gt;Q&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;WINDRES&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--include-dir&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--include-dir&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;&amp;lt;D&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--include-dir&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;srcdir&lt;span class="p"&gt;)&lt;/span&gt;/win32 &lt;span class="nv"&gt;$&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$@&lt;/span&gt;

&lt;span class="nl"&gt;%.rc&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;$(RBCONFIG) $(srcdir)/revision.h $(srcdir)/win32/resource.rb&lt;/span&gt;
    &lt;span class="p"&gt;$(&lt;/span&gt;ECHO&lt;span class="p"&gt;)&lt;/span&gt; generating &lt;span class="nv"&gt;$@&lt;/span&gt;
    &lt;span class="p"&gt;$(&lt;/span&gt;Q&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;MINIRUBY&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;srcdir&lt;span class="p"&gt;)&lt;/span&gt;/win32/resource.rb &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;-ruby_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;RUBY_INSTALL_NAME&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-rubyw_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;RUBYW_INSTALL_NAME&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;-so_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;DLL_BASE_NAME&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;F&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;icondirs&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;srcdir&lt;span class="p"&gt;)&lt;/span&gt;/win32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Makefile lingo this means that &lt;code&gt;.rc&lt;/code&gt; files are turned into &lt;code&gt;.res&lt;/code&gt; files, &lt;em&gt;and&lt;/em&gt; &lt;code&gt;.rc&lt;/code&gt; files are created through the execution of the &lt;a href="https://github.com/ruby/ruby/blob/44cff500a0ad565952e84935bc98523c36a91b06/win32/resource.rb" rel="noopener noreferrer"&gt;&lt;code&gt;win32/resource.rb&lt;/code&gt;&lt;/a&gt; Ruby script. In short, for each Ruby executable and library generated by the compiler—&lt;code&gt;ruby.exe&lt;/code&gt;, &lt;code&gt;rubyw.exe&lt;/code&gt;, and the Ruby DLL library)—the script creates a &lt;code&gt;.rc&lt;/code&gt; file containing various things like the Ruby icon and copyright information. After a quick look through the script, I found the place where the manifest can be included, and it was as simple as including the following line in the generated &lt;code&gt;.rc&lt;/code&gt; file, provided that &lt;code&gt;ruby.manifest&lt;/code&gt; contains the appropriate long path manifest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 RT_MANIFEST ruby.manifest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;small&gt;&lt;em&gt;1 stands for the resource ID, &lt;code&gt;RT_MANIFEST&lt;/code&gt; is the type defined in &lt;code&gt;winuser.h&lt;/code&gt; for application manifests (it maps to the integer &lt;code&gt;24&lt;/code&gt;, which can also be used if you don't have access to the header file), and &lt;code&gt;ruby.manifest&lt;/code&gt; is the file which contains the application manifest.&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;Below is a simplified version of the &lt;code&gt;win32/resource.rb&lt;/code&gt; code that generates the &lt;code&gt;.rc&lt;/code&gt; files, with the newly added manifest line:&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="p"&gt;[&lt;/span&gt; &lt;span class="c1"&gt;# base name    extension         file type  desc, icons&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="vg"&gt;$ruby_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="no"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"EXEEXT"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;'VFT_APP'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'CUI'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ruby_icon&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="vg"&gt;$rubyw_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="no"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"EXEEXT"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;'VFT_APP'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'GUI'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rubyw_icon&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;ruby_icon&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="vg"&gt;$so_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="s1"&gt;'.dll'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="s1"&gt;'VFT_DLL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DLL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dll_icons&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="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;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;icon&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="k"&gt;next&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vg"&gt;$output&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="vg"&gt;$output&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;
  &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;'.rc'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"w"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;binmode&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sr"&gt;/mingw/&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="no"&gt;RUBY_PLATFORM&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;print&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
#include &amp;lt;windows.h&amp;gt;
#include &amp;lt;winver.h&amp;gt;

&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;icon&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
1 RT_MANIFEST ruby.manifest
VS_VERSION_INFO VERSIONINFO
 FILEVERSION    &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;nversion&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
 PRODUCTVERSION &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;nversion&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
 FILEFLAGSMASK  0x3fL
 FILEFLAGS      0x0L
 FILEOS         VOS__WINDOWS32
 FILETYPE       &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
 FILESUBTYPE    VFT2_UNKNOWN
BEGIN
 BLOCK "StringFileInfo"
 BEGIN
  BLOCK "000004b0"
  BEGIN
   VALUE "Comments",         "&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;RUBY_RELEASE_DATE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sh"&gt;0"
   VALUE "CompanyName",      "http://www.ruby-lang.org/&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sh"&gt;0"
   VALUE "FileDescription",  "Ruby interpreter (&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;) &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;sversion&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt; [&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;RUBY_PLATFORM&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;]&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sh"&gt;0"
   VALUE "FileVersion",      "&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;sversion&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sh"&gt;0"
   VALUE "InternalName",     "&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ext&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sh"&gt;0"
   VALUE "LegalCopyright",   "Copyright (C) 1993-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;RUBY_RELEASE_DATE&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;/\d+/&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt; Yukihiro Matsumoto&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sh"&gt;0"
   VALUE "OriginalFilename", "&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ext&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sh"&gt;0"
   VALUE "ProductName",      "Ruby interpreter &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;sversion&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt; [&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;RUBY_PLATFORM&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;]&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sh"&gt;0"
   VALUE "ProductVersion",   "&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;sversion&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sh"&gt;0"
  END
 END
 BLOCK "VarFileInfo"
 BEGIN
  VALUE "Translation", 0x0, 0x4b0
 END
END
&lt;/span&gt;&lt;span class="no"&gt;EOF&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;A tool that helped me a lot in debugging the executables generated by GCC and Visual C++ is &lt;a href="http://www.angusj.com/resourcehacker/" rel="noopener noreferrer"&gt;Resource Hacker&lt;/a&gt;. It can open up executables and show you what resources they contain. This was how I was able to notice that if I set an ID different than 1 to the manifest, GCC would include a default manifest which shadowed my long path manifest, causing the feature to no longer work.&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%2F63bl6odzmuap2yajm68k.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%2F63bl6odzmuap2yajm68k.png" width="786" height="659"&gt;&lt;/a&gt;&lt;br&gt;Right above the manifest we just added, there's also the &lt;code&gt;Version Info&lt;/code&gt; resource which we saw in the code above!
  &lt;/p&gt;

&lt;p&gt;I glossed over the build process for MinGW/GCC, because it's... not as complicated and it didn't involve any Hitman DLLs. I did it using the &lt;a href="https://www.msys2.org/" rel="noopener noreferrer"&gt;MSYS2&lt;/a&gt; toolchain which gives you a bash prompt, then compiled Ruby as if I was on Linux.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;After some digging I realized that the &lt;code&gt;resource.rb&lt;/code&gt; script which created the &lt;code&gt;.rc&lt;/code&gt; files with the manifest was also executed during the Visual C++ build, so changing that Ruby script would accomodate both compilers without the need for additional code changes. One thing to note is that with Visual C++, the &lt;a href="https://docs.microsoft.com/en-us/windows/win32/menurc/resource-compiler" rel="noopener noreferrer"&gt;&lt;code&gt;rc&lt;/code&gt;&lt;/a&gt; tool is used instead of &lt;a href="https://sourceware.org/binutils/docs/binutils/windres.html" rel="noopener noreferrer"&gt;&lt;code&gt;windres&lt;/code&gt;&lt;/a&gt; which achieves similar results, and there's no need for &lt;code&gt;mt&lt;/code&gt; anymore.&lt;/p&gt;

&lt;p&gt;I hurried to open a &lt;a href="https://github.com/ruby/ruby/pull/4505" rel="noopener noreferrer"&gt;pull request&lt;/a&gt;, where &lt;a href="https://github.com/nobu" rel="noopener noreferrer"&gt;&lt;strong&gt;nobu&lt;/strong&gt;&lt;/a&gt; again provided feedback and promptly merged it!&lt;/p&gt;

&lt;p&gt;In the end it was one of those few-line fixes with a huge impact. I'm happy it could be done in a few lines of code, and that I had the chance to learn a lot about Windows and Ruby on Windows in the meantime.&lt;/p&gt;

&lt;p&gt;Things I learned by working on this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If something fails to compile, copying random DLLs around might just fix your problems&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make sure the code you expect to run is actually running&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;if you modify something like a Makefile and nothing appears to change, don't blame your coding skills just yet—find a way to figure out if and when that code path is executed (&lt;code&gt;nmake V=1&lt;/code&gt; may provide more context for Ruby on Windows)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Isolate the problem you want to fix&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;when I was unsure about what the application manifest did, I validated it with the shortest possible C program to confirm its behavior; this way I knew what I was going for when looking through the Ruby codebase&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Find a reliable way to validate your changes&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;for this type of problem, the solution consisted in making the OS aware of the fact that it should enable long paths support, so it's helpful to figure out how that actually happens for an application; i.e. getting as close as possible to how Windows makes this check&lt;/li&gt;
&lt;li&gt;at first I tested my changes by creating directories with paths longer than 260 characters through &lt;code&gt;irb&lt;/code&gt;, but how do you dig deeper when that doesn't work?&lt;/li&gt;
&lt;li&gt;after a lot of trial and error, I found that the most reliable way to validate my changes was to open &lt;code&gt;ruby.exe&lt;/code&gt; in Resource Hacker, and make sure it only included my manifest, with ID 1&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Sleep on it, and don't be afraid to experiment&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;I managed to get a working fix in the first day, but with each following day I changed up the code, and the final fix ended up being in a totally different place than initially expected&lt;/li&gt;
&lt;li&gt;when you feel that something gets more and more complicated and you're not even close to fixing the problem, see if you can approach it in a different way; this made for a way cleaner solution in my case&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;p&gt;To finish things up, here's an oversimplified diagram of the Ruby on Windows build process:&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%2Fd5b5q8m225wzqp6l763h.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%2Fd5b5q8m225wzqp6l763h.png" alt="Ruby Diagram" width="800" height="692"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here's the story of Ruby trying to access long paths on Windows:&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%2F9e7527te83jvz417jqsy.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%2F9e7527te83jvz417jqsy.png" alt="Ruby Comic" width="800" height="875"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The End?
&lt;/h2&gt;

&lt;p&gt;If you take &lt;a href="https://github.com/puppetlabs/Puppet.Dsc/issues/144#issuecomment-852667097" rel="noopener noreferrer"&gt;a closer look&lt;/a&gt; at the GitHub issue I referred to in the introduction, this does not fully solve our problem in Puppet. On Windows, we use the &lt;a href="https://github.com/halostatue/minitar" rel="noopener noreferrer"&gt;&lt;strong&gt;minitar&lt;/strong&gt;&lt;/a&gt; gem to build and install Puppet modules (which are just tarballs downloaded from the Puppet Forge). Unfortunately, there is an issue with minitar being unable to unpack the tarballs it creates if the file paths exceed a certain length. This leaves us in a state where the modules in question can be built, but cannot be installed.&lt;br&gt;
The plan is to attempt to fix the issue in minitar, and maybe blog about it, so stay tuned for the next installment in the &lt;em&gt;Long Paths&lt;/em&gt; series!&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd" rel="noopener noreferrer"&gt;https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/windows/win32/sbscs/application-manifests" rel="noopener noreferrer"&gt;https://docs.microsoft.com/en-us/windows/win32/sbscs/application-manifests&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;&lt;a href="https://bugs.ruby-lang.org/issues/12551" rel="noopener noreferrer"&gt;https://bugs.ruby-lang.org/issues/12551&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/cpp/build/how-to-embed-a-manifest-inside-a-c-cpp-application" rel="noopener noreferrer"&gt;https://docs.microsoft.com/en-us/cpp/build/how-to-embed-a-manifest-inside-a-c-cpp-application&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>ruby</category>
      <category>windows</category>
      <category>puppet</category>
    </item>
  </channel>
</rss>
