123456789_123456789_123456789_123456789_123456789_

Class: Gem::SpecificationPolicy

Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Instance Chain:
Inherits: Object
Defined in: lib/rubygems/specification_policy.rb

Constant Summary

Class Method Summary

Instance Attribute Summary

  • #packaging rw

    If set to true, run packaging-specific checks, as well.

DefaultUserInteraction - Included

Instance Method Summary

UserInteraction - Included

#alert

Displays an alert statement.

#alert_error

Displays an error statement to the error output location.

#alert_warning

Displays a warning statement to the warning output location.

#ask

Asks a question and returns the answer.

#ask_for_password

Asks for a password with a prompt

#ask_yes_no

Asks a yes or no question.

#choose_from_list

Asks the user to answer question with an answer from the given list.

#say

Displays the given statement on the standard output (or equivalent).

#terminate_interaction

Terminates the RubyGems process with the given exit_code

#verbose

Calls say with msg or the results of the block if really_verbose is true.

DefaultUserInteraction - Included

Text - Included

#clean_text

Remove any non-printable characters and make the text suitable for printing.

#format_text

Wraps text to wrap characters and optionally indents by indent characters.

#levenshtein_distance

Returns a value representing the “cost” of transforming str1 into str2 Vendored version of DidYouMean::Levenshtein.distance from the ruby/did_you_mean gem @ 1.4.0 github.com/ruby/did_you_mean/blob/2ddf39b874808685965dbc47d344cf6c7651807c/lib/did_you_mean/levenshtein.rb#L7-L37.

#truncate_text, #min3

Constructor Details

.new(specification) ⇒ SpecificationPolicy

