Esempio n. 1
0
def ensure_xcode_dependencies():
    """
    Ensure that a valid version of Xcode and command line tools are installed.
    """
    msg_intro = 'Running your app in the simulator or on a developer device ' \
                'requires Xcode to be installed on your machine (version ' \
                '%s or higher).' % MIN_XCODE_VERSION

    install_instruct = 'We couldn\'t find an installation, would you like ' \
                       'to open the Mac App Store and install it now? [Y/n]: '

    upgrade_instruct = 'You must upgrade your version of Xcode. Would you ' \
                       'like to open the Mac App Store and install it now?' \
                       ' [Y/n]: '

    if not command_line_tools_installed():
        install_tools = yn('We need to install the Xcode command line tools. ' \
                           'Continue? [Y/n]: ')
        if install_tools:
            subprocess.call(['xcode-select', '--install'])
            wait_for_tools_install()
        else:
            sys.exit(1)

    # Check if xcode build is installed
    if not xcode_is_installed():
        if yn('%s %s' % (msg_intro, install_instruct)):
            open_mac_app_store()
        sys.exit(1)

    if not xcode_version_valid():
        if yn('%s %s' % (msg_intro, upgrade_instruct)):
            open_mac_app_store()
        sys.exit(1)
Esempio n. 2
0
def ensure_xcode_dependencies():
    """
    Ensure that a valid version of Xcode and command line tools are installed.
    """
    msg_intro = 'Running your app in the simulator or on a developer device ' \
                'requires Xcode to be installed on your machine (version ' \
                '%s or higher).' % MIN_XCODE_VERSION

    install_instruct = 'We couldn\'t find an installation, would you like ' \
                       'to open the Mac App Store and install it now? [Y/n]: '

    upgrade_instruct = 'You must upgrade your version of Xcode. Would you ' \
                       'like to open the Mac App Store and install it now?' \
                       ' [Y/n]: '

    if not command_line_tools_installed():
        install_tools = yn('We need to install the Xcode command line tools. ' \
                           'Continue? [Y/n]: ')
        if install_tools:
            subprocess.call(['xcode-select', '--install'])
            wait_for_tools_install()
        else:
            sys.exit(1)

    # Check if xcode build is installed
    if not xcode_is_installed():
        if yn('%s %s' % (msg_intro, install_instruct)):
            open_mac_app_store()
        sys.exit(1)

    if not xcode_version_valid():
        if yn('%s %s' % (msg_intro, upgrade_instruct)):
            open_mac_app_store()
        sys.exit(1)
Esempio n. 3
0
def ensure_base(version):
    # Make sure that the correct siphon-base package is installed for this
    # base version; if not, install it. Returns the location of the
    # base project.
    auth = Auth()
    conf = Config()
    siphon = Siphon(auth.auth_token)
    build = Build(conf.app_id, version)
    pkg_url = siphon.base_package_url(version)
    pkg_dest = Cache.base_package_path(version)

    puts(colored.yellow('Checking for required packages...'))
    content_length = siphon.content_length_for_version(version)

    if Cache.base_package_installed(version):
        cached_content_length = Cache.get_length_for_base_version(version)
        if content_length != cached_content_length:
            update_msg = 'Siphon needs to update some files in order to run ' \
                         'your app (%s). Proceed? ' \
                         '[Y/n]: ' % format_size(content_length)
            update = yn(update_msg)

            if not update:
                sys.exit(1)

            msg = 'Updating Siphon files for base version %s...' % version
            try:
                download_file(pkg_url, pkg_dest, msg)
                Cache.set_length_for_base_version(version, content_length)
            except KeyboardInterrupt:
                sys.exit(1)
            # Get rid of the old build dirs (they are now invalid)
            build.clean_builds()

    else:
        download_msg = 'Siphon needs to download some files in order to run ' \
                       'your app (%s). This will not be ' \
                       'required again unless an update is needed, or a ' \
                       'different base version is specified in ' \
                       'the Siphonfile of one of your apps. Proceed? ' \
                       '[Y/n]: ' % format_size(content_length)
        download = yn(download_msg)

        if not download:
            sys.exit(1)

        msg = 'Downloading compatibility files for base version ' \
            '%s...' % version
        try:
            download_file(pkg_url, pkg_dest, msg)
        except KeyboardInterrupt:
            sys.exit(1)
        Cache.set_length_for_base_version(version, content_length)
        build.clean_builds()
    build.ensure_build_dir()
