summaryrefslogtreecommitdiff
path: root/lib/rubygems/stub_specification.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rubygems/stub_specification.rb')
-rw-r--r--lib/rubygems/stub_specification.rb236
1 files changed, 236 insertions, 0 deletions
diff --git a/lib/rubygems/stub_specification.rb b/lib/rubygems/stub_specification.rb
new file mode 100644
index 0000000000..53b337ed85
--- /dev/null
+++ b/lib/rubygems/stub_specification.rb
@@ -0,0 +1,236 @@
+# 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: "
+
+ # :nodoc:
+ OPEN_MODE = "r:UTF-8:-"
+
+ 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",
+ "test" => "test",
+ "ext" => "ext",
+ }.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(" ", 4)
+ @name = -parts[0]
+ @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").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()
+
+ 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 ||= !loaded_spec.nil?
+ 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_file loaded_from, OPEN_MODE do |file|
+ file.readline # discard encoding line
+ stubline = file.readline
+ if stubline.start_with?(PREFIX)
+ extline = file.readline
+
+ extensions =
+ if extline.delete_prefix!(PREFIX)
+ extline.chomp!
+ extline.split "\0"
+ else
+ StubLine::NO_EXTENSIONS
+ end
+
+ stubline.chomp! # readline(chomp: true) allocates 3x as much as .readline.chomp!
+ @data = StubLine.new stubline, extensions
+ end
+ rescue EOFError
+ 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 RUBY_ENGINE == "jruby"
+ 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 spec
+ @spec ||= loaded_spec if @data
+ @spec ||= Gem::Specification.load(loaded_from)
+ end
+ alias_method :to_spec, :spec
+
+ ##
+ # 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
+
+ def ==(other) # :nodoc:
+ self.class === other &&
+ name == other.name &&
+ version == other.version &&
+ platform == other.platform
+ end
+
+ alias_method :eql?, :== # :nodoc:
+
+ def hash # :nodoc:
+ name.hash ^ version.hash ^ platform.hash
+ end
+
+ def <=>(other) # :nodoc:
+ sort_obj <=> other.sort_obj
+ end
+
+ def sort_obj # :nodoc:
+ [name, version, Gem::Platform.sort_priority(platform)]
+ end
+
+ private
+
+ def loaded_spec
+ spec = Gem.loaded_specs[name]
+ return unless spec && spec.version == version && spec.default_gem? == default_gem?
+
+ spec
+ end
+end