bundler-multilock 1.0.10 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f71e197447bdcd02b427ac7bc3a21a7e41e20a3d67edda69ed3821dea293e3ac
4
- data.tar.gz: bf11ee91ace5dff77af3bc2b56ec283d2d227513e8d9397ce00e199ab73e3d83
3
+ metadata.gz: 12e431ea7783f9bc36d1efae8aaeda88434936f587c954c34ad4163b3338dc44
4
+ data.tar.gz: 8eda2e0bc8f785b0ceefb312d4d34c6fd754dab58c835ec7b818c551af903bdf
5
5
  SHA512:
6
- metadata.gz: 4cad381cf2fbb7d441f98d39322d54a0496570ceb3e564669a39429072224b7cbbca1af2f68205bc1149e05dc7535ca75e3f51631795732c019354d92ce17952
7
- data.tar.gz: 3ddb854fc272683a96711403a8f65fc4d588260c46e7753f71ca790192be19f8f9e7b9b7612457d99ea4596ab6830b8cf8eda5c0385621f0769949f41cb95c87
6
+ metadata.gz: 3fea018e198880d1c2a7dcaa635faa1ccff9b65d46bbb72c15cb153dd20acf34dfb833189bd1c4f3dd04f3053b23e2220ca769b101806e2b99a3ddc92097675c
7
+ data.tar.gz: 80095ec510ebefa11c457ef13c45bc0af40b6961b1ffff54c0724f5010cd72b6fa512f3de11155a6883db71dd6dc87322c6fbef314d5b7c61e99572e4fe77b43
@@ -5,6 +5,8 @@ require "set"
5
5
  module Bundler
6
6
  module Multilock
7
7
  class Check
8
+ attr_reader :lockfiles, :lockfile_contents, :lockfile_specs
9
+
8
10
  class << self
9
11
  def run
10
12
  new.run
@@ -12,23 +14,32 @@ module Bundler
12
14
  end
13
15
 
14
16
  def initialize
15
- default_lockfile_contents = Bundler.default_lockfile.read
16
- @default_lockfile = LockfileParser.new(default_lockfile_contents)
17
- @default_specs = @default_lockfile.specs.to_h do |spec|
17
+ @lockfiles = {}
18
+ @lockfile_contents = {}
19
+ @lockfile_specs = {}
20
+ end
21
+
22
+ def load_lockfile(lockfile)
23
+ return if lockfile_contents.key?(lockfile)
24
+
25
+ contents = lockfile_contents[lockfile] = lockfile.read.freeze
26
+ parser = lockfiles[lockfile] = LockfileParser.new(contents)
27
+ lockfile_specs[lockfile] = parser.specs.to_h do |spec|
18
28
  [[spec.name, spec.platform], spec]
19
29
  end
20
30
  end
21
31
 
22
32
  def run(skip_base_checks: false)
23
- return true unless Bundler.default_lockfile.exist?
33
+ return true unless Bundler.default_lockfile(force_original: true).exist?
24
34
 
25
35
  success = true
26
36
  unless skip_base_checks
