bundle update bundler-multilock

Change-Id: I47f6023ee0f5c50ba0a4cab9a3aa8848e8c540de
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/344095
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Aaron Ogata <aogata@instructure.com>
Build-Review: Aaron Ogata <aogata@instructure.com>
QA-Review: Cody Cutrer <cody@instructure.com>
Product-Review: Cody Cutrer <cody@instructure.com>
This commit is contained in:
Cody Cutrer 2024-03-29 15:11:10 -06:00
parent 6894600a2c
commit 82c87a7b56
96 changed files with 716 additions and 192 deletions

View File

@ -15,7 +15,7 @@ source "https://rubygems.org/"
Plugin.uninstall(["bundler_lockfile_extensions"], {}) if Plugin.installed?("bundler_lockfile_extensions")
# vendored until https://github.com/rubygems/rubygems/pull/6957 is merged and released
plugin "bundler-multilock", "1.2.3", path: "vendor/gems/bundler-multilock"
plugin "bundler-multilock", "1.3.0", path: "vendor/gems/bundler-multilock"
# the extra check here is in case `bundle check` or `bundle exec` gets run before `bundle install`,
# and is also fixed by the same PR
raise GemNotFound, "bundler-multilock plugin is not installed" if !is_a?(Bundler::Plugin::DSL) && !Plugin.installed?("bundler-multilock")

View File

