def recipes(self, args): ctx = self.ctx if args.compact: print(" ".join(set(Recipe.list_recipes(ctx)))) else: for name in sorted(Recipe.list_recipes(ctx)): try: recipe = Recipe.get_recipe(name, ctx) except IOError: warning('Recipe "{}" could not be loaded'.format(name)) except SyntaxError: import traceback traceback.print_exc() warning(('Recipe "{}" could not be loaded due to a ' 'syntax error').format(name)) version = str(recipe.version) print('{Fore.BLUE}{Style.BRIGHT}{recipe.name:<12} ' '{Style.RESET_ALL}{Fore.LIGHTBLUE_EX}' '{version:<8}{Style.RESET_ALL}'.format( recipe=recipe, Fore=Out_Fore, Style=Out_Style, version=version)) print(' {Fore.GREEN}depends: {recipe.depends}' '{Fore.RESET}'.format(recipe=recipe, Fore=Out_Fore)) if recipe.conflicts: print(' {Fore.RED}conflicts: {recipe.conflicts}' '{Fore.RESET}' .format(recipe=recipe, Fore=Out_Fore)) if recipe.opt_depends: print(' {Fore.YELLOW}optional depends: ' '{recipe.opt_depends}{Fore.RESET}' .format(recipe=recipe, Fore=Out_Fore))
def recipes(self, args): parser = argparse.ArgumentParser( description="List all the available recipes") parser.add_argument( "--compact", action="store_true", default=False, help="Produce a compact list suitable for scripting") args = parser.parse_args(args) ctx = self.ctx if args.compact: print(" ".join(set(Recipe.list_recipes(ctx)))) else: for name in sorted(Recipe.list_recipes(ctx)): recipe = Recipe.get_recipe(name, ctx) version = str(recipe.version) print('{Fore.BLUE}{Style.BRIGHT}{recipe.name:<12} ' '{Style.RESET_ALL}{Fore.LIGHTBLUE_EX}' '{version:<8}{Style.RESET_ALL}'.format(recipe=recipe, Fore=Out_Fore, Style=Out_Style, version=version)) print(' {Fore.GREEN}depends: {recipe.depends}' '{Fore.RESET}'.format(recipe=recipe, Fore=Out_Fore)) if recipe.conflicts: print(' {Fore.RED}conflicts: {recipe.conflicts}' '{Fore.RESET}'.format(recipe=recipe, Fore=Out_Fore)) if recipe.opt_depends: print(' {Fore.YELLOW}optional depends: ' '{recipe.opt_depends}{Fore.RESET}'.format( recipe=recipe, Fore=Out_Fore))
def recipes(self, args): parser = argparse.ArgumentParser( description="List all the available recipes") parser.add_argument( "--compact", action="store_true", default=False, help="Produce a compact list suitable for scripting") args = parser.parse_args(args) ctx = self.ctx if args.compact: print(" ".join(set(Recipe.list_recipes(ctx)))) else: for name in sorted(Recipe.list_recipes(ctx)): recipe = Recipe.get_recipe(name, ctx) version = str(recipe.version) print('{Fore.BLUE}{Style.BRIGHT}{recipe.name:<12} ' '{Style.RESET_ALL}{Fore.LIGHTBLUE_EX}' '{version:<8}{Style.RESET_ALL}'.format( recipe=recipe, Fore=Out_Fore, Style=Out_Style, version=version)) print(' {Fore.GREEN}depends: {recipe.depends}' '{Fore.RESET}'.format(recipe=recipe, Fore=Out_Fore)) if recipe.conflicts: print(' {Fore.RED}conflicts: {recipe.conflicts}' '{Fore.RESET}' .format(recipe=recipe, Fore=Out_Fore)) if recipe.opt_depends: print(' {Fore.YELLOW}optional depends: ' '{recipe.opt_depends}{Fore.RESET}' .format(recipe=recipe, Fore=Out_Fore))
def test_list_recipes(self): """ Trivial test verifying list_recipes returns a generator with some recipes. """ ctx = Context() recipes = Recipe.list_recipes(ctx) self.assertTrue(isinstance(recipes, types.GeneratorType)) recipes = list(recipes) self.assertIn('python3', recipes)
def recipes(self, args): """ Prints recipes basic info, e.g. .. code-block:: bash python3 3.7.1 depends: ['hostpython3', 'sqlite3', 'openssl', 'libffi'] conflicts: ['python2'] optional depends: ['sqlite3', 'libffi', 'openssl'] """ ctx = self.ctx if args.compact: print(" ".join(set(Recipe.list_recipes(ctx)))) else: for name in sorted(Recipe.list_recipes(ctx)): try: recipe = Recipe.get_recipe(name, ctx) except (IOError, ValueError): warning('Recipe "{}" could not be loaded'.format(name)) except SyntaxError: import traceback traceback.print_exc() warning(('Recipe "{}" could not be loaded due to a ' 'syntax error').format(name)) version = str(recipe.version) print('{Fore.BLUE}{Style.BRIGHT}{recipe.name:<12} ' '{Style.RESET_ALL}{Fore.LIGHTBLUE_EX}' '{version:<8}{Style.RESET_ALL}'.format( recipe=recipe, Fore=Out_Fore, Style=Out_Style, version=version)) print(' {Fore.GREEN}depends: {recipe.depends}' '{Fore.RESET}'.format(recipe=recipe, Fore=Out_Fore)) if recipe.conflicts: print(' {Fore.RED}conflicts: {recipe.conflicts}' '{Fore.RESET}' .format(recipe=recipe, Fore=Out_Fore)) if recipe.opt_depends: print(' {Fore.YELLOW}optional depends: ' '{recipe.opt_depends}{Fore.RESET}' .format(recipe=recipe, Fore=Out_Fore))
def recipes(self, args): ctx = self.ctx if args.compact: print(" ".join(set(Recipe.list_recipes(ctx)))) else: for name in sorted(Recipe.list_recipes(ctx)): recipe = Recipe.get_recipe(name, ctx) version = str(recipe.version) print('{Fore.BLUE}{Style.BRIGHT}{recipe.name:<12} ' '{Style.RESET_ALL}{Fore.LIGHTBLUE_EX}' '{version:<8}{Style.RESET_ALL}'.format(recipe=recipe, Fore=Out_Fore, Style=Out_Style, version=version)) print(' {Fore.GREEN}depends: {recipe.depends}' '{Fore.RESET}'.format(recipe=recipe, Fore=Out_Fore)) if recipe.conflicts: print(' {Fore.RED}conflicts: {recipe.conflicts}' '{Fore.RESET}'.format(recipe=recipe, Fore=Out_Fore)) if recipe.opt_depends: print(' {Fore.YELLOW}optional depends: ' '{recipe.opt_depends}{Fore.RESET}'.format( recipe=recipe, Fore=Out_Fore))
def recipes(self, args): ctx = self.ctx if args.compact: print(" ".join(set(Recipe.list_recipes(ctx)))) else: for name in sorted(Recipe.list_recipes(ctx)): recipe = Recipe.get_recipe(name, ctx) version = str(recipe.version) print('{Fore.BLUE}{Style.BRIGHT}{recipe.name:<12} ' '{Style.RESET_ALL}{Fore.LIGHTBLUE_EX}' '{version:<8}{Style.RESET_ALL}'.format( recipe=recipe, Fore=Out_Fore, Style=Out_Style, version=version)) print(' {Fore.GREEN}depends: {recipe.depends}' '{Fore.RESET}'.format(recipe=recipe, Fore=Out_Fore)) if recipe.conflicts: print(' {Fore.RED}conflicts: {recipe.conflicts}' '{Fore.RESET}' .format(recipe=recipe, Fore=Out_Fore)) if recipe.opt_depends: print(' {Fore.YELLOW}optional depends: ' '{recipe.opt_depends}{Fore.RESET}' .format(recipe=recipe, Fore=Out_Fore))
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)