Commit 7162c5af authored by Fabio Pelosin's avatar Fabio Pelosin

Completed Pod::Specification refactor.

parent 22de3728
......@@ -33,7 +33,12 @@ module Pod
unless parent
@source = {:git => ''}
end
initialized_multiplatform_attributes
# multi-platform attributes
%w[ source_files resources preserve_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
......@@ -52,8 +57,8 @@ module Pod
# Creates a top level attribute writer. A lambda can
# be passed to initalize the value
def self.top_attr_writer(attr, init_lambda = nil)
raise Informative "Can't set #{attr} for subspecs." if @parent
define_method("#{attr}=") do |value|
raise Informative, "Can't set `#{attr}' for subspecs." if @parent
instance_variable_set("@#{attr}", init_lambda ? init_lambda.call(value) : value);
end
end
......@@ -65,27 +70,21 @@ module Pod
top_attr_writer attr, writer_labmda
end
# Returns the value of the attribute for the active platform.
# In this way clients do not need to be aware of wich attributes
# are multiplatform.
def self.platform_attr_reader(attr)
define_method(attr) do
raise Informative, "#{self.inspect}##{attr} not activated for a platform before consumption." unless active_platform
instance_variable_get("@#{attr}")[active_platform]
end
end
# Returns the value of the attribute for the active platform.
# 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
raise Informative, "#{self.inspect}##{attr} not activated for a platform before consumption." unless active_platform
active_plaform_check
ivar_value = instance_variable_get("@#{attr}")[active_platform]
@parent ? @parent.send(attr) + ivar_value : ivar_value
@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|
......@@ -96,8 +95,13 @@ module Pod
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 `source_files[:ios] = file`
# It allows a syntax like `spec.ios.source_files = file`
class PlatformProxy
def initialize(specification, platform)
@specification, @platform = specification, platform
......@@ -132,6 +136,7 @@ module Pod
attr_accessor :parent
attr_accessor :preferred_dependency
attr_accessor :summary
def name
@parent ? "#{@parent.name}/#{@name}" : @name
......@@ -139,16 +144,20 @@ module Pod
attr_writer :name
### Attributes that return the first value defined in the chain
def summary
@summary || ( @parent.summary if @parent )
def description
@description || summary
end
attr_writer :summary
# TODO: consider converting the following to top level attributes
# A subspec should have a summary instead of a description.
# Some subspecs contain a homepage but we never use this information.
attr_writer :description
attr_accessor :homepage
### Attributes that return the first value defined in the chain
def platform
@platform || ( @parent ? @parent.platform : Platform.new(nil) )
@platform || ( @parent ? @parent.platform : nil )
end
def platform=(platform)
......@@ -163,20 +172,17 @@ module Pod
### 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 :homepage
top_attr_accessor :source
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 :prefix_header_contents #TODO: is this top level?
top_attr_accessor :prefix_header_file, lambda { |file| Pathname.new(file) } #TODO: is this top level?
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 } #TODO: is this top level?
top_attr_writer :header_dir, lambda {|dir| Pathname.new(dir) } #TODO: is this top level?
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=
......@@ -191,52 +197,62 @@ module Pod
### Attributes **with** multiple platform support
def initialized_multiplatform_attributes
%w[ source_files resources frameworks libraries dependencies compiler_flags].each do |attr|
instance_variable_set( "@#{attr}", { :ios => [], :osx => [] } )
end
@xcconfig = { :ios => Xcodeproj::Config.new, :osx => Xcodeproj::Config.new }
end
pltf_chained_attr_reader :source_files
platform_attr_writer :source_files,
lambda {|value, current| pattern_list(value) }
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) }
pltf_chained_attr_accessor :frameworks, lambda {|value, current| current << value }
pltf_chained_attr_accessor :libraries, lambda {|value, current| current << value }
pltf_chained_attr_reader :resources
platform_attr_writer :resources,
lambda {|value, current| pattern_list(value) }
alias_method :resource=, :resources=
# frameworks are chained by the xcofing attr_reader
platform_attr_reader :frameworks
platform_attr_writer :frameworks,
lambda {|value, current| current << value }
alias_method :framework=, :frameworks=
# libraries are chained by the xcofing attr_reader
platform_attr_reader :libraries
platform_attr_writer :libraries,
lambda {|value, current| current << value }
alias_method :library=, :libraries=
def xcconfig
if @parent
chained = @parent.xcconfig.dup.merge! @xcconfig[active_platform]
else
chained = @xcconfig[active_platform].dup
raw_xconfig.dup.
tap { |x| x.add_libraries(libraries) }.
tap { |x| x.add_frameworks(frameworks) }
end
# TODO: move to Xcodeproj
class ::Xcodeproj::Config
# BUG: old implementation would lose keys specified in self
# but not specified in the passed xcconfig.
def merge!(xcconfig)
@attributes.merge!(xcconfig.to_hash) { |key, v1, v2| "#{v1} #{v2}" }
end
def merge(config)
self.dup.tap { |x|x.merge!(config) }
end
def add_libraries(libraries)
flags = [ @attributes['OTHER_LDFLAGS'] ]
flags << "-l#{ libraries.join(' -l').strip}" if libraries && !libraries.empty?
@attributes['OTHER_LDFLAGS'] = flags.compact.join(' ')
end
def add_frameworks(frameworks)
flags = [ @attributes['OTHER_LDFLAGS'] ]
flags << "-framework #{ frameworks.join(' -framework ').strip }" if frameworks && !frameworks.empty?
@attributes['OTHER_LDFLAGS'] = flags.compact.join(' ')
end
chained.merge!({ 'OTHER_LDFLAGS' => '-l' << libraries.join(' -l').strip }) unless libraries.empty?
chained.merge!({ 'OTHER_LDFLAGS' => '-framework ' << frameworks.join(' -framework ').strip }) unless frameworks.empty?
chained
def dup
Xcodeproj::Config.new(self.to_hash.dup)
end
end
def raw_xconfig
@parent ? @parent.raw_xconfig.merge(@xcconfig[active_platform]) : @xcconfig[active_platform]
end
platform_attr_writer :xcconfig, lambda {|value, current| current.tap { |c| c.merge!(value) } }
def compiler_flags
if @parent
chained = @compiler_flags[active_platform].dup.unshift @parent.compiler_flags[active_platform]
chained = @compiler_flags[active_platform].clone.unshift @parent.compiler_flags[active_platform]
else
chained = @compiler_flags[active_platform].dup
chained = @compiler_flags[active_platform].clone
chained.unshift '-fobjc-arc' if @requires_arc
chained.unshift ''
end
......@@ -247,6 +263,8 @@ module Pod
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
......@@ -255,23 +273,24 @@ module Pod
end
# External dependencies are inherited by subspecs
def external_dependencies
result = @dependencies[active_platform] || []
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 of its subspecs as dependencies
# all the compatible subspecs as dependencies
def subspec_dependencies
specs = preferred_dependency ? [subspec_by_name(preferred_dependency)] : subspecs
specs \
.reject { |s| s.supports_platform?(active_platform) } \
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
raise Informative, "#{self.inspect}#dependencies not activated for a platform before consumption." unless active_platform
external_dependencies + subspec_dependencies
end
......@@ -375,14 +394,15 @@ module Pod
false
end
# This is used by the specification set
def dependency_by_top_level_spec_name(name)
dependencies.each do |dep|
external_dependencies(true).each do |dep|
return dep if dep.top_level_spec_name == name
end
end
def to_s
"#{name} (#{version})"
preferred_dependency ? "#{name}/#{preferred_dependency} (#{version})" : "#{name} (#{version})"
end
def inspect
......@@ -397,17 +417,18 @@ module Pod
end
# Returns whether the specification is supported in a given platform
def supports_platform?(plaform)
available_platforms.any? { |p| platform.supports? p }
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.new(platform) if platform.is_a? Hash
raise "#{to_s} is not compatible with #{platform}." unless supports_platform?(platform)
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
......
......@@ -182,6 +182,18 @@ describe "A Pod::Specification, in general," do
list.glob.should == Pod::FileList[(ROOT + '*').to_s].exclude('Rakefile').map { |path| Pathname.new(path) }
end
it "takes a list of paths to preserve" do
@spec.preserve_paths = 'script.sh'
@spec.activate_platform(:ios).preserve_paths.should == %w{ script.sh }
end
it "takes any object for source_files as long as it responds to #glob (we provide this for Rake::FileList)" do
@spec.source_files = Pod::FileList['*'].exclude('Rakefile')
@spec.activate_platform(:ios)
list = ROOT + @spec.source_files.first
list.glob.should == Pod::FileList[(ROOT + '*').to_s].exclude('Rakefile').map { |path| Pathname.new(path) }
end
it "takes a prefix header path which will be appended to the Pods pch file" do
@spec.prefix_header_file.should == nil
@spec.prefix_header_file = 'Classes/Demo.pch'
......@@ -193,6 +205,76 @@ describe "A Pod::Specification, in general," do
@spec.prefix_header_contents = '#import "BlocksKit.h"'
@spec.prefix_header_contents.should == '#import "BlocksKit.h"'
end
it "can be activated for a supported platorm" do
@spec.platform = :ios
lambda {@spec.activate_platform(:ios)}.should.not.raise Pod::Informative
end
it "raised if attempted to be activated for an unsupported platform" do
@spec.platform = :osx, '10.7'
lambda {@spec.activate_platform(:ios)}.should.raise Pod::Informative
lambda {@spec.activate_platform(:ios, '10.6')}.should.raise Pod::Informative
end
it "raises if not activated for a platform before accessing a multiplatform value" do
@spec.platform = :ios
lambda {@spec.source_files}.should.raise Pod::Informative
end
it "returns self on activation for method chainablity" do
@spec.platform = :ios
@spec.activate_platform(:ios).should == @spec
end
end
describe "A Pod::Specification, hierarchy" do
before do
@spec = Pod::Spec.new do |s|
s.name = 'MainSpec'
s.version = '0.999'
s.dependency 'awesome_lib'
s.subspec 'SubSpec.0' do |fss|
fss.platform = :ios
fss.subspec 'SubSpec.0.0' do |sss|
end
end
s.subspec 'SubSpec.1'
end
@subspec = @spec.subspecs.first
@spec.activate_platform(:ios)
end
it "automatically includes all the compatible subspecs as a dependencis if not preference is given" do
@spec.dependencies.map { |s| s.name }.should == %w[ awesome_lib MainSpec/SubSpec.0 MainSpec/SubSpec.1 ]
@spec.activate_platform(:osx).dependencies.map { |s| s.name }.should == %w[ awesome_lib MainSpec/SubSpec.1 ]
end
it "uses the spec version for the dependencies" do
@spec.dependencies.
select { |d| d.name =~ /MainSpec/ }.
all? { |d| d.requirement === Pod::Version.new('0.999') }.
should.be.true
end
it "respecs the preferred dependency for subspecs, if specified" do
@spec.preferred_dependency = 'SubSpec.0'
@spec.dependencies.map { |s| s.name }.should == %w[ awesome_lib MainSpec/SubSpec.0 ]
end
it "raises if it has dependecy on a self or on an upstream subspec" do
lambda { @subspec.dependency('MainSpec/SubSpec.0') }.should.raise Pod::Informative
lambda { @subspec.dependency('MainSpec') }.should.raise Pod::Informative
end
it "inherits external dependecies from the parent" do
@subspec.dependencies.map { |s| s.name }.should == %w[ awesome_lib MainSpec/SubSpec.0/SubSpec.0.0 ]
end
it "it accepts a dependency on a subspec that is in the same level of the hierarchy" do
@subspec.dependency('MainSpec/SubSpec.1')
@subspec.dependencies.map { |s| s.name }.should == %w[ MainSpec/SubSpec.1 awesome_lib MainSpec/SubSpec.0/SubSpec.0.0 ]
end
end
describe "A Pod::Specification subspec" do
......@@ -200,20 +282,29 @@ describe "A Pod::Specification subspec" do
@spec = Pod::Spec.new do |s|
s.name = 'MainSpec'
s.version = '1.2.3'
s.platform = :ios
s.license = 'MIT'
s.author = 'Joe the Plumber'
s.summary = 'A spec with subspecs'
s.source = { :git => '/some/url' }
s.requires_arc = true
s.source_files = 'spec.m'
s.resource = 'resource'
s.platform = :ios
s.library = 'xml'
s.framework = 'CoreData'
s.subspec 'FirstSubSpec' do |fss|
fss.source_files = 'some/file'
fss.ios.source_files = 'subspec_ios.m'
fss.osx.source_files = 'subspec_osx.m'
fss.framework = 'CoreGraphics'
fss.library = 'z'
fss.subspec 'SecondSubSpec' do |sss|
sss.source_files = 'subsubspec.m'
end
end
end
@subspec = @spec.subspecs.first
@subsubspec = @subspec.subspecs.first
end
it "returns the top level parent spec" do
......@@ -231,30 +322,126 @@ describe "A Pod::Specification subspec" do
@spec.subspecs.first.parent.should == @spec
end
it "automatically forwards undefined attributes to the top level parent" do
it "automatically forwards top level attributes to the top level parent" do
@spec.activate_platform(:ios)
[:version, :summary, :platform, :license, :authors, :requires_arc].each do |attr|
[:version, :license, :authors, :requires_arc].each do |attr|
@spec.subspecs.first.send(attr).should == @spec.send(attr)
@spec.subspecs.first.subspecs.first.send(attr).should == @spec.send(attr)
end
end
it "resolves correctly chained attributes" do
@spec.activate_platform(:ios)
@spec.source_files.map { |f| f.to_s }.should == %w[ spec.m ]
@subspec.source_files.map { |f| f.to_s }.should == %w[ spec.m subspec_ios.m ]
@subsubspec.source_files.map { |f| f.to_s }.should == %w[ spec.m subspec_ios.m subsubspec.m ]
@subsubspec.resources.should == %w[ resource ]
end
it "returns empty arrays for chained attributes with no value in the chain" do
@spec = Pod::Spec.new do |s|
s.name = 'MainSpec'
s.platform = :ios
s.subspec 'FirstSubSpec' do |fss|
fss.subspec 'SecondSubSpec' do |sss|
sss.source_files = 'subsubspec.m'
end
end
end
@spec.activate_platform(:ios).source_files.should == []
@spec.subspecs.first.source_files.should == []
@spec.subspecs.first.subspecs.first.source_files.should == %w[ subsubspec.m ]
end
it "does not cache platform attributes and can activate another platform" do
@spec.platform = nil
@spec.activate_platform(:ios)
@subsubspec.source_files.map { |f| f.to_s }.should == %w[ spec.m subspec_ios.m subsubspec.m ]
@spec.activate_platform(:osx)
@subsubspec.source_files.map { |f| f.to_s }.should == %w[ spec.m subspec_osx.m subsubspec.m ]
end
it "resolves correctly the available platforms" do
@spec.platform = nil
@subspec.platform = :ios, '4.0'
@spec.available_platforms.map{ |p| p.to_sym }.should == [ :osx, :ios ]
@subspec.available_platforms.first.to_sym.should == :ios
@subsubspec.available_platforms.first.to_sym.should == :ios
@subsubspec.platform = :ios, '5.0'
@subspec.available_platforms.first.deployment_target.to_s.should == '4.0'
@subsubspec.available_platforms.first.deployment_target.to_s.should == '5.0'
end
it "resolves reports correctly the supported platforms" do
@spec.platform = nil
@subspec.platform = :ios, '4.0'
@subsubspec.platform = :ios, '5.0'
@spec.supports_platform?(:ios).should.be.true
@spec.supports_platform?(:osx).should.be.true
@subspec.supports_platform?(:ios).should.be.true
@subspec.supports_platform?(:osx).should.be.false
@subspec.supports_platform?(:ios, '4.0').should.be.true
@subspec.supports_platform?(:ios, '5.0').should.be.true
@subsubspec.supports_platform?(:ios).should.be.true
@subsubspec.supports_platform?(:osx).should.be.false
@subsubspec.supports_platform?(:ios, '4.0').should.be.false
@subsubspec.supports_platform?(:ios, '5.0').should.be.true
@subsubspec.supports_platform?(Pod::Platform.new(:ios, '4.0')).should.be.false
@subsubspec.supports_platform?(Pod::Platform.new(:ios, '5.0')).should.be.true
end
it "raises a top level attribute is assigned to a spec with a parent" do
lambda { @subspec.version = '0.0.1' }.should.raise Pod::Informative
end
it "returns subspecs by name" do
@spec.subspec_by_name(nil).should == @spec
@spec.subspec_by_name('MainSpec').should == @spec
@spec.subspec_by_name('MainSpec/FirstSubSpec').should == @spec.subspecs.first
@spec.subspec_by_name('MainSpec/FirstSubSpec/SecondSubSpec').should == @spec.subspecs.first.subspecs.first
@spec.subspec_by_name('MainSpec/FirstSubSpec').should == @subspec
@spec.subspec_by_name('MainSpec/FirstSubSpec/SecondSubSpec').should == @subsubspec
end
it "has the same active platform accross the chain attributes" do
@spec.activate_platform(:ios)
@subspec.active_platform.should == :ios
@subsubspec.active_platform.should == :ios
@spec.platform = nil
@subsubspec.activate_platform(:osx)
@subspec.active_platform.should == :osx
@spec.active_platform.should == :osx
end
xit "can be activated for a platorm"
xit "raises if not activated"
xit "returns self on activation for method chainablity"
xit "does not cache platform attributes and can activate another platform"
xit "resolves chained attributes"
xit "resolves not chained attributes"
xit "has the same active platform accross the chain attributes"
xit "raises a top level attribute is assigned to a spec with a parent"
it "resolves the libraries correctly" do
@spec.activate_platform(:ios)
@spec.libraries.should == %w[ xml ]
@subspec.libraries.should == %w[ xml z ]
@subsubspec.libraries.should == %w[ xml z ]
end
it "resolves the frameworks correctly" do
@spec.activate_platform(:ios)
@spec.frameworks.should == %w[ CoreData ]
@subspec.frameworks.should == %w[ CoreData CoreGraphics ]
@subsubspec.frameworks.should == %w[ CoreData CoreGraphics ]
end
it "resolves the xcconfig" do
@spec.activate_platform(:ios)
@spec.xcconfig = { 'OTHER_LDFLAGS' => "-Wl,-no_compact_unwind" }
@spec.xcconfig.should == {"OTHER_LDFLAGS"=>"-Wl,-no_compact_unwind -lxml -framework CoreData"}
@subspec.xcconfig.should == {"OTHER_LDFLAGS"=>"-Wl,-no_compact_unwind -lxml -lz -framework CoreData -framework CoreGraphics"}
@subsubspec.xcconfig.should == {"OTHER_LDFLAGS"=>"-Wl,-no_compact_unwind -lxml -lz -framework CoreData -framework CoreGraphics"}
@subsubspec.xcconfig = { 'HEADER_SEARCH_PATHS' => '$(SDKROOT)/usr/include/libxml2' }
@spec.xcconfig.should == {"OTHER_LDFLAGS"=>"-Wl,-no_compact_unwind -lxml -framework CoreData"}
@subsubspec.xcconfig.should == {"OTHER_LDFLAGS"=>"-Wl,-no_compact_unwind -lxml -lz -framework CoreData -framework CoreGraphics", "HEADER_SEARCH_PATHS"=>"$(SDKROOT)/usr/include/libxml2"}
end
end
describe "A Pod::Specification with :local source" do
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment