-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathas_of.rb
143 lines (116 loc) · 4.09 KB
/
as_of.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# frozen_string_literal: true
require "date"
require "open-uri"
require "json"
require "set"
module Bundler
module AsOf
BUNDLE_AS_OF = "BUNDLE_AS_OF"
class InstallModifier
def initialize
@modified_dependencies = {} # name => modified Bundler::Dependency
end
def modify_dependencies(dependencies)
if as_of_date.nil?
warn("NOTE: bundler-as_of is installed but #{BUNDLE_AS_OF} is not set")
return
end
warn("NOTE: bundler-as_of: bundling dependencies as of #{as_of_date} ...")
resolve_transitive_dependencies(dependencies)
dependencies.clear
@modified_dependencies.each do |name, dep|
dependencies << dep
end
end
def resolve_transitive_dependencies(dependencies)
queued = dependencies.dup
while !queued.empty?
resolving = queued
queued = []
resolving.each do |dependency|
if dependency.name == "bundler"
raise(BundlerError, "ERROR: please remove bundler from the Gemfile or gemspec")
end
next if @modified_dependencies.key?(dependency.name)
orig_req = dependency.requirements_list
release = VersionFinder.new(dependency, as_of_date).resolve
if release
warn("NOTE: bundler-as_of: resolving #{dependency.name} #{orig_req} to #{release.version} released on #{release.date}")
@modified_dependencies[release.name] = Bundler::Dependency.new(release.name, release.version)
release.dependencies.each do |transitive_name, transitive_req|
transitive_dep = Gem::Dependency.new(transitive_name, transitive_req.split(","))
queued << transitive_dep
end
else
warn("NOTE: bundler-as_of: WARNING: could not resolve #{dependency.name} to a version matching #{dependency.requirements_list} from #{as_of_date}")
@modified_dependencies[dependency.name] = dependency
end
end
end
end
def as_of_date
return nil if as_of_env.nil?
@as_of_date ||= begin
Date.parse(as_of_env)
rescue Date::Error
raise(BundlerError, "ERROR: bundler-as_of could not parse #{BUNDLE_AS_OF}=#{as_of_env.inspect}")
end
end
def as_of_env
ENV[BUNDLE_AS_OF]
end
end
class VersionFinder
def initialize(dependency, as_of_date)
@dependency = dependency
@as_of_date = as_of_date
end
def resolve
releases.each do |release|
next if release.prerelease
return release if @dependency.requirement.satisfied_by?(release.version)
end
nil
end
def releases
gem_releases
.select { |r| r.date <= @as_of_date }
.sort_by { |r| [r.date, r.version] }
.reverse
end
def gem_releases
@gem_releases ||=
JSON.parse(::URI.parse(gem_url).open.read)
.map { |r| ReleaseWrapper.new(@dependency.name, r) }
end
def gem_url
@gem_url ||= "https://rubygems.org/api/v1/versions/#{@dependency.name}.json"
end
end
class ReleaseWrapper
attr_reader :name, :version, :date, :prerelease
def initialize(name, release_json)
@name = name
@version = Gem::Version.new(release_json["number"])
@date = Date.parse(release_json["created_at"])
@prerelease = release_json["prerelease"]
end
def to_s
[name, version.to_s, date.to_s, prerelease, dependencies.to_s].to_s
end
def dependencies
gem_info.find { |info| info[:number] == version.to_s }[:dependencies] || []
end
def gem_info
@gem_info ||= Marshal.load(::URI.parse(gem_url).open.read)
end
def gem_url
@gem_url ||= "https://rubygems.org/api/v1/dependencies?gems=#{name}"
end
end
end
end
Bundler::Plugin.add_hook("before-install-all") do |dependencies|
Bundler::AsOf::InstallModifier.new.modify_dependencies(dependencies)
end
require_relative "as_of/version"