fruity_builder 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/README.md +37 -0
 - data/lib/fruity_builder.rb +78 -0
 - data/lib/fruity_builder/build_properties.rb +82 -0
 - data/lib/fruity_builder/lib/execution.rb +78 -0
 - data/lib/fruity_builder/lib/sys_log.rb +39 -0
 - data/lib/fruity_builder/plistutil.rb +50 -0
 - data/lib/fruity_builder/xcodebuild.rb +60 -0
 - metadata +53 -0
 
    
        checksums.yaml
    ADDED
    
    | 
         @@ -0,0 +1,7 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ---
         
     | 
| 
      
 2 
     | 
    
         
            +
            SHA1:
         
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 388eb38a0a12e97f3742704555ca19eaca117ab8
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 39cd6b97094a2b2d2ee8591f7981c25c047b4874
         
     | 
| 
      
 5 
     | 
    
         
            +
            SHA512:
         
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: e7ad3b426b016bd595b0020523a4e93412f80d24376e3627b533b6ba79856594df5a17bdc928bfbe829b7a1c59a74ecaa6a25bcd467867c65aa6b0f1bdf7d92e
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 33eaacaca91a28b4819cf497280e0083df777a86e0fd991c46f6a658e02616fa2870a67e0094a137cdee82cd87f6dd8897f59e21085c18227cd9b92fc68de2f9
         
     | 
    
        data/README.md
    ADDED
    
    | 
         @@ -0,0 +1,37 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Fruity Builder
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Code manipulation tools for iOS code bases.
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            ## Usage
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            Initialise with a path to the project folder:
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 10 
     | 
    
         
            +
            builder = FruityBuilder::IOS::Helper.new(path)
         
     | 
| 
      
 11 
     | 
    
         
            +
            ```
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            ### Replacing build properties
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 16 
     | 
    
         
            +
            builder.build.replace_dev_team('New dev team')
         
     | 
| 
      
 17 
     | 
    
         
            +
            builder.build.replace_code_sign_identity('New identity')
         
     | 
| 
      
 18 
     | 
    
         
            +
            builder.build.replace_provisioning_profile('New profile')
         
     | 
| 
      
 19 
     | 
    
         
            +
            builder.build.save_project_properties
         
     | 
| 
      
 20 
     | 
    
         
            +
            builder.build.replace_bundle_id('New bundle ID')
         
     | 
| 
      
 21 
     | 
    
         
            +
            ```
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
            ### Retrieving build properties
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 26 
     | 
    
         
            +
            builder.build.get_dev_teams # ['Dev Team']
         
     | 
| 
      
 27 
     | 
    
         
            +
            builder.build.get_code_signing_identities # ['Identity 1', 'Identity 2']
         
     | 
| 
      
 28 
     | 
    
         
            +
            builder.build.get_provisioning_profiles # ['Profile 1', 'Profile 2']
         
     | 
| 
      
 29 
     | 
    
         
            +
            ```
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
            ### Retrieving build configurations
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 34 
     | 
    
         
            +
            builder.xcode.get_schemes # ['Scheme 1', 'Scheme 2']
         
     | 
| 
      
 35 
     | 
    
         
            +
            builder.xcode.get_build_configurations # ['Debug', 'Release', 'Test']
         
     | 
| 
      
 36 
     | 
    
         
            +
            builder.xcode.get_target # ['Target 1', 'Target 2']
         
     | 
| 
      
 37 
     | 
    
         
            +
            ```
         
     | 
| 
         @@ -0,0 +1,78 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'fruity_builder/lib/sys_log'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'fruity_builder/xcodebuild'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'fruity_builder/build_properties'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module FruityBuilder
         
     | 
| 
      
 7 
     | 
    
         
            +
              module IOS
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                attr_accessor :log
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                @@log = FruityBuilder::SysLog.new
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def self.log
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @@log
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def self.set_logger(log)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @@log = log
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                class Helper
         
     | 
| 
      
 22 
     | 
    
         
            +
                  attr_accessor :path, :project, :workspace, :build, :plist, :xcode
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  def initialize(path)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    @path = path
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  def project
         
     | 
| 
      
 29 
     | 
    
         
            +
                    if @project.nil?
         
     | 
