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