def test_get_dep_names_of_package(): # TEST 1 from external ref: # Check that colorama is returned without the install condition when # just getting the names (it has a "; ..." conditional originally): dep_names = get_dep_names_of_package("python-for-android") assert "colorama" in dep_names assert "setuptools" not in dep_names dep_names = get_dep_names_of_package("python-for-android", include_build_requirements=True) assert "setuptools" in dep_names # TEST 2 from local folder: assert "colorama" in get_dep_names_of_package(local_repo_folder()) # Now test that exact version pins are kept, but others aren't: test_fake_package = tempfile.mkdtemp() try: with open(os.path.join(test_fake_package, "setup.py"), "w") as f: f.write( textwrap.dedent("""\ from setuptools import setup setup(name='fakeproject', description='fake for testing', install_requires=['buildozer==0.39', 'python-for-android>=0.5.1'], ) """)) # See that we get the deps with the exact version pin kept but # the other version restriction gone: assert set( get_dep_names_of_package( test_fake_package, recursive=False, keep_version_pins=True, verbose=True)) == {"buildozer==0.39", "python-for-android"} # Make sure we also can get the fully cleaned up variant: assert set( get_dep_names_of_package( test_fake_package, recursive=False, keep_version_pins=False, verbose=True)) == {"buildozer", "python-for-android"} # Test with build requirements included: dep_names = get_dep_names_of_package(test_fake_package, recursive=False, keep_version_pins=False, verbose=True, include_build_requirements=True) assert len({ "buildozer", "python-for-android", "setuptools" }.intersection(dep_names)) == 3 # all three must be included finally: shutil.rmtree(test_fake_package)
def test_get_dep_names_of_package(): # TEST 1 from external ref: # Check that colorama is returned without the install condition when # just getting the names (it has a "; ..." conditional originally): dep_names = get_dep_names_of_package("python-for-android") assert "colorama" in dep_names assert "setuptools" not in dep_names dep_names = get_dep_names_of_package("python-for-android", include_build_requirements=True) assert "setuptools" in dep_names # TEST 2 from local folder: assert "colorama" in get_dep_names_of_package(local_repo_folder()) # Now test that exact version pins are kept, but others aren't: test_fake_package = tempfile.mkdtemp() try: with open(os.path.join(test_fake_package, "setup.py"), "w") as f: f.write(textwrap.dedent("""\ from setuptools import setup setup(name='fakeproject', description='fake for testing', install_requires=['buildozer==0.39', 'python-for-android>=0.5.1'], ) """)) # See that we get the deps with the exact version pin kept but # the other version restriction gone: assert set(get_dep_names_of_package( test_fake_package, recursive=False, keep_version_pins=True, verbose=True )) == {"buildozer==0.39", "python-for-android"} # Make sure we also can get the fully cleaned up variant: assert set(get_dep_names_of_package( test_fake_package, recursive=False, keep_version_pins=False, verbose=True )) == {"buildozer", "python-for-android"} # Test with build requirements included: dep_names = get_dep_names_of_package( test_fake_package, recursive=False, keep_version_pins=False, verbose=True, include_build_requirements=True ) assert len( {"buildozer", "python-for-android", "setuptools"}.intersection( dep_names ) ) == 3 # all three must be included finally: shutil.rmtree(test_fake_package)
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)
def test_get_dep_names_of_package(): # TEST 1 from external ref: # Check that colorama is returned without the install condition when # just getting the names (it has a "; ..." conditional originally): dep_names = get_dep_names_of_package("python-for-android") assert "colorama" in dep_names assert "setuptools" not in dep_names try: dep_names = get_dep_names_of_package( "python-for-android", include_build_requirements=True, verbose=True, ) except NotImplementedError as e: # If python-for-android was fetched as wheel then build requirements # cannot be obtained (since that is not implemented for wheels). # Check for the correct error message: assert "wheel" in str(e) # (And yes it would be better to do a local test with something # that is guaranteed to be a wheel and not remote on pypi, # but that might require setting up a full local pypiserver. # Not worth the test complexity for now, but if anyone has an # idea in the future feel free to replace this subtest.) else: # We managed to obtain build requirements! # Check setuptools is in here: assert "setuptools" in dep_names # TEST 2 from local folder: assert "colorama" in get_dep_names_of_package(local_repo_folder()) # Now test that exact version pins are kept, but others aren't: test_fake_package = tempfile.mkdtemp() try: with open(os.path.join(test_fake_package, "setup.py"), "w") as f: f.write(textwrap.dedent("""\ from setuptools import setup setup(name='fakeproject', description='fake for testing', install_requires=['buildozer==0.39', 'python-for-android>=0.5.1'], ) """)) # See that we get the deps with the exact version pin kept but # the other version restriction gone: assert set(get_dep_names_of_package( test_fake_package, recursive=False, keep_version_pins=True, verbose=True )) == {"buildozer==0.39", "python-for-android"} # Make sure we also can get the fully cleaned up variant: assert set(get_dep_names_of_package( test_fake_package, recursive=False, keep_version_pins=False, verbose=True )) == {"buildozer", "python-for-android"} # Test with build requirements included: dep_names = get_dep_names_of_package( test_fake_package, recursive=False, keep_version_pins=False, verbose=True, include_build_requirements=True ) assert len( {"buildozer", "python-for-android", "setuptools"}.intersection( dep_names ) ) == 3 # all three must be included finally: shutil.rmtree(test_fake_package)