Esempio n. 1
0
    def __init__(self):

        argv = sys.argv
        # Buildozer used to pass these arguments in a now-invalid order
        # If that happens, apply this fix
        # This fix will be removed once a fixed buildozer is released
        if (len(argv) > 2 and
            argv[1].startswith('--color') and
            argv[2].startswith('--storage-dir')):
            argv.append(argv.pop(1))  # the --color arg
            argv.append(argv.pop(1))  # the --storage-dir arg

        parser = NoAbbrevParser(
            description=('A packaging tool for turning Python scripts and apps '
                         'into Android APKs'))

        generic_parser = argparse.ArgumentParser(
            add_help=False,
            description=('Generic arguments applied to all commands'))
        dist_parser = argparse.ArgumentParser(
            add_help=False,
            description=('Arguments for dist building'))

        generic_parser.add_argument(
            '--debug', dest='debug', action='store_true',
            default=False,
            help='Display debug output and all build info')
        generic_parser.add_argument(
            '--color', dest='color', choices=['always', 'never', 'auto'],
            help='Enable or disable color output (default enabled on tty)')
        generic_parser.add_argument(
            '--sdk-dir', '--sdk_dir', dest='sdk_dir', default='',
            help='The filepath where the Android SDK is installed')
        generic_parser.add_argument(
            '--ndk-dir', '--ndk_dir', dest='ndk_dir', default='',
            help='The filepath where the Android NDK is installed')
        generic_parser.add_argument(
            '--android-api', '--android_api', dest='android_api', default=0, type=int,
            help='The Android API level to build against.')
        generic_parser.add_argument(
            '--ndk-version', '--ndk_version', dest='ndk_version', default='',
            help=('The version of the Android NDK. This is optional, '
                  'we try to work it out automatically from the ndk_dir.'))
        generic_parser.add_argument(
            '--symlink-java-src', '--symlink_java_src',
            action='store_true',
            dest='symlink_java_src',
            default=False,
            help=('If True, symlinks the java src folder during build and dist '
                  'creation. This is useful for development only, it could also '
                  'cause weird problems.'))

        default_storage_dir = user_data_dir('python-for-android')
        if ' ' in default_storage_dir:
            default_storage_dir = '~/.python-for-android'
        generic_parser.add_argument(
            '--storage-dir', dest='storage_dir',
            default=default_storage_dir,
            help=('Primary storage directory for downloads and builds '
                  '(default: {})'.format(default_storage_dir)))

        generic_parser.add_argument(
            '--arch',
            help='The archs to build for, separated by commas.',
            default='armeabi')

        # Options for specifying the Distribution
        generic_parser.add_argument(
            '--dist-name', '--dist_name',
            help='The name of the distribution to use or create',
            default='')

        generic_parser.add_argument(
            '--requirements',
            help=('Dependencies of your app, should be recipe names or '
                  'Python modules'),
            default='')

        generic_parser.add_argument(
            '--bootstrap',
            help='The bootstrap to build with. Leave unset to choose automatically.',
            default=None)

        generic_parser.add_argument(
            '--hook',
            help='Filename to a module that contain python-for-android hooks',
            default=None)

        add_boolean_option(
            generic_parser, ["force-build"],
            default=False,
            description='Whether to force compilation of a new distribution:')

        add_boolean_option(
            generic_parser, ["require-perfect-match"],
            default=False,
            description=('Whether the dist recipes must perfectly match '
                         'those requested'))

        generic_parser.add_argument(
            '--local-recipes', '--local_recipes',
            dest='local_recipes', default='./p4a-recipes',
            help='Directory to look for local recipes')

        generic_parser.add_argument(
            '--java-build-tool',
            dest='java_build_tool', default='auto',
            choices=['auto', 'ant', 'gradle', 'none'],
            help=('The java build tool to use when packaging the APK, defaults '
                  'to automatically selecting an appropriate tool.'))

        add_boolean_option(
            generic_parser, ['copy-libs'],
            default=False,
            description='Copy libraries instead of using biglink (Android 4.3+)')

        self._read_configuration()

        subparsers = parser.add_subparsers(dest='subparser_name',
                                           help='The command to run')

        def add_parser(subparsers, *args, **kwargs):
            '''
            argparse in python2 doesn't support the aliases option,
            so we just don't provide the aliases there.
            '''
            if 'aliases' in kwargs and sys.version_info.major < 3:
                kwargs.pop('aliases')
            return subparsers.add_parser(*args, **kwargs)

        parser_recipes = add_parser(subparsers,
            'recipes',
            parents=[generic_parser],
            help='List the available recipes')
        parser_recipes.add_argument(
                "--compact", action="store_true", default=False,
                help="Produce a compact list suitable for scripting")

        parser_bootstraps = add_parser(
            subparsers, 'bootstraps',
            help='List the available bootstraps',
            parents=[generic_parser])
        parser_clean_all = add_parser(
            subparsers, 'clean_all',
            aliases=['clean-all'],
            help='Delete all builds, dists and caches',
            parents=[generic_parser])
        parser_clean_dists = add_parser(
            subparsers,
            'clean_dists', aliases=['clean-dists'],
            help='Delete all dists',
            parents=[generic_parser])
        parser_clean_bootstrap_builds = add_parser(
            subparsers,
            'clean_bootstrap_builds', aliases=['clean-bootstrap-builds'],
            help='Delete all bootstrap builds',
            parents=[generic_parser])
        parser_clean_builds = add_parser(
            subparsers,
            'clean_builds', aliases=['clean-builds'],
            help='Delete all builds',
            parents=[generic_parser])

        parser_clean = add_parser(subparsers, 'clean',
                                  help='Delete build components.',
                                  parents=[generic_parser])
        parser_clean.add_argument(
            'component', nargs='+',
            help=('The build component(s) to delete. You can pass any '
                  'number of arguments from "all", "builds", "dists", '
                  '"distributions", "bootstrap_builds", "downloads".'))

        parser_clean_recipe_build = add_parser(subparsers,
            'clean_recipe_build', aliases=['clean-recipe-build'],
            help=('Delete the build components of the given recipe. '
                  'By default this will also delete built dists'),
            parents=[generic_parser])
        parser_clean_recipe_build.add_argument('recipe', help='The recipe name')
        parser_clean_recipe_build.add_argument('--no-clean-dists', default=False,
                                               dest='no_clean_dists',
                                               action='store_true',
                                               help='If passed, do not delete existing dists')

        parser_clean_download_cache= add_parser(subparsers,
            'clean_download_cache', aliases=['clean-download-cache'],
            help='Delete cached downloads for requirement builds',
            parents=[generic_parser])
        parser_clean_download_cache.add_argument(
            'recipes', nargs='*',
            help=('The recipes to clean (space-separated). If no recipe name is '
                  'provided, the entire cache is cleared.'))

        parser_export_dist = add_parser(subparsers,
            'export_dist', aliases=['export-dist'],
            help='Copy the named dist to the given path',
            parents=[generic_parser])
        parser_export_dist.add_argument('output_dir', help=('The output dir to copy to'))
        parser_export_dist.add_argument('--symlink', action='store_true',
                                        help=('Symlink the dist instead of copying'))

        parser_apk = add_parser(subparsers,
            'apk', help='Build an APK',
            parents=[generic_parser])
        parser_apk.add_argument('--release', dest='build_mode', action='store_const',
                        const='release', default='debug',
                        help='Build the PARSER_APK. in Release mode')
        parser_apk.add_argument('--keystore', dest='keystore', action='store', default=None,
                        help=('Keystore for JAR signing key, will use jarsigner '
                              'default if not specified (release build only)'))
        parser_apk.add_argument('--signkey', dest='signkey', action='store', default=None,
                        help='Key alias to sign PARSER_APK. with (release build only)')
        parser_apk.add_argument('--keystorepw', dest='keystorepw', action='store', default=None,
                        help='Password for keystore')
        parser_apk.add_argument('--signkeypw', dest='signkeypw', action='store', default=None,
                        help='Password for key alias')

        parser_create = add_parser(subparsers,
            'create', help='Compile a set of requirements into a dist',
            parents=[generic_parser])
        parser_archs = add_parser(subparsers,
            'archs', help='List the available target architectures',
            parents=[generic_parser])
        parser_distributions = add_parser(subparsers,
            'distributions', aliases=['dists'],
            help='List the currently available (compiled) dists',
            parents=[generic_parser])
        parser_delete_dist = add_parser(subparsers,
            'delete_dist', aliases=['delete-dist'], help='Delete a compiled dist',
            parents=[generic_parser])

        parser_sdk_tools = add_parser(subparsers,
            'sdk_tools', aliases=['sdk-tools'],
            help='Run the given binary from the SDK tools dis',
            parents=[generic_parser])
        parser_sdk_tools.add_argument(
            'tool', help=('The tool binary name to run'))

        parser_adb = add_parser(subparsers,
            'adb', help='Run adb from the given SDK',
            parents=[generic_parser])
        parser_logcat = add_parser(subparsers,
            'logcat', help='Run logcat from the given SDK',
            parents=[generic_parser])
        parser_build_status = add_parser(subparsers,
            'build_status', aliases=['build-status'],
            help='Print some debug information about current built components',
            parents=[generic_parser])

        parser.add_argument('-v', '--version', action='version', version=__version__)

        args, unknown = parser.parse_known_args(sys.argv[1:])
        args.unknown_args = unknown

        self.args = args

        if args.subparser_name is None:
            parser.print_help()
            exit(1)

        setup_color(args.color)

        if args.debug:
            logger.setLevel(logging.DEBUG)

        # strip version from requirements, and put them in environ
        if hasattr(args, 'requirements'):
            requirements = []
            for requirement in split_argument_list(args.requirements):
                if "==" in requirement:
                    requirement, version = requirement.split(u"==", 1)
                    os.environ["VERSION_{}".format(requirement)] = version
                    info('Recipe {}: version "{}" requested'.format(
                        requirement, version))
                requirements.append(requirement)
            args.requirements = u",".join(requirements)

        self.ctx = Context()
        self.storage_dir = args.storage_dir
        self.ctx.setup_dirs(self.storage_dir)
        self.sdk_dir = args.sdk_dir
        self.ndk_dir = args.ndk_dir
        self.android_api = args.android_api
        self.ndk_version = args.ndk_version
        self.ctx.symlink_java_src = args.symlink_java_src
        self.ctx.java_build_tool = args.java_build_tool

        self._archs = split_argument_list(args.arch)

        self.ctx.local_recipes = args.local_recipes
        self.ctx.copy_libs = args.copy_libs

        # Each subparser corresponds to a method
        getattr(self, args.subparser_name.replace('-', '_'))(args)
