from glob import glob from os.path import realpath, join, dirname, curdir, basename, split from setuptools import Command from shutil import copyfile import sys from pythonforandroid.util import rmdir, ensure_dir def argv_contains(t): for arg in sys.argv: if arg.startswith(t): return True return False class Bdist(Command): user_options = [] package_type = None def initialize_options(self): for option in self.user_options: setattr(self, option[0].strip('=').replace('-', '_'), None) option_dict = self.distribution.get_option_dict(self.package_type) # This is a hack, we probably aren't supposed to loop through # the option_dict so early because distutils does exactly the # same thing later to check that we support the # options. However, it works... for (option, (source, value)) in option_dict.items(): setattr(self, option, str(value)) def finalize_options(self): setup_options = self.distribution.get_option_dict(self.package_type) for (option, (source, value)) in setup_options.items(): if source == 'command line': continue if not argv_contains('--' + option): # allow 'permissions': ['permission', 'permission] in apk if option == 'permissions': for perm in value: sys.argv.append('--permission={}'.format(perm)) elif option == 'orientation': for orient in value: sys.argv.append('--orientation={}'.format(orient)) elif value in (None, 'None'): sys.argv.append('--{}'.format(option)) else: sys.argv.append('--{}={}'.format(option, value)) # Inject some argv options from setup.py if the user did not # provide them if not argv_contains('--name'): name = self.distribution.get_name() sys.argv.append('--name="{}"'.format(name)) self.name = name if not argv_contains('--package'): package = 'org.test.{}'.format(self.name.lower().replace(' ', '')) print('WARNING: You did not supply an Android package ' 'identifier, trying {} instead.'.format(package)) print(' This may fail if this is not a valid identifier') sys.argv.append('--package={}'.format(package)) if not argv_contains('--version'): version = self.distribution.get_version() sys.argv.append('--version={}'.format(version)) if not argv_contains('--arch'): arch = 'armeabi-v7a' self.arch = arch sys.argv.append('--arch={}'.format(arch)) def run(self): self.prepare_build_dir() from pythonforandroid.entrypoints import main sys.argv[1] = self.package_type main() def prepare_build_dir(self): if argv_contains('--private') and not argv_contains('--launcher'): print('WARNING: Received --private argument when this would ' 'normally be generated automatically.') print(' This is probably bad unless you meant to do ' 'that.') bdist_dir = 'build/bdist.android-{}'.format(self.arch) rmdir(bdist_dir) ensure_dir(bdist_dir) globs = [] for directory, patterns in self.distribution.package_data.items(): for pattern in patterns: globs.append(join(directory, pattern)) filens = [] for pattern in globs: filens.extend(glob(pattern)) main_py_dirs = [] if not argv_contains('--launcher'): for filen in filens: new_dir = join(bdist_dir, dirname(filen)) ensure_dir(new_dir) print('Including {}'.format(filen)) copyfile(filen, join(bdist_dir, filen)) if basename(filen) in ('main.py', 'main.pyc'): main_py_dirs.append(filen) # This feels ridiculous, but how else to define the main.py dir? # Maybe should just fail? if not main_py_dirs and not argv_contains('--launcher'): print('ERROR: Could not find main.py, so no app build dir defined') print('You should name your app entry point main.py') exit(1) if len(main_py_dirs) > 1: print('WARNING: Multiple main.py dirs found, using the shortest path') main_py_dirs = sorted(main_py_dirs, key=lambda j: len(split(j))) if not argv_contains('--launcher'): sys.argv.append('--private={}'.format( join(realpath(curdir), bdist_dir, dirname(main_py_dirs[0]))) ) class BdistAPK(Bdist): """distutil command handler for 'apk'.""" description = 'Create an APK with python-for-android' package_type = 'apk' class BdistAAR(Bdist): """distutil command handler for 'aar'.""" description = 'Create an AAR with python-for-android' package_type = 'aar' class BdistAAB(Bdist): """distutil command handler for 'aab'.""" description = 'Create an AAB with python-for-android' package_type = 'aab' def _set_user_options(): # This seems like a silly way to do things, but not sure if there's a # better way to pass arbitrary options onwards to p4a user_options = [('requirements=', None, None), ] for i, arg in enumerate(sys.argv): if arg.startswith('--'): if ('=' in arg or (i < (len(sys.argv) - 1) and not sys.argv[i+1].startswith('-'))): user_options.append((arg[2:].split('=')[0] + '=', None, None)) else: user_options.append((arg[2:], None, None)) BdistAPK.user_options = user_options BdistAAB.user_options = user_options BdistAAR.user_options = user_options _set_user_options()