mirror of
https://github.com/zhigang1992/CocoaPods.git
synced 2026-06-14 01:22:14 +08:00
457 lines
16 KiB
Ruby
457 lines
16 KiB
Ruby
require 'xcodeproj/config'
|
||
|
||
module Pod
|
||
extend Config::Mixin
|
||
|
||
def self._eval_podspec(path)
|
||
string = File.open(path, 'r:utf-8') { |f| f.read }
|
||
# TODO: work around for Rubinius incomplete encoding in 1.9 mode
|
||
string.encode!('UTF-8') if string.respond_to?(:encoding) && string.encoding.name != "UTF-8"
|
||
eval(string, nil, path.to_s)
|
||
end
|
||
|
||
class Specification
|
||
autoload :Set, 'cocoapods/specification/set'
|
||
autoload :Statistics, 'cocoapods/specification/statistics'
|
||
|
||
### Initalization
|
||
|
||
# The file is expected to define and return a Pods::Specification.
|
||
# If name is equals to nil it returns the top level Specification,
|
||
# otherwise it returned the specification with the name that matches
|
||
def self.from_file(path, subspec_name = nil)
|
||
unless path.exist?
|
||
raise Informative, "No podspec exists at path `#{path}'."
|
||
end
|
||
spec = ::Pod._eval_podspec(path)
|
||
spec.defined_in_file = path
|
||
spec.subspec_by_name(subspec_name)
|
||
end
|
||
|
||
def initialize(parent = nil, name = nil)
|
||
@parent, @name = parent, name
|
||
@define_for_platforms = [:osx, :ios]
|
||
@clean_paths, @subspecs = [], []
|
||
@deployment_target = {}
|
||
unless parent
|
||
@source = {:git => ''}
|
||
end
|
||
|
||
# multi-platform attributes
|
||
%w[ source_files resources preserve_paths exclude_header_search_paths frameworks libraries dependencies compiler_flags].each do |attr|
|
||
instance_variable_set( "@#{attr}", { :ios => [], :osx => [] } )
|
||
end
|
||
@xcconfig = { :ios => Xcodeproj::Config.new, :osx => Xcodeproj::Config.new }
|
||
|
||
yield self if block_given?
|
||
end
|
||
|
||
### Meta programming
|
||
|
||
# Creates a top level attribute reader. A lambda can
|
||
# be passed to process the ivar before returning it
|
||
def self.top_attr_reader(attr, read_lambda = nil)
|
||
define_method(attr) do
|
||
ivar = instance_variable_get("@#{attr}")
|
||
@parent ? top_level_parent.send(attr) : ( read_lambda ? read_lambda.call(self, ivar) : ivar )
|
||
end
|
||
end
|
||
|
||
# Creates a top level attribute writer. A lambda can
|
||
# be passed to initalize the value
|
||
def self.top_attr_writer(attr, init_lambda = nil)
|
||
define_method("#{attr}=") do |value|
|
||
raise Informative, "#{self.inspect} Can't set `#{attr}' for subspecs." if @parent
|
||
instance_variable_set("@#{attr}", init_lambda ? init_lambda.call(value) : value);
|
||
end
|
||
end
|
||
|
||
# Creates a top level attribute accessor. A lambda can
|
||
# be passed to initialize the value in the attribute writer.
|
||
def self.top_attr_accessor(attr, writer_labmda = nil)
|
||
top_attr_reader attr
|
||
top_attr_writer attr, writer_labmda
|
||
end
|
||
|
||
# Returns the value of the attribute for the active platform
|
||
# chained with the upstream specifications. The ivar must store
|
||
# the platform specific values as an array.
|
||
def self.pltf_chained_attr_reader(attr)
|
||
define_method(attr) do
|
||
active_plaform_check
|
||
ivar_value = instance_variable_get("@#{attr}")[active_platform]
|
||
@parent ? @parent.send(attr) + ivar_value : ( ivar_value )
|
||
end
|
||
end
|
||
|
||
def active_plaform_check
|
||
raise Informative, "#{self.inspect} not activated for a platform before consumption." unless active_platform
|
||
end
|
||
|
||
# Attribute writer that works in conjuction with the PlatformProxy.
|
||
def self.platform_attr_writer(attr, block = nil)
|
||
define_method("#{attr}=") do |value|
|
||
current = instance_variable_get("@#{attr}")
|
||
@define_for_platforms.each do |platform|
|
||
block ? current[platform] = block.call(value, current[platform]) : current[platform] = value
|
||
end
|
||
end
|
||
end
|
||
|
||
def self.pltf_chained_attr_accessor(attr, block = nil)
|
||
pltf_chained_attr_reader(attr)
|
||
platform_attr_writer(attr, block)
|
||
end
|
||
|
||
# The PlatformProxy works in conjuction with Specification#_on_platform.
|
||
# It allows a syntax like `spec.ios.source_files = file`
|
||
class PlatformProxy
|
||
def initialize(specification, platform)
|
||
@specification, @platform = specification, platform
|
||
end
|
||
|
||
%w{ source_files=
|
||
resource=
|
||
resources=
|
||
preserve_paths=
|
||
preserve_path=
|
||
xcconfig=
|
||
framework=
|
||
frameworks=
|
||
library=
|
||
libraries=
|
||
compiler_flags=
|
||
deployment_target=
|
||
dependency }.each do |method|
|
||
define_method(method) do |args|
|
||
@specification._on_platform(@platform) do
|
||
@specification.send(method, args)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
def ios
|
||
PlatformProxy.new(self, :ios)
|
||
end
|
||
|
||
def osx
|
||
PlatformProxy.new(self, :osx)
|
||
end
|
||
|
||
### Deprecated attributes - TODO: remove once master repo and fixtures have been updated
|
||
|
||
attr_writer :part_of_dependency
|
||
attr_writer :part_of
|
||
|
||
top_attr_accessor :clean_paths, lambda { |patterns| pattern_list(patterns) }
|
||
alias_method :clean_path=, :clean_paths=
|
||
|
||
### Regular attributes
|
||
|
||
attr_accessor :parent
|
||
attr_accessor :preferred_dependency
|
||
|
||
def name
|
||
@parent ? "#{@parent.name}/#{@name}" : @name
|
||
end
|
||
attr_writer :name
|
||
|
||
### Attributes that return the first value defined in the chain
|
||
|
||
def platform
|
||
@platform || ( @parent ? @parent.platform : nil )
|
||
end
|
||
|
||
def platform=(platform)
|
||
@platform = Platform.new(*platform)
|
||
end
|
||
|
||
# If not platform is specified all the platforms are returned.
|
||
def available_platforms
|
||
platform.nil? ? @define_for_platforms.map { |platform| Platform.new(platform, deployment_target(platform)) } : [ platform ]
|
||
end
|
||
|
||
### Top level attributes. These attributes represent the unique features of pod and can't be specified by subspecs.
|
||
|
||
top_attr_accessor :defined_in_file
|
||
top_attr_accessor :source
|
||
top_attr_accessor :homepage
|
||
top_attr_accessor :summary
|
||
top_attr_accessor :documentation
|
||
top_attr_accessor :requires_arc
|
||
top_attr_accessor :license, lambda { |l| ( l.kind_of? String ) ? { :type => l } : l }
|
||
top_attr_accessor :version, lambda { |v| Version.new(v) }
|
||
top_attr_accessor :authors, lambda { |a| parse_authors(a) }
|
||
top_attr_accessor :header_mappings_dir, lambda { |file| Pathname.new(file) } # If not provided the headers files are flattened
|
||
top_attr_accessor :prefix_header_file, lambda { |file| Pathname.new(file) }
|
||
top_attr_accessor :prefix_header_contents
|
||
|
||
top_attr_reader :description, lambda {|instance, ivar| ivar || instance.summary }
|
||
top_attr_writer :description
|
||
|
||
top_attr_reader :header_dir, lambda {|instance, ivar| ivar || instance.pod_destroot_name }
|
||
top_attr_writer :header_dir, lambda {|dir| Pathname.new(dir) }
|
||
|
||
alias_method :author=, :authors=
|
||
|
||
def self.parse_authors(*names_and_email_addresses)
|
||
list = names_and_email_addresses.flatten
|
||
unless list.first.is_a?(Hash)
|
||
authors = list.last.is_a?(Hash) ? list.pop : {}
|
||
list.each { |name| authors[name] = nil }
|
||
end
|
||
authors || list.first
|
||
end
|
||
|
||
### Attributes **with** multiple platform support
|
||
|
||
pltf_chained_attr_accessor :source_files, lambda {|value, current| pattern_list(value) }
|
||
pltf_chained_attr_accessor :resources, lambda {|value, current| pattern_list(value) }
|
||
pltf_chained_attr_accessor :preserve_paths, lambda {|value, current| pattern_list(value) } # Paths that should not be cleaned
|
||
pltf_chained_attr_accessor :exclude_header_search_paths, lambda {|value, current| pattern_list(value) } # Headers to be excluded from being added to search paths (RestKit)
|
||
pltf_chained_attr_accessor :frameworks, lambda {|value, current| (current << value).flatten }
|
||
pltf_chained_attr_accessor :libraries, lambda {|value, current| (current << value).flatten }
|
||
|
||
alias_method :resource=, :resources=
|
||
alias_method :preserve_path=, :preserve_paths=
|
||
alias_method :framework=, :frameworks=
|
||
alias_method :library=, :libraries=
|
||
|
||
platform_attr_writer :xcconfig, lambda {|value, current| current.tap { |c| c.merge!(value) } }
|
||
|
||
def xcconfig
|
||
raw_xconfig.dup.
|
||
tap { |x| x.libraries.merge libraries }.
|
||
tap { |x| x.frameworks.merge frameworks }
|
||
end
|
||
|
||
def raw_xconfig
|
||
@parent ? @parent.raw_xconfig.merge(@xcconfig[active_platform]) : @xcconfig[active_platform]
|
||
end
|
||
|
||
|
||
def compiler_flags
|
||
if @parent
|
||
chained = @compiler_flags[active_platform].clone
|
||
# TODO hack to get the parent's compiler flags without it being
|
||
# converted to a String by Specification#compiler_flags.
|
||
chained.unshift @parent.instance_variable_get(:@compiler_flags)[active_platform]
|
||
else
|
||
chained = @compiler_flags[active_platform].clone
|
||
chained.unshift '-fobjc-arc' if @requires_arc
|
||
chained.unshift ''
|
||
end
|
||
chained.join(' ')
|
||
end
|
||
|
||
platform_attr_writer :compiler_flags, lambda {|value, current| current << value }
|
||
|
||
def dependency(*name_and_version_requirements)
|
||
name, *version_requirements = name_and_version_requirements.flatten
|
||
raise Informative, "A specification can't require self as a subspec" if name == self.name
|
||
raise Informative, "A subspec can't require one of its parents specifications" if @parent && @parent.name.include?(name)
|
||
dep = Dependency.new(name, *version_requirements)
|
||
@define_for_platforms.each do |platform|
|
||
@dependencies[platform] << dep
|
||
end
|
||
dep
|
||
end
|
||
|
||
# External dependencies are inherited by subspecs
|
||
def external_dependencies(all_platforms = false)
|
||
active_plaform_check unless all_platforms
|
||
result = all_platforms ? @dependencies.values.flatten : @dependencies[active_platform]
|
||
result += parent.external_dependencies if parent
|
||
result
|
||
end
|
||
|
||
# A specification inherits the preferred_dependency or
|
||
# all the compatible subspecs as dependencies
|
||
def subspec_dependencies
|
||
active_plaform_check
|
||
specs = preferred_dependency ? [subspec_by_name("#{name}/#{preferred_dependency}")] : subspecs
|
||
specs.compact \
|
||
.select { |s| s.supports_platform?(active_platform) } \
|
||
.map { |s| Dependency.new(s.name, version) }
|
||
end
|
||
|
||
def dependencies
|
||
external_dependencies + subspec_dependencies
|
||
end
|
||
|
||
include Config::Mixin
|
||
|
||
def top_level_parent
|
||
@parent ? @parent.top_level_parent : self
|
||
end
|
||
|
||
def subspec?
|
||
!@parent.nil?
|
||
end
|
||
|
||
def subspec(name, &block)
|
||
subspec = Specification.new(self, name, &block)
|
||
@subspecs << subspec
|
||
subspec
|
||
end
|
||
attr_reader :subspecs
|
||
|
||
def recursive_subspecs
|
||
unless @recursive_subspecs
|
||
mapper = lambda do |spec|
|
||
spec.subspecs.map do |subspec|
|
||
[subspec, *mapper.call(subspec)]
|
||
end.flatten
|
||
end
|
||
@recursive_subspecs = mapper.call self
|
||
end
|
||
@recursive_subspecs
|
||
end
|
||
|
||
def subspec_by_name(name)
|
||
return self if name.nil? || name == self.name
|
||
# Remove this spec's name from the beginning of the name we’re looking for
|
||
# and take the first component from the remainder, which is the spec we need
|
||
# to find now.
|
||
remainder = name[self.name.size+1..-1].split('/')
|
||
subspec_name = remainder.shift
|
||
subspec = subspecs.find { |s| s.name == "#{self.name}/#{subspec_name}" }
|
||
# If this was the last component in the name, then return the subspec,
|
||
# otherwise we recursively keep calling subspec_by_name until we reach the
|
||
# last one and return that
|
||
|
||
raise Informative, "Unable to find a subspec named `#{name}'." unless subspec
|
||
remainder.empty? ? subspec : subspec.subspec_by_name(name)
|
||
end
|
||
|
||
def local?
|
||
!source.nil? && !source[:local].nil?
|
||
end
|
||
|
||
def local_path
|
||
Pathname.new(File.expand_path(source[:local]))
|
||
end
|
||
|
||
def pod_destroot
|
||
if local?
|
||
local_path
|
||
else
|
||
config.project_pods_root + top_level_parent.name
|
||
end
|
||
end
|
||
|
||
def pod_destroot_name
|
||
if root = pod_destroot
|
||
root.basename
|
||
end
|
||
end
|
||
|
||
def self.pattern_list(patterns)
|
||
if patterns.is_a?(Array) && (!defined?(Rake) || !patterns.is_a?(Rake::FileList))
|
||
patterns
|
||
else
|
||
[patterns]
|
||
end
|
||
end
|
||
|
||
# This method takes a header path and returns the location it should have
|
||
# in the pod's header dir.
|
||
#
|
||
# By default all headers are copied to the pod's header dir without any
|
||
# namespacing. However if the top level attribute accessor header_mappings_dir
|
||
# is specified the namespacing will be preserved from that directory.
|
||
def copy_header_mapping(from)
|
||
header_mappings_dir ? from.relative_path_from(header_mappings_dir) : from.basename
|
||
end
|
||
|
||
# This is a convenience method which gets called after all pods have been
|
||
# downloaded, installed, and the Xcode project and related files have been
|
||
# generated. (It receives the Pod::Installer::Target instance for the current
|
||
# target.) Override this to, for instance, add to the prefix header:
|
||
#
|
||
# Pod::Spec.new do |s|
|
||
# def s.post_install(target)
|
||
# prefix_header = config.project_pods_root + target.prefix_header_filename
|
||
# prefix_header.open('a') do |file|
|
||
# file.puts(%{#ifdef __OBJC__\n#import "SSToolkitDefines.h"\n#endif})
|
||
# end
|
||
# end
|
||
# end
|
||
def post_install(target)
|
||
end
|
||
|
||
def podfile?
|
||
false
|
||
end
|
||
|
||
# This is used by the specification set
|
||
def dependency_by_top_level_spec_name(name)
|
||
external_dependencies(true).each do |dep|
|
||
return dep if dep.top_level_spec_name == name
|
||
end
|
||
end
|
||
|
||
def to_s
|
||
"#{name} (#{version})"
|
||
end
|
||
|
||
def inspect
|
||
"#<#{self.class.name} for #{to_s}>"
|
||
end
|
||
|
||
def ==(other)
|
||
object_id == other.object_id ||
|
||
(self.class === other &&
|
||
name && name == other.name &&
|
||
version && version == other.version)
|
||
end
|
||
|
||
# Returns whether the specification is supported in a given platform
|
||
def supports_platform?(*platform)
|
||
platform = platform[0].is_a?(Platform) ? platform[0] : Platform.new(*platform)
|
||
available_platforms.any? { |p| platform.supports?(p) }
|
||
end
|
||
|
||
# Defines the active platform for comsumption of the specification and
|
||
# returns self for method chainability.
|
||
# The active platform must the the same accross the chain so attributes
|
||
# that are inherited can be correctly resolved.
|
||
def activate_platform(*platform)
|
||
platform = platform[0].is_a?(Platform) ? platform[0] : Platform.new(*platform)
|
||
raise Informative, "#{to_s} is not compatible with #{platform}." unless supports_platform?(platform)
|
||
top_level_parent.active_platform = platform.to_sym
|
||
self
|
||
end
|
||
|
||
top_attr_accessor :active_platform
|
||
|
||
### Not attributes
|
||
|
||
# @visibility private
|
||
#
|
||
# This is used by PlatformProxy to assign attributes for the scoped platform.
|
||
def _on_platform(platform)
|
||
before, @define_for_platforms = @define_for_platforms, [platform]
|
||
yield
|
||
ensure
|
||
@define_for_platforms = before
|
||
end
|
||
|
||
# @visibility private
|
||
#
|
||
# This is multi-platform and to support
|
||
# subspecs with different platforms is is resolved as the
|
||
# first non nil value accross the chain.
|
||
def deployment_target=(version)
|
||
raise Informative, "The deployment target must be defined per platform like `s.ios.deployment_target = '5.0'`." unless @define_for_platforms.count == 1
|
||
@deployment_target[@define_for_platforms.first] = version
|
||
end
|
||
|
||
def deployment_target(platform)
|
||
@deployment_target[platform] || ( @parent ? @parent.deployment_target(platform) : nil )
|
||
end
|
||
end
|
||
Spec = Specification
|
||
end
|