Created
October 27, 2024 22:57
-
-
Save domhel/a1d8d486ea575be8b2ac2cce74dde7dc to your computer and use it in GitHub Desktop.
Flutter build and upload iOS and Android app
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| # This script sits in the root of your project = <my_flutter_project>/build_stuff.py | |
| # It requires a directory <my_flutter_project>/keys with the credentials below | |
| import os | |
| import subprocess | |
| import sys | |
| import glob | |
| from google.oauth2.service_account import Credentials | |
| from googleapiclient.discovery import build | |
| from googleapiclient.http import MediaFileUpload | |
| appleApiKeyId = "ABCDEFG123" # the AuthKey_<keyId>.p8 file needs to be in ./keys | |
| appleApiIssuerId = "" # unique per apple id, find it at https://appstoreconnect.apple.com/access/integrations/api | |
| package_name = "de.example.my_app" # used for android | |
| googleCredentialsJson = "my-credentials.json" | |
| def build_app(flavor, platform): | |
| # flavor ignored | |
| flutter_build_params_base = [ | |
| "--release", | |
| "--obfuscate", | |
| "--split-debug-info", | |
| "/tmp/flutter-build-split-info", | |
| ] | |
| dart_defines = [] | |
| if platform == "ios": | |
| subprocess.run( | |
| [ | |
| "fvm", | |
| "flutter", | |
| "build", | |
| "ipa", | |
| *flutter_build_params_base, | |
| *dart_defines, | |
| ], | |
| capture_output=False, | |
| ) | |
| elif platform == "android": | |
| for type in ["apk", "appbundle"]: | |
| subprocess.run( | |
| [ | |
| "fvm", | |
| "flutter", | |
| "build", | |
| type, | |
| *flutter_build_params_base, | |
| *dart_defines, | |
| ], | |
| capture_output=False, | |
| ) | |
| else: | |
| print("Invalid platform!") | |
| sys.exit(1) | |
| def upload_app(platform): | |
| if platform == 'ios': | |
| my_env = os.environ.copy() | |
| my_env["API_PRIVATE_KEYS_DIR"] = "./keys" | |
| ipa_files = glob.glob("build/ios/ipa/*.ipa") | |
| if not ipa_files: | |
| print('ipa file not found! Your need to build the project first') | |
| sys.exit(1) | |
| print('Uploading ipa file to App Store') | |
| subprocess.run( | |
| [ | |
| "xcrun", | |
| "altool", | |
| "--upload-app", | |
| "--type", | |
| "ios", | |
| "-f", | |
| ipa_files[0], | |
| "--apiKey", | |
| appleApiKeyId, | |
| "--apiIssuer", | |
| appleApiIssuerId, | |
| ], | |
| capture_output=False, | |
| env=my_env, | |
| ) | |
| elif platform == 'android': | |
| service_account_file = f"./keys/{googleCredentialsJson}" | |
| aab_files = glob.glob("build/app/outputs/bundle/release/app-release.aab") | |
| if not aab_files: | |
| print('AAB file not found! You need to build the project first') | |
| sys.exit(1) | |
| aab_file = aab_files[0] | |
| print('Uploading Android bundle') | |
| # Authenticate and create the Android Publisher service | |
| credentials = Credentials.from_service_account_file( | |
| service_account_file, | |
| scopes=['https://www.googleapis.com/auth/androidpublisher'] | |
| ) | |
| service = build('androidpublisher', 'v3', credentials=credentials) | |
| try: | |
| # Create an edit | |
| edit_request = service.edits().insert(body={}, packageName=package_name) | |
| result = edit_request.execute() | |
| edit_id = result['id'] | |
| # Upload the AAB | |
| aab_response = service.edits().bundles().upload( | |
| editId=edit_id, | |
| packageName=package_name, | |
| media_body=MediaFileUpload(aab_file, mimetype='application/octet-stream') | |
| ).execute() | |
| # Create a new track release | |
| track_response = service.edits().tracks().update( | |
| editId=edit_id, | |
| track='internal', # or 'alpha', 'beta', 'production' | |
| packageName=package_name, | |
| body={ | |
| 'releases': [{ | |
| 'versionCodes': [aab_response['versionCode']], | |
| 'status': 'draft' # change to 'completed' when the app is out of draft mode | |
| }] | |
| } | |
| ).execute() | |
| # Commit the edit | |
| commit_request = service.edits().commit( | |
| editId=edit_id, | |
| packageName=package_name | |
| ).execute() | |
| print(f'App uploaded successfully. Edit ID: {commit_request["id"]}') | |
| except Exception as e: | |
| print(f'Error uploading app: {str(e)}') | |
| sys.exit(1) | |
| else: | |
| print(f'Platform {platform} upload not implemented') | |
| sys.exit(1) | |
| if __name__ == "__main__": | |
| # arg parsing: | |
| if len(sys.argv) < 2: | |
| print("Usage: build_stuff.py {--upload, --skip-build}") | |
| sys.exit(1) | |
| extra_params = sys.argv[1:] | |
| cwd = os.getcwd() | |
| if not os.path.isfile(f'{cwd}/pubspec.yaml'): | |
| print(f'Run this script from within a Flutter project, not from {cwd}') | |
| sys.exit(1) | |
| flavor = "default" | |
| build_platforms = ["ios", "android"] | |
| # build_platforms = ["android"] | |
| upload_platforms = build_platforms[:] | |
| if '--skip-build' not in extra_params: | |
| for platform in build_platforms: | |
| build_app(flavor, platform) | |
| if "--upload" in extra_params: | |
| for platform in upload_platforms: | |
| upload_app(platform) | |
| print('') | |
| print('####################################################') | |
| print('############### SCRIPT DONE ####################') | |
| print('####################################################') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment