From 7a443df1992f60440ff602f144a6e85a1a539750 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 9 Aug 2022 12:37:00 +0400 Subject: [PATCH 01/22] Temp --- build-system/Make/BuildConfiguration.py | 226 ++++++++++ build-system/Make/BuildEnvironment.py | 28 +- build-system/Make/ImportCertificates.py | 95 +++++ build-system/Make/Make.py | 225 ++++++++-- build-system/Make/RemoteBuild.py | 129 ++++++ build-system/appstore-configuration.json | 14 + .../certs/{distribution => }/Public.cer | Bin .../certs/{distribution => }/SelfSigned.p12 | Bin .../BroadcastUpload.mobileprovision | Bin .../{appstore => }/Intents.mobileprovision | Bin .../NotificationContent.mobileprovision | Bin .../NotificationService.mobileprovision | Bin .../{appstore => }/Share.mobileprovision | Bin .../{appstore => }/Telegram.mobileprovision | Bin .../{appstore => }/WatchApp.mobileprovision | Bin .../WatchExtension.mobileprovision | Bin .../{appstore => }/Widget.mobileprovision | Bin buildbox/build.py | 402 +++++++----------- buildbox/guest-build-telegram.sh | 3 +- 19 files changed, 817 insertions(+), 305 deletions(-) create mode 100644 build-system/Make/BuildConfiguration.py create mode 100644 build-system/Make/ImportCertificates.py create mode 100644 build-system/Make/RemoteBuild.py create mode 100755 build-system/appstore-configuration.json rename build-system/fake-codesigning/certs/{distribution => }/Public.cer (100%) rename build-system/fake-codesigning/certs/{distribution => }/SelfSigned.p12 (100%) rename build-system/fake-codesigning/profiles/{appstore => }/BroadcastUpload.mobileprovision (100%) rename build-system/fake-codesigning/profiles/{appstore => }/Intents.mobileprovision (100%) rename build-system/fake-codesigning/profiles/{appstore => }/NotificationContent.mobileprovision (100%) rename build-system/fake-codesigning/profiles/{appstore => }/NotificationService.mobileprovision (100%) rename build-system/fake-codesigning/profiles/{appstore => }/Share.mobileprovision (100%) rename build-system/fake-codesigning/profiles/{appstore => }/Telegram.mobileprovision (100%) rename build-system/fake-codesigning/profiles/{appstore => }/WatchApp.mobileprovision (100%) rename build-system/fake-codesigning/profiles/{appstore => }/WatchExtension.mobileprovision (100%) rename build-system/fake-codesigning/profiles/{appstore => }/Widget.mobileprovision (100%) diff --git a/build-system/Make/BuildConfiguration.py b/build-system/Make/BuildConfiguration.py new file mode 100644 index 0000000000..463a44c8e0 --- /dev/null +++ b/build-system/Make/BuildConfiguration.py @@ -0,0 +1,226 @@ +import json +import os +import sys +import shutil +import tempfile +import plistlib + +from BuildEnvironment import run_executable_with_output + +class BuildConfiguration: + def __init__(self, + bundle_id, + api_id, + api_hash, + team_id, + app_center_id, + is_internal_build, + is_appstore_build, + appstore_id, + app_specific_url_scheme, + premium_iap_product_id, + enable_siri, + enable_icloud + ): + self.bundle_id = bundle_id + self.api_id = api_id + self.api_hash = api_hash + self.team_id = team_id + self.app_center_id = app_center_id + self.is_internal_build = is_internal_build + self.is_appstore_build = is_appstore_build + self.appstore_id = appstore_id + self.app_specific_url_scheme = app_specific_url_scheme + self.premium_iap_product_id = premium_iap_product_id + self.enable_siri = enable_siri + self.enable_icloud = enable_icloud + + def write_to_variables_file(self, aps_environment, path): + string = '' + string += 'telegram_bundle_id = "{}"\n'.format(self.bundle_id) + string += 'telegram_api_id = "{}"\n'.format(self.api_id) + string += 'telegram_api_hash = "{}"\n'.format(self.api_hash) + string += 'telegram_team_id = "{}"\n'.format(self.team_id) + string += 'telegram_app_center_id = "{}"\n'.format(self.app_center_id) + string += 'telegram_is_internal_build = "{}"\n'.format(self.is_internal_build) + string += 'telegram_is_appstore_build = "{}"\n'.format(self.is_appstore_build) + string += 'telegram_appstore_id = "{}"\n'.format(self.appstore_id) + string += 'telegram_app_specific_url_scheme = "{}"\n'.format(self.app_specific_url_scheme) + string += 'telegram_premium_iap_product_id = "{}"\n'.format(self.premium_iap_product_id) + string += 'telegram_aps_environment = "{}"\n'.format(aps_environment) + string += 'telegram_enable_siri = {}\n'.format(self.enable_siri) + string += 'telegram_enable_icloud = {}\n'.format(self.enable_icloud) + string += 'telegram_enable_watch = True\n' + + if os.path.exists(path): + os.remove(path) + with open(path, 'w+') as file: + file.write(string) + + +def build_configuration_from_json(path): + if not os.path.exists(path): + print('Could not load build configuration from {}'.format(path)) + sys.exit(1) + with open(path) as file: + configuration_dict = json.load(file) + required_keys = [ + 'bundle_id', + 'api_id', + 'api_hash', + 'team_id', + 'app_center_id', + 'is_internal_build', + 'is_appstore_build', + 'appstore_id', + 'app_specific_url_scheme', + 'premium_iap_product_id', + 'enable_siri', + 'enable_icloud' + ] + for key in required_keys: + if key not in configuration_dict: + print('Configuration at {} does not contain {}'.format(path, key)) + return BuildConfiguration( + bundle_id=configuration_dict['bundle_id'], + api_id=configuration_dict['api_id'], + api_hash=configuration_dict['api_hash'], + team_id=configuration_dict['team_id'], + app_center_id=configuration_dict['app_center_id'], + is_internal_build=configuration_dict['is_internal_build'], + is_appstore_build=configuration_dict['is_appstore_build'], + appstore_id=configuration_dict['appstore_id'], + app_specific_url_scheme=configuration_dict['app_specific_url_scheme'], + premium_iap_product_id=configuration_dict['premium_iap_product_id'], + enable_siri=configuration_dict['enable_siri'], + enable_icloud=configuration_dict['enable_icloud'] + ) + + +def decrypt_codesigning_directory_recursively(source_base_path, destination_base_path, password): + for file_name in os.listdir(source_base_path): + source_path = source_base_path + '/' + file_name + destination_path = destination_base_path + '/' + file_name + if os.path.isfile(source_path): + os.system('openssl aes-256-cbc -md md5 -k "{password}" -in "{source_path}" -out "{destination_path}" -a -d'.format( + password=password, + source_path=source_path, + destination_path=destination_path + )) + elif os.path.isdir(source_path): + os.makedirs(destination_path, exist_ok=True) + decrypt_codesigning_directory_recursively(source_path, destination_path, password) + + +def load_provisioning_profiles_from_git(working_dir, repo_url, branch, password, always_fetch): + if not os.path.exists(working_dir): + os.makedirs(working_dir, exist_ok=True) + + encrypted_working_dir = working_dir + '/encrypted' + if os.path.exists(encrypted_working_dir): + if always_fetch: + original_working_dir = os.getcwd() + os.chdir(encrypted_working_dir) + os.system('git fetch') + os.system('git checkout "{branch}"'.format(branch=branch)) + os.system('git pull') + os.chdir(original_working_dir) + else: + os.makedirs(encrypted_working_dir, exist_ok=True) + original_working_dir = os.getcwd() + os.chdir(working_dir) + os.system('git clone {repo_url} -b "{branch}" "{target_path}"'.format( + repo_url=repo_url, + branch=branch, + target_path=encrypted_working_dir + )) + os.chdir(original_working_dir) + + decrypted_working_dir = working_dir + '/decrypted' + if os.path.exists(decrypted_working_dir): + shutil.rmtree(decrypted_working_dir) + os.makedirs(decrypted_working_dir, exist_ok=True) + + decrypt_codesigning_directory_recursively(encrypted_working_dir + '/profiles', decrypted_working_dir + '/profiles', password) + decrypt_codesigning_directory_recursively(encrypted_working_dir + '/certs', decrypted_working_dir + '/certs', password) + + +def copy_profiles_from_directory(source_path, destination_path, team_id, bundle_id): + profile_name_mapping = { + '.SiriIntents': 'Intents', + '.NotificationContent': 'NotificationContent', + '.NotificationService': 'NotificationService', + '.Share': 'Share', + '': 'Telegram', + '.watchkitapp': 'WatchApp', + '.watchkitapp.watchkitextension': 'WatchExtension', + '.Widget': 'Widget', + '.BroadcastUpload': 'BroadcastUpload' + } + + for file_name in os.listdir(source_path): + file_path = source_path + '/' + file_name + if os.path.isfile(file_path): + if not file_path.endswith('.mobileprovision'): + continue + + profile_data = run_executable_with_output('openssl', arguments=[ + 'smime', + '-inform', + 'der', + '-verify', + '-noverify', + '-in', + file_path + ], decode=False, stderr_to_stdout=False, check_result=True) + + profile_dict = plistlib.loads(profile_data) + profile_name = profile_dict['Entitlements']['application-identifier'] + + if profile_name.startswith(team_id + '.' + bundle_id): + profile_base_name = profile_name[len(team_id + '.' + bundle_id):] + if profile_base_name in profile_name_mapping: + shutil.copyfile(file_path, destination_path + '/' + profile_name_mapping[profile_base_name] + '.mobileprovision') + else: + print('Warning: skipping provisioning profile at {} with bundle_id {} (base_name {})'.format(file_path, profile_name, profile_base_name)) + + +class ProvisioningProfileSource: + def __init__(self): + pass + + def copy_profiles_to_destination(self, destination_path): + raise Exception('Not implemented') + + +class GitProvisioningProfileSource(ProvisioningProfileSource): + def __init__(self, working_dir, repo_url, team_id, bundle_id, profile_type, password, always_fetch): + self.working_dir = working_dir + self.repo_url = repo_url + self.team_id = team_id + self.bundle_id = bundle_id + self.profile_type = profile_type + self.password = password + self.always_fetch = always_fetch + + def copy_profiles_to_destination(self, destination_path): + load_provisioning_profiles_from_git(working_dir=self.working_dir, repo_url=self.repo_url, branch=self.team_id, password=self.password, always_fetch=self.always_fetch) + copy_profiles_from_directory(source_path=self.working_dir + '/decrypted/profiles/{}'.format(self.profile_type), destination_path=destination_path, team_id=self.team_id, bundle_id=self.bundle_id) + + +class DirectoryProvisioningProfileSource(ProvisioningProfileSource): + def __init__(self, directory_path, team_id, bundle_id): + self.directory_path = directory_path + self.team_id = team_id + self.bundle_id = bundle_id + + def copy_profiles_to_destination(self, destination_path): + profiles_path = self.directory_path + if not os.path.exists(profiles_path): + print('{} does not exist'.format(profiles_path)) + sys.exit(1) + copy_profiles_from_directory(source_path=profiles_path, destination_path=destination_path, team_id=self.team_id, bundle_id=self.bundle_id) + + +def generate_configuration_repository(path, profile_source): + pass diff --git a/build-system/Make/BuildEnvironment.py b/build-system/Make/BuildEnvironment.py index 4b91ab7c99..1f78fe7edd 100644 --- a/build-system/Make/BuildEnvironment.py +++ b/build-system/Make/BuildEnvironment.py @@ -2,7 +2,7 @@ import json import os import platform import subprocess - +import sys def is_apple_silicon(): if platform.processor() == 'arm': @@ -28,20 +28,38 @@ def resolve_executable(program): return None -def run_executable_with_output(path, arguments): +def run_executable_with_output(path, arguments, decode=True, input=None, stderr_to_stdout=True, check_result=False): executable_path = resolve_executable(path) if executable_path is None: raise Exception('Could not resolve {} to a valid executable file'.format(path)) + stderr_assignment = subprocess.DEVNULL + if stderr_to_stdout: + stderr_assignment = subprocess.STDOUT + process = subprocess.Popen( [executable_path] + arguments, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, + stderr=stderr_assignment, + stdin=subprocess.PIPE, env=get_clean_env() ) - output_data, _ = process.communicate() + if input is not None: + output_data, _ = process.communicate(input=input) + else: + output_data, _ = process.communicate() + output_string = output_data.decode('utf-8') - return output_string + + if check_result: + if process.returncode != 0: + print('Command {} {} finished with non-zero return code and output:\n{}'.format(executable_path, arguments, output_string)) + sys.exit(1) + + if decode: + return output_string + else: + return output_data def call_executable(arguments, use_clean_environment=True, check_result=True): diff --git a/build-system/Make/ImportCertificates.py b/build-system/Make/ImportCertificates.py new file mode 100644 index 0000000000..2e6ddd6a1e --- /dev/null +++ b/build-system/Make/ImportCertificates.py @@ -0,0 +1,95 @@ +import os +import sys +import argparse + +from BuildEnvironment import run_executable_with_output + +def import_certificates(certificatesPath): + if not os.path.exists(certificatesPath): + print('{} does not exist'.format(certificatesPath)) + sys.exit(1) + + keychain_name = 'temp.keychain' + keychain_password = 'secret' + + existing_keychains = run_executable_with_output('security', arguments=['list-keychains'], check_result=True) + if keychain_name in existing_keychains: + run_executable_with_output('security', arguments=['delete-keychain'], check_result=True) + + run_executable_with_output('security', arguments=[ + 'create-keychain', + '-p', + keychain_password, + keychain_name + ], check_result=True) + + existing_keychains = run_executable_with_output('security', arguments=['list-keychains', '-d', 'user']) + existing_keychains.replace('"', '') + + run_executable_with_output('security', arguments=[ + 'list-keychains', + '-d', + 'user', + '-s', + keychain_name, + existing_keychains + ], check_result=True) + + run_executable_with_output('security', arguments=['set-keychain-settings', keychain_name]) + run_executable_with_output('security', arguments=['unlock-keychain', '-p', keychain_password, keychain_name]) + + for file_name in os.listdir(certificatesPath): + file_path = certificatesPath + '/' + file_name + if file_path.endwith('.p12') or file_path.endwith('.cer'): + run_executable_with_output('security', arguments=[ + 'import', + file_path, + '-k', + keychain_name, + '-P', + '', + '-T', + '/usr/bin/codesign', + '-T', + '/usr/bin/security' + ], check_result=True) + + run_executable_with_output('security', arguments=[ + 'import', + 'build-system/AppleWWDRCAG3.cer', + '-k', + keychain_name, + '-P', + '', + '-T', + '/usr/bin/codesign', + '-T', + '/usr/bin/security' + ], check_result=True) + + run_executable_with_output('security', arguments=[ + 'set-key-partition-list', + '-S', + 'apple-tool:,apple:', + '-k', + keychain_password, + keychain_name + ], check_result=True) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(prog='build') + + parser.add_argument( + '--path', + required=True, + help='Path to certificates.' + ) + + if len(sys.argv) < 2: + parser.print_help() + sys.exit(1) + + args = parser.parse_args() + + import_certificates(args.path) diff --git a/build-system/Make/Make.py b/build-system/Make/Make.py index 7de3beb456..f3e5c3ed19 100644 --- a/build-system/Make/Make.py +++ b/build-system/Make/Make.py @@ -6,10 +6,13 @@ import shlex import sys import tempfile import subprocess +import shutil from BuildEnvironment import resolve_executable, call_executable, BuildEnvironment from ProjectGeneration import generate from BazelLocation import locate_bazel +from BuildConfiguration import ProvisioningProfileSource, GitProvisioningProfileSource, DirectoryProvisioningProfileSource, BuildConfiguration, build_configuration_from_json +import RemoteBuild class BazelCommandLine: def __init__(self, bazel, override_bazel_version, override_xcode_version, bazel_user_root): @@ -380,34 +383,75 @@ def clean(bazel, arguments): bazel_command_line.invoke_clean() -def resolve_configuration(bazel_command_line: BazelCommandLine, arguments): - if arguments.configurationGenerator is not None: - configuration_generator_arguments = shlex.split(arguments.configurationGenerator) +def resolve_codesigning(arguments, base_path, build_configuration, certificates_path, provisioning_profiles_path): + profile_source = None + if arguments.gitCodesigningRepository is not None: + password = os.getenv('TELEGRAM_CODESIGNING_GIT_PASSWORD') + if password is None: + print('TELEGRAM_CODESIGNING_GIT_PASSWORD environment variable is not set') + sys.exit(1) - configuration_generator_executable = resolve_executable(configuration_generator_arguments[0]) + if arguments.gitCodesigningType is None: + print('--gitCodesigningType is required if --gitCodesigningRepository is set') + sys.exit(1) - if configuration_generator_executable is None: - print('{} is not a valid executable'.format(configuration_generator_arguments[0])) - exit(1) + workdir_path = '{}/build-input/configuration-repository-workdir'.format(base_path) + os.makedirs(workdir_path, exist_ok=True) - temp_configuration_path = tempfile.mkdtemp() - - resolved_configuration_generator_arguments = [configuration_generator_executable] - resolved_configuration_generator_arguments += configuration_generator_arguments[1:] - resolved_configuration_generator_arguments += [temp_configuration_path] - - call_executable(resolved_configuration_generator_arguments, use_clean_environment=False) - - print('TelegramBuild: using generated configuration in {}'.format(temp_configuration_path)) - bazel_command_line.set_configuration_path(temp_configuration_path) - elif arguments.configurationPath is not None: - absolute_configuration_path = os.path.abspath(arguments.configurationPath) - if not os.path.isdir(absolute_configuration_path): - print('Error: {} does not exist'.format(absolute_configuration_path)) - exit(1) - bazel_command_line.set_configuration_path(absolute_configuration_path) + profile_source = GitProvisioningProfileSource( + working_dir=workdir_path, + repo_url=arguments.gitCodesigningRepository, + team_id=build_configuration.team_id, + bundle_id=build_configuration.bundle_id, + profile_type=arguments.gitCodesigningType, + password=password, + always_fetch=arguments.gitCodesigningAlwaysFetch + ) + elif arguments.provisioningProfilesPath is not None: + profile_source = DirectoryProvisioningProfileSource( + directory_path=arguments.provisioningProfilesPath, + team_id=build_configuration.team_id, + bundle_id=build_configuration.bundle_id + ) else: - raise Exception('Neither configurationPath nor configurationGenerator are set') + raise Exception('Neither gitCodesigningRepository nor provisioningProfilesPath are set') + + profile_source.copy_profiles_to_destination(destination_path=provisioning_profiles_path) + + +def resolve_configuration(base_path, bazel_command_line: BazelCommandLine, arguments, aps_environment): + configuration_repository_path = '{}/build-input/configuration-repository'.format(base_path) + os.makedirs(configuration_repository_path, exist_ok=True) + + build_configuration = build_configuration_from_json(path=arguments.configurationPath) + + with open(configuration_repository_path + '/WORKSPACE', 'w+') as file: + pass + + with open(configuration_repository_path + '/BUILD', 'w+') as file: + pass + + build_configuration.write_to_variables_file(aps_environment=aps_environment, path=configuration_repository_path + '/variables.bzl') + + provisioning_path = '{}/provisioning'.format(configuration_repository_path) + if os.path.exists(provisioning_path): + shutil.rmtree(provisioning_path) + os.makedirs(provisioning_path, exist_ok=True) + + resolve_codesigning(arguments=arguments, base_path=base_path, build_configuration=build_configuration, certificates_path=None, provisioning_profiles_path=provisioning_path) + + provisioning_profile_files = [] + for file_name in os.listdir(provisioning_path): + if file_name.endswith('.mobileprovision'): + provisioning_profile_files.append(file_name) + + with open(provisioning_path + '/BUILD', 'w+') as file: + file.write('exports_files([\n') + for file_name in provisioning_profile_files: + file.write(' "{}",\n'.format(file_name)) + file.write('])\n') + + return configuration_repository_path def generate_project(bazel, arguments): @@ -425,7 +469,10 @@ def generate_project(bazel, arguments): bazel_command_line.set_continue_on_error(arguments.continueOnError) - resolve_configuration(bazel_command_line, arguments) + configuration_repository_path = resolve_configuration(base_path=os.getcwd(), bazel_command_line=bazel_command_line, arguments=arguments, aps_environment=arguments.apsEnvironment) + + if bazel_command_line is not None: + bazel_command_line.set_configuration_path(configuration_repository_path) bazel_command_line.set_build_number(arguments.buildNumber) @@ -469,7 +516,7 @@ def build(bazel, arguments): elif arguments.cacheHost is not None: bazel_command_line.add_remote_cache(arguments.cacheHost) - resolve_configuration(bazel_command_line, arguments) + resolve_configuration(base_path=os.getcwd(), bazel_command_line=bazel_command_line, arguments=arguments, aps_environment=arguments.apsEnvironment) bazel_command_line.set_configuration(arguments.configuration) bazel_command_line.set_build_number(arguments.buildNumber) @@ -495,7 +542,7 @@ def test(bazel, arguments): elif arguments.cacheHost is not None: bazel_command_line.add_remote_cache(arguments.cacheHost) - resolve_configuration(bazel_command_line, arguments) + resolve_configuration(base_path=os.getcwd(), bazel_command_line=bazel_command_line, arguments=arguments, aps_environment=arguments.apsEnvironment) bazel_command_line.set_configuration('debug_sim_arm64') bazel_command_line.set_build_number('10000') @@ -503,29 +550,75 @@ def test(bazel, arguments): bazel_command_line.invoke_test() -def add_project_and_build_common_arguments(current_parser: argparse.ArgumentParser): - group = current_parser.add_mutually_exclusive_group(required=True) - group.add_argument( +def add_codesigning_common_arguments(current_parser: argparse.ArgumentParser): + configuration_group = current_parser.add_mutually_exclusive_group(required=True) + configuration_group.add_argument( '--configurationPath', help=''' - Path to a folder containing build configuration and provisioning profiles. - See build-system/example-configuration for an example. + Path to a json containing build configuration. + See build-system/appstore-configuration.json for an example. ''', metavar='path' ) - group.add_argument( - '--configurationGenerator', + + codesigning_group = current_parser.add_mutually_exclusive_group(required=True) + codesigning_group.add_argument( + '--reproducibleCodesigning', + action='store_true', help=''' - A command line invocation that will dynamically generate the configuration data - (project constants and provisioning profiles). - The expression will be parsed according to the shell parsing rules into program and arguments parts. - The program will be then invoked with the given arguments plus the path to the output directory. - See build-system/generate-configuration.sh for an example. - Example: --configurationGenerator="sh ~/my_script.sh argument1" + Use locally generated provisioning profiles and certificates for a reproducible build. + ''' + ) + codesigning_group.add_argument( + '--gitCodesigningRepository', + help=''' + If specified, certificates and provisioning profiles will be loaded from git. + TELEGRAM_CODESIGNING_GIT_PASSWORD environment variable must be set. + ''', + metavar='path' + ) + codesigning_group.add_argument( + '--provisioningProfilesPath', + help=''' + Use provisioning profiles from a local directory. ''', metavar='command' ) + current_parser.add_argument( + '--gitCodesigningType', + required=False, + help=''' + The name of the folder to use inside "profiles" folder in the git repository. + Required if gitCodesigningRepository is specified. + ''', + metavar='type' + ) + + current_parser.add_argument( + '--gitCodesigningAlwaysFetch', + action='store_true', + required=False, + default=True, + help=''' + Always refresh codesigning repository. + ''' + ) + + +def add_project_and_build_common_arguments(current_parser: argparse.ArgumentParser): + current_parser.add_argument( + '--apsEnvironment', + choices=[ + 'development', + 'production' + ], + required=True, + help='APNS environment', + ) + + add_codesigning_common_arguments(current_parser=current_parser) + if __name__ == '__main__': parser = argparse.ArgumentParser(prog='Make') @@ -575,7 +668,7 @@ if __name__ == '__main__': '--cacheHost', required=False, help='Use remote build artifact cache to speed up rebuilds (See https://github.com/buchgr/bazel-remote).', - metavar='http://host:9092' + metavar='grpc://host:9092' ) cacheTypeGroup.add_argument( '--cacheDir', @@ -701,6 +794,34 @@ if __name__ == '__main__': help='Enable sandbox.', ) + remote_build_parser = subparsers.add_parser('remote-build', help='Build the app using a remote environment.') + add_codesigning_common_arguments(remote_build_parser) + remote_build_parser.add_argument( + '--darwinContainersHost', + required=True, + type=str, + help='DarwinContainers host address.' + ) + buildParser.add_argument( + '--configuration', + choices=[ + 'debug_universal', + 'debug_arm64', + 'debug_armv7', + 'release_arm64', + 'release_armv7', + 'release_universal' + ], + required=True, + help='Build configuration' + ) + remote_build_parser.add_argument( + '--bazelCacheHost', + required=False, + type=str, + help='Bazel remote cache host address.' + ) + if len(sys.argv) < 2: parser.print_help() sys.exit(1) @@ -726,6 +847,28 @@ if __name__ == '__main__': generate_project(bazel=bazel_path, arguments=args) elif args.commandName == 'build': build(bazel=bazel_path, arguments=args) + elif args.commandName == 'remote-build': + base_path = os.getcwd() + remote_input_path = '{}/build-input/remote-input'.format(base_path) + certificates_path = '{}/certs'.format(remote_input_path) + provisioning_profiles_path = '{}/profiles'.format(remote_input_path) + + os.makedirs(certificates_path, exist_ok=True) + os.makedirs(provisioning_profiles_path, exist_ok=True) + + configuration_repository_path = resolve_configuration(base_path=os.getcwd(), bazel_command_line=None, arguments=arguments, aps_environment='production') + + certificates_path = '{}/certs'.format(configuration_repository_path) + provisioning_profiles_path = '{}/profiles'.format(configuration_repository_path) + + RemoteBuild.remote_build( + darwin_containers_host=args.darwinContainersHost, + bazel_cache_host=args.bazelCacheHost, + configuration=args.configuration, + certificates_path=certificates_path, + provisioning_profiles_path=provisioning_profiles_path, + configurationPath=args.configurationPath + ) elif args.commandName == 'test': test(bazel=bazel_path, arguments=args) else: diff --git a/build-system/Make/RemoteBuild.py b/build-system/Make/RemoteBuild.py new file mode 100644 index 0000000000..e7bb7f2e6c --- /dev/null +++ b/build-system/Make/RemoteBuild.py @@ -0,0 +1,129 @@ +import os +import sys +import json +import shutil +import shlex +import tempfile + +from BuildEnvironment import run_executable_with_output + +def session_scp_upload(session, source_path, destination_path): + scp_command = 'scp -i {privateKeyPath} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -pr {source_path} containerhost@"{ipAddress}":{destination_path}'.format( + privateKeyPath=session.privateKeyPath, + ipAddress=session.ipAddress, + source_path=shlex.quote(source_path), + destination_path=shlex.quote(destination_path) + ) + if os.system(scp_command) != 0: + print('Command {} finished with a non-zero status'.format(scp_command)) + +def session_scp_download(session, source_path, destination_path): + scp_command = 'scp -i {privateKeyPath} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -pr containerhost@"{ipAddress}":{source_path} {destination_path}'.format( + privateKeyPath=session.privateKeyPath, + ipAddress=session.ipAddress, + source_path=shlex.quote(source_path), + destination_path=shlex.quote(destination_path) + ) + if os.system(scp_command) != 0: + print('Command {} finished with a non-zero status'.format(scp_command)) + +def session_ssh(session, command): + ssh_command = 'ssh -i {privateKeyPath} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null containerhost@"{ipAddress}" -o ServerAliveInterval=60 -t "{command}"'.format( + privateKeyPath=session.privateKeyPath, + ipAddress=session.ipAddress, + command=command + ) + return os.system(ssh_command) + +def remote_build(darwin_containers_host, bazel_cache_host, configuration, certificates_path, provisioning_profiles_path, configurationPath): + from darwin_containers import DarwinContainers + + base_dir = os.getcwd() + + configuration_path = 'versions.json' + xcode_version = '' + with open(configuration_path) as file: + configuration_dict = json.load(file) + if configuration_dict['xcode'] is None: + raise Exception('Missing xcode version in {}'.format(configuration_path)) + xcode_version = configuration_dict['xcode'] + + print('Xcode version: {}'.format(xcode_version)) + + commit_count = run_executable_with_output('git', [ + 'rev-list', + '--count', + 'HEAD' + ]) + + build_number_offset = 0 + with open('build_number_offset') as file: + build_number_offset = int(file.read()) + + build_number = build_number_offset + int(commit_count) + print('Build number: {}'.format(build_number)) + + macos_version = '12.5' + image_name = 'macos-{macos_version}-xcode-{xcode_version}'.format(macos_version=macos_version, xcode_version=xcode_version) + + print('Image name: {}'.format(image_name)) + + source_dir = os.path.basename(base_dir) + buildbox_dir = 'buildbox' + source_archive_path = '{buildbox_dir}/transient-data/source.tar'.format(buildbox_dir=buildbox_dir) + + if os.path.exists(source_archive_path): + os.remove(source_archive_path) + + print('Compressing source code...') + os.system('find . -type f -a -not -regex "\\." -a -not -regex ".*\\./git" -a -not -regex ".*\\./git/.*" -a -not -regex "\\./bazel-bin" -a -not -regex "\\./bazel-bin/.*" -a -not -regex "\\./bazel-out" -a -not -regex "\\./bazel-out/.*" -a -not -regex "\\./bazel-testlogs" -a -not -regex "\\./bazel-testlogs/.*" -a -not -regex "\\./bazel-telegram-ios" -a -not -regex "\\./bazel-telegram-ios/.*" -a -not -regex "\\./buildbox" -a -not -regex "\\./buildbox/.*" -a -not -regex "\\./buck-out" -a -not -regex "\\./buck-out/.*" -a -not -regex "\\./\\.buckd" -a -not -regex "\\./\\.buckd/.*" -a -not -regex "\\./build" -a -not -regex "\\./build/.*" -print0 | tar cf "{buildbox_dir}/transient-data/source.tar" --null -T -'.format(buildbox_dir=buildbox_dir)) + + darwinContainers = DarwinContainers(serverAddress=darwin_containers_host, verbose=False) + + print('Opening container session...') + with darwinContainers.workingImageSession(name=image_name) as session: + print('Uploading data to container...') + session_scp_upload(session=session, source_path=certificates_path, destination_path='certs') + session_scp_upload(session=session, source_path=provisioning_profiles_path, destination_path='profiles') + session_scp_upload(session=session, source_path=configurationPath, destination_path='configuration.json') + session_scp_upload(session=session, source_path='{base_dir}/{buildbox_dir}/transient-data/source.tar'.format(base_dir=base_dir, buildbox_dir=buildbox_dir), destination_path='') + + guest_build_sh = ''' + mkdir telegram-ios + cd telegram-ios + tar -xf ../source.tar + + python3 build-system/Make/ImportCertificates.py --path $HOME/certs + + python3 build-system/Make/Make.py \\ + build \\ + --buildNumber={build_number} \\ + --configuration={configuration} \\ + --configurationPath=$HOME/configuration.json \\ + --apsEnvironment=production \\ + --provisioningProfilesPath=$HOME/profiles + '''.format( + build_number=build_number, + configuration=configuration + ) + guest_build_file_path = tempfile.mktemp() + with open(guest_build_file_path, 'w+') as file: + file.write(guest_build_sh) + session_scp_upload(session=session, source_path=guest_build_file_path, destination_path='guest-build-telegram.sh') + os.unlink(guest_build_file_path) + + print('Executing remote build...') + + if bazel_cache_host is None: + bazel_cache_host = '' + session_ssh(session=session, command='bash -l guest-build-telegram.sh') + + print('Retrieving build artifacts...') + + artifacts_path='{base_dir}/build/artifacts'.format(base_dir=base_dir) + if os.path.exists(artifacts_path): + shutil.rmtree(artifacts_path) + os.makedirs(artifacts_path, exist_ok=True) + + session_scp_download(session=session, source_path='telegram-ios/build/artifacts/*', destination_path='{artifacts_path}/'.format(artifacts_path=artifacts_path)) + print('Artifacts have been stored at {}'.format(artifacts_path)) diff --git a/build-system/appstore-configuration.json b/build-system/appstore-configuration.json new file mode 100755 index 0000000000..9dea35483f --- /dev/null +++ b/build-system/appstore-configuration.json @@ -0,0 +1,14 @@ +{ + "bundle_id": "ph.telegra.Telegraph", + "api_id": "8", + "api_hash": "7245de8e747a0d6fbe11f7cc14fcc0bb", + "team_id": "C67CF9S4VU", + "app_center_id": "0", + "is_internal_build": "false", + "is_appstore_build": "true", + "appstore_id": "686449807", + "app_specific_url_scheme": "tg", + "premium_iap_product_id": "org.telegram.telegramPremium.monthly", + "enable_siri": true, + "enable_icloud": true +} \ No newline at end of file diff --git a/build-system/fake-codesigning/certs/distribution/Public.cer b/build-system/fake-codesigning/certs/Public.cer similarity index 100% rename from build-system/fake-codesigning/certs/distribution/Public.cer rename to build-system/fake-codesigning/certs/Public.cer diff --git a/build-system/fake-codesigning/certs/distribution/SelfSigned.p12 b/build-system/fake-codesigning/certs/SelfSigned.p12 similarity index 100% rename from build-system/fake-codesigning/certs/distribution/SelfSigned.p12 rename to build-system/fake-codesigning/certs/SelfSigned.p12 diff --git a/build-system/fake-codesigning/profiles/appstore/BroadcastUpload.mobileprovision b/build-system/fake-codesigning/profiles/BroadcastUpload.mobileprovision similarity index 100% rename from build-system/fake-codesigning/profiles/appstore/BroadcastUpload.mobileprovision rename to build-system/fake-codesigning/profiles/BroadcastUpload.mobileprovision diff --git a/build-system/fake-codesigning/profiles/appstore/Intents.mobileprovision b/build-system/fake-codesigning/profiles/Intents.mobileprovision similarity index 100% rename from build-system/fake-codesigning/profiles/appstore/Intents.mobileprovision rename to build-system/fake-codesigning/profiles/Intents.mobileprovision diff --git a/build-system/fake-codesigning/profiles/appstore/NotificationContent.mobileprovision b/build-system/fake-codesigning/profiles/NotificationContent.mobileprovision similarity index 100% rename from build-system/fake-codesigning/profiles/appstore/NotificationContent.mobileprovision rename to build-system/fake-codesigning/profiles/NotificationContent.mobileprovision diff --git a/build-system/fake-codesigning/profiles/appstore/NotificationService.mobileprovision b/build-system/fake-codesigning/profiles/NotificationService.mobileprovision similarity index 100% rename from build-system/fake-codesigning/profiles/appstore/NotificationService.mobileprovision rename to build-system/fake-codesigning/profiles/NotificationService.mobileprovision diff --git a/build-system/fake-codesigning/profiles/appstore/Share.mobileprovision b/build-system/fake-codesigning/profiles/Share.mobileprovision similarity index 100% rename from build-system/fake-codesigning/profiles/appstore/Share.mobileprovision rename to build-system/fake-codesigning/profiles/Share.mobileprovision diff --git a/build-system/fake-codesigning/profiles/appstore/Telegram.mobileprovision b/build-system/fake-codesigning/profiles/Telegram.mobileprovision similarity index 100% rename from build-system/fake-codesigning/profiles/appstore/Telegram.mobileprovision rename to build-system/fake-codesigning/profiles/Telegram.mobileprovision diff --git a/build-system/fake-codesigning/profiles/appstore/WatchApp.mobileprovision b/build-system/fake-codesigning/profiles/WatchApp.mobileprovision similarity index 100% rename from build-system/fake-codesigning/profiles/appstore/WatchApp.mobileprovision rename to build-system/fake-codesigning/profiles/WatchApp.mobileprovision diff --git a/build-system/fake-codesigning/profiles/appstore/WatchExtension.mobileprovision b/build-system/fake-codesigning/profiles/WatchExtension.mobileprovision similarity index 100% rename from build-system/fake-codesigning/profiles/appstore/WatchExtension.mobileprovision rename to build-system/fake-codesigning/profiles/WatchExtension.mobileprovision diff --git a/build-system/fake-codesigning/profiles/appstore/Widget.mobileprovision b/build-system/fake-codesigning/profiles/Widget.mobileprovision similarity index 100% rename from build-system/fake-codesigning/profiles/appstore/Widget.mobileprovision rename to build-system/fake-codesigning/profiles/Widget.mobileprovision diff --git a/buildbox/build.py b/buildbox/build.py index 3f66960d07..151967507e 100644 --- a/buildbox/build.py +++ b/buildbox/build.py @@ -8,8 +8,11 @@ import shlex import shutil import subprocess import time +import pipes +import tempfile -from darwin_containers import DarwinContainers +def quote_args(seq): + return ' '.join(pipes.quote(arg) for arg in seq) def get_clean_env(): clean_env = os.environ.copy() @@ -26,7 +29,7 @@ def resolve_executable(program): return None -def run_executable_with_output(path, arguments): +def run_executable_with_output(path, arguments, check_result=False): executable_path = resolve_executable(path) if executable_path is None: raise Exception('Could not resolve {} to a valid executable file'.format(path)) @@ -39,121 +42,110 @@ def run_executable_with_output(path, arguments): ) output_data, _ = process.communicate() output_string = output_data.decode('utf-8') + + if check_result: + if process.returncode != 0: + print('Command {} {} finished with non-zero return code and output:\n{}'.format(executable_path, arguments, output_string)) + sys.exit(1) + return output_string -def session_scp_upload(session, source_path, destination_path): - scp_command = 'scp -i {privateKeyPath} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -pr {source_path} containerhost@"{ipAddress}":{destination_path}'.format( - privateKeyPath=session.privateKeyPath, - ipAddress=session.ipAddress, - source_path=shlex.quote(source_path), - destination_path=shlex.quote(destination_path) - ) - if os.system(scp_command) != 0: - print('Command {} finished with a non-zero status'.format(scp_command)) -def session_ssh(session, command): - ssh_command = 'ssh -i {privateKeyPath} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null containerhost@"{ipAddress}" -o ServerAliveInterval=60 -t "{command}"'.format( - privateKeyPath=session.privateKeyPath, - ipAddress=session.ipAddress, - command=command - ) - return os.system(ssh_command) -def remote_build(darwin_containers_host, configuration): - base_dir = os.getcwd() - configuration_path = 'versions.json' - xcode_version = '' - with open(configuration_path) as file: - configuration_dict = json.load(file) - if configuration_dict['xcode'] is None: - raise Exception('Missing xcode version in {}'.format(configuration_path)) - xcode_version = configuration_dict['xcode'] +def isolated_build(arguments): + if arguments.certificatesPath is not None: + if not os.path.exists(arguments.certificatesPath): + print('{} does not exist'.format(arguments.certificatesPath)) + sys.exit(1) - print('Xcode version: {}'.format(xcode_version)) + keychain_name = 'temp.keychain' + keychain_password = 'secret' - commit_count = run_executable_with_output('git', [ - 'rev-list', - '--count', - 'HEAD' - ]) + existing_keychains = run_executable_with_output('security', arguments=['list-keychains'], check_result=True) + if keychain_name in existing_keychains: + run_executable_with_output('security', arguments=['delete-keychain'], check_result=True) - build_number_offset = 0 - with open('build_number_offset') as file: - build_number_offset = int(file.read()) + run_executable_with_output('security', arguments=[ + 'create-keychain', + '-p', + keychain_password, + keychain_name + ], check_result=True) - build_number = build_number_offset + int(commit_count) - print('Build number: {}'.format(build_number)) + existing_keychains = run_executable_with_output('security', arguments=['list-keychains', '-d', 'user']) + existing_keychains.replace('"', '') - macos_version = '12.5' - image_name = 'macos-{macos_version}-xcode-{xcode_version}'.format(macos_version=macos_version, xcode_version=xcode_version) + run_executable_with_output('security', arguments=[ + 'list-keychains', + '-d', + 'user', + '-s', + keychain_name, + existing_keychains + ], check_result=True) - print('Image name: {}'.format(image_name)) + run_executable_with_output('security', arguments=['set-keychain-settings', keychain_name]) + run_executable_with_output('security', arguments=['unlock-keychain', '-p', keychain_password, keychain_name]) - buildbox_dir = 'buildbox' - os.makedirs('{buildbox_dir}/transient-data'.format(buildbox_dir=buildbox_dir), exist_ok=True) + for file_name in os.listdir(arguments.certificatesPath): + file_path = arguments.certificatesPath + '/' + file_name + if file_path.endwith('.p12') or file_path.endwith('.cer'): + run_executable_with_output('security', arguments=[ + 'import', + file_path, + '-k', + keychain_name, + '-P', + '', + '-T', + '/usr/bin/codesign', + '-T', + '/usr/bin/security' + ], check_result=True) - codesigning_subpath = '' - remote_configuration = '' - if configuration == 'appcenter': - remote_configuration = 'hockeyapp' - elif configuration == 'appstore': - remote_configuration = 'appstore' - elif configuration == 'reproducible': - codesigning_subpath = 'build-system/fake-codesigning' - remote_configuration = 'verify' + run_executable_with_output('security', arguments=[ + 'import', + 'build-system/AppleWWDRCAG3.cer', + '-k', + keychain_name, + '-P', + '', + '-T', + '/usr/bin/codesign', + '-T', + '/usr/bin/security' + ], check_result=True) - destination_codesigning_path = '{buildbox_dir}/transient-data/telegram-codesigning'.format(buildbox_dir=buildbox_dir) - destination_build_configuration_path = '{buildbox_dir}/transient-data/build-configuration'.format(buildbox_dir=buildbox_dir) + run_executable_with_output('security', arguments=[ + 'set-key-partition-list', + '-S', + 'apple-tool:,apple:', + '-k', + keychain_password, + keychain_name + ], check_result=True) - if os.path.exists(destination_codesigning_path): - shutil.rmtree(destination_codesigning_path) - if os.path.exists(destination_build_configuration_path): - shutil.rmtree(destination_build_configuration_path) + build_arguments = ['build-system/Make/Make.py'] + + #build_arguments.append('--bazel="$(pwd)/tools/bazel"') + + if arguments.cacheHost is not None: + build_arguments.append('--cacheHost={}'.format(arguments.cacheHost)) - shutil.copytree('build-system/fake-codesigning', '{buildbox_dir}/transient-data/telegram-codesigning'.format(buildbox_dir=buildbox_dir)) - shutil.copytree('build-system/example-configuration', '{buildbox_dir}/transient-data/build-configuration'.format(buildbox_dir=buildbox_dir)) - else: - print('Unknown configuration {}'.format(configuration)) - sys.exit(1) + build_arguments.append('build') - source_dir = os.path.basename(base_dir) - source_archive_path = '{buildbox_dir}/transient-data/source.tar'.format(buildbox_dir=buildbox_dir) + build_arguments.append('--configurationPath={}'.format(arguments.configurationPath)) + build_arguments.append('--buildNumber={}'.format(arguments.buildNumber)) + build_arguments.append('--configuration={}'.format(arguments.configuration)) + build_arguments.append('--apsEnvironment=production') + build_arguments.append('--disableParallelSwiftmoduleGeneration') + build_arguments.append('--provisioningProfilesPath={}'.format(arguments.provisioningProfilesPath)) - if os.path.exists(source_archive_path): - os.remove(source_archive_path) + build_command = 'python3 ' + quote_args(build_arguments) + print('Running {}'.format(build_command)) + os.system(build_command) - print('Compressing source code...') - os.system('find . -type f -a -not -regex "\\." -a -not -regex ".*\\./git" -a -not -regex ".*\\./git/.*" -a -not -regex "\\./bazel-bin" -a -not -regex "\\./bazel-bin/.*" -a -not -regex "\\./bazel-out" -a -not -regex "\\./bazel-out/.*" -a -not -regex "\\./bazel-testlogs" -a -not -regex "\\./bazel-testlogs/.*" -a -not -regex "\\./bazel-telegram-ios" -a -not -regex "\\./bazel-telegram-ios/.*" -a -not -regex "\\./buildbox" -a -not -regex "\\./buildbox/.*" -a -not -regex "\\./buck-out" -a -not -regex "\\./buck-out/.*" -a -not -regex "\\./\\.buckd" -a -not -regex "\\./\\.buckd/.*" -a -not -regex "\\./build" -a -not -regex "\\./build/.*" -print0 | tar cf "{buildbox_dir}/transient-data/source.tar" --null -T -'.format(buildbox_dir=buildbox_dir)) - - darwinContainers = DarwinContainers(serverAddress=darwin_containers_host, verbose=False) - - print('Opening container session...') - with darwinContainers.workingImageSession(name=image_name) as session: - print('Uploading data to container...') - session_scp_upload(session=session, source_path=codesigning_subpath, destination_path='codesigning_data') - session_scp_upload(session=session, source_path='{base_dir}/{buildbox_dir}/transient-data/build-configuration'.format(base_dir=base_dir, buildbox_dir=buildbox_dir), destination_path='telegram-configuration') - session_scp_upload(session=session, source_path='{base_dir}/{buildbox_dir}/guest-build-telegram.sh'.format(base_dir=base_dir, buildbox_dir=buildbox_dir), destination_path='') - session_scp_upload(session=session, source_path='{base_dir}/{buildbox_dir}/transient-data/source.tar'.format(base_dir=base_dir, buildbox_dir=buildbox_dir), destination_path='') - - print('Executing remote build...') - - bazel_cache_host='' - session_ssh(session=session, command='BUILD_NUMBER="{build_number}" BAZEL_HTTP_CACHE_URL="{bazel_cache_host}" bash -l guest-build-telegram.sh {remote_configuration}'.format( - build_number=build_number, - bazel_cache_host=bazel_cache_host, - remote_configuration=remote_configuration - )) - - print('Retrieving build artifacts...') - - artifacts_path='{base_dir}/build/artifacts'.format(base_dir=base_dir) - if os.path.exists(artifacts_path): - shutil.rmtree(artifacts_path) - os.makedirs(artifacts_path, exist_ok=True) - - session_scp_download(session=session, source_path='telegram-ios/build/artifacts/*', destination_path='{artifacts_path}/'.format(artifacts_path=artifacts_path)) - print('Artifacts have been stored at {}'.format(artifacts_path)) if __name__ == '__main__': parser = argparse.ArgumentParser(prog='build') @@ -182,7 +174,57 @@ if __name__ == '__main__': 'reproducible' ], required=True, - help='Build configuration' + help='Build configuration' + ) + remote_build_parser.add_argument( + '--bazelCacheHost', + required=False, + type=str, + help='Bazel remote cache host address.' + ) + + isolated_build_parser = subparsers.add_parser('isolated-build', help='Build the app inside an isolated environment.') + isolated_build_parser.add_argument( + '--certificatesPath', + required=False, + type=str, + help='Install codesigning certificates from the specified directory.' + ) + isolated_build_parser.add_argument( + '--provisioningProfilesPath', + required=True, + help=''' + Use codesigning provisioning profiles from a local directory. + ''', + metavar='command' + ) + isolated_build_parser.add_argument( + '--cacheHost', + required=False, + type=str, + help='Bazel cache host url.' + ) + isolated_build_parser.add_argument( + '--configurationPath', + help=''' + Path to a json containing build configuration. + See build-system/appstore-configuration.json for an example. + ''', + required=True, + metavar='path' + ) + isolated_build_parser.add_argument( + '--buildNumber', + required=True, + type=int, + help='Build number.', + metavar='number' + ) + isolated_build_parser.add_argument( + '--configuration', + type=str, + required=True, + help='Build configuration' ) if len(sys.argv) < 2: @@ -195,164 +237,8 @@ if __name__ == '__main__': exit(0) if args.commandName == 'remote-build': - remote_build(darwin_containers_host=args.darwinContainersHost, configuration=args.configuration) + remote_build(darwin_containers_host=args.darwinContainersHost, bazel_cache_host=args.bazelCacheHost, configuration=args.configuration) + elif args.commandName == 'isolated-build': + isolated_build(arguments=args) -'''set -e - -rm -f "tools/bazel" -cp "$BAZEL" "tools/bazel" - -BUILD_CONFIGURATION="$1" - -if [ "$BUILD_CONFIGURATION" == "hockeyapp" ] || [ "$BUILD_CONFIGURATION" == "appcenter-experimental" ] || [ "$BUILD_CONFIGURATION" == "appcenter-experimental-2" ]; then - CODESIGNING_SUBPATH="$BUILDBOX_DIR/transient-data/telegram-codesigning/codesigning" -elif [ "$BUILD_CONFIGURATION" == "appstore" ] || [ "$BUILD_CONFIGURATION" == "appstore-development" ]; then - CODESIGNING_SUBPATH="$BUILDBOX_DIR/transient-data/telegram-codesigning/codesigning" -elif [ "$BUILD_CONFIGURATION" == "verify" ]; then - CODESIGNING_SUBPATH="build-system/fake-codesigning" -else - echo "Unknown configuration $1" - exit 1 -fi - -COMMIT_COMMENT="$(git log -1 --pretty=%B)" -case "$COMMIT_COMMENT" in - *"[nocache]"*) - export BAZEL_HTTP_CACHE_URL="" - ;; -esac - -COMMIT_ID="$(git rev-parse HEAD)" -COMMIT_AUTHOR=$(git log -1 --pretty=format:'%an') -if [ -z "$2" ]; then - COMMIT_COUNT=$(git rev-list --count HEAD) - BUILD_NUMBER_OFFSET="$(cat build_number_offset)" - COMMIT_COUNT="$(($COMMIT_COUNT+$BUILD_NUMBER_OFFSET))" - BUILD_NUMBER="$COMMIT_COUNT" -else - BUILD_NUMBER="$2" -fi - -BASE_DIR=$(pwd) - -if [ "$BUILD_CONFIGURATION" == "hockeyapp" ] || [ "$BUILD_CONFIGURATION" == "appcenter-experimental" ] || [ "$BUILD_CONFIGURATION" == "appcenter-experimental-2" ] || [ "$BUILD_CONFIGURATION" == "appstore" ] || [ "$BUILD_CONFIGURATION" == "appstore-development" ]; then - if [ ! `which generate-configuration.sh` ]; then - echo "generate-configuration.sh not found in PATH $PATH" - exit 1 - fi - - mkdir -p "$BASE_DIR/$BUILDBOX_DIR/transient-data/telegram-codesigning" - mkdir -p "$BASE_DIR/$BUILDBOX_DIR/transient-data/build-configuration" - - case "$BUILD_CONFIGURATION" in - "hockeyapp"|"appcenter-experimental"|"appcenter-experimental-2") - generate-configuration.sh internal release "$BASE_DIR/$BUILDBOX_DIR/transient-data/telegram-codesigning" "$BASE_DIR/$BUILDBOX_DIR/transient-data/build-configuration" - ;; - - "appstore") - generate-configuration.sh appstore release "$BASE_DIR/$BUILDBOX_DIR/transient-data/telegram-codesigning" "$BASE_DIR/$BUILDBOX_DIR/transient-data/build-configuration" - ;; - - "appstore-development") - generate-configuration.sh appstore development "$BASE_DIR/$BUILDBOX_DIR/transient-data/telegram-codesigning" "$BASE_DIR/$BUILDBOX_DIR/transient-data/build-configuration" - ;; - - *) - echo "Unknown build configuration $BUILD_CONFIGURATION" - exit 1 - ;; - esac -elif [ "$BUILD_CONFIGURATION" == "verify" ]; then - mkdir -p "$BASE_DIR/$BUILDBOX_DIR/transient-data/telegram-codesigning" - mkdir -p "$BASE_DIR/$BUILDBOX_DIR/transient-data/build-configuration" - - cp -R build-system/fake-codesigning/* "$BASE_DIR/$BUILDBOX_DIR/transient-data/telegram-codesigning/" - cp -R build-system/example-configuration/* "$BASE_DIR/$BUILDBOX_DIR/transient-data/build-configuration/" -fi - -if [ ! -d "$CODESIGNING_SUBPATH" ]; then - echo "$CODESIGNING_SUBPATH does not exist" - exit 1 -fi - -SOURCE_DIR=$(basename "$BASE_DIR") -rm -f "$BUILDBOX_DIR/transient-data/source.tar" -set -x -find . -type f -a -not -regex "\\." -a -not -regex ".*\\./git" -a -not -regex ".*\\./git/.*" -a -not -regex "\\./bazel-bin" -a -not -regex "\\./bazel-bin/.*" -a -not -regex "\\./bazel-out" -a -not -regex "\\./bazel-out/.*" -a -not -regex "\\./bazel-testlogs" -a -not -regex "\\./bazel-testlogs/.*" -a -not -regex "\\./bazel-telegram-ios" -a -not -regex "\\./bazel-telegram-ios/.*" -a -not -regex "\\./buildbox" -a -not -regex "\\./buildbox/.*" -a -not -regex "\\./buck-out" -a -not -regex "\\./buck-out/.*" -a -not -regex "\\./\\.buckd" -a -not -regex "\\./\\.buckd/.*" -a -not -regex "\\./build" -a -not -regex "\\./build/.*" -print0 | tar cf "$BUILDBOX_DIR/transient-data/source.tar" --null -T - - -PROCESS_ID="$$" - -if [ -z "$RUNNING_VM" ]; then - VM_NAME="$VM_BASE_NAME-$(openssl rand -hex 10)-build-telegram-$PROCESS_ID" -else - VM_NAME="$RUNNING_VM" -fi - -if [ "$BUILD_MACHINE" == "linux" ]; then - virt-clone --original "$VM_BASE_NAME" --name "$VM_NAME" --auto-clone - virsh start "$VM_NAME" - - echo "Getting VM IP" - - while [ 1 ]; do - TEST_IP=$(virsh domifaddr "$VM_NAME" 2>/dev/null | egrep -o 'ipv4.*' | sed -e 's/ipv4\s*//g' | sed -e 's|/.*||g') - if [ ! -z "$TEST_IP" ]; then - RESPONSE=$(ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null telegram@"$TEST_IP" -o ServerAliveInterval=60 -t "echo -n 1") - if [ "$RESPONSE" == "1" ]; then - VM_IP="$TEST_IP" - break - fi - fi - sleep 1 - done -elif [ "$BUILD_MACHINE" == "macOS" ]; then - if [ -z "$RUNNING_VM" ]; then - prlctl clone "$VM_BASE_NAME" --linked --name "$VM_NAME" - prlctl start "$VM_NAME" - - echo "Getting VM IP" - - while [ 1 ]; do - TEST_IP=$(prlctl exec "$VM_NAME" "ifconfig | grep inet | grep broadcast | grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1 | tr '\n' '\0'" 2>/dev/null || echo "") - if [ ! -z "$TEST_IP" ]; then - RESPONSE=$(ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null telegram@"$TEST_IP" -o ServerAliveInterval=60 -t "echo -n 1") - if [ "$RESPONSE" == "1" ]; then - VM_IP="$TEST_IP" - break - fi - fi - sleep 1 - done - fi - echo "VM_IP=$VM_IP" -fi - -scp -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -pr "$CODESIGNING_SUBPATH" telegram@"$VM_IP":codesigning_data -scp -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -pr "$BASE_DIR/$BUILDBOX_DIR/transient-data/build-configuration" telegram@"$VM_IP":telegram-configuration - -scp -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -pr "$BUILDBOX_DIR/guest-build-telegram.sh" "$BUILDBOX_DIR/transient-data/source.tar" telegram@"$VM_IP": - -ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null telegram@"$VM_IP" -o ServerAliveInterval=60 -t "export BUILD_NUMBER=\"$BUILD_NUMBER\"; export BAZEL_HTTP_CACHE_URL=\"$BAZEL_HTTP_CACHE_URL\"; $GUEST_SHELL -l guest-build-telegram.sh $BUILD_CONFIGURATION" || true - -OUTPUT_PATH="build/artifacts" -rm -rf "$OUTPUT_PATH" -mkdir -p "$OUTPUT_PATH" - -scp -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -pr telegram@"$VM_IP":"telegram-ios/build/artifacts/*" "$OUTPUT_PATH/" - -if [ -z "$RUNNING_VM" ]; then - if [ "$BUILD_MACHINE" == "linux" ]; then - virsh destroy "$VM_NAME" - virsh undefine "$VM_NAME" --remove-all-storage --nvram - elif [ "$BUILD_MACHINE" == "macOS" ]; then - echo "Deleting VM..." - #prlctl stop "$VM_NAME" --kill - #prlctl delete "$VM_NAME" - fi -fi - -if [ ! -f "$OUTPUT_PATH/Telegram.ipa" ]; then - exit 1 -fi -''' \ No newline at end of file diff --git a/buildbox/guest-build-telegram.sh b/buildbox/guest-build-telegram.sh index 4404ad9b4e..519ec4508f 100644 --- a/buildbox/guest-build-telegram.sh +++ b/buildbox/guest-build-telegram.sh @@ -101,7 +101,8 @@ python3 build-system/Make/Make.py \ --configurationPath="$HOME/telegram-configuration" \ --buildNumber="$BUILD_NUMBER" \ --disableParallelSwiftmoduleGeneration \ - --configuration="$APP_CONFIGURATION" + --configuration="$APP_CONFIGURATION" \ + --apsEnvironment=production OUTPUT_PATH="build/artifacts" rm -rf "$OUTPUT_PATH" From ac0eb23815aacebe518a2eb6be06d971f5c00df1 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Thu, 11 Aug 2022 00:39:11 +0400 Subject: [PATCH 02/22] Working state --- build-system/Make/BuildConfiguration.py | 65 +++++-- build-system/Make/ImportCertificates.py | 2 +- build-system/Make/Make.py | 160 +++++++++++----- build-system/Make/RemoteBuild.py | 57 +++--- buildbox/build.py | 244 ------------------------ 5 files changed, 192 insertions(+), 336 deletions(-) delete mode 100644 buildbox/build.py diff --git a/build-system/Make/BuildConfiguration.py b/build-system/Make/BuildConfiguration.py index 463a44c8e0..3029f6bf08 100644 --- a/build-system/Make/BuildConfiguration.py +++ b/build-system/Make/BuildConfiguration.py @@ -112,7 +112,7 @@ def decrypt_codesigning_directory_recursively(source_base_path, destination_base decrypt_codesigning_directory_recursively(source_path, destination_path, password) -def load_provisioning_profiles_from_git(working_dir, repo_url, branch, password, always_fetch): +def load_codesigning_data_from_git(working_dir, repo_url, branch, password, always_fetch): if not os.path.exists(working_dir): os.makedirs(working_dir, exist_ok=True) @@ -121,15 +121,15 @@ def load_provisioning_profiles_from_git(working_dir, repo_url, branch, password, if always_fetch: original_working_dir = os.getcwd() os.chdir(encrypted_working_dir) - os.system('git fetch') + os.system('GIT_SSH_COMMAND="ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" git fetch') os.system('git checkout "{branch}"'.format(branch=branch)) - os.system('git pull') + os.system('GIT_SSH_COMMAND="ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" git pull') os.chdir(original_working_dir) else: os.makedirs(encrypted_working_dir, exist_ok=True) original_working_dir = os.getcwd() os.chdir(working_dir) - os.system('git clone {repo_url} -b "{branch}" "{target_path}"'.format( + os.system('git -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null clone {repo_url} -b "{branch}" "{target_path}"'.format( repo_url=repo_url, branch=branch, target_path=encrypted_working_dir @@ -185,42 +185,67 @@ def copy_profiles_from_directory(source_path, destination_path, team_id, bundle_ print('Warning: skipping provisioning profile at {} with bundle_id {} (base_name {})'.format(file_path, profile_name, profile_base_name)) -class ProvisioningProfileSource: +def copy_certificates_from_directory(source_path, destination_path): + for file_name in os.listdir(source_path): + file_path = source_path + '/' + file_name + if os.path.isfile(file_path): + if file_path.endswith('.p12') or file_path.endswith('.cer'): + shutil.copyfile(file_path, destination_path + '/' + file_name) + + +class CodesigningSource: def __init__(self): pass + def load_data(self, working_dir): + raise Exception('Not implemented') + def copy_profiles_to_destination(self, destination_path): raise Exception('Not implemented') + def copy_certificates_to_destination(self, destination_path): + raise Exception('Not implemented') -class GitProvisioningProfileSource(ProvisioningProfileSource): - def __init__(self, working_dir, repo_url, team_id, bundle_id, profile_type, password, always_fetch): - self.working_dir = working_dir + +class GitCodesigningSource(CodesigningSource): + def __init__(self, repo_url, team_id, bundle_id, codesigning_type, password, always_fetch): self.repo_url = repo_url self.team_id = team_id self.bundle_id = bundle_id - self.profile_type = profile_type + self.codesigning_type = codesigning_type self.password = password self.always_fetch = always_fetch + def load_data(self, working_dir): + self.working_dir = working_dir + load_codesigning_data_from_git(working_dir=self.working_dir, repo_url=self.repo_url, branch=self.team_id, password=self.password, always_fetch=self.always_fetch) + def copy_profiles_to_destination(self, destination_path): - load_provisioning_profiles_from_git(working_dir=self.working_dir, repo_url=self.repo_url, branch=self.team_id, password=self.password, always_fetch=self.always_fetch) - copy_profiles_from_directory(source_path=self.working_dir + '/decrypted/profiles/{}'.format(self.profile_type), destination_path=destination_path, team_id=self.team_id, bundle_id=self.bundle_id) + source_path = self.working_dir + '/decrypted/profiles/{}'.format(self.codesigning_type) + copy_profiles_from_directory(source_path=source_path, destination_path=destination_path, team_id=self.team_id, bundle_id=self.bundle_id) + + def copy_certificates_to_destination(self, destination_path): + source_path = None + if self.codesigning_type in ['adhoc', 'appstore', 'enterprise']: + source_path = self.working_dir + '/decrypted/certs/distribution' + elif self.codesigning_type == 'development': + source_path = self.working_dir + '/decrypted/certs/development' + else: + raise Exception('Unknown codesigning type {}'.format(self.codesigning_type)) + copy_certificates_from_directory(source_path=source_path, destination_path=destination_path) -class DirectoryProvisioningProfileSource(ProvisioningProfileSource): +class DirectoryCodesigningSource(CodesigningSource): def __init__(self, directory_path, team_id, bundle_id): self.directory_path = directory_path self.team_id = team_id self.bundle_id = bundle_id + def load_data(self, working_dir): + pass + def copy_profiles_to_destination(self, destination_path): - profiles_path = self.directory_path - if not os.path.exists(profiles_path): - print('{} does not exist'.format(profiles_path)) - sys.exit(1) - copy_profiles_from_directory(source_path=profiles_path, destination_path=destination_path, team_id=self.team_id, bundle_id=self.bundle_id) + copy_profiles_from_directory(source_path=self.directory_path + '/profiles', destination_path=destination_path, team_id=self.team_id, bundle_id=self.bundle_id) - -def generate_configuration_repository(path, profile_source): - pass + def copy_certificates_to_destination(self, destination_path): + copy_certificates_from_directory(source_path=self.directory_path + '/certs', destination_path=destination_path) diff --git a/build-system/Make/ImportCertificates.py b/build-system/Make/ImportCertificates.py index 2e6ddd6a1e..c449fe4b5e 100644 --- a/build-system/Make/ImportCertificates.py +++ b/build-system/Make/ImportCertificates.py @@ -40,7 +40,7 @@ def import_certificates(certificatesPath): for file_name in os.listdir(certificatesPath): file_path = certificatesPath + '/' + file_name - if file_path.endwith('.p12') or file_path.endwith('.cer'): + if file_path.endswith('.p12') or file_path.endswith('.cer'): run_executable_with_output('security', arguments=[ 'import', file_path, diff --git a/build-system/Make/Make.py b/build-system/Make/Make.py index f3e5c3ed19..a392e31a71 100644 --- a/build-system/Make/Make.py +++ b/build-system/Make/Make.py @@ -7,11 +7,12 @@ import sys import tempfile import subprocess import shutil +import glob -from BuildEnvironment import resolve_executable, call_executable, BuildEnvironment +from BuildEnvironment import resolve_executable, call_executable, run_executable_with_output, BuildEnvironment from ProjectGeneration import generate from BazelLocation import locate_bazel -from BuildConfiguration import ProvisioningProfileSource, GitProvisioningProfileSource, DirectoryProvisioningProfileSource, BuildConfiguration, build_configuration_from_json +from BuildConfiguration import CodesigningSource, GitCodesigningSource, DirectoryCodesigningSource, BuildConfiguration, build_configuration_from_json import RemoteBuild class BazelCommandLine: @@ -383,7 +384,7 @@ def clean(bazel, arguments): bazel_command_line.invoke_clean() -def resolve_codesigning(arguments, base_path, build_configuration, certificates_path, provisioning_profiles_path): +def resolve_codesigning(arguments, base_path, build_configuration, provisioning_profiles_path, additional_codesigning_output_path): profile_source = None if arguments.gitCodesigningRepository is not None: password = os.getenv('TELEGRAM_CODESIGNING_GIT_PASSWORD') @@ -395,31 +396,36 @@ def resolve_codesigning(arguments, base_path, build_configuration, certificates_ print('--gitCodesigningType is required if --gitCodesigningRepository is set') sys.exit(1) - workdir_path = '{}/build-input/configuration-repository-workdir'.format(base_path) - os.makedirs(workdir_path, exist_ok=True) - - profile_source = GitProvisioningProfileSource( - working_dir=workdir_path, + profile_source = GitCodesigningSource( repo_url=arguments.gitCodesigningRepository, team_id=build_configuration.team_id, bundle_id=build_configuration.bundle_id, - profile_type=arguments.gitCodesigningType, + codesigning_type=arguments.gitCodesigningType, password=password, always_fetch=arguments.gitCodesigningAlwaysFetch ) - elif arguments.provisioningProfilesPath is not None: - profile_source = DirectoryProvisioningProfileSource( - directory_path=arguments.provisioningProfilesPath, + elif arguments.codesigningInformationPath is not None: + profile_source = DirectoryCodesigningSource( + directory_path=arguments.codesigningInformationPath, team_id=build_configuration.team_id, bundle_id=build_configuration.bundle_id ) else: - raise Exception('Neither gitCodesigningRepository nor provisioningProfilesPath are set') + raise Exception('Neither gitCodesigningRepository nor codesigningInformationPath are set') - profile_source.copy_profiles_to_destination(destination_path=provisioning_profiles_path) + workdir_path = '{}/build-input/configuration-repository-workdir'.format(base_path) + os.makedirs(workdir_path, exist_ok=True) + profile_source.load_data(working_dir=workdir_path) + + if provisioning_profiles_path is not None: + profile_source.copy_profiles_to_destination(destination_path=provisioning_profiles_path) + + if additional_codesigning_output_path is not None: + profile_source.copy_profiles_to_destination(destination_path=additional_codesigning_output_path + '/profiles') + profile_source.copy_certificates_to_destination(destination_path=additional_codesigning_output_path + '/certs') -def resolve_configuration(base_path, bazel_command_line: BazelCommandLine, arguments, aps_environment): +def resolve_configuration(base_path, bazel_command_line: BazelCommandLine, arguments, aps_environment, additional_codesigning_output_path): configuration_repository_path = '{}/build-input/configuration-repository'.format(base_path) os.makedirs(configuration_repository_path, exist_ok=True) @@ -438,7 +444,13 @@ def resolve_configuration(base_path, bazel_command_line: BazelCommandLine, argum shutil.rmtree(provisioning_path) os.makedirs(provisioning_path, exist_ok=True) - resolve_codesigning(arguments=arguments, base_path=base_path, build_configuration=build_configuration, certificates_path=None, provisioning_profiles_path=provisioning_path) + resolve_codesigning( + arguments=arguments, + base_path=base_path, + build_configuration=build_configuration, + provisioning_profiles_path=provisioning_path, + additional_codesigning_output_path=additional_codesigning_output_path + ) provisioning_profile_files = [] for file_name in os.listdir(provisioning_path): @@ -451,7 +463,8 @@ def resolve_configuration(base_path, bazel_command_line: BazelCommandLine, argum file.write(' "{}",\n'.format(file_name)) file.write('])\n') - return configuration_repository_path + if bazel_command_line is not None: + bazel_command_line.set_configuration_path(configuration_repository_path) def generate_project(bazel, arguments): @@ -469,10 +482,13 @@ def generate_project(bazel, arguments): bazel_command_line.set_continue_on_error(arguments.continueOnError) - configuration_repository_path = resolve_configuration(base_path=os.getcwd(), bazel_command_line=bazel_command_line, arguments=arguments, aps_environment=arguments.apsEnvironment) - - if bazel_command_line is not None: - bazel_command_line.set_configuration_path(configuration_repository_path) + resolve_configuration( + base_path=os.getcwd(), + bazel_command_line=bazel_command_line, + arguments=arguments, + aps_environment=arguments.apsEnvironment, + additional_codesigning_output_path=None + ) bazel_command_line.set_build_number(arguments.buildNumber) @@ -516,7 +532,13 @@ def build(bazel, arguments): elif arguments.cacheHost is not None: bazel_command_line.add_remote_cache(arguments.cacheHost) - resolve_configuration(base_path=os.getcwd(), bazel_command_line=bazel_command_line, arguments=arguments, aps_environment=arguments.apsEnvironment) + resolve_configuration( + base_path=os.getcwd(), + bazel_command_line=bazel_command_line, + arguments=arguments, + aps_environment=arguments.apsEnvironment, + additional_codesigning_output_path=None + ) bazel_command_line.set_configuration(arguments.configuration) bazel_command_line.set_build_number(arguments.buildNumber) @@ -528,6 +550,36 @@ def build(bazel, arguments): bazel_command_line.invoke_build() + if arguments.outputBuildArtifactsPath is not None: + artifacts_path = arguments.outputBuildArtifactsPath + if os.path.exists(artifacts_path + '/Telegram.ipa'): + os.remove(path) + if os.path.exists(artifacts_path + '/DSYMs'): + shutil.rmtree(artifacts_path + '/DSYMs') + os.makedirs(artifacts_path, exist_ok=True) + os.makedirs(artifacts_path + '/DSYMs', exist_ok=True) + + ipa_paths = glob.glob('bazel-out/applebin_ios-ios_arm*-opt-ST-*/bin/Telegram/Telegram.ipa') + if len(ipa_paths) == 0: + print('Could not find the IPA at bazel-out/applebin_ios-ios_arm*-opt-ST-*/bin/Telegram/Telegram.ipa') + sys.exit(1) + elif len(ipa_paths) > 1: + print('Multiple matching IPA files found: {}'.format(ipa_paths)) + sys.exit(1) + shutil.copyfile(ipa_paths[0], artifacts_path + '/Telegram.ipa') + + dsym_paths = glob.glob('bazel-out/applebin_ios-ios_arm*-opt-ST-*/bin/Telegram/*.dSYM') + for dsym_path in dsym_paths: + file_name = os.path.basename(dsym_path) + shutil.copyfile(ipa_paths[0], artifacts_path + '/DSYMs/{}'.format(file_name)) + run_executable_with_output('zip', arguments=[ + '-9', + '-r', + artifacts_path + '/Telegram.DSYMs.zip', + artifacts_path + '/DSYMs' + ], check_result=True) + shutil.rmtree(artifacts_path + '/DSYMs') + def test(bazel, arguments): bazel_command_line = BazelCommandLine( @@ -542,7 +594,13 @@ def test(bazel, arguments): elif arguments.cacheHost is not None: bazel_command_line.add_remote_cache(arguments.cacheHost) - resolve_configuration(base_path=os.getcwd(), bazel_command_line=bazel_command_line, arguments=arguments, aps_environment=arguments.apsEnvironment) + resolve_configuration( + base_path=os.getcwd(), + bazel_command_line=bazel_command_line, + arguments=arguments, + aps_environment=arguments.apsEnvironment, + additional_codesigning_output_path=None + ) bazel_command_line.set_configuration('debug_sim_arm64') bazel_command_line.set_build_number('10000') @@ -562,13 +620,6 @@ def add_codesigning_common_arguments(current_parser: argparse.ArgumentParser): ) codesigning_group = current_parser.add_mutually_exclusive_group(required=True) - codesigning_group.add_argument( - '--reproducibleCodesigning', - action='store_true', - help=''' - Use locally generated provisioning profiles and certificates for a reproducible build. - ''' - ) codesigning_group.add_argument( '--gitCodesigningRepository', help=''' @@ -578,15 +629,21 @@ def add_codesigning_common_arguments(current_parser: argparse.ArgumentParser): metavar='path' ) codesigning_group.add_argument( - '--provisioningProfilesPath', + '--codesigningInformationPath', help=''' - Use provisioning profiles from a local directory. + Use signing certificates and provisioning profiles from a local directory. ''', metavar='command' ) current_parser.add_argument( '--gitCodesigningType', + choices=[ + 'development', + 'adhoc', + 'appstore', + 'enterprise' + ], required=False, help=''' The name of the folder to use inside "profiles" folder in the git repository. @@ -793,6 +850,12 @@ if __name__ == '__main__': default=False, help='Enable sandbox.', ) + buildParser.add_argument( + '--outputBuildArtifactsPath', + required=False, + help='Store IPA and DSYM at the specified path after a successful build.', + metavar='arguments' + ) remote_build_parser = subparsers.add_parser('remote-build', help='Build the app using a remote environment.') add_codesigning_common_arguments(remote_build_parser) @@ -802,7 +865,7 @@ if __name__ == '__main__': type=str, help='DarwinContainers host address.' ) - buildParser.add_argument( + remote_build_parser.add_argument( '--configuration', choices=[ 'debug_universal', @@ -816,7 +879,7 @@ if __name__ == '__main__': help='Build configuration' ) remote_build_parser.add_argument( - '--bazelCacheHost', + '--cacheHost', required=False, type=str, help='Bazel remote cache host address.' @@ -850,24 +913,27 @@ if __name__ == '__main__': elif args.commandName == 'remote-build': base_path = os.getcwd() remote_input_path = '{}/build-input/remote-input'.format(base_path) - certificates_path = '{}/certs'.format(remote_input_path) - provisioning_profiles_path = '{}/profiles'.format(remote_input_path) + if os.path.exists(remote_input_path): + shutil.rmtree(remote_input_path) + os.makedirs(remote_input_path) + os.makedirs(remote_input_path + '/certs') + os.makedirs(remote_input_path + '/profiles') - os.makedirs(certificates_path, exist_ok=True) - os.makedirs(provisioning_profiles_path, exist_ok=True) - - configuration_repository_path = resolve_configuration(base_path=os.getcwd(), bazel_command_line=None, arguments=arguments, aps_environment='production') - - certificates_path = '{}/certs'.format(configuration_repository_path) - provisioning_profiles_path = '{}/profiles'.format(configuration_repository_path) + resolve_configuration( + base_path=os.getcwd(), + bazel_command_line=None, + arguments=args, + aps_environment='production', + additional_codesigning_output_path=remote_input_path + ) + + shutil.copyfile(args.configurationPath, remote_input_path + '/configuration.json') RemoteBuild.remote_build( darwin_containers_host=args.darwinContainersHost, - bazel_cache_host=args.bazelCacheHost, + bazel_cache_host=args.cacheHost, configuration=args.configuration, - certificates_path=certificates_path, - provisioning_profiles_path=provisioning_profiles_path, - configurationPath=args.configurationPath + build_input_data_path=remote_input_path ) elif args.commandName == 'test': test(bazel=bazel_path, arguments=args) diff --git a/build-system/Make/RemoteBuild.py b/build-system/Make/RemoteBuild.py index e7bb7f2e6c..bdca64501b 100644 --- a/build-system/Make/RemoteBuild.py +++ b/build-system/Make/RemoteBuild.py @@ -35,7 +35,9 @@ def session_ssh(session, command): ) return os.system(ssh_command) -def remote_build(darwin_containers_host, bazel_cache_host, configuration, certificates_path, provisioning_profiles_path, configurationPath): +def remote_build(darwin_containers_host, bazel_cache_host, configuration, build_input_data_path): + macos_version = '12.5' + from darwin_containers import DarwinContainers base_dir = os.getcwd() @@ -63,7 +65,6 @@ def remote_build(darwin_containers_host, bazel_cache_host, configuration, certif build_number = build_number_offset + int(commit_count) print('Build number: {}'.format(build_number)) - macos_version = '12.5' image_name = 'macos-{macos_version}-xcode-{xcode_version}'.format(macos_version=macos_version, xcode_version=xcode_version) print('Image name: {}'.format(image_name)) @@ -83,29 +84,34 @@ def remote_build(darwin_containers_host, bazel_cache_host, configuration, certif print('Opening container session...') with darwinContainers.workingImageSession(name=image_name) as session: print('Uploading data to container...') - session_scp_upload(session=session, source_path=certificates_path, destination_path='certs') - session_scp_upload(session=session, source_path=provisioning_profiles_path, destination_path='profiles') - session_scp_upload(session=session, source_path=configurationPath, destination_path='configuration.json') + session_scp_upload(session=session, source_path=build_input_data_path, destination_path='telegram-build-input') session_scp_upload(session=session, source_path='{base_dir}/{buildbox_dir}/transient-data/source.tar'.format(base_dir=base_dir, buildbox_dir=buildbox_dir), destination_path='') guest_build_sh = ''' - mkdir telegram-ios - cd telegram-ios - tar -xf ../source.tar + set -x + set -e - python3 build-system/Make/ImportCertificates.py --path $HOME/certs + mkdir /Users/Shared/telegram-ios + cd /Users/Shared/telegram-ios + + tar -xf $HOME/source.tar + + python3 build-system/Make/ImportCertificates.py --path $HOME/telegram-build-input/certs + + ''' + + guest_build_sh += 'python3 build-system/Make/Make.py \\' + if bazel_cache_host is not None: + guest_build_sh += '--cacheHost="{}" \\'.format(bazel_cache_host) + guest_build_sh += 'build \\' + guest_build_sh += '' + guest_build_sh += '--buildNumber={} \\'.format(build_number) + guest_build_sh += '--configuration={} \\'.format(configuration) + guest_build_sh += '--configurationPath=$HOME/telegram-build-input/configuration.json \\' + guest_build_sh += '--codesigningInformationPath=$HOME/telegram-build-input \\' + guest_build_sh += '--apsEnvironment=production \\' + guest_build_sh += '--outputBuildArtifactsPath=/Users/Shared/telegram-ios/build/artifacts \\' - python3 build-system/Make/Make.py \\ - build \\ - --buildNumber={build_number} \\ - --configuration={configuration} \\ - --configurationPath=$HOME/configuration.json \\ - --apsEnvironment=production \\ - --provisioningProfilesPath=$HOME/profiles - '''.format( - build_number=build_number, - configuration=configuration - ) guest_build_file_path = tempfile.mktemp() with open(guest_build_file_path, 'w+') as file: file.write(guest_build_sh) @@ -114,8 +120,6 @@ def remote_build(darwin_containers_host, bazel_cache_host, configuration, certif print('Executing remote build...') - if bazel_cache_host is None: - bazel_cache_host = '' session_ssh(session=session, command='bash -l guest-build-telegram.sh') print('Retrieving build artifacts...') @@ -125,5 +129,10 @@ def remote_build(darwin_containers_host, bazel_cache_host, configuration, certif shutil.rmtree(artifacts_path) os.makedirs(artifacts_path, exist_ok=True) - session_scp_download(session=session, source_path='telegram-ios/build/artifacts/*', destination_path='{artifacts_path}/'.format(artifacts_path=artifacts_path)) - print('Artifacts have been stored at {}'.format(artifacts_path)) + session_scp_download(session=session, source_path='/Users/Shared/telegram-ios/build/artifacts/*', destination_path='{artifacts_path}/'.format(artifacts_path=artifacts_path)) + + if os.path.exists(artifacts_path + '/Telegram.ipa'): + print('Artifacts have been stored at {}'.format(artifacts_path)) + else: + print('Telegram.ipa not found') + sys.exit(1) diff --git a/buildbox/build.py b/buildbox/build.py deleted file mode 100644 index 151967507e..0000000000 --- a/buildbox/build.py +++ /dev/null @@ -1,244 +0,0 @@ -#!/usr/bin/python3 - -import argparse -import json -import os -import sys -import shlex -import shutil -import subprocess -import time -import pipes -import tempfile - -def quote_args(seq): - return ' '.join(pipes.quote(arg) for arg in seq) - -def get_clean_env(): - clean_env = os.environ.copy() - return clean_env - -def resolve_executable(program): - def is_executable(fpath): - return os.path.isfile(fpath) and os.access(fpath, os.X_OK) - - for path in get_clean_env()["PATH"].split(os.pathsep): - executable_file = os.path.join(path, program) - if is_executable(executable_file): - return executable_file - return None - - -def run_executable_with_output(path, arguments, check_result=False): - executable_path = resolve_executable(path) - if executable_path is None: - raise Exception('Could not resolve {} to a valid executable file'.format(path)) - - process = subprocess.Popen( - [executable_path] + arguments, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - env=get_clean_env() - ) - output_data, _ = process.communicate() - output_string = output_data.decode('utf-8') - - if check_result: - if process.returncode != 0: - print('Command {} {} finished with non-zero return code and output:\n{}'.format(executable_path, arguments, output_string)) - sys.exit(1) - - return output_string - - - - -def isolated_build(arguments): - if arguments.certificatesPath is not None: - if not os.path.exists(arguments.certificatesPath): - print('{} does not exist'.format(arguments.certificatesPath)) - sys.exit(1) - - keychain_name = 'temp.keychain' - keychain_password = 'secret' - - existing_keychains = run_executable_with_output('security', arguments=['list-keychains'], check_result=True) - if keychain_name in existing_keychains: - run_executable_with_output('security', arguments=['delete-keychain'], check_result=True) - - run_executable_with_output('security', arguments=[ - 'create-keychain', - '-p', - keychain_password, - keychain_name - ], check_result=True) - - existing_keychains = run_executable_with_output('security', arguments=['list-keychains', '-d', 'user']) - existing_keychains.replace('"', '') - - run_executable_with_output('security', arguments=[ - 'list-keychains', - '-d', - 'user', - '-s', - keychain_name, - existing_keychains - ], check_result=True) - - run_executable_with_output('security', arguments=['set-keychain-settings', keychain_name]) - run_executable_with_output('security', arguments=['unlock-keychain', '-p', keychain_password, keychain_name]) - - for file_name in os.listdir(arguments.certificatesPath): - file_path = arguments.certificatesPath + '/' + file_name - if file_path.endwith('.p12') or file_path.endwith('.cer'): - run_executable_with_output('security', arguments=[ - 'import', - file_path, - '-k', - keychain_name, - '-P', - '', - '-T', - '/usr/bin/codesign', - '-T', - '/usr/bin/security' - ], check_result=True) - - run_executable_with_output('security', arguments=[ - 'import', - 'build-system/AppleWWDRCAG3.cer', - '-k', - keychain_name, - '-P', - '', - '-T', - '/usr/bin/codesign', - '-T', - '/usr/bin/security' - ], check_result=True) - - run_executable_with_output('security', arguments=[ - 'set-key-partition-list', - '-S', - 'apple-tool:,apple:', - '-k', - keychain_password, - keychain_name - ], check_result=True) - - build_arguments = ['build-system/Make/Make.py'] - - #build_arguments.append('--bazel="$(pwd)/tools/bazel"') - - if arguments.cacheHost is not None: - build_arguments.append('--cacheHost={}'.format(arguments.cacheHost)) - - build_arguments.append('build') - - build_arguments.append('--configurationPath={}'.format(arguments.configurationPath)) - build_arguments.append('--buildNumber={}'.format(arguments.buildNumber)) - build_arguments.append('--configuration={}'.format(arguments.configuration)) - build_arguments.append('--apsEnvironment=production') - build_arguments.append('--disableParallelSwiftmoduleGeneration') - build_arguments.append('--provisioningProfilesPath={}'.format(arguments.provisioningProfilesPath)) - - build_command = 'python3 ' + quote_args(build_arguments) - print('Running {}'.format(build_command)) - os.system(build_command) - - -if __name__ == '__main__': - parser = argparse.ArgumentParser(prog='build') - - parser.add_argument( - '--verbose', - action='store_true', - default=False, - help='Print debug info' - ) - - subparsers = parser.add_subparsers(dest='commandName', help='Commands') - - remote_build_parser = subparsers.add_parser('remote-build', help='Build the app using a remote environment.') - remote_build_parser.add_argument( - '--darwinContainersHost', - required=True, - type=str, - help='DarwinContainers host address.' - ) - remote_build_parser.add_argument( - '--configuration', - choices=[ - 'appcenter', - 'appstore', - 'reproducible' - ], - required=True, - help='Build configuration' - ) - remote_build_parser.add_argument( - '--bazelCacheHost', - required=False, - type=str, - help='Bazel remote cache host address.' - ) - - isolated_build_parser = subparsers.add_parser('isolated-build', help='Build the app inside an isolated environment.') - isolated_build_parser.add_argument( - '--certificatesPath', - required=False, - type=str, - help='Install codesigning certificates from the specified directory.' - ) - isolated_build_parser.add_argument( - '--provisioningProfilesPath', - required=True, - help=''' - Use codesigning provisioning profiles from a local directory. - ''', - metavar='command' - ) - isolated_build_parser.add_argument( - '--cacheHost', - required=False, - type=str, - help='Bazel cache host url.' - ) - isolated_build_parser.add_argument( - '--configurationPath', - help=''' - Path to a json containing build configuration. - See build-system/appstore-configuration.json for an example. - ''', - required=True, - metavar='path' - ) - isolated_build_parser.add_argument( - '--buildNumber', - required=True, - type=int, - help='Build number.', - metavar='number' - ) - isolated_build_parser.add_argument( - '--configuration', - type=str, - required=True, - help='Build configuration' - ) - - if len(sys.argv) < 2: - parser.print_help() - sys.exit(1) - - args = parser.parse_args() - - if args.commandName is None: - exit(0) - - if args.commandName == 'remote-build': - remote_build(darwin_containers_host=args.darwinContainersHost, bazel_cache_host=args.bazelCacheHost, configuration=args.configuration) - elif args.commandName == 'isolated-build': - isolated_build(arguments=args) - - From af75e84140f129a06ba251c5849138dd92aaafa1 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Thu, 11 Aug 2022 00:49:03 +0400 Subject: [PATCH 03/22] Adjust CI --- .gitlab-ci.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index aa19cd37cd..d8db66722a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -48,15 +48,14 @@ appstore_development: experimental_i: tags: - - ios_internal + - ios_experimental stage: build only: - experimental-3 except: - tags script: - - bash buildbox/build-telegram.sh appcenter-experimental - - bash buildbox/deploy-telegram.sh appcenter-experimental + - python3 build-system/Make/Make.py remote-build --darwinContainersHost="http://host.docker.internal:8650" --cacheHost="grpc://host.docker.internal:9092" --configurationPath=build-system/appstore-configuration.json --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=appstore --configuration=release_arm64 environment: name: experimental artifacts: From 48158082e7104031d24ecff9317ea2aa649080bf Mon Sep 17 00:00:00 2001 From: Ali <> Date: Thu, 11 Aug 2022 00:57:06 +0400 Subject: [PATCH 04/22] Fix git option --- build-system/Make/BuildConfiguration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-system/Make/BuildConfiguration.py b/build-system/Make/BuildConfiguration.py index 3029f6bf08..9aaf798a5c 100644 --- a/build-system/Make/BuildConfiguration.py +++ b/build-system/Make/BuildConfiguration.py @@ -129,7 +129,7 @@ def load_codesigning_data_from_git(working_dir, repo_url, branch, password, alwa os.makedirs(encrypted_working_dir, exist_ok=True) original_working_dir = os.getcwd() os.chdir(working_dir) - os.system('git -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null clone {repo_url} -b "{branch}" "{target_path}"'.format( + os.system('GIT_SSH_COMMAND="ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" git clone {repo_url} -b "{branch}" "{target_path}"'.format( repo_url=repo_url, branch=branch, target_path=encrypted_working_dir From a750799237d36e2d9477cae2f8c5e6850f96393f Mon Sep 17 00:00:00 2001 From: Ali <> Date: Thu, 11 Aug 2022 01:24:03 +0400 Subject: [PATCH 05/22] Allow passing checkout key --- build-system/Make/BuildConfiguration.py | 37 ++++++++++++++++++++----- build-system/Make/Make.py | 3 ++ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/build-system/Make/BuildConfiguration.py b/build-system/Make/BuildConfiguration.py index 9aaf798a5c..3192da172e 100644 --- a/build-system/Make/BuildConfiguration.py +++ b/build-system/Make/BuildConfiguration.py @@ -7,6 +7,11 @@ import plistlib from BuildEnvironment import run_executable_with_output +def check_run_system(command): + if os.system(command) != 0: + print('Command failed: {}'.format(command)) + sys.exit(1) + class BuildConfiguration: def __init__(self, bundle_id, @@ -112,24 +117,29 @@ def decrypt_codesigning_directory_recursively(source_base_path, destination_base decrypt_codesigning_directory_recursively(source_path, destination_path, password) -def load_codesigning_data_from_git(working_dir, repo_url, branch, password, always_fetch): +def load_codesigning_data_from_git(working_dir, repo_url, temp_key_path, branch, password, always_fetch): if not os.path.exists(working_dir): os.makedirs(working_dir, exist_ok=True) + ssh_command = 'ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null' + if temp_key_path is not None: + ssh_command += ' -i {}'.format(temp_key_path) + encrypted_working_dir = working_dir + '/encrypted' if os.path.exists(encrypted_working_dir): if always_fetch: original_working_dir = os.getcwd() os.chdir(encrypted_working_dir) - os.system('GIT_SSH_COMMAND="ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" git fetch') - os.system('git checkout "{branch}"'.format(branch=branch)) - os.system('GIT_SSH_COMMAND="ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" git pull') + check_run_system('GIT_SSH_COMMAND="{ssh_command}" git fetch'.format(ssh_command=ssh_command)) + check_run_system('git checkout "{branch}"'.format(branch=branch)) + check_run_system('GIT_SSH_COMMAND="{ssh_command}" git pull'.format(ssh_command=ssh_command)) os.chdir(original_working_dir) else: os.makedirs(encrypted_working_dir, exist_ok=True) original_working_dir = os.getcwd() os.chdir(working_dir) - os.system('GIT_SSH_COMMAND="ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" git clone {repo_url} -b "{branch}" "{target_path}"'.format( + check_run_system('GIT_SSH_COMMAND="{ssh_command}" git clone --depth=1 {repo_url} -b "{branch}" "{target_path}"'.format( + ssh_command=ssh_command, repo_url=repo_url, branch=branch, target_path=encrypted_working_dir @@ -208,8 +218,9 @@ class CodesigningSource: class GitCodesigningSource(CodesigningSource): - def __init__(self, repo_url, team_id, bundle_id, codesigning_type, password, always_fetch): + def __init__(self, repo_url, private_key, team_id, bundle_id, codesigning_type, password, always_fetch): self.repo_url = repo_url + self.private_key = private_key self.team_id = team_id self.bundle_id = bundle_id self.codesigning_type = codesigning_type @@ -218,7 +229,19 @@ class GitCodesigningSource(CodesigningSource): def load_data(self, working_dir): self.working_dir = working_dir - load_codesigning_data_from_git(working_dir=self.working_dir, repo_url=self.repo_url, branch=self.team_id, password=self.password, always_fetch=self.always_fetch) + temp_key_path = None + if self.private_key is not None: + temp_key_path = tempfile.mktemp() + with open(temp_key_path, 'w+') as file: + file.write(self.private_key) + if not self.private_key.endswith('\n'): + file.write('\n') + os.chmod(temp_key_path, 0o600) + + load_codesigning_data_from_git(working_dir=self.working_dir, repo_url=self.repo_url, temp_key_path=temp_key_path, branch=self.team_id, password=self.password, always_fetch=self.always_fetch) + + if temp_key_path is not None: + os.remove(temp_key_path) def copy_profiles_to_destination(self, destination_path): source_path = self.working_dir + '/decrypted/profiles/{}'.format(self.codesigning_type) diff --git a/build-system/Make/Make.py b/build-system/Make/Make.py index a392e31a71..ba7eed35b9 100644 --- a/build-system/Make/Make.py +++ b/build-system/Make/Make.py @@ -396,8 +396,11 @@ def resolve_codesigning(arguments, base_path, build_configuration, provisioning_ print('--gitCodesigningType is required if --gitCodesigningRepository is set') sys.exit(1) + private_key = os.getenv('TELEGRAM_CODESIGNING_PRIVATE_KEY') + profile_source = GitCodesigningSource( repo_url=arguments.gitCodesigningRepository, + private_key=private_key, team_id=build_configuration.team_id, bundle_id=build_configuration.bundle_id, codesigning_type=arguments.gitCodesigningType, From 18425f44fc3d6338186d5a6ca0984fa4498cff32 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Thu, 11 Aug 2022 01:31:24 +0400 Subject: [PATCH 06/22] Adjust pythonpath and silence warning --- .gitlab-ci.yml | 2 +- build-system/Make/BuildConfiguration.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d8db66722a..fa4cdd9d91 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -55,7 +55,7 @@ experimental_i: except: - tags script: - - python3 build-system/Make/Make.py remote-build --darwinContainersHost="http://host.docker.internal:8650" --cacheHost="grpc://host.docker.internal:9092" --configurationPath=build-system/appstore-configuration.json --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=appstore --configuration=release_arm64 + - PYTHONPATH="$PYTHONPATH:/darwin-containers" python3 build-system/Make/Make.py remote-build --darwinContainersHost="http://host.docker.internal:8650" --cacheHost="grpc://host.docker.internal:9092" --configurationPath=build-system/appstore-configuration.json --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=appstore --configuration=release_arm64 environment: name: experimental artifacts: diff --git a/build-system/Make/BuildConfiguration.py b/build-system/Make/BuildConfiguration.py index 3192da172e..e36ef0451d 100644 --- a/build-system/Make/BuildConfiguration.py +++ b/build-system/Make/BuildConfiguration.py @@ -107,7 +107,7 @@ def decrypt_codesigning_directory_recursively(source_base_path, destination_base source_path = source_base_path + '/' + file_name destination_path = destination_base_path + '/' + file_name if os.path.isfile(source_path): - os.system('openssl aes-256-cbc -md md5 -k "{password}" -in "{source_path}" -out "{destination_path}" -a -d'.format( + os.system('openssl aes-256-cbc -md md5 -k "{password}" -in "{source_path}" -out "{destination_path}" -a -d 2>/dev/null'.format( password=password, source_path=source_path, destination_path=destination_path From e34bd0167b9c6e78c40c55f29ac381909b57105c Mon Sep 17 00:00:00 2001 From: Ali <> Date: Thu, 11 Aug 2022 01:43:42 +0400 Subject: [PATCH 07/22] Create missing directory --- build-system/Make/RemoteBuild.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build-system/Make/RemoteBuild.py b/build-system/Make/RemoteBuild.py index bdca64501b..22cfd45a07 100644 --- a/build-system/Make/RemoteBuild.py +++ b/build-system/Make/RemoteBuild.py @@ -71,6 +71,10 @@ def remote_build(darwin_containers_host, bazel_cache_host, configuration, build_ source_dir = os.path.basename(base_dir) buildbox_dir = 'buildbox' + + transient_data_dir = '{}/transient-data'.format(buildbox_dir) + os.makedirs(transient_data_dir, exist_ok=True) + source_archive_path = '{buildbox_dir}/transient-data/source.tar'.format(buildbox_dir=buildbox_dir) if os.path.exists(source_archive_path): From cb75e86709d71adfcc42b83d850b7ec5753a19ff Mon Sep 17 00:00:00 2001 From: Ali <> Date: Thu, 11 Aug 2022 01:48:23 +0400 Subject: [PATCH 08/22] Pass cache host an environment variable --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fa4cdd9d91..6ecacf5d77 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -55,7 +55,7 @@ experimental_i: except: - tags script: - - PYTHONPATH="$PYTHONPATH:/darwin-containers" python3 build-system/Make/Make.py remote-build --darwinContainersHost="http://host.docker.internal:8650" --cacheHost="grpc://host.docker.internal:9092" --configurationPath=build-system/appstore-configuration.json --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=appstore --configuration=release_arm64 + - PYTHONPATH="$PYTHONPATH:/darwin-containers" python3 build-system/Make/Make.py remote-build --darwinContainersHost="http://host.docker.internal:8650" --cacheHost="TELEGRAM_BAZEL_CACHE_HOST" --configurationPath=build-system/appstore-configuration.json --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=appstore --configuration=release_arm64 environment: name: experimental artifacts: From f8cefa1683a4427da360507490370c2db0ebaf78 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Thu, 11 Aug 2022 01:50:46 +0400 Subject: [PATCH 09/22] Fix typo --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6ecacf5d77..1688793bdc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -55,7 +55,7 @@ experimental_i: except: - tags script: - - PYTHONPATH="$PYTHONPATH:/darwin-containers" python3 build-system/Make/Make.py remote-build --darwinContainersHost="http://host.docker.internal:8650" --cacheHost="TELEGRAM_BAZEL_CACHE_HOST" --configurationPath=build-system/appstore-configuration.json --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=appstore --configuration=release_arm64 + - PYTHONPATH="$PYTHONPATH:/darwin-containers" python3 build-system/Make/Make.py remote-build --darwinContainersHost="http://host.docker.internal:8650" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath=build-system/appstore-configuration.json --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=appstore --configuration=release_arm64 environment: name: experimental artifacts: From a2078467679a98b1c108721d1ca2bf15fc160df0 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Thu, 11 Aug 2022 02:29:15 +0400 Subject: [PATCH 10/22] Fix build --- .gitlab-ci.yml | 2 +- build-system/Make/BuildEnvironment.py | 5 ++++- build-system/Make/Make.py | 12 +++++++----- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1688793bdc..d4e7a17444 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -55,7 +55,7 @@ experimental_i: except: - tags script: - - PYTHONPATH="$PYTHONPATH:/darwin-containers" python3 build-system/Make/Make.py remote-build --darwinContainersHost="http://host.docker.internal:8650" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath=build-system/appstore-configuration.json --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=appstore --configuration=release_arm64 + - PYTHONPATH="$PYTHONPATH:/darwin-containers" python3 -u build-system/Make/Make.py remote-build --darwinContainersHost="http://host.docker.internal:8650" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath=build-system/appstore-configuration.json --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=appstore --configuration=release_arm64 environment: name: experimental artifacts: diff --git a/build-system/Make/BuildEnvironment.py b/build-system/Make/BuildEnvironment.py index 1f78fe7edd..db75c30f5e 100644 --- a/build-system/Make/BuildEnvironment.py +++ b/build-system/Make/BuildEnvironment.py @@ -28,7 +28,7 @@ def resolve_executable(program): return None -def run_executable_with_output(path, arguments, decode=True, input=None, stderr_to_stdout=True, check_result=False): +def run_executable_with_output(path, arguments, decode=True, input=None, stderr_to_stdout=True, print_command=False, check_result=False): executable_path = resolve_executable(path) if executable_path is None: raise Exception('Could not resolve {} to a valid executable file'.format(path)) @@ -37,6 +37,9 @@ def run_executable_with_output(path, arguments, decode=True, input=None, stderr_ if stderr_to_stdout: stderr_assignment = subprocess.STDOUT + if print_command: + print('Running {} {}'.format(executable_path, arguments)) + process = subprocess.Popen( [executable_path] + arguments, stdout=subprocess.PIPE, diff --git a/build-system/Make/Make.py b/build-system/Make/Make.py index ba7eed35b9..2443143251 100644 --- a/build-system/Make/Make.py +++ b/build-system/Make/Make.py @@ -554,7 +554,7 @@ def build(bazel, arguments): bazel_command_line.invoke_build() if arguments.outputBuildArtifactsPath is not None: - artifacts_path = arguments.outputBuildArtifactsPath + artifacts_path = os.path.abspath(arguments.outputBuildArtifactsPath) if os.path.exists(artifacts_path + '/Telegram.ipa'): os.remove(path) if os.path.exists(artifacts_path + '/DSYMs'): @@ -574,13 +574,15 @@ def build(bazel, arguments): dsym_paths = glob.glob('bazel-out/applebin_ios-ios_arm*-opt-ST-*/bin/Telegram/*.dSYM') for dsym_path in dsym_paths: file_name = os.path.basename(dsym_path) - shutil.copyfile(ipa_paths[0], artifacts_path + '/DSYMs/{}'.format(file_name)) + shutil.copytree(dsym_path, artifacts_path + '/DSYMs/{}'.format(file_name)) + previous_directory = os.getcwd() + os.chdir(artifacts_path) run_executable_with_output('zip', arguments=[ - '-9', '-r', - artifacts_path + '/Telegram.DSYMs.zip', - artifacts_path + '/DSYMs' + 'Telegram.DSYMs.zip', + './DSYMs' ], check_result=True) + os.chdir(previous_directory) shutil.rmtree(artifacts_path + '/DSYMs') From 5572a4e0300dc4ea2b6c9035201dee48ffd9e71b Mon Sep 17 00:00:00 2001 From: Ali <> Date: Thu, 11 Aug 2022 03:05:31 +0400 Subject: [PATCH 11/22] Add appcenter --- .gitlab-ci.yml | 1 + build-system/Make/BuildConfiguration.py | 7 +-- build-system/Make/BuildEnvironment.py | 6 +++ build-system/Make/DeployToAppCenter.py | 70 +++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 6 deletions(-) create mode 100644 build-system/Make/DeployToAppCenter.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d4e7a17444..ac4017d263 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -56,6 +56,7 @@ experimental_i: - tags script: - PYTHONPATH="$PYTHONPATH:/darwin-containers" python3 -u build-system/Make/Make.py remote-build --darwinContainersHost="http://host.docker.internal:8650" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath=build-system/appstore-configuration.json --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=appstore --configuration=release_arm64 + - python3 -u build-system/Make/DeployYoAppCenter.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/appcenter-configurations/appcenter-experimental.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip" environment: name: experimental artifacts: diff --git a/build-system/Make/BuildConfiguration.py b/build-system/Make/BuildConfiguration.py index e36ef0451d..c27e191205 100644 --- a/build-system/Make/BuildConfiguration.py +++ b/build-system/Make/BuildConfiguration.py @@ -5,12 +5,7 @@ import shutil import tempfile import plistlib -from BuildEnvironment import run_executable_with_output - -def check_run_system(command): - if os.system(command) != 0: - print('Command failed: {}'.format(command)) - sys.exit(1) +from BuildEnvironment import run_executable_with_output, check_run_system class BuildConfiguration: def __init__(self, diff --git a/build-system/Make/BuildEnvironment.py b/build-system/Make/BuildEnvironment.py index db75c30f5e..6fa5fe4ee9 100644 --- a/build-system/Make/BuildEnvironment.py +++ b/build-system/Make/BuildEnvironment.py @@ -83,6 +83,12 @@ def call_executable(arguments, use_clean_environment=True, check_result=True): subprocess.call(resolved_arguments, env=resolved_env) +def check_run_system(command): + if os.system(command) != 0: + print('Command failed: {}'.format(command)) + sys.exit(1) + + def get_bazel_version(bazel_path): command_result = run_executable_with_output(bazel_path, ['--version']).strip('\n') if not command_result.startswith('bazel '): diff --git a/build-system/Make/DeployToAppCenter.py b/build-system/Make/DeployToAppCenter.py new file mode 100644 index 0000000000..9fb82cbc42 --- /dev/null +++ b/build-system/Make/DeployToAppCenter.py @@ -0,0 +1,70 @@ +import os +import sys +import argparse +import json + +from BuildEnvironment import check_run_system + +def deploy_to_appcenter(args): + if not os.path.exists(args.configuration): + print('{} does not exist'.format(args.configuration)) + sys.exit(1) + if not os.path.exists(args.ipa): + print('{} does not exist'.format(args.ipa)) + sys.exit(1) + if args.dsyms is not None and not os.path.exists(args.dsyms): + print('{} does not exist'.format(args.dsyms)) + sys.exit(1) + + with open(args.configuration) as file: + configuration_dict = json.load(file) + required_keys = [ + 'username', + 'app_name', + 'api_token', + ] + for key in required_keys: + if key not in configuration_dict: + print('Configuration at {} does not contain {}'.format(args.configuration, key)) + + check_run_system('appcenter login --token {token}'.format(token=configuration_dict['api_token'])) + check_run_system('appcenter distribute release --app "{username}/{app_name}" -f "{ipa_path}" -g Internal'.format( + username=configuration_dict['username'], + app_name=configuration_dict['app_name'], + ipa_path=args.ipa, + + )) + if args.dsyms is not None: + check_run_system('appcenter crashes upload-symbols --app "{username}/{app_name}" --symbol "{dsym_path}"'.format( + username=configuration_dict['username'], + app_name=configuration_dict['app_name'], + dsym_path=args.dsyms + )) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(prog='deploy-appcenter') + + parser.add_argument( + '--configuration', + required=True, + help='Path to configuration json.' + ) + parser.add_argument( + '--ipa', + required=True, + help='Path to IPA.' + ) + parser.add_argument( + '--dsyms', + required=False, + help='Path to DSYMs.zip.' + ) + + if len(sys.argv) < 2: + parser.print_help() + sys.exit(1) + + args = parser.parse_args() + + deploy_to_appcenter(args) From c032aa320f87dbabb63878f58baf4d8d556d8b0a Mon Sep 17 00:00:00 2001 From: Ali <> Date: Thu, 11 Aug 2022 03:09:24 +0400 Subject: [PATCH 12/22] Fix typo --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ac4017d263..26e84d4925 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -56,7 +56,7 @@ experimental_i: - tags script: - PYTHONPATH="$PYTHONPATH:/darwin-containers" python3 -u build-system/Make/Make.py remote-build --darwinContainersHost="http://host.docker.internal:8650" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath=build-system/appstore-configuration.json --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=appstore --configuration=release_arm64 - - python3 -u build-system/Make/DeployYoAppCenter.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/appcenter-configurations/appcenter-experimental.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip" + - python3 -u build-system/Make/DeployToAppCenter.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/appcenter-configurations/appcenter-experimental.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip" environment: name: experimental artifacts: From 7c07ceb8b19959152f9fb588c68e7a9f2558863f Mon Sep 17 00:00:00 2001 From: Ali <> Date: Thu, 11 Aug 2022 21:12:03 +0400 Subject: [PATCH 13/22] Temp --- .gitlab-ci.yml | 16 ++--- build-system/Make/BuildConfiguration.py | 4 +- build-system/Make/DeployToAppStoreConnect.py | 62 ++++++++++++++++++++ 3 files changed, 73 insertions(+), 9 deletions(-) create mode 100644 build-system/Make/DeployToAppStoreConnect.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 26e84d4925..46308d2fb0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,8 +18,8 @@ internal: except: - tags script: - - bash buildbox/build-telegram.sh hockeyapp - - bash buildbox/deploy-telegram.sh hockeyapp + - PYTHONPATH="$PYTHONPATH:/darwin-containers" python3 -u build-system/Make/Make.py remote-build --darwinContainersHost="http://host.docker.internal:8650" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="$TELEGRAM_PRIVATE_DATA_PATH/build-configurations/enterprise-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=enterprise --configuration=release_arm64 + - python3 -u build-system/Make/DeployToAppCenter.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/appcenter-configurations/appcenter-internal.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip" environment: name: internal artifacts: @@ -37,8 +37,8 @@ appstore_development: except: - tags script: - - bash buildbox/build-telegram.sh appstore-development - - bash buildbox/deploy-telegram.sh appstore-development + - PYTHONPATH="$PYTHONPATH:/darwin-containers" python3 -u build-system/Make/Make.py remote-build --darwinContainersHost="http://host.docker.internal:8650" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="$TELEGRAM_PRIVATE_DATA_PATH/build-configurations/enterprise-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=enterprise --configuration=release_arm64 + - python3 -u build-system/Make/DeployToAppCenter.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/appcenter-configurations/appstore-development.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip" environment: name: appstore-development artifacts: @@ -55,7 +55,7 @@ experimental_i: except: - tags script: - - PYTHONPATH="$PYTHONPATH:/darwin-containers" python3 -u build-system/Make/Make.py remote-build --darwinContainersHost="http://host.docker.internal:8650" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath=build-system/appstore-configuration.json --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=appstore --configuration=release_arm64 + - PYTHONPATH="$PYTHONPATH:/darwin-containers" python3 -u build-system/Make/Make.py remote-build --darwinContainersHost="http://host.docker.internal:8650" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="$TELEGRAM_PRIVATE_DATA_PATH/build-configurations/enterprise-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=enterprise --configuration=release_arm64 - python3 -u build-system/Make/DeployToAppCenter.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/appcenter-configurations/appcenter-experimental.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip" environment: name: experimental @@ -73,8 +73,8 @@ experimental: except: - tags script: - - bash buildbox/build-telegram.sh appcenter-experimental-2 - - bash buildbox/deploy-telegram.sh appcenter-experimental-2 + - PYTHONPATH="$PYTHONPATH:/darwin-containers" python3 -u build-system/Make/Make.py remote-build --darwinContainersHost="http://host.docker.internal:8650" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="$TELEGRAM_PRIVATE_DATA_PATH/build-configurations/enterprise-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=enterprise --configuration=release_arm64 + - python3 -u build-system/Make/DeployToAppCenter.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/appcenter-configurations/appcenter-experimental2.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip" environment: name: experimental-2 artifacts: @@ -92,7 +92,7 @@ beta_testflight: except: - tags script: - - bash buildbox/build-telegram.sh appstore + - PYTHONPATH="$PYTHONPATH:/darwin-containers" python3 -u build-system/Make/Make.py remote-build --darwinContainersHost="http://host.docker.internal:8650" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="build-system/appstore-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=appstore --configuration=release_universal environment: name: testflight_llc artifacts: diff --git a/build-system/Make/BuildConfiguration.py b/build-system/Make/BuildConfiguration.py index c27e191205..9bd933f4ee 100644 --- a/build-system/Make/BuildConfiguration.py +++ b/build-system/Make/BuildConfiguration.py @@ -244,8 +244,10 @@ class GitCodesigningSource(CodesigningSource): def copy_certificates_to_destination(self, destination_path): source_path = None - if self.codesigning_type in ['adhoc', 'appstore', 'enterprise']: + if self.codesigning_type in ['adhoc', 'appstore']: source_path = self.working_dir + '/decrypted/certs/distribution' + elif self.codesigning_type == 'enterprise': + source_path = self.working_dir + '/decrypted/certs/enterprise' elif self.codesigning_type == 'development': source_path = self.working_dir + '/decrypted/certs/development' else: diff --git a/build-system/Make/DeployToAppStoreConnect.py b/build-system/Make/DeployToAppStoreConnect.py new file mode 100644 index 0000000000..83fd892729 --- /dev/null +++ b/build-system/Make/DeployToAppStoreConnect.py @@ -0,0 +1,62 @@ +import os +import sys +import argparse +import json + +from BuildEnvironment import check_run_system + +def deploy_to_appstore_connect(args): + if not os.path.exists(args.configuration): + print('{} does not exist'.format(args.configuration)) + sys.exit(1) + if not os.path.exists(args.ipa): + print('{} does not exist'.format(args.ipa)) + sys.exit(1) + + with open(args.configuration) as file: + configuration_dict = json.load(file) + required_keys = [ + 'username', + 'app_name', + 'api_token', + ] + for key in required_keys: + if key not in configuration_dict: + print('Configuration at {} does not contain {}'.format(args.configuration, key)) + + check_run_system('appcenter login --token {token}'.format(token=configuration_dict['api_token'])) + check_run_system('appcenter distribute release --app "{username}/{app_name}" -f "{ipa_path}" -g Internal'.format( + username=configuration_dict['username'], + app_name=configuration_dict['app_name'], + ipa_path=args.ipa, + + )) + if args.dsyms is not None: + check_run_system('appcenter crashes upload-symbols --app "{username}/{app_name}" --symbol "{dsym_path}"'.format( + username=configuration_dict['username'], + app_name=configuration_dict['app_name'], + dsym_path=args.dsyms + )) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(prog='deploy-appstore-connect') + + parser.add_argument( + '--configuration', + required=True, + help='Path to configuration json.' + ) + parser.add_argument( + '--ipa', + required=True, + help='Path to IPA.' + ) + + if len(sys.argv) < 2: + parser.print_help() + sys.exit(1) + + args = parser.parse_args() + + deploy_to_appstore_connect(args) From 8d4963a159dfbe2611bf89a5f5628788ab28a79b Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 16 Sep 2022 22:57:08 +0400 Subject: [PATCH 14/22] Update build scripts --- build-system/Make/BuildConfiguration.py | 40 +++++++++++++++++++++++++ build-system/Make/Make.py | 35 ++++++++++------------ 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/build-system/Make/BuildConfiguration.py b/build-system/Make/BuildConfiguration.py index 9bd933f4ee..fae62b4c3e 100644 --- a/build-system/Make/BuildConfiguration.py +++ b/build-system/Make/BuildConfiguration.py @@ -190,6 +190,36 @@ def copy_profiles_from_directory(source_path, destination_path, team_id, bundle_ print('Warning: skipping provisioning profile at {} with bundle_id {} (base_name {})'.format(file_path, profile_name, profile_base_name)) +def resolve_aps_environment_from_directory(source_path, team_id, bundle_id): + for file_name in os.listdir(source_path): + file_path = source_path + '/' + file_name + if os.path.isfile(file_path): + if not file_path.endswith('.mobileprovision'): + continue + + profile_data = run_executable_with_output('openssl', arguments=[ + 'smime', + '-inform', + 'der', + '-verify', + '-noverify', + '-in', + file_path + ], decode=False, stderr_to_stdout=False, check_result=True) + + profile_dict = plistlib.loads(profile_data) + profile_name = profile_dict['Entitlements']['application-identifier'] + + if profile_name.startswith(team_id + '.' + bundle_id): + profile_base_name = profile_name[len(team_id + '.' + bundle_id):] + if profile_base_name == '': + if 'aps-environment' not in profile_dict['Entitlements']: + print('Provisioning profile at {} does not include an aps-environment entitlement'.format(file_path)) + sys.exit(1) + return profile_dict['Entitlements']['aps-environment'] + return None + + def copy_certificates_from_directory(source_path, destination_path): for file_name in os.listdir(source_path): file_path = source_path + '/' + file_name @@ -208,6 +238,9 @@ class CodesigningSource: def copy_profiles_to_destination(self, destination_path): raise Exception('Not implemented') + def resolve_aps_environment(self): + raise Exception('Not implemented') + def copy_certificates_to_destination(self, destination_path): raise Exception('Not implemented') @@ -242,6 +275,10 @@ class GitCodesigningSource(CodesigningSource): source_path = self.working_dir + '/decrypted/profiles/{}'.format(self.codesigning_type) copy_profiles_from_directory(source_path=source_path, destination_path=destination_path, team_id=self.team_id, bundle_id=self.bundle_id) + def resolve_aps_environment(self): + source_path = self.working_dir + '/decrypted/profiles/{}'.format(self.codesigning_type) + return resolve_aps_environment_from_directory(source_path=source_path, team_id=self.team_id, bundle_id=self.bundle_id) + def copy_certificates_to_destination(self, destination_path): source_path = None if self.codesigning_type in ['adhoc', 'appstore']: @@ -267,5 +304,8 @@ class DirectoryCodesigningSource(CodesigningSource): def copy_profiles_to_destination(self, destination_path): copy_profiles_from_directory(source_path=self.directory_path + '/profiles', destination_path=destination_path, team_id=self.team_id, bundle_id=self.bundle_id) + def resolve_aps_environment(self): + return resolve_aps_environment_from_directory(source_path=self.directory_path + '/profiles', team_id=self.team_id, bundle_id=self.bundle_id) + def copy_certificates_to_destination(self, destination_path): copy_certificates_from_directory(source_path=self.directory_path + '/certs', destination_path=destination_path) diff --git a/build-system/Make/Make.py b/build-system/Make/Make.py index 2443143251..c1b8b7ecc0 100644 --- a/build-system/Make/Make.py +++ b/build-system/Make/Make.py @@ -15,6 +15,12 @@ from BazelLocation import locate_bazel from BuildConfiguration import CodesigningSource, GitCodesigningSource, DirectoryCodesigningSource, BuildConfiguration, build_configuration_from_json import RemoteBuild + +class ResolvedCodesigningData: + def __init__(self, aps_environment): + self.aps_environment = aps_environment + + class BazelCommandLine: def __init__(self, bazel, override_bazel_version, override_xcode_version, bazel_user_root): self.build_environment = BuildEnvironment( @@ -384,7 +390,7 @@ def clean(bazel, arguments): bazel_command_line.invoke_clean() -def resolve_codesigning(arguments, base_path, build_configuration, provisioning_profiles_path, additional_codesigning_output_path): +def resolve_codesigning(arguments, base_path, build_configuration, provisioning_profiles_path, additional_codesigning_output_path) -> ResolvedCodesigningData: profile_source = None if arguments.gitCodesigningRepository is not None: password = os.getenv('TELEGRAM_CODESIGNING_GIT_PASSWORD') @@ -427,8 +433,10 @@ def resolve_codesigning(arguments, base_path, build_configuration, provisioning_ profile_source.copy_profiles_to_destination(destination_path=additional_codesigning_output_path + '/profiles') profile_source.copy_certificates_to_destination(destination_path=additional_codesigning_output_path + '/certs') + return ResolvedCodesigningData(aps_environment=profile_source.resolve_aps_environment()) -def resolve_configuration(base_path, bazel_command_line: BazelCommandLine, arguments, aps_environment, additional_codesigning_output_path): + +def resolve_configuration(base_path, bazel_command_line: BazelCommandLine, arguments, additional_codesigning_output_path): configuration_repository_path = '{}/build-input/configuration-repository'.format(base_path) os.makedirs(configuration_repository_path, exist_ok=True) @@ -440,20 +448,23 @@ def resolve_configuration(base_path, bazel_command_line: BazelCommandLine, argum with open(configuration_repository_path + '/BUILD', 'w+') as file: pass - build_configuration.write_to_variables_file(aps_environment=aps_environment, path=configuration_repository_path + '/variables.bzl') - provisioning_path = '{}/provisioning'.format(configuration_repository_path) if os.path.exists(provisioning_path): shutil.rmtree(provisioning_path) os.makedirs(provisioning_path, exist_ok=True) - resolve_codesigning( + codesigning_data = resolve_codesigning( arguments=arguments, base_path=base_path, build_configuration=build_configuration, provisioning_profiles_path=provisioning_path, additional_codesigning_output_path=additional_codesigning_output_path ) + if codesigning_data.aps_environment is None: + print('Could not find a valid aps-environment entitlement in the provided provisioning profiles') + sys.exit(1) + + build_configuration.write_to_variables_file(aps_environment=codesigning_data.aps_environment, path=configuration_repository_path + '/variables.bzl') provisioning_profile_files = [] for file_name in os.listdir(provisioning_path): @@ -489,7 +500,6 @@ def generate_project(bazel, arguments): base_path=os.getcwd(), bazel_command_line=bazel_command_line, arguments=arguments, - aps_environment=arguments.apsEnvironment, additional_codesigning_output_path=None ) @@ -539,7 +549,6 @@ def build(bazel, arguments): base_path=os.getcwd(), bazel_command_line=bazel_command_line, arguments=arguments, - aps_environment=arguments.apsEnvironment, additional_codesigning_output_path=None ) @@ -603,7 +612,6 @@ def test(bazel, arguments): base_path=os.getcwd(), bazel_command_line=bazel_command_line, arguments=arguments, - aps_environment=arguments.apsEnvironment, additional_codesigning_output_path=None ) @@ -669,16 +677,6 @@ def add_codesigning_common_arguments(current_parser: argparse.ArgumentParser): def add_project_and_build_common_arguments(current_parser: argparse.ArgumentParser): - current_parser.add_argument( - '--apsEnvironment', - choices=[ - 'development', - 'production' - ], - required=True, - help='APNS environment', - ) - add_codesigning_common_arguments(current_parser=current_parser) @@ -928,7 +926,6 @@ if __name__ == '__main__': base_path=os.getcwd(), bazel_command_line=None, arguments=args, - aps_environment='production', additional_codesigning_output_path=remote_input_path ) From 7a73e1c8d63d2e9675cb9ab8ebb6de4b78d727da Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 16 Sep 2022 22:57:18 +0400 Subject: [PATCH 15/22] Update Xcode version --- versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versions.json b/versions.json index 64a33b1cca..06b565b23b 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { "app": "9.0", "bazel": "5.1.0", - "xcode": "13.4.1" + "xcode": "14.0" } From 1fa66aad9ff817826b49ed9785e8b393220ba1b6 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 16 Sep 2022 22:58:19 +0400 Subject: [PATCH 16/22] Fix mediabox fetch --- submodules/AccountContext/Sources/FetchMediaUtils.swift | 2 +- submodules/FetchManagerImpl/Sources/FetchManagerImpl.swift | 4 ++-- submodules/Postbox/Sources/MediaBox.swift | 6 ++++++ submodules/Postbox/Sources/MediaBoxFile.swift | 1 + .../TelegramCore/Sources/Network/MultipartFetch.swift | 1 + submodules/TelegramUI/Sources/PrefetchManager.swift | 5 +++++ 6 files changed, 16 insertions(+), 3 deletions(-) diff --git a/submodules/AccountContext/Sources/FetchMediaUtils.swift b/submodules/AccountContext/Sources/FetchMediaUtils.swift index 29bf1e3d89..0ce7cc87ea 100644 --- a/submodules/AccountContext/Sources/FetchMediaUtils.swift +++ b/submodules/AccountContext/Sources/FetchMediaUtils.swift @@ -57,7 +57,7 @@ public func messageMediaImageInteractiveFetched(fetchManager: FetchManager, mess if let range = range { ranges = RangeSet(range.lowerBound ..< range.upperBound) } else { - ranges = RangeSet(0 ..< Int64.max) + ranges = RangeSet(0 ..< Int64(Int32.max)) } return fetchManager.interactivelyFetched(category: .image, location: .chat(messageId.peerId), locationKey: .messageId(messageId), mediaReference: mediaReference, resourceReference: mediaReference.resourceReference(resource), ranges: ranges, statsCategory: .image, elevatedPriority: false, userInitiated: userInitiated, priority: priority, storeToDownloadsPeerType: storeToDownloadsPeerType) } diff --git a/submodules/FetchManagerImpl/Sources/FetchManagerImpl.swift b/submodules/FetchManagerImpl/Sources/FetchManagerImpl.swift index c998542496..a257e4a7fb 100644 --- a/submodules/FetchManagerImpl/Sources/FetchManagerImpl.swift +++ b/submodules/FetchManagerImpl/Sources/FetchManagerImpl.swift @@ -226,7 +226,7 @@ private final class FetchManagerCategoryContext { let storeManager = self.storeManager let parsedRanges: [(Range, MediaBoxFetchPriority)]? - if ranges == RangeSet(0 ..< Int64.max) { + if ranges == RangeSet(0 ..< Int64.max), !"".isEmpty { parsedRanges = nil } else { var resultRanges: [(Range, MediaBoxFetchPriority)] = [] @@ -325,7 +325,7 @@ private final class FetchManagerCategoryContext { isVideoPreload = true } - if count == 1 && isCompleteRange { + if count == 1 && isCompleteRange && !"".isEmpty { parsedRanges = nil } else { var resultRanges: [(Range, MediaBoxFetchPriority)] = [] diff --git a/submodules/Postbox/Sources/MediaBox.swift b/submodules/Postbox/Sources/MediaBox.swift index 61335eaf68..633603c788 100644 --- a/submodules/Postbox/Sources/MediaBox.swift +++ b/submodules/Postbox/Sources/MediaBox.swift @@ -574,6 +574,12 @@ public final class MediaBox { let disposable = MetaDisposable() self.dataQueue.async { + let paths = self.storePathsForId(resource.id) + if let _ = fileSize(paths.complete) { + subscriber.putCompletion() + return + } + guard let (fileContext, releaseContext) = self.fileContext(for: resource.id) else { subscriber.putCompletion() return diff --git a/submodules/Postbox/Sources/MediaBoxFile.swift b/submodules/Postbox/Sources/MediaBoxFile.swift index 3cf588079b..2486bf6ac5 100644 --- a/submodules/Postbox/Sources/MediaBoxFile.swift +++ b/submodules/Postbox/Sources/MediaBoxFile.swift @@ -1253,6 +1253,7 @@ final class MediaBoxFileContext { func fetched(range: Range, priority: MediaBoxFetchPriority, fetch: @escaping (Signal<[(Range, MediaBoxFetchPriority)], NoError>) -> Signal, error: @escaping (MediaResourceDataFetchError) -> Void, completed: @escaping () -> Void) -> Disposable { switch self.content { case .complete: + completed() return EmptyDisposable case let .partial(file): return file.fetched(range: range, priority: priority, fetch: fetch, error: error, completed: completed) diff --git a/submodules/TelegramCore/Sources/Network/MultipartFetch.swift b/submodules/TelegramCore/Sources/Network/MultipartFetch.swift index 1eb94cff1f..7c406cd4b1 100644 --- a/submodules/TelegramCore/Sources/Network/MultipartFetch.swift +++ b/submodules/TelegramCore/Sources/Network/MultipartFetch.swift @@ -715,6 +715,7 @@ private final class MultipartFetchManager { } let part = self.source.request(offset: downloadRange.lowerBound, limit: downloadRange.upperBound - downloadRange.lowerBound, tag: self.parameters?.tag, resource: self.resource, resourceReference: self.resourceReference, fileReference: self.fileReference, continueInBackground: self.continueInBackground) + //|> delay(5.0, queue: self.queue) |> deliverOn(self.queue) let partDisposable = MetaDisposable() self.fetchingParts[downloadRange.lowerBound] = (Int64(downloadRange.count), partDisposable) diff --git a/submodules/TelegramUI/Sources/PrefetchManager.swift b/submodules/TelegramUI/Sources/PrefetchManager.swift index 3ec707682b..a3cc9cc687 100644 --- a/submodules/TelegramUI/Sources/PrefetchManager.swift +++ b/submodules/TelegramUI/Sources/PrefetchManager.swift @@ -104,6 +104,11 @@ private final class PrefetchManagerInnerImpl { } private func updateOrderedPreloadMedia(_ items: [PrefetchMediaItem], automaticDownloadSettings: MediaAutoDownloadSettings, networkType: MediaAutoDownloadNetworkType) { + #if DEBUG + if "".isEmpty { + return + } + #endif var validIds = Set() var order: Int32 = 0 for mediaItem in items { From f05031cc9d1dae4de1188bc72ec78bde21759b0b Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 16 Sep 2022 22:59:00 +0400 Subject: [PATCH 17/22] Reload AppLock state on foreground --- submodules/AppLock/Sources/AppLock.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/submodules/AppLock/Sources/AppLock.swift b/submodules/AppLock/Sources/AppLock.swift index 49ec60fb5d..181f4ea57e 100644 --- a/submodules/AppLock/Sources/AppLock.swift +++ b/submodules/AppLock/Sources/AppLock.swift @@ -132,6 +132,10 @@ public final class AppLockContextImpl: AppLockContext { if !strongSelf.lastActiveValue { strongSelf.lastActiveValue = true strongSelf.lastActiveTimestamp = timestamp + + if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: strongSelf.rootPath))), let current = try? JSONDecoder().decode(LockState.self, from: data) { + strongSelf.currentStateValue = current + } } if let lastActiveTimestamp = strongSelf.lastActiveTimestamp { From ff22c7e9813580bf7068beead789149a94ceaefb Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 16 Sep 2022 22:59:16 +0400 Subject: [PATCH 18/22] [WIP] General UI improvements --- .../Sources/NotificationService.swift | 87 +++++++++++------ .../Sources/ContextActionsContainerNode.swift | 7 +- .../Navigation/NavigationController.swift | 1 + .../Sources/LocalAudioTranscription.swift | 3 + .../Sources/ReactionContextNode.swift | 12 +-- submodules/TelegramApi/Sources/Api0.swift | 7 +- submodules/TelegramApi/Sources/Api20.swift | 74 +++++++++++++-- submodules/TelegramApi/Sources/Api26.swift | 28 ++++-- submodules/TelegramCallsUI/BUILD | 1 + .../Sources/CallKitIntegration.swift | 55 +++++++---- .../Sources/PresentationCall.swift | 6 ++ .../Sources/PresentationCallManager.swift | 67 +++++++++---- .../State/AccountStateManagementUtils.swift | 16 +++- .../Sources/State/AccountViewTracker.swift | 2 +- .../Sources/State/ContactSyncManager.swift | 19 +++- ...onizeInstalledStickerPacksOperations.swift | 18 +++- .../Sources/State/Serialization.swift | 2 +- .../Sources/State/StickerManagement.swift | 16 +++- .../SynchronizeSavedStickersOperation.swift | 2 +- ...SyncCore_ImportableDeviceContactData.swift | 9 +- ...re_TelegramDeviceContactImportedData.swift | 11 ++- .../TelegramDeviceContactImportInfo.swift | 2 +- .../Contacts/TelegramEngineContacts.swift | 27 ++++++ .../Stickers/ImportStickers.swift | 16 +++- .../Stickers/LoadedStickerPack.swift | 16 +++- .../TelegramEngine/Stickers/StickerPack.swift | 2 +- .../Stickers/StickerSetInstallation.swift | 18 +++- .../TelegramUI/Sources/AppDelegate.swift | 95 +++++++++++-------- .../TelegramUI/Sources/ChatController.swift | 7 +- .../ChatReportPeerTitlePanelNode.swift | 5 +- .../TelegramUI/Sources/ChatTitleView.swift | 6 +- .../Sources/DeviceContactDataManager.swift | 7 +- .../Sources/PeerInfo/PeerInfoHeaderNode.swift | 6 +- .../UIViewController+Navigation.m | 18 +++- 34 files changed, 497 insertions(+), 171 deletions(-) diff --git a/Telegram/NotificationService/Sources/NotificationService.swift b/Telegram/NotificationService/Sources/NotificationService.swift index 3dc028bb4b..44bba89c8b 100644 --- a/Telegram/NotificationService/Sources/NotificationService.swift +++ b/Telegram/NotificationService/Sources/NotificationService.swift @@ -464,7 +464,7 @@ private struct NotificationContent: CustomStringConvertible { return string } - mutating func addSenderInfo(mediaBox: MediaBox, accountPeerId: PeerId, peer: Peer) { + mutating func addSenderInfo(mediaBox: MediaBox, accountPeerId: PeerId, peer: Peer, contactIdentifier: String?) { if #available(iOS 15.0, *) { let image = peerAvatar(mediaBox: mediaBox, accountPeerId: accountPeerId, peer: peer) @@ -483,7 +483,7 @@ private struct NotificationContent: CustomStringConvertible { nameComponents: personNameComponents, displayName: displayName, image: image, - contactIdentifier: nil, + contactIdentifier: contactIdentifier, customIdentifier: "\(peer.id.toInt64())", isMe: false, suggestionType: .none @@ -818,6 +818,7 @@ private final class NotificationServiceHandler { var updates: String var accountId: Int64 var peer: EnginePeer? + var localContactId: String? } var callData: CallData? @@ -1033,35 +1034,51 @@ private final class NotificationServiceHandler { if let action = action { switch action { case let .call(callData): - let voipPayload: [AnyHashable: Any] = [ - "call_id": "\(callData.id)", - "call_ah": "\(callData.accessHash)", - "from_id": "\(callData.fromId.id._internalGetInt64Value())", - "updates": callData.updates, - "accountId": "\(callData.accountId)" - ] - - if #available(iOS 14.5, *), voiceCallSettings.enableSystemIntegration { - Logger.shared.log("NotificationService \(episode)", "Will report voip notification") + if let stateManager = strongSelf.stateManager { let content = NotificationContent(isLockedMessage: nil) updateCurrentContent(content) - CXProvider.reportNewIncomingVoIPPushPayload(voipPayload, completion: { error in - Logger.shared.log("NotificationService \(episode)", "Did report voip notification, error: \(String(describing: error))") + let _ = (stateManager.postbox.transaction { transaction -> String? in + if let peer = transaction.getPeer(callData.fromId) as? TelegramUser { + return peer.phone + } else { + return nil + } + }).start(next: { phoneNumber in + var voipPayload: [AnyHashable: Any] = [ + "call_id": "\(callData.id)", + "call_ah": "\(callData.accessHash)", + "from_id": "\(callData.fromId.id._internalGetInt64Value())", + "updates": callData.updates, + "accountId": "\(callData.accountId)" + ] + if let phoneNumber = phoneNumber { + voipPayload["phoneNumber"] = phoneNumber + } - completed() + if #available(iOS 14.5, *), voiceCallSettings.enableSystemIntegration { + Logger.shared.log("NotificationService \(episode)", "Will report voip notification") + let content = NotificationContent(isLockedMessage: nil) + updateCurrentContent(content) + + CXProvider.reportNewIncomingVoIPPushPayload(voipPayload, completion: { error in + Logger.shared.log("NotificationService \(episode)", "Did report voip notification, error: \(String(describing: error))") + + completed() + }) + } else { + var content = NotificationContent(isLockedMessage: nil) + if let peer = callData.peer { + content.title = peer.debugDisplayTitle + content.body = incomingCallMessage + } else { + content.body = "Incoming Call" + } + + updateCurrentContent(content) + completed() + } }) - } else { - var content = NotificationContent(isLockedMessage: nil) - if let peer = callData.peer { - content.title = peer.debugDisplayTitle - content.body = incomingCallMessage - } else { - content.body = "Incoming Call" - } - - updateCurrentContent(content) - completed() } case .logout: Logger.shared.log("NotificationService \(episode)", "Will logout") @@ -1334,7 +1351,23 @@ private final class NotificationServiceHandler { if let interactionAuthorId = interactionAuthorId { if inAppNotificationSettings.displayNameOnLockscreen, let peer = transaction.getPeer(interactionAuthorId) { - content.addSenderInfo(mediaBox: stateManager.postbox.mediaBox, accountPeerId: stateManager.accountPeerId, peer: peer) + var foundLocalId: String? + transaction.enumerateDeviceContactImportInfoItems({ _, value in + if let value = value as? TelegramDeviceContactImportedData { + switch value { + case let .imported(data, _, peerId): + if peerId == interactionAuthorId { + foundLocalId = data.localIdentifiers.first + return false + } + default: + break + } + } + return true + }) + + content.addSenderInfo(mediaBox: stateManager.postbox.mediaBox, accountPeerId: stateManager.accountPeerId, peer: peer, contactIdentifier: foundLocalId) } } diff --git a/submodules/ContextUI/Sources/ContextActionsContainerNode.swift b/submodules/ContextUI/Sources/ContextActionsContainerNode.swift index e4c1dc66ed..8802377b5e 100644 --- a/submodules/ContextUI/Sources/ContextActionsContainerNode.swift +++ b/submodules/ContextUI/Sources/ContextActionsContainerNode.swift @@ -482,7 +482,7 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode { for node in nodes { other.addSubnode(node) - node.layer.animateAlpha(from: node.alpha, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak node] _ in + node.layer.animateAlpha(from: node.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak node] _ in node?.removeFromSupernode() }) } @@ -491,12 +491,11 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode { func animateContentIn() { let nodes: [ASDisplayNode] = [ self.textNode.textNode, - self.iconNode, - self.placeholderNode + self.iconNode ] for node in nodes { - node.layer.animateAlpha(from: 0.0, to: node.alpha, duration: 0.2) + node.layer.animateAlpha(from: 0.0, to: node.alpha, duration: 0.25) } } diff --git a/submodules/Display/Source/Navigation/NavigationController.swift b/submodules/Display/Source/Navigation/NavigationController.swift index 97047ae59e..5dacbdd5ea 100644 --- a/submodules/Display/Source/Navigation/NavigationController.swift +++ b/submodules/Display/Source/Navigation/NavigationController.swift @@ -378,6 +378,7 @@ open class NavigationController: UINavigationController, ContainableController, let belowKeyboardOverlayLayout = layout var globalOverlayLayout = layout + globalOverlayLayout.inputHeight = nil if let globalOverlayBelowKeyboardContainerParent = self.globalOverlayBelowKeyboardContainerParent { if globalOverlayBelowKeyboardContainerParent.view.superview != self.displayNode.view { diff --git a/submodules/Media/LocalAudioTranscription/Sources/LocalAudioTranscription.swift b/submodules/Media/LocalAudioTranscription/Sources/LocalAudioTranscription.swift index 51244ea2e5..950b445d54 100644 --- a/submodules/Media/LocalAudioTranscription/Sources/LocalAudioTranscription.swift +++ b/submodules/Media/LocalAudioTranscription/Sources/LocalAudioTranscription.swift @@ -53,6 +53,9 @@ private func transcribeAudio(path: String, locale: String) -> Signal() diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 24dfbba406..e3d8f921fc 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -730,10 +730,11 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1660637285] = { return Api.StatsGroupTopPoster.parse_statsGroupTopPoster($0) } dict[-875679776] = { return Api.StatsPercentValue.parse_statsPercentValue($0) } dict[1202287072] = { return Api.StatsURL.parse_statsURL($0) } + dict[-50416996] = { return Api.StickerKeyword.parse_stickerKeyword($0) } dict[313694676] = { return Api.StickerPack.parse_stickerPack($0) } dict[768691932] = { return Api.StickerSet.parse_stickerSet($0) } dict[1678812626] = { return Api.StickerSetCovered.parse_stickerSetCovered($0) } - dict[451763941] = { return Api.StickerSetCovered.parse_stickerSetFullCovered($0) } + dict[1087454222] = { return Api.StickerSetCovered.parse_stickerSetFullCovered($0) } dict[872932635] = { return Api.StickerSetCovered.parse_stickerSetMultiCovered($0) } dict[-1609668650] = { return Api.Theme.parse_theme($0) } dict[-94849324] = { return Api.ThemeSettings.parse_themeSettings($0) } @@ -1029,7 +1030,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1802240206] = { return Api.messages.SentEncryptedMessage.parse_sentEncryptedFile($0) } dict[1443858741] = { return Api.messages.SentEncryptedMessage.parse_sentEncryptedMessage($0) } dict[1705297877] = { return Api.messages.SponsoredMessages.parse_sponsoredMessages($0) } - dict[-1240849242] = { return Api.messages.StickerSet.parse_stickerSet($0) } + dict[1846886166] = { return Api.messages.StickerSet.parse_stickerSet($0) } dict[-738646805] = { return Api.messages.StickerSet.parse_stickerSetNotModified($0) } dict[904138920] = { return Api.messages.StickerSetInstallResult.parse_stickerSetInstallResultArchive($0) } dict[946083368] = { return Api.messages.StickerSetInstallResult.parse_stickerSetInstallResultSuccess($0) } @@ -1584,6 +1585,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.StatsURL: _1.serialize(buffer, boxed) + case let _1 as Api.StickerKeyword: + _1.serialize(buffer, boxed) case let _1 as Api.StickerPack: _1.serialize(buffer, boxed) case let _1 as Api.StickerSet: diff --git a/submodules/TelegramApi/Sources/Api20.swift b/submodules/TelegramApi/Sources/Api20.swift index fb4e9a17f9..ce4602c150 100644 --- a/submodules/TelegramApi/Sources/Api20.swift +++ b/submodules/TelegramApi/Sources/Api20.swift @@ -1,3 +1,49 @@ +public extension Api { + enum StickerKeyword: TypeConstructorDescription { + case stickerKeyword(documentId: Int64, keyword: [String]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .stickerKeyword(let documentId, let keyword): + if boxed { + buffer.appendInt32(-50416996) + } + serializeInt64(documentId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(keyword.count)) + for item in keyword { + serializeString(item, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .stickerKeyword(let documentId, let keyword): + return ("stickerKeyword", [("documentId", String(describing: documentId)), ("keyword", String(describing: keyword))]) + } + } + + public static func parse_stickerKeyword(_ reader: BufferReader) -> StickerKeyword? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [String]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.StickerKeyword.stickerKeyword(documentId: _1!, keyword: _2!) + } + else { + return nil + } + } + + } +} public extension Api { enum StickerPack: TypeConstructorDescription { case stickerPack(emoticon: String, documents: [Int64]) @@ -133,7 +179,7 @@ public extension Api { public extension Api { enum StickerSetCovered: TypeConstructorDescription { case stickerSetCovered(set: Api.StickerSet, cover: Api.Document) - case stickerSetFullCovered(set: Api.StickerSet, packs: [Api.StickerPack], documents: [Api.Document]) + case stickerSetFullCovered(set: Api.StickerSet, packs: [Api.StickerPack], keywords: [Api.StickerKeyword], documents: [Api.Document]) case stickerSetMultiCovered(set: Api.StickerSet, covers: [Api.Document]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { @@ -145,9 +191,9 @@ public extension Api { set.serialize(buffer, true) cover.serialize(buffer, true) break - case .stickerSetFullCovered(let set, let packs, let documents): + case .stickerSetFullCovered(let set, let packs, let keywords, let documents): if boxed { - buffer.appendInt32(451763941) + buffer.appendInt32(1087454222) } set.serialize(buffer, true) buffer.appendInt32(481674261) @@ -156,6 +202,11 @@ public extension Api { item.serialize(buffer, true) } buffer.appendInt32(481674261) + buffer.appendInt32(Int32(keywords.count)) + for item in keywords { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) buffer.appendInt32(Int32(documents.count)) for item in documents { item.serialize(buffer, true) @@ -179,8 +230,8 @@ public extension Api { switch self { case .stickerSetCovered(let set, let cover): return ("stickerSetCovered", [("set", String(describing: set)), ("cover", String(describing: cover))]) - case .stickerSetFullCovered(let set, let packs, let documents): - return ("stickerSetFullCovered", [("set", String(describing: set)), ("packs", String(describing: packs)), ("documents", String(describing: documents))]) + case .stickerSetFullCovered(let set, let packs, let keywords, let documents): + return ("stickerSetFullCovered", [("set", String(describing: set)), ("packs", String(describing: packs)), ("keywords", String(describing: keywords)), ("documents", String(describing: documents))]) case .stickerSetMultiCovered(let set, let covers): return ("stickerSetMultiCovered", [("set", String(describing: set)), ("covers", String(describing: covers))]) } @@ -213,15 +264,20 @@ public extension Api { if let _ = reader.readInt32() { _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self) } - var _3: [Api.Document]? + var _3: [Api.StickerKeyword]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerKeyword.self) + } + var _4: [Api.Document]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.StickerSetCovered.stickerSetFullCovered(set: _1!, packs: _2!, documents: _3!) + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.StickerSetCovered.stickerSetFullCovered(set: _1!, packs: _2!, keywords: _3!, documents: _4!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api26.swift b/submodules/TelegramApi/Sources/Api26.swift index ca6c814a2d..77b921c3f0 100644 --- a/submodules/TelegramApi/Sources/Api26.swift +++ b/submodules/TelegramApi/Sources/Api26.swift @@ -638,14 +638,14 @@ public extension Api.messages { } public extension Api.messages { enum StickerSet: TypeConstructorDescription { - case stickerSet(set: Api.StickerSet, packs: [Api.StickerPack], documents: [Api.Document]) + case stickerSet(set: Api.StickerSet, packs: [Api.StickerPack], keywords: [Api.StickerKeyword], documents: [Api.Document]) case stickerSetNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .stickerSet(let set, let packs, let documents): + case .stickerSet(let set, let packs, let keywords, let documents): if boxed { - buffer.appendInt32(-1240849242) + buffer.appendInt32(1846886166) } set.serialize(buffer, true) buffer.appendInt32(481674261) @@ -654,6 +654,11 @@ public extension Api.messages { item.serialize(buffer, true) } buffer.appendInt32(481674261) + buffer.appendInt32(Int32(keywords.count)) + for item in keywords { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) buffer.appendInt32(Int32(documents.count)) for item in documents { item.serialize(buffer, true) @@ -670,8 +675,8 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .stickerSet(let set, let packs, let documents): - return ("stickerSet", [("set", String(describing: set)), ("packs", String(describing: packs)), ("documents", String(describing: documents))]) + case .stickerSet(let set, let packs, let keywords, let documents): + return ("stickerSet", [("set", String(describing: set)), ("packs", String(describing: packs)), ("keywords", String(describing: keywords)), ("documents", String(describing: documents))]) case .stickerSetNotModified: return ("stickerSetNotModified", []) } @@ -686,15 +691,20 @@ public extension Api.messages { if let _ = reader.readInt32() { _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self) } - var _3: [Api.Document]? + var _3: [Api.StickerKeyword]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerKeyword.self) + } + var _4: [Api.Document]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.StickerSet.stickerSet(set: _1!, packs: _2!, documents: _3!) + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.messages.StickerSet.stickerSet(set: _1!, packs: _2!, keywords: _3!, documents: _4!) } else { return nil diff --git a/submodules/TelegramCallsUI/BUILD b/submodules/TelegramCallsUI/BUILD index 92f2c01c62..dbd5bc8794 100644 --- a/submodules/TelegramCallsUI/BUILD +++ b/submodules/TelegramCallsUI/BUILD @@ -104,6 +104,7 @@ swift_library( "//submodules/Components/UndoPanelComponent:UndoPanelComponent", "//submodules/Components/HierarchyTrackingLayer:HierarchyTrackingLayer", "//submodules/PeerInfoUI/CreateExternalMediaStreamScreen:CreateExternalMediaStreamScreen", + "//submodules/PhoneNumberFormat:PhoneNumberFormat", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramCallsUI/Sources/CallKitIntegration.swift b/submodules/TelegramCallsUI/Sources/CallKitIntegration.swift index edef84fe11..530ba5f7ad 100644 --- a/submodules/TelegramCallsUI/Sources/CallKitIntegration.swift +++ b/submodules/TelegramCallsUI/Sources/CallKitIntegration.swift @@ -41,7 +41,7 @@ public final class CallKitIntegration { } func setup( - startCall: @escaping (AccountContext, UUID, String, Bool) -> Signal, + startCall: @escaping (AccountContext, UUID, EnginePeer.Id?, String, Bool) -> Signal, answerCall: @escaping (UUID) -> Void, endCall: @escaping (UUID) -> Signal, setCallMuted: @escaping (UUID, Bool) -> Void, @@ -56,22 +56,17 @@ public final class CallKitIntegration { if !CallKitIntegration.isAvailable { return nil } - - #if targetEnvironment(simulator) - return nil - #else if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { } else { return nil } - #endif } - func startCall(context: AccountContext, peerId: PeerId, isVideo: Bool, displayTitle: String) { + func startCall(context: AccountContext, peerId: PeerId, phoneNumber: String?, localContactId: String?, isVideo: Bool, displayTitle: String) { if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { - (sharedProviderDelegate as? CallKitProviderDelegate)?.startCall(context: context, peerId: peerId, isVideo: isVideo, displayTitle: displayTitle) - self.donateIntent(peerId: peerId, displayTitle: displayTitle) + (sharedProviderDelegate as? CallKitProviderDelegate)?.startCall(context: context, peerId: peerId, phoneNumber: phoneNumber, isVideo: isVideo, displayTitle: displayTitle) + self.donateIntent(peerId: peerId, displayTitle: displayTitle, localContactId: localContactId) } } @@ -87,9 +82,9 @@ public final class CallKitIntegration { } } - public func reportIncomingCall(uuid: UUID, stableId: Int64, handle: String, isVideo: Bool, displayTitle: String, completion: ((NSError?) -> Void)?) { + public func reportIncomingCall(uuid: UUID, stableId: Int64, handle: String, phoneNumber: String?, isVideo: Bool, displayTitle: String, completion: ((NSError?) -> Void)?) { if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { - (sharedProviderDelegate as? CallKitProviderDelegate)?.reportIncomingCall(uuid: uuid, stableId: stableId, handle: handle, isVideo: isVideo, displayTitle: displayTitle, completion: completion) + (sharedProviderDelegate as? CallKitProviderDelegate)?.reportIncomingCall(uuid: uuid, stableId: stableId, handle: handle, phoneNumber: phoneNumber, isVideo: isVideo, displayTitle: displayTitle, completion: completion) } } @@ -99,10 +94,10 @@ public final class CallKitIntegration { } } - private func donateIntent(peerId: PeerId, displayTitle: String) { + private func donateIntent(peerId: PeerId, displayTitle: String, localContactId: String?) { if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { - let handle = INPersonHandle(value: "tg\(peerId.id)", type: .unknown) - let contact = INPerson(personHandle: handle, nameComponents: nil, displayName: displayTitle, image: nil, contactIdentifier: nil, customIdentifier: "tg\(peerId.id)") + let handle = INPersonHandle(value: "tg\(peerId.id._internalGetInt64Value())", type: .unknown) + let contact = INPerson(personHandle: handle, nameComponents: nil, displayName: displayTitle, image: nil, contactIdentifier: localContactId, customIdentifier: "tg\(peerId.id._internalGetInt64Value())") let intent = INStartAudioCallIntent(contacts: [contact]) @@ -122,8 +117,9 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate { private var currentStartCallAccount: (UUID, AccountContext)? private var alreadyReportedIncomingCalls = Set() + private var uuidToPeerIdMapping: [UUID: EnginePeer.Id] = [:] - private var startCall: ((AccountContext, UUID, String, Bool) -> Signal)? + private var startCall: ((AccountContext, UUID, EnginePeer.Id?, String, Bool) -> Signal)? private var answerCall: ((UUID) -> Void)? private var endCall: ((UUID) -> Signal)? private var setCallMuted: ((UUID, Bool) -> Void)? @@ -141,7 +137,7 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate { self.provider.setDelegate(self, queue: nil) } - func setup(audioSessionActivePromise: ValuePromise, startCall: @escaping (AccountContext, UUID, String, Bool) -> Signal, answerCall: @escaping (UUID) -> Void, endCall: @escaping (UUID) -> Signal, setCallMuted: @escaping (UUID, Bool) -> Void, audioSessionActivationChanged: @escaping (Bool) -> Void) { + func setup(audioSessionActivePromise: ValuePromise, startCall: @escaping (AccountContext, UUID, EnginePeer.Id?, String, Bool) -> Signal, answerCall: @escaping (UUID) -> Void, endCall: @escaping (UUID) -> Signal, setCallMuted: @escaping (UUID, Bool) -> Void, audioSessionActivationChanged: @escaping (Bool) -> Void) { self.audioSessionActivePromise = audioSessionActivePromise self.startCall = startCall self.answerCall = answerCall @@ -189,10 +185,18 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate { self.requestTransaction(transaction) } - func startCall(context: AccountContext, peerId: PeerId, isVideo: Bool, displayTitle: String) { + func startCall(context: AccountContext, peerId: PeerId, phoneNumber: String?, isVideo: Bool, displayTitle: String) { let uuid = UUID() self.currentStartCallAccount = (uuid, context) - let handle = CXHandle(type: .generic, value: "\(peerId.id._internalGetInt64Value())") + let handle: CXHandle + if let phoneNumber = phoneNumber { + handle = CXHandle(type: .phoneNumber, value: phoneNumber) + } else { + handle = CXHandle(type: .generic, value: "\(peerId.id._internalGetInt64Value())") + } + + self.uuidToPeerIdMapping[uuid] = peerId + let startCallAction = CXStartCallAction(call: uuid, handle: handle) startCallAction.contactIdentifier = displayTitle @@ -212,7 +216,7 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate { }) } - func reportIncomingCall(uuid: UUID, stableId: Int64, handle: String, isVideo: Bool, displayTitle: String, completion: ((NSError?) -> Void)?) { + func reportIncomingCall(uuid: UUID, stableId: Int64, handle: String, phoneNumber: String?, isVideo: Bool, displayTitle: String, completion: ((NSError?) -> Void)?) { if self.alreadyReportedIncomingCalls.contains(uuid) { completion?(nil) return @@ -220,7 +224,13 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate { self.alreadyReportedIncomingCalls.insert(uuid) let update = CXCallUpdate() - update.remoteHandle = CXHandle(type: .generic, value: handle) + let nativeHandle: CXHandle + if let phoneNumber = phoneNumber { + nativeHandle = CXHandle(type: .phoneNumber, value: phoneNumber) + } else { + nativeHandle = CXHandle(type: .generic, value: handle) + } + update.remoteHandle = nativeHandle update.localizedCallerName = displayTitle update.supportsHolding = false update.supportsGrouping = false @@ -252,7 +262,10 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate { self.currentStartCallAccount = nil let disposable = MetaDisposable() self.disposableSet.add(disposable) - disposable.set((startCall(context, action.callUUID, action.handle.value, action.isVideo) + + let peerId = self.uuidToPeerIdMapping[action.callUUID] + + disposable.set((startCall(context, action.callUUID, peerId, action.handle.value, action.isVideo) |> deliverOnMainQueue |> afterDisposed { [weak self, weak disposable] in if let strongSelf = self, let disposable = disposable { diff --git a/submodules/TelegramCallsUI/Sources/PresentationCall.swift b/submodules/TelegramCallsUI/Sources/PresentationCall.swift index 91c8bf2189..e877e21ebb 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCall.swift @@ -13,6 +13,7 @@ import DeviceAccess import UniversalMediaPlayer import AccountContext import DeviceProximity +import PhoneNumberFormat final class PresentationCallToneRenderer { let queue: Queue @@ -607,10 +608,15 @@ public final class PresentationCallImpl: PresentationCall { if previous == nil || previousControl == nil { if !self.reportedIncomingCall, let stableId = sessionState.stableId { self.reportedIncomingCall = true + var phoneNumber: String? + if let peer = self.peer as? TelegramUser, let phone = peer.phone { + phoneNumber = formatPhoneNumber(phone) + } self.callKitIntegration?.reportIncomingCall( uuid: self.internalId, stableId: stableId, handle: "\(self.peerId.id._internalGetInt64Value())", + phoneNumber: phoneNumber, isVideo: sessionState.type == .video, displayTitle: self.peer?.debugDisplayTitle ?? "Unknown", completion: { [weak self] error in diff --git a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift index 1b8988d4ab..99f57fb5fe 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift @@ -11,6 +11,7 @@ import TelegramVoip import TelegramUIPreferences import AccountContext import CallKit +import PhoneNumberFormat private func callKitIntegrationIfEnabled(_ integration: CallKitIntegration?, settings: VoiceCallSettings?) -> CallKitIntegration? { let enabled = settings?.enableSystemIntegration ?? true @@ -128,16 +129,16 @@ public final class PresentationCallManagerImpl: PresentationCallManager { self.isMediaPlaying = isMediaPlaying self.resumeMediaPlayback = resumeMediaPlayback - var startCallImpl: ((AccountContext, UUID, String, Bool) -> Signal)? + var startCallImpl: ((AccountContext, UUID, EnginePeer.Id?, String, Bool) -> Signal)? var answerCallImpl: ((UUID) -> Void)? var endCallImpl: ((UUID) -> Signal)? var setCallMutedImpl: ((UUID, Bool) -> Void)? var audioSessionActivationChangedImpl: ((Bool) -> Void)? self.callKitIntegration = CallKitIntegration.shared - self.callKitIntegration?.setup(startCall: { context, uuid, handle, isVideo in + self.callKitIntegration?.setup(startCall: { context, uuid, maybePeerId, handle, isVideo in if let startCallImpl = startCallImpl { - return startCallImpl(context, uuid, handle, isVideo) + return startCallImpl(context, uuid, maybePeerId, handle, isVideo) } else { return .single(false) } @@ -215,16 +216,26 @@ public final class PresentationCallManagerImpl: PresentationCallManager { self?.ringingStatesUpdated(ringingStates, enableCallKit: enableCallKit) }) - startCallImpl = { [weak self] context, uuid, handle, isVideo in - if let strongSelf = self, let userId = Int64(handle) { - return strongSelf.startCall(context: context, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), isVideo: isVideo, internalId: uuid) - |> take(1) - |> map { result -> Bool in - return result - } - } else { + startCallImpl = { [weak self] context, uuid, maybePeerId, handle, isVideo in + guard let strongSelf = self else { return .single(false) } + + var peerId: PeerId? + if let maybePeerId = maybePeerId { + peerId = maybePeerId + } else if let userId = Int64(handle) { + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) + } + guard let peerId = peerId else { + return .single(false) + } + + return strongSelf.startCall(context: context, peerId: peerId, isVideo: isVideo, internalId: uuid) + |> take(1) + |> map { result -> Bool in + return result + } } answerCallImpl = { [weak self] uuid in @@ -398,19 +409,39 @@ public final class PresentationCallManagerImpl: PresentationCallManager { |> runOn(Queue.mainQueue()) let postbox = context.account.postbox strongSelf.startCallDisposable.set((accessEnabledSignal - |> mapToSignal { accessEnabled -> Signal in + |> mapToSignal { accessEnabled -> Signal<(Peer?, String?), NoError> in if !accessEnabled { - return .single(nil) + return .single((nil, nil)) + } + return postbox.transaction { transaction -> (Peer?, String?) in + var foundLocalId: String? + transaction.enumerateDeviceContactImportInfoItems({ _, value in + if let value = value as? TelegramDeviceContactImportedData { + switch value { + case let .imported(data, _, importedPeerId): + if importedPeerId == peerId { + foundLocalId = data.localIdentifiers.first + return false + } + default: + break + } + } + return true + }) + + return (transaction.getPeer(peerId), foundLocalId) } - return postbox.loadedPeerWithId(peerId) - |> take(1) - |> map(Optional.init) } - |> deliverOnMainQueue).start(next: { peer in + |> deliverOnMainQueue).start(next: { peer, localContactId in guard let strongSelf = self, let peer = peer else { return } - strongSelf.callKitIntegration?.startCall(context: context, peerId: peerId, isVideo: isVideo, displayTitle: peer.debugDisplayTitle) + var phoneNumber: String? + if let peer = peer as? TelegramUser, let phone = peer.phone { + phoneNumber = formatPhoneNumber(phone) + } + strongSelf.callKitIntegration?.startCall(context: context, peerId: peerId, phoneNumber: phoneNumber, localContactId: localContactId, isVideo: isVideo, displayTitle: peer.debugDisplayTitle) })) } if let currentCall = self.currentCall { diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 20cbc64332..01fdb26065 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -3604,7 +3604,7 @@ func replayFinalState( let namespace: ItemCollectionId.Namespace var items: [ItemCollectionItem] = [] let info: StickerPackCollectionInfo - if case let .stickerSet(set, packs, documents) = apiSet { + if case let .stickerSet(set, packs, keywords, documents) = apiSet { var indexKeysByFile: [MediaId: [MemoryBuffer]] = [:] for pack in packs { switch pack { @@ -3621,6 +3621,20 @@ func replayFinalState( break } } + for keyword in keywords { + switch keyword { + case let .stickerKeyword(documentId, texts): + for text in texts { + let key = ValueBoxKey(text).toMemoryBuffer() + let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: documentId) + if indexKeysByFile[mediaId] == nil { + indexKeysByFile[mediaId] = [key] + } else { + indexKeysByFile[mediaId]!.append(key) + } + } + } + } for apiDocument in documents { if let file = telegramMediaFileFromApiDocument(apiDocument), let id = file.id { diff --git a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift index 2612a775a3..07676c9cbd 100644 --- a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift @@ -1193,7 +1193,7 @@ public final class AccountViewTracker { return account.postbox.transaction { transaction -> Void in for result in results { switch result { - case let .stickerSet(_, _, documents)?: + case let .stickerSet(_, _, _, documents)?: for document in documents { if let file = telegramMediaFileFromApiDocument(document) { if transaction.getMedia(file.fileId) != nil { diff --git a/submodules/TelegramCore/Sources/State/ContactSyncManager.swift b/submodules/TelegramCore/Sources/State/ContactSyncManager.swift index e55613f000..9883f78924 100644 --- a/submodules/TelegramCore/Sources/State/ContactSyncManager.swift +++ b/submodules/TelegramCore/Sources/State/ContactSyncManager.swift @@ -249,7 +249,7 @@ private func pushDeviceContacts(postbox: Postbox, network: Network, importableCo if let updatedData = importableContacts[number] { if let value = value as? TelegramDeviceContactImportedData { switch value { - case let .imported(data, _): + case let .imported(data, _, _): if data != updatedData { updatedDataIdentifiers.insert(identifier) } @@ -289,7 +289,10 @@ private func pushDeviceContacts(postbox: Postbox, network: Network, importableCo outer: for i in (0 ..< orderedPushIdentifiers.count).reversed() { if let user = currentContactDetails[orderedPushIdentifiers[i]], case let .phoneNumber(number) = orderedPushIdentifiers[i], let data = importableContacts[number] { if (user.firstName ?? "") == data.firstName && (user.lastName ?? "") == data.lastName { - transaction.setDeviceContactImportInfo(orderedPushIdentifiers[i].key, value: TelegramDeviceContactImportedData.imported(data: data, importedByCount: 0)) + if data.localIdentifiers.contains("5DFF1D6F-8C0A-48C9-800D-F4BEC59C0E50") { + assert(true) + } + transaction.setDeviceContactImportInfo(orderedPushIdentifiers[i].key, value: TelegramDeviceContactImportedData.imported(data: data, importedByCount: 0, peerId: user.id)) orderedPushIdentifiers.remove(at: i) continue outer } @@ -334,6 +337,7 @@ private func pushDeviceContactData(postbox: Postbox, network: Network, contacts: var addedContactPeerIds = Set() var retryIndices = Set() var importedCounts: [Int: Int32] = [:] + var peerIdByClientId: [Int64: PeerId] = [:] switch result { case let .importedContacts(imported, popularInvites, retryContacts, users): let peers = users.map { TelegramUser(user: $0) as Peer } @@ -342,8 +346,10 @@ private func pushDeviceContactData(postbox: Postbox, network: Network, contacts: }) for item in imported { switch item { - case let .importedContact(userId, _): - addedContactPeerIds.insert(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))) + case let .importedContact(userId, clientId): + let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) + addedContactPeerIds.insert(peerId) + peerIdByClientId[clientId] = peerId } } for item in retryContacts { @@ -363,7 +369,10 @@ private func pushDeviceContactData(postbox: Postbox, network: Network, contacts: importedData = .retryLater addedReimportAttempts[.phoneNumber(batch[i].0)] = timestamp } else { - importedData = .imported(data: batch[i].1, importedByCount: importedCounts[i] ?? 0) + if batch[i].1.localIdentifiers.contains("5DFF1D6F-8C0A-48C9-800D-F4BEC59C0E50") { + assert(true) + } + importedData = .imported(data: batch[i].1, importedByCount: importedCounts[i] ?? 0, peerId: peerIdByClientId[Int64(i)]) } transaction.setDeviceContactImportInfo(TelegramDeviceContactImportIdentifier.phoneNumber(batch[i].0).key, value: importedData) } diff --git a/submodules/TelegramCore/Sources/State/ManagedSynchronizeInstalledStickerPacksOperations.swift b/submodules/TelegramCore/Sources/State/ManagedSynchronizeInstalledStickerPacksOperations.swift index 30ebc429d2..7601c76dae 100644 --- a/submodules/TelegramCore/Sources/State/ManagedSynchronizeInstalledStickerPacksOperations.swift +++ b/submodules/TelegramCore/Sources/State/ManagedSynchronizeInstalledStickerPacksOperations.swift @@ -147,7 +147,7 @@ private func fetchStickerPack(network: Network, info: StickerPackCollectionInfo) switch result { case .stickerSetNotModified: break - case let .stickerSet(stickerSet, packs, documents): + case let .stickerSet(stickerSet, packs, keywords, documents): updatedInfo = StickerPackCollectionInfo(apiSet: stickerSet, namespace: info.id.namespace) var indexKeysByFile: [MediaId: [MemoryBuffer]] = [:] for pack in packs { @@ -165,6 +165,20 @@ private func fetchStickerPack(network: Network, info: StickerPackCollectionInfo) break } } + for keyword in keywords { + switch keyword { + case let .stickerKeyword(documentId, texts): + for text in texts { + let key = ValueBoxKey(text).toMemoryBuffer() + let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: documentId) + if indexKeysByFile[mediaId] == nil { + indexKeysByFile[mediaId] = [key] + } else { + indexKeysByFile[mediaId]!.append(key) + } + } + } + } for apiDocument in documents { if let file = telegramMediaFileFromApiDocument(apiDocument), let id = file.id { @@ -224,7 +238,7 @@ private func installRemoteStickerPacks(network: Network, infos: [StickerPackColl archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id) case let .stickerSetMultiCovered(set, _): archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id) - case let .stickerSetFullCovered(set, _, _): + case let .stickerSetFullCovered(set, _, _, _): archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id) } } diff --git a/submodules/TelegramCore/Sources/State/Serialization.swift b/submodules/TelegramCore/Sources/State/Serialization.swift index b7c633834b..ee119c6d76 100644 --- a/submodules/TelegramCore/Sources/State/Serialization.swift +++ b/submodules/TelegramCore/Sources/State/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 146 + return 147 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/State/StickerManagement.swift b/submodules/TelegramCore/Sources/State/StickerManagement.swift index 68ab5b358a..2f8cd3b50a 100644 --- a/submodules/TelegramCore/Sources/State/StickerManagement.swift +++ b/submodules/TelegramCore/Sources/State/StickerManagement.swift @@ -227,7 +227,7 @@ func parsePreviewStickerSet(_ set: Api.StickerSetCovered, namespace: ItemCollect } } return (info, items) - case let .stickerSetFullCovered(set, packs, documents): + case let .stickerSetFullCovered(set, packs, keywords, documents): var indexKeysByFile: [MediaId: [MemoryBuffer]] = [:] for pack in packs { switch pack { @@ -244,6 +244,20 @@ func parsePreviewStickerSet(_ set: Api.StickerSetCovered, namespace: ItemCollect break } } + for keyword in keywords { + switch keyword { + case let .stickerKeyword(documentId, texts): + for text in texts { + let key = ValueBoxKey(text).toMemoryBuffer() + let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: documentId) + if indexKeysByFile[mediaId] == nil { + indexKeysByFile[mediaId] = [key] + } else { + indexKeysByFile[mediaId]!.append(key) + } + } + } + } let info = StickerPackCollectionInfo(apiSet: set, namespace: namespace) var items: [StickerPackItem] = [] diff --git a/submodules/TelegramCore/Sources/State/SynchronizeSavedStickersOperation.swift b/submodules/TelegramCore/Sources/State/SynchronizeSavedStickersOperation.swift index bb449b1dc3..1c0e89cdda 100644 --- a/submodules/TelegramCore/Sources/State/SynchronizeSavedStickersOperation.swift +++ b/submodules/TelegramCore/Sources/State/SynchronizeSavedStickersOperation.swift @@ -74,7 +74,7 @@ public func addSavedSticker(postbox: Postbox, network: Network, file: TelegramMe switch result { case .stickerSetNotModified: break - case let .stickerSet(_, packs, _): + case let .stickerSet(_, packs, _, _): var stringRepresentationsByFile: [MediaId: [String]] = [:] for pack in packs { switch pack { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_ImportableDeviceContactData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_ImportableDeviceContactData.swift index f14c06f7a6..edac3f319c 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_ImportableDeviceContactData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_ImportableDeviceContactData.swift @@ -3,20 +3,24 @@ import Postbox public final class ImportableDeviceContactData: Equatable, PostboxCoding { public let firstName: String public let lastName: String + public let localIdentifiers: [String] - public init(firstName: String, lastName: String) { + public init(firstName: String, lastName: String, localIdentifiers: [String]) { self.firstName = firstName self.lastName = lastName + self.localIdentifiers = localIdentifiers } public init(decoder: PostboxDecoder) { self.firstName = decoder.decodeStringForKey("f", orElse: "") self.lastName = decoder.decodeStringForKey("l", orElse: "") + self.localIdentifiers = decoder.decodeStringArrayForKey("dis") } public func encode(_ encoder: PostboxEncoder) { encoder.encodeString(self.firstName, forKey: "f") encoder.encodeString(self.lastName, forKey: "l") + encoder.encodeStringArray(self.localIdentifiers, forKey: "dis") } public static func ==(lhs: ImportableDeviceContactData, rhs: ImportableDeviceContactData) -> Bool { @@ -26,6 +30,9 @@ public final class ImportableDeviceContactData: Equatable, PostboxCoding { if lhs.lastName != rhs.lastName { return false } + if lhs.localIdentifiers != rhs.localIdentifiers { + return false + } return true } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramDeviceContactImportedData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramDeviceContactImportedData.swift index c95e2b9e0a..2c986b1a5c 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramDeviceContactImportedData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramDeviceContactImportedData.swift @@ -1,13 +1,13 @@ import Postbox public enum TelegramDeviceContactImportedData: PostboxCoding { - case imported(data: ImportableDeviceContactData, importedByCount: Int32) + case imported(data: ImportableDeviceContactData, importedByCount: Int32, peerId: PeerId?) case retryLater public init(decoder: PostboxDecoder) { switch decoder.decodeInt32ForKey("_t", orElse: 0) { case 0: - self = .imported(data: decoder.decodeObjectForKey("d", decoder: { ImportableDeviceContactData(decoder: $0) }) as! ImportableDeviceContactData, importedByCount: decoder.decodeInt32ForKey("c", orElse: 0)) + self = .imported(data: decoder.decodeObjectForKey("d", decoder: { ImportableDeviceContactData(decoder: $0) }) as! ImportableDeviceContactData, importedByCount: decoder.decodeInt32ForKey("c", orElse: 0), peerId: decoder.decodeOptionalInt64ForKey("pid").flatMap(PeerId.init)) case 1: self = .retryLater default: @@ -18,10 +18,15 @@ public enum TelegramDeviceContactImportedData: PostboxCoding { public func encode(_ encoder: PostboxEncoder) { switch self { - case let .imported(data, importedByCount): + case let .imported(data, importedByCount, peerId): encoder.encodeInt32(0, forKey: "_t") encoder.encodeObject(data, forKey: "d") encoder.encodeInt32(importedByCount, forKey: "c") + if let peerId = peerId { + encoder.encodeInt64(peerId.toInt64(), forKey: "pid") + } else { + encoder.encodeNil(forKey: "pid") + } case .retryLater: encoder.encodeInt32(1, forKey: "_t") } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramDeviceContactImportInfo.swift b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramDeviceContactImportInfo.swift index fcee70fad2..d1d58c6f2c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramDeviceContactImportInfo.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramDeviceContactImportInfo.swift @@ -53,7 +53,7 @@ func _internal_deviceContactsImportedByCount(postbox: Postbox, contacts: [(Strin for (id, numbers) in contacts { var maxCount: Int32 = 0 for number in numbers { - if let value = transaction.getDeviceContactImportInfo(TelegramDeviceContactImportIdentifier.phoneNumber(number).key) as? TelegramDeviceContactImportedData, case let .imported(_, importedByCount) = value { + if let value = transaction.getDeviceContactImportInfo(TelegramDeviceContactImportIdentifier.phoneNumber(number).key) as? TelegramDeviceContactImportedData, case let .imported(_, importedByCount, _) = value { maxCount = max(maxCount, importedByCount) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift index 0bfb9d4fc1..486fc79d59 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift @@ -69,5 +69,32 @@ public extension TelegramEngine { } |> ignoreValues } + + public func findPeerByLocalContactIdentifier(identifier: String) -> Signal { + return self.account.postbox.transaction { transaction -> EnginePeer? in + var foundPeerId: PeerId? + transaction.enumerateDeviceContactImportInfoItems({ _, value in + if let value = value as? TelegramDeviceContactImportedData { + switch value { + case let .imported(data, _, peerId): + if data.localIdentifiers.contains(identifier) { + if let peerId = peerId { + foundPeerId = peerId + return false + } + } + default: + break + } + } + return true + }) + if let foundPeerId = foundPeerId { + return transaction.getPeer(foundPeerId).flatMap(EnginePeer.init) + } else { + return nil + } + } + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift index 2ffeadd0c1..90dbd56f70 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift @@ -167,7 +167,7 @@ func _internal_createStickerSet(account: Account, title: String, shortName: Stri switch result { case .stickerSetNotModified: return .complete() - case let .stickerSet(set, packs, documents): + case let .stickerSet(set, packs, keywords, documents): let namespace: ItemCollectionId.Namespace switch set { case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _, _): @@ -195,6 +195,20 @@ func _internal_createStickerSet(account: Account, title: String, shortName: Stri } } } + for keyword in keywords { + switch keyword { + case let .stickerKeyword(documentId, texts): + for text in texts { + let key = ValueBoxKey(text).toMemoryBuffer() + let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: documentId) + if indexKeysByFile[mediaId] == nil { + indexKeysByFile[mediaId] = [key] + } else { + indexKeysByFile[mediaId]!.append(key) + } + } + } + } for apiDocument in documents { if let file = telegramMediaFileFromApiDocument(apiDocument), let id = file.id { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/LoadedStickerPack.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/LoadedStickerPack.swift index a4f44658be..b46d6ed210 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/LoadedStickerPack.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/LoadedStickerPack.swift @@ -52,7 +52,7 @@ func updatedRemoteStickerPack(postbox: Postbox, network: Network, reference: Sti switch result { case .stickerSetNotModified: return .complete() - case let .stickerSet(set, packs, documents): + case let .stickerSet(set, packs, keywords, documents): let namespace: ItemCollectionId.Namespace switch set { case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _, _): @@ -80,6 +80,20 @@ func updatedRemoteStickerPack(postbox: Postbox, network: Network, reference: Sti } } } + for keyword in keywords { + switch keyword { + case let .stickerKeyword(documentId, texts): + for text in texts { + let key = ValueBoxKey(text).toMemoryBuffer() + let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: documentId) + if indexKeysByFile[mediaId] == nil { + indexKeysByFile[mediaId] = [key] + } else { + indexKeysByFile[mediaId]!.append(key) + } + } + } + } for apiDocument in documents { if let file = telegramMediaFileFromApiDocument(apiDocument), let id = file.id { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerPack.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerPack.swift index a1c124da66..0687adba0d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerPack.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerPack.swift @@ -103,7 +103,7 @@ func _internal_stickerPacksAttachedToMedia(account: Account, media: AnyMediaRefe |> map { result -> [StickerPackReference] in return result.map { pack in switch pack { - case let .stickerSetCovered(set, _), let .stickerSetMultiCovered(set, _), let .stickerSetFullCovered(set, _, _): + case let .stickerSetCovered(set, _), let .stickerSetMultiCovered(set, _), let .stickerSetFullCovered(set, _, _, _): let info = StickerPackCollectionInfo(apiSet: set, namespace: Namespaces.ItemCollection.CloudStickerPacks) return .id(id: info.id.id, accessHash: info.accessHash) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerSetInstallation.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerSetInstallation.swift index 04330c7e02..c59aa7e5cf 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerSetInstallation.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerSetInstallation.swift @@ -71,7 +71,7 @@ func _internal_requestStickerSet(postbox: Postbox, network: Network, reference: switch result { case .stickerSetNotModified: return .complete() - case let .stickerSet(set, packs, documents): + case let .stickerSet(set, packs, keywords, documents): info = StickerPackCollectionInfo(apiSet: set, namespace: Namespaces.ItemCollection.CloudStickerPacks) switch set { @@ -95,6 +95,20 @@ func _internal_requestStickerSet(postbox: Postbox, network: Network, reference: break } } + for keyword in keywords { + switch keyword { + case let .stickerKeyword(documentId, texts): + for text in texts { + let key = ValueBoxKey(text).toMemoryBuffer() + let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: documentId) + if indexKeysByFile[mediaId] == nil { + indexKeysByFile[mediaId] = [key] + } else { + indexKeysByFile[mediaId]!.append(key) + } + } + } + } for apiDocument in documents { if let file = telegramMediaFileFromApiDocument(apiDocument), let id = file.id { @@ -167,7 +181,7 @@ func _internal_installStickerSetInteractively(account: Account, info: StickerPac case let .stickerSetMultiCovered(set: set, covers: covers): apiSet = set apiDocuments = covers - case let .stickerSetFullCovered(set, _, documents): + case let .stickerSetFullCovered(set, _, _, documents): apiSet = set apiDocuments = documents } diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index fb914d8c93..781b3b33bc 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -34,6 +34,7 @@ import DebugSettingsUI import BackgroundTasks import UIKitRuntimeUtils import StoreKit +import PhoneNumberFormat #if canImport(AppCenter) import AppCenter @@ -1557,11 +1558,14 @@ private func extractAccountManagerState(records: AccountRecordsView + if let context = self.contextValue?.context, let contactIdentifier = contact.contactIdentifier { + contactByIdentifier = context.engine.contacts.findPeerByLocalContactIdentifier(identifier: contactIdentifier) + } else { + contactByIdentifier = .single(nil) } - if !processed, let handle = contact.personHandle, let value = handle.value { - switch handle.type { - case .unknown: - if let userId = Int64(value) { - startCall(userId) - processed = true - } - case .phoneNumber: - let phoneNumber = cleanPhoneNumber(value) - if !phoneNumber.isEmpty { - guard let context = self.contextValue?.context else { - return true + + let _ = (contactByIdentifier |> deliverOnMainQueue).start(next: { peerByContact in + var processed = false + if let peerByContact = peerByContact { + startCall(peerByContact.id.id._internalGetInt64Value()) + processed = true + } else if let handle = contact.customIdentifier, handle.hasPrefix("tg") { + let string = handle.suffix(from: handle.index(handle.startIndex, offsetBy: 2)) + if let userId = Int64(string) { + startCall(userId) + processed = true + } + } + if !processed, let handle = contact.personHandle, let value = handle.value { + switch handle.type { + case .unknown: + if let userId = Int64(value) { + startCall(userId) + processed = true } - let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Contacts.List(includePresences: false)) - |> map { contactList -> PeerId? in - var result: PeerId? - for peer in contactList.peers { - if case let .user(peer) = peer, let peerPhoneNumber = peer.phone { - if matchPhoneNumbers(phoneNumber, peerPhoneNumber) { - result = peer.id - break + case .phoneNumber: + let phoneNumber = cleanPhoneNumber(value) + if !phoneNumber.isEmpty { + guard let context = self.contextValue?.context else { + return + } + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Contacts.List(includePresences: false)) + |> map { contactList -> PeerId? in + var result: PeerId? + for peer in contactList.peers { + if case let .user(peer) = peer, let peerPhoneNumber = peer.phone { + if matchPhoneNumbers(phoneNumber, peerPhoneNumber) { + result = peer.id + break + } } } + return result } - return result + |> deliverOnMainQueue).start(next: { peerId in + if let peerId = peerId { + startCall(peerId.id._internalGetInt64Value()) + } + }) + processed = true } - |> deliverOnMainQueue).start(next: { peerId in - if let peerId = peerId { - startCall(peerId.id._internalGetInt64Value()) - } - }) - processed = true - } - default: - break + default: + break + } } - } - + }) + + return true } } else if let sendMessageIntent = userActivity.interaction?.intent as? INSendMessageIntent { if let contact = sendMessageIntent.recipients?.first, let handle = contact.customIdentifier, handle.hasPrefix("tg") { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 521193756a..4dc253ca5c 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -1496,8 +1496,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if packReferences.count > 1 { items.tip = .animatedEmoji(text: presentationData.strings.ChatContextMenu_EmojiSet(Int32(packReferences.count)), arguments: nil, file: nil, action: action) } else if let reference = packReferences.first { - items.tipSignal = context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false) - |> delay(1.0, queue: .mainQueue()) + var tipSignal: Signal + tipSignal = context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false) + + items.tipSignal = tipSignal + |> filter { result in if case .result = result { return true diff --git a/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift index f70a795952..a0c4c8c1c2 100644 --- a/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift @@ -499,7 +499,10 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode { var emojiStatus: PeerEmojiStatus? if let user = interfaceState.renderedPeer?.peer as? TelegramUser, let emojiStatusValue = user.emojiStatus { - emojiStatus = emojiStatusValue + if user.isFake || user.isScam { + } else { + emojiStatus = emojiStatusValue + } } /*#if DEBUG diff --git a/submodules/TelegramUI/Sources/ChatTitleView.swift b/submodules/TelegramUI/Sources/ChatTitleView.swift index 75d5c7f006..7d6695aa88 100644 --- a/submodules/TelegramUI/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Sources/ChatTitleView.swift @@ -150,12 +150,12 @@ final class ChatTitleView: UIView, NavigationBarTitleView { } if peer.id != self.context.account.peerId { let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 }) - if let user = peer as? TelegramUser, let emojiStatus = user.emojiStatus { - titleCredibilityIcon = .emojiStatus(emojiStatus) - } else if peer.isFake { + if peer.isFake { titleCredibilityIcon = .fake } else if peer.isScam { titleCredibilityIcon = .scam + } else if let user = peer as? TelegramUser, let emojiStatus = user.emojiStatus { + titleCredibilityIcon = .emojiStatus(emojiStatus) } else if peer.isVerified { titleCredibilityIcon = .verified } else if peer.isPremium && !premiumConfiguration.isPremiumDisabled { diff --git a/submodules/TelegramUI/Sources/DeviceContactDataManager.swift b/submodules/TelegramUI/Sources/DeviceContactDataManager.swift index c4bcab957c..7e6f3a62c2 100644 --- a/submodules/TelegramUI/Sources/DeviceContactDataManager.swift +++ b/submodules/TelegramUI/Sources/DeviceContactDataManager.swift @@ -429,15 +429,20 @@ private final class DeviceContactDataManagerPrivateImpl { for (stableId, basicData) in self.stableIdToBasicContactData { for phoneNumber in basicData.phoneNumbers { var replace = false + var currentLocalIdentifiers: [String] = [] if let current = importableContactData[phoneNumber.value] { if stableId < current.0 { replace = true + currentLocalIdentifiers = current.1.localIdentifiers } } else { replace = true } if replace { - importableContactData[phoneNumber.value] = (stableId, ImportableDeviceContactData(firstName: basicData.firstName, lastName: basicData.lastName)) + if !currentLocalIdentifiers.contains(stableId) { + currentLocalIdentifiers.append(stableId) + } + importableContactData[phoneNumber.value] = (stableId, ImportableDeviceContactData(firstName: basicData.firstName, lastName: basicData.lastName, localIdentifiers: currentLocalIdentifiers)) } } } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index ee7bae433d..70c6a4ced6 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -2311,13 +2311,13 @@ final class PeerInfoHeaderNode: ASDisplayNode { let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 }) let credibilityIcon: CredibilityIcon - if let user = peer as? TelegramUser, let emojiStatus = user.emojiStatus { - credibilityIcon = .emojiStatus(emojiStatus) - } else if let peer = peer { + if let peer = peer { if peer.isFake { credibilityIcon = .fake } else if peer.isScam { credibilityIcon = .scam + } else if let user = peer as? TelegramUser, let emojiStatus = user.emojiStatus { + credibilityIcon = .emojiStatus(emojiStatus) } else if peer.isVerified { credibilityIcon = .verified } else if peer.isPremium && !premiumConfiguration.isPremiumDisabled && (peer.id != self.context.account.peerId || self.isSettings) { diff --git a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIViewController+Navigation.m b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIViewController+Navigation.m index dc8a2d0455..28bbb9cbd8 100644 --- a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIViewController+Navigation.m +++ b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIViewController+Navigation.m @@ -5,6 +5,7 @@ #import "NSWeakReference.h" + @interface UIViewControllerPresentingProxy : UIViewController @property (nonatomic, copy) void (^dismiss)(); @@ -139,6 +140,18 @@ static bool notyfyingShiftState = false; @end +@interface UIWindow (Telegram) + +@end + +@implementation UIWindow (Telegram) + +- (instancetype)_65087dc8_initWithFrame:(CGRect)frame { + return [self _65087dc8_initWithFrame:frame]; +} + +@end + @protocol UIRemoteKeyboardWindowProtocol + (UIWindow * _Nullable)remoteKeyboardWindowForScreen:(UIScreen * _Nullable)screen create:(BOOL)create; @@ -161,7 +174,7 @@ static bool notyfyingShiftState = false; [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(presentViewController:animated:completion:) newSelector:@selector(_65087dc8_presentViewController:animated:completion:)]; [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(setNeedsStatusBarAppearanceUpdate) newSelector:@selector(_65087dc8_setNeedsStatusBarAppearanceUpdate)]; - [RuntimeUtils swizzleClassMethodOfClass:NSClassFromString(@"UIRemoteKeyboardWindow") currentSelector:NSSelectorFromString(@"remoteKeyboardWindowForScreen:create:") newSelector:NSSelectorFromString(@"_65087dc8_remoteKeyboardWindowForScreen:create:")]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UIWindow class] currentSelector:@selector(initWithFrame:) newSelector:@selector(_65087dc8_initWithFrame:)]; if (@available(iOS 15.0, *)) { [RuntimeUtils swizzleInstanceMethodOfClass:[CADisplayLink class] currentSelector:@selector(setPreferredFrameRateRange:) newSelector:@selector(_65087dc8_setPreferredFrameRateRange:)]; @@ -304,7 +317,8 @@ static bool notyfyingShiftState = false; if (!windowClass) { return nil; } - return [(id)windowClass remoteKeyboardWindowForScreen:[UIScreen mainScreen] create:false]; + UIWindow *result = [(id)windowClass remoteKeyboardWindowForScreen:[UIScreen mainScreen] create:false]; + return result; } @end From 163a9b3ed703631dfb4b3543a5626786cf3e1e87 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 16 Sep 2022 22:59:52 +0400 Subject: [PATCH 19/22] Update gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6d58250ec9..925e33470e 100644 --- a/.gitignore +++ b/.gitignore @@ -62,5 +62,5 @@ bazel-testlogs/* */*.swp *.swp build-input/* -*/*.pyc +**/*.pyc *.pyc From 30b2f4e57da2d1821e6ca5a71506b70aa28a8d47 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 16 Sep 2022 23:05:31 +0400 Subject: [PATCH 20/22] Remove legacy parameter --- build-system/Make/RemoteBuild.py | 1 - 1 file changed, 1 deletion(-) diff --git a/build-system/Make/RemoteBuild.py b/build-system/Make/RemoteBuild.py index 22cfd45a07..a9b39a0681 100644 --- a/build-system/Make/RemoteBuild.py +++ b/build-system/Make/RemoteBuild.py @@ -113,7 +113,6 @@ def remote_build(darwin_containers_host, bazel_cache_host, configuration, build_ guest_build_sh += '--configuration={} \\'.format(configuration) guest_build_sh += '--configurationPath=$HOME/telegram-build-input/configuration.json \\' guest_build_sh += '--codesigningInformationPath=$HOME/telegram-build-input \\' - guest_build_sh += '--apsEnvironment=production \\' guest_build_sh += '--outputBuildArtifactsPath=/Users/Shared/telegram-ios/build/artifacts \\' guest_build_file_path = tempfile.mktemp() From b8528b3568263010e0a08a6e424a6a19236d0e43 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 16 Sep 2022 23:45:23 +0400 Subject: [PATCH 21/22] Debug CI --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 46308d2fb0..df86aebf75 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,6 +18,7 @@ internal: except: - tags script: + - echo "$TELEGRAM_PRIVATE_DATA_PATH" - PYTHONPATH="$PYTHONPATH:/darwin-containers" python3 -u build-system/Make/Make.py remote-build --darwinContainersHost="http://host.docker.internal:8650" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="$TELEGRAM_PRIVATE_DATA_PATH/build-configurations/enterprise-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=enterprise --configuration=release_arm64 - python3 -u build-system/Make/DeployToAppCenter.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/appcenter-configurations/appcenter-internal.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip" environment: From 6e13f8877091a7c73eee5aab45577868914b43f1 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 16 Sep 2022 23:47:22 +0400 Subject: [PATCH 22/22] Debug --- .gitlab-ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index df86aebf75..4549157fb9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,8 +18,7 @@ internal: except: - tags script: - - echo "$TELEGRAM_PRIVATE_DATA_PATH" - - PYTHONPATH="$PYTHONPATH:/darwin-containers" python3 -u build-system/Make/Make.py remote-build --darwinContainersHost="http://host.docker.internal:8650" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="$TELEGRAM_PRIVATE_DATA_PATH/build-configurations/enterprise-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=enterprise --configuration=release_arm64 + - PYTHONPATH="$PYTHONPATH:/darwin-containers" python3 -u build-system/Make/Make.py remote-build --darwinContainersHost="http://host.docker.internal:8650" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="/telegram-private-data/build-configurations/enterprise-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=enterprise --configuration=release_arm64 - python3 -u build-system/Make/DeployToAppCenter.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/appcenter-configurations/appcenter-internal.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip" environment: name: internal