Esempio n. 4
0
def ensure_publish_dir():
    # Make sure that a publish directory exists. If it does not, ask the
    # user if they would like to create one. After running the function
    # we should exit - we don't want to proceed with a publish if the
    # icons are dummy ones
    dir_exists = os.path.exists(PUBLISH_DIR)
    if not dir_exists:
        proceed = yn('A \'publish\' directory does not exist for this app. ' \
        'This folder will contain the icons for your app and is required ' \
        'for publishing. Would you like to create one now? [Y/n]: ')
        if not proceed:
            sys.exit(1)

        # Copy the icons over from our resources dir
        for platform in ALLOWED_PLATFORMS:
            icon_dir = os.path.join(PUBLISH_DIR, platform, 'icons')
            ensure_dir_exists(icon_dir)
            icon = os.path.join(CLI_RESOURCES, 'icons', platform, 'index.png')
            icon_dest = os.path.join(icon_dir, 'index.png')
            copyfile(icon, icon_dest)

        puts(colored.yellow('A \'publish\' directory has been created. ' \
            'This contains placeholder icons for your app. Please make ' \
            'sure you replace these with your own icons.'))
        sys.exit(0)
Esempio n. 5
0
def ensure_publish_dir():
    # Make sure that a publish directory exists. If it does not, ask the
    # user if they would like to create one. After running the function
    # we should exit - we don't want to proceed with a publish if the
    # icons are dummy ones
    dir_exists = os.path.exists(PUBLISH_DIR)
    if not dir_exists:
        proceed = yn('A \'publish\' directory does not exist for this app. ' \
        'This folder will contain the icons for your app and is required ' \
        'for publishing. Would you like to create one now? [Y/n]: ')
        if not proceed:
            sys.exit(1)

        # Copy the icons over from our resources dir
        for platform in ALLOWED_PLATFORMS:
            icon_dir = os.path.join(PUBLISH_DIR, platform, 'icons')
            ensure_dir_exists(icon_dir)
            icon = os.path.join(CLI_RESOURCES, 'icons', platform, 'index.png')
            icon_dest = os.path.join(icon_dir, 'index.png')
            copyfile(icon, icon_dest)

        puts(colored.yellow('A \'publish\' directory has been created. ' \
            'This contains placeholder icons for your app. Please make ' \
            'sure you replace these with your own icons.'))
        sys.exit(0)
Esempio n. 6
0
def prompt_beta_share_for_all(shared_with):
    msg = 'You are about to share the latest changes for this app. ' \
        'The following beta testers will be notified that there is an ' \
        'update available:\n\n'
    for obj in shared_with:
        msg += '    --> %s (%s)\n' % (obj['username'], obj['email'])
    msg += '\nContinue? [Y/n]: '
    return yn(msg)
Esempio n. 7
0
def prompt_beta_share_for_all(shared_with):
    msg = 'You are about to share the latest changes for this app. ' \
        'The following beta testers will be notified that there is an ' \
        'update available:\n\n'
    for obj in shared_with:
        msg += '    --> %s (%s)\n' % (obj['username'], obj['email'])
    msg += '\nContinue? [Y/n]: '
    return yn(msg)
