class Bundler::Dsl

Constants

VALID_KEYS
VALID_PLATFORMS

Attributes

dependencies[RW]
gemspecs[R]

Public Class Methods

evaluate(gemfile, lockfile, unlock) click to toggle source
# File lib/bundler/dsl.rb, line 10
def self.evaluate(gemfile, lockfile, unlock)
  builder = new
  builder.eval_gemfile(gemfile)
  builder.to_definition(lockfile, unlock)
end
new() click to toggle source
# File lib/bundler/dsl.rb, line 24
def initialize
  @source               = nil
  @sources              = SourceList.new
  @git_sources          = {}
  @dependencies         = []
  @groups               = []
  @install_conditionals = []
  @optional_groups      = []
  @platforms            = []
  @env                  = nil
  @ruby_version         = nil
  @gemspecs             = []
  @gemfile              = nil
  @gemfiles             = []
  add_git_sources
end

Public Instance Methods

add_git_sources() click to toggle source
# File lib/bundler/dsl.rb, line 286
    def add_git_sources
      return if Bundler.feature_flag.skip_default_git_sources?

      git_source(:github) do |repo_name|
        warn_deprecated_git_source(:github, <<-'RUBY'.strip, 'Change any "reponame" :github sources to "username/reponame".')
"https://github.com/#{repo_name}.git"
        RUBY
        # It would be better to use https instead of the git protocol, but this
        # can break deployment of existing locked bundles when switching between
        # different versions of Bundler. The change will be made in 2.0, which
        # does not guarantee compatibility with the 1.x series.
        #
        # See https://github.com/bundler/bundler/pull/2569 for discussion
        #
        # This can be overridden by adding this code to your Gemfiles:
        #
        #   git_source(:github) do |repo_name|
        #     repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
        #     "https://github.com/#{repo_name}.git"
        #   end
        repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
        # TODO: 2.0 upgrade this setting to the default
        if Bundler.feature_flag.github_https?
          Bundler::SharedHelpers.major_deprecation 2, "The `github.https` setting will be removed"
          "https://github.com/#{repo_name}.git"
        else
          "git://github.com/#{repo_name}.git"
        end
      end

      # TODO: 2.0 remove this deprecated git source
      git_source(:gist) do |repo_name|
        warn_deprecated_git_source(:gist, '"https://gist.github.com/#{repo_name}.git"')

        "https://gist.github.com/#{repo_name}.git"
      end

      # TODO: 2.0 remove this deprecated git source
      git_source(:bitbucket) do |repo_name|
        warn_deprecated_git_source(:bitbucket, <<-'RUBY'.strip)
user_name, repo_name = repo_name.split("/")
repo_name ||= user_name
"https://#{user_name}@bitbucket.org/#{user_name}/#{repo_name}.git"
        RUBY

        user_name, repo_name = repo_name.split("/")
        repo_name ||= user_name
        "https://#{user_name}@bitbucket.org/#{user_name}/#{repo_name}.git"
      end
    end

    def with_source(source)
      old_source = @source
      if block_given?
        @source = source
        yield
      end
      source
    ensure
      @source = old_source
    end

    def normalize_hash(opts)
      opts.keys.each do |k|
        opts[k.to_s] = opts.delete(k) unless k.is_a?(String)
      end
      opts
    end

    def valid_keys
      @valid_keys ||= VALID_KEYS
    end

    def normalize_options(name, version, opts)
      if name.is_a?(Symbol)
        raise GemfileError, %(You need to specify gem names as Strings. Use 'gem "#{name}"' instead)
      end
      if name =~ /\s/
        raise GemfileError, %('#{name}' is not a valid gem name because it contains whitespace)
      end
      if name.empty?
        raise GemfileError, %(an empty gem name is not valid)
      end

      normalize_hash(opts)

      git_names = @git_sources.keys.map(&:to_s)
      validate_keys("gem '#{name}'", opts, valid_keys + git_names)

      groups = @groups.dup
      opts["group"] = opts.delete("groups") || opts["group"]
      groups.concat Array(opts.delete("group"))
      groups = [:default] if groups.empty?

      install_if = @install_conditionals.dup
      install_if.concat Array(opts.delete("install_if"))
      install_if = install_if.reduce(true) do |memo, val|
        memo && (val.respond_to?(:call) ? val.call : val)
      end

      platforms = @platforms.dup
      opts["platforms"] = opts["platform"] || opts["platforms"]
      platforms.concat Array(opts.delete("platforms"))
      platforms.map!(&:to_sym)
      platforms.each do |p|
        next if VALID_PLATFORMS.include?(p)
        raise GemfileError, "`#{p}` is not a valid platform. The available options are: #{VALID_PLATFORMS.inspect}"
      end

      # Save sources passed in a key
      if opts.key?("source")
        source = normalize_source(opts["source"])
        opts["source"] = @sources.add_rubygems_source("remotes" => source)
      end

      git_name = (git_names & opts.keys).last
      if @git_sources[git_name]
        opts["git"] = @git_sources[git_name].call(opts[git_name])
      end

      %w[git path].each do |type|
        next unless param = opts[type]
        if version.first && version.first =~ /^\s*=?\s*(\d[^\s]*)\s*$/
          options = opts.merge("name" => name, "version" => $1)
        else
          options = opts.dup
        end
        source = send(type, param, options) {}
        opts["source"] = source
      end

      opts["source"]         ||= @source
      opts["env"]            ||= @env
      opts["platforms"]      = platforms.dup
      opts["group"]          = groups
      opts["should_include"] = install_if
    end

    def normalize_group_options(opts, groups)
      normalize_hash(opts)

      groups = groups.map {|group| ":#{group}" }.join(", ")
      validate_keys("group #{groups}", opts, %w[optional])

      opts["optional"] ||= false
    end

    def validate_keys(command, opts, valid_keys)
      invalid_keys = opts.keys - valid_keys

      git_source = opts.keys & @git_sources.keys.map(&:to_s)
      if opts["branch"] && !(opts["git"] || opts["github"] || git_source.any?)
        raise GemfileError, %(The `branch` option for `#{command}` is not allowed. Only gems with a git source can specify a branch)
      end

      return true unless invalid_keys.any?

      message = String.new
      message << "You passed #{invalid_keys.map {|k| ":" + k }.join(", ")} "
      message << if invalid_keys.size > 1
                   "as options for #{command}, but they are invalid."
                 else
                   "as an option for #{command}, but it is invalid."
                 end

      message << " Valid options are: #{valid_keys.join(", ")}."
      message << " You may be able to resolve this by upgrading Bundler to the newest version."
      raise InvalidOption, message
    end

    def normalize_source(source)
      case source
      when :gemcutter, :rubygems, :rubyforge
        Bundler::SharedHelpers.major_deprecation 2, "The source :#{source} is deprecated because HTTP " \
          "requests are insecure.\nPlease change your source to 'https://" \
          "rubygems.org' if possible, or 'http://rubygems.org' if not."
        "http://rubygems.org"
      when String
        source
      else
        raise GemfileError, "Unknown source '#{source}'"
      end
    end

    def check_primary_source_safety(source_list)
      return if source_list.rubygems_primary_remotes.empty? && source_list.global_rubygems_source.nil?

      if Bundler.feature_flag.disable_multisource?
        msg = "This Gemfile contains multiple primary sources. " \
          "Each source after the first must include a block to indicate which gems " \
          "should come from that source"
        unless Bundler.feature_flag.bundler_2_mode?
          msg += ". To downgrade this error to a warning, run " \
            "`bundle config --delete disable_multisource`"
        end
        raise GemfileEvalError, msg
      else
        Bundler::SharedHelpers.major_deprecation 2, "Your Gemfile contains multiple primary sources. " \
          "Using `source` more than once without a block is a security risk, and " \
          "may result in installing unexpected gems. To resolve this warning, use " \
          "a block to indicate which gems should come from the secondary source. " \
          "To upgrade this warning to an error, run `bundle config " \
          "disable_multisource true`."
      end
    end

    def warn_deprecated_git_source(name, replacement, additional_message = nil)
      # TODO: 2.0 remove deprecation
      additional_message &&= " #{additional_message}"
      replacement = if replacement.count("\n").zero?
        "{|repo_name| #{replacement} }"
      else
        "do |repo_name|\n#{replacement.to_s.gsub(/^/, "      ")}\n    end"
      end

      Bundler::SharedHelpers.major_deprecation 2, <<-EOS