Esempio n. 2
0
    def __init__(self):
        parser = argparse.ArgumentParser(
                description="Tool for managing the Android / Python toolchain",
                usage="""toolchain <command> [<args>]

Available commands:
adb           Runs adb binary from the detected SDK dir
apk           Create an APK using the given distribution
bootstraps    List all the bootstraps available to build with.
build_status  Informations about the current build
create        Build an android project with all recipes
clean_all     Delete all build components
clean_builds  Delete all build caches
clean_dists   Delete all compiled distributions
clean_download_cache Delete any downloaded recipe packages
clean_recipe_build   Delete the build files of a recipe
distributions List all distributions
export_dist   Copies a created dist to an output directory
logcat        Runs logcat from the detected SDK dir
print_context_info   Prints debug informations
recipes       List all the available recipes
sdk_tools     Runs android binary from the detected SDK dir
symlink_dist  Symlinks a created dist to an output directory

Planned commands:
build_dist
""")
        parser.add_argument("command", help="Command to run")

        # General options
        parser.add_argument(
            '--debug', dest='debug', action='store_true',
            help='Display debug output and all build info')
        parser.add_argument(
            '--color', dest='color', choices=['always', 'never', 'auto'],
            help='Enable or disable color output (default enabled on tty)')
        parser.add_argument(
            '--sdk-dir', '--sdk_dir', dest='sdk_dir', default='',
            help='The filepath where the Android SDK is installed')
        parser.add_argument(
            '--ndk-dir', '--ndk_dir', dest='ndk_dir', default='',
            help='The filepath where the Android NDK is installed')
        parser.add_argument(
            '--android-api', '--android_api', dest='android_api', default=0, type=int,
            help='The Android API level to build against.')
        parser.add_argument(
            '--ndk-version', '--ndk_version', dest='ndk_version', default='',
            help=('The version of the Android NDK. This is optional, '
                  'we try to work it out automatically from the ndk_dir.'))
        parser.add_argument(
            '--storage-dir', dest='storage_dir',
            default=self.default_storage_dir,
            help=('Primary storage directory for downloads and builds '
                  '(default: {})'.format(self.default_storage_dir)))

        # AND: This option doesn't really fit in the other categories, the
        # arg structure needs a rethink
        parser.add_argument(
            '--arch',
            help='The archs to build for, separated by commas.',
            default='armeabi')

        # Options for specifying the Distribution
        parser.add_argument(
            '--dist-name', '--dist_name',
            help='The name of the distribution to use or create',
            default='')
        parser.add_argument(
            '--requirements',
            help=('Dependencies of your app, should be recipe names or '
                  'Python modules'),
            default='')

        add_boolean_option(
            parser, ["allow-download"],
            default=False,
            description='Whether to allow binary dist download:')

        add_boolean_option(
            parser, ["allow-build"],
            default=True,
            description='Whether to allow compilation of a new distribution:')

        add_boolean_option(
            parser, ["force-build"],
            default=False,
            description='Whether to force compilation of a new distribution:')

        parser.add_argument(
            '--extra-dist-dirs', '--extra_dist_dirs',
            dest='extra_dist_dirs', default='',
            help='Directories in which to look for distributions')

        add_boolean_option(
            parser, ["require-perfect-match"],
            default=False,
            description=('Whether the dist recipes must perfectly match '
                         'those requested'))

        parser.add_argument(
            '--local-recipes', '--local_recipes',
            dest='local_recipes', default='./p4a-recipes',
            help='Directory to look for local recipes')

        add_boolean_option(
            parser, ['copy-libs'],
            default=False,
            description='Copy libraries instead of using biglink (Android 4.3+)')

        self._read_configuration()

        args, unknown = parser.parse_known_args(sys.argv[1:])
        self.dist_args = args

        setup_color(args.color)

        info(''.join(
            [Err_Style.BRIGHT, Err_Fore.RED,
             'This python-for-android revamp is an experimental alpha release!',
             Err_Style.RESET_ALL]))
        info(''.join(
            [Err_Fore.RED,
             ('It should work (mostly), but you may experience '
              'missing features or bugs.'),
             Err_Style.RESET_ALL]))

        # strip version from requirements, and put them in environ
        requirements = []
        for requirement in split_argument_list(args.requirements):
            if "==" in requirement:
                requirement, version = requirement.split(u"==", 1)
                os.environ["VERSION_{}".format(requirement)] = version
                info('Recipe {}: version "{}" requested'.format(
                    requirement, version))
            requirements.append(requirement)
        args.requirements = u",".join(requirements)

        if args.debug:
            logger.setLevel(logging.DEBUG)

        self.ctx = Context()
        self.storage_dir = args.storage_dir
        self.ctx.setup_dirs(self.storage_dir)
        self.sdk_dir = args.sdk_dir
        self.ndk_dir = args.ndk_dir
        self.android_api = args.android_api
        self.ndk_version = args.ndk_version

        self._archs = split_argument_list(args.arch)

        # AND: Fail nicely if the args aren't handled yet
        if args.extra_dist_dirs:
            warning('Received --extra_dist_dirs but this arg currently is not '
                    'handled, exiting.')
            exit(1)
        if args.allow_download:
            warning('Received --allow_download but this arg currently is not '
                    'handled, exiting.')
            exit(1)
        # if args.allow_build:
        #     warning('Received --allow_build but this arg currently is not '
        #             'handled, exiting.')
        #     exit(1)

        command_method_name = args.command.replace('-', '_')

        if not hasattr(self, command_method_name):
            print('Unrecognized command')
            parser.print_help()
            exit(1)

        self.ctx.local_recipes = args.local_recipes
        self.ctx.copy_libs = args.copy_libs

        getattr(self, command_method_name)(unknown)
