summaryrefslogtreecommitdiff
path: root/lib/rubygems/stub_specification.rb
blob: ac7fc6109a8de3766238bd3ac582d935a48588c1 (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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# frozen_string_literal: true
##
# Gem::StubSpecification reads the stub: line from the gemspec.  This prevents
# us having to eval the entire gemspec in order to find out certain
# information.

class Gem::StubSpecification < Gem::BasicSpecification
  # :nodoc:
  PREFIX = "# stub: ".freeze

  # :nodoc:
  OPEN_MODE = 'r:UTF-8:-'.freeze

  class StubLine # :nodoc: all
    attr_reader :name, :version, :platform, :require_paths, :extensions,
                :full_name

    NO_EXTENSIONS = [].freeze

    # These are common require paths.
    REQUIRE_PATHS = { # :nodoc:
      'lib'  => 'lib'.freeze,
      'test' => 'test'.freeze,
      'ext'  => 'ext'.freeze,
    }.freeze

    # These are common require path lists.  This hash is used to optimize
    # and consolidate require_path objects.  Most specs just specify "lib"
    # in their require paths, so lets take advantage of that by pre-allocating
    # a require path list for that case.
    REQUIRE_PATH_LIST = { # :nodoc:
      'lib' => ['lib'].freeze,
    }.freeze

    def initialize(data, extensions)
      parts          = data[PREFIX.length..-1].split(" ".freeze, 4)
      @name          = parts[0].freeze
      @version       = if Gem::Version.correct?(parts[1])
                         Gem::Version.new(parts[1])
                       else
                         Gem::Version.new(0)
                       end

      @platform      = Gem::Platform.new parts[2]
      @extensions    = extensions
      @full_name     = if platform == Gem::Platform::RUBY
                         "#{name}-#{version}"
                       else
                         "#{name}-#{version}-#{platform}"
                       end

      path_list = parts.last
      @require_paths = REQUIRE_PATH_LIST[path_list] || path_list.split("\0".freeze).map! do |x|
        REQUIRE_PATHS[x] || x
      end
    end
  end

  def self.default_gemspec_stub(filename, base_dir, gems_dir)
    new filename, base_dir, gems_dir, true
  end

  def self.gemspec_stub(filename, base_dir, gems_dir)
    new filename, base_dir, gems_dir, false
  end

  attr_reader :base_dir, :gems_dir

  def initialize(filename, base_dir, gems_dir, default_gem)
    super()
    filename.tap(&Gem::UNTAINT)

    self.loaded_from = filename
    @data            = nil
    @name            = nil
    @spec            = nil
    @base_dir        = base_dir
    @gems_dir        = gems_dir
    @default_gem     = default_gem
  end

  ##
  # True when this gem has been activated

  def activated?
    @activated ||=
    begin
      loaded = Gem.loaded_specs[name]
      loaded && loaded.version == version
    end
  end

  def default_gem?
    @default_gem
  end

  def build_extensions # :nodoc:
    return if default_gem?
    return if extensions.empty?

    to_spec.build_extensions
  end

  ##
  # If the gemspec contains a stubline, returns a StubLine instance. Otherwise
  # returns the full Gem::Specification.

  def data
    unless @data
      begin
        saved_lineno = $.

        Gem.open_with_flock loaded_from, OPEN_MODE do |file|
          begin
            file.readline # discard encoding line
            stubline = file.readline.chomp
            if stubline.start_with?(PREFIX)
              extensions = if /\A#{PREFIX}/ =~ file.readline.chomp
                             $'.split "\0"
                           else
                             StubLine::NO_EXTENSIONS
                           end

              @data = StubLine.new stubline, extensions
            end
          rescue EOFError
          end
        end
      ensure
        $. = saved_lineno
      end
    end

    @data ||= to_spec
  end

  private :data

  def raw_require_paths # :nodoc:
    data.require_paths
  end

  def missing_extensions?
    return false if default_gem?
    return false if extensions.empty?
    return false if File.exist? gem_build_complete_path

    to_spec.missing_extensions?
  end

  ##
  # Name of the gem

  def name
    data.name
  end

  ##
  # Platform of the gem

  def platform
    data.platform
  end

  ##
  # Extensions for this gem

  def extensions
    data.extensions
  end

  ##
  # Version of the gem

  def version
    data.version
  end

  def full_name
    data.full_name
  end

  ##
  # The full Gem::Specification for this gem, loaded from evalling its gemspec

  def to_spec
    @spec ||= if @data
                loaded = Gem.loaded_specs[name]
                loaded if loaded && loaded.version == version
              end

    @spec ||= Gem::Specification.load(loaded_from)
  end

  ##
  # Is this StubSpecification valid? i.e. have we found a stub line, OR does
  # the filename contain a valid gemspec?

  def valid?
    data
  end

  ##
  # Is there a stub line present for this StubSpecification?

  def stubbed?
    data.is_a? StubLine
  end
end