@ -169,5 +169,8 @@ DEPENDENCIES
rubocop-rake (~> 0.6)
rubocop-rspec (~> 2.22)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -70,5 +70,8 @@ DEPENDENCIES
rspec (~> 3.12)
simplecov (~> 0.22)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -80,5 +80,8 @@ DEPENDENCIES
rspec (~> 3.12)
simplecov (~> 0.22)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -58,5 +58,8 @@ DEPENDENCIES
rspec (~> 3.12)
sqlite3
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -70,5 +70,8 @@ DEPENDENCIES
rspec (~> 3.12)
sqlite3
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -55,5 +55,8 @@ DEPENDENCIES
rspec (~> 3.12)
simplecov (~> 0.22)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -65,5 +65,8 @@ DEPENDENCIES
rspec (~> 3.12)
simplecov (~> 0.22)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -113,5 +113,8 @@ DEPENDENCIES
railties (~> 7.0)
rspec (~> 3.12)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -132,5 +132,8 @@ DEPENDENCIES
railties (~> 7.0)
rspec (~> 3.12)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -149,5 +149,8 @@ DEPENDENCIES
rspec (~> 3.12)
sqlite3
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -170,5 +170,8 @@ DEPENDENCIES
rspec (~> 3.12)
sqlite3
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -73,5 +73,8 @@ DEPENDENCIES
pry
rspec (~> 3.12)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -85,5 +85,8 @@ DEPENDENCIES
pry
rspec (~> 3.12)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -53,5 +53,8 @@ DEPENDENCIES
rspec (~> 3.12)
simplecov (~> 0.22)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -63,5 +63,8 @@ DEPENDENCIES
rspec (~> 3.12)
simplecov (~> 0.22)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -149,5 +149,8 @@ DEPENDENCIES
rspec
timecop
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -169,5 +169,8 @@ DEPENDENCIES
rspec
timecop
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -151,5 +151,8 @@ DEPENDENCIES
simplecov (~> 0.22)
thrift_client (= 0.9.3)!
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -170,5 +170,8 @@ DEPENDENCIES
simplecov (~> 0.22)
thrift_client (= 0.9.3)!
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -20,5 +20,8 @@ DEPENDENCIES
canvas_color!
rake
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -28,5 +28,8 @@ DEPENDENCIES
rake
simplecov (~> 0.22)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -61,5 +61,8 @@ DEPENDENCIES
rspec (~> 3.12)
simplecov (~> 0.22)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -149,5 +149,8 @@ DEPENDENCIES
debug
rspec
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -170,5 +170,8 @@ DEPENDENCIES
debug
rspec
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -48,5 +48,8 @@ DEPENDENCIES
rake
rspec (~> 3.12)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -58,5 +58,8 @@ DEPENDENCIES
rake
rspec (~> 3.12)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -174,5 +174,8 @@ DEPENDENCIES
rspec (~> 3.12)
webmock (~> 3.18)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -204,5 +204,8 @@ DEPENDENCIES
rspec (~> 3.12)
webmock (~> 3.18)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -191,5 +191,8 @@ DEPENDENCIES
rspec (~> 3.12)
webmock (~> 3.18)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -221,5 +221,8 @@ DEPENDENCIES
rspec (~> 3.12)
webmock (~> 3.18)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -43,5 +43,8 @@ DEPENDENCIES
rspec (~> 3.12)
simplecov (~> 0.22)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -201,5 +201,8 @@ DEPENDENCIES
rspec (~> 3.0)
webmock (~> 3.0)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -230,5 +230,8 @@ DEPENDENCIES
rspec (~> 3.0)
webmock (~> 3.0)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -117,5 +117,8 @@ DEPENDENCIES
rspec (~> 3.12)
simplecov (~> 0.22)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -149,5 +149,8 @@ DEPENDENCIES
rspec (~> 3.12)
simplecov (~> 0.22)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -112,5 +112,8 @@ DEPENDENCIES
rspec (~> 3.12)
simplecov (~> 0.22)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -122,5 +122,8 @@ DEPENDENCIES
rspec (~> 3.12)
simplecov (~> 0.22)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -76,5 +76,8 @@ DEPENDENCIES
rspec (~> 3.12)
simplecov (~> 0.22)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -219,5 +219,8 @@ DEPENDENCIES
rspec
timecop
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -238,5 +238,8 @@ DEPENDENCIES
rspec
timecop
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -37,5 +37,8 @@ DEPENDENCIES
rake
rspec (~> 3.12)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -35,5 +35,8 @@ DEPENDENCIES
rake
rspec (~> 3.12)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -68,5 +68,8 @@ DEPENDENCIES
simplecov (~> 0.22)
sqlite3
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -80,5 +80,8 @@ DEPENDENCIES
simplecov (~> 0.22)
sqlite3
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -47,5 +47,8 @@ DEPENDENCIES
rspec (~> 3.12)
simplecov (~> 0.22)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -50,5 +50,8 @@ DEPENDENCIES
rspec (~> 3.12)
timecop
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -60,5 +60,8 @@ DEPENDENCIES
rspec (~> 3.12)
timecop
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -56,5 +56,8 @@ DEPENDENCIES
rake
rspec (~> 3.12)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -66,5 +66,8 @@ DEPENDENCIES
rake
rspec (~> 3.12)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -113,5 +113,8 @@ DEPENDENCIES
debug
rspec
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -132,5 +132,8 @@ DEPENDENCIES
debug
rspec
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -43,5 +43,8 @@ DEPENDENCIES
rake
rspec (~> 3.12)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -49,5 +49,8 @@ DEPENDENCIES
rake
rspec (~> 3.12)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -64,5 +64,8 @@ DEPENDENCIES
rspec (~> 3.12)
rspec-mocks
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -136,5 +136,8 @@ DEPENDENCIES
rspec
timecop
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -155,5 +155,8 @@ DEPENDENCIES
rspec
timecop
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -198,5 +198,8 @@ DEPENDENCIES
sqlite3
thrift_client (= 0.9.3)!
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -230,5 +230,8 @@ DEPENDENCIES
sqlite3
thrift_client (= 0.9.3)!
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -113,5 +113,8 @@ DEPENDENCIES
timecop
webmock
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -88,5 +88,8 @@ DEPENDENCIES
rspec (~> 3.12)
simplecov (~> 0.22)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -98,5 +98,8 @@ DEPENDENCIES
rspec (~> 3.12)
simplecov (~> 0.22)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -84,5 +84,8 @@ DEPENDENCIES
rake
rspec (~> 3.12)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -94,5 +94,8 @@ DEPENDENCIES
rake
rspec (~> 3.12)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -109,5 +109,8 @@ DEPENDENCIES
simplecov (~> 0.22)
utf8_cleaner!
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -119,5 +119,8 @@ DEPENDENCIES
simplecov (~> 0.22)
utf8_cleaner!
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -250,5 +250,8 @@ DEPENDENCIES
utf8_cleaner!
webrick
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -269,5 +269,8 @@ DEPENDENCIES
utf8_cleaner!
webrick
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -37,5 +37,8 @@ DEPENDENCIES
rake
rspec (~> 3.12)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -53,5 +53,8 @@ DEPENDENCIES
rake
rspec (~> 3.12)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -105,5 +105,8 @@ DEPENDENCIES
rake
rspec (~> 3.12)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -117,5 +117,8 @@ DEPENDENCIES
rake
rspec (~> 3.12)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -71,5 +71,8 @@ DEPENDENCIES
redcarpet
rspec (~> 3.0)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -80,5 +80,8 @@ DEPENDENCIES
redcarpet
rspec (~> 3.0)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -68,5 +68,8 @@ DEPENDENCIES
rspec (~> 3.12)
simplecov (~> 0.22)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -78,5 +78,8 @@ DEPENDENCIES
rspec (~> 3.12)
simplecov (~> 0.22)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -47,5 +47,8 @@ DEPENDENCIES
rspec (~> 3.12)
sqlite3
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -223,5 +223,8 @@ DEPENDENCIES
rspec
timecop
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -242,5 +242,8 @@ DEPENDENCIES
rspec
timecop
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -163,5 +163,8 @@ DEPENDENCIES
rspec (~> 3.12)
rubocop-canvas!
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -185,5 +185,8 @@ DEPENDENCIES
rspec (~> 3.12)
rubocop-canvas!
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -33,5 +33,8 @@ DEPENDENCIES
rspec (~> 3.12)
stringify_ids!
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -36,5 +36,8 @@ DEPENDENCIES
tatl_tael!
timecop (~> 0.9)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -93,5 +93,8 @@ DEPENDENCIES
turnitin_api!
webmock (~> 3.0)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -104,5 +104,8 @@ DEPENDENCIES
turnitin_api!
webmock (~> 3.0)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -116,5 +116,8 @@ DEPENDENCIES
rspec (~> 3.12)
twitter!
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -126,5 +126,8 @@ DEPENDENCIES
rspec (~> 3.12)
twitter!
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -35,5 +35,8 @@ DEPENDENCIES
rspec (~> 3.12)
utf8_cleaner!
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -53,5 +53,8 @@ DEPENDENCIES
rspec (~> 3.12)
workflow!
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -65,5 +65,8 @@ DEPENDENCIES
rspec (~> 3.12)
workflow!
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.5.7

