1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

[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
This commit is contained in:
Ellen Marie Dash 2019-08-17 04:45:09 +00:00 committed by Hiroshi SHIBATA
parent 8436b2717c
commit 508afe2c26
No known key found for this signature in database
GPG key ID: F9CF13417264FAC2
6 changed files with 82 additions and 6 deletions

View file

@ -1242,6 +1242,23 @@ An Array (#{env.inspect}) was passed in from #{caller[3]}
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.

View file

@ -193,7 +193,7 @@ class Gem::Package
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] = {} }

View file

@ -123,7 +123,7 @@ class Gem::Package::TarWriter
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
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
: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
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

View file

@ -1667,7 +1667,7 @@ class Gem::Specification < Gem::BasicSpecification
# 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:

View file

@ -457,4 +457,36 @@ class TestGemCommandsBuildCommand < Gem::TestCase
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

View file

@ -2,6 +2,7 @@
# 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
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]