bundler-multilock 1.2.3 → 1.3.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.
- checksums.yaml +4 -4
- data/lib/bundler/multilock/cache.rb +139 -0
- data/lib/bundler/multilock/check.rb +134 -152
- data/lib/bundler/multilock/ext/dsl.rb +10 -0
- data/lib/bundler/multilock/ui/capture.rb +53 -0
- data/lib/bundler/multilock/version.rb +1 -1
- data/lib/bundler/multilock.rb +115 -42
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4143c13ccc6c8b7ef05ce4372cd1e77b293b3b55c8c1eafbb478e87ce195ae4f
|
|
4
|
+
data.tar.gz: 4e5ea6d45852f02823c578b3ad78eb95109f16dbbf4d2652cc2dc3f9addb1330
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8c45ae78248dcee0436eadde12ed749fc81112a514d43454998b1c4c238164060998713477f2689db433a753d652a6d419d45fa19650ec612f4ff0933ce82242
|
|
7
|
+
data.tar.gz: ff5e788cb22be790791b2f99d9abcd2fb7d1da717954fd7fd5644fd06274d12b0e7d98e01b917a3247e47ab08856ae9f994b99ed74f9d257972adc636c4ac3f4
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "ui/capture"
|
|
4
|
+
|
|
5
|
+
module Bundler
|
|
6
|
+
module Multilock
|
|
7
|
+
# caches lockfiles across multiple lockfile checks or sync runs
|
|
8
|
+
class Cache
|
|
9
|
+
def initialize
|
|
10
|
+
@contents = {}
|
|
11
|
+
@parsers = {}
|
|
12
|
+
@specs = {}
|
|
13
|
+
@reverse_dependencies = {}
|
|
14
|
+
@reverse_requirements = {}
|
|
15
|
+
@base_checks = {}
|
|
16
|
+
@deep_checks = {}
|
|
17
|
+
@base_check_messages = {}
|
|
18
|
+
@deep_check_messages = {}
|
|
19
|
+
@missing_specs = Set.new
|
|
20
|
+
@logged_missing = false
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Removes a given lockfile's associated cached data
|
|
24
|
+
#
|
|
25
|
+
# Should be called if the lockfile is modified
|
|
26
|
+
# @param lockfile_name [Pathname]
|
|
27
|
+
# @return [void]
|
|
28
|
+
def invalidate_lockfile(lockfile_name)
|
|
29
|
+
@contents.delete(lockfile_name)
|
|
30
|
+
@parsers.delete(lockfile_name)
|
|
31
|
+
@specs.delete(lockfile_name)
|
|
32
|
+
@reverse_dependencies.delete(lockfile_name)
|
|
33
|
+
@reverse_requirements.delete(lockfile_name)
|
|
34
|
+
invalidate_checks(lockfile_name)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def invalidate_checks(lockfile_name)
|
|
38
|
+
@base_checks.delete(lockfile_name)
|
|
39
|
+
@base_check_messages.delete(lockfile_name)
|
|
40
|
+
# must clear them all; downstream lockfiles may depend on the state of this lockfile
|
|
41
|
+
@deep_checks.clear
|
|
42
|
+
@deep_check_messages.clear
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @param lockfile_name [Pathname]
|
|
46
|
+
# @return [String] the raw contents of the lockfile
|
|
47
|
+
def contents(lockfile_name)
|
|
48
|
+
@contents.fetch(lockfile_name) do
|
|
49
|
+
@contents[lockfile_name] = lockfile_name.file? && lockfile_name.read.freeze
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @param lockfile_name [Pathname]
|
|
54
|
+
# @return [LockfileParser]
|
|
55
|
+
def parser(lockfile_name)
|
|
56
|
+
@parsers[lockfile_name] ||= LockfileParser.new(contents(lockfile_name))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def specs(lockfile_name)
|
|
60
|
+
@specs[lockfile_name] ||= parser(lockfile_name).specs.to_h do |spec|
|
|
61
|
+
[[spec.name, spec.platform], spec]
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @param lockfile_name [Pathname]
|
|
66
|
+
# @return [Hash<String, Set<String>>] hash of gem name to set of gem names that depend on it
|
|
67
|
+
def reverse_dependencies(lockfile_name)
|
|
68
|
+
ensure_reverse_data(lockfile_name)
|
|
69
|
+
@reverse_dependencies[lockfile_name]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @param lockfile_name [Pathname]
|
|
73
|
+
# @return [Hash<String, Gem::Requirement>] hash of gem name to requirement for that gem
|
|
74
|
+
def reverse_requirements(lockfile_name)
|
|
75
|
+
ensure_reverse_data(lockfile_name)
|
|
76
|
+
@reverse_requirements[lockfile_name]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def conflicting_requirements?(lockfile1_name, lockfile2_name, spec1, spec2)
|
|
80
|
+
reverse_requirements1 = reverse_requirements(lockfile1_name)[spec1.name]
|
|
81
|
+
reverse_requirements2 = reverse_requirements(lockfile2_name)[spec1.name]
|
|
82
|
+
|
|
83
|
+
!reverse_requirements1.satisfied_by?(spec2.version) &&
|
|
84
|
+
!reverse_requirements2.satisfied_by?(spec1.version)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def log_missing_spec(spec)
|
|
88
|
+
return if @missing_specs.include?(spec)
|
|
89
|
+
|
|
90
|
+
Bundler.ui.error "The following gems are missing" if @missing_specs.empty?
|
|
91
|
+
@missing_specs << spec
|
|
92
|
+
Bundler.ui.error(" * #{spec.name} (#{spec.version})")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
%i[base deep].each do |type|
|
|
96
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1 # rubocop:disable Style/DocumentDynamicEvalDefinition
|
|
97
|
+
def #{type}_check(lockfile_name)
|
|
98
|
+
if @#{type}_checks.key?(lockfile_name)
|
|
99
|
+
@#{type}_check_messages[lockfile_name].replay
|
|
100
|
+
@#{type}_checks[lockfile_name]
|
|
101
|
+
else
|
|
102
|
+
result = nil
|
|
103
|
+
messages = Bundler::Multilock::UI::Capture.capture do
|
|
104
|
+
result = @#{type}_checks[lockfile_name] = yield
|
|
105
|
+
end
|
|
106
|
+
@#{type}_check_messages[lockfile_name] = messages.tap(&:replay)
|
|
107
|
+
result
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
RUBY
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
def ensure_reverse_data(lockfile_name)
|
|
116
|
+
return if @reverse_requirements.key?(lockfile_name)
|
|
117
|
+
|
|
118
|
+
# can use Gem::Requirement.default_prelease when Ruby 2.6 support is dropped
|
|
119
|
+
reverse_requirements = Hash.new { |h, k| h[k] = Gem::Requirement.new(">= 0.a") }
|
|
120
|
+
reverse_dependencies = Hash.new { |h, k| h[k] = Set.new }
|
|
121
|
+
|
|
122
|
+
lockfile = parser(lockfile_name)
|
|
123
|
+
|
|
124
|
+
lockfile.dependencies.each_value do |dep|
|
|
125
|
+
reverse_requirements[dep.name].requirements.concat(dep.requirement.requirements)
|
|
126
|
+
end
|
|
127
|
+
lockfile.specs.each do |spec|
|
|
128
|
+
spec.dependencies.each do |dep|
|
|
129
|
+
reverse_requirements[dep.name].requirements.concat(dep.requirement.requirements)
|
|
130
|
+
reverse_dependencies[dep.name] << spec.name
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
@reverse_requirements[lockfile_name] = reverse_requirements
|
|
135
|
+
@reverse_dependencies[lockfile_name] = reverse_dependencies
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -2,31 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
require "set"
|
|
4
4
|
|
|
5
|
+
require_relative "cache"
|
|
6
|
+
|
|
5
7
|
module Bundler
|
|
6
8
|
module Multilock
|
|
7
9
|
class Check
|
|
8
|
-
attr_reader :lockfiles, :lockfile_contents, :lockfile_specs
|
|
9
|
-
|
|
10
10
|
class << self
|
|
11
11
|
def run
|
|
12
12
|
new.run
|
|
13
13
|
end
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def initialize
|
|
17
|
-
@
|
|
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|
|
|
28
|
-
[[spec.name, spec.platform], spec]
|
|
29
|
-
end
|
|
16
|
+
def initialize(cache = Cache.new)
|
|
17
|
+
@cache = cache
|
|
30
18
|
end
|
|
31
19
|
|
|
32
20
|
def run(skip_base_checks: false)
|
|
@@ -34,177 +22,171 @@ module Bundler
|
|
|
34
22
|
|
|
35
23
|
success = true
|
|
36
24
|
unless skip_base_checks
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
25
|
+
default_lockfile_definition = Multilock.default_lockfile_definition
|
|
26
|
+
default_lockfile_definition ||= { gemfile: Bundler.default_gemfile,
|
|
27
|
+
lockfile: Bundler.default_lockfile(force_original: true) }
|
|
28
|
+
base_check(default_lockfile_definition)
|
|
40
29
|
end
|
|
41
|
-
Multilock.lockfile_definitions.each do |lockfile_definition|
|
|
42
|
-
next if
|
|
30
|
+
Multilock.lockfile_definitions.each do |lockfile_name, lockfile_definition|
|
|
31
|
+
next if lockfile_name == Bundler.default_lockfile(force_original: true)
|
|
43
32
|
|
|
44
|
-
unless
|
|
45
|
-
Bundler.ui.error("Lockfile #{
|
|
33
|
+
unless lockfile_name.exist?
|
|
34
|
+
Bundler.ui.error("Lockfile #{lockfile_name} does not exist.")
|
|
46
35
|
success = false
|
|
36
|
+
next
|
|
47
37
|
end
|
|
48
38
|
|
|
49
|
-
|
|
50
|
-
new_missing = base_check(lockfile_definition, log_missing: missing_specs, return_missing: true)
|
|
51
|
-
success = false unless new_missing.empty?
|
|
52
|
-
missing_specs.merge(new_missing)
|
|
53
|
-
end
|
|
54
|
-
success = false unless check(lockfile_definition)
|
|
39
|
+
success &&= base_check(lockfile_definition) && deep_check(lockfile_definition)
|
|
55
40
|
end
|
|
56
41
|
success
|
|
57
42
|
end
|
|
58
43
|
|
|
59
44
|
# this is mostly equivalent to the built in checks in `bundle check`, but even
|
|
60
45
|
# more conservative, and returns false instead of exiting on failure
|
|
61
|
-
def base_check(lockfile_definition,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
Multilock.prepare_block = lockfile_definition[:prepare]
|
|
65
|
-
definition = Definition.build(lockfile_definition[:gemfile], lockfile_definition[:lockfile], false)
|
|
66
|
-
return return_missing ? [] : false unless definition.send(:current_platform_locked?)
|
|
67
|
-
|
|
68
|
-
begin
|
|
69
|
-
definition.validate_runtime!
|
|
70
|
-
not_installed = Bundler.ui.silence { definition.missing_specs }
|
|
71
|
-
rescue RubyVersionMismatch, GemNotFound, SolveFailure
|
|
72
|
-
return return_missing ? [] : false
|
|
73
|
-
end
|
|
46
|
+
def base_check(lockfile_definition, check_missing_deps: false)
|
|
47
|
+
lockfile_name = lockfile_definition[:lockfile]
|
|
48
|
+
default_root = Bundler.root
|
|
74
49
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
next if log_missing.include?(spec)
|
|
50
|
+
result = @cache.base_check(lockfile_name) do
|
|
51
|
+
next false unless lockfile_name.file?
|
|
78
52
|
|
|
79
|
-
|
|
80
|
-
|
|
53
|
+
Multilock.prepare_block = lockfile_definition[:prepare]
|
|
54
|
+
# root needs to be set so that paths are output relative to the correct root in the lockfile
|
|
55
|
+
Bundler.root = lockfile_definition[:gemfile].dirname
|
|
56
|
+
|
|
57
|
+
definition = Definition.build(lockfile_definition[:gemfile], lockfile_name, false)
|
|
58
|
+
next false unless definition.send(:current_platform_locked?)
|
|
59
|
+
|
|
60
|
+
begin
|
|
61
|
+
definition.validate_runtime!
|
|
62
|
+
not_installed = Bundler.ui.silence { definition.missing_specs }
|
|
63
|
+
rescue RubyVersionMismatch, GemNotFound, SolveFailure
|
|
64
|
+
next false
|
|
81
65
|
end
|
|
82
|
-
end
|
|
83
66
|
|
|
84
|
-
|
|
67
|
+
if Bundler.ui.error?
|
|
68
|
+
not_installed.each do |spec|
|
|
69
|
+
@cache.log_missing_spec(spec)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
next false unless not_installed.empty?
|
|
74
|
+
|
|
75
|
+
# cache a sentinel so that we can share a cache regardless of the check_missing_deps argument
|
|
76
|
+
next :missing_deps unless (definition.locked_gems.dependencies.values - definition.dependencies).empty?
|
|
85
77
|
|
|
86
|
-
|
|
87
|
-
|
|
78
|
+
true
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
return !check_missing_deps if result == :missing_deps
|
|
88
82
|
|
|
89
|
-
|
|
83
|
+
result
|
|
90
84
|
ensure
|
|
91
85
|
Multilock.prepare_block = nil
|
|
86
|
+
Bundler.root = default_root
|
|
92
87
|
end
|
|
93
88
|
|
|
94
89
|
# this checks for mismatches between the parent lockfile and the given lockfile,
|
|
95
90
|
# and for pinned dependencies in lockfiles requiring them
|
|
96
|
-
def
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
find_pinned_dependencies(proven_pinned, lockfile.dependencies.each_value)
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
# check for conflicting requirements (and build list of pins, in the same loop)
|
|
124
|
-
lockfile.specs.each do |spec|
|
|
125
|
-
parent_spec = lockfile_specs[parent][[spec.name, spec.platform]]
|
|
91
|
+
def deep_check(lockfile_definition)
|
|
92
|
+
lockfile_name = lockfile_definition[:lockfile]
|
|
93
|
+
@cache.deep_check(lockfile_name) do
|
|
94
|
+
success = true
|
|
95
|
+
proven_pinned = Set.new
|
|
96
|
+
needs_pin_check = []
|
|
97
|
+
parser = @cache.parser(lockfile_name)
|
|
98
|
+
lockfile_path = lockfile_name.relative_path_from(Dir.pwd)
|
|
99
|
+
parent_lockfile_name = lockfile_definition[:parent]
|
|
100
|
+
parent_parser = @cache.parser(parent_lockfile_name)
|
|
101
|
+
unless parser.platforms == parent_parser.platforms
|
|
102
|
+
Bundler.ui.error("The platforms in #{lockfile_path} do not match the parent lockfile.")
|
|
103
|
+
success = false
|
|
104
|
+
end
|
|
105
|
+
unless parser.bundler_version == parent_parser.bundler_version
|
|
106
|
+
Bundler.ui.error("bundler (#{parser.bundler_version}) in #{lockfile_path} " \
|
|
107
|
+
"does not match the parent lockfile's version (@#{parent_parser.bundler_version}).")
|
|
108
|
+
success = false
|
|
109
|
+
end
|
|
110
|
+
unless parser.ruby_version == parent_parser.ruby_version
|
|
111
|
+
Bundler.ui.error("ruby (#{parser.ruby_version || "<none>"}) in #{lockfile_path} " \
|
|
112
|
+
"does not match the parent lockfile's version (#{parent_parser.ruby_version}).")
|
|
113
|
+
success = false
|
|
114
|
+
end
|
|
126
115
|
|
|
116
|
+
# look through top-level explicit dependencies for pinned requirements
|
|
127
117
|
if lockfile_definition[:enforce_pinned_additional_dependencies]
|
|
128
|
-
|
|
129
|
-
find_pinned_dependencies(proven_pinned, spec.dependencies)
|
|
130
|
-
|
|
131
|
-
needs_pin_check << spec unless parent_spec
|
|
118
|
+
find_pinned_dependencies(proven_pinned, parser.dependencies.each_value)
|
|
132
119
|
end
|
|
133
120
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
121
|
+
# check for conflicting requirements (and build list of pins, in the same loop)
|
|
122
|
+
parser.specs.each do |spec|
|
|
123
|
+
parent_spec = @cache.specs(parent_lockfile_name)[[spec.name, spec.platform]]
|
|
124
|
+
|
|
125
|
+
if lockfile_definition[:enforce_pinned_additional_dependencies]
|
|
126
|
+
# look through what this spec depends on, and keep track of all pinned requirements
|
|
127
|
+
find_pinned_dependencies(proven_pinned, spec.dependencies)
|
|
128
|
+
|
|
129
|
+
needs_pin_check << spec unless parent_spec
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
next unless parent_spec
|
|
133
|
+
|
|
134
|
+
# have to ensure Path sources are relative to their lockfile before comparing
|
|
135
|
+
same_source = if [parent_spec.source, spec.source].grep(Source::Path).length == 2
|
|
136
|
+
lockfile_name
|
|
137
|
+
.dirname
|
|
138
|
+
.join(spec.source.path)
|
|
139
|
+
.ascend
|
|
140
|
+
.any?(parent_lockfile_name.dirname.join(parent_spec.source.path))
|
|
141
|
+
else
|
|
142
|
+
parent_spec.source == spec.source
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
next if parent_spec.version == spec.version && same_source
|
|
146
|
+
|
|
147
|
+
# the version in the parent lockfile cannot possibly satisfy the requirements
|
|
148
|
+
# in this lockfile, and vice versa, so we assume it's intentional and allow it
|
|
149
|
+
if @cache.conflicting_requirements?(lockfile_name, parent_lockfile_name, spec, parent_spec)
|
|
150
|
+
# we're allowing it to differ from the parent, so pin check requirement comes into play
|
|
151
|
+
needs_pin_check << spec if lockfile_definition[:enforce_pinned_additional_dependencies]
|
|
152
|
+
next
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
Bundler.ui.error("#{spec}#{spec.git_version} in #{lockfile_path} " \
|
|
156
|
+
"does not match the parent lockfile's version " \
|
|
157
|
+
"(@#{parent_spec.version}#{parent_spec.git_version}); " \
|
|
158
|
+
"this may be due to a conflicting requirement, which would require manual resolution.")
|
|
159
|
+
success = false
|
|
156
160
|
end
|
|
157
161
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
162
|
+
# now that we have built a list of every gem that is pinned, go through
|
|
163
|
+
# the gems that were in this lockfile, but not the parent lockfile, and
|
|
164
|
+
# ensure it's pinned _somehow_
|
|
165
|
+
needs_pin_check.each do |spec|
|
|
166
|
+
pinned = case spec.source
|
|
167
|
+
when Source::Git
|
|
168
|
+
spec.source.ref == spec.source.revision
|
|
169
|
+
when Source::Path
|
|
170
|
+
true
|
|
171
|
+
when Source::Rubygems
|
|
172
|
+
proven_pinned.include?(spec.name)
|
|
173
|
+
else
|
|
174
|
+
false
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
next if pinned
|
|
178
|
+
|
|
179
|
+
Bundler.ui.error("#{spec} in #{lockfile_path} has not been pinned to a specific version, " \
|
|
180
|
+
"which is required since it is not part of the parent lockfile.")
|
|
181
|
+
success = false
|
|
182
|
+
end
|
|
164
183
|
|
|
165
|
-
|
|
166
|
-
# the gems that were in this lockfile, but not the parent lockfile, and
|
|
167
|
-
# ensure it's pinned _somehow_
|
|
168
|
-
needs_pin_check.each do |spec|
|
|
169
|
-
pinned = case spec.source
|
|
170
|
-
when Source::Git
|
|
171
|
-
spec.source.ref == spec.source.revision
|
|
172
|
-
when Source::Path
|
|
173
|
-
true
|
|
174
|
-
when Source::Rubygems
|
|
175
|
-
proven_pinned.include?(spec.name)
|
|
176
|
-
else
|
|
177
|
-
false
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
next if pinned
|
|
181
|
-
|
|
182
|
-
Bundler.ui.error("#{spec} in #{lockfile_path} has not been pinned to a specific version, " \
|
|
183
|
-
"which is required since it is not part of the parent lockfile.")
|
|
184
|
-
success = false
|
|
184
|
+
success
|
|
185
185
|
end
|
|
186
|
-
|
|
187
|
-
success
|
|
188
186
|
end
|
|
189
187
|
|
|
190
188
|
private
|
|
191
189
|
|
|
192
|
-
def cache_reverse_dependencies(lockfile)
|
|
193
|
-
# can use Gem::Requirement.default_prelease when Ruby 2.6 support is dropped
|
|
194
|
-
reverse_dependencies = Hash.new { |h, k| h[k] = Gem::Requirement.new(">= 0.a") }
|
|
195
|
-
|
|
196
|
-
lockfile.dependencies.each_value do |spec|
|
|
197
|
-
reverse_dependencies[spec.name].requirements.concat(spec.requirement.requirements)
|
|
198
|
-
end
|
|
199
|
-
lockfile.specs.each do |spec|
|
|
200
|
-
spec.dependencies.each do |dependency|
|
|
201
|
-
reverse_dependencies[dependency.name].requirements.concat(dependency.requirement.requirements)
|
|
202
|
-
end
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
reverse_dependencies
|
|
206
|
-
end
|
|
207
|
-
|
|
208
190
|
def find_pinned_dependencies(proven_pinned, dependencies)
|
|
209
191
|
dependencies.each do |dependency|
|
|
210
192
|
dependency.requirement.requirements.each do |requirement|
|
|
@@ -11,12 +11,22 @@ module Bundler
|
|
|
11
11
|
|
|
12
12
|
# Significant changes:
|
|
13
13
|
# * evaluate the prepare block as part of the gemfile
|
|
14
|
+
# * keep track of the ruby version set in the default gemfile
|
|
15
|
+
# * apply that ruby version to alternate lockfiles if they didn't set one
|
|
16
|
+
# themselves
|
|
14
17
|
# * mark Multilock as loaded once the main gemfile is evaluated
|
|
15
18
|
# so that they're not loaded multiple times
|
|
16
19
|
def evaluate(gemfile, lockfile, unlock)
|
|
17
20
|
builder = new
|
|
18
21
|
builder.eval_gemfile(gemfile, &Multilock.prepare_block) if Multilock.prepare_block
|
|
19
22
|
builder.eval_gemfile(gemfile)
|
|
23
|
+
if (ruby_version_requirement = builder.instance_variable_get(:@ruby_version))
|
|
24
|
+
Multilock.lockfile_definitions[lockfile][:ruby_version_requirement] = ruby_version_requirement
|
|
25
|
+
elsif (parent_lockfile = Multilock.lockfile_definitions.dig(lockfile, :parent)) &&
|
|
26
|
+
(parent_lockfile_definition = Multilock.lockfile_definitions[parent_lockfile]) &&
|
|
27
|
+
(parent_ruby_version_requirement = parent_lockfile_definition[:ruby_version_requirement])
|
|
28
|
+
builder.instance_variable_set(:@ruby_version, parent_ruby_version_requirement)
|
|
29
|
+
end
|
|
20
30
|
Multilock.loaded!
|
|
21
31
|
builder.to_definition(lockfile, unlock)
|
|
22
32
|
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bundler
|
|
4
|
+
module Multilock
|
|
5
|
+
module UI
|
|
6
|
+
class Capture < Bundler::UI::Silent
|
|
7
|
+
class << self
|
|
8
|
+
def capture
|
|
9
|
+
original_ui = Bundler.ui
|
|
10
|
+
Bundler.ui = new
|
|
11
|
+
yield
|
|
12
|
+
Bundler.ui
|
|
13
|
+
ensure
|
|
14
|
+
Bundler.ui = original_ui
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize
|
|
19
|
+
@messages = []
|
|
20
|
+
|
|
21
|
+
super
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def replay
|
|
25
|
+
@messages.each do |(level, args)|
|
|
26
|
+
Bundler.ui.send(level, *args)
|
|
27
|
+
end
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def add_color(string, _color)
|
|
32
|
+
string
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
%i[info confirm warn error debug].each do |level|
|
|
36
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
37
|
+
def #{level}(message = nil, newline = nil) # def info(message = nil, newline = nil)
|
|
38
|
+
@messages << [:#{level}, [message, newline]] # @messages << [:info, [message, newline]]
|
|
39
|
+
end # end
|
|
40
|
+
#
|
|
41
|
+
def #{level}? # def info?
|
|
42
|
+
true # true
|
|
43
|
+
end # end
|
|
44
|
+
RUBY
|
|
45
|
+
|
|
46
|
+
def trace(message, newline = nil, force = false) # rubocop:disable Style/OptionalBooleanParameter
|
|
47
|
+
@messages << [:trace, [message, newline, force]]
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
data/lib/bundler/multilock.rb
CHANGED
|
@@ -56,26 +56,24 @@ module Bundler
|
|
|
56
56
|
# allow short-form lockfile names
|
|
57
57
|
lockfile = expand_lockfile(lockfile)
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
raise ArgumentError, "Lockfile #{lockfile} is already defined"
|
|
61
|
-
end
|
|
59
|
+
raise ArgumentError, "Lockfile #{lockfile} is already defined" if lockfile_definitions.key?(lockfile)
|
|
62
60
|
|
|
63
61
|
env_lockfile = lockfile if active && ENV["BUNDLE_LOCKFILE"] == "active"
|
|
64
62
|
env_lockfile ||= ENV["BUNDLE_LOCKFILE"]&.then { |l| expand_lockfile(l) }
|
|
65
63
|
active = env_lockfile == lockfile if env_lockfile
|
|
66
64
|
|
|
67
|
-
if active && (old_active = lockfile_definitions.find { |definition| definition[:active] })
|
|
65
|
+
if active && (old_active = lockfile_definitions.each_value.find { |definition| definition[:active] })
|
|
68
66
|
raise ArgumentError, "Only one lockfile (#{old_active[:lockfile]}) can be flagged as active"
|
|
69
67
|
end
|
|
70
68
|
|
|
71
69
|
parent = expand_lockfile(parent)
|
|
72
70
|
if parent != Bundler.default_lockfile(force_original: true) &&
|
|
73
|
-
!lockfile_definitions.
|
|
71
|
+
!lockfile_definitions.key?(parent) &&
|
|
74
72
|
!parent.exist?
|
|
75
73
|
raise ArgumentError, "Parent lockfile #{parent} is not defined"
|
|
76
74
|
end
|
|
77
75
|
|
|
78
|
-
lockfile_definitions
|
|
76
|
+
lockfile_definitions[lockfile] = (lockfile_def = {
|
|
79
77
|
gemfile: (gemfile && Bundler.root.join(gemfile).expand_path) || Bundler.default_gemfile,
|
|
80
78
|
lockfile: lockfile,
|
|
81
79
|
active: active,
|
|
@@ -150,37 +148,52 @@ module Bundler
|
|
|
150
148
|
Bundler.ui.debug("Syncing to alternate lockfiles")
|
|
151
149
|
|
|
152
150
|
attempts = 1
|
|
151
|
+
previous_contents = Set.new
|
|
153
152
|
|
|
154
153
|
default_root = Bundler.root
|
|
155
154
|
|
|
156
|
-
|
|
155
|
+
cache = Cache.new
|
|
156
|
+
checker = Check.new(cache)
|
|
157
157
|
synced_any = false
|
|
158
|
+
local_parser_cache = {}
|
|
158
159
|
Bundler.settings.temporary(cache_all_platforms: true, suppress_install_using_messages: true) do
|
|
159
|
-
lockfile_definitions.each do |lockfile_definition|
|
|
160
|
+
lockfile_definitions.each do |lockfile_name, lockfile_definition|
|
|
160
161
|
# we already wrote the default lockfile
|
|
161
|
-
next if
|
|
162
|
+
next if lockfile_name == Bundler.default_lockfile(force_original: true)
|
|
162
163
|
|
|
163
164
|
# root needs to be set so that paths are output relative to the correct root in the lockfile
|
|
164
165
|
Bundler.root = lockfile_definition[:gemfile].dirname
|
|
165
166
|
|
|
166
|
-
relative_lockfile =
|
|
167
|
+
relative_lockfile = lockfile_name.relative_path_from(Dir.pwd)
|
|
168
|
+
|
|
169
|
+
# prevent infinite loops of tick-tocking back and forth between two versions
|
|
170
|
+
current_contents = cache.contents(lockfile_name)
|
|
171
|
+
if previous_contents.include?(current_contents)
|
|
172
|
+
Bundler.ui.debug("Unable to converge on a single solution for #{lockfile_name}; " \
|
|
173
|
+
"perhaps there are conflicting requirements?")
|
|
174
|
+
attempts = 1
|
|
175
|
+
previous_contents.clear
|
|
176
|
+
next
|
|
177
|
+
end
|
|
178
|
+
previous_contents << current_contents
|
|
167
179
|
|
|
168
180
|
# already up to date?
|
|
169
181
|
up_to_date = false
|
|
170
182
|
Bundler.settings.temporary(frozen: true) do
|
|
171
183
|
Bundler.ui.silence do
|
|
172
184
|
up_to_date = checker.base_check(lockfile_definition, check_missing_deps: true) &&
|
|
173
|
-
checker.
|
|
185
|
+
checker.deep_check(lockfile_definition)
|
|
174
186
|
end
|
|
175
187
|
end
|
|
176
188
|
if up_to_date
|
|
177
189
|
attempts = 1
|
|
190
|
+
previous_contents.clear
|
|
178
191
|
next
|
|
179
192
|
end
|
|
180
193
|
|
|
181
194
|
if Bundler.frozen_bundle?
|
|
182
195
|
# if we're frozen, you have to use the pre-existing lockfile
|
|
183
|
-
unless
|
|
196
|
+
unless lockfile_name.exist?
|
|
184
197
|
Bundler.ui.error("The bundle is locked, but #{relative_lockfile} is missing. " \
|
|
185
198
|
"Please make sure you have checked #{relative_lockfile} " \
|
|
186
199
|
"into version control before deploying.")
|
|
@@ -188,19 +201,19 @@ module Bundler
|
|
|
188
201
|
end
|
|
189
202
|
|
|
190
203
|
Bundler.ui.info("Installing gems for #{relative_lockfile}...")
|
|
191
|
-
write_lockfile(lockfile_definition,
|
|
204
|
+
write_lockfile(lockfile_definition, lockfile_name, cache, install: install)
|
|
192
205
|
else
|
|
193
206
|
Bundler.ui.info("Syncing to #{relative_lockfile}...") if attempts == 1
|
|
194
207
|
synced_any = true
|
|
195
208
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
parent_specs =
|
|
209
|
+
specs = lockfile_name.exist? ? cache.specs(lockfile_name) : {}
|
|
210
|
+
parent_lockfile_name = lockfile_definition[:parent]
|
|
211
|
+
parent_root = parent_lockfile_name.dirname
|
|
212
|
+
parent_specs = cache.specs(parent_lockfile_name)
|
|
200
213
|
|
|
201
214
|
# adjust locked paths from the parent lockfile to be relative to _this_ gemfile
|
|
202
215
|
adjusted_parent_lockfile_contents =
|
|
203
|
-
|
|
216
|
+
cache.contents(parent_lockfile_name).gsub(/PATH\n remote: ([^\n]+)\n/) do |remote|
|
|
204
217
|
remote_path = Pathname.new($1)
|
|
205
218
|
next remote if remote_path.absolute?
|
|
206
219
|
|
|
@@ -220,23 +233,60 @@ module Bundler
|
|
|
220
233
|
TEXT
|
|
221
234
|
end
|
|
222
235
|
|
|
223
|
-
if
|
|
236
|
+
if lockfile_name.exist?
|
|
224
237
|
# if the lockfile already exists, "merge" it together
|
|
225
|
-
parent_lockfile =
|
|
226
|
-
|
|
238
|
+
parent_lockfile = if adjusted_parent_lockfile_contents == cache.contents(lockfile_name)
|
|
239
|
+
cache.parser(parent_lockfile_name)
|
|
240
|
+
else
|
|
241
|
+
local_parser_cache[adjusted_parent_lockfile_contents] ||=
|
|
242
|
+
LockfileParser.new(adjusted_parent_lockfile_contents)
|
|
243
|
+
end
|
|
244
|
+
lockfile = cache.parser(lockfile_name)
|
|
227
245
|
|
|
228
246
|
dependency_changes = false
|
|
229
|
-
|
|
247
|
+
|
|
248
|
+
spec_precedences = {}
|
|
249
|
+
|
|
250
|
+
check_precedence = lambda do |spec, parent_spec|
|
|
251
|
+
next :parent if spec.nil?
|
|
252
|
+
next :self if parent_spec.nil?
|
|
253
|
+
next spec_precedences[spec.name] if spec_precedences.key?(spec.name)
|
|
254
|
+
|
|
255
|
+
precedence = :self if cache.conflicting_requirements?(lockfile_name,
|
|
256
|
+
parent_lockfile_name,
|
|
257
|
+
spec,
|
|
258
|
+
parent_spec)
|
|
259
|
+
|
|
260
|
+
# look through all reverse dependencies; if any of them say it
|
|
261
|
+
# has to come from self, due to conflicts, then this gem has
|
|
262
|
+
# to come from self as well
|
|
263
|
+
[cache.reverse_dependencies(lockfile_name),
|
|
264
|
+
cache.reverse_dependencies(parent_lockfile_name)].each do |reverse_dependencies|
|
|
265
|
+
break if precedence == :self
|
|
266
|
+
|
|
267
|
+
reverse_dependencies[spec.name].each do |dep_name|
|
|
268
|
+
precedence = check_precedence.call(specs[dep_name], parent_specs[dep_name])
|
|
269
|
+
break if precedence == :self
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
spec_precedences[spec.name] = precedence || :parent
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# replace any duplicate specs with what's in the parent lockfile
|
|
230
277
|
lockfile.specs.map! do |spec|
|
|
231
278
|
parent_spec = parent_specs[[spec.name, spec.platform]]
|
|
232
279
|
next spec unless parent_spec
|
|
233
280
|
|
|
281
|
+
next spec if check_precedence.call(spec, parent_spec) == :self
|
|
282
|
+
|
|
234
283
|
dependency_changes ||= spec != parent_spec
|
|
235
|
-
|
|
284
|
+
|
|
285
|
+
new_spec = parent_spec.dup
|
|
286
|
+
new_spec.source = spec.source
|
|
287
|
+
new_spec
|
|
236
288
|
end
|
|
237
289
|
|
|
238
|
-
lockfile.specs.replace(parent_lockfile.specs + lockfile.specs).uniq!
|
|
239
|
-
lockfile.sources.replace(parent_lockfile.sources + lockfile.sources).uniq!
|
|
240
290
|
lockfile.platforms.replace(parent_lockfile.platforms).uniq!
|
|
241
291
|
# prune more specific platforms
|
|
242
292
|
lockfile.platforms.delete_if do |p1|
|
|
@@ -244,9 +294,9 @@ module Bundler
|
|
|
244
294
|
p2 != "ruby" && p1 != p2 && MatchPlatform.platforms_match?(p2, p1)
|
|
245
295
|
end
|
|
246
296
|
end
|
|
247
|
-
lockfile.instance_variable_set(:@ruby_version, parent_lockfile.ruby_version)
|
|
297
|
+
lockfile.instance_variable_set(:@ruby_version, parent_lockfile.ruby_version) if lockfile.ruby_version
|
|
248
298
|
unless lockfile.bundler_version == parent_lockfile.bundler_version
|
|
249
|
-
unlocking_bundler =
|
|
299
|
+
unlocking_bundler = parent_lockfile.bundler_version
|
|
250
300
|
lockfile.instance_variable_set(:@bundler_version, parent_lockfile.bundler_version)
|
|
251
301
|
end
|
|
252
302
|
|
|
@@ -263,24 +313,27 @@ module Bundler
|
|
|
263
313
|
temp_lockfile.write(new_contents)
|
|
264
314
|
temp_lockfile.flush
|
|
265
315
|
|
|
266
|
-
had_changes
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
316
|
+
had_changes ||= write_lockfile(lockfile_definition,
|
|
317
|
+
temp_lockfile.path,
|
|
318
|
+
cache,
|
|
319
|
+
install: install,
|
|
320
|
+
dependency_changes: dependency_changes,
|
|
321
|
+
unlocking_bundler: unlocking_bundler)
|
|
271
322
|
end
|
|
323
|
+
cache.invalidate_lockfile(lockfile_name) if had_changes
|
|
272
324
|
|
|
273
325
|
# if we had changes, bundler may have updated some common
|
|
274
326
|
# dependencies beyond the default lockfile, so re-run it
|
|
275
327
|
# once to reset them back to the default lockfile's version.
|
|
276
328
|
# if it's already good, the `check` check at the beginning of
|
|
277
329
|
# the loop will skip the second sync anyway.
|
|
278
|
-
if had_changes
|
|
330
|
+
if had_changes
|
|
279
331
|
attempts += 1
|
|
280
332
|
Bundler.ui.debug("Re-running sync to #{relative_lockfile} to reset common dependencies")
|
|
281
333
|
redo
|
|
282
334
|
else
|
|
283
335
|
attempts = 1
|
|
336
|
+
previous_contents.clear
|
|
284
337
|
end
|
|
285
338
|
end
|
|
286
339
|
end
|
|
@@ -300,7 +353,7 @@ module Bundler
|
|
|
300
353
|
@loaded = true
|
|
301
354
|
return if lockfile_definitions.empty?
|
|
302
355
|
|
|
303
|
-
return unless lockfile_definitions.none? { |definition| definition[:active] }
|
|
356
|
+
return unless lockfile_definitions.each_value.none? { |definition| definition[:active] }
|
|
304
357
|
|
|
305
358
|
if ENV["BUNDLE_LOCKFILE"]&.then { |l| expand_lockfile(l) } ==
|
|
306
359
|
Bundler.default_lockfile(force_original: true)
|
|
@@ -310,9 +363,7 @@ module Bundler
|
|
|
310
363
|
raise GemfileNotFound, "Could not locate lockfile #{ENV["BUNDLE_LOCKFILE"].inspect}" if ENV["BUNDLE_LOCKFILE"]
|
|
311
364
|
|
|
312
365
|
# Gemfile.lock isn't explicitly specified, otherwise it would be active
|
|
313
|
-
default_lockfile_definition =
|
|
314
|
-
definition[:lockfile] == Bundler.default_lockfile(force_original: true)
|
|
315
|
-
end
|
|
366
|
+
default_lockfile_definition = self.default_lockfile_definition
|
|
316
367
|
return unless default_lockfile_definition && default_lockfile_definition[:active] == false
|
|
317
368
|
|
|
318
369
|
raise GemfileEvalError, "No lockfiles marked as active"
|
|
@@ -377,10 +428,15 @@ module Bundler
|
|
|
377
428
|
|
|
378
429
|
# @!visibility private
|
|
379
430
|
def reset!
|
|
380
|
-
@lockfile_definitions =
|
|
431
|
+
@lockfile_definitions = {}
|
|
381
432
|
@loaded = false
|
|
382
433
|
end
|
|
383
434
|
|
|
435
|
+
# @!visibility private
|
|
436
|
+
def default_lockfile_definition
|
|
437
|
+
lockfile_definitions[Bundler.default_lockfile(force_original: true)]
|
|
438
|
+
end
|
|
439
|
+
|
|
384
440
|
private
|
|
385
441
|
|
|
386
442
|
def expand_lockfile(lockfile)
|
|
@@ -406,7 +462,12 @@ module Bundler
|
|
|
406
462
|
true
|
|
407
463
|
end
|
|
408
464
|
|
|
409
|
-
def write_lockfile(lockfile_definition,
|
|
465
|
+
def write_lockfile(lockfile_definition,
|
|
466
|
+
lockfile,
|
|
467
|
+
cache,
|
|
468
|
+
install:,
|
|
469
|
+
dependency_changes: false,
|
|
470
|
+
unlocking_bundler: false)
|
|
410
471
|
prepare_block = lockfile_definition[:prepare]
|
|
411
472
|
|
|
412
473
|
gemfile = lockfile_definition[:gemfile]
|
|
@@ -415,6 +476,12 @@ module Bundler
|
|
|
415
476
|
builder = Dsl.new
|
|
416
477
|
builder.eval_gemfile(gemfile, &prepare_block) if prepare_block
|
|
417
478
|
builder.eval_gemfile(gemfile)
|
|
479
|
+
if !builder.instance_variable_get(:@ruby_version) &&
|
|
480
|
+
(parent_lockfile = lockfile_definition[:parent]) &&
|
|
481
|
+
(parent_lockfile_definition = lockfile_definitions[parent_lockfile]) &&
|
|
482
|
+
(parent_ruby_version_requirement = parent_lockfile_definition[:ruby_version_requirement])
|
|
483
|
+
builder.instance_variable_set(:@ruby_version, parent_ruby_version_requirement)
|
|
484
|
+
end
|
|
418
485
|
|
|
419
486
|
definition = builder.to_definition(lockfile, { bundler: unlocking_bundler })
|
|
420
487
|
definition.instance_variable_set(:@dependency_changes, dependency_changes) if dependency_changes
|
|
@@ -436,11 +503,12 @@ module Bundler
|
|
|
436
503
|
|
|
437
504
|
current_definition.resolve_with_cache!
|
|
438
505
|
if current_definition.missing_specs.any?
|
|
506
|
+
cache.invalidate_checks(current_lockfile)
|
|
439
507
|
Bundler.with_default_lockfile(current_lockfile) do
|
|
440
508
|
Installer.install(gemfile.dirname, current_definition, {})
|
|
441
509
|
end
|
|
442
510
|
end
|
|
443
|
-
rescue RubyVersionMismatch, GemNotFound, SolveFailure
|
|
511
|
+
rescue RubyVersionMismatch, GemNotFound, SolveFailure, InstallError, ProductionError
|
|
444
512
|
# ignore
|
|
445
513
|
end
|
|
446
514
|
end
|
|
@@ -470,11 +538,16 @@ module Bundler
|
|
|
470
538
|
resolved_remotely = true
|
|
471
539
|
end
|
|
472
540
|
SharedHelpers.capture_filesystem_access do
|
|
541
|
+
definition.instance_variable_set(:@resolved_bundler_version, unlocking_bundler) if unlocking_bundler
|
|
542
|
+
|
|
543
|
+
# need to force it to _not_ preserve unknown sections, so that it
|
|
544
|
+
# will overwrite the ruby version
|
|
545
|
+
definition.instance_variable_set(:@unlocking_bundler, true)
|
|
473
546
|
if Bundler.gem_version >= Gem::Version.new("2.5.6")
|
|
474
547
|
definition.instance_variable_set(:@lockfile, lockfile_definition[:lockfile])
|
|
475
|
-
definition.lock
|
|
548
|
+
definition.lock
|
|
476
549
|
else
|
|
477
|
-
definition.lock(lockfile_definition[:lockfile]
|
|
550
|
+
definition.lock(lockfile_definition[:lockfile])
|
|
478
551
|
end
|
|
479
552
|
end
|
|
480
553
|
ensure
|
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.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Instructure
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2024-
|
|
11
|
+
date: 2024-03-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -79,6 +79,7 @@ extensions: []
|
|
|
79
79
|
extra_rdoc_files: []
|
|
80
80
|
files:
|
|
81
81
|
- lib/bundler/multilock.rb
|
|
82
|
+
- lib/bundler/multilock/cache.rb
|
|
82
83
|
- lib/bundler/multilock/check.rb
|
|
83
84
|
- lib/bundler/multilock/ext/bundler.rb
|
|
84
85
|
- lib/bundler/multilock/ext/definition.rb
|
|
@@ -89,6 +90,7 @@ files:
|
|
|
89
90
|
- lib/bundler/multilock/ext/source.rb
|
|
90
91
|
- lib/bundler/multilock/ext/source_list.rb
|
|
91
92
|
- lib/bundler/multilock/lockfile_generator.rb
|
|
93
|
+
- lib/bundler/multilock/ui/capture.rb
|
|
92
94
|
- lib/bundler/multilock/version.rb
|
|
93
95
|
- plugins.rb
|
|
94
96
|
homepage: https://github.com/instructure/bundler-multilock
|
|
@@ -111,7 +113,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
111
113
|
- !ruby/object:Gem::Version
|
|
112
114
|
version: '0'
|
|
113
115
|
requirements: []
|
|
114
|
-
rubygems_version: 3.5.
|
|
116
|
+
rubygems_version: 3.5.7
|
|
115
117
|
signing_key:
|
|
116
118
|
specification_version: 4
|
|
117
119
|
summary: Support Multiple Lockfiles
|