27
- missing_specs = base_check({ gemfile: Bundler.default_gemfile, lockfile: Bundler.default_lockfile },
37
+ missing_specs = base_check({ gemfile: Bundler.default_gemfile,
38
+ lockfile: Bundler.default_lockfile(force_original: true) },
28
39
  return_missing: true).to_set
29
40
  end
30
41
  Multilock.lockfile_definitions.each do |lockfile_definition|
31
- next if lockfile_definition[:lockfile] == Bundler.default_lockfile
42
+ next if lockfile_definition[:lockfile] == Bundler.default_lockfile(force_original: true)
32
43
 
33
44
  unless lockfile_definition[:lockfile].exist?
34
45
  Bundler.ui.error("Lockfile #{lockfile_definition[:lockfile]} does not exist.")
@@ -77,7 +88,7 @@ module Bundler
77
88
  Multilock.prepare_block = nil
78
89
  end
79
90
 
80
- # this checks for mismatches between the default lockfile and the given lockfile,
91
+ # this checks for mismatches between the parent lockfile and the given lockfile,
81
92
  # and for pinned dependencies in lockfiles requiring them
82
93
  def check(lockfile_definition, allow_mismatched_dependencies: true)
83
94
  success = true
@@ -85,13 +96,16 @@ module Bundler
85
96
  needs_pin_check = []
86
97
  lockfile = LockfileParser.new(lockfile_definition[:lockfile].read)
87
98
  lockfile_path = lockfile_definition[:lockfile].relative_path_from(Dir.pwd)
88
- unless lockfile.platforms == @default_lockfile.platforms
89
- Bundler.ui.error("The platforms in #{lockfile_path} do not match the default lockfile.")
99
+ parent = lockfile_definition[:parent]
100
+ load_lockfile(parent)
101
+ parent_lockfile = lockfiles[parent]
102
+ unless lockfile.platforms == parent_lockfile.platforms
103
+ Bundler.ui.error("The platforms in #{lockfile_path} do not match the parent lockfile.")
90
104
  success = false
91
105
  end
92
- unless lockfile.bundler_version == @default_lockfile.bundler_version
106
+ unless lockfile.bundler_version == parent_lockfile.bundler_version
93
107
  Bundler.ui.error("bundler (#{lockfile.bundler_version}) in #{lockfile_path} " \
94
- "does not match the default lockfile's version (@#{@default_lockfile.bundler_version}).")
108
+ "does not match the parent lockfile's version (@#{parent_lockfile.bundler_version}).")
95
109
  success = false
96
110
  end
97
111
 
@@ -100,13 +114,13 @@ module Bundler
100
114
  allow_mismatched_dependencies = lockfile_definition[:allow_mismatched_dependencies]
101
115
  end
102
116
 
103
- # build list of top-level dependencies that differ from the default lockfile,
117
+ # build list of top-level dependencies that differ from the parent lockfile,
104
118
  # and all _their_ transitive dependencies
105
119
  if allow_mismatched_dependencies
106
120
  transitive_dependencies = Set.new
107
- # only dependencies that differ from the default lockfile
121
+ # only dependencies that differ from the parent lockfile
108
122
  pending_transitive_dependencies = lockfile.dependencies.reject do |name, dep|
109
- @default_lockfile.dependencies[name] == dep
123
+ parent_lockfile.dependencies[name] == dep
110
124
  end.map(&:first)
111
125
 
112
126
  until pending_transitive_dependencies.empty?
@@ -133,40 +147,40 @@ module Bundler
133
147
 
134
148
  # check for conflicting requirements (and build list of pins, in the same loop)
135
149
  specs.values.flatten.each do |spec|
136
- default_spec = @default_specs[[spec.name, spec.platform]]
150
+ parent_spec = lockfile_specs[parent][[spec.name, spec.platform]]
137
151
 
138
152
  if lockfile_definition[:enforce_pinned_additional_dependencies]
139
153
  # look through what this spec depends on, and keep track of all pinned requirements
140
154
  find_pinned_dependencies(proven_pinned, spec.dependencies)
141
155
 
142
- needs_pin_check << spec unless default_spec
156
+ needs_pin_check << spec unless parent_spec
143
157
  end
144
158
 
145
- next unless default_spec
159
+ next unless parent_spec
146
160
 
147
161
  # have to ensure Path sources are relative to their lockfile before comparing
148
- same_source = if [default_spec.source, spec.source].grep(Source::Path).length == 2
162
+ same_source = if [parent_spec.source, spec.source].grep(Source::Path).length == 2
149
163
  lockfile_definition[:lockfile]
150
164
  .dirname
151
165
  .join(spec.source.path)
152
166
  .ascend
153
- .any?(Bundler.default_lockfile.dirname.join(default_spec.source.path))
167
+ .any?(parent.dirname.join(parent_spec.source.path))
154
168
  else
155
- default_spec.source == spec.source
169
+ parent_spec.source == spec.source
156
170
  end
157
171
 
158
- next if default_spec.version == spec.version && same_source
172
+ next if parent_spec.version == spec.version && same_source
159
173
  next if allow_mismatched_dependencies && transitive_dependencies.include?(spec.name)
160
174
 
161
175
  Bundler.ui.error("#{spec}#{spec.git_version} in #{lockfile_path} " \
162
- "does not match the default lockfile's version " \
163
- "(@#{default_spec.version}#{default_spec.git_version}); " \
176
+ "does not match the parent lockfile's version " \
177
+ "(@#{parent_spec.version}#{parent_spec.git_version}); " \
164
178
  "this may be due to a conflicting requirement, which would require manual resolution.")
