appraisal2 3.1.3 → 3.2.0

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.
@@ -8,7 +8,9 @@ module Appraisal
8
8
  attr_reader :command, :env, :gemfile
9
9
 
10
10
  # BUNDLE_* environment variables that must be preserved for proper bundler operation
11
- # and test isolation. These are preserved when using with_bundler_env to ensure:
11
+ # and test isolation. These are preserved after starting from Bundler's
12
+ # unbundled environment so Appraisal can run a fresh Bundler subprocess
13
+ # without losing the target appraisal and isolation settings:
12
14
  # - Bundler version switching works (BUNDLE_GEMFILE)
13
15
  # - Test isolation is maintained (BUNDLE_APP_CONFIG, etc.)
14
16
  # - User settings are respected (BUNDLE_PATH, BUNDLE_USER_CACHE, etc.)
@@ -30,6 +32,7 @@ module Appraisal
30
32
 
31
33
  PRESERVED_RUNTIME_VARS = [
32
34
  "PATH",
35
+ "GEM_HOME",
33
36
  "GEM_PATH"
34
37
  ].freeze
35
38
 
@@ -44,101 +47,112 @@ module Appraisal
44
47
  run_env = test_environment.merge(env)
45
48
 
46
49
  if @skip_bundle_exec
47
- execute(run_env)
50
+ execute(ENV.to_h, run_env)
48
51
  else
49
- # For bundler version switching to work reliably, we need to preserve
50
- # the appraisal Gemfile while avoiding the active parent Bundler process.
51
- # However, we still need to isolate from the parent's bundler state
52
- # to avoid conflicts.
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
52
+ clean_env = bundler_env(run_env)
53
+ ensure_bundler_is_available(clean_env)
54
+ ensure_locked_bundler_is_available(clean_env)
55
+ execute(clean_env, run_env)
62
56
  end
63
57
  end
64
58
 
65
59
  private
66
60
 
67
- # Provide a clean environment while preserving Bundler's version switching
68
- # inputs and test isolation settings.
61
+ # Build an Appraisal subprocess environment from Bundler's explicit env APIs.
69
62
  #
70
- # The current Ruby process has bundler activated, which adds bundler/setup to RUBYOPT.
71
- # When we run a subprocess, we need to remove that activation so the subprocess bundler
72
- # can start fresh. However, we keep all test isolation variables intact.
73
- def with_bundler_env
74
- backup_env = ENV.to_h
75
-
76
- begin
77
- clean_env = if Bundler.respond_to?(:original_env)
78
- Bundler.original_env.to_h
79
- else
80
- backup_env.to_h
81
- end
82
-
83
- # Avoid leaking a global BUNDLE_LOCKFILE into subprocesses.
84
- clean_env.delete("BUNDLE_LOCKFILE")
85
-
86
- preserved_vars = PRESERVED_BUNDLE_VARS + PRESERVED_RUNTIME_VARS
87
-
88
- preserved_vars.each do |var|
89
- clean_env[var] = backup_env[var] if backup_env[var]
90
- end
91
-
92
- # Remove bundler/setup from RUBYOPT so subprocess doesn't auto-load bundler
93
- if backup_env["RUBYOPT"]
94
- rubyopt = backup_env["RUBYOPT"].split(" ")
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)
63
+ # Bundler.clean_env / Bundler.with_clean_env are removed. Bundler now exposes
64
+ # three related but distinct choices:
65
+ #
66
+ # - Bundler.original_env: the environment before the current Bundler activated.
67
+ # This is too broad for Appraisal because unrelated original BUNDLE_* values
68
+ # can leak into the appraisal subprocess.
69
+ # - Bundler.unbundled_env: original_env with Bundler activation removed. This
70
+ # is the right base because it removes parent Bundler state, RUBYOPT
71
+ # bundler/setup activation, and arbitrary BUNDLE_* values.
72
+ # - Bundler.with_unbundled_env: the block form of unbundled_env. Appraisal
73
+ # needs a Hash to pass to Kernel.system and must re-add selected variables,
74
+ # so the block helper is the wrong shape even though its semantics are close.
75
+ #
76
+ # Starting from unbundled_env would normally remove BUNDLE_GEMFILE,
77
+ # BUNDLE_APP_CONFIG, and related isolation knobs. Appraisal intentionally
78
+ # reintroduces only the variables it owns or must preserve, then sets
79
+ # BUNDLE_GEMFILE and the selected Bundler version for the target subprocess.
80
+ def bundler_env(run_env)
81
+ current_env = ENV.to_h
82
+ clean_env = if defined?(Bundler) && Bundler.respond_to?(:unbundled_env)
83
+ Bundler.unbundled_env.to_h
84
+ elsif defined?(Bundler) && Bundler.respond_to?(:original_env)
85
+ Bundler.original_env.to_h
86
+ else
87
+ current_env.to_h
111
88
  end
