diff --git a/CHANGELOG.md b/CHANGELOG.md index 55c3b453a..2b940aafc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ##### Enhancements +* Add podspec `script_phases` DSL + [Dimitris Koutsogiorgas](https://github.com/dnkoutso) + [#413](https://github.com/CocoaPods/Core/pull/413) + * Update commments/docs to indicate prefix_header=false will skip pch generation [Paul Beusterien](https://github.com/paulb777) [#412](https://github.com/CocoaPods/Core/pull/412) diff --git a/lib/cocoapods-core/podfile/target_definition.rb b/lib/cocoapods-core/podfile/target_definition.rb index f553b47d1..c2bb8c210 100644 --- a/lib/cocoapods-core/podfile/target_definition.rb +++ b/lib/cocoapods-core/podfile/target_definition.rb @@ -585,12 +585,6 @@ def store_podspec(options = nil) #--------------------------------------# - SCRIPT_PHASE_REQUIRED_KEYS = [:name, :script].freeze - - SCRIPT_PHASE_OPTIONAL_KEYS = [:shell_path, :input_files, :output_files, :show_env_vars_in_log].freeze - - ALL_SCRIPT_PHASE_KEYS = (SCRIPT_PHASE_REQUIRED_KEYS + SCRIPT_PHASE_OPTIONAL_KEYS).freeze - # Stores the script phase to add for this target definition. # # @param [Hash] options @@ -602,12 +596,12 @@ def store_podspec(options = nil) # def store_script_phase(options) option_keys = options.keys - unrecognized_keys = option_keys - ALL_SCRIPT_PHASE_KEYS + unrecognized_keys = option_keys - Specification::ALL_SCRIPT_PHASE_KEYS unless unrecognized_keys.empty? raise StandardError, "Unrecognized options `#{unrecognized_keys}` in shell script `#{options}` within `#{name}` target. " \ - "Available options are `#{ALL_SCRIPT_PHASE_KEYS}`." + "Available options are `#{Specification::ALL_SCRIPT_PHASE_KEYS}`." end - missing_required_keys = SCRIPT_PHASE_REQUIRED_KEYS - option_keys + missing_required_keys = Specification::SCRIPT_PHASE_REQUIRED_KEYS - option_keys unless missing_required_keys.empty? raise StandardError, "Missing required shell script phase options `#{missing_required_keys.join(', ')}`" end diff --git a/lib/cocoapods-core/specification.rb b/lib/cocoapods-core/specification.rb index 0b264d294..6ec57fafe 100644 --- a/lib/cocoapods-core/specification.rb +++ b/lib/cocoapods-core/specification.rb @@ -352,6 +352,12 @@ def prefix_header_file attributes_hash['prefix_header_file'] end + # @return [Hash{String=>String}] The script_phases value. + # + def script_phases + attributes_hash['script_phases'] + end + #-------------------------------------------------------------------------# public @@ -462,7 +468,7 @@ def platform_hash # @param [Object] value # the value to store. # - # @param [Symbol] platform. + # @param [Symbol] platform_name # If provided the attribute is stored only for the given platform. # # @note If the provides value is Hash the keys are converted to a string. diff --git a/lib/cocoapods-core/specification/consumer.rb b/lib/cocoapods-core/specification/consumer.rb index aef64fe53..a51a31e81 100644 --- a/lib/cocoapods-core/specification/consumer.rb +++ b/lib/cocoapods-core/specification/consumer.rb @@ -179,6 +179,11 @@ def user_target_xcconfig # spec_attr_accessor :resource_bundles + # @return [ArrayString}>] An array of hashes where each hash + # represents a script phase. + # + spec_attr_accessor :script_phases + # @return [Array] A hash where the key represents the # paths of the resources to copy and the values the paths of # the resources that should be copied. @@ -362,6 +367,25 @@ def _prepare_prefix_header_contents(value) end end + # Normalizes script phases value into an array of hashes where each hash represents the entire script phase. + # + # @param [Hash{String=>String}] value. + # The value of the attribute as specified by the user. + # + # @return [ArrayString}>] the normalized script phases array. + # + def _prepare_script_phases(value) + if value + value.map do |name, options| + if options.is_a?(String) + { :name => name, :script => options } + else + DSL::RootAttributesAccessors.convert_keys_to_symbol(options.dup.merge!(:name => name)) + end + end + end + end + # Ensures that the file patterns of the resource bundles are contained in # an array. # diff --git a/lib/cocoapods-core/specification/dsl.rb b/lib/cocoapods-core/specification/dsl.rb index 8af5bdb6b..ff4199010 100644 --- a/lib/cocoapods-core/specification/dsl.rb +++ b/lib/cocoapods-core/specification/dsl.rb @@ -1134,7 +1134,7 @@ def dependency(*args) #------------------# - # @!method resource_bundles=(*frameworks) + # @!method resource_bundles=(*resource_bundles) # # This attribute allows to define the name and the file of the resource # bundles which should be built for the Pod. They are specified as a @@ -1272,6 +1272,47 @@ def dependency(*args) attribute :module_map, :root_only => true + #------------------# + + SCRIPT_PHASE_REQUIRED_KEYS = [:name, :script].freeze + + SCRIPT_PHASE_OPTIONAL_KEYS = [:shell_path, :input_files, :output_files, :show_env_vars_in_log].freeze + + ALL_SCRIPT_PHASE_KEYS = (SCRIPT_PHASE_REQUIRED_KEYS + SCRIPT_PHASE_OPTIONAL_KEYS).freeze + + # @!method script_phases=(*script_phases) + # + # This attribute allows to define a script phase to execute as part of compilation of the Pod. + # Unlike a prepare command, script phases execute as part of `xcodebuild` they can also utilize all environment + # variables that are set during compilation. + # + # A Pod can provide multiple script phases to execute and they will be added in the order they were + # declared. + # + # @example + # + # spec.script_phase = { 'Hello World' => 'echo "Hello World"' } + # + # @example + # + # spec.script_phase = { 'Hello Ruby World' => { :script => 'puts "Hello World"', :shell_path => '/usr/bin/ruby' } } + # + # @example + # + # spec.script_phases = { + # 'Hello World' => 'echo "Hello World"', + # 'Hello Ruby World' => { :script => 'puts "Hello World"', :shell_path => '/usr/bin/ruby' } + # } + # + # @param [Hash{String=>String}] script_phases + # A hash where the keys are the names of the script phases + # and the values represent the content and additional options. + # + attribute :script_phases, + :types => [String, Hash], + :container => Array, + :singularize => true + #-----------------------------------------------------------------------# # @!group Subspecs diff --git a/lib/cocoapods-core/specification/linter.rb b/lib/cocoapods-core/specification/linter.rb index ec18a2c1a..98519b463 100644 --- a/lib/cocoapods-core/specification/linter.rb +++ b/lib/cocoapods-core/specification/linter.rb @@ -394,6 +394,23 @@ def _validate_test_type(t) end end + # Performs validations related to the `script_phases` attribute. + # + def _validate_script_phases(s) + s.each do |script_phase| + keys = script_phase.keys + unrecognized_keys = keys - Specification::ALL_SCRIPT_PHASE_KEYS + unless unrecognized_keys.empty? + results.add_error('script_phases', "Unrecognized options `#{unrecognized_keys}` in script phase `#{script_phase[:name]}`. " \ + "Available options are `#{Specification::ALL_SCRIPT_PHASE_KEYS}`.") + end + missing_required_keys = Specification::SCRIPT_PHASE_REQUIRED_KEYS - keys + unless missing_required_keys.empty? + results.add_error('script_phases', "Missing required shell script phase options `#{missing_required_keys.join(', ')}` in script phase `#{script_phase[:name]}`.") + end + end + end + # Performs validations related to github sources. # def perform_github_source_checks(s) diff --git a/lib/cocoapods-core/specification/root_attribute_accessors.rb b/lib/cocoapods-core/specification/root_attribute_accessors.rb index 8bbf28735..a3b4d9221 100644 --- a/lib/cocoapods-core/specification/root_attribute_accessors.rb +++ b/lib/cocoapods-core/specification/root_attribute_accessors.rb @@ -206,8 +206,6 @@ def convert_keys_to_symbol(value) end result end - - #---------------------------------------------------------------------# end end end diff --git a/spec/specification/consumer_spec.rb b/spec/specification/consumer_spec.rb index 11982ba3d..1eb46a416 100644 --- a/spec/specification/consumer_spec.rb +++ b/spec/specification/consumer_spec.rb @@ -279,6 +279,44 @@ module Pod test_consumer = Specification::Consumer.new(test_spec, :ios) test_consumer.requires_app_host?.should.be.true end + + #------------------# + + it 'returns the script phases in correct format when using a string for value' do + @spec.script_phases = { 'HelloWorld' => 'echo "Hello World"' } + @consumer.script_phases.should == [{ :name => 'HelloWorld', :script => 'echo "Hello World"' }] + end + + it 'returns the script phases in correct format when using a hash for value' do + @spec.script_phases = { 'HelloWorld' => { :script => 'echo "Hello World"', :shell_path => 'usr/bin/ruby' } } + @consumer.script_phases.should == [{ :name => 'HelloWorld', :script => 'echo "Hello World"', :shell_path => 'usr/bin/ruby' }] + end + + it 'returns the script phases in correct format when using both string and hash for values' do + @spec.script_phases = { 'HelloWorld' => 'echo "Hello World"', 'HelloWorld2' => { :script => 'echo "Hello World"' } } + @consumer.script_phases.should == [ + { :name => 'HelloWorld', :script => 'echo "Hello World"' }, + { :name => 'HelloWorld2', :script => 'echo "Hello World"' }, + ] + end + + it 'handles multi-platform script phases' do + @spec.ios.script_phases = { 'HelloWorld' => 'echo "Hello World iOS"' } + @consumer.script_phases.should == [{ :name => 'HelloWorld', :script => 'echo "Hello World iOS"' }] + end + + it 'merges multi platform script phases if needed' do + @spec.script_phases = { 'HelloWorld' => 'echo "Hello World"' } + @spec.ios.script_phases = { 'HelloWorld-iOS' => 'echo "Hello World iOS"' } + @consumer.script_phases.should == [ + { :name => 'HelloWorld', :script => 'echo "Hello World"' }, + { :name => 'HelloWorld-iOS', :script => 'echo "Hello World iOS"' }, + ] + end + + it 'returns the empty hash if no script phases have been specified' do + @consumer.script_phases.should == [] + end end #-------------------------------------------------------------------------# diff --git a/spec/specification/dsl_spec.rb b/spec/specification/dsl_spec.rb index 5060ab22a..00d178051 100644 --- a/spec/specification/dsl_spec.rb +++ b/spec/specification/dsl_spec.rb @@ -293,6 +293,16 @@ module Pod @spec.module_map = 'module.modulemap' @spec.attributes_hash['module_map'].should == 'module.modulemap' end + + it 'allows to specify the script phases shipped with the Pod' do + @spec.script_phases = { 'HelloWorld' => 'echo "Hello World"' } + @spec.attributes_hash['script_phases'].should == { 'HelloWorld' => 'echo "Hello World"' } + end + + it 'allows to specify the script phases shipped with the Pod as a hash' do + @spec.script_phases = { 'HelloWorld' => { :script => 'puts "Hello World"', :shell_path => '/usr/bin/ruby' } } + @spec.attributes_hash['script_phases'].should == { 'HelloWorld' => { 'script' => 'puts "Hello World"', 'shell_path' => '/usr/bin/ruby' } } + end end #-----------------------------------------------------------------------------# @@ -464,7 +474,7 @@ module Pod end singularized.map { |attr| attr.name.to_s }.sort.should == %w( authors compiler_flags default_subspecs frameworks libraries - preserve_paths resource_bundles resources screenshots + preserve_paths resource_bundles resources screenshots script_phases vendored_frameworks vendored_libraries weak_frameworks ) end diff --git a/spec/specification/json_spec.rb b/spec/specification/json_spec.rb index 82b59d771..eef83eb81 100644 --- a/spec/specification/json_spec.rb +++ b/spec/specification/json_spec.rb @@ -115,6 +115,18 @@ module Pod }] end + it 'writes script phases' do + @spec.script_phases = { + 'Hello World' => 'echo "Hello World"', + 'Hello Ruby World' => { :script => 'puts "Hello World"', :shell_path => '/usr/bin/ruby' }, + } + hash = @spec.to_hash + hash['script_phases'].should == { + 'Hello World' => 'echo "Hello World"', + 'Hello Ruby World' => { 'script' => 'puts "Hello World"', 'shell_path' => '/usr/bin/ruby' }, + } + end + it 'writes test type for test subspec' do @spec.test_spec {} hash = @spec.to_hash @@ -158,6 +170,23 @@ module Pod result.test_specs.first.test_type.should.equal :unit end + it 'can load script phases from hash' do + hash = { + 'name' => 'BananaLib', + 'version' => '1.0', + 'script_phases' => { + 'Hello World' => 'echo "Hello World"', + 'Hello Ruby World' => { 'script' => 'puts "Hello World"', 'shell_path' => '/usr/bin/ruby' }, + }, + } + result = Specification.from_hash(hash) + result.script_phases.count.should.equal 2 + result.script_phases.should == { + 'Hello World' => 'echo "Hello World"', + 'Hello Ruby World' => { 'script' => 'puts "Hello World"', 'shell_path' => '/usr/bin/ruby' }, + } + end + it 'can load test specification from 1.3.0 hash format' do hash = { 'name' => 'BananaLib', diff --git a/spec/specification/linter_spec.rb b/spec/specification/linter_spec.rb index e257c6642..abcd7a582 100644 --- a/spec/specification/linter_spec.rb +++ b/spec/specification/linter_spec.rb @@ -457,6 +457,18 @@ def result_should_include(*values) #------------------# + it 'checks script phases include the required keys' do + @spec.script_phases = { 'HelloWorld' => {} } + result_should_include('script_phases', 'Missing required shell script phase options `script` in script phase `HelloWorld`') + end + + it 'checks script phases include the required keys' do + @spec.script_phases = { 'HelloWorld' => {} } + result_should_include('script_phases', 'Missing required shell script phase options `script` in script phase `HelloWorld`') + end + + #------------------# + it 'accepts valid frameworks' do @spec.frameworks = %w(AddressBook Audio-Frameworks) @linter.lint