165
179
  success = false
166
180
  end
167
181
 
168
182
  # now that we have built a list of every gem that is pinned, go through
169
- # the gems that were in this lockfile, but not the default lockfile, and
183
+ # the gems that were in this lockfile, but not the parent lockfile, and
170
184
  # ensure it's pinned _somehow_
171
185
  needs_pin_check.each do |spec|
172
186
  pinned = case spec.source
@@ -183,7 +197,7 @@ module Bundler
183
197
  next if pinned
184
198
 
185
199
  Bundler.ui.error("#{spec} in #{lockfile_path} has not been pinned to a specific version, " \
186
- "which is required since it is not part of the default lockfile.")
200
+ "which is required since it is not part of the parent lockfile.")
187
201
  success = false
188
202
  end
189
203
 
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler
4
+ module Multilock
5
+ module Ext
6
+ module SharedHeleprs
7
+ module ClassMethods
8
+ ::Bundler::SharedHelpers.singleton_class.prepend(self)
9
+ ::Bundler::SharedHelpers.instance_variable_set(:@filesystem_accesses, nil)
10
+
11
+ def capture_filesystem_access
12
+ @filesystem_accesses = []
13
+ yield
14
+ @filesystem_accesses
15
+ ensure
16
+ @filesystem_accesses = nil
17
+ end
18
+
19
+ def filesystem_access(path, action = :write)
20
+ @filesystem_accesses << [path, action] if @filesystem_accesses
21
+
22
+ super
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler
4
+ module Multilock
5
+ module Ext
6
+ module Source
7
+ ::Bundler::Source.prepend(self)
8
+
9
+ def print_using_message(...)
10
+ return if Bundler.settings[:suppress_install_using_messages]
11
+
12
+ super
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Bundler
4
4
  module Multilock
5
- VERSION = "1.0.10"
5
+ VERSION = "1.1.0"
6
6
  end
7
7
  end
@@ -5,6 +5,8 @@ require_relative "multilock/ext/definition"
5
5
  require_relative "multilock/ext/dsl"
6
6
  require_relative "multilock/ext/plugin"
7
7
  require_relative "multilock/ext/plugin/dsl"
8
+ require_relative "multilock/ext/shared_helpers"
9
+ require_relative "multilock/ext/source"
8
10
  require_relative "multilock/ext/source_list"
9
11
  require_relative "multilock/version"
10
12
 
@@ -20,8 +22,11 @@ module Bundler
20
22
  # @param builder [Dsl] The Bundler DSL
21
23
  # @param gemfile [String, nil]
22
24
  # The Gemfile for this lockfile (defaults to Gemfile)
23
- # @param default [Boolean]
25
+ # @param active [Boolean]
24
26
  # If this lockfile should be the default (instead of Gemfile.lock)
27
+ # BUNDLE_LOCKFILE will still override a lockfile tagged as active
28
+ # @param parent [String] The parent lockfile to sync dependencies from.
29
+ # Also used for comparing enforce_pinned_additional_dependencies against.
25
30
  # @param allow_mismatched_dependencies [true, false]
26
31
  # Allows version differences in dependencies between this lockfile and
27
32
  # the default lockfile. Note that even with this option, only top-level
@@ -32,34 +37,30 @@ module Bundler
32
37
  # default lockfile, enforce that they are pinned.
33
38
  # @yield
34
39
  # Block executed only when this lockfile is active.
35
- # @return [true, false] if the lockfile is the current lockfile
40
+ # @return [true, false] if the lockfile is the active lockfile
36
41
  def add_lockfile(lockfile = nil,
37
42
  builder:,
38
43
  gemfile: nil,
44
+ active: nil,
39
45
  default: nil,
46
+ parent: nil,
40
47
  allow_mismatched_dependencies: true,
41
48
  enforce_pinned_additional_dependencies: false,
42
49
  &block)
43
- # terminology gets confusing here. The "default" param means
44
- # "use this lockfile when not overridden by BUNDLE_LOCKFILE"
45
- # but Bundler.defaul_lockfile (usually) means "Gemfile.lock"
46
- # so refer to the former as "current" internally
47
- current = default
48
- current = true if current.nil? && lockfile_definitions.empty? && lockfile.nil? && gemfile.nil?
50
+ # backcompat
51
+ active = default if active.nil?
52
+ Bundler.ui.warn("lockfile(default:) is deprecated. Use lockfile(active:) instead.") if default
53
+
54
+ active = true if active.nil? && lockfile_definitions.empty? && lockfile.nil? && gemfile.nil?
49
55
 