112
- end
113
89
 
114
- def execute(run_env)
115
- announce
90
+ # Avoid leaking a global BUNDLE_LOCKFILE into subprocesses.
91
+ clean_env["BUNDLE_LOCKFILE"] = nil
92
+
93
+ preserved_vars = PRESERVED_BUNDLE_VARS + PRESERVED_RUNTIME_VARS
116
94
 
117
- ENV["BUNDLE_GEMFILE"] = gemfile
118
- if (bundler_version = locked_bundler_version)
119
- ENV["BUNDLER_VERSION"] = bundler_version
95
+ preserved_vars.each do |var|
96
+ clean_env[var] = current_env[var] if current_env[var]
120
97
  end
121
- ENV["APPRAISAL_INITIALIZED"] = "1"
98
+
99
+ clean_env["RUBYOPT"] = current_env["RUBYOPT"] if current_env["RUBYOPT"]
100
+
101
+ # Remove bundler/setup from RUBYOPT so subprocess doesn't auto-load bundler.
102
+ # Bundler.original_env can also contain RUBYOPT, so strip from the final
103
+ # command environment rather than only from the active process env.
104
+ if clean_env["RUBYOPT"]
105
+ rubyopt = clean_env["RUBYOPT"].split(" ")
106
+ rubyopt.reject! { |opt| opt == "-rbundler/setup" || opt.include?("bundler/setup") }
107
+ clean_env["RUBYOPT"] = rubyopt.join(" ")
108
+ clean_env["RUBYOPT"] = nil if clean_env["RUBYOPT"].empty?
109
+ end
110
+
111
+ # Remove Bundler activation markers from the subprocess environment.
112
+ # BUNDLE_BIN_PATH pins the already-active Bundler executable, so keeping
113
+ # it would bypass the BUNDLED WITH version selected below.
114
+ clean_env["BUNDLE_BIN_PATH"] = nil
115
+ clean_env["BUNDLE_VERSION"] = nil
116
+ clean_env["BUNDLER_SETUP"] = nil
117
+ clean_env["BUNDLER_VERSION"] = nil
118
+
122
119
  run_env.each_pair do |key, value|
123
- ENV[key] = value
120
+ clean_env[key] = value
124
121
  end
125
122
 
126
- exit(1) unless Kernel.system(command_as_string)
123
+ clean_env
127
124
  end
128
125
 
129
- def apply_run_env(run_env)
130
- run_env.each_pair do |key, value|
131
- ENV[key] = value
126
+ def execute(base_env, run_env)
127
+ announce
128
+
129
+ process_env = base_env.merge(run_env)
130
+ process_env["BUNDLE_GEMFILE"] = gemfile if gemfile
131
+ if (bundler_version = subprocess_bundler_version(process_env))
132
+ process_env["BUNDLE_VERSION"] = bundler_version
133
+ process_env["BUNDLER_VERSION"] = bundler_version
132
134
  end
135
+ process_env["APPRAISAL_INITIALIZED"] = "1"
136
+
137
+ exit(1) unless Kernel.system(process_env, command_as_string)
133
138
  end
134
139
 
135
- def ensure_bundler_is_available
136
- # Check if any version of bundler is available
137
- return if system(%(gem list --silent -i bundler))
140
+ def ensure_bundler_is_available(process_env)
141
+ rubygems_env = rubygems_command_env(process_env)
142
+
143
+ # First ask the actual Bundler executable whether it can boot in the
144
+ # environment Appraisal is about to use. In CI, especially on alternate
145
+ # Ruby engines, Bundler may be available through the active app bundle
146
+ # while a manually scrubbed RubyGems spec lookup cannot see it through the
147
+ # current GEM_HOME/GEM_PATH.
148
+ return if system(process_env, "bundle -v > /dev/null 2>&1")
149
+
150
+ # Check if any version of bundler is available through RubyGems.
151
+ return if system(rubygems_env, bundler_available_command)
138
152
 
