diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b87ef1a..81d6f047 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,13 @@ -Version UNRELEASED +Version 0.46.0 ------------- +**Features** +- Add new option `--max-find-build-wait` to action `app-store-connect publish` to configure maximum waiting time to discover uploaded build from App Store Connect before failing publishing. Defaults to 10 minutes. [PR #355](https://github.com/codemagic-ci-cd/cli-tools/pull/355) + **Docs** +- Add option `--max-find-build-wait` documentation for action `app-store-connect publish`. [PR #355](https://github.com/codemagic-ci-cd/cli-tools/pull/355) - Update option `--cancel-previous-submissions` documentation for actions `app-store-connect builds submit-to-app-store` and `app-store-connect publish`. [PR #353](https://github.com/codemagic-ci-cd/cli-tools/pull/353) - Version 0.45.3 ------------- diff --git a/docs/app-store-connect/publish.md b/docs/app-store-connect/publish.md index 3a522d5e..4be3811a 100644 --- a/docs/app-store-connect/publish.md +++ b/docs/app-store-connect/publish.md @@ -23,6 +23,7 @@ app-store-connect publish [-h] [--log-stream STREAM] [--no-color] [--version] [- [--enable-package-validation] [--skip-package-validation] [--skip-package-upload] + [--max-find-build-wait MAX_BUILD_FIND_WAIT] [--max-build-processing-wait MAX_BUILD_PROCESSING_WAIT] [--beta-build-localizations BETA_BUILD_LOCALIZATIONS] [--testflight] @@ -74,6 +75,10 @@ Validate package before uploading it to App Store Connect. Use this switch to en Skip package upload before doing any other TestFlight or App Store related actions. Using this switch will opt out from running `altool --upload-app` as part of publishing action. Use this option in case your application package is already uploaded to App Store. If not given, the value will be checked from the environment variable `APP_STORE_CONNECT_SKIP_PACKAGE_UPLOAD`. +##### `--max-find-build-wait=MAX_BUILD_FIND_WAIT` + + +The maximum amount of minutes to wait for the freshly uploaded build to become discoverable in App Store Connect. Works in conjunction with TestFlight beta review submission or App Store review submission and operations that depend on either one of those. If the build does not become available within the specified timeframe, further submission will be terminated. If not given, the value will be checked from the environment variable `APP_STORE_CONNECT_MAX_FIND_BUILD_WAIT`. [Default: 10] ##### `--max-build-processing-wait, -w=MAX_BUILD_PROCESSING_WAIT` diff --git a/pyproject.toml b/pyproject.toml index 8d491a80..057ab487 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "codemagic-cli-tools" -version = "0.45.3" +version = "0.46.0" description = "CLI tools used in Codemagic builds" readme = "README.md" authors = [ diff --git a/src/codemagic/__version__.py b/src/codemagic/__version__.py index a093c0a8..ea7cf142 100644 --- a/src/codemagic/__version__.py +++ b/src/codemagic/__version__.py @@ -1,5 +1,5 @@ __title__ = "codemagic-cli-tools" __description__ = "CLI tools used in Codemagic builds" -__version__ = "0.45.3.dev" +__version__ = "0.46.0.dev" __url__ = "https://github.com/codemagic-ci-cd/cli-tools" __licence__ = "GNU General Public License v3.0" diff --git a/src/codemagic/tools/_app_store_connect/actions/publish_action.py b/src/codemagic/tools/_app_store_connect/actions/publish_action.py index 7d4596af..4bc95706 100644 --- a/src/codemagic/tools/_app_store_connect/actions/publish_action.py +++ b/src/codemagic/tools/_app_store_connect/actions/publish_action.py @@ -1,6 +1,7 @@ from __future__ import annotations import dataclasses +import itertools import pathlib import time from abc import ABCMeta @@ -86,6 +87,7 @@ class SubmitToAppStoreOptions: PublishArgument.APPLE_ID, PublishArgument.APP_SPECIFIC_PASSWORD, *ArgumentGroups.PACKAGE_UPLOAD_ARGUMENTS, + PublishArgument.MAX_BUILD_FIND_WAIT, PublishArgument.MAX_BUILD_PROCESSING_WAIT, *PublishArgument.with_custom_argument_group( "add localized What's new (what to test) information to uploaded build", @@ -286,6 +288,7 @@ def publish( altool_retries_count: Optional[Types.AltoolRetriesCount] = None, altool_retry_wait: Optional[Types.AltoolRetryWait] = None, altool_verbose_logging: Optional[bool] = None, + max_find_build_wait: Union[Types.MaxFindBuildWait, int] = PublishArgument.MAX_BUILD_FIND_WAIT.get_default(), **app_store_connect_submit_options, ) -> None: """ @@ -318,6 +321,7 @@ def publish( ) self._process_application_after_upload( application_package, + Types.MaxFindBuildWait.resolve_value(max_find_build_wait), *self._get_app_store_connect_submit_options( application_package, submit_to_testflight, @@ -369,6 +373,7 @@ def _publish_application_package( def _process_application_after_upload( self, application_package: Union[Ipa, MacOsPackage], + max_find_build_wait: int, testflight_options: Optional[SubmitToTestFlightOptions], app_store_options: Optional[SubmitToAppStoreOptions], beta_test_info_options: Optional[AddBetaTestInfoOptions], @@ -378,7 +383,7 @@ def _process_application_after_upload( return # Nothing to do with the application... app = self._get_uploaded_build_application(application_package) - build = self._get_uploaded_build(app, application_package) + build = self._get_uploaded_build(app, application_package, max_find_build_wait) if beta_test_info_options: self.add_beta_test_info(build.id, **beta_test_info_options.__dict__) @@ -408,8 +413,8 @@ def _find_build( self, app_id: ResourceId, application_package: Union[Ipa, MacOsPackage], - retries: int = 20, - retry_wait_seconds: int = 30, + max_find_build_minutes: int, + retry_wait_seconds: int, ) -> Build: """ Find corresponding build for the uploaded ipa or macOS package. @@ -423,44 +428,43 @@ def _find_build( pre_release_version_version=application_package.version, pre_release_version_platform=self._get_application_package_platform(application_package), ) - try: - found_builds = self.api_client.builds.list( - builds_filter, - ordering=self.api_client.builds.Ordering.UPLOADED_DATE, - reverse=True, - ) - except AppStoreConnectApiError as api_error: - raise AppStoreConnectError(str(api_error)) - - if found_builds: - return found_builds[0] - - # Matching build was not found. - if retries > 0: - # There are retries left, wait a bit and try again. - self.logger.info( - ( - "Build has finished uploading but is processing on App Store Connect side. Could not find the " - "build matching the uploaded version yet. Waiting %d seconds to try again, %d attempts remaining." - ), - retry_wait_seconds, - retries, - ) - time.sleep(retry_wait_seconds) - return self._find_build( - app_id, - application_package, - retries=retries - 1, - retry_wait_seconds=retry_wait_seconds, - ) - else: - # There are no more retries left, give up. - raise IOError( - "The build was successfully uploaded to App Store Connect but processing the corresponding artifact " - f'"{application_package.path}" by Apple took longer than expected. Further actions like updating the ' - "What to test information or submitting the build to beta review could not be performed at the moment " - "but can be completed manually in TestFlight once the build has finished processing.", - ) + + not_found_message = "Could not find the build matching the uploaded version" + default_retry_message = f"{not_found_message}, waiting {retry_wait_seconds} seconds to try again." + first_retry_message = ( + f"Build has finished uploading but is not available in App Store Connect yet. " + f"{default_retry_message} Timeout in {max_find_build_minutes} minutes." + ) + + find_started_at = time.time() + for attempt in itertools.count(): + try: + found_builds = self.api_client.builds.list( + builds_filter, + ordering=self.api_client.builds.Ordering.UPLOADED_DATE, + reverse=True, + ) + except AppStoreConnectApiError as api_error: + raise AppStoreConnectError(str(api_error)) + + if found_builds: + return found_builds[0] + elif int(time.time() - find_started_at) <= max_find_build_minutes * 60: + self.logger.info(first_retry_message if attempt == 0 else default_retry_message) + time.sleep(retry_wait_seconds) + else: + self.logger.info(f"{not_found_message}. Timeout reached, stop trying.\n") + break # No more time left, give up. + + error_message = ( + "The build was successfully uploaded to App Store Connect but processing the corresponding artifact " + f'"{application_package.path}" by Apple took longer than expected. Further actions like updating the ' + '"What to test information" or submitting the build to beta review could not be performed at the moment ' + "but can be completed manually in TestFlight once the build has finished processing.\n" + f"You can configure maximum timeout using {PublishArgument.MAX_BUILD_FIND_WAIT.flag} " + f"command line option, or {Types.MaxFindBuildWait.environment_variable_key} environment variable." + ) + raise IOError(error_message) def _get_uploaded_build_application(self, application_package: Union[Ipa, MacOsPackage]) -> App: bundle_id = application_package.bundle_identifier @@ -481,9 +485,16 @@ def _get_uploaded_build( self, app: App, application_package: Union[Ipa, MacOsPackage], + max_find_build_minutes: int, + retry_wait_seconds: int = 30, ) -> Build: self.logger.info(Colors.BLUE("\nFind uploaded build")) - build = self._find_build(app.id, application_package) + build = self._find_build( + app.id, + application_package, + max_find_build_minutes, + retry_wait_seconds, + ) self.logger.info(Colors.GREEN("\nUploaded build is")) self.printer.print_resource(build, True) return build diff --git a/src/codemagic/tools/_app_store_connect/arguments.py b/src/codemagic/tools/_app_store_connect/arguments.py index 6c7a6428..8f2b3557 100644 --- a/src/codemagic/tools/_app_store_connect/arguments.py +++ b/src/codemagic/tools/_app_store_connect/arguments.py @@ -147,6 +147,15 @@ class AltoolVerboseLogging(cli.TypedCliArgument[bool]): argument_type = bool environment_variable_key = "APP_STORE_CONNECT_ALTOOL_VERBOSE_LOGGING" + class MaxFindBuildWait(cli.TypedCliArgument[int]): + argument_type = int + environment_variable_key = "APP_STORE_CONNECT_MAX_FIND_BUILD_WAIT" + default_value = 10 + + @classmethod + def _is_valid(cls, value: int) -> bool: + return value > 0 + class MaxBuildProcessingWait(cli.TypedCliArgument[int]): argument_type = int environment_variable_key = "APP_STORE_CONNECT_MAX_BUILD_PROCESSING_WAIT" @@ -899,6 +908,22 @@ class PublishArgument(cli.Argument): "required": False, }, ) + MAX_BUILD_FIND_WAIT = cli.ArgumentProperties( + key="max_find_build_wait", + flags=("--max-find-build-wait",), + type=Types.MaxFindBuildWait, + description=( + "The maximum amount of minutes to wait for the freshly uploaded build " + "to become discoverable in App Store Connect. Works in conjunction with " + "TestFlight beta review submission or App Store review submission and " + "operations that depend on either one of those. If the build does not " + "become available within the specified timeframe, further submission " + "will be terminated." + ), + argparse_kwargs={ + "required": False, + }, + ) MAX_BUILD_PROCESSING_WAIT = cli.ArgumentProperties( key="max_build_processing_wait", flags=("--max-build-processing-wait", "-w"),