This method is for internal use only.
[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 25

def initialize(specification)
  @warnings = 0

  @specification = specification
end

Instance Attribute Details

#packaging (rw)

If set to true, run packaging-specific checks, as well.

[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 34

attr_accessor :packaging

Instance Method Details

#error(statement) (private)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 498

def error(statement) # :nodoc:
  raise Gem::InvalidSpecificationException, statement
ensure
  alert_warning help_text
end

#help_text (private)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 504

def help_text # :nodoc:
  "See https://guides.rubygems.org/specification-reference/ for help"
end

#validate(strict = false)

Does a sanity check on the specification.

Raises InvalidSpecificationException if the spec does not pass the checks.

It also performs some validations that do not raise but print warning messages instead.

[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 45

def validate(strict = false)
  validate_required!

  validate_optional(strict) if packaging || strict

  true
end

#validate_array_attribute(field) (private)

[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 339

def validate_array_attribute(field)
  val = @specification.send(field)
  klass = case field
  when :dependencies then
    Gem::Dependency
  else
    String
  end

  unless Array === val && val.all? {|x| x.kind_of?(klass) }
    error "#{field} must be an Array of #{klass}"
  end
end

#validate_array_attributes (private)

[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 333

def validate_array_attributes
  Gem::Specification.array_attributes.each do |field|
    validate_array_attribute(field)
  end
end

#validate_attribute_present(attribute) (private)

[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 448

def validate_attribute_present(attribute)
  value = @specification.send attribute
  warning("no #{attribute} specified") if value.nil? || value.empty?
end

#validate_authors_field (private)

[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 353

def validate_authors_field
  return unless @specification.authors.empty?

  error "authors may not be empty"
end

#validate_dependencies

This method is for internal use only.

Checks that the gem does not depend on itself. Checks that dependencies use requirements as we recommend. Warnings are issued when dependencies are open-ended or overly strict for semantic versioning.

[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 183

def validate_dependencies # :nodoc:
  warning_messages = []
  @specification.dependencies.each do |dep|
    if dep.name == @specification.name # warn on self reference
      warning_messages << "Self referencing dependency is unnecessary and strongly discouraged."
    end

    prerelease_dep = dep.requirements_list.any? do |req|
      Gem::Requirement.new(req).prerelease?
    end

    warning_messages << "prerelease dependency on #{dep} is not recommended" if
        prerelease_dep && !@specification.version.prerelease?

    open_ended = dep.requirement.requirements.all? do |op, version|
      !version.prerelease? && (op == ">" || op == ">=")
    end

    if open_ended
      op, dep_version = dep.requirement.requirements.first

      segments = dep_version.segments

      base = segments.first 2

      recommendation = if (op == ">" || op == ">=") && segments == [0]
        "  use a bounded requirement, such as '~> x.y'"
      else
        bugfix = if op == ">"
          ", '> #{dep_version}'"
        elsif op == ">=" && base != segments
          ", '>= #{dep_version}'"
        end

        "  if #{dep.name} is semantically versioned, use:\n" \
        "    add_#{dep.type}_dependency '#{dep.name}', '~> #{base.join '.'}'#{bugfix}"
      end

      warning_messages << ["open-ended dependency on #{dep} is not recommended", recommendation].join("\n") + "\n"
    end
  end
  if warning_messages.any?
    warning_messages.each {|warning_message| warning warning_message }
  end
end

#validate_duplicate_dependencies

This method is for internal use only.

Checks that no duplicate dependencies are specified.

[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 157

def validate_duplicate_dependencies # :nodoc:
  # NOTE: see REFACTOR note in Gem::Dependency about types - this might be brittle
  seen = Gem::Dependency::TYPES.inject({}) {|types, type| types.merge({ type => {} }) }

  error_messages = []
  @specification.dependencies.each do |dep|
    if prev = seen[dep.type][dep.name]
      error_messages << <<-MESSAGE
duplicate dependency on #{dep}, (#{prev.requirement}) use:
  add_#{dep.type}_dependency '#{dep.name}', '#{dep.requirement}', '#{prev.requirement}'
      MESSAGE
    end

    seen[dep.type][dep.name] = dep
  end
  if error_messages.any?
    error error_messages.join
  end
end

#validate_extensions (private)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 466

def validate_extensions # :nodoc:
  require_relative "ext"
  builder = Gem::Ext::Builder.new(@specification)

  validate_rake_extensions(builder)
  validate_rust_extensions(builder)
end

#validate_lazy_metadata (private)

[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 394

def 
  unless @specification.authors.grep(LAZY_PATTERN).empty?
    error "#{LAZY} is not an author"
  end

  unless Array(@specification.email).grep(LAZY_PATTERN).empty?
    error "#{LAZY} is not an email"
  end

  if @specification.description =~ LAZY_PATTERN
    error "#{LAZY} is not a description"
  end

  if @specification.summary =~ LAZY_PATTERN
    error "#{LAZY} is not a summary"
  end

  homepage = @specification.homepage

  # Make sure a homepage is valid HTTP/HTTPS URI
  if homepage && !homepage.empty?
    require "uri"
    begin
      homepage_uri = URI.parse(homepage)
      unless [URI::HTTP, URI::HTTPS].member? homepage_uri.class
        error "\"#{homepage}\" is not a valid HTTP URI"
      end
    rescue URI::InvalidURIError
      error "\"#{homepage}\" is not a valid HTTP URI"
    end
  end
end

#validate_licenses (private)

[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 369

def validate_licenses
  licenses = @specification.licenses

  licenses.each do |license|
    if !Gem::Licenses.match?(license)
      suggestions = Gem::Licenses.suggestions(license)
      message = <<-WARNING
license value '#{license}' is invalid.  Use a license identifier from
http://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard license.
      WARNING
      message += "Did you mean #{suggestions.map {|s| "'#{s}'" }.join(', ')}?\n" unless suggestions.nil?
      warning(message)
    end
  end

  warning <<-WARNING if licenses.empty?
licenses is empty, but is recommended.  Use a license identifier from
http://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard license.
  WARNING
end

#validate_licenses_length (private)

[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 359

def validate_licenses_length
  licenses = @specification.licenses

  licenses.each do |license|
    if license.length > 64
      error "each license must be 64 characters or less"
    end
  end
end

#validate_metadata

Implementation for Specification#validate_metadata

[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 121

def 
   = @specification.

  unless Hash === 
    error "metadata must be a hash"
  end

  .each do |key, value|
    entry = "metadata['#{key}']"
    if !key.kind_of?(String)
      error "metadata keys must be a String"
    end

    if key.size > 128
      error "metadata key is too large (#{key.size} > 128)"
    end

    if !value.kind_of?(String)
      error "#{entry} value must be a String"
    end

    if value.size > 1024
      error "#{entry} value is too large (#{value.size} > 1024)"
    end

    if METADATA_LINK_KEYS.include? key
      if value !~ VALID_URI_PATTERN
        error "#{entry} has invalid link: #{value.inspect}"
      end
    end
  end
end

#validate_name (private)

[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 279

def validate_name
  name = @specification.name

  if !name.is_a?(String)
    error "invalid value for attribute name: \"#{name.inspect}\" must be a string"
  elsif name !~ /[a-zA-Z]/
    error "invalid value for attribute name: #{name.dump} must include at least one letter"
  elsif name !~ VALID_NAME_PATTERN
    error "invalid value for attribute name: #{name.dump} can only include letters, numbers, dashes, and underscores"
  elsif name =~ SPECIAL_CHARACTERS
    error "invalid value for attribute name: #{name.dump} can not begin with a period, dash, or underscore"
  end
end

#validate_nil_attributes (private)

[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 253

def validate_nil_attributes
  nil_attributes = Gem::Specification.non_nil_attributes.select do |attrname|
    @specification.instance_variable_get("@#{attrname}").nil?
  end
  return if nil_attributes.empty?
  error "#{nil_attributes.join ', '} must not be nil"
end

#validate_non_files (private)

[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 299

def validate_non_files
  return unless packaging

  non_files = @specification.files.reject {|x| File.file?(x) || File.symlink?(x) }

  unless non_files.empty?
    error "[\"#{non_files.join "\", \""}\"] are not files"
  end
end

#validate_optional(strict)

[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 96

def validate_optional(strict)
  validate_licenses

  validate_permissions

  validate_values

  validate_dependencies

  validate_extensions

  validate_removed_attributes

  if @warnings > 0
    if strict
      error "specification has warnings"
    else
      alert_warning help_text
    end
  end
end

#validate_permissions

Issues a warning for each file to be packaged which is world-readable.

Implementation for Specification#validate_permissions

[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 234

def validate_permissions
  return if Gem.win_platform?

  @specification.files.each do |file|
    next unless File.file?(file)
    next if File.stat(file).mode & 0444 == 0444
    warning "#{file} is not world-readable"
  end

  @specification.executables.each do |name|
    exec = File.join @specification.bindir, name
    next unless File.file?(exec)
    next if File.stat(exec).executable?
    warning "#{exec} is not executable"
  end
end

#validate_platform (private)

[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 323

def validate_platform
  platform = @specification.platform

  case platform
  when Gem::Platform, Gem::Platform::RUBY # ok
  else
    error "invalid platform #{platform.inspect}, see Gem::Platform"
  end
end

#validate_rake_extensions(builder) (private)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 483

def validate_rake_extensions(builder) # :nodoc:
  rake_extension = @specification.extensions.any? {|s| builder.builder_for(s) == Gem::Ext::RakeBuilder }
  rake_dependency = @specification.dependencies.any? {|d| d.name == "rake" }

  warning <<-WARNING if rake_extension && !rake_dependency
You have specified rake based extension, but rake is not added as dependency. It is recommended to add rake as a dependency in gemspec since there's no guarantee rake will be already installed.
  WARNING
end

#validate_removed_attributes (private)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 460

def validate_removed_attributes # :nodoc:
  @specification.removed_method_calls.each do |attr|
    warning("#{attr} is deprecated and ignored. Please remove this from your gemspec to ensure that your gem continues to build in the future.")
  end
end

#validate_require_paths (private)

[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 293

def validate_require_paths
  return unless @specification.raw_require_paths.empty?

  error "specification must have at least one require_path"
end

#validate_required!

Does a sanity check on the specification.

Raises InvalidSpecificationException if the spec does not pass the checks.

Only runs checks that are considered necessary for the specification to be functional.

[ GitHub ]

#validate_required_attributes (private)

[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 271

def validate_required_attributes
  Gem::Specification.required_attributes.each do |symbol|
    unless @specification.send symbol
      error "missing value for attribute #{symbol}"
    end
  end
end

#validate_rubygems_version (private)

[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 261

def validate_rubygems_version
  return unless packaging

  rubygems_version = @specification.rubygems_version

  return if rubygems_version == Gem::VERSION

  error "expected RubyGems version #{Gem::VERSION}, was #{rubygems_version}"
end

#validate_rust_extensions(builder) (private)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 474

def validate_rust_extensions(builder) # :nodoc:
  rust_extension = @specification.extensions.any? {|s| builder.builder_for(s).is_a? Gem::Ext::CargoBuilder }
  missing_cargo_lock = !@specification.files.any? {|f| f.end_with?("Cargo.lock") }

  error <<-ERROR if rust_extension && missing_cargo_lock
You have specified rust based extension, but Cargo.lock is not part of the gem files. Please run `cargo generate-lockfile` or any other command to generate Cargo.lock and ensure it is added to your gem files section in gemspec.
  ERROR
end

#validate_self_inclusion_in_files_list (private)

[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 309

def validate_self_inclusion_in_files_list
  file_name = @specification.file_name

  return unless @specification.files.include?(file_name)

  error "#{@specification.full_name} contains itself (#{file_name}), check your files list"
end

#validate_shebang_line_in(executable) (private)

[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 453

def validate_shebang_line_in(executable)
  executable_path = File.join(@specification.bindir, executable)
  return if File.read(executable_path, 2) == "#!"

  warning "#{executable_path} is missing #! line"
end

#validate_specification_version (private)

[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 317

def validate_specification_version
  return if @specification.specification_version.is_a?(Integer)

  error "specification_version must be an Integer (did you mean version?)"
end

#validate_values (private)

[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 427

def validate_values
  %w[author homepage summary files].each do |attribute|
    validate_attribute_present(attribute)
  end

  if @specification.description == @specification.summary
    warning "description and summary are identical"
  end

  # TODO: raise at some given date
  warning "deprecated autorequire specified" if @specification.autorequire

  @specification.executables.each do |executable|
    validate_shebang_line_in(executable)
  end

  @specification.files.select {|f| File.symlink?(f) }.each do |file|
    warning "#{file} is a symlink, which is not supported on all platforms"
  end
end

#warning(statement) (private)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/rubygems/specification_policy.rb', line 492

def warning(statement) # :nodoc:
  @warnings += 1

  alert_warning statement
end