Esempio n. 8
0
    def ensure_develop_dir(self):
        ensure_dir_exists(self.directory)
        # Make sure that the entry script is copied over
        scripts_dir = os.path.join(PACKAGER_RESOURCES, self.base_version,
                                   'scripts')
        scripts = os.listdir(scripts_dir)
        for s in scripts:
            src = os.path.join(scripts_dir, s)
            dest = os.path.join(self.directory, s)
            shutil.copyfile(src, dest)

        # We make sure that the required node modules are installed
        modules_installed = True
        dependencies = packager_dependencies(self.base_version)
        required_modules = list(dependencies.keys())
        try:
            modules_dir = os.path.join(self.directory, 'node_modules')
            node_modules = os.listdir(modules_dir)

            if not set(node_modules) >= set(required_modules):
                modules_installed = False
        except OSError:
            modules_installed = False

        if not modules_installed:
            proceed = yn('We need to download some dependencies. ' \
                         'This may take a few minutes. Proceed? [Y/n] ')
            if not proceed:
                sys.exit(1)
            with cd(self.directory):
                try:
                    npm = npm_cmd(self.base_version)
                    # We want to install react-native first so peer
                    # dependencies are met
                    required_modules.insert(0, required_modules.pop( \
                        required_modules.index('react-native')))
                    for m in required_modules:
                        version = dependencies[m]
                        is_repo = 'http' in version
                        print('Downloading %s...' % m)
                        if is_repo:
                            p = background_process('%s install %s' % \
                                               (npm, version),
                                               spinner=True)
                        else:
                            p = background_process('%s install ' \
                                               '%s@%s' % (npm, m, version),
                                               spinner=True)
                        out, err = p.communicate()
                        if p.returncode != 0:
                            print(err.decode())
                            sys.exit(1)
                    # Run the associated post-install script if it exists
                    if os.path.isfile('post-install.sh'):
                        print('Running postinstall script...')
                        background_process('sh post-install.sh', spinner=True)
                except KeyboardInterrupt:
                    sys.exit(1)
Esempio n. 9
0
def prompt_team_share(shared_with):
    msg = 'This will share a special team copy of your app with the person ' \
        'who owns the email address that you specified. They will be sent ' \
        'an email with an explanation and a link to accept the invite.'
    msg += '\n\nPlease read this page before you continue: %s' % \
        TEAM_SHARE_DOCS_URL
    if shared_with:
        msg += '\n\nThe follow users are currently active team members for ' \
            'this app: \n\n'
        for obj in shared_with:
            msg += '    --> %s (%s)' % (obj['username'], obj['email'])
    msg += '\n\nContinue? [Y/n]: '
    return yn(msg)
Esempio n. 10
0
def prompt_team_share(shared_with):
    msg = 'This will share a special team copy of your app with the person ' \
        'who owns the email address that you specified. They will be sent ' \
        'an email with an explanation and a link to accept the invite.'
    msg += '\n\nPlease read this page before you continue: %s' % \
        TEAM_SHARE_DOCS_URL
    if shared_with:
        msg += '\n\nThe follow users are currently active team members for ' \
            'this app: \n\n'
        for obj in shared_with:
            msg += '    --> %s (%s)' % (obj['username'], obj['email'])
    msg += '\n\nContinue? [Y/n]: '
    return yn(msg)
Esempio n. 11
0
def prompt_beta_share_for_specific_email(shared_with):
    msg = 'This will share a read-only copy of your app with the person who ' \
        'owns the email address that you specified.\n\nThey will be sent an ' \
        'email explaining how to download the Siphon Sandbox and run your ' \
        'app. If they do not yet have a Siphon account, they will be ' \
        'prompted to create one.'
    if shared_with:
        msg += '\n\nIn addition, the following active beta testers will '  \
            'receive the latest app changes:\n\n'
        for obj in shared_with:
            msg += '    --> %s (%s)' % (obj['username'], obj['email'])
    msg += '\n\nContinue? [Y/n]: '
    return yn(msg)
