summaryrefslogtreecommitdiff
path: root/lib/rubygems/specification.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rubygems/specification.rb')
-rw-r--r--lib/rubygems/specification.rb1046
1 files changed, 721 insertions, 325 deletions
diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb
index 70a3fd09b4..1d290c8af5 100644
--- a/lib/rubygems/specification.rb
+++ b/lib/rubygems/specification.rb
@@ -4,32 +4,56 @@
# See LICENSE.txt for permissions.
#++
+##
+# The Specification class contains the information for a Gem. Typically
+# defined in a .gemspec file or a Rakefile, and looks like this:
+#
+# Gem::Specification.new do |s|
+# s.name = 'example'
+# s.version = '0.1.0'
+# s.summary = "This is an example!"
+# s.description = "Much longer explanation of the example!"
+# s.authors = ["Ruby Coder"]
+# s.email = 'rubycoder@example.com'
+# s.files = ["lib/example.rb"]
+# s.homepage = 'http://rubygems.org/gems/example'
+# end
+#
+# Starting in RubyGems 1.9.0, a Specification can hold arbitrary
+# metadata. This metadata is accessed via Specification#metadata
+# and has the following restrictions:
+#
+# * Must be a Hash object
+# * All keys and values must be Strings
+# * Keys can be a maximum of 128 bytes and values can be a
+# maximum of 1024 bytes
+# * All strings must be UTF8, no binary data is allowed
+#
+# For example, to add metadata for the location of a bugtracker:
+#
+# s.metadata = { "bugtracker" => "http://somewhere.com/blah" }
+#
+
+
require 'rubygems/version'
require 'rubygems/requirement'
require 'rubygems/platform'
-require "rubygems/deprecate"
+require 'rubygems/deprecate'
# :stopdoc:
-class Date; end # for ruby_code if date.rb wasn't required
+# date.rb can't be loaded for `make install` due to miniruby
+# Date is needed for old gems that stored #date as Date instead of Time.
+class Date; end
# :startdoc:
-##
-# The Specification class contains the metadata for a Gem. Typically
-# defined in a .gemspec file or a Rakefile, and looks like this:
-#
-# spec = Gem::Specification.new do |s|
-# s.name = 'example'
-# s.version = '1.0'
-# s.summary = 'Example gem specification'
-# ...
-# end
-#
-# For a great way to package gems, use Hoe.
-
class Gem::Specification
+ # REFACTOR: Consider breaking out this version stuff into a separate
+ # module. There's enough special stuff around it that it may justify
+ # a separate class.
+
##
- # The the version number of a specification that does not specify one
+ # The version number of a specification that does not specify one
# (i.e. RubyGems 0.7 or earlier).
NONEXISTENT_SPECIFICATION_VERSION = -1
@@ -49,17 +73,37 @@ class Gem::Specification
# 2 0.9.5 2007-10-01 Added "required_rubygems_version"
# Now forward-compatible with future versions
# 3 1.3.2 2009-01-03 Added Fixnum validation to specification_version
+ # 4 1.9.0 2011-06-07 Added metadata
#--
# When updating this number, be sure to also update #to_ruby.
#
# NOTE RubyGems < 1.2 cannot load specification versions > 2.
- CURRENT_SPECIFICATION_VERSION = 3
-
- # :stopdoc:
+ CURRENT_SPECIFICATION_VERSION = 4
+
+ ##
+ # An informal list of changes to the specification. The highest-valued
+ # key should be equal to the CURRENT_SPECIFICATION_VERSION.
+
+ SPECIFICATION_VERSION_HISTORY = {
+ -1 => ['(RubyGems versions up to and including 0.7 did not have versioned specifications)'],
+ 1 => [
+ 'Deprecated "test_suite_file" in favor of the new, but equivalent, "test_files"',
+ '"test_file=x" is a shortcut for "test_files=[x]"'
+ ],
+ 2 => [
+ 'Added "required_rubygems_version"',
+ 'Now forward-compatible with future versions',
+ ],
+ 3 => [
+ 'Added Fixnum validation to the specification_version'
+ ],
+ 4 => [
+ 'Added sandboxed freeform metadata to the specification version.'
+ ]
+ }
- # version => # of fields
- MARSHAL_FIELDS = { -1 => 16, 1 => 16, 2 => 16, 3 => 17 }
+ MARSHAL_FIELDS = { -1 => 16, 1 => 16, 2 => 16, 3 => 17, 4 => 18 }
today = Time.now.utc
TODAY = Time.utc(today.year, today.month, today.day)
@@ -95,6 +139,7 @@ class Gem::Specification
:files => [],
:homepage => nil,
:licenses => [],
+ :metadata => {},
:name => nil,
:platform => Gem::Platform::RUBY,
:post_install_message => nil,
@@ -122,19 +167,37 @@ class Gem::Specification
# :section: Required gemspec attributes
##
- # This gem's name
+ # This gem's name.
+ #
+ # Usage:
+ #
+ # spec.name = 'rake'
attr_accessor :name
##
- # This gem's version
+ # This gem's version.
+ #
+ # The version string can contain numbers and periods, such as +1.0.0+.
+ # A gem is a 'prerelease' gem if the version has a letter in it, such as
+ # +1.0.0.pre+.
+ #
+ # Usage:
+ #
+ # spec.version = '0.4.1'
attr_reader :version
##
- # Paths in the gem to add to $LOAD_PATH when this gem is activated.
+ # Paths in the gem to add to <tt>$LOAD_PATH</tt> when this gem is activated.
#
- # The default ['lib'] is typically sufficient.
+ # Usage:
+ #
+ # # If all library files are in the root directory...
+ # spec.require_path = '.'
+ #
+ # # If you have 'lib' and 'ext' directories...
+ # spec.require_paths << 'ext'
attr_accessor :require_paths
@@ -146,32 +209,113 @@ class Gem::Specification
attr_accessor :rubygems_version
##
- # The Gem::Specification version of this gemspec.
+ # A short summary of this gem's description. Displayed in `gem list -d`.
#
- # Do not set this, it is set automatically when the gem is packaged.
+ # The description should be more detailed than the summary.
+ #
+ # Usage:
+ #
+ # spec.summary = "This is a small summary of my gem"
- attr_accessor :specification_version
+ attr_reader :summary
##
- # A short summary of this gem's description. Displayed in `gem list -d`.
+ # The platform this gem runs on.
+ #
+ # This is usually Gem::Platform::RUBY or Gem::Platform::CURRENT.
+ #
+ # Most gems contain pure Ruby code; they should simply leave the default value
+ # in place. Some gems contain C (or other) code to be compiled into a Ruby
+ # “extension”. The should leave the default value in place unless their code
+ # will only compile on a certain type of system. Some gems consist of
+ # pre-compiled code (“binary gems”). It’s especially important that they set
+ # the platform attribute appropriately. A shortcut is to set the platform to
+ # Gem::Platform::CURRENT, which will cause the gem builder to set the platform
+ # to the appropriate value for the system on which the build is being performed.
+ #
+ # If this attribute is set to a non-default value, it will be included in the
+ # filename of the gem when it is built, e.g. fxruby-1.2.0-win32.gem.
+ #
+ # Usage:
#
- # The description should be more detailed than the summary. For example,
- # you might wish to copy the entire README into the description.
+ # spec.platform = Gem::Platform::Win32
- attr_reader :summary
+ def platform= platform
+ if @original_platform.nil? or
+ @original_platform == Gem::Platform::RUBY then
+ @original_platform = platform
+ end
- ######################################################################
- # :section: Optional gemspec attributes
+ case platform
+ when Gem::Platform::CURRENT then
+ @new_platform = Gem::Platform.local
+ @original_platform = @new_platform.to_s
+
+ when Gem::Platform then
+ @new_platform = platform
+
+ # legacy constants
+ when nil, Gem::Platform::RUBY then
+ @new_platform = Gem::Platform::RUBY
+ when 'mswin32' then # was Gem::Platform::WIN32
+ @new_platform = Gem::Platform.new 'x86-mswin32'
+ when 'i586-linux' then # was Gem::Platform::LINUX_586
+ @new_platform = Gem::Platform.new 'x86-linux'
+ when 'powerpc-darwin' then # was Gem::Platform::DARWIN
+ @new_platform = Gem::Platform.new 'ppc-darwin'
+ else
+ @new_platform = Gem::Platform.new platform
+ end
+
+ @platform = @new_platform.to_s
+
+ invalidate_memoized_attributes
+
+ @new_platform
+ end
##
- # Autorequire was used by old RubyGems to automatically require a file.
+ # Files included in this gem. You cannot append to this accessor, you must
+ # assign to it.
#
- # Deprecated: It is neither supported nor functional.
+ # Only add files you can require to this list, not directories, etc.
+ #
+ # Directories are automatically stripped from this list when building a gem,
+ # other non-files cause an error.
+ #
+ # Usage:
+ #
+ # require 'rake'
+ # spec.files = FileList['lib/**/*.rb',
+ # 'bin/*',
+ # '[A-Z]*',
+ # 'test/**/*'].to_a
+ #
+ # # or without Rake...
+ # spec.files = Dir['lib/**/*.rb'] + Dir['bin/*']
+ # spec.files += Dir['[A-Z]*'] + Dir['test/**/*']
+ # spec.files.reject! { |fn| fn.include? "CVS" }
- attr_accessor :autorequire
+ def files
+ # DO NOT CHANGE TO ||= ! This is not a normal accessor. (yes, it sucks)
+ # DOC: Why isn't it normal? Why does it suck? How can we fix this?
+ @files = [@files,
+ @test_files,
+ add_bindir(@executables),
+ @extra_rdoc_files,
+ @extensions,
+ ].flatten.uniq.compact
+ end
+
+ ######################################################################
+ # :section: Optional gemspec attributes
##
# The path in the gem for executable scripts. Usually 'bin'
+ #
+ # Usage:
+ #
+ # spec.bindir = 'bin'
attr_accessor :bindir
@@ -183,39 +327,241 @@ class Gem::Specification
##
# A long description of this gem
+ #
+ # The description should be more detailed than the summary.
+ #
+ # Usage:
+ #
+ # spec.description = <<-EOF
+ # Rake is a Make-like program implemented in Ruby. Tasks and
+ # dependencies are specified in standard Ruby syntax.
+ # EOF
attr_reader :description
##
- # Sets the default executable for this gem.
+ # A contact email for this gem
#
- # Deprecated: You must now specify the executable name to Gem.bin_path.
+ # Usage:
+ #
+ # spec.email = 'john.jones@example.com'
+ # spec.email = ['jack@example.com', 'jill@example.com']
- attr_writer :default_executable
+ attr_accessor :email
##
- # A contact email for this gem
+ # The URL of this gem's home page
+ #
+ # Usage:
#
- # If you are providing multiple authors and multiple emails they should be
- # in the same order such that:
+ # spec.homepage = 'http://rake.rubyforge.org'
+
+ attr_accessor :homepage
+
+ ##
+ # A message that gets displayed after the gem is installed.
#
- # Hash[*spec.authors.zip(spec.emails).flatten]
+ # Usage:
#
- # Gives a hash of author name to email address.
+ # spec.post_install_message = "Thanks for installing!"
- attr_accessor :email
+ attr_accessor :post_install_message
##
- # The URL of this gem's home page
+ # The key used to sign this gem. See Gem::Security for details.
- attr_accessor :homepage
+ attr_accessor :signing_key
##
- # True when this gemspec has been activated. This attribute is not persisted.
+ # :attr_accessor: metadata
+ #
+ # Arbitrary metadata for this gem. An instance of Hash.
+ #
+ # metadata is simply a Symbol => String association that contains arbitary
+ # data that could be useful to other consumers.
- attr_accessor :loaded # :nodoc:
+ attr_accessor :metadata
- alias :loaded? :loaded # :nodoc:
+ ##
+ # Adds a development dependency named +gem+ with +requirements+ to this
+ # gem.
+ #
+ # Usage:
+ #
+ # spec.add_development_dependency 'example', '~> 1.1', '>= 1.1.4'
+ #
+ # Development dependencies aren't installed by default and aren't
+ # activated when a gem is required.
+
+ def add_development_dependency(gem, *requirements)
+ add_dependency_with_type(gem, :development, *requirements)
+ end
+
+ ##
+ # Adds a runtime dependency named +gem+ with +requirements+ to this gem.
+ #
+ # Usage:
+ #
+ # spec.add_runtime_dependency 'example', '~> 1.1', '>= 1.1.4'
+
+ def add_runtime_dependency(gem, *requirements)
+ add_dependency_with_type(gem, :runtime, *requirements)
+ end
+
+ ##
+ # Singular writer for #authors
+ #
+ # Usage:
+ #
+ # spec.author = 'John Jones'
+
+ def author= o
+ self.authors = [o]
+ end
+
+ ##
+ # Sets the list of authors, ensuring it is an array.
+ #
+ # Usage:
+ #
+ # spec.authors = ['John Jones', 'Mary Smith']
+
+ def authors= value
+ @authors = Array(value).flatten.grep(String)
+ end
+
+ ##
+ # Executables included in the gem.
+ #
+ # For example, the rake gem has rake as an executable. You don’t specify the
+ # full path (as in bin/rake); all application-style files are expected to be
+ # found in bindir. These files must be executable ruby files. Files that
+ # use bash or other interpreters will not work.
+ #
+ # Usage:
+ #
+ # spec.executables << 'rake'
+
+ def executables
+ @executables ||= []
+ end
+
+ ##
+ # Extensions to build when installing the gem, specifically the paths to
+ # extconf.rb-style files used to compile extensions.
+ #
+ # These files will be run when the gem is installed, causing the C (or
+ # whatever) code to be compiled on the user’s machine.
+ #
+ # Usage:
+ #
+ # spec.extensions << 'ext/rmagic/extconf.rb'
+
+ def extensions
+ @extensions ||= []
+ end
+
+ ##
+ # Extra files to add to RDoc such as README or doc/examples.txt
+ #
+ # When the user elects to generate the RDoc documentation for a gem (typically
+ # at install time), all the library files are sent to RDoc for processing.
+ # This option allows you to have some non-code files included for a more
+ # complete set of documentation.
+ #
+ # Usage:
+ #
+ # spec.extra_rdoc_files = ['README', 'doc/user-guide.txt']
+
+ def extra_rdoc_files
+ @extra_rdoc_files ||= []
+ end
+
+ ##
+ # The license for this gem.
+ #
+ # The license must be a short name, no more than 64 characters.
+ #
+ # This should just be the name of your license. The full
+ # text of the license should be inside of the gem when you build it.
+ #
+ # Usage:
+ # spec.license = 'MIT'
+
+ def license=o
+ self.licenses = [o]
+ end
+
+ ##
+ # The license(s) for the library.
+ #
+ # Each license must be a short name, no more than 64 characters.
+ #
+ # This should just be the name of your license. The full
+ # text of the license should be inside of the gem when you build it.
+ #
+ # Usage:
+ # spec.licenses = ['MIT', 'GPL-2']
+
+ def licenses= licenses
+ @licenses = Array licenses
+ end
+
+ ##
+ # Specifies the rdoc options to be used when generating API documentation.
+ #
+ # Usage:
+ #
+ # spec.rdoc_options << '--title' << 'Rake -- Ruby Make' <<
+ # '--main' << 'README' <<
+ # '--line-numbers'
+
+ def rdoc_options
+ @rdoc_options ||= []
+ end
+
+ ##
+ # The version of ruby required by this gem
+ #
+ # Usage:
+ #
+ # # If it will work with 1.8.6 or greater...
+ # spec.required_ruby_version = '>= 1.8.6'
+ #
+ # # Hopefully by now:
+ # spec.required_ruby_version = '>= 1.9.2'
+
+ def required_ruby_version= req
+ @required_ruby_version = Gem::Requirement.create req
+ end
+
+ ##
+ # Lists the external (to RubyGems) requirements that must be met for this gem
+ # to work. It’s simply information for the user.
+ #
+ # Usage:
+ #
+ # spec.requirements << 'libmagick, v6.0'
+ # spec.requirements << 'A good graphics card'
+
+ def requirements
+ @requirements ||= []
+ end
+
+ ##
+ # A collection of unit test files. They will be loaded as unit tests when
+ # the user requests a gem to be unit tested.
+ #
+ # Usage:
+ # spec.test_files = Dir.glob('test/tc_*.rb')
+ # spec.test_files = ['tests/test-suite.rb']
+
+ def test_files= files
+ @test_files = Array files
+ end
+
+ ######################################################################
+ # :section: Specification internals
##
# True when this gemspec has been activated. This attribute is not persisted.
@@ -225,6 +571,20 @@ class Gem::Specification
alias :activated? :activated
##
+ # Autorequire was used by old RubyGems to automatically require a file.
+ #
+ # Deprecated: It is neither supported nor functional.
+
+ attr_accessor :autorequire
+
+ ##
+ # Sets the default executable for this gem.
+ #
+ # Deprecated: You must now specify the executable name to Gem.bin_path.
+
+ attr_writer :default_executable
+
+ ##
# Path this gemspec was loaded from. This attribute is not persisted.
attr_reader :loaded_from
@@ -235,11 +595,6 @@ class Gem::Specification
attr_writer :original_platform # :nodoc:
##
- # A message that gets displayed after the gem is installed
-
- attr_accessor :post_install_message
-
- ##
# The version of ruby required by this gem
attr_reader :required_ruby_version
@@ -248,7 +603,6 @@ class Gem::Specification
# The RubyGems version required by this gem
attr_reader :required_rubygems_version
-
##
# The rubyforge project this gem lives under. i.e. RubyGems'
# rubyforge_project is "rubygems".
@@ -256,22 +610,49 @@ class Gem::Specification
attr_accessor :rubyforge_project
##
- # The key used to sign this gem. See Gem::Security for details.
+ # The Gem::Specification version of this gemspec.
+ #
+ # Do not set this, it is set automatically when the gem is packaged.
- attr_accessor :signing_key
+ attr_accessor :specification_version
- def self._all # :nodoc:
- unless defined?(@@all) && @@all then
- specs = {}
+ class << self
+ def default_specifications_dir
+ File.join(Gem.default_dir, "specifications", "default")
+ end
- self.dirs.each { |dir|
+ private
+ def each_spec(search_dirs) # :nodoc:
+ search_dirs.each { |dir|
Dir[File.join(dir, "*.gemspec")].each { |path|
spec = Gem::Specification.load path.untaint
# #load returns nil if the spec is bad, so we just ignore
# it at this stage
- specs[spec.full_name] ||= spec if spec
+ yield(spec) if spec
}
}
+ end
+
+ def each_default(&block) # :nodoc:
+ each_spec([default_specifications_dir],
+ &block)
+ end
+
+ def each_normal(&block) # :nodoc:
+ each_spec(dirs, &block)
+ end
+ end
+
+ def self._all # :nodoc:
+ unless defined?(@@all) && @@all then
+
+ specs = {}
+ each_default do |spec|
+ specs[spec.full_name] ||= spec
+ end
+ each_normal do |spec|
+ specs[spec.full_name] ||= spec
+ end
@@all = specs.values
@@ -289,6 +670,15 @@ class Gem::Specification
end
##
+ # Loads the default specifications. It should be called only once.
+
+ def self.load_defaults
+ each_default do |spec|
+ Gem.register_default_spec(spec)
+ end
+ end
+
+ ##
# Adds +spec+ to the known specifications, keeping the collection
# properly sorted.
@@ -380,7 +770,7 @@ class Gem::Specification
def self.dirs
@@dirs ||= Gem.path.collect { |dir|
- File.join dir, "specifications"
+ File.join dir.dup.untaint, "specifications"
}
end
@@ -445,11 +835,21 @@ class Gem::Specification
end
##
+ # Return the best specification that contains the file matching +path+
+ # amongst the specs that are not activated.
+
+ def self.find_inactive_by_path path
+ self.find { |spec|
+ spec.contains_requirable_file? path unless spec.activated?
+ }
+ end
+
+ ##
# Return currently unresolved specs that contain the file matching +path+.
def self.find_in_unresolved path
# TODO: do we need these?? Kill it
- specs = Gem.unresolved_deps.values.map { |dep| dep.to_specs }.flatten
+ specs = unresolved_deps.values.map { |dep| dep.to_specs }.flatten
specs.find_all { |spec| spec.contains_requirable_file? path }
end
@@ -459,7 +859,7 @@ class Gem::Specification
# specs that contain the file matching +path+.
def self.find_in_unresolved_tree path
- specs = Gem.unresolved_deps.values.map { |dep| dep.to_specs }.flatten
+ specs = unresolved_deps.values.map { |dep| dep.to_specs }.flatten
specs.reverse_each do |spec|
trails = []
@@ -498,13 +898,9 @@ class Gem::Specification
raise Gem::Exception, "YAML data doesn't evaluate to gem specification"
end
- unless (spec.instance_variables.include? '@specification_version' or
- spec.instance_variables.include? :@specification_version) and
- spec.instance_variable_get :@specification_version
- spec.instance_variable_set :@specification_version,
- NONEXISTENT_SPECIFICATION_VERSION
- end
-
+ spec.instance_eval { @specification_version ||= NONEXISTENT_SPECIFICATION_VERSION }
+ spec.reset_nil_attributes_to_default
+
spec
end
@@ -533,9 +929,9 @@ class Gem::Specification
# Loads Ruby format gemspec from +file+.
def self.load file
- return unless file && File.file?(file)
-
+ return unless file
file = file.dup.untaint
+ return unless File.file?(file)
code = if defined? Encoding
File.read file, :mode => 'r:UTF-8:-'
@@ -595,9 +991,9 @@ class Gem::Specification
latest_specs.each do |local|
dependency = Gem::Dependency.new local.name, ">= #{local.version}"
- remotes = fetcher.find_matching dependency
- remotes = remotes.map { |(_, version, _), _| version }
- latest = remotes.sort.last
+ remotes, _ = fetcher.search_for_dependency dependency
+ remotes = remotes.map { |n, _| n.version }
+ latest = remotes.sort.last
outdateds << local.name if latest and local.version < latest
end
@@ -635,14 +1031,27 @@ class Gem::Specification
def self.reset
@@dirs = nil
- # from = caller.first(10).reject { |s| s =~ /minitest/ }
- # warn ""
- # warn "NOTE: Specification.reset from #{from.inspect}"
- Gem.pre_reset_hooks.each { |hook| hook.call }
+ Gem.pre_reset_hooks.each { |hook| hook.call }
@@all = nil
+ unresolved = unresolved_deps
+ unless unresolved.empty? then
+ w = "W" + "ARN"
+ warn "#{w}: Unresolved specs during Gem::Specification.reset:"
+ unresolved.values.each do |dep|
+ warn " #{dep}"
+ end
+ warn "#{w}: Clearing out unresolved specs."
+ warn "Please report a bug if this causes problems."
+ unresolved.clear
+ end
Gem.post_reset_hooks.each { |hook| hook.call }
end
+ # DOC: This method needs documented or nodoc'd
+ def self.unresolved_deps
+ @unresolved_deps ||= Hash.new { |h, n| h[n] = Gem::Dependency.new n }
+ end
+
##
# Load custom marshal format, re-initializing defaults as needed
@@ -690,6 +1099,7 @@ class Gem::Specification
spec.instance_variable_set :@new_platform, array[16]
spec.instance_variable_set :@platform, array[16].to_s
spec.instance_variable_set :@license, array[17]
+ spec.instance_variable_set :@metadata, array[18]
spec.instance_variable_set :@loaded, false
spec.instance_variable_set :@activated, false
@@ -732,7 +1142,8 @@ class Gem::Specification
@homepage,
true, # has_rdoc
@new_platform,
- @licenses
+ @licenses,
+ @metadata
]
end
@@ -763,6 +1174,8 @@ class Gem::Specification
# resolved later, as needed.
def activate_dependencies
+ unresolved = Gem::Specification.unresolved_deps
+
self.runtime_dependencies.each do |spec_dep|
if loaded = Gem.loaded_specs[spec_dep.name]
next if spec_dep.matches_spec? loaded
@@ -780,11 +1193,11 @@ class Gem::Specification
specs.first.activate
else
name = spec_dep.name
- Gem.unresolved_deps[name] = Gem.unresolved_deps[name].merge spec_dep
+ unresolved[name] = unresolved[name].merge spec_dep
end
end
- Gem.unresolved_deps.delete self.name
+ unresolved.delete self.name
end
##
@@ -816,8 +1229,7 @@ class Gem::Specification
end
unless dependency.respond_to?(:name) &&
- dependency.respond_to?(:version_requirements)
-
+ dependency.respond_to?(:version_requirements)
dependency = Gem::Dependency.new(dependency, requirements, type)
end
@@ -826,29 +1238,6 @@ class Gem::Specification
private :add_dependency_with_type
- ##
- # Adds a development dependency named +gem+ with +requirements+ to this
- # Gem. For example:
- #
- # spec.add_development_dependency 'example', '~> 1.1', '>= 1.1.4'
- #
- # Development dependencies aren't installed by default and aren't
- # activated when a gem is required.
-
- def add_development_dependency(gem, *requirements)
- add_dependency_with_type(gem, :development, *requirements)
- end
-
- ##
- # Adds a runtime dependency named +gem+ with +requirements+ to this Gem.
- # For example:
- #
- # spec.add_runtime_dependency 'example', '~> 1.1', '>= 1.1.4'
-
- def add_runtime_dependency(gem, *requirements)
- add_dependency_with_type(gem, :runtime, *requirements)
- end
-
alias add_dependency add_runtime_dependency
##
@@ -879,34 +1268,13 @@ class Gem::Specification
end
##
- # Singular writer for #authors
-
- def author= o
- self.authors = [o]
- end
-
- ##
# The list of author names who wrote this gem.
- #
- # If you are providing multiple authors and multiple emails they should be
- # in the same order such that:
- #
- # Hash[*spec.authors.zip(spec.emails).flatten]
- #
- # Gives a hash of author name to email address.
def authors
@authors ||= []
end
##
- # Sets the list of authors, ensuring it is an array.
-
- def authors= value
- @authors = Array(value).flatten.grep(String)
- end
-
- ##
# Returns the full path to the base gem directory.
#
# eg: /usr/local/lib/ruby/gems/1.8
@@ -934,6 +1302,32 @@ class Gem::Specification
end
##
+ # Returns the build_args used to install the gem
+
+ def build_args
+ if File.exists? build_info_file
+ File.readlines(build_info_file).map { |x| x.strip }
+ else
+ []
+ end
+ end
+
+ ##
+ # Returns the full path to the build info directory
+
+ def build_info_dir
+ File.join base_dir, "build_info"
+ end
+
+ ##
+ # Returns the full path to the file containing the build
+ # information generated when the gem was installed
+
+ def build_info_file
+ File.join build_info_dir, "#{full_name}.info"
+ end
+
+ ##
# Returns the full path to the cache directory containing this
# spec's cached gem.
@@ -948,8 +1342,6 @@ class Gem::Specification
@cache_file ||= File.join cache_dir, "#{full_name}.gem"
end
- alias :cache_gem :cache_file
-
##
# Return any possible conflicts against the currently loaded specs.
@@ -969,17 +1361,13 @@ class Gem::Specification
# Return true if this spec can require +file+.
def contains_requirable_file? file
- root = full_gem_path
+ root = full_gem_path
+ suffixes = Gem.suffixes
- require_paths.each do |lib|
+ require_paths.any? do |lib|
base = "#{root}/#{lib}/#{file}"
- Gem.suffixes.each do |suf|
- path = "#{base}#{suf}"
- return true if File.file? path
- end
+ suffixes.any? { |suf| File.file? "#{base}#{suf}" }
end
-
- return false
end
##
@@ -989,10 +1377,15 @@ class Gem::Specification
@date ||= TODAY
end
+ DateTimeFormat = /\A
+ (\d{4})-(\d{2})-(\d{2})
+ (\s+ \d{2}:\d{2}:\d{2}\.\d+ \s* (Z | [-+]\d\d:\d\d) )?
+ \Z/x
+
##
# The date this gem was created
#
- # Do not set this, it is set automatically when the gem is packaged.
+ # DO NOT set this, it is set automatically when the gem is packaged.
def date= date
# We want to end up with a Time object with one-day resolution.
@@ -1000,7 +1393,7 @@ class Gem::Specification
# way to do it.
@date = case date
when String then
- if /\A(\d{4})-(\d{2})-(\d{2})\Z/ =~ date then
+ if DateTimeFormat =~ date then
Time.utc($1.to_i, $2.to_i, $3.to_i)
# Workaround for where the date format output from psych isn't
@@ -1060,6 +1453,7 @@ class Gem::Specification
# [depending_gem, dependency, [list_of_gems_that_satisfy_dependency]]
def dependent_gems
+ # REFACTOR: out = []; each; out; ? Really? No #collect love?
out = []
Gem::Specification.each do |spec|
spec.dependencies.each do |dep|
@@ -1097,10 +1491,21 @@ class Gem::Specification
end
##
- # Returns the full path to this spec's documentation directory.
+ # Returns the full path to this spec's documentation directory. If +type+
+ # is given it will be appended to the end. For examlpe:
+ #
+ # spec.doc_dir # => "/path/to/gem_repo/doc/a-1"
+ #
+ # spec.doc_dir 'ri' # => "/path/to/gem_repo/doc/a-1/ri"
- def doc_dir
+ def doc_dir type = nil
@doc_dir ||= File.join base_dir, 'doc', full_name
+
+ if type then
+ File.join @doc_dir, type
+ else
+ @doc_dir
+ end
end
def encode_with coder # :nodoc:
@@ -1143,13 +1548,6 @@ class Gem::Specification
end
##
- # Executables included in the gem.
-
- def executables
- @executables ||= []
- end
-
- ##
# Sets executables to +value+, ensuring it is an array. Don't
# use this, push onto the array instead.
@@ -1159,14 +1557,6 @@ class Gem::Specification
end
##
- # Extensions to build when installing the gem. See
- # Gem::Installer#build_extensions for valid values.
-
- def extensions
- @extensions ||= []
- end
-
- ##
# Sets extensions to +extensions+, ensuring it is an array. Don't
# use this, push onto the array instead.
@@ -1176,13 +1566,6 @@ class Gem::Specification
end
##
- # Extra files to add to RDoc such as README or doc/examples.txt
-
- def extra_rdoc_files
- @extra_rdoc_files ||= []
- end
-
- ##
# Sets extra_rdoc_files to +files+, ensuring it is an array. Don't
# use this, push onto the array instead.
@@ -1201,25 +1584,6 @@ class Gem::Specification
end
##
- # Files included in this gem. You cannot append to this accessor, you must
- # assign to it.
- #
- # Only add files you can require to this list, not directories, etc.
- #
- # Directories are automatically stripped from this list when building a gem,
- # other non-files cause an error.
-
- def files
- # DO NOT CHANGE TO ||= ! This is not a normal accessor. (yes, it sucks)
- @files = [@files,
- @test_files,
- add_bindir(@executables),
- @extra_rdoc_files,
- @extensions,
- ].flatten.uniq.compact
- end
-
- ##
# Sets files to +files+, ensuring it is an array.
def files= files
@@ -1253,11 +1617,14 @@ class Gem::Specification
# The full path to the gem (install path + full name).
def full_gem_path
- # TODO: try to get rid of this... or the awkward
+ # TODO: This is a heavily used method by gems, so we'll need
+ # to aleast just alias it to #gem_dir rather than remove it.
+
# TODO: also, shouldn't it default to full_name if it hasn't been written?
return @full_gem_path if defined?(@full_gem_path) && @full_gem_path
@full_gem_path = File.expand_path File.join(gems_dir, full_name)
+ @full_gem_path.untaint
return @full_gem_path if File.directory? @full_gem_path
@@ -1270,11 +1637,11 @@ class Gem::Specification
# default Ruby platform.
def full_name
- if platform == Gem::Platform::RUBY or platform.nil? then
- "#{@name}-#{@version}"
- else
- "#{@name}-#{@version}-#{platform}"
- end
+ @full_name ||= if platform == Gem::Platform::RUBY or platform.nil? then
+ "#{@name}-#{@version}".untaint
+ else
+ "#{@name}-#{@version}-#{platform}".untaint
+ end
end
##
@@ -1372,13 +1739,9 @@ class Gem::Specification
# Duplicates array_attributes from +other_spec+ so state isn't shared.
def initialize_copy other_spec
- other_ivars = other_spec.instance_variables
- other_ivars = other_ivars.map { |ivar| ivar.intern } if # for 1.9
- String === other_ivars.first
-
self.class.array_attributes.each do |name|
name = :"@#{name}"
- next unless other_ivars.include? name
+ next unless other_spec.instance_variable_defined? name
begin
val = other_spec.instance_variable_get(name)
@@ -1398,11 +1761,22 @@ class Gem::Specification
end
##
- # The directory that this gem was installed into.
- # TODO: rename - horrible. this is the base_dir for a gem path
+ # Expire memoized instance variables that can incorrectly generate, replace
+ # or miss files due changes in certain attributes used to compute them.
- def installation_path
- loaded_from && base_dir
+ def invalidate_memoized_attributes
+ @full_name = nil
+ @cache_file = nil
+ end
+
+ private :invalidate_memoized_attributes
+
+ def inspect
+ if $DEBUG
+ super
+ else
+ "#<#{self.class}:0x#{__id__.to_s(16)} #{full_name}>"
+ end
end
##
@@ -1425,7 +1799,7 @@ class Gem::Specification
def lib_files
@files.select do |file|
require_paths.any? do |path|
- file.index(path) == 0
+ file.start_with? path
end
end
end
@@ -1438,33 +1812,31 @@ class Gem::Specification
end
##
- # Singular accessor for #licenses
-
- def license=o
- self.licenses = [o]
- end
-
- ##
- # The license(s) for the library. Each license must be a short name, no
- # more than 64 characters.
+ # Plural accessor for setting licenses
def licenses
@licenses ||= []
end
##
- # Set licenses to +licenses+, ensuring it is an array.
-
- def licenses= licenses
- @licenses = Array licenses
- end
-
- ##
# Set the location a Specification was loaded from. +obj+ is converted
# to a String.
def loaded_from= path
- @loaded_from = path.to_s
+ @loaded_from = path.to_s
+
+ # reset everything @loaded_from depends upon
+ @base_dir = nil
+ @bin_dir = nil
+ @cache_dir = nil
+ @cache_file = nil
+ @doc_dir = nil
+ @full_gem_path = nil
+ @gem_dir = nil
+ @gems_dir = nil
+ @ri_dir = nil
+ @spec_dir = nil
+ @spec_file = nil
end
##
@@ -1517,6 +1889,13 @@ class Gem::Specification
end
##
+ # Return a NameTuple that represents this Specification
+
+ def name_tuple
+ Gem::NameTuple.new name, version, original_platform
+ end
+
+ ##
# Returns the full name (name-version) of this gemspec using the original
# platform. For use with legacy gems.
@@ -1542,44 +1921,6 @@ class Gem::Specification
@new_platform ||= Gem::Platform::RUBY
end
- ##
- # The platform this gem runs on. See Gem::Platform for details.
- #
- # Setting this to any value other than Gem::Platform::RUBY or
- # Gem::Platform::CURRENT is probably wrong.
-
- def platform= platform
- if @original_platform.nil? or
- @original_platform == Gem::Platform::RUBY then
- @original_platform = platform
- end
-
- case platform
- when Gem::Platform::CURRENT then
- @new_platform = Gem::Platform.local
- @original_platform = @new_platform.to_s
-
- when Gem::Platform then
- @new_platform = platform
-
- # legacy constants
- when nil, Gem::Platform::RUBY then
- @new_platform = Gem::Platform::RUBY
- when 'mswin32' then # was Gem::Platform::WIN32
- @new_platform = Gem::Platform.new 'x86-mswin32'
- when 'i586-linux' then # was Gem::Platform::LINUX_586
- @new_platform = Gem::Platform.new 'x86-linux'
- when 'powerpc-darwin' then # was Gem::Platform::DARWIN
- @new_platform = Gem::Platform.new 'ppc-darwin'
- else
- @new_platform = Gem::Platform.new platform
- end
-
- @platform = @new_platform.to_s
-
- @new_platform
- end
-
def pretty_print(q) # :nodoc:
q.group 2, 'Gem::Specification.new do |s|', 'end' do
q.breakable
@@ -1639,13 +1980,6 @@ class Gem::Specification
end
##
- # An ARGV style array of options to RDoc
-
- def rdoc_options
- @rdoc_options ||= []
- end
-
- ##
# Sets rdoc_options to +value+, ensuring it is an array. Don't
# use this, push onto the array instead.
@@ -1669,13 +2003,6 @@ class Gem::Specification
end
##
- # The version of ruby required by this gem
-
- def required_ruby_version= req
- @required_ruby_version = Gem::Requirement.create req
- end
-
- ##
# The RubyGems version required by this gem
def required_rubygems_version= req
@@ -1683,14 +2010,6 @@ class Gem::Specification
end
##
- # An array or things required by this gem. Not used by anything
- # presently.
-
- def requirements
- @requirements ||= []
- end
-
- ##
# Set requirements to +req+, ensuring it is an array. Don't
# use this, push onto the array instead.
@@ -1714,6 +2033,9 @@ class Gem::Specification
case obj
when String then obj.dump
when Array then '[' + obj.map { |x| ruby_code x }.join(", ") + ']'
+ when Hash then
+ seg = obj.keys.sort.map { |k| "#{k.to_s.dump} => #{obj[k].to_s.dump}" }
+ "{ #{seg.join(', ')} }"
when Gem::Version then obj.to_s.dump
when Date then obj.strftime('%Y-%m-%d').dump
when Time then obj.strftime('%Y-%m-%d').dump
@@ -1800,7 +2122,7 @@ class Gem::Specification
end
##
- # Singular accessor for #test_files
+ # Singular mutator for #test_files
def test_file= file
self.test_files = [file]
@@ -1826,27 +2148,13 @@ class Gem::Specification
end
##
- # Set test_files to +files+, ensuring it is an array.
-
- def test_files= files
- @test_files = Array files
- end
-
- def test_suite_file # :nodoc:
- # TODO: deprecate
- test_files.first
- end
-
- def test_suite_file= file # :nodoc:
- # TODO: deprecate
- @test_files = [] unless defined? @test_files
- @test_files << file
- end
-
- ##
# Returns a Ruby code representation of this specification, such that it can
# be eval'ed and reconstruct the same specification later. Attributes that
# still have their default values are omitted.
+ #
+ # REFACTOR: This, plus stuff like #ruby_code and #pretty_print, should
+ # probably be extracted out into some sort of separate class. SRP, do you
+ # speak it!??!
def to_ruby
mark_version
@@ -1863,6 +2171,10 @@ class Gem::Specification
result << ""
result << " s.required_rubygems_version = #{ruby_code required_rubygems_version} if s.respond_to? :required_rubygems_version="
+ if metadata and !metadata.empty?
+ result << " s.metadata = #{ruby_code metadata} if s.respond_to? :metadata="
+ end
+
handled = [
:dependencies,
:name,
@@ -1872,6 +2184,7 @@ class Gem::Specification
:version,
:has_rdoc,
:default_executable,
+ :metadata
]
@@attributes.each do |attr_name|
@@ -1883,34 +2196,36 @@ class Gem::Specification
end
end
- result << nil
- result << " if s.respond_to? :specification_version then"
- result << " s.specification_version = #{specification_version}"
- result << nil
+ unless dependencies.empty? then
+ result << nil
+ result << " if s.respond_to? :specification_version then"
+ result << " s.specification_version = #{specification_version}"
+ result << nil
- result << " if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then"
+ result << " if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then"
- dependencies.each do |dep|
- req = dep.requirements_list.inspect
- dep.instance_variable_set :@type, :runtime if dep.type.nil? # HACK
- result << " s.add_#{dep.type}_dependency(%q<#{dep.name}>, #{req})"
- end
+ dependencies.each do |dep|
+ req = dep.requirements_list.inspect
+ dep.instance_variable_set :@type, :runtime if dep.type.nil? # HACK
+ result << " s.add_#{dep.type}_dependency(%q<#{dep.name}>, #{req})"
+ end
- result << " else"
+ result << " else"
- dependencies.each do |dep|
- version_reqs_param = dep.requirements_list.inspect
- result << " s.add_dependency(%q<#{dep.name}>, #{version_reqs_param})"
- end
+ dependencies.each do |dep|
+ version_reqs_param = dep.requirements_list.inspect
+ result << " s.add_dependency(%q<#{dep.name}>, #{version_reqs_param})"
+ end
- result << ' end'
+ result << ' end'
- result << " else"
+ result << " else"
dependencies.each do |dep|
version_reqs_param = dep.requirements_list.inspect
result << " s.add_dependency(%q<#{dep.name}>, #{version_reqs_param})"
end
- result << " end"
+ result << " end"
+ end
result << "end"
result << nil
@@ -1946,6 +2261,7 @@ class Gem::Specification
ast = builder.tree
io = StringIO.new
+ io.set_encoding Encoding::UTF_8 if Object.const_defined? :Encoding
Psych::Visitors::Emitter.new(io).accept(ast)
@@ -2057,12 +2373,42 @@ class Gem::Specification
end
end
+ # FIX: uhhhh single element array.each?
[:authors].each do |field|
val = self.send field
raise Gem::InvalidSpecificationException, "#{field} may not be empty" if
val.empty?
end
+ unless Hash === metadata
+ raise Gem::InvalidSpecificationException,
+ 'metadata must be a hash'
+ end
+
+ metadata.keys.each do |k|
+ if !k.kind_of?(String)
+ raise Gem::InvalidSpecificationException,
+ 'metadata keys must be a String'
+ end
+
+ if k.size > 128
+ raise Gem::InvalidSpecificationException,
+ "metadata key too large (#{k.size} > 128)"
+ end
+ end
+
+ metadata.values.each do |k|
+ if !k.kind_of?(String)
+ raise Gem::InvalidSpecificationException,
+ 'metadata values must be a String'
+ end
+
+ if k.size > 1024
+ raise Gem::InvalidSpecificationException,
+ "metadata value too large (#{k.size} > 1024)"
+ end
+ end
+
licenses.each { |license|
if license.length > 64
raise Gem::InvalidSpecificationException,
@@ -2070,8 +2416,13 @@ class Gem::Specification
end
}
+ alert_warning 'licenses is empty' if licenses.empty?
+
+ validate_permissions
+
# reject lazy developers:
+ # FIX: Doesn't this just evaluate to "FIXME" or "TODO"?
lazy = '"FIxxxXME" or "TOxxxDO"'.gsub(/xxx/, '')
unless authors.grep(/FI XME|TO DO/x).empty? then
@@ -2117,10 +2468,35 @@ class Gem::Specification
alert_warning "#{executable_path} is missing #! line" unless shebang
end
+ dependencies.each do |dep|
+ prerelease_dep = dep.requirements_list.any? do |req|
+ Gem::Requirement.new(req).prerelease?
+ end
+
+ alert_warning "prerelease dependency on #{dep} is not recommended" if
+ prerelease_dep
+ end
+
true
end
##
+ # Checks to see if the files to be packaged are world-readable.
+
+ def validate_permissions
+ files.each do |file|
+ next if File.stat(file).world_readable?
+ alert_warning "#{file} is not world-readable"
+ end
+
+ executables.each do |name|
+ exec = File.join @bindir, name
+ next if File.stat(exec).executable?
+ alert_warning "#{exec} is not executable"
+ end
+ end
+
+ ##
# Set the version to +version+, potentially also setting
# required_rubygems_version if +version+ indicates it is a
# prerelease.
@@ -2128,6 +2504,8 @@ class Gem::Specification
def version= version
@version = Gem::Version.create(version)
self.required_rubygems_version = '> 1.3.1' if @version.prerelease?
+ invalidate_memoized_attributes
+
return @version
end
@@ -2147,15 +2525,33 @@ class Gem::Specification
self.platform = Gem::Platform.new @platform
end
+ ##
+ # Reset nil attributes to their default values to make the spec valid
+
+ def reset_nil_attributes_to_default
+ nil_attributes = self.class.non_nil_attributes.find_all do |name|
+ !instance_variable_defined?("@#{name}") || instance_variable_get("@#{name}").nil?
+ end
+
+ nil_attributes.each do |attribute|
+ default = self.default_value attribute
+
+ value = case default
+ when Time, Numeric, Symbol, true, false, nil then default
+ else default.dup
+ end
+
+ instance_variable_set "@#{attribute}", value
+ end
+ end
+
+ def default_gem?
+ loaded_from &&
+ File.dirname(loaded_from) == self.class.default_specifications_dir
+ end
+
extend Gem::Deprecate
- deprecate :test_suite_file, :test_file, 2011, 10
- deprecate :test_suite_file=, :test_file=, 2011, 10
- deprecate :loaded, :activated, 2011, 10
- deprecate :loaded?, :activated?, 2011, 10
- deprecate :loaded=, :activated=, 2011, 10
- deprecate :installation_path, :base_dir, 2011, 10
- deprecate :cache_gem, :cache_file, 2011, 10
# TODO:
# deprecate :has_rdoc, :none, 2011, 10
# deprecate :has_rdoc?, :none, 2011, 10
@@ -2167,5 +2563,5 @@ class Gem::Specification
# deprecate :full_gem_path, :cache_file, 2011, 10
end
+# DOC: What is this and why is it here, randomly, at the end of this file?
Gem.clear_paths
-