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 26

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 35

attr_accessor :packaging

Instance Method Details

#error(statement) (private)

This method is for internal use only.
[ GitHub ]

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

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 519

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 46

def validate(strict = false)
  validate_required!
  validate_required_metadata!

  validate_optional(strict) if packaging || strict

  true
end

#validate_array_attribute(field) (private)

[ GitHub ]

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

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.is_a?(klass) || (field == :licenses && x.nil?) }
    error "#{field} must be an Array of #{klass}"
  end
end

#validate_array_attributes (private)

[ GitHub ]

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

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 447

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 338

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.

[ GitHub ]

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

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
  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 171

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 465

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

  validate_rake_extensions(builder)
  validate_rust_extensions(builder)
end

#validate_for_resolution

[ GitHub ]

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

def validate_for_resolution
  validate_required!
end

#validate_lazy_metadata (private)

[ GitHub ]

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

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 LAZY_PATTERN.match?(@specification.description)
    error "#{LAZY} is not a description"
  end

  if LAZY_PATTERN.match?(@specification.summary)
    error "#{LAZY} is not a summary"
  end

  homepage = @specification.homepage

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

#validate_licenses (private)

[ GitHub ]

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

def validate_licenses
  licenses = @specification.licenses

  licenses.each do |license|
    next if Gem::Licenses.match?(license) || license.nil?
    license_id_deprecated = Gem::Licenses.deprecated_license_id?(license)
    exception_id_deprecated = Gem::Licenses.deprecated_exception_id?(license)
    suggestions = Gem::Licenses.suggestions(license)

    if license_id_deprecated
      main_message = "License identifier '#{license}' is deprecated"
    elsif exception_id_deprecated
      main_message = "Exception identifier at '#{license}' is deprecated"
    else
      main_message = "License identifier '#{license}' is invalid"
    end

    message = <<-WARNING
#{main_message}. Use an identifier from
https://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard license,
or set it to nil if you don't want to specify a license.
    WARNING
    message += "Did you mean #{suggestions.map {|s| "'#{s}'" }.join(", ")}?\n" unless suggestions.nil?
    warning(message)
  end

  warning <<-WARNING if licenses.empty?
licenses is empty, but is recommended. Use an license identifier from
https://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard license,
or set it to nil if you don't want to specify a license.
  WARNING
end

#validate_licenses_length (private)

[ GitHub ]

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

def validate_licenses_length
  licenses = @specification.licenses

  licenses.each do |license|
    next if license.nil?

    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 136

def 
   = @specification.

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

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

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

    unless value.is_a?(String)
      error "#{entry} value must be a String"
    end

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

    next unless METADATA_LINK_KEYS.include? key
    unless VALID_URI_PATTERN.match?(value)
      error "#{entry} has invalid link: #{value.inspect}"
    end
  end
end

#validate_name (private)

[ GitHub ]

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

def validate_name
  name = @specification.name

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

#validate_nil_attributes (private)

[ GitHub ]

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

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 284

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 100

def validate_optional(strict)
  validate_licenses

  validate_permissions

  validate_values

  validate_dependencies

  validate_required_ruby_version

  validate_extensions

  validate_removed_attributes

  validate_unique_links

  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 217

def validate_permissions
  return if Gem.win_platform?

  @specification.files.each do |file|
    next unless File.file?(file)
    next if File.stat(file).mode & 0o444 == 0o444
    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 308

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 482

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" && d.type == :runtime }

  warning <<-WARNING if rake_extension && !rake_dependency
You have specified rake based extension, but rake is not added as runtime dependency. It is recommended to add rake as a runtime 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 459

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 278

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 256

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_required_metadata!

[ GitHub ]

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

def validate_required_metadata!
  

  
end

#validate_required_ruby_version

[ GitHub ]

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

def validate_required_ruby_version
  if @specification.required_ruby_version.requirements == [Gem::Requirement::DefaultRequirement]
    warning "make sure you specify the oldest ruby version constraint (like \">= 3.0\") that you want your gem to support by setting the `required_ruby_version` gemspec attribute"
  end
end

#validate_rubygems_version (private)

[ GitHub ]

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

def validate_rubygems_version
  return unless packaging

  rubygems_version = @specification.rubygems_version

  return if rubygems_version == Gem::VERSION

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

  @specification.rubygems_version = Gem::VERSION
end

#validate_rust_extensions(builder) (private)

This method is for internal use only.
[ GitHub ]

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

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 294

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 452

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 302

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 426

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 507

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

  alert_warning statement
end