import functoolsimport globimport importlibimport osfrom os.path import (join, dirname, isdir, normpath, splitext, basename)from os import listdir, walk, sepimport shimport shleximport shutilfrom pythonforandroid.logger import (shprint, info, logger, debug)from pythonforandroid.util import (current_directory, ensure_dir, temp_directory, BuildInterruptingException,rmdir, move)from pythonforandroid.recipe import Recipedef copy_files(src_root, dest_root, override=True, symlink=False):for root, dirnames, filenames in walk(src_root):for filename in filenames:subdir = normpath(root.replace(src_root, ""))if subdir.startswith(sep): # ensure it is relativesubdir = subdir[1:]dest_dir = join(dest_root, subdir)if not os.path.exists(dest_dir):os.makedirs(dest_dir)src_file = join(root, filename)dest_file = join(dest_dir, filename)if os.path.isfile(src_file):if override and os.path.exists(dest_file):os.unlink(dest_file)if not os.path.exists(dest_file):if symlink:os.symlink(src_file, dest_file)else:shutil.copy(src_file, dest_file)else:os.makedirs(dest_file)default_recipe_priorities = ["webview", "sdl2", "service_only" # last is highest]# ^^ NOTE: these are just the default priorities if no special rules# apply (which you can find in the code below), so basically if no# known graphical lib or web lib is used - in which case service_only# is the most reasonable guess.def _cmp_bootstraps_by_priority(a, b):def rank_bootstrap(bootstrap):""" Returns a ranking index for each bootstrap,with higher priority ranked with higher number. """if bootstrap.name in default_recipe_priorities:return default_recipe_priorities.index(bootstrap.name) + 1return 0# Rank bootstraps in order:rank_a = rank_bootstrap(a)rank_b = rank_bootstrap(b)if rank_a != rank_b:return (rank_b - rank_a)else:if a.name < b.name: # alphabetic sort for determinismreturn -1else:return 1class Bootstrap:'''An Android project template, containing recipe stuff forcompilation and templated fields for APK info.'''jni_subdir = '/jni'ctx = Nonebootstrap_dir = Nonebuild_dir = Nonedist_name = Nonedistribution = None# All bootstraps should include Python in some way:recipe_depends = ['python3', 'android']can_be_chosen_automatically = True'''Determines whether the bootstrap can be chosen as one thatsatisfies user requirements. If False, it will not be returnedfrom Bootstrap.get_bootstrap_from_recipes.'''# Other things a Bootstrap might need to track (maybe separately):# ndk_main.c# whitelist.txt# blacklist.txt@propertydef dist_dir(self):'''The dist dir at which to place the finished distribution.'''if self.distribution is None:raise BuildInterruptingException('Internal error: tried to access {}.dist_dir, but {}.distribution ''is None'.format(self, self))return self.distribution.dist_dir@propertydef jni_dir(self):return self.name + self.jni_subdirdef check_recipe_choices(self):'''Checks what recipes are being built to see which of the alternativeand optional dependencies are being used,and returns a list of these.'''recipes = []built_recipes = self.ctx.recipe_build_order or []for recipe in self.recipe_depends:if isinstance(recipe, (tuple, list)):for alternative in recipe:if alternative in built_recipes:recipes.append(alternative)breakreturn sorted(recipes)def get_build_dir_name(self):choices = self.check_recipe_choices()dir_name = '-'.join([self.name] + choices)return dir_namedef get_build_dir(self):return join(self.ctx.build_dir, 'bootstrap_builds', self.get_build_dir_name())def get_dist_dir(self, name):return join(self.ctx.dist_dir, name)@propertydef name(self):modname = self.__class__.__module__return modname.split(".", 2)[-1]def get_bootstrap_dirs(self):"""get all bootstrap directories, following the MRO path"""# get all bootstrap names along the __mro__, cutting off Bootstrap and objectclasses = self.__class__.__mro__[:-2]bootstrap_names = [cls.name for cls in classes] + ['common']bootstrap_dirs = [join(self.ctx.root_dir, 'bootstraps', bootstrap_name)for bootstrap_name in reversed(bootstrap_names)]return bootstrap_dirsdef _copy_in_final_files(self):if self.name == "sdl2":# Get the paths for copying SDL2's java source code:sdl2_recipe = Recipe.get_recipe("sdl2", self.ctx)sdl2_build_dir = sdl2_recipe.get_jni_dir()src_dir = join(sdl2_build_dir, "SDL", "android-project","app", "src", "main", "java","org", "libsdl", "app")target_dir = join(self.dist_dir, 'src', 'main', 'java', 'org','libsdl', 'app')# Do actual copying:info('Copying in SDL2 .java files from: ' + str(src_dir))if not os.path.exists(target_dir):os.makedirs(target_dir)copy_files(src_dir, target_dir, override=True)def prepare_build_dir(self):"""Ensure that a build dir exists for the recipe. This same singledir will be used for building all different archs."""bootstrap_dirs = self.get_bootstrap_dirs()# now do a cumulative copy of all bootstrap dirsself.build_dir = self.get_build_dir()for bootstrap_dir in bootstrap_dirs:copy_files(join(bootstrap_dir, 'build'), self.build_dir, symlink=self.ctx.symlink_bootstrap_files)with current_directory(self.build_dir):with open('project.properties', 'w') as fileh:fileh.write('target=android-{}'.format(self.ctx.android_api))def prepare_dist_dir(self):ensure_dir(self.dist_dir)def assemble_distribution(self):''' Copies all the files into the distribution (this function isoverridden by the specific bootstrap classes to do this)and add in the distribution info.'''self._copy_in_final_files()self.distribution.save_info(self.dist_dir)@classmethoddef all_bootstraps(cls):'''Find all the available bootstraps and return them.'''forbidden_dirs = ('__pycache__', 'common')bootstraps_dir = join(dirname(__file__), 'bootstraps')result = set()for name in listdir(bootstraps_dir):if name in forbidden_dirs:continuefilen = join(bootstraps_dir, name)if isdir(filen):result.add(name)return result@classmethoddef get_usable_bootstraps_for_recipes(cls, recipes, ctx):'''Returns all bootstrap whose recipe requirements do not conflictwith the given recipes, in no particular order.'''info('Trying to find a bootstrap that matches the given recipes.')bootstraps = [cls.get_bootstrap(name, ctx)for name in cls.all_bootstraps()]acceptable_bootstraps = set()# Find out which bootstraps are acceptable:for bs in bootstraps:if not bs.can_be_chosen_automatically:continuepossible_dependency_lists = expand_dependencies(bs.recipe_depends, ctx)for possible_dependencies in possible_dependency_lists:ok = True# Check if the bootstap's dependencies have an internal conflict:for recipe in possible_dependencies:recipe = Recipe.get_recipe(recipe, ctx)if any(conflict in recipes for conflict in recipe.conflicts):ok = Falsebreak# Check if bootstrap's dependencies conflict with chosen# packages:for recipe in recipes:try:recipe = Recipe.get_recipe(recipe, ctx)except ValueError:conflicts = []else:conflicts = recipe.conflictsif any(conflict in possible_dependenciesfor conflict in conflicts):ok = Falsebreakif ok and bs not in acceptable_bootstraps:acceptable_bootstraps.add(bs)info('Found {} acceptable bootstraps: {}'.format(len(acceptable_bootstraps),[bs.name for bs in acceptable_bootstraps]))return acceptable_bootstraps@classmethoddef get_bootstrap_from_recipes(cls, recipes, ctx):'''Picks a single recommended default bootstrap out ofall_usable_bootstraps_from_recipes() for the given reicpes,and returns it.'''known_web_packages = {"flask"} # to pick webview over service_onlyrecipes_with_deps_lists = expand_dependencies(recipes, ctx)acceptable_bootstraps = cls.get_usable_bootstraps_for_recipes(recipes, ctx)def have_dependency_in_recipes(dep):for dep_list in recipes_with_deps_lists:if dep in dep_list:return Truereturn False# Special rule: return SDL2 bootstrap if there's an sdl2 dep:if (have_dependency_in_recipes("sdl2") and"sdl2" in [b.name for b in acceptable_bootstraps]):info('Using sdl2 bootstrap since it is in dependencies')return cls.get_bootstrap("sdl2", ctx)# Special rule: return "webview" if we depend on common web recipe:for possible_web_dep in known_web_packages:if have_dependency_in_recipes(possible_web_dep):# We have a web package dep!if "webview" in [b.name for b in acceptable_bootstraps]:info('Using webview bootstrap since common web packages ''were found {}'.format(known_web_packages.intersection(recipes)))return cls.get_bootstrap("webview", ctx)prioritized_acceptable_bootstraps = sorted(list(acceptable_bootstraps),key=functools.cmp_to_key(_cmp_bootstraps_by_priority))if prioritized_acceptable_bootstraps:info('Using the highest ranked/first of these: {}'.format(prioritized_acceptable_bootstraps[0].name))return prioritized_acceptable_bootstraps[0]return None@classmethoddef get_bootstrap(cls, name, ctx):'''Returns an instance of a bootstrap with the given name.This is the only way you should access a bootstrap class, asit sets the bootstrap directory correctly.'''if name is None:return Noneif not hasattr(cls, 'bootstraps'):cls.bootstraps = {}if name in cls.bootstraps:return cls.bootstraps[name]mod = importlib.import_module('pythonforandroid.bootstraps.{}'.format(name))if len(logger.handlers) > 1:logger.removeHandler(logger.handlers[1])bootstrap = mod.bootstrapbootstrap.bootstrap_dir = join(ctx.root_dir, 'bootstraps', name)bootstrap.ctx = ctxreturn bootstrapdef distribute_libs(self, arch, src_dirs, wildcard='*', dest_dir="libs"):'''Copy existing arch libs from build dirs to current dist dir.'''info('Copying libs')tgt_dir = join(dest_dir, arch.arch)ensure_dir(tgt_dir)for src_dir in src_dirs:libs = glob.glob(join(src_dir, wildcard))if libs:shprint(sh.cp, '-a', *libs, tgt_dir)def distribute_javaclasses(self, javaclass_dir, dest_dir="src"):'''Copy existing javaclasses from build dir to current dist dir.'''info('Copying java files')ensure_dir(dest_dir)filenames = glob.glob(javaclass_dir)shprint(sh.cp, '-a', *filenames, dest_dir)def distribute_aars(self, arch):'''Process existing .aar bundles and copy to current dist dir.'''info('Unpacking aars')for aar in glob.glob(join(self.ctx.aars_dir, '*.aar')):self._unpack_aar(aar, arch)def _unpack_aar(self, aar, arch):'''Unpack content of .aar bundle and copy to current dist dir.'''with temp_directory() as temp_dir:name = splitext(basename(aar))[0]jar_name = name + '.jar'info("unpack {} aar".format(name))debug(" from {}".format(aar))debug(" to {}".format(temp_dir))shprint(sh.unzip, '-o', aar, '-d', temp_dir)jar_src = join(temp_dir, 'classes.jar')jar_tgt = join('libs', jar_name)debug("copy {} jar".format(name))debug(" from {}".format(jar_src))debug(" to {}".format(jar_tgt))ensure_dir('libs')shprint(sh.cp, '-a', jar_src, jar_tgt)so_src_dir = join(temp_dir, 'jni', arch.arch)so_tgt_dir = join('libs', arch.arch)debug("copy {} .so".format(name))debug(" from {}".format(so_src_dir))debug(" to {}".format(so_tgt_dir))ensure_dir(so_tgt_dir)so_files = glob.glob(join(so_src_dir, '*.so'))shprint(sh.cp, '-a', *so_files, so_tgt_dir)def strip_libraries(self, arch):info('Stripping libraries')env = arch.get_env()tokens = shlex.split(env['STRIP'])strip = sh.Command(tokens[0])if len(tokens) > 1:strip = strip.bake(tokens[1:])libs_dir = join(self.dist_dir, f'_python_bundle__{arch.arch}','_python_bundle', 'modules')filens = shprint(sh.find, libs_dir, join(self.dist_dir, 'libs'),'-iname', '*.so', _env=env).stdout.decode('utf-8')logger.info('Stripping libraries in private dir')for filen in filens.split('\n'):if not filen:continue # skip the last ''try:strip(filen, _env=env)except sh.ErrorReturnCode_1:logger.debug('Failed to strip ' + filen)def fry_eggs(self, sitepackages):info('Frying eggs in {}'.format(sitepackages))for d in listdir(sitepackages):rd = join(sitepackages, d)if isdir(rd) and d.endswith('.egg'):info(' ' + d)files = [join(rd, f) for f in listdir(rd) if f != 'EGG-INFO']for f in files:move(f, sitepackages)rmdir(d)def expand_dependencies(recipes, ctx):""" This function expands to lists of all different availablealternative recipe combinations, with the dependencies added inONLY for all the not-with-alternative recipes.(So this is like the deps graph very simplified and incomplete, buthopefully good enough for most basic bootstrap compatibility checks)"""# Add in all the deps of recipes where there is no alternative:recipes_with_deps = list(recipes)for entry in recipes:if not isinstance(entry, (tuple, list)) or len(entry) == 1:if isinstance(entry, (tuple, list)):entry = entry[0]try:recipe = Recipe.get_recipe(entry, ctx)recipes_with_deps += recipe.dependsexcept ValueError:# it's a pure python package without a recipe, so we# don't know the dependencies...skipping for nowpass# Split up lists by available alternatives:recipe_lists = [[]]for recipe in recipes_with_deps:if isinstance(recipe, (tuple, list)):new_recipe_lists = []for alternative in recipe:for old_list in recipe_lists:new_list = [i for i in old_list]new_list.append(alternative)new_recipe_lists.append(new_list)recipe_lists = new_recipe_listselse:for existing_list in recipe_lists:existing_list.append(recipe)return recipe_lists
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。