def run_ios(build, device): runner = IOSRunner(path.abspath('development')) if not device or device.lower() == 'simulator': LOG.info('Running iOS Simulator') runner.run_iphone_simulator(build) else: LOG.info('Running on iOS device: {device}'.format(device=device)) certificate_to_sign_with = build.tool_config.get('ios.profile.developer_certificate') provisioning_profile = build.tool_config.get('ios.profile.provisioning_profile') if not provisioning_profile: lib.local_config_problem( build, message="You must specify a provisioning profile.", examples={ "ios.profiles.DEFAULT.provisioning_profile": os.path.abspath("/path/to/embedded.profile") }, more_info="http://current-docs.trigger.io/command-line.html#local-conf-ios" ) certificate_path = build.tool_config.get('ios.profile.developer_certificate_path') certificate_password = build.tool_config.get('ios.profile.developer_certificate_password') runner.run_idevice( build=build, device=device, provisioning_profile=provisioning_profile, certificate=certificate_to_sign_with, certificate_path=certificate_path, certificate_password=certificate_password, )
def _install_sdk_automatically(build): # Attempt download temp_d = tempfile.mkdtemp() try: LOG.info('Downloading Android SDK (about 30MB, may take some time)') if sys.platform.startswith('win'): path_info = _download_sdk_for_windows(temp_d) elif sys.platform.startswith('darwin'): path_info = _download_sdk_for_mac(temp_d) elif sys.platform.startswith('linux'): path_info = _download_sdk_for_linux(temp_d) _update_sdk(path_info) LOG.info('Android SDK update complete') return path_info except Exception, e: LOG.error(e) lib.local_config_problem( build, message= "Automatic SDK download failed, please install manually from %s and specify where it is in your local config." % _android_sdk_url(), examples={"android.sdk": path.abspath("/path/to/android-sdk")}, )
def _missing_provisioning_profile(self, build, path_to_pp): lib.local_config_problem( build, message="Couldn't find the specified provisioning profile at {path}".format(path=path_to_pp), examples={ "ios.profiles.DEFAULT.provisioning_profile": os.path.abspath("/path/to/embedded.profile") }, more_info="http://current-docs.trigger.io/command-line.html#local-conf-ios" )
def run_iphone_simulator(self, build): if not sys.platform.startswith('darwin'): lib.local_config_problem( build, message="iOS Simulator is only available on OS X, please change the iOS run settings in your local config to 'device' or a specific device.", examples={ "ios.device": "device", }, more_info="http://current-docs.trigger.io/tools/local-config.html#ios" ) possible_app_location = '{0}/ios/simulator-*/'.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 it in the simulator") path_to_app = possible_apps[0] LOG.debug('Trying to run app %s' % path_to_app) if path.exists(SIMULATOR_IN_42): LOG.debug("Detected XCode version 4.2 or older") ios_sim_binary = "ios-sim-xc4.2" elif path.exists(SIMULATOR_IN_43): LOG.debug("Detected XCode version 4.3 or newer") ios_sim_binary = "ios-sim-xc4.3" else: raise IOSError("Couldn't find iOS simulator in {old} or {new}, if you want to use the iOS simulator then you need to install XCode".format( old=SIMULATOR_IN_42, new=SIMULATOR_IN_43, )) def could_not_start_simulator(line): return line.startswith("[DEBUG] Could not start simulator") try: logfile = tempfile.mkstemp()[1] process_group = ProcessGroup() LOG.info('Starting simulator') process_group.spawn( [path.join(self._lib_path(), ios_sim_binary), "launch", path_to_app, '--stderr', logfile], fail_if=could_not_start_simulator ) LOG.info('Showing log output:') process_group.spawn( ["tail", "-f", logfile], command_log_level=logging.INFO ) process_group.wait_for_success() finally: os.remove(logfile)
def _missing_provisioning_profile(self, build, path_to_pp): lib.local_config_problem( build, message="Couldn't find the specified provisioning profile at {path}" .format(path=path_to_pp), examples={ "ios.profiles.DEFAULT.provisioning_profile": os.path.abspath("/path/to/embedded.profile") }, more_info= "http://current-docs.trigger.io/command-line.html#local-conf-ios")
def package_ios(build): provisioning_profile = build.tool_config.get( 'ios.profile.provisioning_profile') if not provisioning_profile: lib.local_config_problem( build, message="You must specify a provisioning profile.", examples={ "ios.profiles.DEFAULT.provisioning_profile": os.path.abspath("/path/to/embedded.profile") }, more_info= "http://current-docs.trigger.io/tools/local-config.html#ios") # raise IOSError("see http://current-docs.trigger.io/tools/local-config.html#ios") certificate_to_sign_with = build.tool_config.get( 'ios.profile.developer_certificate') certificate_path = build.tool_config.get( 'ios.profile.developer_certificate_path', '') certificate_password = build.tool_config.get( 'ios.profile.developer_certificate_password', '') runner = IOSRunner(path.abspath('development')) try: relative_path_to_itunes_artwork = build.config['plugins']['icons'][ 'config']['ios']['512'] except KeyError: relative_path_to_itunes_artwork = None file_name = "{name}-{time}".format(name=re.sub( "[^a-zA-Z0-9]", "", build.config["name"].lower()), time=str(int(time.time()))) output_path_for_ipa = path.abspath( path.join('release', 'ios', file_name + '.ipa')) output_path_for_manifest = path.abspath( path.join('release', 'ios', file_name + '-WirelessDistribution.plist')) runner.create_ipa_from_app( build=build, provisioning_profile=provisioning_profile, certificate_to_sign_with=certificate_to_sign_with, relative_path_to_itunes_artwork=relative_path_to_itunes_artwork, output_path_for_ipa=output_path_for_ipa, certificate_path=certificate_path, certificate_password=certificate_password, output_path_for_manifest=output_path_for_manifest, )
def run_iphone_simulator(self, build): if not sys.platform.startswith('darwin'): lib.local_config_problem( build, message="iOS Simulator is only available on OS X, please change the iOS run settings in your local config to 'device' or a specific device.", examples={ "ios.device": "device", }, more_info="http://current-docs.trigger.io/tools/local-config.html#ios" ) possible_app_location = '{0}/ios/simulator-*/'.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 it in the simulator") path_to_app = possible_apps[0] LOG.debug('trying to run app %s' % path_to_app) if path.exists(SIMULATOR_IN_42): LOG.debug("Detected XCode version 4.2 or older") ios_sim_binary = "ios-sim-xc4.2" elif path.exists(SIMULATOR_IN_43): LOG.debug("Detected XCode version 4.3 or newer") ios_sim_binary = "ios-sim-xc4.3" else: raise IOSError("Couldn't find iOS simulator in {old} or {new}, if you want to use the iOS simulator then you need to install XCode".format( old=SIMULATOR_IN_42, new=SIMULATOR_IN_43, )) logfile = tempfile.mkstemp()[1] ios_sim_proc = subprocess.Popen([path.join(self._lib_path(), ios_sim_binary), "launch", path_to_app, '--stderr', logfile]) LOG.info('Showing log output:') try: run_shell("tail", "-f", logfile, fail_silently=False, command_log_level=logging.INFO, check_for_interrupt=True) finally: lib.progressive_kill(ios_sim_proc.pid) os.remove(logfile)
def _find_or_install_sdk(build): """Searches for and returns the details of an Android SDK already existing on the operating system, otherwise presents the user with a choice to install one and returns the details of that after doing so. :param build: Contains information about the system, e.g. user specific SDK Returns a PathInfo object constructed using the SDK found or installed. """ already_installed = _search_for_sdk(build) if already_installed: return already_installed if _ask_user_if_should_install_sdk(): return _install_sdk_automatically(build) else: lib.local_config_problem( build, message="Couldn't find Android SDK, please set this in your local config for this app.", examples={"android.sdk": path.abspath("/path/to/android-sdk")}, )
def package_ios(build): provisioning_profile = build.tool_config.get('ios.profile.provisioning_profile') if not provisioning_profile: lib.local_config_problem( build, message="You must specify a provisioning profile.", examples={ "ios.profiles.DEFAULT.provisioning_profile": os.path.abspath("/path/to/embedded.profile") }, more_info="http://current-docs.trigger.io/tools/local-config.html#ios" ) # raise IOSError("see http://current-docs.trigger.io/tools/local-config.html#ios") certificate_to_sign_with = build.tool_config.get('ios.profile.developer_certificate') certificate_path = build.tool_config.get('ios.profile.developer_certificate_path', '') certificate_password = build.tool_config.get('ios.profile.developer_certificate_password', '') runner = IOSRunner(path.abspath('development')) try: relative_path_to_itunes_artwork = build.config['plugins']['icons']['config']['ios']['512'] except KeyError: relative_path_to_itunes_artwork = None file_name = "{name}-{time}".format( name=re.sub("[^a-zA-Z0-9]", "", build.config["name"].lower()), time=str(int(time.time())) ) output_path_for_ipa = path.abspath(path.join('release', 'ios', file_name+'.ipa')) output_path_for_manifest = path.abspath(path.join('release', 'ios', file_name+'-WirelessDistribution.plist')) runner.create_ipa_from_app( build=build, provisioning_profile=provisioning_profile, certificate_to_sign_with=certificate_to_sign_with, relative_path_to_itunes_artwork=relative_path_to_itunes_artwork, output_path_for_ipa=output_path_for_ipa, certificate_path=certificate_path, certificate_password=certificate_password, output_path_for_manifest=output_path_for_manifest, )
def _find_or_install_sdk(build): """Searches for and returns the details of an Android SDK already existing on the operating system, otherwise presents the user with a choice to install one and returns the details of that after doing so. :param build: Contains information about the system, e.g. user specific SDK Returns a PathInfo object constructed using the SDK found or installed. """ already_installed = _search_for_sdk(build) if already_installed: return already_installed if _ask_user_if_should_install_sdk(): return _install_sdk_automatically(build) else: lib.local_config_problem( build, message= "Couldn't find Android SDK, please set this in your local config for this app.", examples={"android.sdk": path.abspath("/path/to/android-sdk")}, )
def run_ios(build, device): runner = IOSRunner(path.abspath('development')) if not device or device.lower() == 'simulator': LOG.info('Running iOS Simulator') runner.run_iphone_simulator(build) else: LOG.info('Running on iOS device: {device}'.format(device=device)) certificate_to_sign_with = build.tool_config.get( 'ios.profile.developer_certificate') provisioning_profile = build.tool_config.get( 'ios.profile.provisioning_profile') if not provisioning_profile: lib.local_config_problem( build, message="You must specify a provisioning profile.", examples={ "ios.profiles.DEFAULT.provisioning_profile": os.path.abspath("/path/to/embedded.profile") }, more_info= "http://current-docs.trigger.io/command-line.html#local-conf-ios" ) certificate_path = build.tool_config.get( 'ios.profile.developer_certificate_path') certificate_password = build.tool_config.get( 'ios.profile.developer_certificate_password') runner.run_idevice( build=build, device=device, provisioning_profile=provisioning_profile, certificate=certificate_to_sign_with, certificate_path=certificate_path, certificate_password=certificate_password, )
def _install_sdk_automatically(build): # Attempt download temp_d = tempfile.mkdtemp() try: LOG.info('Downloading Android SDK (about 30MB, may take some time)') if sys.platform.startswith('win'): path_info = _download_sdk_for_windows(temp_d) elif sys.platform.startswith('darwin'): path_info = _download_sdk_for_mac(temp_d) elif sys.platform.startswith('linux'): path_info = _download_sdk_for_linux(temp_d) _update_sdk(path_info) LOG.info('Android SDK update complete') return path_info except Exception, e: LOG.error(e) lib.local_config_problem( build, message="Automatic SDK download failed, please install manually from %s and specify where it is in your local config." % _android_sdk_url(), examples={"android.sdk": path.abspath("/path/to/android-sdk")}, )
def run_iphone_simulator(self, build): if not sys.platform.startswith('darwin'): lib.local_config_problem( build, message= "iOS Simulator is only available on OS X, please change the iOS run settings in your local config to 'device' or a specific device.", examples={ "ios.device": "device", }, more_info= "http://current-docs.trigger.io/tools/local-config.html#ios") possible_app_location = '{0}/ios/simulator-*/'.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 it in the simulator") path_to_app = possible_apps[0] LOG.debug('Trying to run app %s' % path_to_app) if path.exists(SIMULATOR_IN_43): LOG.debug("Detected XCode version 4.3 or newer") ios_sim_binary = "ios-sim-xc4.3" elif path.exists(SIMULATOR_IN_42): LOG.debug("Detected XCode version 4.2 or older") ios_sim_binary = "ios-sim-xc4.2" else: raise IOSError( "Couldn't find iOS simulator in {old} or {new}, if you want to use the iOS simulator then you need to install XCode" .format( old=SIMULATOR_IN_42, new=SIMULATOR_IN_43, )) def could_not_start_simulator(line): return line.startswith("[DEBUG] Could not start simulator") try: logfile = tempfile.mkstemp()[1] process_group = ProcessGroup() ios_sim_cmd = [ path.join(self._lib_path(), ios_sim_binary), "launch", path_to_app, '--stderr', logfile ] sdk = build.tool_config.get('ios.simulatorsdk') if sdk is not None: ios_sim_cmd = ios_sim_cmd + ['--sdk', sdk] family = build.tool_config.get('ios.simulatorfamily') if family is not None: ios_sim_cmd = ios_sim_cmd + ['--family', family] LOG.info('Starting simulator') process_group.spawn(ios_sim_cmd, fail_if=could_not_start_simulator) LOG.info('Showing log output:') process_group.spawn(["tail", "-f", logfile], command_log_level=logging.INFO) process_group.wait_for_success() finally: os.remove(logfile)
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= "http://current-docs.trigger.io/tools/ios-windows.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= "http://current-docs.trigger.io/tools/ios-windows.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 _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="http://current-docs.trigger.io/tools/ios-windows.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="http://current-docs.trigger.io/tools/ios-windows.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 _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="http://docs.trigger.io/en/v1.3/tools/ios-windows.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="http://docs.trigger.io/en/v1.3/tools/ios-windows.html", ) # 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: with ZipFile(app_zip_file, 'w') 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)) 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() 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)