1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/lib/rubygems/specification_policy.rb
svn 5018141974 * remove trailing spaces.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@63529 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2018-05-30 13:01:37 +00:00

410 lines
12 KiB
Ruby

require 'delegate'
require 'uri'
class Gem::SpecificationPolicy < SimpleDelegator
VALID_NAME_PATTERN = /\A[a-zA-Z0-9\.\-\_]+\z/ # :nodoc:
VALID_URI_PATTERN = %r{\Ahttps?:\/\/([^\s:@]+:[^\s:@]*@)?[A-Za-z\d\-]+(\.[A-Za-z\d\-]+)+\.?(:\d{1,5})?([\/?]\S*)?\z} # :nodoc:
METADATA_LINK_KEYS = %w[
bug_tracker_uri
changelog_uri
documentation_uri
homepage_uri
mailing_list_uri
source_code_uri
wiki_uri
] # :nodoc:
##
# If set to true, run packaging-specific checks, as well.
attr_accessor :packaging
##
# Checks that the specification contains all required fields, and does a
# very basic sanity check.
#
# Raises InvalidSpecificationException if the spec does not pass the
# checks.
def validate
validate_nil_attributes
validate_rubygems_version
validate_required_attributes
validate_name
validate_require_paths
keep_only_files_and_directories
validate_non_files
validate_self_inclusion_in_files_list
validate_specification_version
validate_platform
validate_array_attributes
validate_authors_field
validate_metadata
validate_licenses
validate_permissions
validate_lazy_metadata
validate_values
validate_dependencies
true
end
##
# Implementation for Specification#validate_metadata
def validate_metadata
unless Hash === metadata then
raise Gem::InvalidSpecificationException,
'metadata must be a hash'
end
metadata.each do |key, value|
if !key.kind_of?(String) then
raise Gem::InvalidSpecificationException,
"metadata keys must be a String"
end
if key.size > 128 then
raise Gem::InvalidSpecificationException,
"metadata key too large (#{key.size} > 128)"
end
if !value.kind_of?(String) then
raise Gem::InvalidSpecificationException,
"metadata values must be a String"
end
if value.size > 1024 then
raise Gem::InvalidSpecificationException,
"metadata value too large (#{value.size} > 1024)"
end
if METADATA_LINK_KEYS.include? key then
if value !~ VALID_URI_PATTERN then
raise Gem::InvalidSpecificationException,
"metadata['#{key}'] has invalid link: #{value.inspect}"
end
end
end
end
##
# Implementation for Specification#validate_dependencies
def validate_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 = []
warning_messages = []
dependencies.each do |dep|
if prev = seen[dep.type][dep.name] then
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
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 && !version.prerelease?
overly_strict = dep.requirement.requirements.length == 1 &&
dep.requirement.requirements.any? do |op, version|
op == '~>' and
not version.prerelease? and
version.segments.length > 2 and
version.segments.first != 0
end
if overly_strict then
_, dep_version = dep.requirement.requirements.first
base = dep_version.segments.first 2
upper_bound = dep_version.segments.first(dep_version.segments.length - 1)
upper_bound[-1] += 1
warning_messages << <<-WARNING
pessimistic dependency on #{dep} may be overly strict
if #{dep.name} is semantically versioned, use:
add_#{dep.type}_dependency '#{dep.name}', '~> #{base.join '.'}', '>= #{dep_version}'
if #{dep.name} is not semantically versioned, you can bypass this warning with:
add_#{dep.type}_dependency '#{dep.name}', '>= #{dep_version}', '< #{upper_bound.join '.'}.a'
WARNING
end
open_ended = dep.requirement.requirements.all? do |op, version|
not version.prerelease? and (op == '>' or op == '>=')
end
if open_ended then
op, dep_version = dep.requirement.requirements.first
base = dep_version.segments.first 2
bugfix = if op == '>' then
", '> #{dep_version}'"
elsif op == '>=' and base != dep_version.segments then
", '>= #{dep_version}'"
end
warning_messages << <<-WARNING
open-ended dependency on #{dep} is not recommended
if #{dep.name} is semantically versioned, use:
add_#{dep.type}_dependency '#{dep.name}', '~> #{base.join '.'}'#{bugfix}
WARNING
end
end
if error_messages.any? then
raise Gem::InvalidSpecificationException, error_messages.join
end
if warning_messages.any? then
warning_messages.each { |warning_message| warning warning_message }
end
end
##
# Issues a warning for each file to be packaged which is world-readable.
#
# Implementation for Specification#validate_permissions
def validate_permissions
return if Gem.win_platform?
files.each do |file|
next unless File.file?(file)
next if File.stat(file).mode & 0444 == 0444
warning "#{file} is not world-readable"
end
executables.each do |name|
exec = File.join bindir, name
next unless File.file?(exec)
next if File.stat(exec).executable?
warning "#{exec} is not executable"
end
end
private
def validate_nil_attributes
nil_attributes = Gem::Specification.non_nil_attributes.select do |attrname|
__getobj__.instance_variable_get("@#{attrname}").nil?
end
return if nil_attributes.empty?
raise Gem::InvalidSpecificationException,
"#{nil_attributes.join ', '} must not be nil"
end
def validate_rubygems_version
return unless packaging
return if rubygems_version == Gem::VERSION
raise Gem::InvalidSpecificationException,
"expected RubyGems version #{Gem::VERSION}, was #{rubygems_version}"
end
def validate_required_attributes
Gem::Specification.required_attributes.each do |symbol|
unless send symbol then
raise Gem::InvalidSpecificationException,
"missing value for attribute #{symbol}"
end
end
end
def validate_name
if !name.is_a?(String) then
raise Gem::InvalidSpecificationException,
"invalid value for attribute name: \"#{name.inspect}\" must be a string"
elsif name !~ /[a-zA-Z]/ then
raise Gem::InvalidSpecificationException,
"invalid value for attribute name: #{name.dump} must include at least one letter"
elsif name !~ VALID_NAME_PATTERN then
raise Gem::InvalidSpecificationException,
"invalid value for attribute name: #{name.dump} can only include letters, numbers, dashes, and underscores"
end
end
def validate_require_paths
return unless raw_require_paths.empty?
raise Gem::InvalidSpecificationException,
'specification must have at least one require_path'
end
def validate_non_files
return unless packaging
non_files = files.reject {|x| File.file?(x) || File.symlink?(x)}
unless non_files.empty? then
raise Gem::InvalidSpecificationException,
"[\"#{non_files.join "\", \""}\"] are not files"
end
end
def validate_self_inclusion_in_files_list
return unless files.include?(file_name)
raise Gem::InvalidSpecificationException,
"#{full_name} contains itself (#{file_name}), check your files list"
end
def validate_specification_version
return if specification_version.is_a?(Integer)
raise Gem::InvalidSpecificationException,
'specification_version must be an Integer (did you mean version?)'
end
def validate_platform
case platform
when Gem::Platform, Gem::Platform::RUBY then # ok
else
raise Gem::InvalidSpecificationException,
"invalid platform #{platform.inspect}, see Gem::Platform"
end
end
def validate_array_attributes
Gem::Specification.array_attributes.each do |field|
validate_array_attribute(field)
end
end
def validate_array_attribute(field)
val = self.send(field)
klass = case field
when :dependencies then
Gem::Dependency
else
String
end
unless Array === val and val.all? {|x| x.kind_of?(klass)} then
raise(Gem::InvalidSpecificationException,
"#{field} must be an Array of #{klass}")
end
end
def validate_authors_field
return unless authors.empty?
raise Gem::InvalidSpecificationException,
"authors may not be empty"
end
def validate_licenses
licenses.each { |license|
if license.length > 64 then
raise Gem::InvalidSpecificationException,
"each license must be 64 characters or less"
end
if !Gem::Licenses.match?(license) then
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
}
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
LAZY = '"FIxxxXME" or "TOxxxDO"'.gsub(/xxx/, '')
LAZY_PATTERN = /FI XME|TO DO/x
HOMEPAGE_URI_PATTERN = /\A[a-z][a-z\d+.-]*:/i
def validate_lazy_metadata
unless authors.grep(LAZY_PATTERN).empty? then
raise Gem::InvalidSpecificationException, "#{LAZY} is not an author"
end
unless Array(email).grep(LAZY_PATTERN).empty? then
raise Gem::InvalidSpecificationException, "#{LAZY} is not an email"
end
if description =~ LAZY_PATTERN then
raise Gem::InvalidSpecificationException, "#{LAZY} is not a description"
end
if summary =~ LAZY_PATTERN then
raise Gem::InvalidSpecificationException, "#{LAZY} is not a summary"
end
# Make sure a homepage is valid HTTP/HTTPS URI
if homepage and not homepage.empty?
begin
homepage_uri = URI.parse(homepage)
unless [URI::HTTP, URI::HTTPS].member? homepage_uri.class
raise Gem::InvalidSpecificationException, "\"#{homepage}\" is not a valid HTTP URI"
end
rescue URI::InvalidURIError
raise Gem::InvalidSpecificationException, "\"#{homepage}\" is not a valid HTTP URI"
end
end
end
def validate_values
%w[author homepage summary files].each do |attribute|
validate_attribute_present(attribute)
end
if description == summary then
warning "description and summary are identical"
end
# TODO: raise at some given date
warning "deprecated autorequire specified" if autorequire
executables.each do |executable|
validate_shebang_line_in(executable)
end
files.select { |f| File.symlink?(f) }.each do |file|
warning "#{file} is a symlink, which is not supported on all platforms"
end
end
def validate_attribute_present(attribute)
value = self.send attribute
warning("no #{attribute} specified") if value.nil? || value.empty?
end
def validate_shebang_line_in(executable)
executable_path = File.join(bindir, executable)
return if File.read(executable_path, 2) == '#!'
warning "#{executable_path} is missing #! line"
end
end