Skip to content

For gem developers

Lars Kanis edited this page Aug 1, 2024 · 33 revisions

RubyInstaller2 for Windows porting guide for gem developers

DLL loading

RubyInstaller2 (aka RubyInstaller-2.4 and newer) distinguishs between search paths for executables and for libraries (DLLs). While the PATH environment variable defines the paths to be searched for executables, it is ignored for all DLL searches within the Ruby process. Dependent DLLs can instead be loaded by setting the Windows DLL search path using the environment variable RUBY_DLL_PATH or by the function RubyInstaller::Runtime.add_dll_directory.

Environment variable RUBY_DLL_PATH

This environment variable can be used to add DLL paths for Ruby sub-processes. It can contain multiple semicolon separated unquoted absolute paths. The variable is interpreted at the ruby startup - later changes doesn't affect the running process. Please use the Ruby function add_dll_directory to do runtime changes to DLL search paths.

Ruby function add_dll_directory

This function immediately activates additional DLL paths. It can be used with a block, so that the path is deactivated after the block operation, like in the pg gem . Note, that sub-processes are not affected by this setting. They use the standard Windows DLL search order or whatever the executable requests to use.

Debug DLL loading

Windows DLL loading is very complicated and depends on so many settings, that it's often not obviously, why it fails and what exactly fails. Windows is also often not very verbose about failures in DLL loading.

Fortunately it's possible to enable additional debugging information which helps to track down such issues. This registry setting file enables DLL loader debug flags. Now ruby.exe can be started per gdb, which (after typing r and Enter) prints useful debug information to the console window like so:

  regedit debug-loader.reg
  pacman -S mingw-w64-x86_64-gdb
  gdb --args ruby -S rails s

MSYS2 library dependency

RubyInstaller2 allows to define dependent MSYS2 packages in the gemspec which are required for installation of the gem. The gem can then link to libraries of this package or make use of commands provided by the package. Both MINGW and MSYS2 packages can be specified, although only MINGW packages are usable as library to link to.

MSYS2 packages can be specified per gemspec.metadata['msys2_dependencies']. MINGW packages can be specified per gemspec.metadata['msys2_mingw_dependencies']. The MINGW architecture is set at install time according to the architecture of the running ruby process. Therefore only the architecture independent part of the package name is expected - the prefix mingw-w64-i686- and mingw-w64-x86_64- must be omitted.

Examples:

gemspec.metadata['msys2_mingw_dependencies'] = 'libusb sqlite'

Optionally the name can be followed by a version restriction as described in the pacman man page:

gemspec.metadata['msys2_mingw_dependencies'] = 'libusb>=1.0.21'

However keep in mind that MSYS2 usually provides only one version at the same time for each package.

MSYS2 package dependencies are installed per pacman command, when a gem is about to be installed. The extconf.rb within a gem can access the newly installed MSYS2-MINGW libraries. If the package installation fails, the output of pacman is printed to the console, but the gem installation continues nevertheless. Insofar setting a MSYS2 dependency can make the installation easier, but will not brake the gem installation in case of somehow changed MSYS2 packages.

Installation of MSYS2/MINGW packages can be disabled per gem install gemname.gem --ignore-dependencies .

Examples for dependency definition: sqlite3, glut, fxruby, openssl, mysql2

Path separator

Simple rule: Use backslashs for escaping and forward slashs as filesystem path separator.

Although the "official" path separator on Windows is the backslash, almost all Windows APIs calls accept forward slashs as well, regardless of the Windows version. Since Ruby on Windows returns forward slashs for Dir.glob and others, it is best to completely avoid backslashs in paths written in ruby code. This makes the code portable and more readable.

Note: In contrast - registry access (per stdlib win32/registry) requires the use of backslashs and doesn't work with forward slashs.

Shell escaping

Shell escaping is a very difficult thing on Windows, since there are so many different shells (cmd, powershell, bash, msvcrt) with very different and partly obscure escaping rules. The Ruby stdlib shellwords supports bash escaping only (although there is an implementation on github for Windows shells). So using shellwords often makes things even worse.

It is therefore best to avoid shell escaping at all by using Array argument methods where possible:

system('program', 'with arguments')                     # instead of system("program 'with arguments'")
out = IO.popen(['program', 'with arguments'], &:read)   # instead of out = `program 'with arguments'`
sh "ruby", "program.rb", somearg                        # in a Rakefile

How to use Ruby (-head) on Appveyor for CI

Appveyor is a popular cloud based CI provider for the Windows OS. They provide pre-installed RubyInstaller versions from 1.9 to 3.0 as both 32-bit and 64-bit flavours installed into these directories. They can be selected through the test matrix by setting the PATH environment variable like in this example.

In addition it's possible to run tests on the latest ruby-head version. It is not pre-installed by Appveyor, but can be downloaded and installed easily. See the above example or this commit for one way to do this.

How to use Ruby (-head) on Github Actions?

All released RubyInstaller versions are provided by the official setup-ruby action of the ruby community. In addition it's possible to run tests on the latest ruby-head version by setting ruby-version: ruby-head. See for instance this commit how to enable Windows tests in Github Actions.