50
- # allow short-form lockfile names
51
- if lockfile.is_a?(String) && !(lockfile.include?("/") || lockfile.end_with?(".lock"))
52
- lockfile = "Gemfile.#{lockfile}.lock"
53
- end
54
56
  # if a gemfile was provided, but not a lockfile, infer the default lockfile for that gemfile
55
57
  lockfile ||= "#{gemfile}.lock" if gemfile
56
- # use absolute paths
57
- lockfile = Bundler.root.join(lockfile).expand_path if lockfile
58
- # use the default lockfile (Gemfile.lock) if none was given
59
- lockfile ||= Bundler.default_lockfile(force_original: true)
60
- raise ArgumentError, "Lockfile #{lockfile} is already defined" if lockfile_definitions.any? do |definition|
61
- definition[:lockfile] == lockfile
62
- end
58
+ # allow short-form lockfile names
59
+ lockfile = expand_lockfile(lockfile)
60
+
61
+ if lockfile_definitions.find { |definition| definition[:lockfile] == lockfile }
62
+ raise ArgumentError, "Lockfile #{lockfile} is already defined"
63
+ end
63
64
 
64
65
  env_lockfile = ENV["BUNDLE_LOCKFILE"]
65
66
  if env_lockfile
@@ -67,18 +68,25 @@ module Bundler
67
68
  env_lockfile = "Gemfile.#{env_lockfile}.lock"
68
69
  end
69
70
  env_lockfile = Bundler.root.join(env_lockfile).expand_path
70
- current = env_lockfile == lockfile
71
+ active = env_lockfile == lockfile
72
+ end
73
+
74
+ if active && (old_active = lockfile_definitions.find { |definition| definition[:active] })
75
+ raise ArgumentError, "Only one lockfile (#{old_active[:lockfile]}) can be flagged as the default"
71
76
  end
72
77
 
73
- if current && (old_current = lockfile_definitions.find { |definition| definition[:current] })
74
- raise ArgumentError, "Only one lockfile (#{old_current[:lockfile]}) can be flagged as the default"
78
+ parent = expand_lockfile(parent)
79
+ if parent != Bundler.default_lockfile(force_original: true) &&
80
+ !lockfile_definitions.find { |definition| definition[:lockfile] == parent }
81
+ raise ArgumentError, "Parent lockfile #{parent} is not defined"
75
82
  end
76
83
 
77
84
  lockfile_definitions << (lockfile_def = {
78
85
  gemfile: (gemfile && Bundler.root.join(gemfile).expand_path) || Bundler.default_gemfile,
79
86
  lockfile: lockfile,
80
- current: current,
87
+ active: active,
81
88
  prepare: block,
89
+ parent: parent,
82
90
  allow_mismatched_dependencies: allow_mismatched_dependencies,
83
91
  enforce_pinned_additional_dependencies: enforce_pinned_additional_dependencies
84
92
  })
@@ -94,10 +102,10 @@ module Bundler
94
102
  # If they're using BUNDLE_LOCKFILE, then they really do want to
95
103
  # use a particular lockfile, and it overrides whatever they
96
104
  # dynamically set in their gemfile
97
- current = lockfile == Bundler.default_lockfile(force_original: true)
105
+ active = lockfile == Bundler.default_lockfile(force_original: true)
98
106
  end
99
107
 
100
- if current
108
+ if active
101
109
  block&.call
102
110
  Bundler.default_lockfile = lockfile
103
111
 
@@ -146,16 +154,13 @@ module Bundler
146
154
  require "tempfile"
147
155
  require_relative "multilock/lockfile_generator"
148
156
 
157
+ Bundler.ui.debug("Syncing to alternate lockfiles")
149
158
  Bundler.ui.info ""
150
159
 
151
- default_lockfile_contents = Bundler.default_lockfile.read.freeze
152
- default_specs = LockfileParser.new(default_lockfile_contents).specs.to_h do |spec|
153
- [[spec.name, spec.platform], spec]
154
- end
155
- default_root = Bundler.root
156
-
157
160
  attempts = 1