Esempio n. 12
0
def clear_builds():
    """
    Wipe any build directories for the app.
    """
    proceed = yn('This operation will remove any builds for this app. ' \
                 'The app will need to be rebuilt to run it again. ' \
                 'Proceed? [Y/n]: ')
    if proceed:
        conf = Config()
        build = Build(conf.app_id, '')
        build.clean_builds()
    else:
        return
Esempio n. 13
0
def prompt_beta_share_for_specific_email(shared_with):
    msg = 'This will share a read-only copy of your app with the person who ' \
        'owns the email address that you specified.\n\nThey will be sent an ' \
        'email explaining how to download the Siphon Sandbox and run your ' \
        'app. If they do not yet have a Siphon account, they will be ' \
        'prompted to create one.'
    if shared_with:
        msg += '\n\nIn addition, the following active beta testers will '  \
            'receive the latest app changes:\n\n'
        for obj in shared_with:
            msg += '    --> %s (%s)' % (obj['username'], obj['email'])
    msg += '\n\nContinue? [Y/n]: '
    return yn(msg)
Esempio n. 14
0
def clear_builds():
    """
    Wipe any build directories for the app.
    """
    proceed = yn('This operation will remove any builds for this app. ' \
                 'The app will need to be rebuilt to run it again. ' \
                 'Proceed? [Y/n]: ')
    if proceed:
        conf = Config()
        build = Build(conf.app_id, '')
        build.clean_builds()
    else:
        return
Esempio n. 15
0
def ensure_wwdr_cert():
    valid_cert_installed = valid_wwdr_cert_installed()
    if not valid_cert_installed:
        puts(colored.red('No valid WWDR certificate detected.'))
        proceed = yn('We need to download and install a new one '  \
          'in your keychain ' \
          '(See https://developer.apple.com/support/certificates/expiration/ ' \
          'for more details).\n'
          'Proceed? [Y/n]: ')
        if proceed:
            try:
                print('Downloading and installing new certificate...')
                add_wwdr_cert()
                print('Certificate installed successfully')
                return True
            except KeyboardInterrupt:
                return False
    else:
        return True
Esempio n. 16
0
def ensure_wwdr_cert():
    valid_cert_installed = valid_wwdr_cert_installed()
    if not valid_cert_installed:
        puts(colored.red('No valid WWDR certificate detected.'))
        proceed = yn('We need to download and install a new one '  \
          'in your keychain ' \
          '(See https://developer.apple.com/support/certificates/expiration/ ' \
          'for more details).\n'
          'Proceed? [Y/n]: ')
        if proceed:
            try:
                print('Downloading and installing new certificate...')
                add_wwdr_cert()
                print('Certificate installed successfully')
                return True
            except KeyboardInterrupt:
                return False
    else:
        return True
Esempio n. 17
0
def ensure_node(version):
    if not node_cmd(version):
        # Neither a valid global or siphon installation was found, so we
        # must download the correct binary.
        version_exists = NODE_BINARIES.get(version)
        if not version_exists:
            puts(colored.red('Base version not supported. Please set ' \
                 'the "base_version" value in your app\'s Siphonfile to one ' \
                 'of the following: '))

            for k in reversed(sorted(list(NODE_BINARIES.keys()))):
                print(k)
            sys.exit(1)

        if get_platform_name() != PLATFORM_DARWIN:
            raise SiphonClientException('Node not supported on platform.')

        url = NODE_BINARIES[version]['darwin-64']['url']
        node_size = format_size(get_download_size(url))
        proceed = yn('We need to download Node.js & npm (we won\'t override ' \
                    'any current installations you may have). ' \
                    'These are required to run the packager and download ' \
                    'any dependencies we need. Download? (%s) ' \
                    '[Y/n] ' % node_size)

        if not proceed:
            sys.exit(1)
        version_dest = os.path.join(NODE_DESTINATION, version)
        ensure_dir_exists(version_dest)
        dest = os.path.join(version_dest, os.path.basename(url))
        download_file(url, dest, 'Downloading Node.js & npm...')
        print('Installing node...')
        tf = tarfile.open(dest, 'r:gz')
        content_dir = os.path.join(version_dest,
                  NODE_BINARIES[version]['darwin-64']['content'])
        tf.extractall(version_dest)
        move_contents_to_parent(content_dir)
        print('Installation successful.')