| 
      
 30 
     | 
    
         
            +
                      if @path.scan(/.*xcodeproj$/).count > 0
         
     | 
| 
      
 31 
     | 
    
         
            +
                        return @path
         
     | 
| 
      
 32 
     | 
    
         
            +
                      end
         
     | 
| 
      
 33 
     | 
    
         
            +
                      projects = Dir["#{@path}/**/*.xcodeproj"]
         
     | 
| 
      
 34 
     | 
    
         
            +
                      projects = projects.select { |project| !project.include?('Pods')}
         
     | 
| 
      
 35 
     | 
    
         
            +
                      raise HelperCommandError.new('Project not found') if projects.empty?
         
     | 
| 
      
 36 
     | 
    
         
            +
                      raise HelperCommandError.new('Mulitple projects found, please specify one') if projects.count > 1
         
     | 
| 
      
 37 
     | 
    
         
            +
                      project = projects.first
         
     | 
| 
      
 38 
     | 
    
         
            +
                      @project = project
         
     | 
| 
      
 39 
     | 
    
         
            +
                    end
         
     | 
| 
      
 40 
     | 
    
         
            +
                    @project
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                  def workspace
         
     | 
| 
      
 44 
     | 
    
         
            +
                    if @workspace.nil?
         
     | 
| 
      
 45 
     | 
    
         
            +
                      if @path.scan(/.*xcworkspace$/).count > 0
         
     | 
| 
      
 46 
     | 
    
         
            +
                        return @path
         
     | 
| 
      
 47 
     | 
    
         
            +
                      end
         
     | 
| 
      
 48 
     | 
    
         
            +
                      workspace = Dir["#{@path}/**/*.xcworkspace"].first
         
     | 
| 
      
 49 
     | 
    
         
            +
                      raise 'Workspace not found' if workspace.nil?
         
     | 
| 
      
 50 
     | 
    
         
            +
                      @workspace = workspace
         
     | 
| 
      
 51 
     | 
    
         
            +
                    end
         
     | 
| 
      
 52 
     | 
    
         
            +
                    @workspace
         
     | 
| 
      
 53 
     | 
    
         
            +
                  end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                  # Handle for the BuildProperties class
         
     | 
| 
      
 56 
     | 
    
         
            +
                  def build
         
     | 
| 
      
 57 
     | 
    
         
            +
                    if @build.nil?
         
     | 
| 
      
 58 
     | 
    
         
            +
                      @build = FruityBuilder::IOS::BuildProperties.new("#{project}/project.pbxproj")
         
     | 
| 
      
 59 
     | 
    
         
            +
                    end
         
     | 
| 
      
 60 
     | 
    
         
            +
                    @build
         
     | 
| 
      
 61 
     | 
    
         
            +
                  end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                  # Handle for the XCodeBuild class
         
     | 
| 
      
 64 
     | 
    
         
            +
                  def xcode
         
     | 
| 
      
 65 
     | 
    
         
            +
                    if @xcode.nil?
         
     | 
| 
      
 66 
     | 
    
         
            +
                      @xcode = FruityBuilder::IOS::XCodeBuild.new(project)
         
     | 
| 
      
 67 
     | 
    
         
            +
                    end
         
     | 
| 
      
 68 
     | 
    
         
            +
                    @xcode
         
     | 
| 
      
 69 
     | 
    
         
            +
                  end
         
     | 
| 
      
 70 
     | 
    
         
            +
                end
         
     | 
| 
      
 71 
     | 
    
         
            +
              end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
              class HelperCommandError < StandardError
         
     | 
| 
      
 74 
     | 
    
         
            +
                def initialize(msg)
         
     | 
| 
      
 75 
     | 
    
         
            +
                  super(msg)
         
     | 
| 
      
 76 
     | 
    
         
            +
                end
         
     | 
| 
      
 77 
     | 
    
         
            +
              end
         
     | 
| 
      
 78 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,82 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'fruity_builder/lib/execution'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'fruity_builder/plistutil'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module FruityBuilder
         
     | 
| 
      
 5 
     | 
    
         
            +
              module IOS
         
     | 
| 
      
 6 
     | 
    
         
            +
                class BuildProperties < Execution
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  attr_accessor :project, :properties
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  def initialize(project_path)
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @project = project_path
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  def open_project_properties
         
     | 