158
161
 
162
+ default_root = Bundler.root
163
+
159
164
  checker = Check.new
160
165
  synced_any = false
161
166
  Bundler.settings.temporary(cache_all_platforms: true, suppress_install_using_messages: true) do
@@ -196,21 +201,26 @@ module Bundler
196
201
  Bundler.ui.info("Syncing to #{relative_lockfile}...") if attempts == 1
197
202
  synced_any = true
198
203
 
199
- # adjust locked paths from the default lockfile to be relative to _this_ gemfile
200
- adjusted_default_lockfile_contents =
201
- default_lockfile_contents.gsub(/PATH\n remote: ([^\n]+)\n/) do |remote|
204
+ parent = lockfile_definition[:parent]
205
+ parent_root = parent.dirname
206
+ checker.load_lockfile(parent)
207
+ parent_specs = checker.lockfile_specs[parent]
208
+
209
+ # adjust locked paths from the parent lockfile to be relative to _this_ gemfile
210
+ adjusted_parent_lockfile_contents =
211
+ checker.lockfile_contents[parent].gsub(/PATH\n remote: ([^\n]+)\n/) do |remote|
202
212
  remote_path = Pathname.new($1)
203
213
  next remote if remote_path.absolute?
204
214
 
205
- relative_remote_path = remote_path.expand_path(default_root).relative_path_from(Bundler.root).to_s
215
+ relative_remote_path = remote_path.expand_path(parent_root).relative_path_from(Bundler.root).to_s
206
216
  remote.sub($1, relative_remote_path)
207
217
  end
208
218
 
209
219
  # add a source for the current gem
210
- gem_spec = default_specs[[File.basename(Bundler.root), "ruby"]]
220
+ gem_spec = parent_specs[[File.basename(Bundler.root), "ruby"]]
211
221
 
212
222
  if gem_spec
213
- adjusted_default_lockfile_contents += <<~TEXT
223
+ adjusted_parent_lockfile_contents += <<~TEXT
214
224
  PATH
215
225
  remote: .
216
226
  specs:
@@ -220,36 +230,39 @@ module Bundler
220
230
 
221
231
  if lockfile_definition[:lockfile].exist?
222
232
  # if the lockfile already exists, "merge" it together
223
- default_lockfile = LockfileParser.new(adjusted_default_lockfile_contents)
233
+ parent_lockfile = LockfileParser.new(adjusted_parent_lockfile_contents)
224
234
  lockfile = LockfileParser.new(lockfile_definition[:lockfile].read)
225
235
 
226
236
  dependency_changes = false
227
237
  # replace any duplicate specs with what's in the default lockfile
228
238
  lockfile.specs.map! do |spec|
229
- default_spec = default_specs[[spec.name, spec.platform]]
230
- next spec unless default_spec
239
+ parent_spec = parent_specs[[spec.name, spec.platform]]
240
+ next spec unless parent_spec
231
241
 
232
- dependency_changes ||= spec != default_spec
233
- default_spec
242
+ dependency_changes ||= spec != parent_spec
243
+ parent_spec
234
244
  end
235
245
 
236
- lockfile.specs.replace(default_lockfile.specs + lockfile.specs).uniq!
237
- lockfile.sources.replace(default_lockfile.sources + lockfile.sources).uniq!
238
- lockfile.platforms.replace(default_lockfile.platforms).uniq!
246
+ lockfile.specs.replace(parent_lockfile.specs + lockfile.specs).uniq!
247
+ lockfile.sources.replace(parent_lockfile.sources + lockfile.sources).uniq!
248
+ lockfile.platforms.replace(parent_lockfile.platforms).uniq!
239
249
  # prune more specific platforms
240
250
  lockfile.platforms.delete_if do |p1|
241
251
  lockfile.platforms.any? do |p2|
242
252
  p2 != "ruby" && p1 != p2 && MatchPlatform.platforms_match?(p2, p1)
243
253
  end
244
254
  end
245
- lockfile.instance_variable_set(:@ruby_version, default_lockfile.ruby_version)
246
- lockfile.instance_variable_set(:@bundler_version, default_lockfile.bundler_version)
255
+ lockfile.instance_variable_set(:@ruby_version, parent_lockfile.ruby_version)
256
+ unless lockfile.bundler_version == parent_lockfile.bundler_version
257
+ unlocking_bundler = true
258
+ lockfile.instance_variable_set(:@bundler_version, parent_lockfile.bundler_version)
259
+ end
247
260
 
