ruby-changes:57913
From: Ellen <ko1@a...>
Date: Thu, 26 Sep 2019 18:11:23 +0900 (JST)
Subject: [ruby-changes:57913] 508afe2c26 (master): [rubygems/rubygems] Set SOURCE_DATE_EPOCH env var if not provided.
https://git.ruby-lang.org/ruby.git/commit/?id=508afe2c26 From 508afe2c26737e0be60a72faa9d6740a06b0914c Mon Sep 17 00:00:00 2001 From: Ellen Marie Dash <the@s...> Date: Sat, 17 Aug 2019 04:45:09 +0000 Subject: [rubygems/rubygems] Set SOURCE_DATE_EPOCH env var if not provided. Fixes #2290. 1. `Gem::Specification.date` returns SOURCE_DATE_EPOCH when defined, 2. this commit makes RubyGems set it _persistently_ when not provided. This combination means that you can build a gem, check the build time, and use that value to generate a new build -- and then verify they're the same. https://github.com/rubygems/rubygems/commit/d830d53f59 diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 4d06d98..2676fbd 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -1242,6 +1242,23 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} https://github.com/ruby/ruby/blob/trunk/lib/rubygems.rb#L1242 end + ## + # The SOURCE_DATE_EPOCH environment variable (or, if that's not set, the current time), converted to Time object. + # This is used throughout RubyGems for enabling reproducible builds. + # + # If it is not set as an environment variable already, this also sets it. + # + # Details on SOURCE_DATE_EPOCH: + # https://reproducible-builds.org/specs/source-date-epoch/ + + def self.source_date_epoch + if ENV["SOURCE_DATE_EPOCH"].nil? || ENV["SOURCE_DATE_EPOCH"].empty? + ENV["SOURCE_DATE_EPOCH"] = Time.now.to_i.to_s + end + + Time.at(ENV["SOURCE_DATE_EPOCH"].to_i).utc.freeze + end + # FIX: Almost everywhere else we use the `def self.` way of defining class # methods, and then we switch over to `class << self` here. Pick one or the # other. diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb index 16cda5a..bef37ae 100644 --- a/lib/rubygems/package.rb +++ b/lib/rubygems/package.rb @@ -193,7 +193,7 @@ class Gem::Package https://github.com/ruby/ruby/blob/trunk/lib/rubygems/package.rb#L193 def initialize(gem, security_policy) # :notnew: @gem = gem - @build_time = ENV["SOURCE_DATE_EPOCH"] ? Time.at(ENV["SOURCE_DATE_EPOCH"].to_i).utc : Time.now + @build_time = Gem.source_date_epoch @checksums = {} @contents = nil @digests = Hash.new { |h, algorithm| h[algorithm] = {} } diff --git a/lib/rubygems/package/tar_writer.rb b/lib/rubygems/package/tar_writer.rb index 87ee39a..96d8184 100644 --- a/lib/rubygems/package/tar_writer.rb +++ b/lib/rubygems/package/tar_writer.rb @@ -123,7 +123,7 @@ class Gem::Package::TarWriter https://github.com/ruby/ruby/blob/trunk/lib/rubygems/package/tar_writer.rb#L123 header = Gem::Package::TarHeader.new :name => name, :mode => mode, :size => size, :prefix => prefix, - :mtime => ENV["SOURCE_DATE_EPOCH"] ? Time.at(ENV["SOURCE_DATE_EPOCH"].to_i).utc : Time.now + :mtime => Gem.source_date_epoch @io.write header @io.pos = final_pos @@ -217,7 +217,7 @@ class Gem::Package::TarWriter https://github.com/ruby/ruby/blob/trunk/lib/rubygems/package/tar_writer.rb#L217 header = Gem::Package::TarHeader.new(:name => name, :mode => mode, :size => size, :prefix => prefix, - :mtime => ENV["SOURCE_DATE_EPOCH"] ? Time.at(ENV["SOURCE_DATE_EPOCH"].to_i).utc : Time.now).to_s + :mtime => Gem.source_date_epoch).to_s @io.write header os = BoundedStream.new @io, size @@ -245,7 +245,7 @@ class Gem::Package::TarWriter https://github.com/ruby/ruby/blob/trunk/lib/rubygems/package/tar_writer.rb#L245 :size => 0, :typeflag => "2", :linkname => target, :prefix => prefix, - :mtime => ENV["SOURCE_DATE_EPOCH"] ? Time.at(ENV["SOURCE_DATE_EPOCH"].to_i).utc : Time.now).to_s + :mtime => Gem.source_date_epoch).to_s @io.write header @@ -298,7 +298,7 @@ class Gem::Package::TarWriter https://github.com/ruby/ruby/blob/trunk/lib/rubygems/package/tar_writer.rb#L298 header = Gem::Package::TarHeader.new :name => name, :mode => mode, :typeflag => "5", :size => 0, :prefix => prefix, - :mtime => ENV["SOURCE_DATE_EPOCH"] ? Time.at(ENV["SOURCE_DATE_EPOCH"].to_i).utc : Time.now + :mtime => Gem.source_date_epoch @io.write header diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index c023e4f..b3db311 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -1667,7 +1667,7 @@ class Gem::Specification < Gem::BasicSpecification https://github.com/ruby/ruby/blob/trunk/lib/rubygems/specification.rb#L1667 # https://reproducible-builds.org/specs/source-date-epoch/ def date - @date ||= ENV["SOURCE_DATE_EPOCH"] ? Time.utc(*Time.at(ENV["SOURCE_DATE_EPOCH"].to_i).utc.to_a[3..5].reverse) : TODAY + @date ||= Time.utc(*Gem.source_date_epoch.utc.to_a[3..5].reverse) end DateLike = Object.new # :nodoc: diff --git a/test/rubygems/test_gem_commands_build_command.rb b/test/rubygems/test_gem_commands_build_command.rb index 0c511b2..50c447e 100644 --- a/test/rubygems/test_gem_commands_build_command.rb +++ b/test/rubygems/test_gem_commands_build_command.rb @@ -457,4 +457,36 @@ class TestGemCommandsBuildCommand < Gem::TestCase https://github.com/ruby/ruby/blob/trunk/test/rubygems/test_gem_commands_build_command.rb#L457 assert_match(/INFO: Your expired cert will be located at: .+\Wgem-public_cert\.pem\.expired\.[0-9]+/, output.shift) end + def test_build_is_reproducible + epoch = ENV["SOURCE_DATE_EPOCH"] + new_epoch = Time.now.to_i.to_s + ENV["SOURCE_DATE_EPOCH"] = new_epoch + + gem_file = File.basename(@gem.cache_file) + + gemspec_file = File.join(@tempdir, @gem.spec_name) + File.write(gemspec_file, @gem.to_ruby) + @cmd.options[:args] = [gemspec_file] + + util_test_build_gem @gem + + build1_contents = File.read(gem_file) + + # Guarantee the time has changed. + sleep 1 if Time.now.to_i == new_epoch + + ENV["SOURCE_DATE_EPOCH"] = new_epoch + + @ui = Gem::MockGemUi.new + @cmd.options[:args] = [gemspec_file] + + util_test_build_gem @gem + + build2_contents = File.read(gem_file) + + assert_equal build1_contents, build2_contents + ensure + ENV["SOURCE_DATE_EPOCH"] = epoch + end + end diff --git a/test/rubygems/test_gem_package.rb b/test/rubygems/test_gem_package.rb index f2f712e..a282e0c 100644 --- a/test/rubygems/test_gem_package.rb +++ b/test/rubygems/test_gem_package.rb @@ -2,6 +2,7 @@ https://github.com/ruby/ruby/blob/trunk/test/rubygems/test_gem_package.rb#L2 # frozen_string_literal: true require 'rubygems/package/tar_test_case' +require 'digest' class TestGemPackage < Gem::Package::TarTestCase @@ -123,6 +124,32 @@ class TestGemPackage < Gem::Package::TarTestCase https://github.com/ruby/ruby/blob/trunk/test/rubygems/test_gem_package.rb#L124 ENV["SOURCE_DATE_EPOCH"] = epoch end + def test_build_time_source_date_epoch_automatically_set + epoch = ENV["SOURCE_DATE_EPOCH"] + ENV["SOURCE_DATE_EPOCH"] = nil + + start_time = Time.now.utc.to_i + + spec = Gem::Specification.new 'build', '1' + spec.summary = 'build' + spec.authors = 'build' + spec.files = ['lib/code.rb'] + spec.rubygems_version = Gem::Version.new '0' + + package = Gem::Package.new spec.file_name + + end_time = Time.now.utc.to_i + + assert package.build_time.is_a?(Time) + + build_time = package.build_time.to_i + + assert(start_time <= build_time) + assert(build_time <= end_time) + ensure + ENV["SOURCE_DATE_EPOCH"] = epoch + end + def test_add_files spec = Gem::Specification.new spec.files = %w[lib/code.rb lib/empty] -- cgit v0.10.2 -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/