Esempio n. 18
0
def archive(sim, arch_dir, project_dir):
    """
    Handle archiving in a user-friendly way
    """
    proceed = yn('A build is required for this app. ' \
                 'This step is needed if you haven\'t run this ' \
                 'app on this particular simulator before, ' \
                 'you have changed the name or base version, or ' \
                 'if an update has recently been performed. ' \
                 'This may take a few minutes. Proceed? ' \
                 '[Y/n]: ')
    if proceed:
        try:
            app = sim.archive(project_dir, arch_dir)
            return app
        except SiphonSimulatorException:
            cleanup_dir(arch_dir)
            sys.exit(1)
        except KeyboardInterrupt:
            puts(colored.red('\nArchiving interrupted'))
            cleanup_dir(arch_dir)
            sys.exit(1)
    else:
        sys.exit(1)
Esempio n. 19
0
def archive(sim, arch_dir, project_dir):
    """
    Handle archiving in a user-friendly way
    """
    proceed = yn('A build is required for this app. ' \
                 'This step is needed if you haven\'t run this ' \
                 'app on this particular simulator before, ' \
                 'you have changed the name or base version, or ' \
                 'if an update has recently been performed. ' \
                 'This may take a few minutes. Proceed? ' \
                 '[Y/n]: ')
    if proceed:
        try:
            app = sim.archive(project_dir, arch_dir)
            return app
        except SiphonSimulatorException:
            cleanup_dir(arch_dir)
            sys.exit(1)
        except KeyboardInterrupt:
            puts(colored.red('\nArchiving interrupted'))
            cleanup_dir(arch_dir)
            sys.exit(1)
    else:
        sys.exit(1)
Esempio n. 20
0
def publish(platform):
    # Ensure that a publish directory exists
    ensure_publish_dir()
    ensure_platform_keys()

    # Load the app config and wrapper
    auth = Auth()
    conf = Config()
    siphon = Siphon(auth.auth_token)

    mixpanel_props = {
        'upgrade_required': False,
        'platform': platform
    }

    # We first check that the user has a valid subscription, because we're
    # going to do an implicit push before submitting, and it would otherwise
    # be confusing.
    puts(colored.yellow('Checking your account...'))
    if not siphon.user_can_publish():
        mixpanel_props['upgrade_required'] = True
        mixpanel_event(MIXPANEL_EVENT_PUBLISH, properties=mixpanel_props)
        prompt_for_upgrade()
        sys.exit(1)
    else:
        puts(colored.green('Your account is ready for publishing.'))

    mixpanel_event(MIXPANEL_EVENT_PUBLISH, properties=mixpanel_props)
    # It looks like the user can publish, so we do an implicit push
    # and validate the app.
    app_valid = validate_app([platform])
    if not app_valid:
        # The last push before publishing should be a successful one
        sys.exit(1)

    # Check if this submission requires a hard update, and if so then
    # prompt the user before going ahead with the submit so they understand.
    hard_update = siphon.app_requires_hard_update(conf.app_id, platform)
    if hard_update:
        if not prompt_for_hard_update(platform):
            return
    else:
        puts(colored.green('This update can be published without changing ' \
            'the app binary. It will be available straight away.'))

    # Prompt for platform info and do the actual submission
    user, password = None, None
    if hard_update:
        user, password = prompt_for_platform_info(platform)

    if hard_update:
        if not yn('Your app is about to be processed and submitted to the ' \
        'store. Are you sure you want to continue? [Y/n]: '):
            sys.exit(0)
    else:
        if not yn('Your app update is about to be submitted and the users ' \
        'of your app will receive it as an over-the-air update. Are you ' \
        'sure you want to continue? [Y/n]: '):
            sys.exit(0)

    puts(colored.yellow('Submitting...'))
    siphon.submit(conf.app_id, platform, username=user, password=password)

    puts(colored.green('\nThanks! Your app is in the queue. We will send ' \
        'status updates to your registered email address.'))
    print('Please note that after this submission has been released, you ' \
        'can push instant updates to your users by running the ' \
        '"siphon publish" command again.')
