Skip to content

Commit

Permalink
Use new side-by-side mechanism only on ruby-3.4+
Browse files Browse the repository at this point in the history
It it too heavy to be introduced in stable releases.

The creation of the junction file for OpenSSL is backported to Ruby-3.2.x and 3.3.x but it links to the old destination /bin/etc/ssl there.
This should ease the transition to ruby-3.4 and makes the documentation unifrom.
  • Loading branch information
larskanis committed Jan 14, 2025
1 parent 58bbe75 commit ff96dd3
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 159 deletions.
5 changes: 3 additions & 2 deletions recipes/installer-inno/rubyinstaller.iss.erb
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,16 @@ Root: <%= regroot %>; Subkey: Software\Classes\<%= rubyname %>File\shell\open\co
; So use Innosetup to add described permissions and icacls to disable unwanted inheritance.
[Dirs]
Name: {app}; Permissions: creatorowner-full users-readexec admins-full
Name: "{app}/lib/ruby/<%= package.rubylibver %>/etc"
Name: "{app}/<%= package.rubyver2 =~ /^3\.[23]$/ ? "bin/etc" : "lib/ruby/#{ package.rubylibver }/etc" %>"

[Run]
; Set permissions on install directories
Filename: "icacls.exe"; Parameters: """{app}"" /inheritancelevel:r "; WorkingDir: "{app}"; StatusMsg: "Changing install Directory Permissions"; Flags: runhidden
<% if with_msys %>
Filename: "icacls.exe"; Parameters: """{app}\<%= package.msysdir %>\tmp"" /inheritancelevel:r /grant *S-1-5-32-545:(CI)(WD,AD,WEA,WA) /grant *S-1-3-0:(OI)(CI)(IO)(F) /grant *S-1-5-32-544:(OI)(CI)(F) /grant *S-1-5-32-545:(NP)(RX) "; WorkingDir: "{app}"; StatusMsg: "Changing MSYS2 /tmp Directory Permissions"; Flags: runhidden; Components: msys2
<% end %>
; Add link to SSL CA certs so that OpenSSL finds them based on the libssl.dll location
Filename: "{cmd}"; Parameters: "/c mklink /j ssl ..\..\..\..\ssl"; WorkingDir: "{app}/lib/ruby/<%= package.rubylibver %>/etc"; StatusMsg: "Add link to SSL CA certs"; Flags: runhidden
Filename: "{cmd}"; Parameters: "/c mklink /j <%= package.rubyver2 =~ /^3\.[23]$/ ? "bin" : "lib\\ruby\\#{package.rubylibver}" %>\etc\ssl ssl"; WorkingDir: "{app}"; StatusMsg: "Add link to SSL CA certs"; Flags: runhidden