| 
      
 15 
     | 
    
         
            +
                    @properties = File.read(@project)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  def properties
         
     | 
| 
      
 19 
     | 
    
         
            +
                    open_project_properties if @properties.nil?
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @properties
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  def save_project_properties
         
     | 
| 
      
 24 
     | 
    
         
            +
                    File.write(@project, properties)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  def replace_bundle_id(new_bundle_id)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    path = Pathname.new(File.dirname(@project) + '/../').realdirpath.to_s
         
     | 
| 
      
 29 
     | 
    
         
            +
                    xcode = FruityBuilder::IOS::XCodeBuild.new(File.dirname(@project))
         
     | 
| 
      
 30 
     | 
    
         
            +
                    targets = xcode.get_targets
         
     | 
| 
      
 31 
     | 
    
         
            +
                    project_files = Dir["#{path}/**/Info.plist"]
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                    files = project_files.select { |project| targets.any? { |target| project.include?("#{target}/Info.plist") } }
         
     | 
| 
      
 34 
     | 
    
         
            +
                    files.each do |file|
         
     | 
| 
      
 35 
     | 
    
         
            +
                      FruityBuilder::IOS::Plistutil.replace_bundle_id(new_id: new_bundle_id, file: file)
         
     | 
| 
      
 36 
     | 
    
         
            +
                    end
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  def get_dev_teams
         
     | 
| 
      
 40 
     | 
    
         
            +
                    @properties.scan(/.*DevelopmentTeam = (.*);.*/).uniq.flatten
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                  def get_code_signing_identities
         
     | 
| 
      
 44 
     | 
    
         
            +
                    @properties.scan(/.*CODE_SIGN_IDENTITY.*= "(.*)";.*/).uniq.flatten
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                  def get_provisioning_profiles
         
     | 
| 
      
 48 
     | 
    
         
            +
                    @properties.scan(/.*PROVISIONING_PROFILE = "(.*)";.*/).uniq.flatten
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                  def replace_dev_team(new_dev_team)
         
     | 
| 
      
 52 
     | 
    
         
            +
                    @properties = self.class.replace_project_data(regex: '.*DevelopmentTeam = (.*);.*', data: properties, new_value: new_dev_team)
         
     | 
| 
      
 53 
     | 
    
         
            +
                  end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                  def replace_code_sign_identity(new_identity)
         
     | 
| 
      
 56 
     | 
    
         
            +
                    @properties = self.class.replace_project_data(regex: '.*CODE_SIGN_IDENTITY.*= "(.*)";.*', data: properties, new_value: new_identity)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                  def replace_provisioning_profile(new_profile)
         
     | 
| 
      
 60 
     | 
    
         
            +
                    @properties = self.class.replace_project_data(regex: '.*PROVISIONING_PROFILE = "(.*)";.*', data: properties, new_value: new_profile)
         
     | 
| 
      
 61 
     | 
    
         
            +
                  end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                  def self.replace_project_data(options = {})
         
     | 
| 
      
 64 
     | 
    
         
            +
                    regex = Regexp.new(options[:regex])
         
     | 
| 
      
 65 
     | 
    
         
            +
                    replacements = options[:data].scan(regex).uniq.flatten
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                    result = options[:data]
         
     | 
| 
      
 68 
     | 
    
         
            +
                    replacements.each do |to_replace|
         
     | 
| 
      
 69 
     | 
    
         
            +
                      result = result.gsub(to_replace, options[:new_value])
         
     | 
| 
      
 70 
     | 
    
         
            +
                    end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                    result
         
     | 
| 
      
 73 
     | 
    
         
            +
                  end
         
     | 
| 
      
 74 
     | 
    
         
            +
                end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                class BuildPropertiesError < StandardError
         
     | 
| 
      
 77 
     | 
    
         
            +
                  def initialize(msg)
         
     | 
| 
      
 78 
     | 
    
         
            +
                    super(msg)
         
     | 
| 
      
 79 
     | 
    
         
            +
                  end
         
     | 
| 
      
 80 
     | 
    
         
            +
                end
         
     | 
| 
      
 81 
     | 
    
         
            +
              end
         
     | 