248
261
  new_contents = LockfileGenerator.generate(lockfile)
249
262
  else
250
- # no lockfile? just start out with the default lockfile's contents to inherit its
263
+ # no lockfile? just start out with the parent lockfile's contents to inherit its
251
264
  # locked gems
252
- new_contents = adjusted_default_lockfile_contents
265
+ new_contents = adjusted_parent_lockfile_contents
253
266
  end
254
267
 
255
268
  had_changes = false
@@ -261,7 +274,8 @@ module Bundler
261
274
  had_changes = write_lockfile(lockfile_definition,
262
275
  temp_lockfile.path,
263
276
  install: install,
264
- dependency_changes: dependency_changes)
277
+ dependency_changes: dependency_changes,
278
+ unlocking_bundler: unlocking_bundler)
265
279
  end
266
280
 
267
281
  # if we had changes, bundler may have updated some common
@@ -269,8 +283,9 @@ module Bundler
269
283
  # once to reset them back to the default lockfile's version.
270
284
  # if it's already good, the `check` check at the beginning of
271
285
  # the loop will skip the second sync anyway.
272
- if had_changes && attempts < 3
286
+ if had_changes && attempts < 2
273
287
  attempts += 1
288
+ Bundler.ui.debug("Re-running sync to #{relative_lockfile} to reset common dependencies")
274
289
  redo
275
290
  else
276
291
  attempts = 1
@@ -293,9 +308,9 @@ module Bundler
293
308
  @loaded = true
294
309
  return if lockfile_definitions.empty?
295
310
 
296
- return unless lockfile_definitions.none? { |definition| definition[:current] }
311
+ return unless lockfile_definitions.none? { |definition| definition[:active] }
297
312
 
298
- # Gemfile.lock isn't explicitly specified, otherwise it would be current
313
+ # Gemfile.lock isn't explicitly specified, otherwise it would be active
299
314
  default_lockfile_definition = lockfile_definitions.find do |definition|
300
315
  definition[:lockfile] == Bundler.default_lockfile(force_original: true)
301
316
  end
@@ -305,7 +320,7 @@ module Bundler
305
320
 
306
321
  raise GemfileNotFound, "Could not locate lockfile #{ENV["BUNDLE_LOCKFILE"].inspect}" if ENV["BUNDLE_LOCKFILE"]
307
322
 
308
- return unless default_lockfile_definition && default_lockfile_definition[:current] == false
323
+ return unless default_lockfile_definition && default_lockfile_definition[:active] == false
309
324
 
310
325
  raise GemfileEvalError, "No lockfiles marked as default"
311
326
  end
@@ -317,8 +332,10 @@ module Bundler
317
332
 
318
333
  # @!visibility private
319
334
  def inject_preamble
335
+ Bundler.ui.debug("Injecting multilock preamble")
336
+
320
337
  minor_version = Gem::Version.new(::Bundler::Multilock::VERSION).segments[0..1].join(".")