139
153
  puts ">> Bundler not found, attempting to install..."
140
154
  # If that fails, try to install the latest stable version
141
- return if system("gem install bundler")
155
+ return if system(rubygems_env, "ruby --disable=gems -S gem install bundler")
142
156
 
143
157
  puts
144
158
  puts <<-ERROR.rstrip
@@ -150,14 +164,16 @@ manually.
150
164
  exit(1)
151
165
  end
152
166
 
153
- def ensure_locked_bundler_is_available
167
+ def ensure_locked_bundler_is_available(process_env)
154
168
  locked_version = locked_bundler_version
155
169
  return unless locked_version
156
170
 
157
- return if system(%(gem list --silent -i bundler -v "#{locked_version}"))
171
+ rubygems_env = rubygems_command_env(process_env)
172
+
173
+ return if system(rubygems_env, bundler_available_command(locked_version))
158
174
 
159
175
  puts ">> Bundler #{locked_version} not found, attempting to install..."
160
- return if system("gem install bundler -v #{Shellwords.escape(locked_version)} --no-document")
176
+ return if system(rubygems_env, "ruby --disable=gems -S gem install bundler -v #{Shellwords.escape(locked_version)} --no-document")
161
177
 
162
178
  puts
163
179
  puts <<-ERROR.rstrip
@@ -180,6 +196,37 @@ manually.
180
196
  match && match[1]
181
197
  end
182
198
 