Esempio n. 21
0
def play(device, dev_mode=False, platform='ios'):
    """
    Takes a device object and runs an app
    """
    conf = Config()
    auth = Auth()
    siphon = Siphon(auth.auth_token)

    # Mixpanel tracking
    current_app_data = siphon.get_app(conf.app_id)
    mixpanel_props = {
        'app_id': conf.app_id,
        'app_name': current_app_data['name'],
    }
    mixpanel_event(MIXPANEL_EVENT_PLAY, properties=mixpanel_props)

    # The app needs to be built (archived) if:
    #
    #   1. An archive for this base version doesn't exist
    #   2. An archive exists and the display name has changed
    #   3. An archive exists and the facebook app id has changed
    #   4. An archive exists and the provisioning profile used to create it
    #      is not installed or is not compatible with the connected device.
    #      (device.profile_synchronised() is False.)
    #
    # After each build, we cache the build info and use it for future
    # reference. If build info is not stored for this app and base version,
    # we build the app anyway and cache the build info.

    # Push the app and fetch the updated settings if successful
    push(track_event=False)
    updated_app_data = siphon.get_app(conf.app_id)
    version = updated_app_data['base_version']

    # Create a Build instance for the app
    build = Build(conf.app_id, version, dev_mode)

    # Make sure the correct base version is installed and the build
    # directory exists
    ensure_base(version)
    build.ensure_build_dir()

    # The new display name defaults to the app name if it is not set
    display_name = updated_app_data.get('display_name')
    if not display_name:
        display_name = updated_app_data.get('name')

    facebook_app_id = updated_app_data.get('facebook_app_id')

    bundle_id = 'com.getsiphon.%s' % conf.app_id

    # Initialize a BaseConfig instance that will be used to configure
    # the base project we're archiving.
    base_conf = BaseConfig(display_name, bundle_id,
                           facebook_app_id=facebook_app_id)

    # Get the cached build info for the previous build
    cache = Cache()
    build_info_updated = cache.build_info_updated(build.build_id,
                                                display_name, facebook_app_id)

    # If the build info has been updated, we clear the old archives
    if build_info_updated:
        build.clean_archives()

    # If the provisioning profile needs updating, then we clear the archive
    # of the device build only. (The profile doesn't affect
    # other builds for the simulator).

    # Is the installed profile compatible with the connected device?
    profile_synchronised = device.profile_synchronised()
    if not profile_synchronised:
        build.clean_archive(device.formatted_name)
        # Sort out a new provisioning profile that includes the device
        device.update_requirements()

    # Was previous build performed with the installed profile? If we're
    # here then a compatible profile is installed.
    build_profile_ok = device.build_profile_ok(build.build_id)
    if not build_profile_ok:
        build.clean_archive(device.formatted_name)

    # If an archive doesn't exist at this point (perhaps it never existed,
    # or was deleted when invalidated above) we need to rebuild.
    if not build.archived(device.formatted_name):
        archive = True
    else:
        archive = False

    arch_dir = build.archive_dir_path(device.formatted_name)

    # Archiving takes a while, so prompt the user
    if archive:
        proceed = yn('A build is required for this app. ' \
                     'This step is needed if you haven\'t run this ' \
                     'app on the device before, ' \
                     'you have changed the name or base version, or ' \
                     'if an update has recently been performed. ' \
                     'This may take a few minutes. Proceed? ' \
                     '[Y/n]: ')
        if proceed:
            try:
                provisioning_profile = cache.get_provisioning_profile_id()
                base = BaseProject(build.project_dir(platform))

                with base.configure(base_conf):
                    app = device.archive(
                        base.directory,
                        arch_dir,
                        conf.app_id,
                        auth.auth_token,
                        provisioning_profile,
                        dev_mode
                    )

                # Update the cached build info
                cache.set_build_info(build.build_id, display_name,
                                     facebook_app_id)
                # Record the provisioning profile used to archive this app
                cache.set_build_profile(build.build_id, provisioning_profile)
            except SiphonDeviceException:
                cleanup_dir(arch_dir)
                sys.exit(1)
            except KeyboardInterrupt:
                puts(colored.red('\nArchiving interrupted'))
                cleanup_dir(arch_dir)
                sys.exit(1)
        else:
            sys.exit(1)
    else:
        app = device.app_path(arch_dir)

    device.run(app)