| 
      
 82 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,78 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Encoding: utf-8
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'open3'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'ostruct'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'timeout'
         
     | 
| 
      
 5 
     | 
    
         
            +
            #require 'fruity_builder'
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            module FruityBuilder
         
     | 
| 
      
 8 
     | 
    
         
            +
              # Provides method to execute terminal commands in a reusable way
         
     | 
| 
      
 9 
     | 
    
         
            +
              class Execution
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                # execute_with_timeout_and_retry constants
         
     | 
| 
      
 12 
     | 
    
         
            +
                COMMAND_TIMEOUT = 30 # base number of seconds to wait until adb command times out
         
     | 
| 
      
 13 
     | 
    
         
            +
                COMMAND_RETRIES = 5 # number of times we will retry the adb command.
         
     | 
| 
      
 14 
     | 
    
         
            +
                # actual maximum seconds waited before timeout is
         
     | 
| 
      
 15 
     | 
    
         
            +
                # (1 * s) + (2 * s) + (3 * s) ... up to (n * s)
         
     | 
| 
      
 16 
     | 
    
         
            +
                # where s = COMMAND_TIMEOUT
         
     | 
| 
      
 17 
     | 
    
         
            +
                # n = COMMAND_RETRIES
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                def self.execute(command)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  # Execute out to shell
         
     | 
| 
      
 21 
     | 
    
         
            +
                  # Returns a struct collecting the execution results
         
     | 
| 
      
 22 
     | 
    
         
            +
                  # struct = DeviceAPI::ADB.execute( 'adb devices' )
         
     | 
| 
      
 23 
     | 
    
         
            +
                  # struct.stdout #=> "std out"
         
     | 
| 
      
 24 
     | 
    
         
            +
                  # struct.stderr #=> ''
         
     | 
| 
      
 25 
     | 
    
         
            +
                  # strict.exit #=> 0
         
     | 
| 
      
 26 
     | 
    
         
            +
                  result = OpenStruct.new
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  stdout, stderr, status = Open3.capture3(command)
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  result.exit = status.exitstatus
         
     | 
| 
      
 31 
     | 
    
         
            +
                  result.stdout = stdout
         
     | 
| 
      
 32 
     | 
    
         
            +
                  result.stderr = stderr
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  result
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                def self.execute_with_timeout_and_retry(command)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  retries_left = COMMAND_RETRIES
         
     | 
| 
      
 39 
     | 
    
         
            +
                  cmd_successful = false
         
     | 
| 
      
 40 
     | 
    
         
            +
                  result = 0
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                  while (retries_left > 0) and (cmd_successful == false) do
         
     | 
| 
      
 43 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 44 
     | 
    
         
            +
                      ::Timeout.timeout(COMMAND_TIMEOUT) do
         
     | 
| 
      
 45 
     | 
    
         
            +
                        result = execute(command)
         
     | 
| 
      
 46 
     | 
    
         
            +
                        cmd_successful = true
         
     | 
| 
      
 47 
     | 
    
         
            +
                      end
         
     | 
| 
      
 48 
     | 
    
         
            +
                    rescue ::Timeout::Error
         
     | 
| 
      
 49 
     | 
    
         
            +
                      retries_left -= 1
         
     | 
| 
      
 50 
     | 
    
         
            +
                      if retries_left > 0
         
     | 
| 
      
 51 
     | 
    
         
            +
                        FruityBuilder.log.error "Command #{command} timed out after #{COMMAND_TIMEOUT.to_s} sec, retrying,"\
         
     | 
| 
      
 52 
     | 
    
         
            +
                            + " #{retries_left.to_s} attempts left.."
         
     | 
| 
      
 53 
     | 
    
         
            +
                      end
         
     | 
| 
      
 54 
     | 
    
         
            +
                    end
         
     | 
| 
      
 55 
     | 
    
         
            +
                  end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                  if retries_left < COMMAND_RETRIES # if we had to retry
         
     | 
| 
      
 58 
     | 
    
         
            +
                    if cmd_successful == false
         
     | 
| 
      
 59 
     | 
    
         
            +
                      msg = "Command #{command} timed out after #{COMMAND_RETRIES.to_s} retries. !"\
         
     | 