199
+ def subprocess_bundler_version(process_env)
200
+ # Production subprocesses pin Bundler only when the appraisal lockfile
201
+ # says to. Acceptance specs may provide APPRAISAL_TEST_BUNDLER_VERSION so
202
+ # the nested subprocesses use the same isolated Bundler selected by the
203
+ # harness, without changing production behavior.
204
+ locked_bundler_version || process_env["APPRAISAL_TEST_BUNDLER_VERSION"]
205
+ end
206
+
207
+ def rubygems_command_env(process_env)
208
+ process_env.dup.tap do |env|
209
+ env["BUNDLE_APP_CONFIG"] = nil
210
+ env["BUNDLE_BIN_PATH"] = nil
211
+ env["BUNDLE_GEMFILE"] = nil
212
+ env["BUNDLE_LOCKFILE"] = nil
213
+ env["BUNDLE_VERSION"] = nil
214
+ env["BUNDLER_SETUP"] = nil
215
+ env["BUNDLER_VERSION"] = nil
216
+ env["RUBYOPT"] = nil
217
+ end
218
+ end
219
+
220
+ def bundler_available_command(version = nil)
221
+ requirement = version ? ", Gem::Requirement.new(#{version.inspect})" : ""
222
+ code = <<-RUBY.chomp
223
+ require "rubygems"
224
+ specs = Gem::Specification.find_all_by_name("bundler"#{requirement})
225
+ exit(specs.empty? ? 1 : 0)
226
+ RUBY
227
+ "ruby --disable=gems -e #{Shellwords.escape(code)}"
228
+ end
229
+
183
230
  def announce
184
231
  if gemfile
185
232
  puts ">> BUNDLE_GEMFILE=#{gemfile} #{command_as_string}"
@@ -219,7 +266,7 @@ manually.
219
266
 
220
267
  {
221
268
  "GEM_HOME" => ENV["GEM_HOME"],
222
- "GEM_PATH" => ""
269
+ "GEM_PATH" => ENV["APPRAISAL_TEST_SYSTEM_GEM_PATH"].to_s
223
270
  }
224
271
  end
225
272
  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
- ["bundle", "update", *gems].compact
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
@@ -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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Appraisal
4
4
  module Version
5
- VERSION = "3.1.2"
5
+ VERSION = "3.1.4"
6
6
  end
7
7
  VERSION = Version::VERSION # Traditional constant location
8
8
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Appraisal2
4
4
  module Version
5
- VERSION = "3.1.3"
5
+ VERSION = "3.2.0"
6
6
  end
7
7
  VERSION = Version::VERSION # Traditional Constant Location
8
8
  end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appraisal2
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.3
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Ferris
@@ -121,20 +121,20 @@ dependencies:
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: '2.2'
124
+ version: '2.3'
125
125
  - - ">="
126
126
  - !ruby/object:Gem::Version
127
- version: 2.2.15
127
+ version: 2.3.6
128
128
  type: :development
129
129
  prerelease: false
130
130
  version_requirements: !ruby/object:Gem::Requirement
131
131
  requirements:
132
132
  - - "~>"
133
133
  - !ruby/object:Gem::Version
134
- version: '2.2'
134
+ version: '2.3'
135
135
  - - ">="
136
136
  - !ruby/object:Gem::Version
137
- version: 2.2.15
137
+ version: 2.3.6
138
138
  - !ruby/object:Gem::Dependency
139
139
  name: bundler-audit
140
140
  requirement: !ruby/object:Gem::Requirement
@@ -178,7 +178,7 @@ dependencies:
178
178
  version: '2.0'
179
179
  - - ">="
180
180
  - !ruby/object:Gem::Version
181
- version: 2.0.6
181
+ version: 2.0.11
182
182
  type: :development
183
183
  prerelease: false
184
184
  version_requirements: !ruby/object:Gem::Requirement
@@ -188,7 +188,7 @@ dependencies:
188
188
  version: '2.0'
189
189
  - - ">="
190
190
  - !ruby/object:Gem::Version
191
- version: 2.0.6
191
+ version: 2.0.11
192
192
  - !ruby/object:Gem::Dependency
193
193
  name: turbo_tests2
194
194
  requirement: !ruby/object:Gem::Requirement
@@ -198,7 +198,7 @@ dependencies:
198
198
  version: '3.1'
199
199
  - - ">="
200
200
  - !ruby/object:Gem::Version
201
- version: 3.1.4
201
+ version: 3.1.14
202
202
  type: :development
203
203
  prerelease: false
204
204
  version_requirements: !ruby/object:Gem::Requirement
@@ -208,7 +208,7 @@ dependencies:
208
208
  version: '3.1'
209
209
  - - ">="
210
210
  - !ruby/object:Gem::Version
211
- version: 3.1.4
211
+ version: 3.1.14
212
212
  - !ruby/object:Gem::Dependency
213
213
  name: ruby-progressbar
214
214
  requirement: !ruby/object:Gem::Requirement
@@ -232,7 +232,7 @@ dependencies:
232
232
  version: '1.0'
233
233
  - - ">="
234
234
  - !ruby/object:Gem::Version
235
- version: 1.0.3
235
+ version: 1.0.6
236
236
  type: :development
237
237
  prerelease: false
238
238
  version_requirements: !ruby/object:Gem::Requirement
@@ -242,7 +242,7 @@ dependencies:
242
242
  version: '1.0'
243
243
  - - ">="
244
244
  - !ruby/object:Gem::Version
245
- version: 1.0.3
245
+ version: 1.0.6
246
246
  - !ruby/object:Gem::Dependency
247
247
  name: gitmoji-regex
248
248
  requirement: !ruby/object:Gem::Requirement
@@ -252,7 +252,7 @@ dependencies:
252
252
  version: '2.0'
253
253
  - - ">="
254
254
  - !ruby/object:Gem::Version
255
- version: 2.0.3
255
+ version: 2.0.4
256
256
  type: :development
257
257
  prerelease: false
258
258
  version_requirements: !ruby/object:Gem::Requirement
@@ -262,7 +262,7 @@ dependencies:
262
262
  version: '2.0'
263
263
  - - ">="
264
264
  - !ruby/object:Gem::Version
265
- version: 2.0.3
265
+ version: 2.0.4
266
266
  - !ruby/object:Gem::Dependency
267
267
  name: activesupport
268
268
  requirement: !ruby/object:Gem::Requirement
@@ -354,7 +354,7 @@ dependencies:
354
354
  version: '1.0'
355
355
  - - ">="
356
356
  - !ruby/object:Gem::Version
357
- version: 1.0.4
357
+ version: 1.0.6
358
358
  type: :development
359
359
  prerelease: false
360
360
  version_requirements: !ruby/object:Gem::Requirement
@@ -364,7 +364,7 @@ dependencies:
364
364
  version: '1.0'
365
365
  - - ">="
366
366
  - !ruby/object:Gem::Version
367
- version: 1.0.4
367
+ version: 1.0.6
368
368
  - !ruby/object:Gem::Dependency
369
369
  name: silent_stream
370
370
  requirement: !ruby/object:Gem::Requirement
@@ -393,27 +393,11 @@ email:
393
393
  executables:
394
394
  - appraisal
395
395
  extensions: []
396
- extra_rdoc_files:
397
- - CHANGELOG.md
398
- - CITATION.cff
399
- - CODE_OF_CONDUCT.md
400
- - CONTRIBUTING.md
401
- - FUNDING.md
402
- - LICENSE.md
403
- - README.md
404
- - RUBOCOP.md
405
- - SECURITY.md
396
+ extra_rdoc_files: []
406
397
  files:
407
398
  - CHANGELOG.md
408
- - CITATION.cff
409
- - CODE_OF_CONDUCT.md
410
- - CONTRIBUTING.md
411
- - FUNDING.md
412
399
  - LICENSE.md
413
400
  - README.md
414
- - RUBOCOP.md
415
- - SECURITY.md
416
- - certs/pboling.pem
417
401
  - exe/appraisal
418
402
  - lib/appraisal.rb
419
403
  - lib/appraisal/appraisal.rb
@@ -445,16 +429,16 @@ files:
445
429
  - lib/appraisal/version.rb
446
430
  - lib/appraisal2.rb
447
431
  - lib/appraisal2/version.rb
448
- - sig/appraisal2/version.rbs
432
+ - sig/appraisal2.rbs
449
433
  homepage: https://github.com/appraisal-rb/appraisal2
450
434
  licenses:
451
435
  - MIT
452
436
  metadata:
453
437
  homepage_uri: https://appraisal2.galtzo.com
454
- source_code_uri: https://github.com/appraisal-rb/appraisal2/tree/v3.1.3
455
- changelog_uri: https://github.com/appraisal-rb/appraisal2/blob/v3.1.3/CHANGELOG.md
438
+ source_code_uri: https://github.com/appraisal-rb/appraisal2/tree/v3.2.0
439
+ changelog_uri: https://github.com/appraisal-rb/appraisal2/blob/v3.2.0/CHANGELOG.md
456
440
  bug_tracker_uri: https://github.com/appraisal-rb/appraisal2/issues
457
- documentation_uri: https://www.rubydoc.info/gems/appraisal2/3.1.3
441
+ documentation_uri: https://www.rubydoc.info/gems/appraisal2/3.2.0
458
442
  funding_uri: https://github.com/sponsors/pboling
459
443
  wiki_uri: https://github.com/appraisal-rb/appraisal2/wiki
460
444
  news_uri: https://www.railsbling.com/tags/appraisal2
@@ -483,7 +467,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
483
467
  - !ruby/object:Gem::Version
484
468
  version: '0'
485
469
  requirements: []
486
- rubygems_version: 4.0.10
470
+ rubygems_version: 4.0.16
487
471
  specification_version: 4
488
472
  summary: "\U0001F50D️ Find out what your Ruby gems are worth"
489
473
  test_files: []
metadata.gz.sig CHANGED
Binary file
data/CITATION.cff DELETED
@@ -1,20 +0,0 @@
1
- cff-version: 1.2.0
2
- title: "appraisal2"
3
- message: >-
4
- If you use this work and you want to cite it,
5
- then you can use the metadata from this file.
6
- type: software
7
- authors:
8
- - given-names: "Peter H."
9
- family-names: "Boling"
10
- email: "floss@galtzo.com"
11
- affiliation: "galtzo.com"
12
- orcid: 'https://orcid.org/0009-0008-8519-441X'
13
- identifiers:
14
- - type: url
15
- value: 'https://github.com/appraisal-rb/appraisal2'
16
- description: "appraisal2"
17
- repository-code: 'https://github.com/appraisal-rb/appraisal2'
18
- abstract: >-
19
- appraisal2
20
- license: See license file