Esempio n. 22
0
def publish(platform):
    # Ensure that a publish directory exists
    ensure_publish_dir()
    ensure_platform_keys()

    # Load the app config and wrapper
    auth = Auth()
    conf = Config()
    siphon = Siphon(auth.auth_token)

    mixpanel_props = {'upgrade_required': False, 'platform': platform}

    # We first check that the user has a valid subscription, because we're
    # going to do an implicit push before submitting, and it would otherwise
    # be confusing.
    puts(colored.yellow('Checking your account...'))
    if not siphon.user_can_publish():
        mixpanel_props['upgrade_required'] = True
        mixpanel_event(MIXPANEL_EVENT_PUBLISH, properties=mixpanel_props)
        prompt_for_upgrade()
        sys.exit(1)
    else:
        puts(colored.green('Your account is ready for publishing.'))

    mixpanel_event(MIXPANEL_EVENT_PUBLISH, properties=mixpanel_props)
    # It looks like the user can publish, so we do an implicit push
    # and validate the app.
    app_valid = validate_app([platform])
    if not app_valid:
        # The last push before publishing should be a successful one
        sys.exit(1)

    # Check if this submission requires a hard update, and if so then
    # prompt the user before going ahead with the submit so they understand.
    hard_update = siphon.app_requires_hard_update(conf.app_id, platform)
    if hard_update:
        if not prompt_for_hard_update(platform):
            return
    else:
        puts(colored.green('This update can be published without changing ' \
            'the app binary. It will be available straight away.'))

    # Prompt for platform info and do the actual submission
    user, password = None, None
    if hard_update:
        user, password = prompt_for_platform_info(platform)

    if hard_update:
        if not yn('Your app is about to be processed and submitted to the ' \
        'store. Are you sure you want to continue? [Y/n]: '):
            sys.exit(0)
    else:
        if not yn('Your app update is about to be submitted and the users ' \
        'of your app will receive it as an over-the-air update. Are you ' \
        'sure you want to continue? [Y/n]: '):
            sys.exit(0)

    puts(colored.yellow('Submitting...'))
    siphon.submit(conf.app_id, platform, username=user, password=password)

    puts(colored.green('\nThanks! Your app is in the queue. We will send ' \
        'status updates to your registered email address.'))
    print('Please note that after this submission has been released, you ' \
        'can push instant updates to your users by running the ' \
        '"siphon publish" command again.')
