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:
parent
8436b2717c
commit
508afe2c26
6 changed files with 82 additions and 6 deletions
|
@ -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.
|
||||
|
|
|
@ -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] = {} }
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
Loading…
Add table
Reference in a new issue