Esempio n. 3
0
    def __init__(self):

        argv = sys.argv
        self.warn_on_carriage_return_args(argv)
        # Buildozer used to pass these arguments in a now-invalid order
        # If that happens, apply this fix
        # This fix will be removed once a fixed buildozer is released
        if (len(argv) > 2
                and argv[1].startswith('--color')
                and argv[2].startswith('--storage-dir')):
            argv.append(argv.pop(1))  # the --color arg
            argv.append(argv.pop(1))  # the --storage-dir arg

        parser = NoAbbrevParser(
            description='A packaging tool for turning Python scripts and apps '
                        'into Android APKs')

        generic_parser = argparse.ArgumentParser(
            add_help=False,
            description='Generic arguments applied to all commands')
        argparse.ArgumentParser(
            add_help=False, description='Arguments for dist building')

        generic_parser.add_argument(
            '--debug', dest='debug', action='store_true', default=False,
            help='Display debug output and all build info')
        generic_parser.add_argument(
            '--color', dest='color', choices=['always', 'never', 'auto'],
            help='Enable or disable color output (default enabled on tty)')
        generic_parser.add_argument(
            '--sdk-dir', '--sdk_dir', dest='sdk_dir', default='',
            help='The filepath where the Android SDK is installed')
        generic_parser.add_argument(
            '--ndk-dir', '--ndk_dir', dest='ndk_dir', default='',
            help='The filepath where the Android NDK is installed')
        generic_parser.add_argument(
            '--android-api',
            '--android_api',
            dest='android_api',
            default=0,
            type=int,
            help=('The Android API level to build against defaults to {} if '
                  'not specified.').format(RECOMMENDED_TARGET_API))
        generic_parser.add_argument(
            '--ndk-version', '--ndk_version', dest='ndk_version', default=None,
            help=('DEPRECATED: the NDK version is now found automatically or '
                  'not at all.'))
        generic_parser.add_argument(
            '--ndk-api', type=int, default=None,
            help=('The Android API level to compile against. This should be your '
                  '*minimal supported* API, not normally the same as your --android-api. '
                  'Defaults to min(ANDROID_API, {}) if not specified.').format(RECOMMENDED_NDK_API))
        generic_parser.add_argument(
            '--symlink-java-src', '--symlink_java_src',
            action='store_true',
            dest='symlink_java_src',
            default=False,
            help=('If True, symlinks the java src folder during build and dist '
                  'creation. This is useful for development only, it could also'
                  ' cause weird problems.'))

        default_storage_dir = user_data_dir('python-for-android')
        if ' ' in default_storage_dir:
            default_storage_dir = '~/.python-for-android'
        generic_parser.add_argument(
            '--storage-dir', dest='storage_dir', default=default_storage_dir,
            help=('Primary storage directory for downloads and builds '
                  '(default: {})'.format(default_storage_dir)))

        generic_parser.add_argument(
            '--arch', help='The arch to build for.',
            default='armeabi-v7a')

        # Options for specifying the Distribution
        generic_parser.add_argument(
            '--dist-name', '--dist_name',
            help='The name of the distribution to use or create', default='')

        generic_parser.add_argument(
            '--requirements',
            help=('Dependencies of your app, should be recipe names or '
                  'Python modules. NOT NECESSARY if you are using '
                  'Python 3 with --use-setup-py'),
            default='')

        generic_parser.add_argument(
            '--recipe-blacklist',
            help=('Blacklist an internal recipe from use. Allows '
                  'disabling Python 3 core modules to save size'),
            dest="recipe_blacklist",
            default='')

        generic_parser.add_argument(
            '--blacklist-requirements',
            help=('Blacklist an internal recipe from use. Allows '
                  'disabling Python 3 core modules to save size'),
            dest="blacklist_requirements",
            default='')

        generic_parser.add_argument(
            '--bootstrap',
            help='The bootstrap to build with. Leave unset to choose '
                 'automatically.',
            default=None)

        generic_parser.add_argument(
            '--hook',
            help='Filename to a module that contains python-for-android hooks',
            default=None)

        add_boolean_option(
            generic_parser, ["force-build"],
            default=False,
            description='Whether to force compilation of a new distribution')

        add_boolean_option(
            generic_parser, ["require-perfect-match"],
            default=False,
            description=('Whether the dist recipes must perfectly match '
                         'those requested'))

        add_boolean_option(
            generic_parser, ["allow-replace-dist"],
            default=True,
            description='Whether existing dist names can be automatically replaced'
            )

        generic_parser.add_argument(
            '--local-recipes', '--local_recipes',
            dest='local_recipes', default='./p4a-recipes',
            help='Directory to look for local recipes')

        generic_parser.add_argument(
            '--java-build-tool',
            dest='java_build_tool', default='auto',
            choices=['auto', 'ant', 'gradle'],
            help=('The java build tool to use when packaging the APK, defaults '
                  'to automatically selecting an appropriate tool.'))

        add_boolean_option(
            generic_parser, ['copy-libs'],
            default=False,
            description='Copy libraries instead of using biglink (Android 4.3+)'
        )

        self._read_configuration()

        subparsers = parser.add_subparsers(dest='subparser_name',
                                           help='The command to run')

        def add_parser(subparsers, *args, **kwargs):
            """
            argparse in python2 doesn't support the aliases option,
            so we just don't provide the aliases there.
            """
            if 'aliases' in kwargs and sys.version_info.major < 3:
                kwargs.pop('aliases')
            return subparsers.add_parser(*args, **kwargs)

        add_parser(
            subparsers,
            'recommendations',
            parents=[generic_parser],
            help='List recommended p4a dependencies')
        parser_recipes = add_parser(
            subparsers,
            'recipes',
            parents=[generic_parser],
            help='List the available recipes')
        parser_recipes.add_argument(
            "--compact",
            action="store_true", default=False,
            help="Produce a compact list suitable for scripting")
        add_parser(
            subparsers, 'bootstraps',
            help='List the available bootstraps',
            parents=[generic_parser])
        add_parser(
            subparsers, 'clean_all',
            aliases=['clean-all'],
            help='Delete all builds, dists and caches',
            parents=[generic_parser])
        add_parser(
            subparsers, 'clean_dists',
            aliases=['clean-dists'],
            help='Delete all dists',
            parents=[generic_parser])
        add_parser(
            subparsers, 'clean_bootstrap_builds',
            aliases=['clean-bootstrap-builds'],
            help='Delete all bootstrap builds',
            parents=[generic_parser])
        add_parser(
            subparsers, 'clean_builds',
            aliases=['clean-builds'],
            help='Delete all builds',
            parents=[generic_parser])

        parser_clean = add_parser(
            subparsers, 'clean',
            help='Delete build components.',
            parents=[generic_parser])
        parser_clean.add_argument(
            'component', nargs='+',
            help=('The build component(s) to delete. You can pass any '
                  'number of arguments from "all", "builds", "dists", '
                  '"distributions", "bootstrap_builds", "downloads".'))

        parser_clean_recipe_build = add_parser(
            subparsers,
            'clean_recipe_build', aliases=['clean-recipe-build'],
            help=('Delete the build components of the given recipe. '
                  'By default this will also delete built dists'),
            parents=[generic_parser])
        parser_clean_recipe_build.add_argument(
            'recipe', help='The recipe name')
        parser_clean_recipe_build.add_argument(
            '--no-clean-dists', default=False,
            dest='no_clean_dists',
            action='store_true',
            help='If passed, do not delete existing dists')

        parser_clean_download_cache = add_parser(
            subparsers,
            'clean_download_cache', aliases=['clean-download-cache'],
            help='Delete cached downloads for requirement builds',
            parents=[generic_parser])
        parser_clean_download_cache.add_argument(
            'recipes',
            nargs='*',
            help='The recipes to clean (space-separated). If no recipe name is'
                  ' provided, the entire cache is cleared.')

        parser_export_dist = add_parser(
            subparsers,
            'export_dist', aliases=['export-dist'],
            help='Copy the named dist to the given path',
            parents=[generic_parser])
        parser_export_dist.add_argument('output_dir',
                                        help='The output dir to copy to')
        parser_export_dist.add_argument(
            '--symlink',
            action='store_true',
            help='Symlink the dist instead of copying')

        parser_apk = add_parser(
            subparsers,
            'apk', help='Build an APK',
            parents=[generic_parser])
        # This is actually an internal argument of the build.py
        # (see pythonforandroid/bootstraps/common/build/build.py).
        # However, it is also needed before the distribution is finally
        # assembled for locating the setup.py / other build systems, which
        # is why we also add it here:
        parser_apk.add_argument(
            '--private', dest='private',
            help='the directory with the app source code files' +
                 ' (containing your main.py entrypoint)',
            required=False, default=None)
        parser_apk.add_argument(
            '--release', dest='build_mode', action='store_const',
            const='release', default='debug',
            help='Build the PARSER_APK. in Release mode')
        parser_apk.add_argument(
            '--use-setup-py', dest="use_setup_py",
            action='store_true', default=False,
            help="Process the setup.py of a project if present. " +
                 "(Experimental!")
        parser_apk.add_argument(
            '--ignore-setup-py', dest="ignore_setup_py",
            action='store_true', default=False,
            help="Don't run the setup.py of a project if present. " +
                 "This may be required if the setup.py is not " +
                 "designed to work inside p4a (e.g. by installing " +
                 "dependencies that won't work or aren't desired " +
                 "on Android")
        parser_apk.add_argument(
            '--keystore', dest='keystore', action='store', default=None,
            help=('Keystore for JAR signing key, will use jarsigner '
                  'default if not specified (release build only)'))
        parser_apk.add_argument(
            '--signkey', dest='signkey', action='store', default=None,
            help='Key alias to sign PARSER_APK. with (release build only)')
        parser_apk.add_argument(
            '--keystorepw', dest='keystorepw', action='store', default=None,
            help='Password for keystore')
        parser_apk.add_argument(
            '--signkeypw', dest='signkeypw', action='store', default=None,
            help='Password for key alias')

        add_parser(
            subparsers,
            'create', help='Compile a set of requirements into a dist',
            parents=[generic_parser])
        add_parser(
            subparsers,
            'archs', help='List the available target architectures',
            parents=[generic_parser])
        add_parser(
            subparsers,
            'distributions', aliases=['dists'],
            help='List the currently available (compiled) dists',
            parents=[generic_parser])
        add_parser(
            subparsers,
            'delete_dist', aliases=['delete-dist'], help='Delete a compiled dist',
            parents=[generic_parser])

        parser_sdk_tools = add_parser(
            subparsers,
            'sdk_tools', aliases=['sdk-tools'],
            help='Run the given binary from the SDK tools dis',
            parents=[generic_parser])
        parser_sdk_tools.add_argument(
            'tool', help='The binary tool name to run')

        add_parser(
            subparsers,
            'adb', help='Run adb from the given SDK',
            parents=[generic_parser])
        add_parser(
            subparsers,
            'logcat', help='Run logcat from the given SDK',
            parents=[generic_parser])
        add_parser(
            subparsers,
            'build_status', aliases=['build-status'],
            help='Print some debug information about current built components',
            parents=[generic_parser])

        parser.add_argument('-v', '--version', action='version',
                            version=__version__)

        args, unknown = parser.parse_known_args(sys.argv[1:])
        args.unknown_args = unknown

        if hasattr(args, "private") and args.private is not None:
            # Pass this value on to the internal bootstrap build.py:
            args.unknown_args += ["--private", args.private]
        if hasattr(args, "ignore_setup_py") and args.ignore_setup_py:
            args.use_setup_py = False

        self.args = args

        if args.subparser_name is None:
            parser.print_help()
            exit(1)

        setup_color(args.color)

        if args.debug:
            logger.setLevel(logging.DEBUG)

        self.ctx = Context()
        self.ctx.use_setup_py = getattr(args, "use_setup_py", True)

        have_setup_py_or_similar = False
        if getattr(args, "private", None) is not None:
            project_dir = getattr(args, "private")
            if (os.path.exists(os.path.join(project_dir, "setup.py")) or
                    os.path.exists(os.path.join(project_dir,
                                                "pyproject.toml"))):
                have_setup_py_or_similar = True

        # Process requirements and put version in environ
        if hasattr(args, 'requirements'):
            requirements = []

            # Add dependencies from setup.py, but only if they are recipes
            # (because otherwise, setup.py itself will install them later)
            if (have_setup_py_or_similar and
                    getattr(args, "use_setup_py", False)):
                try:
                    info("Analyzing package dependencies. MAY TAKE A WHILE.")
                    # Get all the dependencies corresponding to a recipe:
                    dependencies = [
                        dep.lower() for dep in
                        get_dep_names_of_package(
                            args.private,
                            keep_version_pins=True,
                            recursive=True,
                            verbose=True,
                        )
                    ]
                    info("Dependencies obtained: " + str(dependencies))
                    all_recipes = [
                        recipe.lower() for recipe in
                        set(Recipe.list_recipes(self.ctx))
                    ]
                    dependencies = set(dependencies).intersection(
                        set(all_recipes)
                    )
                    # Add dependencies to argument list:
                    if len(dependencies) > 0:
                        if len(args.requirements) > 0:
                            args.requirements += u","
                        args.requirements += u",".join(dependencies)
                except ValueError:
                    # Not a python package, apparently.
                    warning(
                        "Processing failed, is this project a valid "
                        "package? Will continue WITHOUT setup.py deps."
                    )

            # Parse --requirements argument list:
            for requirement in split_argument_list(args.requirements):
                if "==" in requirement:
                    requirement, version = requirement.split(u"==", 1)
                    os.environ["VERSION_{}".format(requirement)] = version
                    info('Recipe {}: version "{}" requested'.format(
                        requirement, version))
                requirements.append(requirement)
            args.requirements = u",".join(requirements)

        self.warn_on_deprecated_args(args)

        self.storage_dir = args.storage_dir
        self.ctx.setup_dirs(self.storage_dir)
        self.sdk_dir = args.sdk_dir
        self.ndk_dir = args.ndk_dir
        self.android_api = args.android_api
        self.ndk_api = args.ndk_api
        self.ctx.symlink_java_src = args.symlink_java_src
        self.ctx.java_build_tool = args.java_build_tool

        self._archs = split_argument_list(args.arch)

        self.ctx.local_recipes = args.local_recipes
        self.ctx.copy_libs = args.copy_libs

        # Each subparser corresponds to a method
        getattr(self, args.subparser_name.replace('-', '_'))(args)
Esempio n. 4
0
    def __init__(self):
        parser = argparse.ArgumentParser(
            description="Tool for managing the Android / Python toolchain",
            usage="""toolchain <command> [<args>]

Available commands:
adb           Runs adb binary from the detected SDK dir
apk           Create an APK using the given distribution
bootstraps    List all the bootstraps available to build with.
build_status  Informations about the current build
create        Build an android project with all recipes
clean_all     Delete all build components
clean_builds  Delete all build caches
clean_dists   Delete all compiled distributions
clean_bootstrap_builds     Delete all compiled bootstraps
clean_download_cache Delete any downloaded recipe packages
clean_recipe_build   Delete the build files of a recipe
distributions List all distributions
export_dist   Copies a created dist to an output directory
logcat        Runs logcat from the detected SDK dir
print_context_info   Prints debug informations
recipes       List all the available recipes
sdk_tools     Runs android binary from the detected SDK dir
symlink_dist  Symlinks a created dist to an output directory

Planned commands:
build_dist
""")
        parser.add_argument("command", help="Command to run")

        # General options
        parser.add_argument('--debug',
                            dest='debug',
                            action='store_true',
                            help='Display debug output and all build info')
        parser.add_argument(
            '--color',
            dest='color',
            choices=['always', 'never', 'auto'],
            help='Enable or disable color output (default enabled on tty)')
        parser.add_argument(
            '--sdk-dir',
            '--sdk_dir',
            dest='sdk_dir',
            default='',
            help='The filepath where the Android SDK is installed')
        parser.add_argument(
            '--ndk-dir',
            '--ndk_dir',
            dest='ndk_dir',
            default='',
            help='The filepath where the Android NDK is installed')
        parser.add_argument('--android-api',
                            '--android_api',
                            dest='android_api',
                            default=0,
                            type=int,
                            help='The Android API level to build against.')
        parser.add_argument(
            '--ndk-version',
            '--ndk_version',
            dest='ndk_version',
            default='',
            help=('The version of the Android NDK. This is optional, '
                  'we try to work it out automatically from the ndk_dir.'))
        parser.add_argument(
            '--storage-dir',
            dest='storage_dir',
            default=self.default_storage_dir,
            help=('Primary storage directory for downloads and builds '
                  '(default: {})'.format(self.default_storage_dir)))

        # AND: This option doesn't really fit in the other categories, the
        # arg structure needs a rethink
        parser.add_argument(
            '--arch',
            help='The archs to build for, separated by commas.',
            default='armeabi')

        # Options for specifying the Distribution
        parser.add_argument(
            '--dist-name',
            '--dist_name',
            help='The name of the distribution to use or create',
            default='')
        parser.add_argument(
            '--requirements',
            help=('Dependencies of your app, should be recipe names or '
                  'Python modules'),
            default='')

        add_boolean_option(
            parser, ["allow-download"],
            default=False,
            description='Whether to allow binary dist download:')

        add_boolean_option(
            parser, ["allow-build"],
            default=True,
            description='Whether to allow compilation of a new distribution:')

        add_boolean_option(
            parser, ["force-build"],
            default=False,
            description='Whether to force compilation of a new distribution:')

        parser.add_argument(
            '--extra-dist-dirs',
            '--extra_dist_dirs',
            dest='extra_dist_dirs',
            default='',
            help='Directories in which to look for distributions')

        add_boolean_option(
            parser, ["require-perfect-match"],
            default=False,
            description=('Whether the dist recipes must perfectly match '
                         'those requested'))

        parser.add_argument('--local-recipes',
                            '--local_recipes',
                            dest='local_recipes',
                            default='./p4a-recipes',
                            help='Directory to look for local recipes')

        add_boolean_option(
            parser, ['copy-libs'],
            default=False,
            description='Copy libraries instead of using biglink (Android 4.3+)'
        )

        self._read_configuration()

        args, unknown = parser.parse_known_args(sys.argv[1:])
        self.dist_args = args

        setup_color(args.color)

        info(''.join([
            Err_Style.BRIGHT, Err_Fore.RED,
            'This python-for-android revamp is an experimental alpha release!',
            Err_Style.RESET_ALL
        ]))
        info(''.join([
            Err_Fore.RED,
            ('It should work (mostly), but you may experience '
             'missing features or bugs.'), Err_Style.RESET_ALL
        ]))

        # strip version from requirements, and put them in environ
        requirements = []
        for requirement in split_argument_list(args.requirements):
            if "==" in requirement:
                requirement, version = requirement.split(u"==", 1)
                os.environ["VERSION_{}".format(requirement)] = version
                info('Recipe {}: version "{}" requested'.format(
                    requirement, version))
            requirements.append(requirement)
        args.requirements = u",".join(requirements)

        if args.debug:
            logger.setLevel(logging.DEBUG)

        self.ctx = Context()
        self.storage_dir = args.storage_dir
        self.ctx.setup_dirs(self.storage_dir)
        self.sdk_dir = args.sdk_dir
        self.ndk_dir = args.ndk_dir
        self.android_api = args.android_api
        self.ndk_version = args.ndk_version

        self._archs = split_argument_list(args.arch)

        # AND: Fail nicely if the args aren't handled yet
        if args.extra_dist_dirs:
            warning('Received --extra_dist_dirs but this arg currently is not '
                    'handled, exiting.')
            exit(1)
        if args.allow_download:
            warning('Received --allow_download but this arg currently is not '
                    'handled, exiting.')
            exit(1)
        # if args.allow_build:
        #     warning('Received --allow_build but this arg currently is not '
        #             'handled, exiting.')
        #     exit(1)

        command_method_name = args.command.replace('-', '_')

        if not hasattr(self, command_method_name):
            print('Unrecognized command')
            parser.print_help()
            exit(1)

        self.ctx.local_recipes = args.local_recipes
        self.ctx.copy_libs = args.copy_libs

        getattr(self, command_method_name)(unknown)
Esempio n. 5
0
    def __init__(self):

        argv = sys.argv
        # Buildozer used to pass these arguments in a now-invalid order
        # If that happens, apply this fix
        # This fix will be removed once a fixed buildozer is released
        if (len(argv) > 2 and
            argv[1].startswith('--color') and
            argv[2].startswith('--storage-dir')):
            argv.append(argv.pop(1))  # the --color arg
            argv.append(argv.pop(1))  # the --storage-dir arg

        parser = NoAbbrevParser(
            description=('A packaging tool for turning Python scripts and apps '
                         'into Android APKs'))

        generic_parser = argparse.ArgumentParser(
            add_help=False,
            description=('Generic arguments applied to all commands'))
        dist_parser = argparse.ArgumentParser(
            add_help=False,
            description=('Arguments for dist building'))

        generic_parser.add_argument(
            '--debug', dest='debug', action='store_true',
            default=False,
            help='Display debug output and all build info')
        generic_parser.add_argument(
            '--color', dest='color', choices=['always', 'never', 'auto'],
            help='Enable or disable color output (default enabled on tty)')
        generic_parser.add_argument(
            '--sdk-dir', '--sdk_dir', dest='sdk_dir', default='',
            help='The filepath where the Android SDK is installed')
        generic_parser.add_argument(
            '--ndk-dir', '--ndk_dir', dest='ndk_dir', default='',
            help='The filepath where the Android NDK is installed')
        generic_parser.add_argument(
            '--android-api', '--android_api', dest='android_api', default=0, type=int,
            help='The Android API level to build against.')
        generic_parser.add_argument(
            '--ndk-version', '--ndk_version', dest='ndk_version', default='',
            help=('The version of the Android NDK. This is optional, '
                  'we try to work it out automatically from the ndk_dir.'))
        generic_parser.add_argument(
            '--symlink-java-src', '--symlink_java_src',
            action='store_true',
            dest='symlink_java_src',
            default=False,
            help=('If True, symlinks the java src folder during build and dist '
                  'creation. This is useful for development only, it could also '
                  'cause weird problems.'))

        default_storage_dir = user_data_dir('python-for-android')
        if ' ' in default_storage_dir:
            default_storage_dir = '~/.python-for-android'
        generic_parser.add_argument(
            '--storage-dir', dest='storage_dir',
            default=default_storage_dir,
            help=('Primary storage directory for downloads and builds '
                  '(default: {})'.format(default_storage_dir)))

        generic_parser.add_argument(
            '--arch',
            help='The archs to build for, separated by commas.',
            default='armeabi')

        # Options for specifying the Distribution
        generic_parser.add_argument(
            '--dist-name', '--dist_name',
            help='The name of the distribution to use or create',
            default='')

        generic_parser.add_argument(
            '--requirements',
            help=('Dependencies of your app, should be recipe names or '
                  'Python modules'),
            default='')

        generic_parser.add_argument(
            '--bootstrap',
            help='The bootstrap to build with. Leave unset to choose automatically.',
            default=None)

        generic_parser.add_argument(
            '--hook',
            help='Filename to a module that contain python-for-android hooks',
            default=None)

        add_boolean_option(
            generic_parser, ["force-build"],
            default=False,
            description='Whether to force compilation of a new distribution:')

        add_boolean_option(
            generic_parser, ["require-perfect-match"],
            default=False,
            description=('Whether the dist recipes must perfectly match '
                         'those requested'))

        generic_parser.add_argument(
            '--local-recipes', '--local_recipes',
            dest='local_recipes', default='./p4a-recipes',
            help='Directory to look for local recipes')

        generic_parser.add_argument(
            '--java-build-tool',
            dest='java_build_tool', default='auto',
            choices=['auto', 'ant', 'gradle'],
            help=('The java build tool to use when packaging the APK, defaults '
                  'to automatically selecting an appropriate tool.'))

        add_boolean_option(
            generic_parser, ['copy-libs'],
            default=False,
            description='Copy libraries instead of using biglink (Android 4.3+)')

        self._read_configuration()

        subparsers = parser.add_subparsers(dest='subparser_name',
                                           help='The command to run')

        def add_parser(subparsers, *args, **kwargs):
            '''
            argparse in python2 doesn't support the aliases option,
            so we just don't provide the aliases there.
            '''
            if 'aliases' in kwargs and sys.version_info.major < 3:
                kwargs.pop('aliases')
            return subparsers.add_parser(*args, **kwargs)

        parser_recipes = add_parser(subparsers,
            'recipes',
            parents=[generic_parser],
            help='List the available recipes')
        parser_recipes.add_argument(
                "--compact", action="store_true", default=False,
                help="Produce a compact list suitable for scripting")

        parser_bootstraps = add_parser(
            subparsers, 'bootstraps',
            help='List the available bootstraps',
            parents=[generic_parser])
        parser_clean_all = add_parser(
            subparsers, 'clean_all',
            aliases=['clean-all'],
            help='Delete all builds, dists and caches',
            parents=[generic_parser])
        parser_clean_dists = add_parser(
            subparsers,
            'clean_dists', aliases=['clean-dists'],
            help='Delete all dists',
            parents=[generic_parser])
        parser_clean_bootstrap_builds = add_parser(
            subparsers,
            'clean_bootstrap_builds', aliases=['clean-bootstrap-builds'],
            help='Delete all bootstrap builds',
            parents=[generic_parser])
        parser_clean_builds = add_parser(
            subparsers,
            'clean_builds', aliases=['clean-builds'],
            help='Delete all builds',
            parents=[generic_parser])

        parser_clean = add_parser(subparsers, 'clean',
                                  help='Delete build components.',
                                  parents=[generic_parser])
        parser_clean.add_argument(
            'component', nargs='+',
            help=('The build component(s) to delete. You can pass any '
                  'number of arguments from "all", "builds", "dists", '
                  '"distributions", "bootstrap_builds", "downloads".'))

        parser_clean_recipe_build = add_parser(subparsers,
            'clean_recipe_build', aliases=['clean-recipe-build'],
            help=('Delete the build components of the given recipe. '
                  'By default this will also delete built dists'),
            parents=[generic_parser])
        parser_clean_recipe_build.add_argument('recipe', help='The recipe name')
        parser_clean_recipe_build.add_argument('--no-clean-dists', default=False,
                                               dest='no_clean_dists',
                                               action='store_true',
                                               help='If passed, do not delete existing dists')

        parser_clean_download_cache= add_parser(subparsers,
            'clean_download_cache', aliases=['clean-download-cache'],
            help='Delete cached downloads for requirement builds',
            parents=[generic_parser])
        parser_clean_download_cache.add_argument(
            'recipes', nargs='*',
            help=('The recipes to clean (space-separated). If no recipe name is '
                  'provided, the entire cache is cleared.'))

        parser_export_dist = add_parser(subparsers,
            'export_dist', aliases=['export-dist'],
            help='Copy the named dist to the given path',
            parents=[generic_parser])
        parser_export_dist.add_argument('output_dir', help=('The output dir to copy to'))
        parser_export_dist.add_argument('--symlink', action='store_true',
                                        help=('Symlink the dist instead of copying'))

        parser_apk = add_parser(subparsers,
            'apk', help='Build an APK',
            parents=[generic_parser])
        parser_apk.add_argument('--release', dest='build_mode', action='store_const',
                        const='release', default='debug',
                        help='Build the PARSER_APK. in Release mode')
        parser_apk.add_argument('--keystore', dest='keystore', action='store', default=None,
                        help=('Keystore for JAR signing key, will use jarsigner '
                              'default if not specified (release build only)'))
        parser_apk.add_argument('--signkey', dest='signkey', action='store', default=None,
                        help='Key alias to sign PARSER_APK. with (release build only)')
        parser_apk.add_argument('--keystorepw', dest='keystorepw', action='store', default=None,
                        help='Password for keystore')
        parser_apk.add_argument('--signkeypw', dest='signkeypw', action='store', default=None,
                        help='Password for key alias')

        parser_create = add_parser(subparsers,
            'create', help='Compile a set of requirements into a dist',
            parents=[generic_parser])
        parser_archs = add_parser(subparsers,
            'archs', help='List the available target architectures',
            parents=[generic_parser])
        parser_distributions = add_parser(subparsers,
            'distributions', aliases=['dists'],
            help='List the currently available (compiled) dists',
            parents=[generic_parser])
        parser_delete_dist = add_parser(subparsers,
            'delete_dist', aliases=['delete-dist'], help='Delete a compiled dist',
            parents=[generic_parser])

        parser_sdk_tools = add_parser(subparsers,
            'sdk_tools', aliases=['sdk-tools'],
            help='Run the given binary from the SDK tools dis',
            parents=[generic_parser])
        parser_sdk_tools.add_argument(
            'tool', help=('The tool binary name to run'))

        parser_adb = add_parser(subparsers,
            'adb', help='Run adb from the given SDK',
            parents=[generic_parser])
        parser_logcat = add_parser(subparsers,
            'logcat', help='Run logcat from the given SDK',
            parents=[generic_parser])
        parser_build_status = add_parser(subparsers,
            'build_status', aliases=['build-status'],
            help='Print some debug information about current built components',
            parents=[generic_parser])

        parser.add_argument('-v', '--version', action='version', version=__version__)

        args, unknown = parser.parse_known_args(sys.argv[1:])
        args.unknown_args = unknown

        self.args = args

        if args.subparser_name is None:
            parser.print_help()
            exit(1)

        setup_color(args.color)

        if args.debug:
            logger.setLevel(logging.DEBUG)

        # strip version from requirements, and put them in environ
        if hasattr(args, 'requirements'):
            requirements = []
            for requirement in split_argument_list(args.requirements):
                if "==" in requirement:
                    requirement, version = requirement.split(u"==", 1)
                    os.environ["VERSION_{}".format(requirement)] = version
                    info('Recipe {}: version "{}" requested'.format(
                        requirement, version))
                requirements.append(requirement)
            args.requirements = u",".join(requirements)

        self.ctx = Context()
        self.storage_dir = args.storage_dir
        self.ctx.setup_dirs(self.storage_dir)
        self.sdk_dir = args.sdk_dir
        self.ndk_dir = args.ndk_dir
        self.android_api = args.android_api
        self.ndk_version = args.ndk_version
        self.ctx.symlink_java_src = args.symlink_java_src
        self.ctx.java_build_tool = args.java_build_tool

        self._archs = split_argument_list(args.arch)

        self.ctx.local_recipes = args.local_recipes
        self.ctx.copy_libs = args.copy_libs

        # Each subparser corresponds to a method
        getattr(self, args.subparser_name.replace('-', '_'))(args)