Esempio n. 23
0
def play(device, dev_mode=False, platform='ios'):
    """
    Takes a device object and runs an app
    """
    conf = Config()
    auth = Auth()
    siphon = Siphon(auth.auth_token)

    # Mixpanel tracking
    current_app_data = siphon.get_app(conf.app_id)
    mixpanel_props = {
        'app_id': conf.app_id,
        'app_name': current_app_data['name'],
    }
    mixpanel_event(MIXPANEL_EVENT_PLAY, properties=mixpanel_props)

    # The app needs to be built (archived) if:
    #
    #   1. An archive for this base version doesn't exist
    #   2. An archive exists and the display name has changed
    #   3. An archive exists and the facebook app id has changed
    #   4. An archive exists and the provisioning profile used to create it
    #      is not installed or is not compatible with the connected device.
    #      (device.profile_synchronised() is False.)
    #
    # After each build, we cache the build info and use it for future
    # reference. If build info is not stored for this app and base version,
    # we build the app anyway and cache the build info.

    # Push the app and fetch the updated settings if successful
    push(track_event=False)
    updated_app_data = siphon.get_app(conf.app_id)
    version = updated_app_data['base_version']

    # Create a Build instance for the app
    build = Build(conf.app_id, version, dev_mode)

    # Make sure the correct base version is installed and the build
    # directory exists
    ensure_base(version)
    build.ensure_build_dir()

    # The new display name defaults to the app name if it is not set
    display_name = updated_app_data.get('display_name')
    if not display_name:
        display_name = updated_app_data.get('name')

    facebook_app_id = updated_app_data.get('facebook_app_id')

    bundle_id = 'com.getsiphon.%s' % conf.app_id

    # Initialize a BaseConfig instance that will be used to configure
    # the base project we're archiving.
    base_conf = BaseConfig(display_name,
                           bundle_id,
                           facebook_app_id=facebook_app_id)

    # Get the cached build info for the previous build
    cache = Cache()
    build_info_updated = cache.build_info_updated(build.build_id, display_name,
                                                  facebook_app_id)

    # If the build info has been updated, we clear the old archives
    if build_info_updated:
        build.clean_archives()

    # If the provisioning profile needs updating, then we clear the archive
    # of the device build only. (The profile doesn't affect
    # other builds for the simulator).

    # Is the installed profile compatible with the connected device?
    profile_synchronised = device.profile_synchronised()
    if not profile_synchronised:
        build.clean_archive(device.formatted_name)
        # Sort out a new provisioning profile that includes the device
        device.update_requirements()

    # Was previous build performed with the installed profile? If we're
    # here then a compatible profile is installed.
    build_profile_ok = device.build_profile_ok(build.build_id)
    if not build_profile_ok:
        build.clean_archive(device.formatted_name)

    # If an archive doesn't exist at this point (perhaps it never existed,
    # or was deleted when invalidated above) we need to rebuild.
    if not build.archived(device.formatted_name):
        archive = True
    else:
        archive = False

    arch_dir = build.archive_dir_path(device.formatted_name)

    # Archiving takes a while, so prompt the user
    if archive:
        proceed = yn('A build is required for this app. ' \
                     'This step is needed if you haven\'t run this ' \
                     'app on the device before, ' \
                     'you have changed the name or base version, or ' \
                     'if an update has recently been performed. ' \
                     'This may take a few minutes. Proceed? ' \
                     '[Y/n]: ')
        if proceed:
            try:
                provisioning_profile = cache.get_provisioning_profile_id()
                base = BaseProject(build.project_dir(platform))

                with base.configure(base_conf):
                    app = device.archive(base.directory, arch_dir, conf.app_id,
                                         auth.auth_token, provisioning_profile,
                                         dev_mode)

                # Update the cached build info
                cache.set_build_info(build.build_id, display_name,
                                     facebook_app_id)
                # Record the provisioning profile used to archive this app
                cache.set_build_profile(build.build_id, provisioning_profile)
            except SiphonDeviceException:
                cleanup_dir(arch_dir)
                sys.exit(1)
            except KeyboardInterrupt:
                puts(colored.red('\nArchiving interrupted'))
                cleanup_dir(arch_dir)
                sys.exit(1)
        else:
            sys.exit(1)
    else:
        app = device.app_path(arch_dir)

    device.run(app)