summaryrefslogtreecommitdiff
path: root/lib/bundler/endpoint_specification.rb
blob: 8668c4ea7fe9a8ff6b00f64cfcf37d82f1044b11 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# frozen_string_literal: true

module Bundler
  # used for Creating Specifications from the Gemcutter Endpoint
  class EndpointSpecification < Gem::Specification
    ILLFORMED_MESSAGE = 'Ill-formed requirement ["#<YAML::Syck::DefaultKey'.freeze
    include MatchPlatform

    attr_reader :name, :version, :platform, :required_rubygems_version, :required_ruby_version, :checksum
    attr_accessor :source, :remote, :dependencies

    def initialize(name, version, platform, dependencies, metadata = nil)
      super()
      @name         = name
      @version      = Gem::Version.create version
      @platform     = platform
      @dependencies = dependencies.map {|dep, reqs| build_dependency(dep, reqs) }

      @loaded_from          = nil
      @remote_specification = nil

      parse_metadata(metadata)
    end

    def fetch_platform
      @platform
    end

    # needed for standalone, load required_paths from local gemspec
    # after the gem is installed
    def require_paths
      if @remote_specification
        @remote_specification.require_paths
      elsif _local_specification
        _local_specification.require_paths
      else
        super
      end
    end

    # needed for inline
    def load_paths
      # remote specs aren't installed, and can't have load_paths
      if _local_specification
        _local_specification.load_paths
      else
        super
      end
    end

    # needed for binstubs
    def executables
      if @remote_specification
        @remote_specification.executables
      elsif _local_specification
        _local_specification.executables
      else
        super
      end
    end

    # needed for bundle clean
    def bindir
      if @remote_specification
        @remote_specification.bindir
      elsif _local_specification
        _local_specification.bindir
      else
        super
      end
    end

    # needed for post_install_messages during install
    def post_install_message
      if @remote_specification
        @remote_specification.post_install_message
      elsif _local_specification
        _local_specification.post_install_message
      else
        super
      end
    end

    # needed for "with native extensions" during install
    def extensions
      if @remote_specification
        @remote_specification.extensions
      elsif _local_specification
        _local_specification.extensions
      else
        super
      end
    end

    def _local_specification
      return unless @loaded_from && File.exist?(local_specification_path)
      eval(File.read(local_specification_path)).tap do |spec|
        spec.loaded_from = @loaded_from
      end
    end

    def __swap__(spec)
      SharedHelpers.ensure_same_dependencies(self, dependencies, spec.dependencies)
      @remote_specification = spec
    end

  private

    def local_specification_path
      "#{base_dir}/specifications/#{full_name}.gemspec"
    end

    def parse_metadata(data)
      return unless data
      data.each do |k, v|
        next unless v
        case k.to_s
        when "checksum"
          @checksum = v.last
        when "rubygems"
          @required_rubygems_version = Gem::Requirement.new(v)
        when "ruby"
          @required_ruby_version = Gem::Requirement.new(v)
        end
      end
    rescue => e
      raise GemspecError, "There was an error parsing the metadata for the gem #{name} (#{version}): #{e.class}\n#{e}\nThe metadata was #{data.inspect}"
    end

    def build_dependency(name, requirements)
      Gem::Dependency.new(name, requirements)
    rescue ArgumentError => e
      raise unless e.message.include?(ILLFORMED_MESSAGE)
      puts # we shouldn't print the error message on the "fetching info" status line
      raise GemspecError,
        "Unfortunately, the gem #{name} (#{version}) has an invalid " \
        "gemspec.\nPlease ask the gem author to yank the bad version to fix " \
        "this issue. For more information, see http://bit.ly/syck-defaultkey."
    end
  end
end