appraisal2 3.1.4 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +148 -1
- data/LICENSE.md +2 -1
- data/README.md +77 -34
- data/lib/appraisal/appraisal.rb +3 -2
- data/lib/appraisal/appraisal_file.rb +1 -1
- data/lib/appraisal/cli.rb +131 -21
- data/lib/appraisal/command.rb +142 -83
- data/lib/appraisal/gem_manager/bundler_adapter.rb +15 -2
- data/lib/appraisal/utils.rb +4 -0
- data/lib/appraisal/version.rb +5 -2
- data/lib/appraisal.rb +2 -0
- data/lib/appraisal2.rb +0 -2
- data.tar.gz.sig +0 -0
- metadata +43 -41
- metadata.gz.sig +0 -0
- data/CITATION.cff +0 -20
- data/CODE_OF_CONDUCT.md +0 -134
- data/CONTRIBUTING.md +0 -275
- data/FUNDING.md +0 -70
- data/MIT.md +0 -21
- data/RUBOCOP.md +0 -71
- data/SECURITY.md +0 -21
- data/certs/pboling.pem +0 -27
- data/lib/appraisal2/version.rb +0 -8
- data/sig/appraisal2/version.rbs +0 -6
data/lib/appraisal/cli.rb
CHANGED
|
@@ -2,17 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
require "thor"
|
|
4
4
|
require "fileutils"
|
|
5
|
+
require "thread"
|
|
5
6
|
|
|
6
7
|
module Appraisal
|
|
7
8
|
class CLI < Thor
|
|
9
|
+
MINIMUM_PARALLEL_BUNDLER_VERSION = Gem::Version.new("2.1.0")
|
|
10
|
+
|
|
8
11
|
default_task :install
|
|
9
12
|
map ["-v", "--version"] => "version"
|
|
13
|
+
map "generate-install" => "generate_install"
|
|
14
|
+
map "generate-update" => "generate_update"
|
|
10
15
|
|
|
11
16
|
class_option "gem-manager",
|
|
12
17
|
:aliases => "-g",
|
|
13
18
|
:type => :string,
|
|
14
19
|
:default => "bundler",
|
|
15
20
|
:desc => "Gem manager to use for install/update (bundler or ore)"
|
|
21
|
+
class_option "appraisal-jobs",
|
|
22
|
+
:aliases => "-n",
|
|
23
|
+
:type => :numeric,
|
|
24
|
+
:banner => "SIZE",
|
|
25
|
+
:desc => "Process appraisals in parallel using the given number of workers."
|
|
16
26
|
|
|
17
27
|
class << self
|
|
18
28
|
# Override help command to print out usage
|
|
@@ -27,7 +37,8 @@ module Appraisal
|
|
|
27
37
|
appraisal, otherwise it runs the EXTERNAL_COMMAND against all appraisals.
|
|
28
38
|
HELP
|
|
29
39
|
|
|
30
|
-
|
|
40
|
+
appraisals_file = ENV["APPRAISAL_FILE"] || "Appraisals"
|
|
41
|
+
if File.exist?(appraisals_file)
|
|
31
42
|
shell.say
|
|
32
43
|
shell.say("Available Appraisal(s):")
|
|
33
44
|
|
|
@@ -59,7 +70,7 @@ module Appraisal
|
|
|
59
70
|
:type => :numeric,
|
|
60
71
|
:default => 1,
|
|
61
72
|
:banner => "SIZE",
|
|
62
|
-
:desc => "
|
|
73
|
+
:desc => "Pass the given parallel job count to the selected gem manager."
|
|
63
74
|
method_option "retry",
|
|
64
75
|
:type => :numeric,
|
|
65
76
|
:default => 1,
|
|
@@ -78,11 +89,7 @@ module Appraisal
|
|
|
78
89
|
"Bundler will remember this option."
|
|
79
90
|
|
|
80
91
|
def install
|
|
81
|
-
install_options
|
|
82
|
-
AppraisalFile.each do |appraisal|
|
|
83
|
-
appraisal.install(install_options)
|
|
84
|
-
appraisal.relativize
|
|
85
|
-
end
|
|
92
|
+
install_appraisals(appraisals, install_options, appraisal_jobs(options))
|
|
86
93
|
end
|
|
87
94
|
|
|
88
95
|
desc "generate-install", "Generate gemfiles, then resolve and install dependencies for each appraisal"
|
|
@@ -91,7 +98,7 @@ module Appraisal
|
|
|
91
98
|
:type => :numeric,
|
|
92
99
|
:default => 1,
|
|
93
100
|
:banner => "SIZE",
|
|
94
|
-
:desc => "
|
|
101
|
+
:desc => "Pass the given parallel job count to the selected gem manager."
|
|
95
102
|
method_option "retry",
|
|
96
103
|
:type => :numeric,
|
|
97
104
|
:default => 1,
|
|
@@ -109,15 +116,14 @@ module Appraisal
|
|
|
109
116
|
:desc => "Install gems in the specified directory. " \
|
|
110
117
|
"Bundler will remember this option."
|
|
111
118
|
def generate_install
|
|
112
|
-
|
|
113
|
-
|
|
119
|
+
appraisal_list = appraisals
|
|
120
|
+
generate_appraisals(appraisal_list, appraisal_jobs(options))
|
|
121
|
+
install_appraisals(appraisal_list, install_options, appraisal_jobs(options))
|
|
114
122
|
end
|
|
115
123
|
|
|
116
124
|
desc "generate", "Generate a gemfile for each appraisal"
|
|
117
125
|
def generate
|
|
118
|
-
|
|
119
|
-
appraisal.write_gemfile
|
|
120
|
-
end
|
|
126
|
+
generate_appraisals(appraisals, appraisal_jobs(options))
|
|
121
127
|
end
|
|
122
128
|
|
|
123
129
|
desc "clean", "Remove all generated gemfiles and lockfiles from gemfiles folder"
|
|
@@ -127,17 +133,14 @@ module Appraisal
|
|
|
127
133
|
|
|
128
134
|
desc "update [LIST_OF_GEMS]", "Update dependencies for each generated appraisal gemfile"
|
|
129
135
|
def update(*gems)
|
|
130
|
-
|
|
131
|
-
update_options = gem_manager ? {:gem_manager => gem_manager} : {}
|
|
132
|
-
AppraisalFile.each do |appraisal|
|
|
133
|
-
appraisal.update(gems, update_options)
|
|
134
|
-
end
|
|
136
|
+
update_appraisals(appraisals, gems, update_options, appraisal_jobs(options))
|
|
135
137
|
end
|
|
136
138
|
|
|
137
139
|
desc "generate-update [LIST_OF_GEMS]", "Generate gemfiles, then update dependencies for each appraisal"
|
|
138
140
|
def generate_update(*gems)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
+
appraisal_list = appraisals
|
|
142
|
+
generate_appraisals(appraisal_list, appraisal_jobs(options))
|
|
143
|
+
update_appraisals(appraisal_list, gems, update_options, appraisal_jobs(options))
|
|
141
144
|
end
|
|
142
145
|
|
|
143
146
|
desc "list", "List the names of the defined appraisals"
|
|
@@ -152,7 +155,99 @@ module Appraisal
|
|
|
152
155
|
|
|
153
156
|
private
|
|
154
157
|
|
|
158
|
+
def appraisals
|
|
159
|
+
AppraisalFile.new.appraisals
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def generate_appraisals(appraisal_list, jobs)
|
|
163
|
+
each_appraisal(appraisal_list, jobs) do |appraisal|
|
|
164
|
+
appraisal.write_gemfile
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def install_appraisals(appraisal_list, install_options, jobs)
|
|
169
|
+
each_appraisal(appraisal_list, jobs) do |appraisal|
|
|
170
|
+
appraisal.install(install_options.dup)
|
|
171
|
+
appraisal.relativize
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def update_appraisals(appraisal_list, gems, update_options, jobs)
|
|
176
|
+
each_appraisal(appraisal_list, jobs) do |appraisal|
|
|
177
|
+
appraisal.update(gems, update_options.dup)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def each_appraisal(appraisal_list, jobs)
|
|
182
|
+
jobs = worker_count(jobs, appraisal_list.length)
|
|
183
|
+
return appraisal_list.each { |appraisal| yield(appraisal) } if jobs <= 1
|
|
184
|
+
|
|
185
|
+
queue = Queue.new
|
|
186
|
+
errors = []
|
|
187
|
+
mutex = Mutex.new
|
|
188
|
+
|
|
189
|
+
appraisal_list.each { |appraisal| queue << appraisal }
|
|
190
|
+
jobs.times { queue << nil }
|
|
191
|
+
|
|
192
|
+
# rubocop:disable ThreadSafety/NewThread
|
|
193
|
+
threads = Array.new(jobs) do
|
|
194
|
+
Thread.new do
|
|
195
|
+
while (appraisal = queue.pop)
|
|
196
|
+
begin
|
|
197
|
+
yield(appraisal)
|
|
198
|
+
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
199
|
+
mutex.synchronize { errors << error }
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
# rubocop:enable ThreadSafety/NewThread
|
|
205
|
+
threads.each(&:join)
|
|
206
|
+
|
|
207
|
+
raise errors.first unless errors.empty?
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def worker_count(jobs, item_count)
|
|
211
|
+
jobs = jobs.to_i
|
|
212
|
+
jobs = 1 if jobs < 1
|
|
213
|
+
jobs = 1 unless parallel_appraisals_supported?
|
|
214
|
+
return jobs if item_count.zero?
|
|
215
|
+
|
|
216
|
+
[jobs, item_count].min
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def parallel_appraisals_supported?
|
|
220
|
+
# Parallel install/update workers depend on Appraisal::Command being able
|
|
221
|
+
# to build independent Bundler subprocess environments. Bundler 2.1 is the
|
|
222
|
+
# floor for the modern original_env/unbundled_env API split that replaced
|
|
223
|
+
# clean_env, so older Bundler versions keep the historical serial path.
|
|
224
|
+
bundler_version = Gem::Specification.find_all_by_name("bundler").map(&:version).max
|
|
225
|
+
return false unless bundler_version
|
|
226
|
+
|
|
227
|
+
bundler_version >= MINIMUM_PARALLEL_BUNDLER_VERSION
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def appraisal_jobs(command_options)
|
|
231
|
+
command_options["appraisal-jobs"] || command_options[:appraisal_jobs] || ENV["APPRAISAL_JOBS"] || 2
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def install_options
|
|
235
|
+
options.to_h.reject { |key, _value| key.to_s == "appraisal-jobs" }
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def update_options
|
|
239
|
+
gem_manager = options["gem-manager"] || options[:gem_manager]
|
|
240
|
+
gem_manager ? {:gem_manager => gem_manager} : {}
|
|
241
|
+
end
|
|
242
|
+
|
|
155
243
|
def method_missing(name, *args)
|
|
244
|
+
case name.to_s
|
|
245
|
+
when "generate-install"
|
|
246
|
+
return generate_install
|
|
247
|
+
when "generate-update"
|
|
248
|
+
return generate_update(*args)
|
|
249
|
+
end
|
|
250
|
+
|
|
156
251
|
matching_appraisal = AppraisalFile.new.appraisals.detect do |appraisal|
|
|
157
252
|
appraisal.name == name.to_s
|
|
158
253
|
end
|
|
@@ -213,7 +308,7 @@ module Appraisal
|
|
|
213
308
|
Command.new(actual_args, :gemfile => matching_appraisal.gemfile_path).run
|
|
214
309
|
end
|
|
215
310
|
else
|
|
216
|
-
AppraisalFile.
|
|
311
|
+
each_appraisal(AppraisalFile.new.appraisals, appraisal_jobs(options)) do |appraisal|
|
|
217
312
|
Command.new(ARGV, :gemfile => appraisal.gemfile_path).run
|
|
218
313
|
end
|
|
219
314
|
end
|
|
@@ -240,6 +335,17 @@ module Appraisal
|
|
|
240
335
|
options[:jobs] = Regexp.last_match(1).to_i
|
|
241
336
|
when /^-j(\d+)$/
|
|
242
337
|
options[:jobs] = Regexp.last_match(1).to_i
|
|
338
|
+
when /^-j$/
|
|
339
|
+
options[:jobs] = args[index + 1].to_i
|
|
340
|
+
skip_next = true
|
|
341
|
+
when /^--appraisal-jobs=\d+$/
|
|
342
|
+
nil
|
|
343
|
+
when /^--appraisal-jobs$/
|
|
344
|
+
skip_next = true
|
|
345
|
+
when /^-n\d+$/
|
|
346
|
+
nil
|
|
347
|
+
when /^-n$/
|
|
348
|
+
skip_next = true
|
|
243
349
|
when /^--retry=(\d+)$/
|
|
244
350
|
options[:retry] = Regexp.last_match(1).to_i
|
|
245
351
|
when /^--without=(.+)$/
|
|
@@ -271,6 +377,10 @@ module Appraisal
|
|
|
271
377
|
# Next arg should be the value
|
|
272
378
|
options[:gem_manager] = args[index + 1]
|
|
273
379
|
skip_next = true
|
|
380
|
+
when /^--appraisal-jobs$/
|
|
381
|
+
skip_next = true
|
|
382
|
+
when /^-n$/
|
|
383
|
+
skip_next = true
|
|
274
384
|
when /^-/
|
|
275
385
|
# Other options are not gems, but we don't handle them all here
|
|
276
386
|
# For now, just skip them to be safe if they start with -
|
data/lib/appraisal/command.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "rbconfig"
|
|
3
4
|
require "shellwords"
|
|
4
5
|
|
|
5
6
|
module Appraisal
|
|
@@ -8,7 +9,9 @@ module Appraisal
|
|
|
8
9
|
attr_reader :command, :env, :gemfile
|
|
9
10
|
|
|
10
11
|
# BUNDLE_* environment variables that must be preserved for proper bundler operation
|
|
11
|
-
# and test isolation. These are preserved
|
|
12
|
+
# and test isolation. These are preserved after starting from Bundler's
|
|
13
|
+
# unbundled environment so Appraisal can run a fresh Bundler subprocess
|
|
14
|
+
# without losing the target appraisal and isolation settings:
|
|
12
15
|
# - Bundler version switching works (BUNDLE_GEMFILE)
|
|
13
16
|
# - Test isolation is maintained (BUNDLE_APP_CONFIG, etc.)
|
|
14
17
|
# - User settings are respected (BUNDLE_PATH, BUNDLE_USER_CACHE, etc.)
|
|
@@ -30,6 +33,7 @@ module Appraisal
|
|
|
30
33
|
|
|
31
34
|
PRESERVED_RUNTIME_VARS = [
|
|
32
35
|
"PATH",
|
|
36
|
+
"GEM_HOME",
|
|
33
37
|
"GEM_PATH"
|
|
34
38
|
].freeze
|
|
35
39
|
|
|
@@ -44,101 +48,115 @@ module Appraisal
|
|
|
44
48
|
run_env = test_environment.merge(env)
|
|
45
49
|
|
|
46
50
|
if @skip_bundle_exec
|
|
47
|
-
execute(run_env)
|
|
51
|
+
execute(ENV.to_h, run_env)
|
|
48
52
|
else
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
#
|
|
54
|
-
# Solution: Use a selective environment approach instead of with_original_env,
|
|
55
|
-
# which strips all BUNDLE_* variables and breaks version switching.
|
|
56
|
-
with_bundler_env do
|
|
57
|
-
apply_run_env(run_env)
|
|
58
|
-
ensure_bundler_is_available
|
|
59
|
-
ensure_locked_bundler_is_available
|
|
60
|
-
execute(run_env)
|
|
61
|
-
end
|
|
53
|
+
clean_env = bundler_env(run_env)
|
|
54
|
+
ensure_bundler_is_available(clean_env)
|
|
55
|
+
ensure_locked_bundler_is_available(clean_env)
|
|
56
|
+
execute(clean_env, run_env)
|
|
62
57
|
end
|
|
63
58
|
end
|
|
64
59
|
|
|
65
60
|
private
|
|
66
61
|
|
|
67
|
-
#
|
|
68
|
-
# inputs and test isolation settings.
|
|
62
|
+
# Build an Appraisal subprocess environment from Bundler's explicit env APIs.
|
|
69
63
|
#
|
|
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
|
-
rubyopt.reject! { |opt| opt == "-rbundler/setup" || opt.include?("bundler/setup") }
|
|
96
|
-
clean_env["RUBYOPT"] = rubyopt.join(" ") unless rubyopt.empty?
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
# Remove Bundler activation markers from the subprocess environment.
|
|
100
|
-
# BUNDLE_BIN_PATH pins the already-active Bundler executable, so keeping
|
|
101
|
-
# it would bypass the BUNDLED WITH version selected below.
|
|
102
|
-
clean_env.delete("BUNDLE_BIN_PATH")
|
|
103
|
-
clean_env.delete("BUNDLER_SETUP")
|
|
104
|
-
clean_env.delete("BUNDLER_VERSION")
|
|
105
|
-
|
|
106
|
-
ENV.replace(clean_env)
|
|
107
|
-
|
|
108
|
-
yield
|
|
109
|
-
ensure
|
|
110
|
-
ENV.replace(backup_env)
|
|
64
|
+
# Bundler.clean_env / Bundler.with_clean_env are removed. Bundler now exposes
|
|
65
|
+
# three related but distinct choices:
|
|
66
|
+
#
|
|
67
|
+
# - Bundler.original_env: the environment before the current Bundler activated.
|
|
68
|
+
# This is too broad for Appraisal because unrelated original BUNDLE_* values
|
|
69
|
+
# can leak into the appraisal subprocess.
|
|
70
|
+
# - Bundler.unbundled_env: original_env with Bundler activation removed. This
|
|
71
|
+
# is the right base because it removes parent Bundler state, RUBYOPT
|
|
72
|
+
# bundler/setup activation, and arbitrary BUNDLE_* values.
|
|
73
|
+
# - Bundler.with_unbundled_env: the block form of unbundled_env. Appraisal
|
|
74
|
+
# needs a Hash to pass to Kernel.system and must re-add selected variables,
|
|
75
|
+
# so the block helper is the wrong shape even though its semantics are close.
|
|
76
|
+
#
|
|
77
|
+
# Starting from unbundled_env would normally remove BUNDLE_GEMFILE,
|
|
78
|
+
# BUNDLE_APP_CONFIG, and related isolation knobs. Appraisal intentionally
|
|
79
|
+
# reintroduces only the variables it owns or must preserve, then sets
|
|
80
|
+
# BUNDLE_GEMFILE and the selected Bundler version for the target subprocess.
|
|
81
|
+
def bundler_env(run_env)
|
|
82
|
+
current_env = ENV.to_h
|
|
83
|
+
clean_env = if defined?(Bundler) && Bundler.respond_to?(:unbundled_env)
|
|
84
|
+
Bundler.unbundled_env.to_h
|
|
85
|
+
elsif defined?(Bundler) && Bundler.respond_to?(:original_env)
|
|
86
|
+
Bundler.original_env.to_h
|
|
87
|
+
else
|
|
88
|
+
current_env.to_h
|
|
111
89
|
end
|
|
112
|
-
end
|
|
113
90
|
|
|
114
|
-
|
|
115
|
-
|
|
91
|
+
# Avoid leaking a global BUNDLE_LOCKFILE into subprocesses.
|
|
92
|
+
clean_env["BUNDLE_LOCKFILE"] = nil
|
|
93
|
+
|
|
94
|
+
preserved_vars = PRESERVED_BUNDLE_VARS + PRESERVED_RUNTIME_VARS
|
|
95
|
+
|
|
96
|
+
preserved_vars.each do |var|
|
|
97
|
+
clean_env[var] = current_env[var] if current_env[var]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
clean_env["RUBYOPT"] = current_env["RUBYOPT"] if current_env["RUBYOPT"]
|
|
116
101
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
102
|
+
# Remove bundler/setup from RUBYOPT so subprocess doesn't auto-load bundler.
|
|
103
|
+
# Bundler.original_env can also contain RUBYOPT, so strip from the final
|
|
104
|
+
# command environment rather than only from the active process env.
|
|
105
|
+
if clean_env["RUBYOPT"]
|
|
106
|
+
rubyopt = clean_env["RUBYOPT"].split(" ")
|
|
107
|
+
rubyopt.reject! { |opt| opt == "-rbundler/setup" || opt.include?("bundler/setup") }
|
|
108
|
+
clean_env["RUBYOPT"] = rubyopt.join(" ")
|
|
109
|
+
clean_env["RUBYOPT"] = nil if clean_env["RUBYOPT"].empty?
|
|
120
110
|
end
|
|
121
|
-
|
|
111
|
+
|
|
112
|
+
# Remove Bundler activation markers from the subprocess environment.
|
|
113
|
+
# BUNDLE_BIN_PATH pins the already-active Bundler executable, so keeping
|
|
114
|
+
# it would bypass the BUNDLED WITH version selected below.
|
|
115
|
+
clean_env["BUNDLE_BIN_PATH"] = nil
|
|
116
|
+
clean_env["BUNDLE_VERSION"] = nil
|
|
117
|
+
clean_env["BUNDLER_SETUP"] = nil
|
|
118
|
+
clean_env["BUNDLER_VERSION"] = nil
|
|
119
|
+
|
|
122
120
|
run_env.each_pair do |key, value|
|
|
123
|
-
|
|
121
|
+
clean_env[key] = value
|
|
124
122
|
end
|
|
125
123
|
|
|
126
|
-
|
|
124
|
+
clean_env["RUBYLIB"] = nil
|
|
125
|
+
|
|
126
|
+
clean_env
|
|
127
127
|
end
|
|
128
128
|
|
|
129
|
-
def
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
def execute(base_env, run_env)
|
|
130
|
+
process_env = base_env.merge(run_env)
|
|
131
|
+
process_env["BUNDLE_GEMFILE"] = gemfile if gemfile
|
|
132
|
+
bundler_version = subprocess_bundler_version(process_env)
|
|
133
|
+
announce
|
|
134
|
+
command_text = command_as_string(:bundler_version => bundler_version)
|
|
135
|
+
if bundler_version
|
|
136
|
+
process_env["BUNDLE_VERSION"] = bundler_version
|
|
137
|
+
process_env["BUNDLER_VERSION"] = bundler_version
|
|
132
138
|
end
|
|
139
|
+
process_env["APPRAISAL_INITIALIZED"] = "1"
|
|
140
|
+
|
|
141
|
+
exit(1) unless Kernel.system(process_env, command_text)
|
|
133
142
|
end
|
|
134
143
|
|
|
135
|
-
def ensure_bundler_is_available
|
|
136
|
-
|
|
137
|
-
|
|
144
|
+
def ensure_bundler_is_available(process_env)
|
|
145
|
+
rubygems_env = rubygems_command_env(process_env)
|
|
146
|
+
|
|
147
|
+
# First ask the actual Bundler executable whether it can boot in the
|
|
148
|
+
# environment Appraisal is about to use. In CI, especially on alternate
|
|
149
|
+
# Ruby engines, Bundler may be available through the active app bundle
|
|
150
|
+
# while a manually scrubbed RubyGems spec lookup cannot see it through the
|
|
151
|
+
# current GEM_HOME/GEM_PATH.
|
|
152
|
+
return if system(process_env, "bundle -v > /dev/null 2>&1")
|
|
153
|
+
|
|
154
|
+
# Check if any version of bundler is available through RubyGems.
|
|
155
|
+
return if system(rubygems_env, bundler_available_command)
|
|
138
156
|
|
|
139
157
|
puts ">> Bundler not found, attempting to install..."
|
|
140
158
|
# If that fails, try to install the latest stable version
|
|
141
|
-
return if system("gem install bundler")
|
|
159
|
+
return if system(rubygems_env, "ruby --disable=gems -S gem install bundler")
|
|
142
160
|
|
|
143
161
|
puts
|
|
144
162
|
puts <<-ERROR.rstrip
|
|
@@ -150,14 +168,16 @@ manually.
|
|
|
150
168
|
exit(1)
|
|
151
169
|
end
|
|
152
170
|
|
|
153
|
-
def ensure_locked_bundler_is_available
|
|
171
|
+
def ensure_locked_bundler_is_available(process_env)
|
|
154
172
|
locked_version = locked_bundler_version
|
|
155
173
|
return unless locked_version
|
|
156
174
|
|
|
157
|
-
|
|
175
|
+
rubygems_env = rubygems_command_env(process_env)
|
|
176
|
+
|
|
177
|
+
return if system(rubygems_env, bundler_available_command(locked_version))
|
|
158
178
|
|
|
159
179
|
puts ">> Bundler #{locked_version} not found, attempting to install..."
|
|
160
|
-
return if system("gem install bundler -v #{Shellwords.escape(locked_version)} --no-document")
|
|
180
|
+
return if system(rubygems_env, "ruby --disable=gems -S gem install bundler -v #{Shellwords.escape(locked_version)} --no-document")
|
|
161
181
|
|
|
162
182
|
puts
|
|
163
183
|
puts <<-ERROR.rstrip
|
|
@@ -180,11 +200,42 @@ manually.
|
|
|
180
200
|
match && match[1]
|
|
181
201
|
end
|
|
182
202
|
|
|
183
|
-
def
|
|
203
|
+
def subprocess_bundler_version(process_env)
|
|
204
|
+
# Production subprocesses pin Bundler only when the appraisal lockfile
|
|
205
|
+
# says to. Acceptance specs may provide APPRAISAL_TEST_BUNDLER_VERSION so
|
|
206
|
+
# the nested subprocesses use the same isolated Bundler selected by the
|
|
207
|
+
# harness, without changing production behavior.
|
|
208
|
+
locked_bundler_version || process_env["APPRAISAL_TEST_BUNDLER_VERSION"]
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def rubygems_command_env(process_env)
|
|
212
|
+
process_env.dup.tap do |env|
|
|
213
|
+
env["BUNDLE_APP_CONFIG"] = nil
|
|
214
|
+
env["BUNDLE_BIN_PATH"] = nil
|
|
215
|
+
env["BUNDLE_GEMFILE"] = nil
|
|
216
|
+
env["BUNDLE_LOCKFILE"] = nil
|
|
217
|
+
env["BUNDLE_VERSION"] = nil
|
|
218
|
+
env["BUNDLER_SETUP"] = nil
|
|
219
|
+
env["BUNDLER_VERSION"] = nil
|
|
220
|
+
env["RUBYOPT"] = nil
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def bundler_available_command(version = nil)
|
|
225
|
+
requirement = version ? ", Gem::Requirement.new(#{version.inspect})" : ""
|
|
226
|
+
code = <<-RUBY.chomp
|
|
227
|
+
require "rubygems"
|
|
228
|
+
specs = Gem::Specification.find_all_by_name("bundler"#{requirement})
|
|
229
|
+
exit(specs.empty? ? 1 : 0)
|
|
230
|
+
RUBY
|
|
231
|
+
"ruby --disable=gems -e #{Shellwords.escape(code)}"
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def announce(command_text = command_as_string)
|
|
184
235
|
if gemfile
|
|
185
|
-
puts ">> BUNDLE_GEMFILE=#{gemfile} #{
|
|
236
|
+
puts ">> BUNDLE_GEMFILE=#{gemfile} #{command_text}"
|
|
186
237
|
else
|
|
187
|
-
puts ">> #{
|
|
238
|
+
puts ">> #{command_text}"
|
|
188
239
|
end
|
|
189
240
|
end
|
|
190
241
|
|
|
@@ -206,12 +257,20 @@ manually.
|
|
|
206
257
|
end
|
|
207
258
|
end
|
|
208
259
|
|
|
209
|
-
def command_as_string
|
|
210
|
-
if command.is_a?(Array)
|
|
260
|
+
def command_as_string(bundler_version: nil)
|
|
261
|
+
command_text = if command.is_a?(Array)
|
|
211
262
|
Shellwords.join(command)
|
|
212
263
|
else
|
|
213
264
|
command
|
|
214
265
|
end
|
|
266
|
+
return command_text unless bundler_version
|
|
267
|
+
|
|
268
|
+
command_text.gsub(/\bbundle(?=\s)/) { versioned_bundler_command(bundler_version) }
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def versioned_bundler_command(version)
|
|
272
|
+
script = %(gem "bundler", #{version.inspect}; load Gem.bin_path("bundler", "bundle"))
|
|
273
|
+
Shellwords.join([RbConfig.ruby, "-e", script])
|
|
215
274
|
end
|
|
216
275
|
|
|
217
276
|
def test_environment
|
|
@@ -219,7 +278,7 @@ manually.
|
|
|
219
278
|
|
|
220
279
|
{
|
|
221
280
|
"GEM_HOME" => ENV["GEM_HOME"],
|
|
222
|
-
"GEM_PATH" => ""
|
|
281
|
+
"GEM_PATH" => ENV["APPRAISAL_TEST_SYSTEM_GEM_PATH"].to_s
|
|
223
282
|
}
|
|
224
283
|
end
|
|
225
284
|
end
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require "shellwords"
|
|
4
4
|
|
|
5
|
+
require "appraisal/utils"
|
|
6
|
+
|
|
5
7
|
require_relative "base"
|
|
6
8
|
|
|
7
9
|
module Appraisal
|
|
@@ -53,7 +55,16 @@ module Appraisal
|
|
|
53
55
|
end
|
|
54
56
|
|
|
55
57
|
def update_command(gems)
|
|
56
|
-
|
|
58
|
+
gems = Array(gems).compact
|
|
59
|
+
return full_update_command if gems.empty?
|
|
60
|
+
|
|
61
|
+
["bundle", "update", *gems]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def full_update_command
|
|
65
|
+
return ["bundle", "update", "--all"] if Utils.support_bundle_update_all?
|
|
66
|
+
|
|
67
|
+
["bundle", "update"]
|
|
57
68
|
end
|
|
58
69
|
|
|
59
70
|
def bundle_options(options)
|
|
@@ -85,12 +96,14 @@ module Appraisal
|
|
|
85
96
|
end
|
|
86
97
|
|
|
87
98
|
def install_environment(options)
|
|
88
|
-
env = {}
|
|
99
|
+
env = {"BUNDLE_JOBS" => DEFAULT_INSTALL_OPTIONS.fetch("jobs").to_s}
|
|
89
100
|
|
|
90
101
|
if options["path"].nil? && Bundler.settings[:path]
|
|
91
102
|
env["BUNDLE_DISABLE_SHARED_GEMS"] = "1"
|
|
92
103
|
end
|
|
93
104
|
|
|
105
|
+
env["BUNDLE_JOBS"] = options["jobs"].to_s if options["jobs"].to_i > 1 && Utils.support_parallel_installation?
|
|
106
|
+
|
|
94
107
|
env
|
|
95
108
|
end
|
|
96
109
|
end
|
data/lib/appraisal/utils.rb
CHANGED
|
@@ -12,6 +12,10 @@ module Appraisal
|
|
|
12
12
|
Gem::Version.create(Bundler::VERSION) > Gem::Version.create("2.4.22")
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
def support_bundle_update_all?
|
|
16
|
+
Gem::Version.create(Bundler::VERSION) >= Gem::Version.create("1.16.5")
|
|
17
|
+
end
|
|
18
|
+
|
|
15
19
|
# Appraisal needs to print Gemfiles in the oldest Ruby syntax that is supported by Appraisal.
|
|
16
20
|
# Otherwise, a project would not be able to use Appraisal to test compatibility
|
|
17
21
|
# with older versions of Ruby, which is a core use case for Appraisal.
|
data/lib/appraisal/version.rb
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Appraisal
|
|
4
|
+
# Version namespace for this gem.
|
|
4
5
|
module Version
|
|
5
|
-
|
|
6
|
+
# Current gem version.
|
|
7
|
+
VERSION = "3.2.1"
|
|
6
8
|
end
|
|
7
|
-
|
|
9
|
+
# Current gem version exposed at the traditional constant location.
|
|
10
|
+
VERSION = Version::VERSION # Traditional Constant Location
|
|
8
11
|
end
|
data/lib/appraisal.rb
CHANGED
data/lib/appraisal2.rb
CHANGED
data.tar.gz.sig
CHANGED
|
Binary file
|