pilot/lib/pilot/build_manager.rb in fastlane-2.125.0.beta.20190531200016 vs pilot/lib/pilot/build_manager.rb in fastlane-2.125.0
- old
+ new
@@ -6,41 +6,34 @@
require 'fastlane_core/build_watcher'
require 'fastlane_core/ipa_upload_package_builder'
require_relative 'manager'
module Pilot
+ # rubocop:disable Metrics/ClassLength
class BuildManager < Manager
def upload(options)
# Only need to login before upload if no apple_id was given
# 'login' will be deferred until before waiting for build processing
should_login_in_start = options[:apple_id].nil?
start(options, should_login: should_login_in_start)
- options[:changelog] = self.class.sanitize_changelog(options[:changelog]) if options[:changelog]
-
UI.user_error!("No ipa file given") unless config[:ipa]
- if options[:changelog].nil? && options[:distribute_external] == true
- if UI.interactive?
- options[:changelog] = UI.input("No changelog provided for new build. You can provide a changelog using the `changelog` option. For now, please provide a changelog here:")
- else
- UI.user_error!("No changelog provided for new build. Please either disable `distribute_external` or provide a changelog using the `changelog` option")
- end
- end
+ check_for_changelog_or_whats_new!(options)
- UI.success("Ready to upload new build to TestFlight (App: #{fetch_apple_id})...")
+ UI.success("Ready to upload new build to TestFlight (App: #{fetch_app_id})...")
dir = Dir.mktmpdir
platform = fetch_app_platform
- package_path = FastlaneCore::IpaUploadPackageBuilder.new.generate(app_id: fetch_apple_id,
+ package_path = FastlaneCore::IpaUploadPackageBuilder.new.generate(app_id: fetch_app_id,
ipa_path: options[:ipa],
package_path: dir,
platform: platform)
transporter = transporter_for_selected_team(options)
- result = transporter.upload(fetch_apple_id, package_path)
+ result = transporter.upload(fetch_app_id, package_path)
unless result
UI.user_error!("Error uploading ipa file, for more information see above")
end
@@ -58,18 +51,45 @@
UI.message("If you want to skip waiting for the processing to be finished, use the `skip_waiting_for_build_processing` option")
latest_build = wait_for_build_processing_to_be_complete
distribute(options, build: latest_build)
end
+ def has_changelog_or_whats_new?(options)
+ # Look for legacy :changelog option
+ has_changelog = !options[:changelog].nil?
+
+ # Look for :whats_new in :localized_build_info
+ unless has_changelog
+ infos_by_lang = options[:localized_build_info] || []
+ infos_by_lang.each do |k, v|
+ next if has_changelog
+ v ||= {}
+ has_changelog = v.key?(:whats_new) || v.key?('whats_new')
+ end
+ end
+
+ return has_changelog
+ end
+
+ def check_for_changelog_or_whats_new!(options)
+ if !has_changelog_or_whats_new?(options) && options[:distribute_external] == true
+ if UI.interactive?
+ options[:changelog] = UI.input("No changelog provided for new build. You can provide a changelog using the `changelog` option. For now, please provide a changelog here:")
+ else
+ UI.user_error!("No changelog provided for new build. Please either disable `distribute_external` or provide a changelog using the `changelog` option")
+ end
+ end
+ end
+
def wait_for_build_processing_to_be_complete
platform = fetch_app_platform
app_version = FastlaneCore::IpaFileAnalyser.fetch_app_version(config[:ipa])
app_build = FastlaneCore::IpaFileAnalyser.fetch_app_build(config[:ipa])
- latest_build = FastlaneCore::BuildWatcher.wait_for_build_processing_to_be_complete(app_id: app.apple_id, platform: platform, train_version: app_version, build_version: app_build, poll_interval: config[:wait_processing_interval], strict_build_watch: config[:wait_for_uploaded_build])
+ latest_build = FastlaneCore::BuildWatcher.wait_for_build_processing_to_be_complete(app_id: app.id, platform: platform, train_version: app_version, build_version: app_build, poll_interval: config[:wait_processing_interval], return_spaceship_testflight_build: false)
- unless latest_build.train_version == app_version && latest_build.build_version == app_build
- UI.important("Uploaded app #{app_version} - #{app_build}, but received build #{latest_build.train_version} - #{latest_build.build_version}.")
+ unless latest_build.app_version == app_version && latest_build.version == app_build
+ UI.important("Uploaded app #{app_version} - #{app_build}, but received build #{latest_build.app_version} - #{latest_build.version}.")
end
return latest_build
end
@@ -77,11 +97,22 @@
start(options)
if config[:apple_id].to_s.length == 0 && config[:app_identifier].to_s.length == 0
config[:app_identifier] = UI.input("App Identifier: ")
end
- build ||= Spaceship::TestFlight::Build.latest(app_id: app.apple_id, platform: fetch_app_platform)
+ # Get latest uploaded build if no build specified
+ build ||= Spaceship::ConnectAPI::Build.all(app_id: app.id, sort: "-uploadedDate", limit: 1).first
+
+ # Verify the build has all the includes that we need
+ # and fetch a new build if not
+ if build && (!build.app || !build.build_beta_detail || !build.pre_release_version)
+ UI.important("Build did include information for app, build beta detail and pre release version")
+ UI.important("Fetching a new build with all the information needed")
+ build = Spaceship::ConnectAPI::Build.get(build_id: build.id)
+ end
+
+ # Error out if no build
if build.nil?
UI.user_error!("No build to distribute!")
end
# Update beta app meta info
@@ -92,16 +123,16 @@
# 5. Auto notify enabled with config[:notify_external_testers]
update_beta_app_meta(options, build)
return if config[:skip_submission]
if options[:reject_build_waiting_for_review]
- waiting_for_review_build = Spaceship::TestFlight::Build.all_waiting_for_review(app_id: build.app_id, platform: fetch_app_platform).first
+ waiting_for_review_build = build.app.get_builds(filter: { "betaAppReviewSubmission.betaReviewState" => "WAITING_FOR_REVIEW" }, includes: "betaAppReviewSubmission,preReleaseVersion").first
unless waiting_for_review_build.nil?
- UI.important("Another build is already in review. Going to expire that build and submit the new one.")
- UI.important("Expiring build: #{waiting_for_review_build.train_version} - #{waiting_for_review_build.build_version}")
- waiting_for_review_build.expire!
- UI.success("Expired previous build: #{waiting_for_review_build.train_version} - #{waiting_for_review_build.build_version}")
+ UI.important("Another build is already in review. Going to remove that build and submit the new one.")
+ UI.important("Deleting beta app review submission for build: #{waiting_for_review_build.app_version} - #{waiting_for_review_build.version}")
+ waiting_for_review_build.beta_app_review_submission.delete!
+ UI.success("Deleted beta app review submission for previous build: #{waiting_for_review_build.app_version} - #{waiting_for_review_build.version}")
end
end
distribute_build(build, options)
type = options[:distribute_external] ? 'External' : 'Internal'
UI.success("Successfully distributed build to #{type} testers 🚀")
@@ -111,60 +142,77 @@
start(options)
if config[:apple_id].to_s.length == 0 && config[:app_identifier].to_s.length == 0
config[:app_identifier] = UI.input("App Identifier: ")
end
- platform = fetch_app_platform(required: false)
- builds = app.all_processing_builds(platform: platform) + app.builds(platform: platform)
- # sort by upload_date
- builds.sort! { |a, b| a.upload_date <=> b.upload_date }
- rows = builds.collect { |build| describe_build(build) }
+ # Get processing builds
+ build_deliveries = app.get_build_deliveries.map do |build_delivery|
+ [
+ build_delivery.cf_build_short_version_string,
+ build_delivery.cf_build_version
+ ]
+ end
+ # Get processed builds
+ builds = app.get_builds(includes: "betaBuildMetrics,preReleaseVersion", sort: "-uploadedDate").map do |build|
+ [
+ build.app_version,
+ build.version,
+ build.beta_build_metrics.map(&:install_count).reduce(:+)
+ ]
+ end
+
+ # Only show table if there are any build deliveries
+ unless build_deliveries.empty?
+ puts(Terminal::Table.new(
+ title: "#{app.name} Processing Builds".green,
+ headings: ["Version #", "Build #"],
+ rows: FastlaneCore::PrintTable.transform_output(build_deliveries)
+ ))
+ end
+
puts(Terminal::Table.new(
title: "#{app.name} Builds".green,
headings: ["Version #", "Build #", "Installs"],
- rows: FastlaneCore::PrintTable.transform_output(rows)
+ rows: FastlaneCore::PrintTable.transform_output(builds)
))
end
def update_beta_app_meta(options, build)
- # App Store Connect API build id
- build_id = build.find_app_store_connect_build["id"]
-
# Setting account required wth AppStore Connect API
- update_review_detail(build.app_id, { demo_account_required: options[:demo_account_required] })
+ update_review_detail(build, { demo_account_required: options[:demo_account_required] })
if should_update_beta_app_review_info(options)
- update_review_detail(build.app_id, options[:beta_app_review_info])
+ update_review_detail(build, options[:beta_app_review_info])
end
if should_update_localized_app_information?(options)
- update_localized_app_review(build.app_id, options[:localized_app_info])
+ update_localized_app_review(build, options[:localized_app_info])
elsif should_update_app_test_information?(options)
default_info = {}
default_info[:feedback_email] = options[:beta_app_feedback_email] if options[:beta_app_feedback_email]
default_info[:description] = options[:beta_app_description] if options[:beta_app_description]
begin
- update_localized_app_review(build.app_id, {}, default_info: default_info)
+ update_localized_app_review(build, {}, default_info: default_info)
UI.success("Successfully set the beta_app_feedback_email and/or beta_app_description")
rescue => ex
UI.user_error!("Could not set beta_app_feedback_email and/or beta_app_description: #{ex}")
end
end
if should_update_localized_build_information?(options)
- update_localized_build_review(build_id, options[:localized_build_info])
+ update_localized_build_review(build, options[:localized_build_info])
elsif should_update_build_information?(options)
begin
- update_localized_build_review(build_id, {}, default_info: { whats_new: options[:changelog] })
+ update_localized_build_review(build, {}, default_info: { whats_new: options[:changelog] })
UI.success("Successfully set the changelog for build")
rescue => ex
UI.user_error!("Could not set changelog: #{ex}")
end
end
- update_build_beta_details(build_id, {
+ update_build_beta_details(build, {
auto_notify_enabled: options[:notify_external_testers]
})
end
def self.truncate_changelog(changelog)
@@ -240,41 +288,31 @@
return generic_transporter
end
end
def distribute_build(uploaded_build, options)
- UI.message("Distributing new build to testers: #{uploaded_build.train_version} - #{uploaded_build.build_version}")
+ UI.message("Distributing new build to testers: #{uploaded_build.app_version} - #{uploaded_build.version}")
# This is where we could add a check to see if encryption is required and has been updated
set_export_compliance_if_needed(uploaded_build, options)
if options[:groups] || options[:distribute_external]
- begin
- uploaded_build.submit_for_testflight_review!
- rescue => ex
- # App Store Connect currently may 504 on this request even though it manages to get the build in
- # the approved state, this is a temporary workaround.
- raise ex unless ex.to_s.include?("504")
- UI.message("Submitting the build for review timed out, trying to recover.")
- updated_build = Spaceship::TestFlight::Build.find(app_id: uploaded_build.app_id, build_id: uploaded_build.id)
- raise ex unless updated_build.approved?
+ if uploaded_build.ready_for_beta_submission?
+ uploaded_build.post_beta_app_review_submission
+ else
+ UI.message("Build #{uploaded_build.app_version} - #{uploaded_build.version} already submitted for review")
end
end
if options[:groups]
- client = Spaceship::ConnectAPI::Base.client
- beta_group_ids = client.get_beta_groups(filter: { app: uploaded_build.app_id }).select do |group|
- options[:groups].include?(group["attributes"]["name"])
- end.map do |group|
- group["id"]
+ app = uploaded_build.app
+ beta_groups = app.get_beta_groups.select do |group|
+ options[:groups].include?(group.name)
end
- unless beta_group_ids.empty?
- build = uploaded_build.find_app_store_connect_build
- build_id = build["id"]
-
- client.add_beta_groups_to_build(build_id: build_id, beta_group_ids: beta_group_ids)
+ unless beta_groups.empty?
+ uploaded_build.add_beta_groups(beta_groups: beta_groups)
end
end
if options[:distribute_external] && options[:groups].nil?
# Legacy Spaceship::TestFlight API used to have a `default_external_group` that would automatically
@@ -284,26 +322,24 @@
true
end
def set_export_compliance_if_needed(uploaded_build, options)
- build = uploaded_build.find_app_store_connect_build
- build_attributes = build["attributes"] || {}
- if build_attributes["usesNonExemptEncryption"].nil?
+ if uploaded_build.uses_non_exempt_encryption.nil?
uses_non_exempt_encryption = options[:uses_non_exempt_encryption]
attributes = { usesNonExemptEncryption: uses_non_exempt_encryption }
client = Spaceship::ConnectAPI::Base.client
- client.patch_builds(build_id: build["id"], attributes: attributes)
+ client.patch_builds(build_id: uploaded_build.id, attributes: attributes)
UI.important("Export compliance has been set to '#{uses_non_exempt_encryption}'. Need to wait for build to finishing processing again...")
UI.important("Set 'ITSAppUsesNonExemptEncryption' in the 'Info.plist' to skip this step and speed up the submission")
wait_for_build_processing_to_be_complete
end
end
- def update_review_detail(app_id, info)
+ def update_review_detail(build, info)
info = info.collect { |k, v| [k.to_sym, v] }.to_h
attributes = {}
attributes[:contactEmail] = info[:contact_email] if info.key?(:contact_email)
attributes[:contactFirstName] = info[:contact_first_name] if info.key?(:contact_first_name)
@@ -313,114 +349,110 @@
attributes[:demoAccountPassword] = info[:demo_account_password] if info.key?(:demo_account_password)
attributes[:demoAccountRequired] = info[:demo_account_required] if info.key?(:demo_account_required)
attributes[:notes] = info[:notes] if info.key?(:notes)
client = Spaceship::ConnectAPI::Base.client
- client.patch_beta_app_review_detail(app_id: app_id, attributes: attributes)
+ client.patch_beta_app_review_detail(app_id: build.app.id, attributes: attributes)
end
- def update_localized_app_review(app_id, info_by_lang, default_info: nil)
+ def update_localized_app_review(build, info_by_lang, default_info: nil)
info_by_lang = info_by_lang.collect { |k, v| [k.to_sym, v] }.to_h
if default_info
info_by_lang.delete(:default)
else
default_info = info_by_lang.delete(:default)
end
- # Initialize hash of lang codes
- langs_localization_ids = {}
+ # Initialize hash of lang codes with info_by_lang keys
+ localizations_by_lang = {}
+ info_by_lang.each_key do |key|
+ localizations_by_lang[key] = nil
+ end
# Validate locales exist
- client = Spaceship::ConnectAPI::Base.client
- localizations = client.get_beta_app_localizations(filter: { app: app_id })
+ localizations = app.get_beta_app_localizations
localizations.each do |localization|
- localization_id = localization["id"]
- attributes = localization["attributes"]
- locale = attributes["locale"]
-
- langs_localization_ids[locale.to_sym] = localization_id
+ localizations_by_lang[localization.locale.to_sym] = localization
end
# Create or update localized app review info
- langs_localization_ids.each do |lang_code, localization_id|
+ localizations_by_lang.each do |lang_code, localization|
info = info_by_lang[lang_code]
info = default_info unless info
- update_localized_app_review_for_lang(app_id, localization_id, lang_code, info) if info
+ update_localized_app_review_for_lang(app, localization, lang_code, info) if info
end
end
- def update_localized_app_review_for_lang(app_id, localization_id, locale, info)
+ def update_localized_app_review_for_lang(app, localization, locale, info)
attributes = {}
attributes[:feedbackEmail] = info[:feedback_email] if info.key?(:feedback_email)
attributes[:marketingUrl] = info[:marketing_url] if info.key?(:marketing_url)
attributes[:privacyPolicyUrl] = info[:privacy_policy_url] if info.key?(:privacy_policy_url)
attributes[:tvOsPrivacyPolicy] = info[:tv_os_privacy_policy_url] if info.key?(:tv_os_privacy_policy_url)
attributes[:description] = info[:description] if info.key?(:description)
client = Spaceship::ConnectAPI::Base.client
- if localization_id
- client.patch_beta_app_localizations(localization_id: localization_id, attributes: attributes)
+ if localization
+ client.patch_beta_app_localizations(localization_id: localization.id, attributes: attributes)
else
attributes[:locale] = locale if locale
- client.post_beta_app_localizations(app_id: app_id, attributes: attributes)
+ client.post_beta_app_localizations(app_id: app.id, attributes: attributes)
end
end
- def update_localized_build_review(build_id, info_by_lang, default_info: nil)
+ def update_localized_build_review(build, info_by_lang, default_info: nil)
info_by_lang = info_by_lang.collect { |k, v| [k.to_sym, v] }.to_h
if default_info
info_by_lang.delete(:default)
else
default_info = info_by_lang.delete(:default)
end
- # Initialize hash of lang codes
- langs_localization_ids = {}
+ # Initialize hash of lang codes with info_by_lang keys
+ localizations_by_lang = {}
+ info_by_lang.each_key do |key|
+ localizations_by_lang[key] = nil
+ end
# Validate locales exist
- client = Spaceship::ConnectAPI::Base.client
- localizations = client.get_beta_build_localizations(filter: { build: build_id })
+ localizations = build.get_beta_build_localizations
localizations.each do |localization|
- localization_id = localization["id"]
- attributes = localization["attributes"]
- locale = attributes["locale"]
-
- langs_localization_ids[locale.to_sym] = localization_id
+ localizations_by_lang[localization.locale.to_sym] = localization
end
# Create or update localized app review info
- langs_localization_ids.each do |lang_code, localization_id|
+ localizations_by_lang.each do |lang_code, localization|
info = info_by_lang[lang_code]
info = default_info unless info
- update_localized_build_review_for_lang(build_id, localization_id, lang_code, info) if info
+ update_localized_build_review_for_lang(build, localization, lang_code, info) if info
end
end
- def update_localized_build_review_for_lang(build_id, localization_id, locale, info)
+ def update_localized_build_review_for_lang(build, localization, locale, info)
attributes = {}
- attributes[:whatsNew] = info[:whats_new] if info.key?(:whats_new)
+ attributes[:whatsNew] = self.class.sanitize_changelog(info[:whats_new]) if info.key?(:whats_new)
client = Spaceship::ConnectAPI::Base.client
- if localization_id
- client.patch_beta_build_localizations(localization_id: localization_id, attributes: attributes)
+ if localization
+ client.patch_beta_build_localizations(localization_id: localization.id, attributes: attributes)
else
attributes[:locale] = locale if locale
- client.post_beta_build_localizations(build_id: build_id, attributes: attributes)
+ client.post_beta_build_localizations(build_id: build.id, attributes: attributes)
end
end
- def update_build_beta_details(build_id, info)
- client = Spaceship::ConnectAPI::Base.client
- resp = client.get_build_beta_details(filter: { build: build_id })
- build_beta_details_id = resp.first["id"]
+ def update_build_beta_details(build, info)
+ build_beta_detail = build.build_beta_detail
attributes = {}
attributes[:autoNotifyEnabled] = info[:auto_notify_enabled] if info.key?(:auto_notify_enabled)
- client.patch_build_beta_details(build_beta_details_id: build_beta_details_id, attributes: attributes)
+ client = Spaceship::ConnectAPI::Base.client
+ client.patch_build_beta_details(build_beta_details_id: build_beta_detail.id, attributes: attributes)
end
end
+ # rubocop:enable Metrics/ClassLength
end