| 
      
 60 
     | 
    
         
            +
                        + " Exiting.."
         
     | 
| 
      
 61 
     | 
    
         
            +
                      FruityBuilder.log.fatal(msg)
         
     | 
| 
      
 62 
     | 
    
         
            +
                      raise FruityBuilder::CommandTimeoutError.new(msg)
         
     | 
| 
      
 63 
     | 
    
         
            +
                    else
         
     | 
| 
      
 64 
     | 
    
         
            +
                      FruityBuilder.log.info "Command #{command} succeeded execution after retrying"
         
     | 
| 
      
 65 
     | 
    
         
            +
                    end
         
     | 
| 
      
 66 
     | 
    
         
            +
                  end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                  result
         
     | 
| 
      
 69 
     | 
    
         
            +
                end
         
     | 
| 
      
 70 
     | 
    
         
            +
              end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
              class CommandTimeoutError < StandardError
         
     | 
| 
      
 73 
     | 
    
         
            +
                def initialize(msg)
         
     | 
| 
      
 74 
     | 
    
         
            +
                  super(msg)
         
     | 
| 
      
 75 
     | 
    
         
            +
                end
         
     | 
| 
      
 76 
     | 
    
         
            +
              end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,39 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'syslog'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module FruityBuilder
         
     | 
| 
      
 4 
     | 
    
         
            +
              class SysLog
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                attr_accessor :syslog
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                def log(priority,message)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  if @syslog and @syslog.opened?
         
     | 
| 
      
 10 
     | 
    
         
            +
                    @syslog = Syslog.reopen('device-api-gem', Syslog::LOG_PID, Syslog::LOG_DAEMON)
         
     | 
| 
      
 11 
     | 
    
         
            +
                  else
         
     | 
| 
      
 12 
     | 
    
         
            +
                    @syslog = Syslog.open('device-api-gem', Syslog::LOG_PID, Syslog::LOG_DAEMON)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  @syslog.log(priority,message)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @syslog.close
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                def fatal(message)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  self.log(Syslog::LOG_CRIT,message)
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                def error(message)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  self.log(Syslog::LOG_ERR,message)
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                def warn(message)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  self.log(Syslog::LOG_WARNING,message)
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                def info(message)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  self.log(Syslog::LOG_INFO,message)
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                def debug(message)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  self.log(Syslog::LOG_DEBUG,message)
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
              end
         
     | 
| 
      
 39 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,50 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'fruity_builder/lib/execution'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module FruityBuilder
         
     | 
| 
      
 4 
     | 
    
         
            +
              module IOS
         
     | 
| 
      
 5 
     | 
    
         
            +
                class Plistutil < Execution
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                  def self.get_bundle_id(options = {})
         
     | 
| 
      
 8 
     | 
    
         
            +
                    if options.key?(:file)
         
     | 
| 
      
 9 
     | 
    
         
            +
                      xml = IO.read(options[:file])
         
     | 
| 
      
 10 
     | 
    
         
            +
                    elsif options.key?(:xml)
         
     | 
| 
      
 11 
     | 
    
         
            +
                      xml = options[:xml]
         
     | 
| 
      
 12 
     | 
    
         
            +
                    end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                    raise PlistutilCommandError.new('No XML was passed') unless xml
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                    identifiers = xml.scan(/.*CFBundleIdentifier<\/key>\n\t<string>(.*?)<\/string>/)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    identifiers << xml.scan(/.*CFBundleName<\/key>\n\t<string>(.*?)<\/string>/)
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                    identifiers.flatten.uniq
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  def self.replace_bundle_id(options = {})
         
     | 
| 
      
 23 
     | 
    
         
            +
                    if options.key?(:file)
         
     | 
| 
      
 24 
     | 
    
         
            +
                      xml = IO.read(options[:file])
         
     | 
| 
      
 25 
     | 
    
         
            +
                    elsif options.key?(:xml)
         
     | 
| 
      
 26 
     | 
    
         
            +
                      xml = options[:xml]
         
     | 
| 
      
 27 
     | 
    
         
            +
                    end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                    raise PlistutilCommandError.new('No XML was passed') unless xml
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                    replacements = xml.scan(/.*CFBundleIdentifier<\/key>\n\t<string>(.*?)<\/string>/)
         
     | 
