ÿØÿà JFIF    ÿÛ „ ( %!1!%*+...983,7(-.- PKSe](ÑŠ –3–3 lockfile.rbnu„[µü¤require 'strscan' ## # Parses a gem.deps.rb.lock file and constructs a LockSet containing the # dependencies found inside. If the lock file is missing no LockSet is # constructed. class Gem::RequestSet::Lockfile ## # Raised when a lockfile cannot be parsed class ParseError < Gem::Exception ## # The column where the error was encountered attr_reader :column ## # The line where the error was encountered attr_reader :line ## # The location of the lock file attr_reader :path ## # Raises a ParseError with the given +message+ which was encountered at a # +line+ and +column+ while parsing. def initialize message, column, line, path @line = line @column = column @path = path super "#{message} (at line #{line} column #{column})" end end ## # The platforms for this Lockfile attr_reader :platforms ## # Creates a new Lockfile for the given +request_set+ and +gem_deps_file+ # location. def initialize request_set, gem_deps_file @set = request_set @gem_deps_file = File.expand_path(gem_deps_file) @gem_deps_dir = File.dirname(@gem_deps_file) @current_token = nil @line = 0 @line_pos = 0 @platforms = [] @tokens = [] end def add_DEPENDENCIES out # :nodoc: out << "DEPENDENCIES" @requests.sort_by { |r| r.name }.each do |request| spec = request.spec if [Gem::Resolver::VendorSpecification, Gem::Resolver::GitSpecification].include? spec.class then out << " #{request.name}!" else requirement = request.request.dependency.requirement out << " #{request.name}#{requirement.for_lockfile}" end end out << nil end def add_GEM out # :nodoc: return if @spec_groups.empty? source_groups = @spec_groups.values.flatten.group_by do |request| request.spec.source.uri end source_groups.sort_by { |group,| group.to_s }.map do |group, requests| out << "GEM" out << " remote: #{group}" out << " specs:" requests.sort_by { |request| request.name }.each do |request| platform = "-#{request.spec.platform}" unless Gem::Platform::RUBY == request.spec.platform out << " #{request.name} (#{request.version}#{platform})" request.full_spec.dependencies.sort.each do |dependency| requirement = dependency.requirement out << " #{dependency.name}#{requirement.for_lockfile}" end end out << nil end end def add_GIT out return unless git_requests = @spec_groups.delete(Gem::Resolver::GitSpecification) by_repository_revision = git_requests.group_by do |request| source = request.spec.source [source.repository, source.rev_parse] end out << "GIT" by_repository_revision.each do |(repository, revision), requests| out << " remote: #{repository}" out << " revision: #{revision}" out << " specs:" requests.sort_by { |request| request.name }.each do |request| out << " #{request.name} (#{request.version})" dependencies = request.spec.dependencies.sort_by { |dep| dep.name } dependencies.each do |dep| out << " #{dep.name}#{dep.requirement.for_lockfile}" end end end out << nil end def relative_path_from dest, base # :nodoc: dest = File.expand_path(dest) base = File.expand_path(base) if dest.index(base) == 0 return dest[base.size+1..-1] else dest end end def add_PATH out # :nodoc: return unless path_requests = @spec_groups.delete(Gem::Resolver::VendorSpecification) out << "PATH" path_requests.each do |request| directory = File.expand_path(request.spec.source.uri) out << " remote: #{relative_path_from directory, @gem_deps_dir}" out << " specs:" out << " #{request.name} (#{request.version})" end out << nil end def add_PLATFORMS out # :nodoc: out << "PLATFORMS" platforms = @requests.map { |request| request.spec.platform }.uniq platforms.delete Gem::Platform::RUBY if platforms.length > 1 platforms.each do |platform| out << " #{platform}" end out << nil end ## # Gets the next token for a Lockfile def get expected_types = nil, expected_value = nil # :nodoc: @current_token = @tokens.shift type, value, column, line = @current_token if expected_types and not Array(expected_types).include? type then unget message = "unexpected token [#{type.inspect}, #{value.inspect}], " + "expected #{expected_types.inspect}" raise ParseError.new message, column, line, "#{@gem_deps_file}.lock" end if expected_value and expected_value != value then unget message = "unexpected token [#{type.inspect}, #{value.inspect}], " + "expected [#{expected_types.inspect}, " + "#{expected_value.inspect}]" raise ParseError.new message, column, line, "#{@gem_deps_file}.lock" end @current_token end def parse # :nodoc: tokenize until @tokens.empty? do type, data, column, line = get case type when :section then skip :newline case data when 'DEPENDENCIES' then parse_DEPENDENCIES when 'GIT' then parse_GIT when 'GEM' then parse_GEM when 'PATH' then parse_PATH when 'PLATFORMS' then parse_PLATFORMS else type, = get until @tokens.empty? or peek.first == :section end else raise "BUG: unhandled token #{type} (#{data.inspect}) at line #{line} column #{column}" end end end def parse_DEPENDENCIES # :nodoc: while not @tokens.empty? and :text == peek.first do _, name, = get :text requirements = [] case peek[0] when :bang then get :bang spec = @set.sets.select { |set| Gem::Resolver::GitSet === set or Gem::Resolver::VendorSet === set }.map { |set| set.specs[name] }.first requirements << spec.version when :l_paren then get :l_paren loop do _, op, = get :requirement _, version, = get :text requirements << "#{op} #{version}" break unless peek[0] == :comma get :comma end get :r_paren end @set.gem name, *requirements skip :newline end end def parse_GEM # :nodoc: get :entry, 'remote' _, data, = get :text source = Gem::Source.new data skip :newline get :entry, 'specs' skip :newline set = Gem::Resolver::LockSet.new source last_spec = nil while not @tokens.empty? and :text == peek.first do _, name, column, = get :text case peek[0] when :newline then last_spec.add_dependency Gem::Dependency.new name if column == 6 when :l_paren then get :l_paren type, data, = get [:text, :requirement] if type == :text and column == 4 then version, platform = data.split '-', 2 platform = platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY last_spec = set.add name, version, platform else dependency = parse_dependency name, data last_spec.add_dependency dependency end get :r_paren else raise "BUG: unknown token #{peek}" end skip :newline end @set.sets << set end def parse_GIT # :nodoc: get :entry, 'remote' _, repository, = get :text skip :newline get :entry, 'revision' _, revision, = get :text skip :newline get :entry, 'specs' skip :newline set = Gem::Resolver::GitSet.new last_spec = nil while not @tokens.empty? and :text == peek.first do _, name, column, = get :text case peek[0] when :newline then last_spec.add_dependency Gem::Dependency.new name if column == 6 when :l_paren then get :l_paren type, data, = get [:text, :requirement] if type == :text and column == 4 then last_spec = set.add_git_spec name, data, repository, revision, true else dependency = parse_dependency name, data last_spec.spec.dependencies << dependency end get :r_paren else raise "BUG: unknown token #{peek}" end skip :newline end @set.sets << set end def parse_PATH # :nodoc: get :entry, 'remote' _, directory, = get :text skip :newline get :entry, 'specs' skip :newline set = Gem::Resolver::VendorSet.new last_spec = nil while not @tokens.empty? and :text == peek.first do _, name, column, = get :text case peek[0] when :newline then last_spec.add_dependency Gem::Dependency.new name if column == 6 when :l_paren then get :l_paren type, data, = get [:text, :requirement] if type == :text and column == 4 then last_spec = set.add_vendor_gem name, directory else dependency = parse_dependency name, data last_spec.spec.dependencies << dependency end get :r_paren else raise "BUG: unknown token #{peek}" end skip :newline end @set.sets << set end def parse_PLATFORMS # :nodoc: while not @tokens.empty? and :text == peek.first do _, name, = get :text @platforms << name skip :newline end end ## # Parses the requirements following the dependency +name+ and the +op+ for # the first token of the requirements and returns a Gem::Dependency object. def parse_dependency name, op # :nodoc: return Gem::Dependency.new name unless peek[0] == :text _, version, = get :text requirements = ["#{op} #{version}"] while peek[0] == :comma do get :comma _, op, = get :requirement _, version, = get :text requirements << "#{op} #{version}" end Gem::Dependency.new name, requirements end ## # Peeks at the next token for Lockfile def peek # :nodoc: @tokens.first || [:EOF] end def skip type # :nodoc: get while not @tokens.empty? and peek.first == type end ## # The contents of the lock file. def to_s @set.resolve out = [] @requests = @set.sorted_requests @spec_groups = @requests.group_by do |request| request.spec.class end add_PATH out add_GIT out add_GEM out add_PLATFORMS out add_DEPENDENCIES out out.join "\n" end ## # Calculates the column (by byte) and the line of the current token based on # +byte_offset+. def token_pos byte_offset # :nodoc: [byte_offset - @line_pos, @line] end ## # Converts a lock file into an Array of tokens. If the lock file is missing # an empty Array is returned. def tokenize # :nodoc: @line = 0 @line_pos = 0 @platforms = [] @tokens = [] @current_token = nil lock_file = "#{@gem_deps_file}.lock" @input = File.read lock_file s = StringScanner.new @input until s.eos? do pos = s.pos pos = s.pos if leading_whitespace = s.scan(/ +/) if s.scan(/[<|=>]{7}/) then message = "your #{lock_file} contains merge conflict markers" column, line = token_pos pos raise ParseError.new message, column, line, lock_file end @tokens << case when s.scan(/\r?\n/) then token = [:newline, nil, *token_pos(pos)] @line_pos = s.pos @line += 1 token when s.scan(/[A-Z]+/) then if leading_whitespace then text = s.matched text += s.scan(/[^\s)]*/).to_s # in case of no match [:text, text, *token_pos(pos)] else [:section, s.matched, *token_pos(pos)] end when s.scan(/([a-z]+):\s/) then s.pos -= 1 # rewind for possible newline [:entry, s[1], *token_pos(pos)] when s.scan(/\(/) then [:l_paren, nil, *token_pos(pos)] when s.scan(/\)/) then [:r_paren, nil, *token_pos(pos)] when s.scan(/<=|>=|=|~>|<|>|!=/) then [:requirement, s.matched, *token_pos(pos)] when s.scan(/,/) then [:comma, nil, *token_pos(pos)] when s.scan(/!/) then [:bang, nil, *token_pos(pos)] when s.scan(/[^\s),!]*/) then [:text, s.matched, *token_pos(pos)] else raise "BUG: can't create token for: #{s.string[s.pos..-1].inspect}" end end @tokens rescue Errno::ENOENT @tokens end ## # Ungets the last token retrieved by #get def unget # :nodoc: @tokens.unshift @current_token end ## # Writes the lock file alongside the gem dependencies file def write open "#{@gem_deps_file}.lock", 'w' do |io| io.write to_s end end end PKSe]ªMØ~3~3gem_dependency_api.rbnu„[µü¤## # A semi-compatible DSL for the Bundler Gemfile and Isolate formats. class Gem::RequestSet::GemDependencyAPI ENGINE_MAP = { # :nodoc: :jruby => %w[jruby], :jruby_18 => %w[jruby], :jruby_19 => %w[jruby], :maglev => %w[maglev], :mri => %w[ruby], :mri_18 => %w[ruby], :mri_19 => %w[ruby], :mri_20 => %w[ruby], :mri_21 => %w[ruby], :rbx => %w[rbx], :ruby => %w[ruby rbx maglev], :ruby_18 => %w[ruby rbx maglev], :ruby_19 => %w[ruby rbx maglev], :ruby_20 => %w[ruby rbx maglev], :ruby_21 => %w[ruby rbx maglev], } x86_mingw = Gem::Platform.new 'x86-mingw32' x64_mingw = Gem::Platform.new 'x64-mingw32' PLATFORM_MAP = { # :nodoc: :jruby => Gem::Platform::RUBY, :jruby_18 => Gem::Platform::RUBY, :jruby_19 => Gem::Platform::RUBY, :maglev => Gem::Platform::RUBY, :mingw => x86_mingw, :mingw_18 => x86_mingw, :mingw_19 => x86_mingw, :mingw_20 => x86_mingw, :mingw_21 => x86_mingw, :mri => Gem::Platform::RUBY, :mri_18 => Gem::Platform::RUBY, :mri_19 => Gem::Platform::RUBY, :mri_20 => Gem::Platform::RUBY, :mri_21 => Gem::Platform::RUBY, :mswin => Gem::Platform::RUBY, :rbx => Gem::Platform::RUBY, :ruby => Gem::Platform::RUBY, :ruby_18 => Gem::Platform::RUBY, :ruby_19 => Gem::Platform::RUBY, :ruby_20 => Gem::Platform::RUBY, :ruby_21 => Gem::Platform::RUBY, :x64_mingw => x64_mingw, :x64_mingw_20 => x64_mingw, :x64_mingw_21 => x64_mingw } gt_eq_0 = Gem::Requirement.new '>= 0' tilde_gt_1_8_0 = Gem::Requirement.new '~> 1.8.0' tilde_gt_1_9_0 = Gem::Requirement.new '~> 1.9.0' tilde_gt_2_0_0 = Gem::Requirement.new '~> 2.0.0' tilde_gt_2_1_0 = Gem::Requirement.new '~> 2.1.0' VERSION_MAP = { # :nodoc: :jruby => gt_eq_0, :jruby_18 => tilde_gt_1_8_0, :jruby_19 => tilde_gt_1_9_0, :maglev => gt_eq_0, :mingw => gt_eq_0, :mingw_18 => tilde_gt_1_8_0, :mingw_19 => tilde_gt_1_9_0, :mingw_20 => tilde_gt_2_0_0, :mingw_21 => tilde_gt_2_1_0, :mri => gt_eq_0, :mri_18 => tilde_gt_1_8_0, :mri_19 => tilde_gt_1_9_0, :mri_20 => tilde_gt_2_0_0, :mri_21 => tilde_gt_2_1_0, :mswin => gt_eq_0, :rbx => gt_eq_0, :ruby => gt_eq_0, :ruby_18 => tilde_gt_1_8_0, :ruby_19 => tilde_gt_1_9_0, :ruby_20 => tilde_gt_2_0_0, :ruby_21 => tilde_gt_2_1_0, :x64_mingw => gt_eq_0, :x64_mingw_20 => tilde_gt_2_0_0, :x64_mingw_21 => tilde_gt_2_1_0, } WINDOWS = { # :nodoc: :mingw => :only, :mingw_18 => :only, :mingw_19 => :only, :mingw_20 => :only, :mingw_21 => :only, :mri => :never, :mri_18 => :never, :mri_19 => :never, :mri_20 => :never, :mri_21 => :never, :mswin => :only, :rbx => :never, :ruby => :never, :ruby_18 => :never, :ruby_19 => :never, :ruby_20 => :never, :ruby_21 => :never, :x64_mingw => :only, :x64_mingw_20 => :only, :x64_mingw_21 => :only, } ## # A set of gems that are loaded via the +:git+ option to #gem attr_reader :git_set # :nodoc: ## # A Hash containing gem names and files to require from those gems. attr_reader :requires # :nodoc: ## # A set of gems that are loaded via the +:path+ option to #gem attr_reader :vendor_set # :nodoc: ## # The groups of gems to exclude from installation attr_accessor :without_groups # :nodoc: ## # Creates a new GemDependencyAPI that will add dependencies to the # Gem::RequestSet +set+ based on the dependency API description in +path+. def initialize set, path @set = set @path = path @current_groups = nil @current_platform = nil @current_repository = nil @default_sources = true @git_set = @set.git_set @requires = Hash.new { |h, name| h[name] = [] } @vendor_set = @set.vendor_set @gem_sources = {} @without_groups = [] end ## # Adds +dependencies+ to the request set if any of the +groups+ are allowed. # This is used for gemspec dependencies. def add_dependencies groups, dependencies # :nodoc: return unless (groups & @without_groups).empty? dependencies.each do |dep| @set.gem dep.name, *dep.requirement end end private :add_dependencies ## # Finds a gemspec with the given +name+ that lives at +path+. def find_gemspec name, path # :nodoc: glob = File.join path, "#{name}.gemspec" spec_files = Dir[glob] case spec_files.length when 1 then spec_file = spec_files.first spec = Gem::Specification.load spec_file return spec if spec raise ArgumentError, "invalid gemspec #{spec_file}" when 0 then raise ArgumentError, "no gemspecs found at #{Dir.pwd}" else raise ArgumentError, "found multiple gemspecs at #{Dir.pwd}, " + "use the name: option to specify the one you want" end end ## # Loads the gem dependency file def load instance_eval File.read(@path).untaint, @path, 1 end ## # :category: Gem Dependencies DSL # :call-seq: # gem(name) # gem(name, *requirements) # gem(name, *requirements, options) # # Specifies a gem dependency with the given +name+ and +requirements+. You # may also supply +options+ following the +requirements+ def gem name, *requirements options = requirements.pop if requirements.last.kind_of?(Hash) options ||= {} options[:git] = @current_repository if @current_repository source_set = false source_set ||= gem_path name, options source_set ||= gem_git name, options source_set ||= gem_github name, options return unless gem_platforms options groups = gem_group name, options return unless (groups & @without_groups).empty? pin_gem_source name, :default unless source_set gem_requires name, options @set.gem name, *requirements end ## # Handles the git: option from +options+ for gem +name+. # # Returns +true+ if the path option was handled. def gem_git name, options # :nodoc: if gist = options.delete(:gist) then options[:git] = "https://gist.github.com/#{gist}.git" end return unless repository = options.delete(:git) pin_gem_source name, :git, repository reference = nil reference ||= options.delete :ref reference ||= options.delete :branch reference ||= options.delete :tag reference ||= 'master' submodules = options.delete :submodules @git_set.add_git_gem name, repository, reference, submodules true end private :gem_git ## # Handles the github: option from +options+ for gem +name+. # # Returns +true+ if the path option was handled. def gem_github name, options # :nodoc: return unless path = options.delete(:github) options[:git] = "git://github.com/#{path}.git" gem_git name, options true end private :gem_github ## # Handles the :group and :groups +options+ for the gem with the given # +name+. def gem_group name, options # :nodoc: g = options.delete :group all_groups = g ? Array(g) : [] groups = options.delete :groups all_groups |= groups if groups all_groups |= @current_groups if @current_groups all_groups end private :gem_group ## # Handles the path: option from +options+ for gem +name+. # # Returns +true+ if the path option was handled. def gem_path name, options # :nodoc: return unless directory = options.delete(:path) pin_gem_source name, :path, directory @vendor_set.add_vendor_gem name, directory true end private :gem_path ## # Handles the platforms: option from +options+. Returns true if the # platform matches the current platform. def gem_platforms options # :nodoc: platform_names = Array(options.delete :platforms) platform_names << @current_platform if @current_platform return true if platform_names.empty? platform_names.any? do |platform_name| raise ArgumentError, "unknown platform #{platform_name.inspect}" unless platform = PLATFORM_MAP[platform_name] next false unless Gem::Platform.match platform if engines = ENGINE_MAP[platform_name] then next false unless engines.include? Gem.ruby_engine end case WINDOWS[platform_name] when :only then next false unless Gem.win_platform? when :never then next false if Gem.win_platform? end VERSION_MAP[platform_name].satisfied_by? Gem.ruby_version end end private :gem_platforms ## # Handles the require: option from +options+ and adds those files, or the # default file to the require list for +name+. def gem_requires name, options # :nodoc: if options.include? :require then if requires = options.delete(:require) then @requires[name].concat Array requires end else @requires[name] << name end end private :gem_requires ## # :category: Gem Dependencies DSL # # Block form for specifying gems from a git +repository+. def git repository @current_repository = repository yield ensure @current_repository = nil end ## # Returns the basename of the file the dependencies were loaded from def gem_deps_file # :nodoc: File.basename @path end ## # :category: Gem Dependencies DSL # # Loads dependencies from a gemspec file. def gemspec options = {} name = options.delete(:name) || '{,*}' path = options.delete(:path) || '.' development_group = options.delete(:development_group) || :development spec = find_gemspec name, path groups = gem_group spec.name, {} add_dependencies groups, spec.runtime_dependencies groups << development_group add_dependencies groups, spec.development_dependencies gem_requires spec.name, options end ## # :category: Gem Dependencies DSL # Block form for placing a dependency in the given +groups+. def group *groups @current_groups = groups yield ensure @current_groups = nil end ## # Pins the gem +name+ to the given +source+. Adding a gem with the same # name from a different +source+ will raise an exception. def pin_gem_source name, type = :default, source = nil source_description = case type when :default then '(default)' when :path then "path: #{source}" when :git then "git: #{source}" else '(unknown)' end raise ArgumentError, "duplicate source #{source_description} for gem #{name}" if @gem_sources.fetch(name, source) != source @gem_sources[name] = source end private :pin_gem_source ## # :category: Gem Dependencies DSL # # Block form for restricting gems to a particular platform. def platform what @current_platform = what yield ensure @current_platform = nil end ## # :category: Gem Dependencies DSL # # Block form for restricting gems to a particular platform. alias :platforms :platform ## # :category: Gem Dependencies DSL # Restricts this gem dependencies file to the given ruby +version+. The # +:engine+ options from Bundler are currently ignored. def ruby version, options = {} engine = options[:engine] engine_version = options[:engine_version] raise ArgumentError, 'you must specify engine_version along with the ruby engine' if engine and not engine_version unless RUBY_VERSION == version then message = "Your Ruby version is #{RUBY_VERSION}, " + "but your #{gem_deps_file} requires #{version}" raise Gem::RubyVersionMismatch, message end if engine and engine != Gem.ruby_engine then message = "Your ruby engine is #{Gem.ruby_engine}, " + "but your #{gem_deps_file} requires #{engine}" raise Gem::RubyVersionMismatch, message end if engine_version then my_engine_version = Object.const_get "#{Gem.ruby_engine.upcase}_VERSION" if engine_version != my_engine_version then message = "Your ruby engine version is #{Gem.ruby_engine} #{my_engine_version}, " + "but your #{gem_deps_file} requires #{engine} #{engine_version}" raise Gem::RubyVersionMismatch, message end end return true end ## # :category: Gem Dependencies DSL # # Sets +url+ as a source for gems for this dependency API. def source url Gem.sources.clear if @default_sources @default_sources = false Gem.sources << url end # TODO: remove this typo name at RubyGems 3.0 Gem::RequestSet::GemDepedencyAPI = self # :nodoc: end PKSe](ÑŠ –3–3 lockfile.rbnu„[µü¤PKSe]ªMØ~3~3Ñ3gem_dependency_api.rbnu„[µü¤PK ”g