From 451569df793788322cc5d9f91b4b70cab9c5b800 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Wed, 17 Jul 2024 15:52:27 -0700 Subject: [PATCH] Reproducible gem builds on jruby --- .gitignore | 2 ++ spec/gem_server_conformance_spec.rb | 34 +++++++++++++++++++++++----- spec/support/request_helpers.rb | 35 +++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 8baa9b4..3a63e6c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ # rspec failure tracking *.rspec_status + +/build_gem/ diff --git a/spec/gem_server_conformance_spec.rb b/spec/gem_server_conformance_spec.rb index f7c2869..a8bc69d 100644 --- a/spec/gem_server_conformance_spec.rb +++ b/spec/gem_server_conformance_spec.rb @@ -70,24 +70,43 @@ io.binmode package = Gem::Package.new(io) actual = [] + + presence = proc do |x| + x if x && !x.empty? + end + dump_tar = proc do |tar_io, into = actual| Gem::Package::TarReader.new(tar_io) do |gem| gem.each do |entry| body = entry.read - body = Zlib.gunzip(body) if entry.full_name.end_with?(".gz") + extra = nil + if entry.full_name.end_with?(".gz") + extra = Zlib::GzipReader.wrap(StringIO.new(body)) do |gz| + magic, compression_method, flags, mtime, compression, os_id = body.unpack("H4ccVcC") + { + magic: magic, compression_method: compression_method, flags: flags, mtime: mtime, + compression: compression, os_id: os_id, comment: presence[gz.comment], crc: gz.crc, + orig_name: presence[gz.orig_name] + }.compact + end + body = Zlib.gunzip(body) + end body = dump_tar[StringIO.new(body), []] if entry.full_name.end_with?(".tar.gz") into << { header: entry.header.instance_variables.to_h do |ivar| [ivar.to_s.tr("@", "").to_sym, entry.header.instance_variable_get(ivar)] end, - body: body - } + body: body, + extra: extra + }.compact end end into end package.gem.with_read_io(&dump_tar) + expected_gz_extra = { compression: 2, compression_method: 8, crc: 0, flags: 0, magic: "1f8b", mtime: 0, + os_id: 3 } expect(actual).to eq( [{ body: <<~YAML, --- !ruby/object:Gem::Specification @@ -142,7 +161,8 @@ typeflag: "0", uid: 0, uname: "wheel", - version: 0 } }, + version: 0 }, + extra: expected_gz_extra }, { body: [], header: { checksum: 5834, devmajor: 0, @@ -160,7 +180,8 @@ typeflag: "0", uid: 0, uname: "wheel", - version: 0 } }, + version: 0 }, + extra: expected_gz_extra }, { body: <<~YAML, --- SHA256: @@ -186,7 +207,8 @@ typeflag: "0", uid: 0, uname: "wheel", - version: 0 } }] + version: 0 }, + extra: expected_gz_extra }] ) end diff --git a/spec/support/request_helpers.rb b/spec/support/request_helpers.rb index 7e5a0d4..e486d30 100644 --- a/spec/support/request_helpers.rb +++ b/spec/support/request_helpers.rb @@ -23,6 +23,34 @@ def encode_with(coder) end end + module PackageGzipToConsistentOS + class IOWrapper < SimpleDelegator + def write(str) + str[9] = "\x03".b if str.size == 10 && str.start_with?("\x1f\x8b".b) + super + end + end + + def gzip_to(io, &blk) + super(IOWrapper.new(io), &blk) + end + end + + ::Gem::Dependency.class_eval do + if Gem::Dependency.method_defined?(:to_yaml_properties) + prepend( + Module.new do + def to_yaml_properties + expected = %i[@name @requirement @type @prerelease @version_requirements] + actual = super + + (expected & actual) + (actual - expected) + end + end + ) + end + end + def build_gem(name, version, platform: nil) spec = Gem::Specification.new do |s| s.name = name @@ -40,10 +68,17 @@ def build_gem(name, version, platform: nil) package = Gem::Package.new(StringIO.new.binmode) package.build_time = Time.utc(1970) package.spec = spec + package.singleton_class.prepend(PackageGzipToConsistentOS) package.gem.singleton_class.send(:define_method, :path) { "" } package.build + if ENV["DUMP_BUILD_GEM"] + tmp = "build_gem/#{RUBY_ENGINE}/rubygems-#{Gem::VERSION}/#{@time}" + FileUtils.mkdir_p(tmp) + File.binwrite("#{tmp}/#{spec.full_name}.gem", package.gem.io.string) + end + MockGem.new( name: name, version: spec.version,