class Spaceship::Tunes::BuildTrain

Represents a build train of builds from App Store Connect A build train is all builds for a given version number with different build numbers

Attributes

application[RW]

@return (Spaceship::Tunes::Application) A reference to the application this train is for

builds[R]

@return (Array) An array of all builds that are inside this train (Spaceship::Tunes::Build)

external_testing_enabled[R]

@return (Bool) Is external beta testing enabled for this train? Only one train can have enabled testing.

internal_testing_enabled[R]

@return (Bool) Is internal beta testing enabled for this train? Only one train can have enabled testing.

invalid_builds[R]

@return (Array) An array of all invalid builds that are inside this train

platform[R]

@return (String) Platform (e.g. “ios”)

processing_builds[R]

@return (Array) An array of all processing builds that are inside this train (Spaceship::Tunes::Build) Does not include invalid builds.

I never got this to work to properly try and debug this
version_set[RW]

@return (Spaceship::Tunes::VersionSet) A reference to the version set

this train is for
version_string[R]

@return (String) The version number of this train

Public Class Methods

all(application, app_id, platform: nil) click to toggle source

@param application (Spaceship::Tunes::Application) The app this train is for @param app_id (String) The unique Apple ID of this app

# File spaceship/lib/spaceship/tunes/build_train.rb, line 51
def all(application, app_id, platform: nil)
  trains = []
  trains += client.build_trains(app_id, 'internal', platform: platform)['trains']
  trains += client.build_trains(app_id, 'external', platform: platform)['trains']

  result = {}
  trains.each do |attrs|
    attrs[:application] = application
    current = self.factory(attrs)
    if (!platform.nil? && current.platform == platform) || platform.nil?
      result[current.version_string] = current
    end
  end
  result
end

Public Instance Methods

latest_build() click to toggle source

@return (Spaceship::Tunes::Build) The latest build for this train, sorted by upload time.

# File spaceship/lib/spaceship/tunes/build_train.rb, line 114
def latest_build
  @builds.max_by(&:upload_date)
end
setup() click to toggle source

Setup all the builds and processing builds

Calls superclass method
# File spaceship/lib/spaceship/tunes/build_train.rb, line 69
def setup
  super

  @builds = (self.raw_data['builds'] || []).collect do |attrs|
    attrs[:build_train] = self
    Tunes::Build.factory(attrs)
  end

  @invalid_builds = @builds.select do |build|
    build.processing_state == 'processingFailed' || build.processing_state == 'invalidBinary'
  end

  # This step may not be necessary anymore - it seems as if every processing build will be caught by the
  # @builds.each below, but not every processing build makes it to buildsInProcessing, so this is redundant
  @processing_builds = (self.raw_data['buildsInProcessing'] || []).collect do |attrs|
    attrs[:build_train] = self
    Tunes::Build.factory(attrs)
  end

  # since buildsInProcessing appears empty, fallback to also including processing state from @builds
  @builds.each do |build|
    # What combination of attributes constitutes which state is pretty complicated. The table below summarizes
    # what I've observed, but there's no reason to believe there aren't more states I just haven't seen yet.
    # The column headers are qualitative states of a given build, and the first column is the observed attributes
    # of that build.
    # NOTE: Some of the builds in the build_trains.json fixture do not follow these rules. I don't know if that is
    # because those examples are older, and the iTC API has changed, or if their format is still a possibility.
    # The second part of the OR clause in the line below exists so that those suspicious examples continue to be
    # accepted for unit tests.
    # +---------------------+-------------------+-------------------+-----------------+--------------------+---------+
    # |                     | just after upload | normal processing | invalid binary  | processing failed  | success |
    # +---------------------+-------------------+-------------------+-----------------+--------------------+---------+
    # |  build.processing = | true              | true              | true            | true               | false   |
    # |       build.valid = | false             | true              | false           | true               | true    |
    # | .processing_state = | "processing"      | "processing"      | "invalidBinary" | "processingFailed" | nil     |
    # +---------------------+-------------------+-------------------+-----------------+--------------------+---------+
    if build.processing_state == 'processing' || (build.processing && build.processing_state != 'invalidBinary' && build.processing_state != 'processingFailed')
      @processing_builds << build
    end
  end

  self.version_set = self.application.version_set_for_platform(self.platform)
end
update_testing_status!(new_value, testing_type, build = nil) click to toggle source

@param (testing_type) internal or external

# File spaceship/lib/spaceship/tunes/build_train.rb, line 119
def update_testing_status!(new_value, testing_type, build = nil)
  build ||= latest_build if testing_type == 'external'
  platform = build ? build.platform : self.application.platform
  testing_key = "#{testing_type}Testing"

  data = client.build_trains(self.application.apple_id, testing_type, platform: platform)

  # Delete the irrelevant trains and update the relevant one to enable testing
  data['trains'].delete_if do |train|
    if train['versionString'] != version_string
      true
    else
      train[testing_key]['value'] = new_value

      # also update the builds
      train['builds'].delete_if do |b|
        if b[testing_key].nil?
          true
        elsif build && b["buildVersion"] == build.build_version
          b[testing_key]['value'] = new_value
          false
        elsif b[testing_key]['value'] == true
          b[testing_key]['value'] = false
          false
        else
          true
        end
      end

      false
    end
  end

  begin
    result = client.update_build_trains!(application.apple_id, testing_type, data)
  rescue Spaceship::Tunes::Error => ex
    if ex.to_s.include?("You must provide an answer for this question")
      # This is a very common error message that's raised by TestFlight
      # We want to show a nicer error message with instructions on how
      # to resolve the underlying issue
      # https://github.com/fastlane/fastlane/issues/1873
      # https://github.com/fastlane/fastlane/issues/4002
      error_message = [""] # to have a nice new-line in the beginning
      error_message << "TestFlight requires you to provide the answer to the encryption question"
      error_message << "to provide the reply, please add the following to your Info.plist file"
      error_message << ""
      error_message << "<key>ITSAppUsesNonExemptEncryption</key><false/>"
      error_message << ""
      error_message << "Afterwards re-build your app and try again"
      error_message << "App Store Connect reported: '#{ex}'"
      raise error_message.join("\n")
    else
      raise ex
    end
  end
  self.internal_testing_enabled = new_value if testing_type == 'internal'
  self.external_testing_enabled = new_value if testing_type == 'external'

  result
end