Example #1
0
def create_apk(build, sdk, output_filename, interactive=True):
	'''
	:param output_filename: name of the file to which we'll write
	'''
	path_info = _find_or_install_sdk(build)
	jre = ""
 
	jre = _get_jre()

	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(zipf_name, path_info, package_name, lib_path, dev_dir)
		
		with temp_file() as signed_zipf_name:
			# Sign APK
			_sign_zipf_debug(lib_path, jre, zipf_name, signed_zipf_name)
			
			# Align APK
			_align_apk(path_info, signed_zipf_name, output_filename)
Example #2
0
def package_android(build):
    path_info = _find_or_install_sdk(build)

    lib_path = path.normpath(path.join(".template", "lib"))
    dev_dir = path.normpath(path.join("development", "android"))
    output = _generate_path_to_output_apk(build)
    signing_info = _lookup_or_prompt_for_signing_info(build)

    signing_info["keystore"] = lib.expand_relative_path(build, signing_info["keystore"])
    jre = _get_jre() or ""

    LOG.info("Creating Android .apk file")
    package_name = _generate_package_name(build)
    # zip
    with temp_file() as zipf_name:
        _create_apk_with_aapt(zipf_name, path_info, package_name, lib_path, dev_dir)

        with temp_file() as signed_zipf_name:
            # sign
            _sign_zipf_release(lib_path, jre, 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
Example #3
0
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 signed_zipf_name:
            #sign
            _sign_zipf_release(lib_path, jre, 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
Example #4
0
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 signed_zipf_name:
			#sign
			_sign_zipf_release(lib_path, jre, 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
Example #5
0
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 signed_zipf_name:
            # Sign APK
            _sign_zipf_debug(lib_path, jre, zipf_name, signed_zipf_name)

            # Align APK
            _align_apk(path_info, signed_zipf_name, output_filename)
Example #6
0
	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)
		LOG.info("Plist OK")
		
		if sys.platform.startswith('darwin'):
			with temp_file() as temp_file_path:
				self._create_entitlements_file(build, plist_dict, temp_file_path)
				
				self._sign_app(build=build,
					provisioning_profile=provisioning_profile,
					certificate=certificate,
					entitlements_file=temp_file_path,
				)
			
			fruitstrap = [ensure_lib_available(build, 'fruitstrap'), '-d', '-g', ensure_lib_available(build, 'gdb-arm-apple-darwin'), '-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?')

			run_shell(*fruitstrap, fail_silently=False, command_log_level=logging.INFO, filter=lambda x: not x.startswith("warning"), check_for_interrupt=True)
		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)
Example #7
0
def push_stream(build, stream_id):
	from forge import build_config
	manifest = dict()
	file_for_hash = dict()

	# TODO: Some kind of partial build (insert all.js references)
	
	src = os.path.join('development', 'reload', 'src')
	for root, dirs, files in os.walk(src):
		for filename in files:
				filename = os.path.join(root, filename)
				with open(filename, 'rb') as file:
					hash = hashlib.sha1(file.read()).hexdigest()
					manifest[filename[len(src)+1:].replace('\\','/')] = hash
					file_for_hash[hash] = filename

	remote_hashes = remote._api_post('reload/snapshots/filter', files={'manifest': StringIO(json.dumps(manifest))})
	if remote_hashes['result'] != 'ok':
		raise ReloadError("Remote hash filter failed")
	hashes_to_upload = set(remote_hashes['manifest'].values())

	with lib.temp_file() as zip_file_path:
		with ZipFile(zip_file_path, 'w') as zip_file:
			for hash in hashes_to_upload:
				zip_file.write(file_for_hash[hash], hash)

		with open(zip_file_path, 'rb') as zip_file:
			created = remote._api_post('reload/snapshots/%s/%s' % (build.config['uuid'], stream_id), files={'config': StringIO(json.dumps(build_config.load_app())), 'manifest': StringIO(json.dumps(manifest)), 'forge-deploy': zip_file})
			if created['result'] != 'ok':
				raise ReloadError("Remote snapshot creation failed")
	LOG.info("Pushed snapshot to stream '%s'" % stream_id)
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:
		_prompt_user_to_attach_device(path_info)

		return run_android(build, build_type_dir, sdk, device,
				interactive=interactive)

	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.template.LoadActivity'], 60, path_info)
	LOG.debug(proc_std)
	
	#follow log
	_follow_log(path_info, chosen_device)
Example #9
0
def push_stream(build, stream_id):
    from forge import build_config
    manifest = dict()
    file_for_hash = dict()

    # TODO: Some kind of partial build (insert all.js references)

    src = os.path.join('development', 'reload', 'src')
    for root, dirs, files in os.walk(src):
        for filename in files:
            filename = os.path.join(root, filename)
            with open(filename, 'rb') as file:
                hash = hashlib.sha1(file.read()).hexdigest()
                manifest[filename[len(src) + 1:].replace('\\', '/')] = hash
                file_for_hash[hash] = filename

    remote_hashes = remote._api_post(
        'reload/snapshots/filter',
        files={'manifest': StringIO(json.dumps(manifest))})
    if remote_hashes['result'] != 'ok':
        raise ReloadError("Remote hash filter failed")
    hashes_to_upload = set(remote_hashes['manifest'].values())

    with lib.temp_file() as zip_file_path:
        with ZipFile(zip_file_path, 'w') as zip_file:
            for hash in hashes_to_upload:
                zip_file.write(file_for_hash[hash], hash)

        with open(zip_file_path, 'rb') as zip_file:
            created = remote._api_post(
                'reload/snapshots/%s/%s' % (build.config['uuid'], stream_id),
                files={
                    'config': StringIO(json.dumps(build_config.load_app())),
                    'manifest': StringIO(json.dumps(manifest)),
                    'forge-deploy': zip_file
                })
            if created['result'] != 'ok':
                raise ReloadError("Remote snapshot creation failed")
    LOG.info("Pushed snapshot to stream '%s'" % stream_id)
Example #10
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)
Example #11
0
	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)
				
				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', '-g', '-i mi -q', '-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?')

			partial_line = ['']
			def filter_and_combine(logline):
				if logline.startswith('[') or logline.startswith('-'):
					return logline.rstrip()
				elif logline.startswith('@'):
					partial_line[0] += logline[2:-2]
					if partial_line[0].endswith('\\r\\n'):
						try:
							return partial_line[0][:-4]
						finally:
							partial_line[0] = ""

				return False

			run_shell(*fruitstrap, fail_silently=False, command_log_level=logging.INFO, filter=filter_and_combine, check_for_interrupt=True)
		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.')
Example #12
0
	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)
				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') 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']
				}
			}]}
			plistlib.writePlist(manifest, output_path_for_manifest)
			
		return output_path_for_ipa
Example #13
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)
Example #14
0
    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(provisioning_profile)
        plist_dict = self._parse_plist(plist_str)
        self.check_plist_dict(plist_dict, self.path_to_ios_build)
        LOG.info("Plist OK")

        if sys.platform.startswith('darwin'):
            with temp_file() as temp_file_path:
                self._create_entitlements_file(build, plist_dict,
                                               temp_file_path)

                self._sign_app(
                    build=build,
                    provisioning_profile=provisioning_profile,
                    certificate=certificate,
                    entitlements_file=temp_file_path,
                )

            fruitstrap = [
                ensure_lib_available(build, 'fruitstrap'), '-d', '-g',
                ensure_lib_available(build, 'gdb-arm-apple-darwin'), '-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?')

            run_shell(*fruitstrap,
                      fail_silently=False,
                      command_log_level=logging.INFO,
                      filter=lambda x: not x.startswith("warning"),
                      check_for_interrupt=True)
        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)
Example #15
0
    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):
        """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')

        if certificate_to_sign_with is None:
            certificate_to_sign_with = 'iPhone Developer'

        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(provisioning_profile)
        plist_dict = self._parse_plist(plist_str)
        self.check_plist_dict(plist_dict, self.path_to_ios_build)
        LOG.info("Plist OK")

        self.log_profile(plist_dict)

        seed_id = self._extract_seed_id(plist_dict)

        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, plist_dict,
                                               temp_file_path)
                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') as out_ipa:
                for root, dirs, files in os.walk(temp_dir):
                    for file in files:
                        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))
        return output_path_for_ipa
Example #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):
            raise IOSError(
                "{path} is not a provisioning_profile: "
                "use the --ios.profile.provisioning_profile option".format(
                    path=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:
                raise IOSError(
                    "To deploy iOS apps to a device, you must specify a "
                    "path to a certificate to sign with: "
                    "see http://docs.trigger.io/en/v1.3/tools/ios-windows.html"
                )

            if not certificate_password:
                raise IOSError(
                    "To deploy iOS apps to a device, you must specify a "
                    "the to unlock your certificate: "
                    "see 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)
Example #17
0
def run_android(build, build_type_dir, sdk, device, interactive=True, purge=False):
	if sdk:
		sdk = path.normpath(path.join(build.orig_wd, sdk))
	sdk = _check_for_sdk(sdk, interactive=interactive)
	jre = ""

	if not check_for_java():
		jres = _look_for_java()
		if not jres:
			raise AndroidError("Java not found: Java must be installed and available in your path in order to run Android")
		jre = path.join(jres[0], 'bin')

	path_info = _create_path_info_from_sdk(sdk)
	lib_path = path.normpath(path.join(build.orig_wd, '.template', 'lib'))
	dev_dir = path.normpath(path.join(build.orig_wd, 'development', 'android'))

	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:
		# TODO: allow for prompting of user in either webui situation or commandline situation
		if interactive:
			_prompt_user_to_attach_device(path_info)
		else:
			_create_avd_if_necessary(path_info)
			_launch_avd(path_info)

		return run_android(build, build_type_dir, sdk, device, interactive=interactive)

	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)
	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(zipf_name, path_info, package_name, lib_path, dev_dir)
		
		with temp_file() as signed_zipf_name:
			# Sign APK
			_sign_zipf_debug(lib_path, jre, zipf_name, signed_zipf_name)
			
			with temp_file() as out_apk_name:
				# Align APK
				_align_apk(sdk, signed_zipf_name, out_apk_name)

				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.template.LoadActivity'], 60, path_info)
				LOG.debug(proc_std)
				
				#follow log
				_follow_log(path_info, chosen_device)
Example #18
0
def package_android(build):
	sdk = build.tool_config.get('android.sdk')
	interactive = build.tool_config.get('general.interactive', True)
	if sdk:
		sdk = path.normpath(path.join(build.orig_wd, sdk))
	sdk = _check_for_sdk(sdk, interactive=interactive)
	keystore = build.tool_config.get('android.profile.keystore')
	storepass = build.tool_config.get('android.profile.storepass')
	keyalias = build.tool_config.get('android.profile.keyalias')
	keypass = build.tool_config.get('android.profile.keypass')

	path_info = _create_path_info_from_sdk(sdk)
	lib_path = path.normpath(path.join(build.orig_wd, '.template', 'lib'))
	dev_dir = path.normpath(path.join(build.orig_wd, 'development', 'android'))

	SigningInfoPrompt = namedtuple('SigningInfoPrompt', 'name description secure')
	signing_info = {}
	file_name = "{name}-{time}.apk".format(
		name=re.sub("[^a-zA-Z0-9]", "", build.config["name"].lower()),
		time=str(int(time.time()))
	)
	output = path.abspath(path.join('release', 'android', file_name))
	
	if not interactive and not all((keystore, storepass, keyalias, keypass)):
		raise AndroidError("When running in non-interactive mode, keystore, storepass, keyalias and keypass arguments must be supplied")
	
	signing_info["keystore"] = keystore
	signing_info["storepass"] = storepass
	signing_info["keyalias"] = keyalias
	signing_info["keypass"] = keypass
	
	if interactive:
		signing_prompts = (
			SigningInfoPrompt(name="keystore", description="the location of your release keystore", secure=False),
			SigningInfoPrompt(name="storepass", description="the password of your release keystore", secure=True),
			SigningInfoPrompt(name="keyalias", description="the alias of your release key", secure=False),
			SigningInfoPrompt(name="keypass", description="the password for your release key", secure=True),
		)
		for prompt in signing_prompts:
			if signing_info[prompt.name]:
				# value given supplied in configuration or on command line
				continue
				
			response = ""
			while not response:
				msg = "Please enter {0}: ".format(prompt.description)
				if prompt.secure:
					response = getpass(msg)
				else:
					response = raw_input(msg)
			signing_info[prompt.name] = response
	
	# need to make fix paths which may be relative to original build directory
	if sdk:
		sdk = path.normpath(path.join(build.orig_wd, sdk))
	signing_info["keystore"] = path.normpath(path.join(build.orig_wd, signing_info["keystore"]))
	
	sdk = _check_for_sdk(sdk, interactive=interactive)
	jre = ""

	if not check_for_java():
		jres = _look_for_java()
		if not jres:
			raise AndroidError("Java not found: Java must be installed and available in your path in order to create Android packages.")
		jre = path.join(jres[0], 'bin')

	LOG.info('Creating Android .apk file')
	package_name = _generate_package_name(build)
	#zip
	with temp_file() as zipf_name:
		_create_apk_with_aapt(zipf_name, path_info, package_name, lib_path, dev_dir)

		with temp_file() as signed_zipf_name:
			#sign
			_sign_zipf_release(lib_path, jre, zipf_name, signed_zipf_name, **signing_info)

			# create output directory for APK if necessary
			_create_output_directory(output)

			#align
			_align_apk(sdk, signed_zipf_name, output)
			LOG.debug('removing zipfile and un-aligned APK')

			LOG.info("created APK: {output}".format(output=output))
			return output
Example #19
0
    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)
                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') 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']
                    }
                }]
            }
            plistlib.writePlist(manifest, output_path_for_manifest)

        return output_path_for_ipa
Example #20
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)
Example #21
0
	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):
		"""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')
		
		if certificate_to_sign_with is None:
			certificate_to_sign_with = 'iPhone Developer'

		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)
		LOG.info("Plist OK")
		
		self.log_profile(plist_dict)
		
		seed_id = self._extract_seed_id(plist_dict)
		
		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, plist_dict, temp_file_path)
				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') 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))
		return output_path_for_ipa
Example #22
0
    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)

                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', '-g', '-i mi -q', '-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?')

            partial_line = ['']

            def filter_and_combine(logline):
                if logline.startswith('[') or logline.startswith('-'):
                    return logline.rstrip()
                elif logline.startswith('@'):
                    partial_line[0] += logline[2:-2]
                    if partial_line[0].endswith('\\r\\n'):
                        try:
                            return partial_line[0][:-4]
                        finally:
                            partial_line[0] = ""

                return False

            run_shell(*fruitstrap,
                      fail_silently=False,
                      command_log_level=logging.INFO,
                      filter=filter_and_combine,
                      check_for_interrupt=True)
        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.'
                )
Example #23
0
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)