diff --git a/CHANGELOG.md b/CHANGELOG.md index 48fe6d35f..f5e241514 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ##### Enhancements +* Add Podfile DSL for `script_phase` + [Dimitris Koutsogiorgas](https://github.com/dnkoutso) + [#389](https://github.com/CocoaPods/Core/pull/389) + * Introduce `static_framework` podspec attribute [Paul Beusterien](https://github.com/paulb777) [#386](https://github.com/CocoaPods/Core/pull/386) diff --git a/lib/cocoapods-core/podfile/dsl.rb b/lib/cocoapods-core/podfile/dsl.rb index 70b9ad397..652baa0a9 100644 --- a/lib/cocoapods-core/podfile/dsl.rb +++ b/lib/cocoapods-core/podfile/dsl.rb @@ -334,6 +334,48 @@ def target(name, options = nil) self.current_target_definition = parent end + # Adds a script phase to be integrated with this target. A script phase can be used to execute an arbitrary + # script that can use all Xcode environment variables during execution. A target may include multiple script + # phases which they will be added in the order they were declared. Deleting a script phase will effectively remove + # it from the target if it has been added previously. + # + # @example + # script_phase :name => 'HelloWorldScript', :script => 'echo "Hello World"' + # + # @example + # script_phase :name => 'HelloWorldScript', :script => 'puts "Hello World"', :shell_path => '/usr/bin/ruby' + # + # @param [Hash] options + # the options for this script phase. + # + # @option options [String] :name + # the name of the script phase. This option is required. + # + # @option options [String] :script + # the body of the script to execute. This option is required. + # + # @option options [String] :shell_path + # the shell path to use for this script phase, for example `/usr/bin/ruby` to use Ruby for this phase. + # + # @option options [Array] :input_paths + # the input paths to use for script. This is used by Xcode to determine whether to re-execute this + # script phase if the input paths have changed or not. + # + # @option options [Array] :output_paths + # the output paths to use for script. This is used by Xcode to avoid re-executing this script phase if + # none of the output paths have changed. + # + # @option options [Boolean] :show_env_vars_in_log + # whether this script phase should output the environment variables during execution. + # + # @return [void] + # + def script_phase(options) + raise Informative, 'Script phases can only be added within target definitions.' if current_target_definition.root? + raise Informative, 'Script phases cannot be added to abstract targets.' if current_target_definition.abstract? + current_target_definition.store_script_phase(options) + end + # Defines a new abstract target that can be used for convenient # target dependency inheritance. # diff --git a/lib/cocoapods-core/podfile/target_definition.rb b/lib/cocoapods-core/podfile/target_definition.rb index a22230be8..942e235a2 100644 --- a/lib/cocoapods-core/podfile/target_definition.rb +++ b/lib/cocoapods-core/podfile/target_definition.rb @@ -288,6 +288,14 @@ def build_configurations=(hash) #--------------------------------------# + # @return [Array] The list of the script phases of the target definition. + # + def script_phases + get_hash_value('script_phases') || [] + end + + #--------------------------------------# + # @return [Bool] whether the target definition should inhibit warnings # for a single pod. If inhibit_all_warnings is true, it will # return true for any asked pod. @@ -575,6 +583,41 @@ def store_podspec(options = nil) end end + #--------------------------------------# + + 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 + # The options to use for this script phase. The required keys + # are: `:name`, `:script`, while the optional keys are: + # `:shell_path`, `:input_files`, `:output_files` and `:show_env_vars_in_log`. + # + # @return [void] + # + def store_script_phase(options) + option_keys = options.keys + unrecognized_keys = option_keys - 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}`." + end + missing_required_keys = SCRIPT_PHASE_REQUIRED_KEYS - option_keys + unless missing_required_keys.empty? + raise StandardError, "Missing required shell script phase options `#{missing_required_keys.join(', ')}`" + end + script_phases_hash = get_hash_value('script_phases', []) + if script_phases_hash.map { |script_phase_options| script_phase_options[:name] }.include?(options[:name]) + raise StandardError, "Script phase with name `#{options[:name]}` name already present for target `#{name}`." + end + script_phases_hash << options + end + #-----------------------------------------------------------------------# public @@ -595,6 +638,7 @@ def store_podspec(options = nil) user_project_path build_configurations dependencies + script_phases children configuration_pod_whitelist uses_frameworks diff --git a/spec/podfile/dsl_spec.rb b/spec/podfile/dsl_spec.rb index 9ddef4849..3c8583980 100644 --- a/spec/podfile/dsl_spec.rb +++ b/spec/podfile/dsl_spec.rb @@ -508,6 +508,26 @@ module Pod end end + describe 'script phases' do + it 'raises when adding a script phase to the root target definition' do + should.raise(Informative) do + Podfile.new do + script_phase :name => 'PhaseName', :script => 'echo "Hello World"' + end + end.message.should == 'Script phases can only be added within target definitions.' + end + + it 'raises when adding a script phase to an abstract target' do + should.raise(Informative) do + Podfile.new do + abstract_target 'AbstractTarget' do + script_phase :name => 'PhaseName', :script => 'echo "Hello World"' + end + end + end.message.should == 'Script phases cannot be added to abstract targets.' + end + end + #-------------------------------------------------------------------------# end end diff --git a/spec/podfile/target_definition_spec.rb b/spec/podfile/target_definition_spec.rb index 942a98d4d..0020bb4c4 100644 --- a/spec/podfile/target_definition_spec.rb +++ b/spec/podfile/target_definition_spec.rb @@ -325,6 +325,41 @@ module Pod #--------------------------------------# + it 'raises if script phase is missing required key' do + e = lambda { @parent.store_script_phase(:name => 'PhaseName') }.should.raise Podfile::StandardError + e.message.should == 'Missing required shell script phase options `script`' + end + + it 'raises if script phase includes an unrecognized key' do + e = lambda { @parent.store_script_phase(:name => 'PhaseName', :unknown => 'Unknown') }.should.raise Podfile::StandardError + e.message.should == 'Unrecognized options `[:unknown]` in shell script `{:name=>"PhaseName", :unknown=>"Unknown"}` within `MyApp` target. ' \ + 'Available options are `[:name, :script, :shell_path, :input_files, :output_files, :show_env_vars_in_log]`.' + end + + it 'raises if the same script phase name already exists' do + e = lambda do + @parent.store_script_phase(:name => 'PhaseName', :script => 'echo "Hello World"') + @parent.store_script_phase(:name => 'PhaseName', :script => 'echo "Hello World"') + end.should.raise Podfile::StandardError + e.message.should == 'Script phase with name `PhaseName` name already present for target `MyApp`.' + end + + it 'stores a script phase if requirements are provided' do + @parent.store_script_phase(:name => 'PhaseName', :script => 'echo "Hello World"') + @parent.script_phases.should == [ + { :name => 'PhaseName', :script => 'echo "Hello World"' }, + ] + end + + it 'stores a script phase with requirements and optional keys' do + @parent.store_script_phase(:name => 'PhaseName', :script => 'echo "Hello World"', :shell_path => :'/usr/bin/ruby') + @parent.script_phases.should == [ + { :name => 'PhaseName', :script => 'echo "Hello World"', :shell_path => :'/usr/bin/ruby' }, + ] + end + + #--------------------------------------# + it 'whitelists pods by default' do @parent.store_pod('ObjectiveSugar') @parent.should.pod_whitelisted_for_configuration?('ObjectiveSugar', 'Release')