View File

@ -56,26 +56,24 @@ module Bundler
# allow short-form lockfile names
lockfile = expand_lockfile(lockfile)
if lockfile_definitions.find { |definition| definition[:lockfile] == lockfile }
raise ArgumentError, "Lockfile #{lockfile} is already defined"
end
raise ArgumentError, "Lockfile #{lockfile} is already defined" if lockfile_definitions.key?(lockfile)
env_lockfile = lockfile if active && ENV["BUNDLE_LOCKFILE"] == "active"
env_lockfile ||= ENV["BUNDLE_LOCKFILE"]&.then { |l| expand_lockfile(l) }
active = env_lockfile == lockfile if env_lockfile
if active && (old_active = lockfile_definitions.find { |definition| definition[:active] })
if active && (old_active = lockfile_definitions.each_value.find { |definition| definition[:active] })
raise ArgumentError, "Only one lockfile (#{old_active[:lockfile]}) can be flagged as active"
end
parent = expand_lockfile(parent)
if parent != Bundler.default_lockfile(force_original: true) &&
!lockfile_definitions.find { |definition| definition[:lockfile] == parent } &&
!lockfile_definitions.key?(parent) &&
!parent.exist?
raise ArgumentError, "Parent lockfile #{parent} is not defined"
end
lockfile_definitions << (lockfile_def = {
lockfile_definitions[lockfile] = (lockfile_def = {
gemfile: (gemfile && Bundler.root.join(gemfile).expand_path) || Bundler.default_gemfile,
lockfile: lockfile,
active: active,
@ -150,37 +148,52 @@ module Bundler
Bundler.ui.debug("Syncing to alternate lockfiles")
attempts = 1
previous_contents = Set.new
default_root = Bundler.root
checker = Check.new
cache = Cache.new
checker = Check.new(cache)
synced_any = false
local_parser_cache = {}
Bundler.settings.temporary(cache_all_platforms: true, suppress_install_using_messages: true) do
lockfile_definitions.each do |lockfile_definition|
lockfile_definitions.each do |lockfile_name, lockfile_definition|
# we already wrote the default lockfile
next if lockfile_definition[:lockfile] == Bundler.default_lockfile(force_original: true)
next if lockfile_name == Bundler.default_lockfile(force_original: true)
# root needs to be set so that paths are output relative to the correct root in the lockfile
Bundler.root = lockfile_definition[:gemfile].dirname
relative_lockfile = lockfile_definition[:lockfile].relative_path_from(Dir.pwd)
relative_lockfile = lockfile_name.relative_path_from(Dir.pwd)
# prevent infinite loops of tick-tocking back and forth between two versions
current_contents = cache.contents(lockfile_name)
if previous_contents.include?(current_contents)
Bundler.ui.debug("Unable to converge on a single solution for #{lockfile_name}; " \
"perhaps there are conflicting requirements?")
attempts = 1
previous_contents.clear
next
end
previous_contents << current_contents
# already up to date?
up_to_date = false
Bundler.settings.temporary(frozen: true) do
Bundler.ui.silence do
up_to_date = checker.base_check(lockfile_definition, check_missing_deps: true) &&
checker.check(lockfile_definition)
checker.deep_check(lockfile_definition)
end
end
if up_to_date
attempts = 1
previous_contents.clear
next
end
if Bundler.frozen_bundle?
# if we're frozen, you have to use the pre-existing lockfile
unless lockfile_definition[:lockfile].exist?
unless lockfile_name.exist?
Bundler.ui.error("The bundle is locked, but #{relative_lockfile} is missing. " \
"Please make sure you have checked #{relative_lockfile} " \
"into version control before deploying.")
@ -188,19 +201,19 @@ module Bundler
end
Bundler.ui.info("Installing gems for #{relative_lockfile}...")
write_lockfile(lockfile_definition, lockfile_definition[:lockfile], install: install)
write_lockfile(lockfile_definition, lockfile_name, cache, install: install)
else
Bundler.ui.info("Syncing to #{relative_lockfile}...") if attempts == 1
synced_any = true
parent = lockfile_definition[:parent]
parent_root = parent.dirname
checker.load_lockfile(parent)
parent_specs = checker.lockfile_specs[parent]
specs = lockfile_name.exist? ? cache.specs(lockfile_name) : {}
parent_lockfile_name = lockfile_definition[:parent]
parent_root = parent_lockfile_name.dirname
parent_specs = cache.specs(parent_lockfile_name)
# adjust locked paths from the parent lockfile to be relative to _this_ gemfile
adjusted_parent_lockfile_contents =
checker.lockfile_contents[parent].gsub(/PATH\n remote: ([^\n]+)\n/) do |remote|
cache.contents(parent_lockfile_name).gsub(/PATH\n remote: ([^\n]+)\n/) do |remote|
remote_path = Pathname.new($1)
next remote if remote_path.absolute?
@ -220,23 +233,60 @@ module Bundler
TEXT
end
if lockfile_definition[:lockfile].exist?
if lockfile_name.exist?
# if the lockfile already exists, "merge" it together
parent_lockfile = LockfileParser.new(adjusted_parent_lockfile_contents)
lockfile = LockfileParser.new(lockfile_definition[:lockfile].read)
parent_lockfile = if adjusted_parent_lockfile_contents == cache.contents(lockfile_name)
cache.parser(parent_lockfile_name)
else
local_parser_cache[adjusted_parent_lockfile_contents] ||=
LockfileParser.new(adjusted_parent_lockfile_contents)
end
lockfile = cache.parser(lockfile_name)
dependency_changes = false
# replace any duplicate specs with what's in the default lockfile
spec_precedences = {}
check_precedence = lambda do |spec, parent_spec|
next :parent if spec.nil?
next :self if parent_spec.nil?
next spec_precedences[spec.name] if spec_precedences.key?(spec.name)
precedence = :self if cache.conflicting_requirements?(lockfile_name,
parent_lockfile_name,
spec,
parent_spec)
# look through all reverse dependencies; if any of them say it
# has to come from self, due to conflicts, then this gem has
# to come from self as well
[cache.reverse_dependencies(lockfile_name),
cache.reverse_dependencies(parent_lockfile_name)].each do |reverse_dependencies|
break if precedence == :self
reverse_dependencies[spec.name].each do |dep_name|
precedence = check_precedence.call(specs[dep_name], parent_specs[dep_name])
break if precedence == :self
end
end
spec_precedences[spec.name] = precedence || :parent
end
# replace any duplicate specs with what's in the parent lockfile
lockfile.specs.map! do |spec|
parent_spec = parent_specs[[spec.name, spec.platform]]
next spec unless parent_spec
next spec if check_precedence.call(spec, parent_spec) == :self
dependency_changes ||= spec != parent_spec
parent_spec
new_spec = parent_spec.dup
new_spec.source = spec.source
new_spec
end
lockfile.specs.replace(parent_lockfile.specs + lockfile.specs).uniq!
lockfile.sources.replace(parent_lockfile.sources + lockfile.sources).uniq!
lockfile.platforms.replace(parent_lockfile.platforms).uniq!
# prune more specific platforms
lockfile.platforms.delete_if do |p1|
@ -244,9 +294,9 @@ module Bundler
p2 != "ruby" && p1 != p2 && MatchPlatform.platforms_match?(p2, p1)
end
end
lockfile.instance_variable_set(:@ruby_version, parent_lockfile.ruby_version)
lockfile.instance_variable_set(:@ruby_version, parent_lockfile.ruby_version) if lockfile.ruby_version
unless lockfile.bundler_version == parent_lockfile.bundler_version
unlocking_bundler = true
unlocking_bundler = parent_lockfile.bundler_version
lockfile.instance_variable_set(:@bundler_version, parent_lockfile.bundler_version)
end
@ -263,24 +313,27 @@ module Bundler
temp_lockfile.write(new_contents)
temp_lockfile.flush
had_changes = write_lockfile(lockfile_definition,
temp_lockfile.path,
install: install,
dependency_changes: dependency_changes,
unlocking_bundler: unlocking_bundler)
had_changes ||= write_lockfile(lockfile_definition,
temp_lockfile.path,
cache,
install: install,
dependency_changes: dependency_changes,
unlocking_bundler: unlocking_bundler)
end
cache.invalidate_lockfile(lockfile_name) if had_changes
# if we had changes, bundler may have updated some common
# dependencies beyond the default lockfile, so re-run it
# once to reset them back to the default lockfile's version.
# if it's already good, the `check` check at the beginning of
# the loop will skip the second sync anyway.
if had_changes && attempts < 2
if had_changes
attempts += 1
Bundler.ui.debug("Re-running sync to #{relative_lockfile} to reset common dependencies")
redo
else
attempts = 1
previous_contents.clear
end
end
end
@ -300,7 +353,7 @@ module Bundler
@loaded = true
return if lockfile_definitions.empty?
return unless lockfile_definitions.none? { |definition| definition[:active] }
return unless lockfile_definitions.each_value.none? { |definition| definition[:active] }
if ENV["BUNDLE_LOCKFILE"]&.then { |l| expand_lockfile(l) } ==
Bundler.default_lockfile(force_original: true)
@ -310,9 +363,7 @@ module Bundler
raise GemfileNotFound, "Could not locate lockfile #{ENV["BUNDLE_LOCKFILE"].inspect}" if ENV["BUNDLE_LOCKFILE"]
# Gemfile.lock isn't explicitly specified, otherwise it would be active
default_lockfile_definition = lockfile_definitions.find do |definition|
definition[:lockfile] == Bundler.default_lockfile(force_original: true)
end
default_lockfile_definition = self.default_lockfile_definition
return unless default_lockfile_definition && default_lockfile_definition[:active] == false
raise GemfileEvalError, "No lockfiles marked as active"
@ -377,10 +428,15 @@ module Bundler
# @!visibility private
def reset!
@lockfile_definitions = []
@lockfile_definitions = {}
@loaded = false
end
# @!visibility private
def default_lockfile_definition
lockfile_definitions[Bundler.default_lockfile(force_original: true)]
end
private
def expand_lockfile(lockfile)
@ -406,7 +462,12 @@ module Bundler
true
end
def write_lockfile(lockfile_definition, lockfile, install:, dependency_changes: false, unlocking_bundler: false)
def write_lockfile(lockfile_definition,
lockfile,
cache,
install:,
dependency_changes: false,
unlocking_bundler: false)
prepare_block = lockfile_definition[:prepare]
gemfile = lockfile_definition[:gemfile]
@ -415,6 +476,12 @@ module Bundler
builder = Dsl.new
builder.eval_gemfile(gemfile, &prepare_block) if prepare_block
builder.eval_gemfile(gemfile)
if !builder.instance_variable_get(:@ruby_version) &&
(parent_lockfile = lockfile_definition[:parent]) &&
(parent_lockfile_definition = lockfile_definitions[parent_lockfile]) &&
(parent_ruby_version_requirement = parent_lockfile_definition[:ruby_version_requirement])
builder.instance_variable_set(:@ruby_version, parent_ruby_version_requirement)
end
definition = builder.to_definition(lockfile, { bundler: unlocking_bundler })
definition.instance_variable_set(:@dependency_changes, dependency_changes) if dependency_changes
@ -436,11 +503,12 @@ module Bundler
current_definition.resolve_with_cache!
if current_definition.missing_specs.any?
cache.invalidate_checks(current_lockfile)
Bundler.with_default_lockfile(current_lockfile) do
Installer.install(gemfile.dirname, current_definition, {})
end
end
rescue RubyVersionMismatch, GemNotFound, SolveFailure
rescue RubyVersionMismatch, GemNotFound, SolveFailure, InstallError, ProductionError
# ignore
end
end
@ -470,11 +538,16 @@ module Bundler
resolved_remotely = true
end
SharedHelpers.capture_filesystem_access do
definition.instance_variable_set(:@resolved_bundler_version, unlocking_bundler) if unlocking_bundler
# need to force it to _not_ preserve unknown sections, so that it
# will overwrite the ruby version
definition.instance_variable_set(:@unlocking_bundler, true)
if Bundler.gem_version >= Gem::Version.new("2.5.6")
definition.instance_variable_set(:@lockfile, lockfile_definition[:lockfile])
definition.lock(true)
definition.lock
else
definition.lock(lockfile_definition[:lockfile], true)
definition.lock(lockfile_definition[:lockfile])
end
end
ensure

View File

@ -0,0 +1,139 @@
# frozen_string_literal: true
require_relative "ui/capture"
module Bundler
module Multilock
# caches lockfiles across multiple lockfile checks or sync runs
class Cache
def initialize
@contents = {}
@parsers = {}
@specs = {}
@reverse_dependencies = {}
@reverse_requirements = {}
@base_checks = {}
@deep_checks = {}
@base_check_messages = {}
@deep_check_messages = {}
@missing_specs = Set.new
@logged_missing = false
end
# Removes a given lockfile's associated cached data
#
# Should be called if the lockfile is modified
# @param lockfile_name [Pathname]
# @return [void]
def invalidate_lockfile(lockfile_name)
@contents.delete(lockfile_name)
@parsers.delete(lockfile_name)
@specs.delete(lockfile_name)
@reverse_dependencies.delete(lockfile_name)
@reverse_requirements.delete(lockfile_name)
invalidate_checks(lockfile_name)
end
def invalidate_checks(lockfile_name)
@base_checks.delete(lockfile_name)
@base_check_messages.delete(lockfile_name)
# must clear them all; downstream lockfiles may depend on the state of this lockfile
@deep_checks.clear
@deep_check_messages.clear
end
# @param lockfile_name [Pathname]
# @return [String] the raw contents of the lockfile
def contents(lockfile_name)
@contents.fetch(lockfile_name) do
@contents[lockfile_name] = lockfile_name.file? && lockfile_name.read.freeze
end
end
# @param lockfile_name [Pathname]
# @return [LockfileParser]
def parser(lockfile_name)
@parsers[lockfile_name] ||= LockfileParser.new(contents(lockfile_name))
end
def specs(lockfile_name)
@specs[lockfile_name] ||= parser(lockfile_name).specs.to_h do |spec|
[[spec.name, spec.platform], spec]
end
end
# @param lockfile_name [Pathname]
# @return [Hash<String, Set<String>>] hash of gem name to set of gem names that depend on it
def reverse_dependencies(lockfile_name)
ensure_reverse_data(lockfile_name)
@reverse_dependencies[lockfile_name]
end
# @param lockfile_name [Pathname]
# @return [Hash<String, Gem::Requirement>] hash of gem name to requirement for that gem
def reverse_requirements(lockfile_name)
ensure_reverse_data(lockfile_name)
@reverse_requirements[lockfile_name]
end
def conflicting_requirements?(lockfile1_name, lockfile2_name, spec1, spec2)
reverse_requirements1 = reverse_requirements(lockfile1_name)[spec1.name]
reverse_requirements2 = reverse_requirements(lockfile2_name)[spec1.name]
!reverse_requirements1.satisfied_by?(spec2.version) &&
!reverse_requirements2.satisfied_by?(spec1.version)
end
def log_missing_spec(spec)
return if @missing_specs.include?(spec)
Bundler.ui.error "The following gems are missing" if @missing_specs.empty?
@missing_specs << spec
Bundler.ui.error(" * #{spec.name} (#{spec.version})")
end
%i[base deep].each do |type|
class_eval <<~RUBY, __FILE__, __LINE__ + 1 # rubocop:disable Style/DocumentDynamicEvalDefinition
def #{type}_check(lockfile_name)
if @#{type}_checks.key?(lockfile_name)
@#{type}_check_messages[lockfile_name].replay
@#{type}_checks[lockfile_name]
else
result = nil
messages = Bundler::Multilock::UI::Capture.capture do
result = @#{type}_checks[lockfile_name] = yield
end
@#{type}_check_messages[lockfile_name] = messages.tap(&:replay)
result
end
end
RUBY
end
private
def ensure_reverse_data(lockfile_name)
return if @reverse_requirements.key?(lockfile_name)
# can use Gem::Requirement.default_prelease when Ruby 2.6 support is dropped
reverse_requirements = Hash.new { |h, k| h[k] = Gem::Requirement.new(">= 0.a") }
reverse_dependencies = Hash.new { |h, k| h[k] = Set.new }
lockfile = parser(lockfile_name)
lockfile.dependencies.each_value do |dep|
reverse_requirements[dep.name].requirements.concat(dep.requirement.requirements)
end
lockfile.specs.each do |spec|
spec.dependencies.each do |dep|
reverse_requirements[dep.name].requirements.concat(dep.requirement.requirements)
reverse_dependencies[dep.name] << spec.name
end
end
@reverse_requirements[lockfile_name] = reverse_requirements
@reverse_dependencies[lockfile_name] = reverse_dependencies
end
end
end
end

View File

@ -2,31 +2,19 @@
require "set"
require_relative "cache"
module Bundler
module Multilock
class Check
attr_reader :lockfiles, :lockfile_contents, :lockfile_specs
class << self
def run
new.run
end
end
def initialize
@lockfiles = {}
@lockfile_contents = {}
@lockfile_specs = {}
end
def load_lockfile(lockfile)
return if lockfile_contents.key?(lockfile)
contents = lockfile_contents[lockfile] = lockfile.read.freeze
parser = lockfiles[lockfile] = LockfileParser.new(contents)
lockfile_specs[lockfile] = parser.specs.to_h do |spec|
[[spec.name, spec.platform], spec]
end
def initialize(cache = Cache.new)
@cache = cache
end
def run(skip_base_checks: false)
@ -34,177 +22,171 @@ module Bundler
success = true
unless skip_base_checks
missing_specs = base_check({ gemfile: Bundler.default_gemfile,
lockfile: Bundler.default_lockfile(force_original: true) },
return_missing: true).to_set
default_lockfile_definition = Multilock.default_lockfile_definition
default_lockfile_definition ||= { gemfile: Bundler.default_gemfile,
lockfile: Bundler.default_lockfile(force_original: true) }
base_check(default_lockfile_definition)
end
Multilock.lockfile_definitions.each do |lockfile_definition|
next if lockfile_definition[:lockfile] == Bundler.default_lockfile(force_original: true)
Multilock.lockfile_definitions.each do |lockfile_name, lockfile_definition|
next if lockfile_name == Bundler.default_lockfile(force_original: true)
unless lockfile_definition[:lockfile].exist?
Bundler.ui.error("Lockfile #{lockfile_definition[:lockfile]} does not exist.")
unless lockfile_name.exist?
Bundler.ui.error("Lockfile #{lockfile_name} does not exist.")
success = false
next
end
unless skip_base_checks
new_missing = base_check(lockfile_definition, log_missing: missing_specs, return_missing: true)
success = false unless new_missing.empty?
missing_specs.merge(new_missing)
end
success = false unless check(lockfile_definition)
success &&= base_check(lockfile_definition) && deep_check(lockfile_definition)
end
success
end
# this is mostly equivalent to the built in checks in `bundle check`, but even
# more conservative, and returns false instead of exiting on failure
def base_check(lockfile_definition, log_missing: false, return_missing: false, check_missing_deps: false)
return return_missing ? [] : false unless lockfile_definition[:lockfile].file?
def base_check(lockfile_definition, check_missing_deps: false)
lockfile_name = lockfile_definition[:lockfile]
default_root = Bundler.root
Multilock.prepare_block = lockfile_definition[:prepare]
definition = Definition.build(lockfile_definition[:gemfile], lockfile_definition[:lockfile], false)
return return_missing ? [] : false unless definition.send(:current_platform_locked?)
result = @cache.base_check(lockfile_name) do
next false unless lockfile_name.file?
begin
definition.validate_runtime!
not_installed = Bundler.ui.silence { definition.missing_specs }
rescue RubyVersionMismatch, GemNotFound, SolveFailure
return return_missing ? [] : false
end
Multilock.prepare_block = lockfile_definition[:prepare]
# root needs to be set so that paths are output relative to the correct root in the lockfile
Bundler.root = lockfile_definition[:gemfile].dirname
if log_missing
not_installed.each do |spec|
next if log_missing.include?(spec)
definition = Definition.build(lockfile_definition[:gemfile], lockfile_name, false)
next false unless definition.send(:current_platform_locked?)
Bundler.ui.error "The following gems are missing" if log_missing.empty?
Bundler.ui.error(" * #{spec.name} (#{spec.version})")
begin
definition.validate_runtime!
not_installed = Bundler.ui.silence { definition.missing_specs }
rescue RubyVersionMismatch, GemNotFound, SolveFailure
next false
end
if Bundler.ui.error?
not_installed.each do |spec|
@cache.log_missing_spec(spec)
end
end
next false unless not_installed.empty?
# cache a sentinel so that we can share a cache regardless of the check_missing_deps argument
next :missing_deps unless (definition.locked_gems.dependencies.values - definition.dependencies).empty?
true
end
return not_installed if return_missing
return !check_missing_deps if result == :missing_deps
return false unless not_installed.empty? && definition.no_resolve_needed?
return true unless check_missing_deps
(definition.locked_gems.dependencies.values - definition.dependencies).empty?
result
ensure
Multilock.prepare_block = nil
Bundler.root = default_root
end
# this checks for mismatches between the parent lockfile and the given lockfile,
# and for pinned dependencies in lockfiles requiring them
def check(lockfile_definition)
success = true
proven_pinned = Set.new
needs_pin_check = []
lockfile = LockfileParser.new(lockfile_definition[:lockfile].read)
lockfile_path = lockfile_definition[:lockfile].relative_path_from(Dir.pwd)
parent = lockfile_definition[:parent]
load_lockfile(parent)
parent_lockfile = lockfiles[parent]
unless lockfile.platforms == parent_lockfile.platforms
Bundler.ui.error("The platforms in #{lockfile_path} do not match the parent lockfile.")
success = false
end
unless lockfile.bundler_version == parent_lockfile.bundler_version
Bundler.ui.error("bundler (#{lockfile.bundler_version}) in #{lockfile_path} " \
"does not match the parent lockfile's version (@#{parent_lockfile.bundler_version}).")
success = false
end
reverse_dependencies = cache_reverse_dependencies(lockfile)
parent_reverse_dependencies = cache_reverse_dependencies(parent_lockfile)
# look through top-level explicit dependencies for pinned requirements
if lockfile_definition[:enforce_pinned_additional_dependencies]
find_pinned_dependencies(proven_pinned, lockfile.dependencies.each_value)
end
# check for conflicting requirements (and build list of pins, in the same loop)
lockfile.specs.each do |spec|
parent_spec = lockfile_specs[parent][[spec.name, spec.platform]]
def deep_check(lockfile_definition)
lockfile_name = lockfile_definition[:lockfile]
@cache.deep_check(lockfile_name) do
success = true
proven_pinned = Set.new
needs_pin_check = []
parser = @cache.parser(lockfile_name)
lockfile_path = lockfile_name.relative_path_from(Dir.pwd)
parent_lockfile_name = lockfile_definition[:parent]
parent_parser = @cache.parser(parent_lockfile_name)
unless parser.platforms == parent_parser.platforms
Bundler.ui.error("The platforms in #{lockfile_path} do not match the parent lockfile.")
success = false
end
unless parser.bundler_version == parent_parser.bundler_version
Bundler.ui.error("bundler (#{parser.bundler_version}) in #{lockfile_path} " \
"does not match the parent lockfile's version (@#{parent_parser.bundler_version}).")
success = false
end
unless parser.ruby_version == parent_parser.ruby_version
Bundler.ui.error("ruby (#{parser.ruby_version || "<none>"}) in #{lockfile_path} " \
"does not match the parent lockfile's version (#{parent_parser.ruby_version}).")
success = false
end
# look through top-level explicit dependencies for pinned requirements
if lockfile_definition[:enforce_pinned_additional_dependencies]
# look through what this spec depends on, and keep track of all pinned requirements
find_pinned_dependencies(proven_pinned, spec.dependencies)
needs_pin_check << spec unless parent_spec
find_pinned_dependencies(proven_pinned, parser.dependencies.each_value)
end
next unless parent_spec
# check for conflicting requirements (and build list of pins, in the same loop)
parser.specs.each do |spec|
parent_spec = @cache.specs(parent_lockfile_name)[[spec.name, spec.platform]]
# have to ensure Path sources are relative to their lockfile before comparing
same_source = if [parent_spec.source, spec.source].grep(Source::Path).length == 2
lockfile_definition[:lockfile]
.dirname
.join(spec.source.path)
.ascend
.any?(parent.dirname.join(parent_spec.source.path))
else
parent_spec.source == spec.source
end
if lockfile_definition[:enforce_pinned_additional_dependencies]
# look through what this spec depends on, and keep track of all pinned requirements
find_pinned_dependencies(proven_pinned, spec.dependencies)
next if parent_spec.version == spec.version && same_source
needs_pin_check << spec unless parent_spec
end
# the version in the parent lockfile cannot possibly satisfy the requirements
# in this lockfile, and vice versa, so we assume it's intentional and allow it
unless reverse_dependencies[spec.name].satisfied_by?(parent_spec.version) ||
parent_reverse_dependencies[spec.name].satisfied_by?(spec.version)
# we're allowing it to differ from the parent, so pin check requirement comes into play
needs_pin_check << spec if lockfile_definition[:enforce_pinned_additional_dependencies]
next
next unless parent_spec
# have to ensure Path sources are relative to their lockfile before comparing
same_source = if [parent_spec.source, spec.source].grep(Source::Path).length == 2
lockfile_name
.dirname
.join(spec.source.path)
.ascend
.any?(parent_lockfile_name.dirname.join(parent_spec.source.path))
else
parent_spec.source == spec.source
end
next if parent_spec.version == spec.version && same_source
# the version in the parent lockfile cannot possibly satisfy the requirements
# in this lockfile, and vice versa, so we assume it's intentional and allow it
if @cache.conflicting_requirements?(lockfile_name, parent_lockfile_name, spec, parent_spec)
# we're allowing it to differ from the parent, so pin check requirement comes into play
needs_pin_check << spec if lockfile_definition[:enforce_pinned_additional_dependencies]
next
end
Bundler.ui.error("#{spec}#{spec.git_version} in #{lockfile_path} " \
"does not match the parent lockfile's version " \
"(@#{parent_spec.version}#{parent_spec.git_version}); " \
"this may be due to a conflicting requirement, which would require manual resolution.")
success = false
end
Bundler.ui.error("#{spec}#{spec.git_version} in #{lockfile_path} " \
"does not match the parent lockfile's version " \
"(@#{parent_spec.version}#{parent_spec.git_version}); " \
"this may be due to a conflicting requirement, which would require manual resolution.")
success = false
# now that we have built a list of every gem that is pinned, go through
# the gems that were in this lockfile, but not the parent lockfile, and
# ensure it's pinned _somehow_
needs_pin_check.each do |spec|
pinned = case spec.source
when Source::Git
spec.source.ref == spec.source.revision
when Source::Path
true
when Source::Rubygems
proven_pinned.include?(spec.name)
else
false
end
next if pinned
Bundler.ui.error("#{spec} in #{lockfile_path} has not been pinned to a specific version, " \
"which is required since it is not part of the parent lockfile.")
success = false
end
success
end
# now that we have built a list of every gem that is pinned, go through
# the gems that were in this lockfile, but not the parent lockfile, and
# ensure it's pinned _somehow_
needs_pin_check.each do |spec|
pinned = case spec.source
when Source::Git
spec.source.ref == spec.source.revision
when Source::Path
true
when Source::Rubygems
proven_pinned.include?(spec.name)
else
false
end
next if pinned
Bundler.ui.error("#{spec} in #{lockfile_path} has not been pinned to a specific version, " \
"which is required since it is not part of the parent lockfile.")
success = false
end
success
end
private
def cache_reverse_dependencies(lockfile)
# can use Gem::Requirement.default_prelease when Ruby 2.6 support is dropped
reverse_dependencies = Hash.new { |h, k| h[k] = Gem::Requirement.new(">= 0.a") }
lockfile.dependencies.each_value do |spec|
reverse_dependencies[spec.name].requirements.concat(spec.requirement.requirements)
end
lockfile.specs.each do |spec|
spec.dependencies.each do |dependency|
reverse_dependencies[dependency.name].requirements.concat(dependency.requirement.requirements)
end
end
reverse_dependencies
end
def find_pinned_dependencies(proven_pinned, dependencies)
dependencies.each do |dependency|
dependency.requirement.requirements.each do |requirement|

View File

@ -11,12 +11,22 @@ module Bundler
# Significant changes:
# * evaluate the prepare block as part of the gemfile
# * keep track of the ruby version set in the default gemfile
# * apply that ruby version to alternate lockfiles if they didn't set one
# themselves
# * mark Multilock as loaded once the main gemfile is evaluated
# so that they're not loaded multiple times
def evaluate(gemfile, lockfile, unlock)
builder = new
builder.eval_gemfile(gemfile, &Multilock.prepare_block) if Multilock.prepare_block
builder.eval_gemfile(gemfile)
if (ruby_version_requirement = builder.instance_variable_get(:@ruby_version))
Multilock.lockfile_definitions[lockfile][:ruby_version_requirement] = ruby_version_requirement
elsif (parent_lockfile = Multilock.lockfile_definitions.dig(lockfile, :parent)) &&
(parent_lockfile_definition = Multilock.lockfile_definitions[parent_lockfile]) &&
(parent_ruby_version_requirement = parent_lockfile_definition[:ruby_version_requirement])
builder.instance_variable_set(:@ruby_version, parent_ruby_version_requirement)
end
Multilock.loaded!
builder.to_definition(lockfile, unlock)
end

View File

@ -0,0 +1,53 @@
# frozen_string_literal: true
module Bundler
module Multilock
module UI
class Capture < Bundler::UI::Silent
class << self
def capture
original_ui = Bundler.ui
Bundler.ui = new
yield
Bundler.ui
ensure
Bundler.ui = original_ui
end
end
def initialize
@messages = []
super
end
def replay
@messages.each do |(level, args)|
Bundler.ui.send(level, *args)
end
nil
end
def add_color(string, _color)
string
end
%i[info confirm warn error debug].each do |level|
class_eval <<~RUBY, __FILE__, __LINE__ + 1
def #{level}(message = nil, newline = nil) # def info(message = nil, newline = nil)
@messages << [:#{level}, [message, newline]] # @messages << [:info, [message, newline]]
end # end
#
def #{level}? # def info?
true # true
end # end
RUBY
def trace(message, newline = nil, force = false) # rubocop:disable Style/OptionalBooleanParameter
@messages << [:trace, [message, newline, force]]
end
end
end
end
end
end

View File

@ -2,6 +2,6 @@
module Bundler
module Multilock
VERSION = "1.2.3"
VERSION = "1.3.0"
end
end