Ejemplo n.º 1
0
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,
		)
Ejemplo n.º 2
0
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")},
        )
Ejemplo n.º 3
0
	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"
		)
Ejemplo n.º 4
0
	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)
Ejemplo n.º 5
0
 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")
Ejemplo n.º 6
0
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,
    )
Ejemplo n.º 7
0
	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)
Ejemplo n.º 8
0
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")},
		)
Ejemplo n.º 9
0
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,
	)
Ejemplo n.º 10
0
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")},
        )
Ejemplo n.º 11
0
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,
        )
Ejemplo n.º 12
0
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")},
		)
Ejemplo n.º 13
0
    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)
Ejemplo n.º 14
0
    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)
Ejemplo n.º 15
0
	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)
Ejemplo n.º 16
0
	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)