| 
      
 32 
     | 
    
         
            +
                    replacements << xml.scan(/.*CFBundleName<\/key>\n\t<string>(.*?)<\/string>/)
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                    replacements.flatten.uniq.each do |replacement|
         
     | 
| 
      
 35 
     | 
    
         
            +
                      xml = xml.gsub(replacement, options[:new_id])
         
     | 
| 
      
 36 
     | 
    
         
            +
                    end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                    IO.write(options[:file], xml) if options.key?(:file)
         
     | 
| 
      
 39 
     | 
    
         
            +
                    xml
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                # plistutil error class
         
     | 
| 
      
 44 
     | 
    
         
            +
                class PlistutilCommandError < StandardError
         
     | 
| 
      
 45 
     | 
    
         
            +
                  def initialize(msg)
         
     | 
| 
      
 46 
     | 
    
         
            +
                    super(msg)
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
              end
         
     | 
| 
      
 50 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,60 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'fruity_builder/lib/execution'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module FruityBuilder
         
     | 
| 
      
 4 
     | 
    
         
            +
              module IOS
         
     | 
| 
      
 5 
     | 
    
         
            +
                class XCodeBuild < Execution
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                  attr_accessor :path
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  def initialize(path)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    @path = path
         
     | 
| 
      
 11 
     | 
    
         
            +
                  end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  def get_schemes
         
     | 
| 
      
 14 
     | 
    
         
            +
                    self.class.get_schemes(path)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  def get_targets
         
     | 
| 
      
 18 
     | 
    
         
            +
                    self.class.get_targets(path)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  def get_build_configurations
         
     | 
| 
      
 22 
     | 
    
         
            +
                    self.class.get_build_configurations(path)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  def self.retrieve_project_section(project_info, section)
         
     | 
| 
      
 26 
     | 
    
         
            +
                    index = project_info.index(section)
         
     | 
| 
      
 27 
     | 
    
         
            +
                    section_values = []
         
     | 
| 
      
 28 
     | 
    
         
            +
                    for i in index+1..project_info.count - 1
         
     | 
| 
      
 29 
     | 
    
         
            +
                      break if project_info[i].empty?
         
     | 
| 
      
 30 
     | 
    
         
            +
                      section_values << project_info[i]
         
     | 
| 
      
 31 
     | 
    
         
            +
                    end
         
     | 
| 
      
 32 
     | 
    
         
            +
                    section_values
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  def self.get_schemes(project_path)
         
     | 
| 
      
 36 
     | 
    
         
            +
                    retrieve_project_section(get_project_info(project_path), 'Schemes:')
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  def self.get_build_configurations(project_path)
         
     | 
| 
      
 40 
     | 
    
         
            +
                    retrieve_project_section(get_project_info(project_path), 'Build Configurations:')
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                  def self.get_targets(project_path)
         
     | 
| 
      
 44 
     | 
    
         
            +
                    retrieve_project_section(get_project_info(project_path), 'Targets:')
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                  def self.get_project_info(project_path)
         
     | 
| 
      
 48 
     | 
    
         
            +
                    info = execute("xcodebuild -project #{project_path} -list")
         
     | 
| 
      
 49 
     | 
    
         
            +
                    raise XCodeBuildCommandError.new(info.stderr) if info.exit != 0
         
     | 
| 
      
 50 
     | 
    
         
            +
                    info.stdout.split("\n").map { |a| a.strip }
         
     | 
| 
      
 51 
     | 
    
         
            +
                  end
         
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                class XCodeBuildCommandError < StandardError
         
     | 
| 
      
 55 
     | 
    
         
            +
                  def initialize(msg)
         
     | 
| 
      
 56 
     | 
    
         
            +
                    super(msg)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  end
         
     | 
| 
      
 58 
     | 
    
         
            +
                end
         
     | 
| 
      
 59 
     | 
    
         
            +
              end
         
     | 
| 
      
 60 
     | 
    
         
            +
            end
         
     | 
    
        metadata
    ADDED
    
    | 
         @@ -0,0 +1,53 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            --- !ruby/object:Gem::Specification
         
     | 
| 
      
 2 
     | 
    
         
            +
            name: fruity_builder
         
     | 
