def _get_publishing_application_packages( self, path_patterns: Sequence[pathlib.Path] ) -> List[Union[Ipa, MacOsPackage]]: found_application_paths = list(self.find_paths(*path_patterns)) application_packages: List[Union[Ipa, MacOsPackage]] = [] for path in found_application_paths: if path.suffix == '.ipa': application_package: Union[Ipa, MacOsPackage] = Ipa(path) elif path.suffix == '.pkg': application_package = MacOsPackage(path) else: raise AppStoreConnectError( f'Unsupported package type for App Store Connect publishing: {path}' ) try: application_package.get_summary() except FileNotFoundError as fnf: message = f'Invalid package for App Store Connect publishing: {fnf.args[0]} not found from {path}' self.logger.warning(Colors.YELLOW(message)) except (ValueError, IOError) as error: message = f'Unable to process package {path} for App Store Connect publishing: {error.args[0]}' self.logger.warning(Colors.YELLOW(message)) else: application_packages.append(application_package) if not application_packages: patterns = ', '.join(f'"{pattern}"' for pattern in path_patterns) raise AppStoreConnectError( f'No application packages found for patterns {patterns}') return application_packages
def _detect_project_bundle_ids(self, xcode_project: pathlib.Path, target_name: Optional[str], config_name: Optional[str], include_pods: bool) -> List[str]: def group(bundle_ids): groups = defaultdict(list) for bundle_id in bundle_ids: groups['$' in bundle_id].append(bundle_id) return groups[True], groups[False] if not include_pods and xcode_project.stem == 'Pods': self.logger.info(f'Skip Bundle ID detection from Pod project {xcode_project}') return [] detector = BundleIdDetector(xcode_project, target_name, config_name) detector.notify() try: detected_bundle_ids = detector.detect() except (ValueError, IOError) as error: raise XcodeProjectException(*error.args) env_var_bundle_ids, valid_bundle_ids = group(detected_bundle_ids) if env_var_bundle_ids: msg = f'Bundle IDs {", ".join(env_var_bundle_ids)} contain environment variables, exclude them.' self.logger.info(Colors.YELLOW(msg)) self.logger.info(f'Detected Bundle IDs: {", ".join(valid_bundle_ids)}') return valid_bundle_ids
def log_filtered(self, resource_type: Type[R], resources: Sequence[R], constraint: str): count = len(resources) name = resource_type.plural(count) if count == 0: self.logger.info(Colors.YELLOW(f'Did not find any {name} {constraint}')) else: self.logger.info(Colors.GREEN(f'Filtered out {count} {name} {constraint}'))
def missing_profile(bundle_id) -> bool: try: bundle_ids_profiles = self.api_client.bundle_ids.list_profile_ids(bundle_id) return not (profile_ids & {p.id for p in bundle_ids_profiles}) except AppStoreConnectApiError as err: error = f'Listing {Profile.s} for {BundleId} {bundle_id.id} failed unexpectedly' self.logger.warning(Colors.YELLOW(f'{error}: {err.error_response}')) return True
def has_certificate(profile) -> bool: try: profile_certificates = self.api_client.profiles.list_certificate_ids(profile) return bool(certificate_ids.issubset({c.id for c in profile_certificates})) except AppStoreConnectApiError as err: error = f'Listing {SigningCertificate.s} for {Profile} {profile.id} failed unexpectedly' self.logger.warning(Colors.YELLOW(f'{error}: {err.error_response}')) return False
def notify_profile_usage(self): self.logger.info( Colors.GREEN('Completed configuring code signing settings')) if not self._matched_profiles: message = 'Did not find matching provisioning profiles for code signing!' self.logger.warning(Colors.YELLOW(message)) return for info in sorted(self._matched_profiles, key=lambda i: i.sort_key()): self.logger.info(Colors.BLUE(info.format()))
def _deprecation_notice(self): from .android_app_bundle import AndroidAppBundle current_action = f'{self.get_executable_name()} {self.generate.action_name}' new_action = f'{AndroidAppBundle.get_executable_name()} {AndroidAppBundle.build_universal_apks.action_name}' lines = ( f'Warning! Action "{current_action}" is deprecated and will be removed in future releases.', f'Please use action "{new_action}" instead.', f'See "{AndroidAppBundle.get_executable_name()} --help" for more information.', ) for line in lines: self.logger.info(Colors.YELLOW(line))
def log_found(self, resource_type: Type[R], resources: Sequence[R], resource_filter: Optional[ResourceManager.Filter] = None, related_resource_type: Optional[Type[R2]] = None): count = len(resources) name = resource_type.plural(count) suffix = f' matching specified filters: {resource_filter}.' if resource_filter is not None else '' related = f' for {related_resource_type}' if related_resource_type is not None else '' if count == 0: self.logger.info(Colors.YELLOW(f'Did not find any {name}{related}{suffix}')) else: self.logger.info(Colors.GREEN(f'Found {count} {name}{related}{suffix}'))
def use_profiles(self, xcode_project_patterns: Sequence[pathlib.Path], profile_path_patterns: Sequence[pathlib.Path], export_options_plist: pathlib.Path = ExportIpaArgument. EXPORT_OPTIONS_PATH.get_default(), custom_export_options: Optional[Dict] = None, warn_only: bool = False): """ Set up code signing settings on specified Xcode projects to use given provisioning profiles """ from .keychain import Keychain self.logger.info(Colors.BLUE('Configure code signing settings')) profile_paths = self.find_paths(*profile_path_patterns) xcode_projects = self.find_paths(*xcode_project_patterns) try: profiles = [ ProvisioningProfile.from_path(p) for p in profile_paths ] except (ValueError, IOError) as error: raise XcodeProjectException(*error.args) available_certs = Keychain().list_code_signing_certificates( should_print=False) code_signing_settings_manager = CodeSigningSettingsManager( profiles, available_certs) for xcode_project in xcode_projects: try: code_signing_settings_manager.use_profiles(xcode_project) except (ValueError, IOError) as error: if warn_only: self.logger.warning( Colors.YELLOW( f'Using profiles on {xcode_project} failed')) else: raise XcodeProjectException(*error.args) code_signing_settings_manager.notify_profile_usage() export_options = code_signing_settings_manager.generate_export_options( custom_export_options) export_options.notify( Colors.GREEN('Generated options for exporting the project')) export_options.save(export_options_plist) self.logger.info( Colors.GREEN(f'Saved export options to {export_options_plist}')) return export_options
def _find_certificates(self): process = self.execute(('security', 'find-certificate', '-a', '-p', self.path), show_output=False) if process.returncode != 0: raise KeychainError(f'Unable to list certificates from keychain {self.path}', process) pem = '' for line in process.stdout.splitlines(): pem += line + '\n' if line == '-----END CERTIFICATE-----': try: yield Certificate.from_pem(pem) except ValueError: self.logger.warning(Colors.YELLOW('Failed to read certificate from keychain')) pem = ''