321
- bundle_preamble1_match = %(plugin "bundler-multilock")
338
+ bundle_preamble1_match = /plugin ["']bundler-multilock["']/
322
339
  bundle_preamble1 = <<~RUBY
323
340
  plugin "bundler-multilock", "~> #{minor_version}"
324
341
  RUBY
@@ -342,7 +359,16 @@ module Bundler
342
359
  end
343
360
 
344
361
  builder = Bundler::Plugin::DSL.new
345
- builder.eval_gemfile(Bundler.default_gemfile)
362
+ # this method is called as part of the plugin loading, but @loaded_plugin_names
363
+ # hasn't been set yet, so avoid re-entrancy issues
364
+ plugins = Bundler::Plugin.instance_variable_get(:@loaded_plugin_names)
365
+ original_plugins = plugins.dup
366
+ plugins << "bundler-multilock"
367
+ begin
368
+ builder.eval_gemfile(Bundler.default_gemfile)
369
+ ensure
370
+ plugins.replace(original_plugins)
371
+ end
346
372
  gemfiles = builder.instance_variable_get(:@gemfiles).map(&:read)
347
373
 
348
374
  modified = inject_specific_preamble(gemfile, gemfiles, injection_point, bundle_preamble2, add_newline: true)
@@ -364,8 +390,20 @@ module Bundler
364
390
 
365
391
  private
366
392
 
367
- def inject_specific_preamble(gemfile, gemfiles, injection_point, preamble, add_newline:, match: preamble)
368
- return false if gemfiles.any? { |g| g.include?(match) }
393
+ def expand_lockfile(lockfile)
394
+ if lockfile.is_a?(String) && !(lockfile.include?("/") || lockfile.end_with?(".lock"))
395
+ lockfile = "Gemfile.#{lockfile}.lock"
396
+ end
397
+ # use absolute paths
398
+ lockfile = Bundler.root.join(lockfile).expand_path if lockfile
399
+ # use the default lockfile (Gemfile.lock) if none was given
400
+ lockfile || Bundler.default_lockfile(force_original: true)
401
+ end
402
+
403
+ def inject_specific_preamble(gemfile, gemfiles, injection_point, preamble, add_newline:, match: nil)
404
+ # allow either type of quotes
405
+ match ||= Regexp.new(Regexp.escape(preamble).gsub('"', %(["'])))
406
+ return false if gemfiles.any? { |g| match.match?(g) }
369
407
 
370
408
  add_newline = false unless gemfile[injection_point - 1] == "\n"
371
409
 
@@ -375,7 +413,7 @@ module Bundler
375
413
  true
376
414
  end
377
415
 
378
- def write_lockfile(lockfile_definition, lockfile, install:, dependency_changes: false)
416
+ def write_lockfile(lockfile_definition, lockfile, install:, dependency_changes: false, unlocking_bundler: false)
379
417
  prepare_block = lockfile_definition[:prepare]
380
418
 
381
419
  gemfile = lockfile_definition[:gemfile]
@@ -385,33 +423,45 @@ module Bundler
385
423
  builder.eval_gemfile(gemfile, &prepare_block) if prepare_block
386
424
  builder.eval_gemfile(gemfile)
387
425
 
388
- definition = builder.to_definition(lockfile, {})
426
+ definition = builder.to_definition(lockfile, { bundler: unlocking_bundler })
389
427
  definition.instance_variable_set(:@dependency_changes, dependency_changes) if dependency_changes
390
- orig_definition = definition.dup # we might need it twice
391
428
 
392
429
  current_lockfile = lockfile_definition[:lockfile]
393
- if current_lockfile.exist?
394
- definition.instance_variable_set(:@lockfile_contents, current_lockfile.read)
395
- if install
430
+ definition.instance_variable_set(:@lockfile_contents, current_lockfile.read) if current_lockfile.exist?
431
+
432
+ orig_definition = definition.dup # we might need it twice
433
+
434
+ if current_lockfile.exist? && install
435
+ Bundler.settings.temporary(frozen: true) do
396
436
  current_definition = builder.to_definition(current_lockfile, {})
397
- begin
398
- current_definition.resolve_with_cache!
399
- if current_definition.missing_specs.any?
400
- Bundler.with_default_lockfile(current_lockfile) do
401
- Installer.install(gemfile.dirname, current_definition, {})
402
- end
437
+ current_definition.resolve_with_cache!
438
+ if current_definition.missing_specs.any?
439
+ Bundler.with_default_lockfile(current_lockfile) do
440
+ Installer.install(gemfile.dirname, current_definition, {})
403
441
  end
404
- rescue RubyVersionMismatch, GemNotFound, SolveFailure
405
- # ignore
406
442
  end
443
+ rescue RubyVersionMismatch, GemNotFound, SolveFailure
444
+ # ignore
407
445
  end
408
446
  end
409
447
 
410
448
  resolved_remotely = false
411
- begin
449
+ accesses = begin
412
450
  previous_ui_level = Bundler.ui.level
413
451
  Bundler.ui.level = "warn"
414
452
  begin
453
+ # this is a horrible hack, to fix what I consider to be a Bundler bug.
454
+ # basically, if you have multiple platform specific gems in your
455
+ # lockfile, and that gem gets unlocked, Bundler will only search
456
+ # locally to find them. But non-platform-local gems are _never_
457
+ # installed locally. So just find the non-platform-local gems
458
+ # in the lockfile (that we know are there from a prior remote
459
+ # resolution), and add them to the locally installed spec list.
460
+ definition.send(:source_map).locked_specs.each do |spec|
461
+ next if spec.match_platform(Bundler.local_platform)
462
+
463
+ spec.source.specs.add(spec)
464
+ end
415
465
  definition.resolve_with_cache!
416
466
  rescue GemNotFound, SolveFailure
417
467
  definition = orig_definition
@@ -419,7 +469,9 @@ module Bundler
419
469
  definition.resolve_remotely!
420
470
  resolved_remotely = true
421
471
  end
422
- definition.lock(lockfile_definition[:lockfile], true)
472
+ SharedHelpers.capture_filesystem_access do
473
+ definition.lock(lockfile_definition[:lockfile], true)
474
+ end
423
475
  ensure
424
476
  Bundler.ui.level = previous_ui_level
425
477
  end
@@ -432,7 +484,7 @@ module Bundler
432
484
  end
433
485
  end
434
486
 
435
- !definition.nothing_changed?
487
+ accesses && !accesses.empty?
436
488
  end
437
489
  end
438
490
 
@@ -444,3 +496,26 @@ module Bundler
444
496
  end
445
497
 
446
498
  Bundler::Multilock.inject_preamble unless Bundler::Multilock.loaded?
499
+
500
+ # this is terrible, but we can't prepend into these modules because we only load
501
+ # _inside_ of the CLI commands already running
502
+ if defined?(Bundler::CLI::Check)
503
+ require_relative "multilock/check"
504
+ at_exit do
505
+ next unless $!.nil?
506
+ next if $!.is_a?(SystemExit) && !$!.success?
507
+
508
+ next if Bundler::Multilock::Check.run
509
+
510
+ Bundler.ui.warn("You can attempt to fix by running `bundle install`")
511
+ exit 1
512
+ end
513
+ end
514
+ if defined?(Bundler::CLI::Lock)
515
+ at_exit do
516
+ next unless $!.nil?
517
+ next if $!.is_a?(SystemExit) && !$!.success?
518
+
519
+ Bundler::Multilock.after_install_all(install: false)
520
+ end
521
+ end
data/plugins.rb CHANGED
@@ -20,29 +20,6 @@
20
20
 
21
21
  require_relative "lib/bundler/multilock"
22
22
 
23
- # this is terrible, but we can't prepend into these modules because we only load
24
- # _inside_ of the CLI commands already running
25
- if defined?(Bundler::CLI::Check)
26
- require_relative "lib/bundler/multilock/check"
27
- at_exit do
28
- next unless $!.nil?
29
- next if $!.is_a?(SystemExit) && !$!.success?
30
-
31
- next if Bundler::Multilock::Check.run
32
-
33
- Bundler.ui.warn("You can attempt to fix by running `bundle install`")
34
- exit 1
35
- end
36
- end
37
- if defined?(Bundler::CLI::Lock)
38
- at_exit do
39
- next unless $!.nil?
40
- next if $!.is_a?(SystemExit) && !$!.success?
41
-
42
- Bundler::Multilock.after_install_all(install: false)
43
- end
44
- end
45
-
46
23
  Bundler::Plugin.add_hook(Bundler::Plugin::Events::GEM_AFTER_INSTALL_ALL) do |_|
47
24
  Bundler::Multilock.after_install_all
48
25
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bundler-multilock
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.10
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Instructure
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-09-26 00:00:00.000000000 Z
11
+ date: 2023-10-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -25,19 +25,19 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: 2.4.19
27
27
  - !ruby/object:Gem::Dependency
28
- name: byebug
28
+ name: debug
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '11.1'
33
+ version: '1.8'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '11.1'
40
+ version: '1.8'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -121,6 +121,8 @@ files:
121
121
  - lib/bundler/multilock/ext/dsl.rb
122
122
  - lib/bundler/multilock/ext/plugin.rb
123
123
  - lib/bundler/multilock/ext/plugin/dsl.rb
124
+ - lib/bundler/multilock/ext/shared_helpers.rb
125
+ - lib/bundler/multilock/ext/source.rb
124
126
  - lib/bundler/multilock/ext/source_list.rb
125
127
  - lib/bundler/multilock/lockfile_generator.rb
126
128
  - lib/bundler/multilock/version.rb