diff options
Diffstat (limited to 'lib/bundler/lockfile_parser.rb')
-rw-r--r-- | lib/bundler/lockfile_parser.rb | 208 |
1 files changed, 137 insertions, 71 deletions
diff --git a/lib/bundler/lockfile_parser.rb b/lib/bundler/lockfile_parser.rb index 6ff4910a36..1e11621e55 100644 --- a/lib/bundler/lockfile_parser.rb +++ b/lib/bundler/lockfile_parser.rb @@ -2,18 +2,49 @@ module Bundler class LockfileParser - attr_reader :sources, :dependencies, :specs, :platforms, :bundler_version, :ruby_version - - BUNDLED = "BUNDLED WITH".freeze - DEPENDENCIES = "DEPENDENCIES".freeze - PLATFORMS = "PLATFORMS".freeze - RUBY = "RUBY VERSION".freeze - GIT = "GIT".freeze - GEM = "GEM".freeze - PATH = "PATH".freeze - PLUGIN = "PLUGIN SOURCE".freeze - SPECS = " specs:".freeze - OPTIONS = /^ ([a-z]+): (.*)$/i.freeze + class Position + attr_reader :line, :column + def initialize(line, column) + @line = line + @column = column + end + + def advance!(string) + lines = string.count("\n") + if lines > 0 + @line += lines + @column = string.length - string.rindex("\n") + else + @column += string.length + end + end + + def to_s + "#{line}:#{column}" + end + end + + attr_reader( + :sources, + :dependencies, + :specs, + :platforms, + :bundler_version, + :ruby_version, + :checksums, + ) + + BUNDLED = "BUNDLED WITH" + DEPENDENCIES = "DEPENDENCIES" + CHECKSUMS = "CHECKSUMS" + PLATFORMS = "PLATFORMS" + RUBY = "RUBY VERSION" + GIT = "GIT" + GEM = "GEM" + PATH = "PATH" + PLUGIN = "PLUGIN SOURCE" + SPECS = " specs:" + OPTIONS = /^ ([a-z]+): (.*)$/i SOURCE = [GIT, GEM, PATH, PLUGIN].freeze SECTIONS_BY_VERSION_INTRODUCED = { @@ -21,14 +52,18 @@ module Bundler Gem::Version.create("1.10") => [BUNDLED].freeze, Gem::Version.create("1.12") => [RUBY].freeze, Gem::Version.create("1.13") => [PLUGIN].freeze, + Gem::Version.create("2.5.0") => [CHECKSUMS].freeze, }.freeze - KNOWN_SECTIONS = SECTIONS_BY_VERSION_INTRODUCED.values.flatten.freeze + KNOWN_SECTIONS = SECTIONS_BY_VERSION_INTRODUCED.values.flatten!.freeze ENVIRONMENT_VERSION_SECTIONS = [BUNDLED, RUBY].freeze + deprecate_constant(:ENVIRONMENT_VERSION_SECTIONS) def self.sections_in_lockfile(lockfile_contents) - lockfile_contents.scan(/^\w[\w ]*$/).uniq + sections = lockfile_contents.scan(/^\w[\w ]*$/) + sections.uniq! + sections end def self.unknown_sections_in_lockfile(lockfile_contents) @@ -37,7 +72,7 @@ module Bundler def self.sections_to_ignore(base_version = nil) base_version &&= base_version.release - base_version ||= Gem::Version.create("1.0".dup) + base_version ||= Gem::Version.create("1.0") attributes = [] SECTIONS_BY_VERSION_INTRODUCED.each do |version, introduced| next if version <= base_version @@ -46,83 +81,88 @@ module Bundler attributes end + def self.bundled_with + lockfile = Bundler.default_lockfile + return unless lockfile.file? + + lockfile_contents = Bundler.read_file(lockfile) + return unless lockfile_contents.include?(BUNDLED) + + lockfile_contents.split(BUNDLED).last.strip + end + def initialize(lockfile) @platforms = [] @sources = [] @dependencies = {} - @state = nil + @parse_method = nil @specs = {} + @lockfile_path = begin + SharedHelpers.relative_lockfile_path + rescue GemfileNotFound + "Gemfile.lock" + end + @pos = Position.new(1, 1) - if lockfile.match(/<<<<<<<|=======|>>>>>>>|\|\|\|\|\|\|\|/) - raise LockfileError, "Your #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} contains merge conflicts.\n" \ - "Run `git checkout HEAD -- #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}` first to get a clean lock." + if lockfile.match?(/<<<<<<<|=======|>>>>>>>|\|\|\|\|\|\|\|/) + raise LockfileError, "Your #{@lockfile_path} contains merge conflicts.\n" \ + "Run `git checkout HEAD -- #{@lockfile_path}` first to get a clean lock." end - lockfile.split(/(?:\r?\n)+/).each do |line| + lockfile.split(/((?:\r?\n)+)/) do |line| + # split alternates between the line and the following whitespace + next @pos.advance!(line) if line.match?(/^\s*$/) + if SOURCE.include?(line) - @state = :source + @parse_method = :parse_source parse_source(line) elsif line == DEPENDENCIES - @state = :dependency + @parse_method = :parse_dependency + elsif line == CHECKSUMS + # This is a temporary solution to make this feature disabled by default + # for all gemfiles that don't already explicitly include the feature. + @checksums = true + @parse_method = :parse_checksum elsif line == PLATFORMS - @state = :platform + @parse_method = :parse_platform elsif line == RUBY - @state = :ruby + @parse_method = :parse_ruby elsif line == BUNDLED - @state = :bundled_with - elsif line =~ /^[^\s]/ - @state = nil - elsif @state - send("parse_#{@state}", line) + @parse_method = :parse_bundled_with + elsif /^[^\s]/.match?(line) + @parse_method = nil + elsif @parse_method + send(@parse_method, line) end + @pos.advance!(line) end - @specs = @specs.values.sort_by(&:identifier) - warn_for_outdated_bundler_version + @specs = @specs.values.sort_by!(&:full_name) rescue ArgumentError => e Bundler.ui.debug(e) - raise LockfileError, "Your lockfile is unreadable. Run `rm #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}` " \ - "and then `bundle install` to generate a new lockfile." + raise LockfileError, "Your lockfile is unreadable. Run `rm #{@lockfile_path}` " \ + "and then `bundle install` to generate a new lockfile. The error occurred while " \ + "evaluating #{@lockfile_path}:#{@pos}" end - def warn_for_outdated_bundler_version - return unless bundler_version - return if bundler_version.segments.last == "dev" - prerelease_text = bundler_version.prerelease? ? " --pre" : "" - current_version = Gem::Version.create(Bundler::VERSION) - return unless current_version < bundler_version - Bundler.ui.warn "Warning: the running version of Bundler (#{current_version}) is older " \ - "than the version that created the lockfile (#{bundler_version}). We suggest you to " \ - "upgrade to the version that created the lockfile by running `gem install " \ - "bundler:#{bundler_version}#{prerelease_text}`.\n" + def may_include_redundant_platform_specific_gems? + bundler_version.nil? || bundler_version < Gem::Version.new("1.16.2") end private TYPES = { - GIT => Bundler::Source::Git, - GEM => Bundler::Source::Rubygems, - PATH => Bundler::Source::Path, + GIT => Bundler::Source::Git, + GEM => Bundler::Source::Rubygems, + PATH => Bundler::Source::Path, PLUGIN => Bundler::Plugin, }.freeze def parse_source(line) case line when SPECS - case @type - when PATH - @current_source = TYPES[@type].from_lock(@opts) - @sources << @current_source - when GIT - @current_source = TYPES[@type].from_lock(@opts) - @sources << @current_source - when GEM - @opts["remotes"] = Array(@opts.delete("remote")).reverse - @current_source = TYPES[@type].from_lock(@opts) - @sources << @current_source - when PLUGIN - @current_source = Plugin.source_from_lock(@opts) - @sources << @current_source - end + return unless TYPES.key?(@type) + @current_source = TYPES[@type].from_lock(@opts) + @sources << @current_source when OPTIONS value = $2 value = true if value == "true" @@ -152,18 +192,19 @@ module Bundler (?:#{space}\(([^-]*) # Space, followed by version (?:-(.*))?\))? # Optional platform (!)? # Optional pinned marker + (?:#{space}([^ ]+))? # Optional checksum $ # Line end - /xo.freeze + /xo def parse_dependency(line) return unless line =~ NAME_VERSION spaces = $1 return unless spaces.size == 2 - name = $2 + name = -$2 version = $3 pinned = $5 - version = version.split(",").map(&:strip) if version + version = version.split(",").each(&:strip!) if version dep = Bundler::Dependency.new(name, version) @@ -184,23 +225,47 @@ module Bundler @dependencies[dep.name] = dep end - def parse_spec(line) + def parse_checksum(line) return unless line =~ NAME_VERSION + spaces = $1 + return unless spaces.size == 2 + checksums = $6 + return unless checksums name = $2 version = $3 platform = $4 + version = Gem::Version.new(version) + platform = platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY + full_name = Gem::NameTuple.new(name, version, platform).full_name + return unless spec = @specs[full_name] + + checksums.split(",") do |lock_checksum| + column = line.index(lock_checksum) + 1 + checksum = Checksum.from_lock(lock_checksum, "#{@lockfile_path}:#{@pos.line}:#{column}") + spec.source.checksum_store.register(spec, checksum) + end + end + + def parse_spec(line) + return unless line =~ NAME_VERSION + spaces = $1 + name = -$2 + version = $3 + if spaces.size == 4 + # only load platform for non-dependency (spec) line + platform = $4 + version = Gem::Version.new(version) platform = platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY - @current_spec = LazySpecification.new(name, version, platform) - @current_spec.source = @current_source + @current_spec = LazySpecification.new(name, version, platform, @current_source) @current_source.add_dependency_names(name) - @specs[@current_spec.identifier] = @current_spec + @specs[@current_spec.full_name] = @current_spec elsif spaces.size == 6 - version = version.split(",").map(&:strip) if version + version = version.split(",").each(&:strip!) if version dep = Gem::Dependency.new(name, version) @current_spec.dependencies << dep end @@ -211,13 +276,14 @@ module Bundler end def parse_bundled_with(line) - line = line.strip + line.strip! return unless Gem::Version.correct?(line) @bundler_version = Gem::Version.create(line) end def parse_ruby(line) - @ruby_version = line.strip + line.strip! + @ruby_version = line end end end |