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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
|
require "bundler"
require "set"
module GemfileUtil
# gemspec and gem need to use absolute paths for things in order for our Gemfile
# to be *included* in another. This works around some issues in bundler 1.11.2.
def gemspec(options = {})
options[:path] = File.expand_path(options[:path] || ".", Bundler.default_gemfile.dirname)
super
end
#
# gemspec and gem need to use absolute paths for things in order for our Gemfile
# to be *included* in another. This works around some issues in bundler 1.11.2.
# Also adds `override: true`, which allows your statement to override any other
# gem statement about the same gem in the Gemfile.
#
def gem(name, *args)
Bundler.ui.debug "gem #{name}, #{args.join(", ")}"
current_dep = dependencies.find { |dep| dep.name == name }
# Set path to absolute in case this is an included Gemfile in bundler 1.11.2 and below
options = args[-1].is_a?(Hash) ? args[-1] : {}
if options[:path]
# path sourced gems are assumed to be overrides.
options[:override] = true
options[:path] = File.expand_path(options[:path], Bundler.default_gemfile.dirname)
end
# Handle override
if options[:override]
override = true
options.delete(:override)
if current_dep
dependencies.delete(current_dep)
end
else
# If an override gem already exists, and we're not an override gem,
# ignore this gem in favor of the override (but warn if they don't match)
if overridden_gems.include?(name)
args.pop if args[-1].is_a?(Hash)
version = args || [">=0"]
desired_dep = Bundler::Dependency.new(name, version, options.dup)
if desired_dep =~ current_dep
Bundler.ui.debug "Replaced Gemfile dependency #{desired_dep} (#{desired_dep.source}) with override gem #{current_dep} (#{current_dep.source})"
else
Bundler.ui.warn "Replaced Gemfile dependency #{desired_dep} (#{desired_dep.source}) with incompatible override gem #{current_dep} (#{current_dep.source})"
end
return
end
end
# Add the gem normally
super
overridden_gems << name if override
# Emit a warning if we're replacing a dep that doesn't match
if current_dep && override
added_dep = dependencies.find { |dep| dep.name == name }
if added_dep =~ current_dep
Bundler.ui.debug "Replaced Gemfile dependency #{current_dep} (#{current_dep.source}) with override gem #{added_dep} (#{added_dep.source})"
else
Bundler.ui.warn "Replaced Gemfile dependency #{current_dep} (#{current_dep.source}) with incompatible override gem #{added_dep} (#{added_dep.source})"
end
end
end
def overridden_gems
@overridden_gems ||= Set.new
end
#
# Include all gems in the locked gemfile.
#
# @param gemfile Path to the Gemfile to load
# @param groups A list of groups to include (whitelist). If not passed (or set
# to nil), all gems will be selected.
# @param gems A list of gems to include above and beyond the given groups.
# Gems in this list must be explicitly included in the Gemfile
# with a `gem "gem_name", ...` line or they will be silently
# ignored.
#
def include_locked_gemfile(gemfile, groups: nil, gems: [])
Bundler.ui.info "Loading locks from #{gemfile} ..."
gemfile = File.expand_path(gemfile, Bundler.default_gemfile.dirname)
#
# Read the gemfile and inject its locks as first-class dependencies
#
old_gemfile = ENV["BUNDLE_GEMFILE"]
old_frozen = Bundler.settings[:frozen]
begin
# Set frozen to true so we don't try to install stuff.
Bundler.settings[:frozen] = true
# Set BUNDLE_GEMFILE to the new gemfile temporarily so all bundler's things work
# This works around some issues in bundler 1.11.2.
ENV["BUNDLE_GEMFILE"] = gemfile
bundle = Bundler::Definition.build(gemfile, "#{gemfile}.lock", nil)
# Narrow it down to just the dependencies in the desired groups
dependencies = bundle.dependencies.select do |dep|
groups.nil? || (dep.groups & groups).any? || gems.include?(dep.name)
end
# Get the resolved specs descended from our dependencies, from the lockfile
# This is sacrilege: figure out a way we can grab the list of dependencies *without*
# requiring everything to be installed or calling private methods ...
specs = bundle.resolve.for(bundle.send(:expand_dependencies, dependencies))
# Go through and create the actual gemfile from the given locks and
# groups.
specs.sort_by { |spec| spec.name }.each do |spec|
# bundler can't be installed by bundler so don't pin it.
next if spec.name == "bundler"
# Copy groups and platforms from included Gemfile
gem_metadata = {}
dep = bundle.dependencies.find { |d| d.name == spec.name }
if dep
gem_metadata[:groups] = dep.groups unless dep.groups == [:default]
gem_metadata[:platforms] = dep.platforms unless dep.platforms.empty?
end
gem_metadata[:override] = true
# Copy source information from included Gemfile
use_version = false
case spec.source
when Bundler::Source::Rubygems
gem_metadata[:source] = spec.source.remotes.first.to_s
use_version = true
when Bundler::Source::Git
gem_metadata[:git] = spec.source.uri.to_s
gem_metadata[:ref] = spec.source.revision
when Bundler::Source::Path
gem_metadata[:path] = spec.source.path.to_s
else
raise "Unknown source #{spec.source} for gem #{spec.name}"
end
# Emit the dep
if use_version
Bundler.ui.debug("Adding #{spec.name}, #{spec.version}, #{gem_metadata} from #{gemfile}")
gem spec.name, spec.version, gem_metadata
else
Bundler.ui.debug("Adding #{spec.name}, #{gem_metadata} from #{gemfile}")
gem spec.name, gem_metadata
end
end
Bundler.ui.info "Loaded #{bundle.resolve.count} locked gem versions #{groups ? "from groups #{groups.join(", ")}" : ""}#{gems.empty? ? "" : " (including #{gems.join(", ")})"} from #{gemfile}"
rescue Exception
# Bundler does a bad job of rescuing.
Bundler.ui.info $!
Bundler.ui.info $!.backtrace
raise
ensure
Bundler.settings[:frozen] = old_frozen
ENV["BUNDLE_GEMFILE"] = old_gemfile
end
end
end
|