def create_apk(build, sdk, output_filename, interactive=True): """Create an APK file from the the contents of development/android. :param output_filename: name of the file to which we'll write """ jre = _get_jre() path_info = _find_or_install_sdk(build) lib_path = path.normpath(path.join('.template', 'lib')) dev_dir = path.normpath(path.join('development', 'android')) package_name = _generate_package_name(build) LOG.info('Creating Android .apk file') with temp_file() as zipf_name: # Compile XML files into APK _create_apk_with_aapt(build, zipf_name, path_info, package_name, lib_path, dev_dir) with temp_file() as compressed_zipf_name: with zipfile.ZipFile(zipf_name, 'r') as zipf: with zipfile.ZipFile(compressed_zipf_name, 'w') as compressed_zipf: for name in zipf.namelist(): compress_type = zipfile.ZIP_STORED if name == 'classes.dex': compress_type = zipfile.ZIP_DEFLATED compressed_zipf.writestr(name, zipf.read(name), compress_type=compress_type) with temp_file() as signed_zipf_name: # Sign APK _sign_zipf_debug(lib_path, jre, compressed_zipf_name, signed_zipf_name) # Align APK _align_apk(path_info, signed_zipf_name, output_filename)
def package_android(build): jre = _get_jre() path_info = _find_or_install_sdk(build) lib_path = path.normpath(path.join('.template', 'lib')) dev_dir = path.normpath(path.join('development', 'android')) output = path.abspath(_generate_path_to_output_apk(build)) signing_info = _lookup_or_prompt_for_signing_info(build) LOG.info('Creating Android .apk file') package_name = _generate_package_name(build) #zip with temp_file() as zipf_name: _create_apk_with_aapt(build, zipf_name, path_info, package_name, lib_path, dev_dir) with temp_file() as compressed_zipf_name: with zipfile.ZipFile(zipf_name, 'r') as zipf: with zipfile.ZipFile(compressed_zipf_name, 'w') as compressed_zipf: for name in zipf.namelist(): compress_type = zipfile.ZIP_STORED if name == 'classes.dex': compress_type = zipfile.ZIP_DEFLATED compressed_zipf.writestr(name, zipf.read(name), compress_type=compress_type) with temp_file() as signed_zipf_name: #sign _sign_zipf_release(lib_path, jre, compressed_zipf_name, signed_zipf_name, signing_info) # create output directory for APK if necessary _create_output_directory(output) #align _align_apk(path_info, signed_zipf_name, output) LOG.debug('removing zipfile and un-aligned APK') LOG.info("created APK: {output}".format(output=output)) return output
def run_android(build, build_type_dir, sdk, device, interactive=True, purge=False): # TODO: remove sdk parameter from here and call sites, information is # contained in build.tool_config already # TODO: remove build_type_dir from method and call sites, doesn't seem to # be used anywhere # TODO: remove interactive parameter. this information is contained in the # build, but we should never use this anyway, as we can now interact with # the toolkit from here jre = _get_jre() path_info = _find_or_install_sdk(build) LOG.info('Starting ADB if not running') run_detached([path_info.adb, 'start-server'], wait=True) LOG.info('Looking for Android device') available_devices = _get_available_devices(path_info) if not available_devices and device and device.lower() == 'emulator': LOG.info('Using android emulator') _create_avd_if_necessary(path_info) _launch_avd(path_info) return run_android(build, build_type_dir, sdk, device, interactive=interactive) if not available_devices: _prompt_user_to_attach_device(path_info) return run_android(build, build_type_dir, sdk, device, interactive=interactive) if device and device == 'emulator': emulators = [d for d in available_devices if d.startswith('emulator')] if not emulators: LOG.info('No emulator found') _create_avd_if_necessary(path_info) _launch_avd(path_info) return run_android(build, build_type_dir, sdk, device, interactive=interactive) else: device = emulators[0] if device: if device in available_devices: chosen_device = device LOG.info('Using specified android device %s' % chosen_device) else: LOG.error('No such device "%s"' % device) LOG.error('The available devices are:') LOG.error("\n".join(available_devices)) raise AndroidError else: chosen_device = available_devices[0] LOG.info('No android device specified, defaulting to %s' % chosen_device) with temp_file() as out_apk_name: create_apk(build, sdk, out_apk_name, interactive=interactive) package_name = _generate_package_name(build) # If required remove previous installs from device if purge: _run_adb([path_info.adb, 'uninstall', package_name], 30, path_info) # Install APK to device LOG.info('Installing apk') proc_std = _run_adb([path_info.adb, '-s', chosen_device, 'install', '-r', out_apk_name], 60, path_info) LOG.debug(proc_std) # Start app on device proc_std = _run_adb([path_info.adb, '-s', chosen_device, 'shell', 'am', 'start', '-n', package_name+'/io.trigger.forge.android.core.ForgeActivity'], 60, path_info) LOG.debug(proc_std) #follow log _follow_log(path_info, chosen_device)
def run_idevice(self, build, device, provisioning_profile, certificate=None, certificate_path=None, certificate_password=None): possible_app_location = '{0}/ios/device-*/'.format(self.path_to_ios_build) LOG.debug('Looking for apps at {0}'.format(possible_app_location)) possible_apps = glob(possible_app_location) if not possible_apps: raise IOSError("Couldn't find iOS app to run on a device") path_to_app = possible_apps[0] LOG.debug("Signing {app}".format(app=path_to_app)) plist_str = self._grab_plist_from_binary_mess(build, provisioning_profile) plist_dict = self._parse_plist(plist_str) self.check_plist_dict(plist_dict, self.path_to_ios_build) self.provisioning_profile = plist_dict LOG.info("Plist OK") certificate = self._select_certificate(certificate) self.log_profile() if sys.platform.startswith('darwin'): with temp_file() as temp_file_path: self._create_entitlements_file(build, temp_file_path, plist_dict) self._sign_app(build=build, provisioning_profile=provisioning_profile, certificate=certificate, entitlements_file=temp_file_path, ) fruitstrap = [ensure_lib_available(build, 'fruitstrap'), '-d', '-u', '-t', '10', '-b', path_to_app] if device and device.lower() != 'device': # pacific device given fruitstrap.append('-i') fruitstrap.append(device) LOG.info('Installing app on device {device}: is it connected?'.format(device=device)) else: LOG.info('Installing app on device: is it connected?') def filter_and_combine(logline): return logline.rstrip() ensure_lib_available(build, 'lldb_framework.zip', extract=True) env = deepcopy(os.environ) env['PATH'] = os.path.dirname(ensure_lib_available(build, 'lldb'))+":"+env['PATH'] run_shell(*fruitstrap, fail_silently=False, command_log_level=logging.INFO, filter=filter_and_combine, check_for_interrupt=True, env=env) elif sys.platform.startswith('win'): with temp_file() as ipa_path: self.create_ipa_from_app( build=build, provisioning_profile=provisioning_profile, output_path_for_ipa=ipa_path, certificate_path=certificate_path, certificate_password=certificate_password, ) win_ios_install = [ensure_lib_available(build, 'win-ios-install.exe')] if device and device.lower() != 'device': # pacific device given win_ios_install.append(device) LOG.info('Installing app on device {device}: is it connected?'.format(device=device)) else: LOG.info('Installing app on device: is it connected?') win_ios_install.append(ipa_path) win_ios_install.append(_generate_package_name(build)) run_shell(*win_ios_install, fail_silently=False, command_log_level=logging.INFO, check_for_interrupt=True) else: if not which('ideviceinstaller'): raise Exception("Can't find ideviceinstaller - is it installed and on your PATH?") with temp_file() as ipa_path: self.create_ipa_from_app( build=build, provisioning_profile=provisioning_profile, output_path_for_ipa=ipa_path, certificate_path=certificate_path, certificate_password=certificate_password, ) linux_ios_install = ['ideviceinstaller'] if device and device.lower() != 'device': # pacific device given linux_ios_install.append('-U') linux_ios_install.append(device) LOG.info('Installing app on device {device}: is it connected?'.format(device=device)) else: LOG.info('Installing app on device: is it connected?') linux_ios_install.append('-i') linux_ios_install.append(ipa_path) run_shell(*linux_ios_install, fail_silently=False, command_log_level=logging.INFO, check_for_interrupt=True) LOG.info('App installed, you will need to run the app on the device manually.')
def create_ipa_from_app(self, build, provisioning_profile, output_path_for_ipa, certificate_to_sign_with=None, relative_path_to_itunes_artwork=None, certificate_path=None, certificate_password=None, output_path_for_manifest=None): """Create an ipa from an app, with an embedded provisioning profile provided by the user, and signed with a certificate provided by the user. :param build: instance of build :param provisioning_profile: Absolute path to the provisioning profile to embed in the ipa :param output_path_for_ipa: Path to save the created IPA :param certificate_to_sign_with: (Optional) The name of the certificate to sign the ipa with :param relative_path_to_itunes_artwork: (Optional) A path to a 512x512 png picture for the App view in iTunes. This should be relative to the location of the user assets. """ LOG.info('Starting package process for iOS') directory = path.dirname(output_path_for_ipa) if not path.isdir(directory): os.makedirs(directory) app_folder_name = self._locate_ios_app(error_message="Couldn't find iOS app in order to sign it") path_to_app = path.abspath(path.join(self.path_to_ios_build, 'ios', app_folder_name)) LOG.info('Going to package: %s' % path_to_app) plist_str = self._grab_plist_from_binary_mess(build, provisioning_profile) plist_dict = self._parse_plist(plist_str) self.check_plist_dict(plist_dict, self.path_to_ios_build) self.provisioning_profile = plist_dict LOG.info("Plist OK") # use distribution cert automatically if PP is distribution certificate_to_sign_with = self._select_certificate(certificate_to_sign_with) self.log_profile() seed_id = self._extract_seed_id() LOG.debug("Extracted seed ID: {0}".format(seed_id)) with lib.temp_dir() as temp_dir: LOG.debug('Making Payload directory') os.mkdir(path.join(temp_dir, 'Payload')) path_to_payload = path.abspath(path.join(temp_dir, 'Payload')) path_to_payload_app = path.abspath(path.join(path_to_payload, app_folder_name)) if relative_path_to_itunes_artwork is not None: path_to_itunes_artwork = path.join(path_to_payload_app, 'assets', 'src', relative_path_to_itunes_artwork) else: path_to_itunes_artwork = None with temp_file() as temp_file_path: self._create_entitlements_file(build, temp_file_path, plist_dict) self._sign_app(build=build, provisioning_profile=provisioning_profile, certificate=certificate_to_sign_with, entitlements_file=temp_file_path, certificate_path=certificate_path, certificate_password=certificate_password, ) shutil.copytree(path_to_app, path.join(path_to_payload, path.basename(path_to_app))) if path_to_itunes_artwork: shutil.copy2(path_to_itunes_artwork, path.join(temp_dir, 'iTunesArtwork')) with ZipFile(output_path_for_ipa, 'w', compression=ZIP_DEFLATED) as out_ipa: for root, dirs, files in os.walk(temp_dir): for file in files: LOG.debug('adding to IPA: {file}'.format( file=path.join(root, file), )) out_ipa.write(path.join(root, file), path.join(root[len(temp_dir):], file)) LOG.info("created IPA: {output}".format(output=output_path_for_ipa)) if output_path_for_manifest and self.plist_supports_wireless_distribution(plist_dict): LOG.info("Provisioning profile supports wireless distributions, creating manifest: %s" % output_path_for_manifest) # Based on https://help.apple.com/iosdeployment-apps/#app43ad78b3 manifest = {"items": [{ "assets": [{ "kind": "software-package", "url": "http://www.example.com/app.ipa" },{ "kind": "display-image", "needs-shine": True, "url": "http://www.example.com/image.57x57.png", },{ "kind": "full-size-image", "needs-shine": True, "url": "http://www.example.com/image.512x512.jpg", }], "metadata": { "bundle-identifier": _generate_package_name(build), "bundle-version": build.config['version'], "kind": "software", "title": build.config['name'] } }]} with open(output_path_for_manifest, 'wb') as manifest_file: plistlib.writePlist(manifest, manifest_file) return output_path_for_ipa
def _sign_app(self, build, provisioning_profile, entitlements_file, certificate=None, certificate_path=None, certificate_password=None): app_folder_name = self._locate_ios_app(error_message="Couldn't find iOS app in order to sign it") path_to_app = path.abspath(path.join(self.path_to_ios_build, 'ios', app_folder_name)) embedded_profile = 'embedded.mobileprovision' path_to_embedded_profile = path.abspath(path.join(path_to_app, embedded_profile)) path_to_pp = path.join(build.orig_wd, provisioning_profile) if not path.isfile(path_to_pp): self._missing_provisioning_profile(build, path_to_pp) try: os.remove(path_to_embedded_profile) except Exception: LOG.warning("Couldn't remove {profile}".format(profile=path_to_embedded_profile)) shutil.copy2(path_to_pp, path_to_embedded_profile) if not sys.platform.startswith('darwin'): if not certificate_path: lib.local_config_problem( build, message="To deploy iOS apps to a device, you must specify a " "path to a certificate to sign with.", examples={ "ios.profiles.DEFAULT.developer_certificate_path": path.abspath("/Users/Bob/certificate.pfx") }, more_info="https://trigger.io/docs/current/tools/local_config.html" ) if not certificate_password: lib.local_config_problem( build, message="To deploy iOS apps to a device, you must specify a " "path the password to unlock your certificate.", examples={ "ios.profiles.DEFAULT.developer_certificate_password": "******" }, more_info="https://trigger.io/docs/current/tools/local_config.html" ) cache_file = None development_certificate = False try: cert_name = subprocess.check_output(['java', '-jar', ensure_lib_available(build, 'p12name.jar'), certificate_path, certificate_password]).strip() if cert_name.startswith('iPhone Developer:'): development_certificate = True except Exception: pass if development_certificate: # Development certificate signings can be cached # Hash for Forge binary + signing certificate + profile + info.plist h = hashlib.sha1() with open(path.join(path_to_app, 'Forge'), 'rb') as binary_file: h.update(binary_file.read()) with open(path.join(path_to_app, 'Info.plist'), 'rb') as info_plist_file: h.update(info_plist_file.read()) with open(certificate_path, 'rb') as certificate_file: h.update(certificate_file.read()) with open(path_to_embedded_profile, 'rb') as embedded_file: h.update(embedded_file.read()) if not path.exists(path.abspath(path.join(self.path_to_ios_build, '..', '.template', 'ios-signing-cache'))): os.makedirs(path.abspath(path.join(self.path_to_ios_build, '..', '.template', 'ios-signing-cache'))) cache_file = path.abspath(path.join(self.path_to_ios_build, '..', '.template', 'ios-signing-cache', h.hexdigest())) # XXX: Currently cache file is never saved, see below. if cache_file is not None and path.exists(cache_file): with temp_file() as resource_rules_temp: shutil.copy2(path.join(path_to_app, 'ResourceRules.plist'), resource_rules_temp) zip_to_extract = ZipFile(cache_file) zip_to_extract.extractall(path_to_app) zip_to_extract.close() shutil.copy2(resource_rules_temp, path.join(path_to_app, 'ResourceRules.plist')) return # Remote LOG.info('Sending app to remote server for codesigning. Uploading may take some time.') # Zip up app with temp_file() as app_zip_file: if cache_file is None: with ZipFile(app_zip_file, 'w', compression=ZIP_DEFLATED) as app_zip: for root, dirs, files in os.walk(path_to_app, topdown=False): for file in files: app_zip.write(path.join(root, file), path.join(root[len(path_to_app):], file)) os.remove(path.join(root, file)) for dir in dirs: os.rmdir(path.join(root, dir)) else: with ZipFile(app_zip_file, 'w', compression=ZIP_DEFLATED) as app_zip: app_zip.write(path.join(path_to_app, 'Forge'), 'Forge') app_zip.write(path.join(path_to_app, 'Info.plist'), 'Info.plist') app_zip.write(path_to_embedded_profile, 'embedded.mobileprovision') with temp_file() as tweaked_resource_rules: import biplist rules = biplist.readPlist(path.join(path_to_app, 'ResourceRules.plist')) # Don't sign anything rules['rules']['.*'] = False with open(tweaked_resource_rules, 'wb') as tweaked_resource_rules_file: biplist.writePlist(rules, tweaked_resource_rules_file) app_zip.write(tweaked_resource_rules, 'ResourceRules.plist') from poster.encode import multipart_encode from poster.streaminghttp import register_openers import urllib2 class FileWithProgress: def __init__(self, path, flags): self.total_size = os.path.getsize(path) self.file = open(path, flags) self.name = self.file.name self.path = path self.amount_read = 0; self.last_progress = 0; def read(self, length): data = self.file.read(length) if data != "": self.amount_read = self.amount_read + len(data) # TODO: Nicer progress output progress = 10*self.amount_read/self.total_size if progress > self.last_progress: self.last_progress = progress LOG.info(str(10*progress) + " percent uploaded: "+self.path) else: self.file.close() return data def fileno(self): return self.file.fileno() def seek(self, pos): return self.file.seek(pos) files = { 'app': FileWithProgress(app_zip_file, 'rb'), 'entitlements': FileWithProgress(entitlements_file, 'rb'), 'certificate': FileWithProgress(certificate_path, 'rb'), 'password': certificate_password } # Register the streaming http handlers with urllib2 register_openers() # headers contains the necessary Content-Type and Content-Length # datagen is a generator object that yields the encoded parameters datagen, headers = multipart_encode(files) # Create the Request object request = urllib2.Request("https://trigger.io/codesign/sign", datagen, headers) with temp_file() as signed_zip_file: resp = urllib2.urlopen(request) # Read the log lines from the start of the response while True: data = resp.readline() if data == "--failure\n": raise IOSError("Remote codesign failed") elif data == "--data\n" or data == "": break LOG.info(data.rstrip('\r\n')) # Read the binary data from the 2nd part of the response # TODO: Chunked download and progress with open(signed_zip_file, 'wb') as signed_zip: signed_zip.write(resp.read()) # Unzip response zip_to_extract = ZipFile(signed_zip_file) zip_to_extract.extractall(path_to_app) zip_to_extract.close() # XXX: Caching currently disabled as Info.plist changes on every build """if cache_file is not None: shutil.copy2(signed_zip_file, cache_file)""" LOG.info('Signed app received, continuing with packaging.') else: # Local codesign = self._check_for_codesign() resource_rules = path.abspath(path.join(path_to_app, 'ResourceRules.plist')) run_shell(codesign, '--force', '--preserve-metadata', '--entitlements', entitlements_file, '--sign', certificate, '--resource-rules={0}'.format(resource_rules), path_to_app)
def run_iphone_device(self, build, device, provisioning_profile, certificate=None, certificate_path=None, certificate_password=None): path_to_app, app_folder_name = self._locate_device_app(build, "Couldn't find iOS app to run on a device") LOG.debug("Signing {app}".format(app=path_to_app)) plist_str = self._grab_plist_from_binary_mess(build, provisioning_profile) plist_dict = self._parse_plist(plist_str) self.check_plist_dict(build, plist_dict, self.path_to_ios_build) self.provisioning_profile = plist_dict LOG.info("Plist OK") certificate = self._select_certificate(certificate) self.log_profile() if sys.platform.startswith('darwin'): with temp_file() as temp_file_path: self._create_entitlements_file(build, temp_file_path, plist_dict) self._sign_app(build=build, provisioning_profile=provisioning_profile, certificate=certificate, entitlements_file=temp_file_path, ident=_generate_package_name(build) ) ios_deploy = [ensure_lib_available(build, 'ios-deploy'), '-d', '-u', '-t', '10', '-b', path_to_app] if device and device.lower() != 'device': # specific device given ios_deploy.append('-i') ios_deploy.append(device) LOG.info('Installing app on device {device}: is it connected?'.format(device=device)) else: LOG.info('Installing app on device: is it connected?') def filter_and_combine(logline): return logline.rstrip() env = deepcopy(os.environ) run_shell(*ios_deploy, fail_silently=False, command_log_level=logging.INFO, filter=filter_and_combine, check_for_interrupt=True, env=env) elif sys.platform.startswith('win'): with temp_file() as ipa_path: self.create_ipa_from_app( build=build, provisioning_profile=provisioning_profile, output_path_for_ipa=ipa_path, certificate_path=certificate_path, certificate_password=certificate_password, ) win_ios_install = [ensure_lib_available(build, 'win-ios-install.exe')] if device and device.lower() != 'device': # pacific device given win_ios_install.append(device) LOG.info('Installing app on device {device}: is it connected?'.format(device=device)) else: LOG.info('Installing app on device: is it connected?') win_ios_install.append(ipa_path) win_ios_install.append(_generate_package_name(build)) run_shell(*win_ios_install, fail_silently=False, command_log_level=logging.INFO, check_for_interrupt=True) else: if not which('ideviceinstaller'): raise Exception("Can't find ideviceinstaller - is it installed and on your PATH?") with temp_file() as ipa_path: self.create_ipa_from_app( build=build, provisioning_profile=provisioning_profile, output_path_for_ipa=ipa_path, certificate_path=certificate_path, certificate_password=certificate_password, ) linux_ios_install = ['ideviceinstaller'] if device and device.lower() != 'device': # pacific device given linux_ios_install.append('-U') linux_ios_install.append(device) LOG.info('Installing app on device {device}: is it connected?'.format(device=device)) else: LOG.info('Installing app on device: is it connected?') linux_ios_install.append('-i') linux_ios_install.append(ipa_path) run_shell(*linux_ios_install, fail_silently=False, command_log_level=logging.INFO, check_for_interrupt=True) LOG.info('App installed, you will need to run the app on the device manually.')
def run_android(build, build_type_dir, sdk, device, interactive=True, purge=False): # TODO: remove sdk parameter from here and call sites, information is # contained in build.tool_config already # TODO: remove build_type_dir from method and call sites, doesn't seem to # be used anywhere # TODO: remove interactive parameter. this information is contained in the # build, but we should never use this anyway, as we can now interact with # the toolkit from here path_info = _find_or_install_sdk(build) LOG.info('Starting ADB if not running') run_detached([path_info.adb, 'start-server'], wait=True) LOG.info('Looking for Android device') available_devices = _get_available_devices(path_info) if not available_devices and device and device.lower() == 'emulator': LOG.info('Using android emulator') _create_avd_if_necessary(path_info) _launch_avd(path_info) return run_android(build, build_type_dir, sdk, device, interactive=interactive) if not available_devices: _prompt_user_to_attach_device(path_info) return run_android(build, build_type_dir, sdk, device, interactive=interactive) if device and device == 'emulator': emulators = [d for d in available_devices if d.startswith('emulator')] if not emulators: LOG.info('No emulator found') _create_avd_if_necessary(path_info) _launch_avd(path_info) return run_android(build, build_type_dir, sdk, device, interactive=interactive) else: device = emulators[0] if device: if device in available_devices: chosen_device = device LOG.info('Using specified android device %s' % chosen_device) else: LOG.error('No such device "%s"' % device) LOG.error('The available devices are:') LOG.error("\n".join(available_devices)) raise AndroidError else: chosen_device = available_devices[0] LOG.info('No android device specified, defaulting to %s' % chosen_device) with temp_file() as out_apk_name: create_apk(build, sdk, out_apk_name, interactive=interactive) package_name = _generate_package_name(build) # If required remove previous installs from device if purge: _run_adb([path_info.adb, 'uninstall', package_name], 30, path_info) # Install APK to device LOG.info('Installing apk') proc_std = _run_adb([ path_info.adb, '-s', chosen_device, 'install', '-r', out_apk_name ], 60, path_info) LOG.debug(proc_std) # Start app on device proc_std = _run_adb([ path_info.adb, '-s', chosen_device, 'shell', 'am', 'start', '-n', package_name + '/io.trigger.forge.android.core.ForgeActivity' ], 60, path_info) LOG.debug(proc_std) #follow log _follow_log(path_info, chosen_device)
def run_idevice(self, build, device, provisioning_profile, certificate=None, certificate_path=None, certificate_password=None): possible_app_location = '{0}/ios/device-*/'.format( self.path_to_ios_build) LOG.debug('Looking for apps at {0}'.format(possible_app_location)) possible_apps = glob(possible_app_location) if not possible_apps: raise IOSError("Couldn't find iOS app to run on a device") path_to_app = possible_apps[0] LOG.debug("Signing {app}".format(app=path_to_app)) plist_str = self._grab_plist_from_binary_mess(build, provisioning_profile) plist_dict = self._parse_plist(plist_str) self.check_plist_dict(plist_dict, self.path_to_ios_build) self.provisioning_profile = plist_dict LOG.info("Plist OK") certificate = self._select_certificate(certificate) self.log_profile() if sys.platform.startswith('darwin'): with temp_file() as temp_file_path: self._create_entitlements_file(build, temp_file_path, plist_dict) self._sign_app(build=build, provisioning_profile=provisioning_profile, certificate=certificate, entitlements_file=temp_file_path, ident=_generate_package_name(build)) fruitstrap = [ ensure_lib_available(build, 'fruitstrap'), '-d', '-u', '-t', '10', '-b', path_to_app ] if device and device.lower() != 'device': # pacific device given fruitstrap.append('-i') fruitstrap.append(device) LOG.info('Installing app on device {device}: is it connected?'. format(device=device)) else: LOG.info('Installing app on device: is it connected?') def filter_and_combine(logline): return logline.rstrip() ensure_lib_available(build, 'lldb_framework.zip', extract=True) env = deepcopy(os.environ) env['PATH'] = os.path.dirname(ensure_lib_available( build, 'lldb')) + ":" + env['PATH'] run_shell(*fruitstrap, fail_silently=False, command_log_level=logging.INFO, filter=filter_and_combine, check_for_interrupt=True, env=env) elif sys.platform.startswith('win'): with temp_file() as ipa_path: self.create_ipa_from_app( build=build, provisioning_profile=provisioning_profile, output_path_for_ipa=ipa_path, certificate_path=certificate_path, certificate_password=certificate_password, ) win_ios_install = [ ensure_lib_available(build, 'win-ios-install.exe') ] if device and device.lower() != 'device': # pacific device given win_ios_install.append(device) LOG.info( 'Installing app on device {device}: is it connected?'. format(device=device)) else: LOG.info('Installing app on device: is it connected?') win_ios_install.append(ipa_path) win_ios_install.append(_generate_package_name(build)) run_shell(*win_ios_install, fail_silently=False, command_log_level=logging.INFO, check_for_interrupt=True) else: if not which('ideviceinstaller'): raise Exception( "Can't find ideviceinstaller - is it installed and on your PATH?" ) with temp_file() as ipa_path: self.create_ipa_from_app( build=build, provisioning_profile=provisioning_profile, output_path_for_ipa=ipa_path, certificate_path=certificate_path, certificate_password=certificate_password, ) linux_ios_install = ['ideviceinstaller'] if device and device.lower() != 'device': # pacific device given linux_ios_install.append('-U') linux_ios_install.append(device) LOG.info( 'Installing app on device {device}: is it connected?'. format(device=device)) else: LOG.info('Installing app on device: is it connected?') linux_ios_install.append('-i') linux_ios_install.append(ipa_path) run_shell(*linux_ios_install, fail_silently=False, command_log_level=logging.INFO, check_for_interrupt=True) LOG.info( 'App installed, you will need to run the app on the device manually.' )
def create_ipa_from_app(self, build, provisioning_profile, output_path_for_ipa, certificate_to_sign_with=None, relative_path_to_itunes_artwork=None, certificate_path=None, certificate_password=None, output_path_for_manifest=None): """Create an ipa from an app, with an embedded provisioning profile provided by the user, and signed with a certificate provided by the user. :param build: instance of build :param provisioning_profile: Absolute path to the provisioning profile to embed in the ipa :param output_path_for_ipa: Path to save the created IPA :param certificate_to_sign_with: (Optional) The name of the certificate to sign the ipa with :param relative_path_to_itunes_artwork: (Optional) A path to a 512x512 png picture for the App view in iTunes. This should be relative to the location of the user assets. """ LOG.info('Starting package process for iOS') directory = path.dirname(output_path_for_ipa) if not path.isdir(directory): os.makedirs(directory) app_folder_name = self._locate_ios_app( error_message="Couldn't find iOS app in order to sign it") path_to_app = path.abspath( path.join(self.path_to_ios_build, 'ios', app_folder_name)) LOG.info('Going to package: %s' % path_to_app) plist_str = self._grab_plist_from_binary_mess(build, provisioning_profile) plist_dict = self._parse_plist(plist_str) self.check_plist_dict(plist_dict, self.path_to_ios_build) self.provisioning_profile = plist_dict LOG.info("Plist OK") # use distribution cert automatically if PP is distribution certificate_to_sign_with = self._select_certificate( certificate_to_sign_with) self.log_profile() seed_id = self._extract_seed_id() LOG.debug("Extracted seed ID: {0}".format(seed_id)) with lib.temp_dir() as temp_dir: LOG.debug('Making Payload directory') os.mkdir(path.join(temp_dir, 'Payload')) path_to_payload = path.abspath(path.join(temp_dir, 'Payload')) path_to_payload_app = path.abspath( path.join(path_to_payload, app_folder_name)) if relative_path_to_itunes_artwork is not None: path_to_itunes_artwork = path.join( path_to_payload_app, 'assets', 'src', relative_path_to_itunes_artwork) else: path_to_itunes_artwork = None with temp_file() as temp_file_path: self._create_entitlements_file(build, temp_file_path, plist_dict) self._sign_app(build=build, provisioning_profile=provisioning_profile, certificate=certificate_to_sign_with, entitlements_file=temp_file_path, certificate_path=certificate_path, certificate_password=certificate_password, ident=_generate_package_name(build)) shutil.copytree( path_to_app, path.join(path_to_payload, path.basename(path_to_app))) if path_to_itunes_artwork: shutil.copy2(path_to_itunes_artwork, path.join(temp_dir, 'iTunesArtwork')) with ZipFile(output_path_for_ipa, 'w', compression=ZIP_DEFLATED) as out_ipa: for root, dirs, files in os.walk(temp_dir): for file in files: LOG.debug('adding to IPA: {file}'.format( file=path.join(root, file), )) out_ipa.write(path.join(root, file), path.join(root[len(temp_dir):], file)) LOG.info("created IPA: {output}".format(output=output_path_for_ipa)) if output_path_for_manifest and self.plist_supports_wireless_distribution( plist_dict): LOG.info( "Provisioning profile supports wireless distributions, creating manifest: %s" % output_path_for_manifest) # Based on https://help.apple.com/iosdeployment-apps/#app43ad78b3 manifest = { "items": [{ "assets": [{ "kind": "software-package", "url": "http://www.example.com/app.ipa" }, { "kind": "display-image", "needs-shine": True, "url": "http://www.example.com/image.57x57.png", }, { "kind": "full-size-image", "needs-shine": True, "url": "http://www.example.com/image.512x512.jpg", }], "metadata": { "bundle-identifier": _generate_package_name(build), "bundle-version": build.config['version'], "kind": "software", "title": build.config['name'] } }] } with open(output_path_for_manifest, 'wb') as manifest_file: plistlib.writePlist(manifest, manifest_file) return output_path_for_ipa