| 
      
 3 
     | 
    
         
            +
            version: !ruby/object:Gem::Version
         
     | 
| 
      
 4 
     | 
    
         
            +
              version: 1.0.0
         
     | 
| 
      
 5 
     | 
    
         
            +
            platform: ruby
         
     | 
| 
      
 6 
     | 
    
         
            +
            authors:
         
     | 
| 
      
 7 
     | 
    
         
            +
            - BBC
         
     | 
| 
      
 8 
     | 
    
         
            +
            - Jon Wilson
         
     | 
| 
      
 9 
     | 
    
         
            +
            autorequire: 
         
     | 
| 
      
 10 
     | 
    
         
            +
            bindir: bin
         
     | 
| 
      
 11 
     | 
    
         
            +
            cert_chain: []
         
     | 
| 
      
 12 
     | 
    
         
            +
            date: 2015-10-20 00:00:00.000000000 Z
         
     | 
| 
      
 13 
     | 
    
         
            +
            dependencies: []
         
     | 
| 
      
 14 
     | 
    
         
            +
            description: iOS code signing utilities - used to replace bundle IDs, development
         
     | 
| 
      
 15 
     | 
    
         
            +
              teams and provisioning profiles programmatically
         
     | 
| 
      
 16 
     | 
    
         
            +
            email:
         
     | 
| 
      
 17 
     | 
    
         
            +
            - jon.wilson01@bbc.co.uk
         
     | 
| 
      
 18 
     | 
    
         
            +
            executables: []
         
     | 
| 
      
 19 
     | 
    
         
            +
            extensions: []
         
     | 
| 
      
 20 
     | 
    
         
            +
            extra_rdoc_files: []
         
     | 
| 
      
 21 
     | 
    
         
            +
            files:
         
     | 
| 
      
 22 
     | 
    
         
            +
            - README.md
         
     | 
| 
      
 23 
     | 
    
         
            +
            - lib/fruity_builder.rb
         
     | 
| 
      
 24 
     | 
    
         
            +
            - lib/fruity_builder/build_properties.rb
         
     | 
| 
      
 25 
     | 
    
         
            +
            - lib/fruity_builder/lib/execution.rb
         
     | 
| 
      
 26 
     | 
    
         
            +
            - lib/fruity_builder/lib/sys_log.rb
         
     | 
| 
      
 27 
     | 
    
         
            +
            - lib/fruity_builder/plistutil.rb
         
     | 
| 
      
 28 
     | 
    
         
            +
            - lib/fruity_builder/xcodebuild.rb
         
     | 
| 
      
 29 
     | 
    
         
            +
            homepage: https://github.com/bbc/fruity_builder
         
     | 
| 
      
 30 
     | 
    
         
            +
            licenses:
         
     | 
| 
      
 31 
     | 
    
         
            +
            - MIT
         
     | 
| 
      
 32 
     | 
    
         
            +
            metadata: {}
         
     | 
| 
      
 33 
     | 
    
         
            +
            post_install_message: 
         
     | 
| 
      
 34 
     | 
    
         
            +
            rdoc_options: []
         
     | 
| 
      
 35 
     | 
    
         
            +
            require_paths:
         
     | 
| 
      
 36 
     | 
    
         
            +
            - lib
         
     | 
| 
      
 37 
     | 
    
         
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 38 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 39 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 40 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 41 
     | 
    
         
            +
                  version: '0'
         
     | 
| 
      
 42 
     | 
    
         
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 43 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 44 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 45 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 46 
     | 
    
         
            +
                  version: '0'
         
     | 
| 
      
 47 
     | 
    
         
            +
            requirements: []
         
     | 
| 
      
 48 
     | 
    
         
            +
            rubyforge_project: 
         
     | 
| 
      
 49 
     | 
    
         
            +
            rubygems_version: 2.4.8
         
     | 
| 
      
 50 
     | 
    
         
            +
            signing_key: 
         
     | 
| 
      
 51 
     | 
    
         
            +
            specification_version: 4
         
     | 
| 
      
 52 
     | 
    
         
            +
            summary: iOS code signing utilities
         
     | 
| 
      
 53 
     | 
    
         
            +
            test_files: []
         
     |