import functools import glob import importlib import os from os.path import (join, dirname, isdir, normpath, splitext, basename) from os import listdir, walk, sep import sh import shlex import shutil from pythonforandroid.logger import (shprint, info, logger, debug) from pythonforandroid.util import ( current_directory, ensure_dir, temp_directory, BuildInterruptingException) from pythonforandroid.recipe import Recipe def copy_files(src_root, dest_root, override=True): 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 relative subdir = 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): 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) + 1 return 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 determinism return -1 else: return 1 class Bootstrap(object): '''An Android project template, containing recipe stuff for compilation and templated fields for APK info. ''' name = '' jni_subdir = '/jni' ctx = None bootstrap_dir = None build_dir = None dist_name = None distribution = None # All bootstraps should include Python in some way: recipe_depends = [("python2", "python3"), 'android'] can_be_chosen_automatically = True '''Determines whether the bootstrap can be chosen as one that satisfies user requirements. If False, it will not be returned from Bootstrap.get_bootstrap_from_recipes. ''' # Other things a Bootstrap might need to track (maybe separately): # ndk_main.c # whitelist.txt # blacklist.txt @property def 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 @property def jni_dir(self): return self.name + self.jni_subdir def check_recipe_choices(self): '''Checks what recipes are being built to see which of the alternative and optional dependencies are being used, and returns a list of these.''' recipes = [] built_recipes = self.ctx.recipe_build_order for recipe in self.recipe_depends: if isinstance(recipe, (tuple, list)): for alternative in recipe: if alternative in built_recipes: recipes.append(alternative) break return sorted(recipes) def get_build_dir_name(self): choices = self.check_recipe_choices() dir_name = '-'.join([self.name] + choices) return dir_name def 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) def get_common_dir(self): return os.path.abspath(join(self.bootstrap_dir, "..", 'common')) @property def name(self): modname = self.__class__.__module__ return modname.split(".", 2)[-1] def prepare_build_dir(self): '''Ensure that a build dir exists for the recipe. This same single dir will be used for build