ruby/tool/binary-package.rb
Hiroshi SHIBATA 908150f7e2 Harden path handling and license collection in binary-package
Normalize backslashes in the directory arguments before they reach
Dir.glob so an overridden vcpkg path fails clearly instead of through
a misleading missing-DLL abort, and abort when no vcpkg copyright files
are collected so the package never ships a bundled DLL without its
license terms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 13:26:29 +09:00

90 lines
3.6 KiB
Ruby

#!/usr/bin/ruby
# Assemble a relocatable binary package from a staged installation on
# Windows (mswin). Invoked by the `binary-package` target in
# win32/Makefile.sub; not intended to be run manually.
require 'optparse'
require 'fileutils'
stage = name = srcdir = vcpkgdir = output = nil
opt = OptionParser.new
opt.on('--stage=DIR') {|v| stage = v}
opt.on('--name=NAME') {|v| name = v}
opt.on('--srcdir=DIR') {|v| srcdir = v}
opt.on('--vcpkg-dir=DIR') {|v| vcpkgdir = v}
opt.on('--output=FILE') {|v| output = v}
opt.parse!(ARGV)
abort(opt.to_s) unless stage and name and srcdir and vcpkgdir and output
stage = File.expand_path(stage)
output = File.expand_path(output)
# Normalize so backslashes are not treated as glob escapes below.
srcdir = srcdir.tr("\\", "/")
vcpkgdir = vcpkgdir.tr("\\", "/")
# Find the installed prefix inside the stage. DESTDIR staging
# reproduces the prefix below the stage directory (with the drive
# letter stripped), so descend while there is a single directory
# until bin/ appears.
root = stage
until File.directory?(File.join(root, "bin"))
entries = Dir.children(root)
unless entries.size == 1 and File.directory?(dir = File.join(root, entries[0]))
abort "#{$0}: could not locate installed prefix under #{stage}"
end
root = dir
end
abort "#{$0}: prefix must not be the stage root" if root == stage
bindir = File.join(root, "bin")
# Bundle the vcpkg runtime DLLs next to ruby.exe, where they are
# found first by the DLL search order.
dlls = Dir.glob(File.join(vcpkgdir, "bin", "*.dll"))
dlls.reject! {|f| File.basename(f, ".dll") == "readline"}
abort "#{$0}: no DLLs in #{vcpkgdir}/bin; run `nmake install-vcpkg' first" if dlls.empty?
dlls.each {|f| FileUtils.cp(f, bindir)}
# The VC runtime (vcruntime140*.dll) is deliberately not bundled.
# App-local copies are never serviced by Windows Update; the package
# assumes the VC++ Redistributable is installed.
# https://bugs.ruby-lang.org/issues/22180
# Collect license terms of everything the package redistributes.
licdir = File.join(root, "LICENSES")
FileUtils.mkdir_p(File.join(licdir, "ruby"))
%w[COPYING COPYING.ja BSDL LEGAL].each do |f|
src = File.join(srcdir, f)
FileUtils.cp(src, File.join(licdir, "ruby", f)) if File.exist?(src)
end
copyrights = Dir.glob(File.join(vcpkgdir, "share", "*", "copyright"))
abort "#{$0}: no copyright files in #{vcpkgdir}/share" if copyrights.empty?
copyrights.each do |f|
FileUtils.cp(f, File.join(licdir, "#{File.basename(File.dirname(f))}.txt"))
end
# Scrub build-machine specific paths from the recorded configure
# arguments. mkmf applies $configure_args (notably --with-opt-dir) to
# every extension build, so a leftover absolute path breaks or taints
# gem compilation on the destination machine.
rbconfigs = Dir.glob(File.join(root, "lib/ruby/*/*/rbconfig.rb"))
abort "#{$0}: rbconfig.rb not found under #{root}" if rbconfigs.empty?
rbconfigs.each do |file|
src = File.binread(file)
src.sub!(/^\s*CONFIG\["configure_args"\]\s*=\s*"\K.*(?=")/) do |args|
args.scan(/\\".*?\\"|\S+/).grep_v(%r{\b[A-Za-z]:[/\\]}).join(" ")
end or abort "#{$0}: configure_args not found in #{file}"
File.binwrite(file, src)
end
# Rename the prefix directory to the package name and archive it with
# bsdtar, which ships with Windows 10 and later. Prefer the inbox
# tar.exe; a GNU tar earlier in PATH cannot write zip with -a.
pkgdir = File.join(stage, name)
File.rename(root, pkgdir) unless root == pkgdir
FileUtils.rm_f(output)
tar = File.join(ENV["SystemRoot"] || "C:/Windows", "System32", "tar.exe")
tar = "tar" unless File.exist?(tar)
system(tar, "-a", "-c", "-f", output, "-C", stage, name, exception: true)
puts "packaged #{output}"