Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix build watcher #3

Closed
68 changes: 21 additions & 47 deletions fastlane_core/lib/fastlane_core/build_watcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ def wait_for_build_processing_to_be_complete(app_id: nil, platform: nil, train_v

showed_info = false
loop do
matched_build, app_version_queried = matching_build(watched_app_version: app_version, watched_build_version: build_version, app_id: app_id, platform: platform, select_latest: select_latest)

matched_build = matching_build(watched_app_version: app_version, watched_build_version: build_version, app_id: app_id, platform: platform, select_latest: select_latest)
if matched_build.nil? && !showed_info
UI.important("Read more information on why this build isn't showing up yet - https://github.com/fastlane/fastlane/issues/14997")
showed_info = true
Expand All @@ -48,13 +47,6 @@ def wait_for_build_processing_to_be_complete(app_id: nil, platform: nil, train_v
# having a build resource appear in AppStoreConnect (matched_build) may be enough (i.e. setting a changelog)
# so here we may choose to skip the full processing of the build if return_when_build_appears is true
if matched_build && (return_when_build_appears || processed?(build: matched_build, wait_for_build_beta_detail_processing: wait_for_build_beta_detail_processing))

if !app_version.nil? && app_version != app_version_queried
UI.important("App version is #{app_version} but build was found while querying #{app_version_queried}")
UI.important("This shouldn't be an issue as Apple sees #{app_version} and #{app_version_queried} as equal")
UI.important("See docs for more info - https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-102364")
end

if return_spaceship_testflight_build
return matched_build.to_testflight_build
else
Expand All @@ -71,24 +63,25 @@ def wait_for_build_processing_to_be_complete(app_id: nil, platform: nil, train_v

private

# Remove leading zeros ( https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-102364 )
def remove_version_leading_zeros(version: nil)
return version.instance_of?(String) ? version.split('.').map { |s| s.to_i.to_s }.join('.') : version
def normalize_version(version: nil)
return version unless version.instance_of?(String)

# Ensure the version has 3 parts and add .0 to [Major].[Minor] if needed
version_parts = version.split('.').map { |s| s.to_i.to_s }

# Add missing parts to conform to the [Major].[Minor].[Patch] format
version_parts << "0" while version_parts.size < 3

version_parts.join('.')
end

def matching_build(watched_app_version: nil, watched_build_version: nil, app_id: nil, platform: nil, select_latest: false)
# Get build deliveries (newly uploaded processing builds)
watched_app_version = remove_version_leading_zeros(version: watched_app_version)
watched_build_version = remove_version_leading_zeros(version: watched_build_version)
# Normalize the watched app version and build version to ensure consistency
watched_app_version = normalize_version(version: watched_app_version)
watched_build_version = normalize_version(version: watched_build_version)

# App Store Connect will allow users to upload X.Y is the same as X.Y.0 and treat them as the same version
# However, only the first uploaded version format will be the one that is queryable
# This could lead to BuildWatcher never finding X.Y.0 if X.Y was uploaded first as X.Y will only yield results
#
# This will add an additional request to search for both X.Y and X.Y.0 but
# will give preference to the version format specified passed in
watched_app_version_alternate = alternate_version(watched_app_version)
versions = [watched_app_version, watched_app_version_alternate].compact
# Only query for the specific version, without generating alternates like X.Y vs X.Y.0
versions = [watched_app_version].compact

if versions.empty?
if select_latest
Expand All @@ -100,6 +93,7 @@ def matching_build(watched_app_version: nil, watched_build_version: nil, app_id:
end
end

# Perform the query for builds with the exact version
version_matches = versions.map do |version|
match = VersionMatches.new
match.version = version
Expand All @@ -109,45 +103,25 @@ def matching_build(watched_app_version: nil, watched_build_version: nil, app_id:
build_number: watched_build_version,
platform: platform
)

match
end.flatten

# Raise error if more than 1 build is returned
# This should never happen but need to inform the user if it does
# Raise an error if more than one build is returned
matched_builds = version_matches.map(&:builds).flatten

if matched_builds.size > 1 && !select_latest
error_builds = matched_builds.map do |build|
"#{build.app_version}(#{build.version}) for #{build.platform} - #{build.processing_state}"
end.join("\n")
error_message = "Found more than 1 matching build: \n#{error_builds}"
raise BuildWatcherError.new, error_message
raise BuildWatcherError.new, "Found more than 1 matching build: \n#{error_builds}"
end

version_match = version_matches.reject do |match|
match.builds.empty?
end.first
matched_build = version_match&.builds&.first

return matched_build, version_match&.version
matched_builds.last
end

def alternate_version(version)
return nil if version.nil?

version_info = Gem::Version.new(version)
if version_info.segments.size == 3 && version_info.segments[2] == 0
return version_info.segments[0..1].join(".")
elsif version_info.segments.size == 2
return "#{version}.0"
end

return nil
end

def processed?(build: nil, wait_for_build_beta_detail_processing: false)
return false unless build

is_processed = build.processed?

# App Store Connect API has multiple build processing states
Expand Down
Loading