[Icons]
Name: {autoprograms}\{#InstallerName}\{cm:InteractiveRubyTitle}; Filename: {app}\bin\irb.<%= package.rubyver2 < '3.1' ? "cmd" : "bat" %>; IconFilename: {app}\bin\ruby.exe
Expand Down
88 changes: 88 additions & 0 deletions recipes/sandbox/60-side-by-side-assembly-ruby-3.123.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Move bundled RubyInstaller DLLs to a subdirectory.
# This avoids interferences with other apps when ruby.exe is in the PATH.

if package.rubyver2 < "3.4"
libruby_regex = /(msvcrt|ucrt)-ruby\d+\.dll$/i
bin_dir = File.join(sandboxdir, "bin")
dlls_dir = File.join(sandboxdir, "bin/ruby_builtin_dlls")
directory bin_dir
directory dlls_dir

# Select the DLLs from "bin/" which shall be moved into "bin/ruby_builtin_dlls/"
dlls = self.sandboxfiles.select do |destpath|
destpath.start_with?(bin_dir+"/") && destpath =~ /\.dll$/i && destpath !~ libruby_regex
end

dlls.each do |destpath|
# Add tasks to write the DLLs into the sub directory
new_destpath = File.join(File.dirname(destpath), "ruby_builtin_dlls", File.basename(destpath))
file new_destpath => [destpath.sub(sandboxdir, unpackdirmgw), dlls_dir] do |t|
cp(t.prerequisites.first, t.name)
end

# Move the DLLs in the dependent files list to the subdirectory
self.sandboxfiles.delete(destpath)
self.sandboxfiles << new_destpath
end

# Add a custom manifest to both ruby.exe and rubyw.exe, so that they find the DLLs to be moved
self.sandboxfiles.select do |destpath|
destpath =~ /\/rubyw?\.exe$/i
end.each do |destpath|
file destpath => [destpath.sub(sandboxdir, unpackdirmgw), bin_dir] do |t|
puts "patching manifest of #{t.name}"
libruby = File.basename(self.sandboxfiles.find{|a| a=~libruby_regex })

image = File.binread(t.prerequisites.first)
# The XML elements we want to add to the default MINGW manifest:
new = <<-EOT
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
<ws2:longPathAware>true</ws2:longPathAware>
</windowsSettings>
</application>
<dependency>
<dependentAssembly>
<assemblyIdentity version="1.0.0.0" type="win32" name="ruby_builtin_dlls" />
</dependentAssembly>
</dependency>
<file name="#{ libruby }"/>
EOT

# There are two regular options to add a custom manifest:
# 1. Change a given exe file per Microsofts "mt.exe" after the build
# 2. Specify a the manifest while linking with the MINGW toolchain
#
# Since we don't want to depend on particular Microsoft tools and want to avoid additional patching of the ruby build, we do a nifty trick here.
# We patch the exe file manually.
# Removing unnecessary spaces and comments from the embedded XML manifest gives us enough space to add the above XML elements.
# Then the default MINGW manifest gets replaced by our custom XML content.
# The rest of the available bytes is simply padded with spaces, so that we don't change positions within the EXE image.
image.gsub!(/<\?xml.*?<assembly.*?<\/assembly>\n/m) do |m|
newm = m.gsub(/^\s*<\/assembly>\s*$/, new + "</assembly>")
.gsub(/<!--.*?-->/m, "")
.gsub(/^ +/, "")
.gsub(/\n+/m, "\n")

raise "replacement manifest to big #{m.bytesize} < #{newm.bytesize}" if m.bytesize < newm.bytesize
newm + " " * (m.bytesize - newm.bytesize)
end
File.binwrite(t.name, image)
end
end

# Add a detached manifest file within the sub directory that lists all DLLs in question
manifest2 = File.join(sandboxdir, "bin/ruby_builtin_dlls/ruby_builtin_dlls.manifest")
file manifest2 => [dlls_dir] do |t|
puts "generate #{t.name}"
File.binwrite t.name, <<-EOT
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32" name="ruby_builtin_dlls" version="1.0.0.0"></assemblyIdentity>
#{ dlls.map{|dll| %Q{<file name="#{File.basename(dll)}"/>} }.join }
</assembly>
EOT
end
self.sandboxfiles << manifest2
end
156 changes: 156 additions & 0 deletions recipes/sandbox/60-side-by-side-assembly-ruby-3.4+.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# Move bundled RubyInstaller DLLs to a subdirectory.
# This avoids interferences with other apps when ruby.exe is in the PATH.

if package.rubyver2 >= "3.4"
libruby_regex = /(msvcrt|ucrt)-ruby\d+\.dll$/i
bin_dir = File.join(sandboxdir, "bin")
dlls_dir = File.join(sandboxdir, "bin/ruby_builtin_dlls")
directory bin_dir
directory dlls_dir

# Select the DLLs from "bin/" which shall be moved into "bin/ruby_builtin_dlls/"
dlls = self.sandboxfiles.select do |destpath|
destpath.start_with?(bin_dir+"/") && destpath =~ /\.dll$/i && destpath !~ libruby_regex
end

ext_dll_defs = {
"lib/ruby/#{package.rubylibver}/#{package.ruby_arch}/fiddle.so" => /^libffi-\d.dll$/,
"lib/ruby/#{package.rubylibver}/#{package.ruby_arch}/openssl.so" => /^libssl-[\d_]+(-x64)?.dll$|^libcrypto-[\d_]+(-x64)?.dll$/,
"lib/ruby/#{package.rubylibver}/#{package.ruby_arch}/psych.so" => /^libyaml-[-\d]+.dll$/,
"lib/ruby/#{package.rubylibver}/#{package.ruby_arch}/zlib.so" => /^zlib\d.dll$/,
}

core_dll_defs = [
/^libgmp-\d+.dll$/,
/^libwinpthread-\d+.dll$/,
/^libgcc_s_.*.dll$/,
]

# create rake tasks to trigger additional processing of so files
ext_dll_defs.keys.each do |so_file|
self.sandboxfiles << File.join(sandboxdir, so_file)
end

core_dlls, dlls = dlls.partition do |destpath|
core_dll_defs.any? { |re| re =~ File.basename(destpath) }
end
ext_dlls, dlls = dlls.partition do |destpath|
ext_dll_defs.values.any? { |re| re =~ File.basename(destpath) }
end
raise "DLL belonging neither to core nor to exts: #{dlls}" unless dlls.empty?


###########################################################################
# Add manifest to extension.so files pointing to linked MINGW library DLLs
# next to it
###########################################################################

# Add tasks to move the DLLs into the extension directory
ext_dlls.each do |destpath|
so_fname, _ = ext_dll_defs.find { |_, re| re =~ File.basename(destpath) }

new_destpath = File.join(sandboxdir, File.dirname(so_fname), File.basename(destpath))
file new_destpath => [destpath.sub(sandboxdir, unpackdirmgw), File.dirname(new_destpath)] do |t|
cp(t.prerequisites.first, t.name)
end

# Move the DLLs in the dependent files list to the subdirectory
self.sandboxfiles.delete(destpath)
self.sandboxfiles << new_destpath
end

# Add a custom manifest to each extension.so, so that they find the DLLs to be moved
ext_dlls.each do |destpath|
so_fname, _ = ext_dll_defs.find { |_, re| re =~ File.basename(destpath) }
sandbox_so_fname = File.join(sandboxdir, so_fname)

file sandbox_so_fname => [sandbox_so_fname.sub(sandboxdir, unpackdirmgw), File.dirname(sandbox_so_fname)] do |t|
puts "patching manifest of #{t.name}"

# The XML elements we want to add to the default MINGW manifest:
new = <<~EOT
<dependency>
<dependentAssembly>
<assemblyIdentity version="1.0.0.0" type="win32" name="#{File.basename(so_fname)}-assembly" />
</dependentAssembly>
</dependency>
EOT

ManifestUpdater.update_file(t.prerequisites.first, new, t.name)
end
end

# Add a detached manifest file within the ext.so directory that lists all linked DLLs
ext_dll_defs.each do |so_fname, re|
mani_path = File.join(sandboxdir, so_fname + "-assembly.manifest")
e_dlls = ext_dlls.select { |dll| re =~ File.basename(dll) }

file mani_path => [File.dirname(mani_path)] do |t|
puts "generate #{t.name}"

File.binwrite t.name, <<~EOT
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32" name="#{File.basename(so_fname)}-assembly" version="1.0.0.0"></assemblyIdentity>
#{ e_dlls.map{|dll| %Q{<file name="#{File.basename(dll)}"/>} }.join }
</assembly>
EOT
end
self.sandboxfiles << mani_path
end


#################################################################################
# Add manifest to ruby.exe, rubyw.exe files pointing to DLLs in ruby_builtin_dlls
#################################################################################

core_dlls.each do |destpath|

# Add tasks to write the DLLs into the sub directory
new_destpath = File.join(File.dirname(destpath), "ruby_builtin_dlls", File.basename(destpath))
file new_destpath => [destpath.sub(sandboxdir, unpackdirmgw), dlls_dir] do |t|
cp(t.prerequisites.first, t.name)
end

# Move the DLLs in the dependent files list to the subdirectory
self.sandboxfiles.delete(destpath)
self.sandboxfiles << new_destpath
end

# Add a custom manifest to ruby.exe, rubyw.exe and libruby, so that they find the DLLs to be moved
self.sandboxfiles.select do |destpath|
destpath =~ libruby_regex
end.each do |destpath|

file destpath => [destpath.sub(sandboxdir, unpackdirmgw), bin_dir] do |t|
puts "patching manifest of #{t.name}"

# The XML elements we want to add to the default MINGW manifest:
new = <<~EOT
<dependency>
<dependentAssembly>
<assemblyIdentity version="1.0.0.0" type="win32" name="ruby_builtin_dlls" />
</dependentAssembly>
</dependency>
EOT

ManifestUpdater.update_file(t.prerequisites.first, new, t.name)
end
end

# Add a detached manifest file within the sub directory that lists all DLLs in question
manifest2 = File.join(sandboxdir, "bin/ruby_builtin_dlls/ruby_builtin_dlls.manifest")
file manifest2 => [dlls_dir] do |t|
puts "generate #{t.name}"
File.binwrite t.name, <<~EOT
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32" name="ruby_builtin_dlls" version="1.0.0.0"></assemblyIdentity>
#{ core_dlls.map{|dll| %Q{<file name="#{File.basename(dll)}"/>} }.join }
</assembly>
EOT
end
self.sandboxfiles << manifest2
end
Loading

0 comments on commit ff96dd3

Please sign in to comment.