The :#{name} git source is deprecated, and will be removed in Bundler 2.0.#{additional_message} Add this code to the top of your Gemfile to ensure it continues to work:

    git_source(:#{name}) #{replacement}

      EOS
    end

    class DSLError < GemfileError
      # @return [String] the description that should be presented to the user.
      #
      attr_reader :description

      # @return [String] the path of the dsl file that raised the exception.
      #
      attr_reader :dsl_path

      # @return [Exception] the backtrace of the exception raised by the
      #         evaluation of the dsl file.
      #
      attr_reader :backtrace

      # @param [Exception] backtrace @see backtrace
      # @param [String]    dsl_path  @see dsl_path
      #
      def initialize(description, dsl_path, backtrace, contents = nil)
        @status_code = $!.respond_to?(:status_code) && $!.status_code

        @description = description
        @dsl_path    = dsl_path
        @backtrace   = backtrace
        @contents    = contents
      end

      def status_code
        @status_code || super
      end

      # @return [String] the contents of the DSL that cause the exception to
      #         be raised.
      #
      def contents
        @contents ||= begin
          dsl_path && File.exist?(dsl_path) && File.read(dsl_path)
        end
      end

      # The message of the exception reports the content of podspec for the
      # line that generated the original exception.
      #
      # @example Output
      #
      #   Invalid podspec at `RestKit.podspec` - undefined method
      #   `exclude_header_search_paths=' for #<Pod::Specification for
      #   `RestKit/Network (0.9.3)`>
      #
      #       from spec-repos/master/RestKit/0.9.3/RestKit.podspec:36
      #       -------------------------------------------
      #           # because it would break: #import <CoreData/CoreData.h>
      #    >      ns.exclude_header_search_paths = 'Code/RestKit.h'
      #         end
      #       -------------------------------------------
      #
      # @return [String] the message of the exception.
      #
      def to_s
        @to_s ||= begin
          trace_line, description = parse_line_number_from_description

          m = String.new("\n[!] ")
          m << description
          m << ". Bundler cannot continue.\n"

          return m unless backtrace && dsl_path && contents

          trace_line = backtrace.find {|l| l.include?(dsl_path.to_s) } || trace_line
          return m unless trace_line
          line_numer = trace_line.split(":")[1].to_i - 1
          return m unless line_numer

          lines      = contents.lines.to_a
          indent     = " #  "
          indicator  = indent.tr("#", ">")
          first_line = line_numer.zero?
          last_line  = (line_numer == (lines.count - 1))

          m << "\n"
          m << "#{indent}from #{trace_line.gsub(/:in.*$/, "")}\n"
          m << "#{indent}-------------------------------------------\n"
          m << "#{indent}#{lines[line_numer - 1]}" unless first_line
          m << "#{indicator}#{lines[line_numer]}"
          m << "#{indent}#{lines[line_numer + 1]}" unless last_line
          m << "\n" unless m.end_with?("\n")
          m << "#{indent}-------------------------------------------\n"
        end
      end

    private

      def parse_line_number_from_description
        description = self.description
        if dsl_path && description =~ /((#{Regexp.quote File.expand_path(dsl_path)}|#{Regexp.quote dsl_path.to_s}):\d+)/
          trace_line = Regexp.last_match[1]
          description = description.sub(/#{Regexp.quote trace_line}:\s*/, "").sub("\n", " - ")
        end
        [trace_line, description]
      end
    end

    def gemfile_root
      @gemfile ||= Bundler.default_gemfile
      @gemfile.dirname
    end
  end
end
check_primary_source_safety(source_list) click to toggle source
# File lib/bundler/dsl.rb, line 470
    def check_primary_source_safety(source_list)
      return if source_list.rubygems_primary_remotes.empty? && source_list.global_rubygems_source.nil?

      if Bundler.feature_flag.disable_multisource?
        msg = "This Gemfile contains multiple primary sources. " \
          "Each source after the first must include a block to indicate which gems " \
          "should come from that source"
        unless Bundler.feature_flag.bundler_2_mode?
          msg += ". To downgrade this error to a warning, run " \
            "`bundle config --delete disable_multisource`"
        end
        raise GemfileEvalError, msg
      else
        Bundler::SharedHelpers.major_deprecation 2, "Your Gemfile contains multiple primary sources. " \
          "Using `source` more than once without a block is a security risk, and " \
          "may result in installing unexpected gems. To resolve this warning, use " \
          "a block to indicate which gems should come from the secondary source. " \
          "To upgrade this warning to an error, run `bundle config " \
          "disable_multisource true`."
      end
    end

    def warn_deprecated_git_source(name, replacement, additional_message = nil)
      # TODO: 2.0 remove deprecation
      additional_message &&= " #{additional_message}"
      replacement = if replacement.count("\n").zero?
        "{|repo_name| #{replacement} }"
      else
        "do |repo_name|\n#{replacement.to_s.gsub(/^/, "      ")}\n    end"
      end

      Bundler::SharedHelpers.major_deprecation 2, <<-EOS
The :#{name} git source is deprecated, and will be removed in Bundler 2.0.#{additional_message} Add this code to the top of your Gemfile to ensure it continues to work:

    git_source(:#{name}) #{replacement}

      EOS
    end

    class DSLError < GemfileError
      # @return [String] the description that should be presented to the user.
      #
      attr_reader :description

      # @return [String] the path of the dsl file that raised the exception.
      #
      attr_reader :dsl_path

      # @return [Exception] the backtrace of the exception raised by the
      #         evaluation of the dsl file.
      #
      attr_reader :backtrace

      # @param [Exception] backtrace @see backtrace
      # @param [String]    dsl_path  @see dsl_path
      #
      def initialize(description, dsl_path, backtrace, contents = nil)
        @status_code = $!.respond_to?(:status_code) && $!.status_code

        @description = description
        @dsl_path    = dsl_path
        @backtrace   = backtrace
        @contents    = contents
      end

      def status_code
        @status_code || super
      end

      # @return [String] the contents of the DSL that cause the exception to
      #         be raised.
      #
      def contents
        @contents ||= begin
          dsl_path && File.exist?(dsl_path) && File.read(dsl_path)
        end
      end

      # The message of the exception reports the content of podspec for the
      # line that generated the original exception.
      #
      # @example Output
      #
      #   Invalid podspec at `RestKit.podspec` - undefined method
      #   `exclude_header_search_paths=' for #<Pod::Specification for
      #   `RestKit/Network (0.9.3)`>
      #
      #       from spec-repos/master/RestKit/0.9.3/RestKit.podspec:36
      #       -------------------------------------------
      #           # because it would break: #import <CoreData/CoreData.h>
      #    >      ns.exclude_header_search_paths = 'Code/RestKit.h'
      #         end
      #       -------------------------------------------
      #
      # @return [String] the message of the exception.
      #
      def to_s
        @to_s ||= begin
          trace_line, description = parse_line_number_from_description

          m = String.new("\n[!] ")
          m << description
          m << ". Bundler cannot continue.\n"

          return m unless backtrace && dsl_path && contents

          trace_line = backtrace.find {|l| l.include?(dsl_path.to_s) } || trace_line
          return m unless trace_line
          line_numer = trace_line.split(":")[1].to_i - 1
          return m unless line_numer

          lines      = contents.lines.to_a
          indent     = " #  "
          indicator  = indent.tr("#", ">")
          first_line = line_numer.zero?
          last_line  = (line_numer == (lines.count - 1))

          m << "\n"
          m << "#{indent}from #{trace_line.gsub(/:in.*$/, "")}\n"
          m << "#{indent}-------------------------------------------\n"
          m << "#{indent}#{lines[line_numer - 1]}" unless first_line
          m << "#{indicator}#{lines[line_numer]}"
          m << "#{indent}#{lines[line_numer + 1]}" unless last_line
          m << "\n" unless m.end_with?("\n")
          m << "#{indent}-------------------------------------------\n"
        end
      end

    private

      def parse_line_number_from_description
        description = self.description
        if dsl_path && description =~ /((#{Regexp.quote File.expand_path(dsl_path)}|#{Regexp.quote dsl_path.to_s}):\d+)/
          trace_line = Regexp.last_match[1]
          description = description.sub(/#{Regexp.quote trace_line}:\s*/, "").sub("\n", " - ")
        end
        [trace_line, description]
      end
    end

    def gemfile_root
      @gemfile ||= Bundler.default_gemfile
      @gemfile.dirname
    end
  end
env(name) { || ... } click to toggle source
# File lib/bundler/dsl.rb, line 268
def env(name)
  old = @env
  @env = name
  yield
ensure
  @env = old
end
eval_gemfile(gemfile, contents = nil) click to toggle source
# File lib/bundler/dsl.rb, line 41
def eval_gemfile(gemfile, contents = nil)
  expanded_gemfile_path = Pathname.new(gemfile).expand_path(@gemfile && @gemfile.parent)
  original_gemfile = @gemfile
  @gemfile = expanded_gemfile_path
  @gemfiles << expanded_gemfile_path
  contents ||= Bundler.read_file(@gemfile.to_s)
  instance_eval(contents.dup.untaint, gemfile.to_s, 1)
rescue Exception => e
  message = "There was an error " \
    "#{e.is_a?(GemfileEvalError) ? "evaluating" : "parsing"} " \
    "`#{File.basename gemfile.to_s}`: #{e.message}"

  raise DSLError.new(message, gemfile, e.backtrace, contents)
ensure
  @gemfile = original_gemfile
end
gem(name, *args) click to toggle source
# File lib/bundler/dsl.rb, line 94
    def gem(name, *args)
      options = args.last.is_a?(Hash) ? args.pop.dup : {}
      options["gemfile"] = @gemfile
      version = args || [">= 0"]

      normalize_options(name, version, options)

      dep = Dependency.new(name, version, options)

      # if there's already a dependency with this name we try to prefer one
      if current = @dependencies.find {|d| d.name == dep.name }
        deleted_dep = @dependencies.delete(current) if current.type == :development

        if current.requirement != dep.requirement
          unless deleted_dep
            return if dep.type == :development

            update_prompt = ""

            if File.basename(@gemfile) == Injector::INJECTED_GEMS
              if dep.requirements_list.include?(">= 0") && !current.requirements_list.include?(">= 0")
                update_prompt = ". Gem already added"
              else
                update_prompt = ". If you want to update the gem version, run `bundle update #{current.name}`"

                update_prompt += ". You may also need to change the version requirement specified in the Gemfile if it's too restrictive." unless current.requirements_list.include?(">= 0")
              end
            end

            raise GemfileError, "You cannot specify the same gem twice with different version requirements.\n" \
                            "You specified: #{current.name} (#{current.requirement}) and #{dep.name} (#{dep.requirement})" \
                             "#{update_prompt}"
          end

        else
          Bundler.ui.warn "Your Gemfile lists the gem #{current.name} (#{current.requirement}) more than once.\n" \
                          "You should probably keep only one of them.\n" \
                          "Remove any duplicate entries and specify the gem only once (per group).\n" \
                          "While it's not a problem now, it could cause errors if you change the version of one of them later."
        end

        if current.source != dep.source
          unless deleted_dep
            return if dep.type == :development
            raise GemfileError, "You cannot specify the same gem twice coming from different sources.\n" \
                            "You specified that #{dep.name} (#{dep.requirement}) should come from " \
                            "#{current.source || "an unspecified source"} and #{dep.source}\n"
          end
        end
      end

      @dependencies << dep
    end

    def source(source, *args, &blk)
      options = args.last.is_a?(Hash) ? args.pop.dup : {}
      options = normalize_hash(options)
      source = normalize_source(source)

      if options.key?("type")
        options["type"] = options["type"].to_s
        unless Plugin.source?(options["type"])
          raise InvalidOption, "No plugin sources available for #{options["type"]}"
        end

        unless block_given?
          raise InvalidOption, "You need to pass a block to #source with :type option"
        end

        source_opts = options.merge("uri" => source)
        with_source(@sources.add_plugin_source(options["type"], source_opts), &blk)
      elsif block_given?
        with_source(@sources.add_rubygems_source("remotes" => source), &blk)
      else
        check_primary_source_safety(@sources)
        @sources.global_rubygems_source = source
      end
    end

    def git_source(name, &block)
      unless block_given?
        raise InvalidOption, "You need to pass a block to #git_source"
      end

      if valid_keys.include?(name.to_s)
        raise InvalidOption, "You cannot use #{name} as a git source. It " \
          "is a reserved key. Reserved keys are: #{valid_keys.join(", ")}"
      end

      @git_sources[name.to_s] = block
    end

    def path(path, options = {}, &blk)
      unless block_given?
        msg = "You can no longer specify a path source by itself. Instead, \n" \
              "either use the :path option on a gem, or specify the gems that \n" \
              "bundler should find in the path source by passing a block to \n" \
              "the path method, like: \n\n" \
              "    path 'dir/containing/rails' do\n" \
              "      gem 'rails'\n" \
              "    end\n\n"

        raise DeprecatedError, msg if Bundler.feature_flag.disable_multisource?
        SharedHelpers.major_deprecation(2, msg.strip)
      end

      source_options = normalize_hash(options).merge(
        "path" => Pathname.new(path),
        "root_path" => gemfile_root,
        "gemspec" => gemspecs.find {|g| g.name == options["name"] }
      )
      source = @sources.add_path_source(source_options)
      with_source(source, &blk)
    end

    def git(uri, options = {}, &blk)
      unless block_given?
        msg = "You can no longer specify a git source by itself. Instead, \n" \
              "either use the :git option on a gem, or specify the gems that \n" \
              "bundler should find in the git source by passing a block to \n" \
              "the git method, like: \n\n" \
              "  git 'git://github.com/rails/rails.git' do\n" \
              "    gem 'rails'\n" \
              "  end"
        raise DeprecatedError, msg
      end

      with_source(@sources.add_git_source(normalize_hash(options).merge("uri" => uri)), &blk)
    end

    def github(repo, options = {})
      raise ArgumentError, "GitHub sources require a block" unless block_given?
      raise DeprecatedError, "The #github method has been removed" if Bundler.feature_flag.skip_default_git_sources?
      github_uri  = @git_sources["github"].call(repo)
      git_options = normalize_hash(options).merge("uri" => github_uri)
      git_source  = @sources.add_git_source(git_options)
      with_source(git_source) { yield }
    end

    def to_definition(lockfile, unlock)
      Definition.new(lockfile, @dependencies, @sources, unlock, @ruby_version, @optional_groups, @gemfiles)
    end

    def group(*args, &blk)
      options = args.last.is_a?(Hash) ? args.pop.dup : {}
      normalize_group_options(options, args)

      @groups.concat args

      if options["optional"]
        optional_groups = args - @optional_groups
        @optional_groups.concat optional_groups
      end

      yield
    ensure
      args.each { @groups.pop }
    end

    def install_if(*args)
      @install_conditionals.concat args
      yield
    ensure
      args.each { @install_conditionals.pop }
    end

    def platforms(*platforms)
      @platforms.concat platforms
      yield
    ensure
      platforms.each { @platforms.pop }
    end
    alias_method :platform, :platforms

    def env(name)
      old = @env
      @env = name
      yield
    ensure
      @env = old
    end

    def plugin(*args)
      # Pass on
    end

    def method_missing(name, *args)
      raise GemfileError, "Undefined local variable or method `#{name}' for Gemfile"
    end

  private

    def add_git_sources
      return if Bundler.feature_flag.skip_default_git_sources?

      git_source(:github) do |repo_name|
        warn_deprecated_git_source(:github, <<-'RUBY'.strip, 'Change any "reponame" :github sources to "username/reponame".')
"https://github.com/#{repo_name}.git"
        RUBY
        # It would be better to use https instead of the git protocol, but this
        # can break deployment of existing locked bundles when switching between
        # different versions of Bundler. The change will be made in 2.0, which
        # does not guarantee compatibility with the 1.x series.
        #
        # See https://github.com/bundler/bundler/pull/2569 for discussion
        #
        # This can be overridden by adding this code to your Gemfiles:
        #
        #   git_source(:github) do |repo_name|
        #     repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
        #     "https://github.com/#{repo_name}.git"
        #   end
        repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
        # TODO: 2.0 upgrade this setting to the default
        if Bundler.feature_flag.github_https?
          Bundler::SharedHelpers.major_deprecation 2, "The `github.https` setting will be removed"
          "https://github.com/#{repo_name}.git"
        else
          "git://github.com/#{repo_name}.git"
        end
      end

      # TODO: 2.0 remove this deprecated git source
      git_source(:gist) do |repo_name|
        warn_deprecated_git_source(:gist, '"https://gist.github.com/#{repo_name}.git"')

        "https://gist.github.com/#{repo_name}.git"
      end

      # TODO: 2.0 remove this deprecated git source
      git_source(:bitbucket) do |repo_name|
        warn_deprecated_git_source(:bitbucket, <<-'RUBY'.strip)
user_name, repo_name = repo_name.split("/")
repo_name ||= user_name
"https://#{user_name}@bitbucket.org/#{user_name}/#{repo_name}.git"
        RUBY

        user_name, repo_name = repo_name.split("/")
        repo_name ||= user_name
        "https://#{user_name}@bitbucket.org/#{user_name}/#{repo_name}.git"
      end
    end

    def with_source(source)
      old_source = @source
      if block_given?
        @source = source
        yield
      end
      source
    ensure
      @source = old_source
    end

    def normalize_hash(opts)
      opts.keys.each do |k|
        opts[k.to_s] = opts.delete(k) unless k.is_a?(String)
      end
      opts
    end

    def valid_keys
      @valid_keys ||= VALID_KEYS
    end

    def normalize_options(name, version, opts)
      if name.is_a?(Symbol)
        raise GemfileError, %(You need to specify gem names as Strings. Use 'gem "#{name}"' instead)
      end
      if name =~ /\s/
        raise GemfileError, %('#{name}' is not a valid gem name because it contains whitespace)
      end
      if name.empty?
        raise GemfileError, %(an empty gem name is not valid)
      end

      normalize_hash(opts)

      git_names = @git_sources.keys.map(&:to_s)
      validate_keys("gem '#{name}'", opts, valid_keys + git_names)

      groups = @groups.dup
      opts["group"] = opts.delete("groups") || opts["group"]
      groups.concat Array(opts.delete("group"))
      groups = [:default] if groups.empty?

      install_if = @install_conditionals.dup
      install_if.concat Array(opts.delete("install_if"))
      install_if = install_if.reduce(true) do |memo, val|
        memo && (val.respond_to?(:call) ? val.call : val)
      end

      platforms = @platforms.dup
      opts["platforms"] = opts["platform"] || opts["platforms"]
      platforms.concat Array(opts.delete("platforms"))
      platforms.map!(&:to_sym)
      platforms.each do |p|
        next if VALID_PLATFORMS.include?(p)
        raise GemfileError, "`#{p}` is not a valid platform. The available options are: #{VALID_PLATFORMS.inspect}"
      end

      # Save sources passed in a key
      if opts.key?("source")
        source = normalize_source(opts["source"])
        opts["source"] = @sources.add_rubygems_source("remotes" => source)
      end

      git_name = (git_names & opts.keys).last
      if @git_sources[git_name]
        opts["git"] = @git_sources[git_name].call(opts[git_name])
      end

      %w[git path].each do |type|
        next unless param = opts[type]
        if version.first && version.first =~ /^\s*=?\s*(\d[^\s]*)\s*$/
          options = opts.merge("name" => name, "version" => $1)
        else
          options = opts.dup
        end
        source = send(type, param, options) {}
        opts["source"] = source
      end

      opts["source"]         ||= @source
      opts["env"]            ||= @env
      opts["platforms"]      = platforms.dup
      opts["group"]          = groups
      opts["should_include"] = install_if
    end

    def normalize_group_options(opts, groups)
      normalize_hash(opts)

      groups = groups.map {|group| ":#{group}" }.join(", ")
      validate_keys("group #{groups}", opts, %w[optional])

      opts["optional"] ||= false
    end

    def validate_keys(command, opts, valid_keys)
      invalid_keys = opts.keys - valid_keys

      git_source = opts.keys & @git_sources.keys.map(&:to_s)
      if opts["branch"] && !(opts["git"] || opts["github"] || git_source.any?)
        raise GemfileError, %(The `branch` option for `#{command}` is not allowed. Only gems with a git source can specify a branch)
      end

      return true unless invalid_keys.any?

      message = String.new
      message << "You passed #{invalid_keys.map {|k| ":" + k }.join(", ")} "
      message << if invalid_keys.size > 1
                   "as options for #{command}, but they are invalid."
                 else
                   "as an option for #{command}, but it is invalid."
                 end

      message << " Valid options are: #{valid_keys.join(", ")}."
      message << " You may be able to resolve this by upgrading Bundler to the newest version."
      raise InvalidOption, message
    end

    def normalize_source(source)
      case source
      when :gemcutter, :rubygems, :rubyforge
        Bundler::SharedHelpers.major_deprecation 2, "The source :#{source} is deprecated because HTTP " \
          "requests are insecure.\nPlease change your source to 'https://" \
          "rubygems.org' if possible, or 'http://rubygems.org' if not."
        "http://rubygems.org"
      when String
        source
      else
        raise GemfileError, "Unknown source '#{source}'"
      end
    end

    def check_primary_source_safety(source_list)
      return if source_list.rubygems_primary_remotes.empty? && source_list.global_rubygems_source.nil?

      if Bundler.feature_flag.disable_multisource?
        msg = "This Gemfile contains multiple primary sources. " \
          "Each source after the first must include a block to indicate which gems " \
          "should come from that source"
        unless Bundler.feature_flag.bundler_2_mode?
          msg += ". To downgrade this error to a warning, run " \
            "`bundle config --delete disable_multisource`"
        end
        raise GemfileEvalError, msg
      else
        Bundler::SharedHelpers.major_deprecation 2, "Your Gemfile contains multiple primary sources. " \
          "Using `source` more than once without a block is a security risk, and " \
          "may result in installing unexpected gems. To resolve this warning, use " \
          "a block to indicate which gems should come from the secondary source. " \
          "To upgrade this warning to an error, run `bundle config " \
          "disable_multisource true`."
      end
    end

    def warn_deprecated_git_source(name, replacement, additional_message = nil)
      # TODO: 2.0 remove deprecation
      additional_message &&= " #{additional_message}"
      replacement = if replacement.count("\n").zero?
        "{|repo_name| #{replacement} }"
      else
        "do |repo_name|\n#{replacement.to_s.gsub(/^/, "      ")}\n    end"
      end

      Bundler::SharedHelpers.major_deprecation 2, <<-EOS
The :#{name} git source is deprecated, and will be removed in Bundler 2.0.#{additional_message} Add this code to the top of your Gemfile to ensure it continues to work:

    git_source(:#{name}) #{replacement}

      EOS
    end

    class DSLError < GemfileError
      # @return [String] the description that should be presented to the user.
      #
      attr_reader :description

      # @return [String] the path of the dsl file that raised the exception.
      #
      attr_reader :dsl_path

      # @return [Exception] the backtrace of the exception raised by the
      #         evaluation of the dsl file.
      #
      attr_reader :backtrace

      # @param [Exception] backtrace @see backtrace
      # @param [String]    dsl_path  @see dsl_path
      #
      def initialize(description, dsl_path, backtrace, contents = nil)
        @status_code = $!.respond_to?(:status_code) && $!.status_code

        @description = description
        @dsl_path    = dsl_path
        @backtrace   = backtrace
        @contents    = contents
      end

      def status_code
        @status_code || super
      end

      # @return [String] the contents of the DSL that cause the exception to
      #         be raised.
      #
      def contents
        @contents ||= begin
          dsl_path && File.exist?(dsl_path) && File.read(dsl_path)
        end
      end

      # The message of the exception reports the content of podspec for the
      # line that generated the original exception.
      #
      # @example Output
      #
      #   Invalid podspec at `RestKit.podspec` - undefined method
      #   `exclude_header_search_paths=' for #<Pod::Specification for
      #   `RestKit/Network (0.9.3)`>
      #
      #       from spec-repos/master/RestKit/0.9.3/RestKit.podspec:36
      #       -------------------------------------------
      #           # because it would break: #import <CoreData/CoreData.h>
      #    >      ns.exclude_header_search_paths = 'Code/RestKit.h'
      #         end
      #       -------------------------------------------
      #
      # @return [String] the message of the exception.
      #
      def to_s
        @to_s ||= begin
          trace_line, description = parse_line_number_from_description

          m = String.new("\n[!] ")
          m << description
          m << ". Bundler cannot continue.\n"

          return m unless backtrace && dsl_path && contents

          trace_line = backtrace.find {|l| l.include?(dsl_path.to_s) } || trace_line
          return m unless trace_line
          line_numer = trace_line.split(":")[1].to_i - 1
          return m unless line_numer

          lines      = contents.lines.to_a
          indent     = " #  "
          indicator  = indent.tr("#", ">")
          first_line = line_numer.zero?
          last_line  = (line_numer == (lines.count - 1))

          m << "\n"
          m << "#{indent}from #{trace_line.gsub(/:in.*$/, "")}\n"
          m << "#{indent}-------------------------------------------\n"
          m << "#{indent}#{lines[line_numer - 1]}" unless first_line
          m << "#{indicator}#{lines[line_numer]}"
          m << "#{indent}#{lines[line_numer + 1]}" unless last_line
          m << "\n" unless m.end_with?("\n")
          m << "#{indent}-------------------------------------------\n"
        end
      end

    private

      def parse_line_number_from_description
        description = self.description
        if dsl_path && description =~ /((#{Regexp.quote File.expand_path(dsl_path)}|#{Regexp.quote dsl_path.to_s}):\d+)/
          trace_line = Regexp.last_match[1]
          description = description.sub(/#{Regexp.quote trace_line}:\s*/, "").sub("\n", " - ")
        end
        [trace_line, description]
      end
    end

    def gemfile_root
      @gemfile ||= Bundler.default_gemfile
      @gemfile.dirname
    end
  end
gemfile_root() click to toggle source
# File lib/bundler/dsl.rb, line 610
def gemfile_root
  @gemfile ||= Bundler.default_gemfile
  @gemfile.dirname
end
gemspec(opts = nil) click to toggle source
# File lib/bundler/dsl.rb, line 58
def gemspec(opts = nil)
  opts ||= {}
  path              = opts[:path] || "."
  glob              = opts[:glob]
  name              = opts[:name]
  development_group = opts[:development_group] || :development
  expanded_path     = gemfile_root.join(path)

  gemspecs = Dir[File.join(expanded_path, "{,*}.gemspec")].map {|g| Bundler.load_gemspec(g) }.compact
  gemspecs.reject! {|s| s.name != name } if name
  Index.sort_specs(gemspecs)
  specs_by_name_and_version = gemspecs.group_by {|s| [s.name, s.version] }

  case specs_by_name_and_version.size
  when 1
    specs = specs_by_name_and_version.values.first
    spec = specs.find {|s| s.match_platform(Bundler.local_platform) } || specs.first

    @gemspecs << spec

    gem_platforms = Bundler::Dependency::REVERSE_PLATFORM_MAP[Bundler::GemHelpers.generic_local_platform]
    gem spec.name, :name => spec.name, :path => path, :glob => glob, :platforms => gem_platforms

    group(development_group) do
      spec.development_dependencies.each do |dep|
        gem dep.name, *(dep.requirement.as_list + [:type => :development])
      end
    end
  when 0
    raise InvalidOption, "There are no gemspecs at #{expanded_path}"
  else
    raise InvalidOption, "There are multiple gemspecs at #{expanded_path}. " \
      "Please use the :name option to specify which one should be used"
  end
end
git(uri, options = {}, &blk) click to toggle source
# File lib/bundler/dsl.rb, line 209
def git(uri, options = {}, &blk)
  unless block_given?
    msg = "You can no longer specify a git source by itself. Instead, \n" \
          "either use the :git option on a gem, or specify the gems that \n" \
          "bundler should find in the git source by passing a block to \n" \
          "the git method, like: \n\n" \
          "  git 'git://github.com/rails/rails.git' do\n" \
          "    gem 'rails'\n" \
          "  end"
    raise DeprecatedError, msg
  end

  with_source(@sources.add_git_source(normalize_hash(options).merge("uri" => uri)), &blk)
end
git_source(name, &block) click to toggle source
# File lib/bundler/dsl.rb, line 173
def git_source(name, &block)
  unless block_given?
    raise InvalidOption, "You need to pass a block to #git_source"
  end

  if valid_keys.include?(name.to_s)
    raise InvalidOption, "You cannot use #{name} as a git source. It " \
      "is a reserved key. Reserved keys are: #{valid_keys.join(", ")}"
  end

  @git_sources[name.to_s] = block
end
github(repo, options = {}) { || ... } click to toggle source
# File lib/bundler/dsl.rb, line 224
def github(repo, options = {})
  raise ArgumentError, "GitHub sources require a block" unless block_given?
  raise DeprecatedError, "The #github method has been removed" if Bundler.feature_flag.skip_default_git_sources?
  github_uri  = @git_sources["github"].call(repo)
  git_options = normalize_hash(options).merge("uri" => github_uri)
  git_source  = @sources.add_git_source(git_options)
  with_source(git_source) { yield }
end
group(*args) { || ... } click to toggle source
# File lib/bundler/dsl.rb, line 237
def group(*args, &blk)
  options = args.last.is_a?(Hash) ? args.pop.dup : {}
  normalize_group_options(options, args)

  @groups.concat args

  if options["optional"]
    optional_groups = args - @optional_groups
    @optional_groups.concat optional_groups
  end

  yield
ensure
  args.each { @groups.pop }
end
install_if(*args) { || ... } click to toggle source
# File lib/bundler/dsl.rb, line 253
def install_if(*args)
  @install_conditionals.concat args
  yield
ensure
  args.each { @install_conditionals.pop }
end
method_missing(name, *args) click to toggle source
# File lib/bundler/dsl.rb, line 280
def method_missing(name, *args)
  raise GemfileError, "Undefined local variable or method `#{name}' for Gemfile"
end
normalize_group_options(opts, groups) click to toggle source
# File lib/bundler/dsl.rb, line 424
def normalize_group_options(opts, groups)
  normalize_hash(opts)

  groups = groups.map {|group| ":#{group}" }.join(", ")
  validate_keys("group #{groups}", opts, %w[optional])

  opts["optional"] ||= false
end
normalize_hash(opts) click to toggle source
# File lib/bundler/dsl.rb, line 348
def normalize_hash(opts)
  opts.keys.each do |k|
    opts[k.to_s] = opts.delete(k) unless k.is_a?(String)
  end
  opts
end
normalize_options(name, version, opts) click to toggle source
# File lib/bundler/dsl.rb, line 359
def normalize_options(name, version, opts)
  if name.is_a?(Symbol)
    raise GemfileError, %(You need to specify gem names as Strings. Use 'gem "#{name}"' instead)
  end
  if name =~ /\s/
    raise GemfileError, %('#{name}' is not a valid gem name because it contains whitespace)
  end
  if name.empty?
    raise GemfileError, %(an empty gem name is not valid)
  end

  normalize_hash(opts)

  git_names = @git_sources.keys.map(&:to_s)
  validate_keys("gem '#{name}'", opts, valid_keys + git_names)

  groups = @groups.dup
  opts["group"] = opts.delete("groups") || opts["group"]
  groups.concat Array(opts.delete("group"))
  groups = [:default] if groups.empty?

  install_if = @install_conditionals.dup
  install_if.concat Array(opts.delete("install_if"))
  install_if = install_if.reduce(true) do |memo, val|
    memo && (val.respond_to?(:call) ? val.call : val)
  end

  platforms = @platforms.dup
  opts["platforms"] = opts["platform"] || opts["platforms"]
  platforms.concat Array(opts.delete("platforms"))
  platforms.map!(&:to_sym)
  platforms.each do |p|
    next if VALID_PLATFORMS.include?(p)
    raise GemfileError, "`#{p}` is not a valid platform. The available options are: #{VALID_PLATFORMS.inspect}"
  end

  # Save sources passed in a key
  if opts.key?("source")
    source = normalize_source(opts["source"])
    opts["source"] = @sources.add_rubygems_source("remotes" => source)
  end

  git_name = (git_names & opts.keys).last
  if @git_sources[git_name]
    opts["git"] = @git_sources[git_name].call(opts[git_name])
  end

  %w[git path].each do |type|
    next unless param = opts[type]
    if version.first && version.first =~ /^\s*=?\s*(\d[^\s]*)\s*$/
      options = opts.merge("name" => name, "version" => $1)
    else
      options = opts.dup
    end
    source = send(type, param, options) {}
    opts["source"] = source
  end

  opts["source"]         ||= @source
  opts["env"]            ||= @env
  opts["platforms"]      = platforms.dup
  opts["group"]          = groups
  opts["should_include"] = install_if
end
normalize_source(source) click to toggle source
# File lib/bundler/dsl.rb, line 456
def normalize_source(source)
  case source
  when :gemcutter, :rubygems, :rubyforge
    Bundler::SharedHelpers.major_deprecation 2, "The source :#{source} is deprecated because HTTP " \
      "requests are insecure.\nPlease change your source to 'https://" \
      "rubygems.org' if possible, or 'http://rubygems.org' if not."
    "http://rubygems.org"
  when String
    source
  else
    raise GemfileError, "Unknown source '#{source}'"
  end
end
path(path, options = {}, &blk) click to toggle source
# File lib/bundler/dsl.rb, line 186
def path(path, options = {}, &blk)
  unless block_given?
    msg = "You can no longer specify a path source by itself. Instead, \n" \
          "either use the :path option on a gem, or specify the gems that \n" \
          "bundler should find in the path source by passing a block to \n" \
          "the path method, like: \n\n" \
          "    path 'dir/containing/rails' do\n" \
          "      gem 'rails'\n" \
          "    end\n\n"

    raise DeprecatedError, msg if Bundler.feature_flag.disable_multisource?
    SharedHelpers.major_deprecation(2, msg.strip)
  end

  source_options = normalize_hash(options).merge(
    "path" => Pathname.new(path),
    "root_path" => gemfile_root,
    "gemspec" => gemspecs.find {|g| g.name == options["name"] }
  )
  source = @sources.add_path_source(source_options)
  with_source(source, &blk)
end
platforms(*platforms) { || ... } click to toggle source
# File lib/bundler/dsl.rb, line 260
def platforms(*platforms)
  @platforms.concat platforms
  yield
ensure
  platforms.each { @platforms.pop }
end
plugin(*args) click to toggle source
# File lib/bundler/dsl.rb, line 276
def plugin(*args)
  # Pass on
end
source(source, *args, &blk) click to toggle source
# File lib/bundler/dsl.rb, line 148
def source(source, *args, &blk)
  options = args.last.is_a?(Hash) ? args.pop.dup : {}
  options = normalize_hash(options)
  source = normalize_source(source)

  if options.key?("type")
    options["type"] = options["type"].to_s
    unless Plugin.source?(options["type"])
      raise InvalidOption, "No plugin sources available for #{options["type"]}"
    end

    unless block_given?
      raise InvalidOption, "You need to pass a block to #source with :type option"
    end

    source_opts = options.merge("uri" => source)
    with_source(@sources.add_plugin_source(options["type"], source_opts), &blk)
  elsif block_given?
    with_source(@sources.add_rubygems_source("remotes" => source), &blk)
  else
    check_primary_source_safety(@sources)
    @sources.global_rubygems_source = source
  end
end
to_definition(lockfile, unlock) click to toggle source
# File lib/bundler/dsl.rb, line 233
def to_definition(lockfile, unlock)
  Definition.new(lockfile, @dependencies, @sources, unlock, @ruby_version, @optional_groups, @gemfiles)
end
valid_keys() click to toggle source
# File lib/bundler/dsl.rb, line 355
def valid_keys
  @valid_keys ||= VALID_KEYS
end
validate_keys(command, opts, valid_keys) click to toggle source
# File lib/bundler/dsl.rb, line 433
def validate_keys(command, opts, valid_keys)
  invalid_keys = opts.keys - valid_keys

  git_source = opts.keys & @git_sources.keys.map(&:to_s)
  if opts["branch"] && !(opts["git"] || opts["github"] || git_source.any?)
    raise GemfileError, %(The `branch` option for `#{command}` is not allowed. Only gems with a git source can specify a branch)
  end

  return true unless invalid_keys.any?

  message = String.new
  message << "You passed #{invalid_keys.map {|k| ":" + k }.join(", ")} "
  message << if invalid_keys.size > 1
               "as options for #{command}, but they are invalid."
             else
               "as an option for #{command}, but it is invalid."
             end

  message << " Valid options are: #{valid_keys.join(", ")}."
  message << " You may be able to resolve this by upgrading Bundler to the newest version."
  raise InvalidOption, message
end
warn_deprecated_git_source(name, replacement, additional_message = nil) click to toggle source
# File lib/bundler/dsl.rb, line 492
    def warn_deprecated_git_source(name, replacement, additional_message = nil)
      # TODO: 2.0 remove deprecation
      additional_message &&= " #{additional_message}"
      replacement = if replacement.count("\n").zero?
        "{|repo_name| #{replacement} }"
      else
        "do |repo_name|\n#{replacement.to_s.gsub(/^/, "      ")}\n    end"
      end

      Bundler::SharedHelpers.major_deprecation 2, <<-EOS
The :#{name} git source is deprecated, and will be removed in Bundler 2.0.#{additional_message} Add this code to the top of your Gemfile to ensure it continues to work:

    git_source(:#{name}) #{replacement}

      EOS
    end
with_source(source) { || ... } click to toggle source
# File lib/bundler/dsl.rb, line 337
def with_source(source)
  old_source = @source
  if block_given?
    @source = source
    yield
  end
  source
ensure
  @source = old_source
end