from __future__ import print_function from os.path import (join, realpath, dirname, expanduser, exists, split, isdir) from os import environ, listdir import os import glob import sys import re import sh from pythonforandroid.util import (ensure_dir, current_directory) from pythonforandroid.logger import (info, warning, error, info_notify, Err_Fore, Err_Style, info_main, shprint) from pythonforandroid.archs import ArchARM, ArchARMv7_a, Archx86, Archx86_64, ArchAarch_64 from pythonforandroid.recipe import Recipe DEFAULT_ANDROID_API = 15 class Context(object): '''A build context. If anything will be built, an instance this class will be instantiated and used to hold all the build state.''' env = environ.copy() root_dir = None # the filepath of toolchain.py storage_dir = None # the root dir where builds and dists will be stored build_dir = None # in which bootstraps are copied for building # and recipes are built dist_dir = None # the Android project folder where everything ends up libs_dir = None # where Android libs are cached after build but # before being placed in dists aars_dir = None ccache = None # whether to use ccache cython = None # the cython interpreter name ndk_platform = None # the ndk platform directory dist_name = None # should be deprecated in favour of self.dist.dist_name bootstrap = None bootstrap_build_dir = None recipe_build_order = None # Will hold the list of all built recipes symlink_java_src = False # If True, will symlink instead of copying during build java_build_tool = 'auto' @property def packages_path(self): '''Where packages are downloaded before being unpacked''' return join(self.storage_dir, 'packages') @property def templates_dir(self): return join(self.root_dir, 'templates') @property def libs_dir(self): # Was previously hardcoded as self.build_dir/libs dir = join(self.build_dir, 'libs_collections', self.bootstrap.distribution.name) ensure_dir(dir) return dir @property def javaclass_dir(self): # Was previously hardcoded as self.build_dir/java dir = join(self.build_dir, 'javaclasses', self.bootstrap.distribution.name) ensure_dir(dir) return dir @property def aars_dir(self): dir = join(self.build_dir, 'aars', self.bootstrap.distribution.name) ensure_dir(dir) return dir @property def python_installs_dir(self): dir = join(self.build_dir, 'python-installs') ensure_dir(dir) return dir def get_python_install_dir(self): dir = join(self.python_installs_dir, self.bootstrap.distribution.name) return dir def setup_dirs(self, storage_dir): '''Calculates all the storage and build dirs, and makes sure the directories exist where necessary.''' self.storage_dir = expanduser(storage_dir) if ' ' in self.storage_dir: raise ValueError('storage dir path cannot contain spaces, please ' 'specify a path with --storage-dir') self.build_dir = join(self.storage_dir, 'build') self.dist_dir = join(self.storage_dir, 'dists') def ensure_dirs(self): ensure_dir(self.storage_dir) ensure_dir(self.build_dir) ensure_dir(self.dist_dir) ensure_dir(join(self.build_dir, 'bootstrap_builds')) ensure_dir(join(self.build_dir, 'other_builds')) @property def android_api(self): '''The Android API being targeted.''' if self._android_api is None: raise ValueError('Tried to access android_api but it has not ' 'been set - this should not happen, something ' 'went wrong!') return self._android_api @android_api.setter def android_api(self, value): self._android_api = value @property def ndk_ver(self): '''The version of the NDK being used for compilation.''' if self._ndk_ver is None: raise ValueError('Tried to access ndk_ver but it has not ' 'been set - this should not happen, something ' 'went wrong!') return self._ndk_ver @ndk_ver.setter def ndk_ver(self, value): self._ndk_ver = value @property def sdk_dir(self): '''The path to the Android SDK.''' if self._sdk_dir is None: raise ValueError('Tried to access sdk_dir but it has not ' 'been set - this should not happen, something ' 'went wrong!') return self._sdk_dir @sdk_dir.setter def sdk_dir(self, value): self._sdk_dir = value @property def ndk_dir(self): '''The path to the Android NDK.''' if self._ndk_dir is None: raise ValueError('Tried to access ndk_dir but it has not ' 'been set - this should not happen, something ' 'went wrong!') return self._ndk_dir @ndk_dir.setter def ndk_dir(self, value): self._ndk_dir = value def prepare_build_environment(self, user_sdk_dir, user_ndk_dir, user_android_api, user_ndk_ver): '''Checks that build dependencies exist and sets internal variables for the Android SDK etc. ..warning:: This *must* be called before trying any build stuff ''' self.ensure_dirs() if self._build_env_prepared: return ok = True # Work out where the Android SDK is sdk_dir = None if user_sdk_dir: sdk_dir = user_sdk_dir if sdk_dir is None: # This is the old P4A-specific var sdk_dir = environ.get('ANDROIDSDK', None) if sdk_dir is None: # This seems used more conventionally sdk_dir = environ.get('ANDROID_HOME', None) if sdk_dir is None: # Checks in the buildozer SDK dir, useful # for debug tests of p4a possible_dirs = glob.glob(expanduser(join( '~', '.buildozer', 'android', 'platform', 'android-sdk-*'))) possible_dirs = [d for d in possible_dirs if not (d.endswith('.bz2') or d.endswith('.gz'))] if possible_dirs: info('Found possible SDK dirs in buildozer dir: {}'.format( ', '.join([d.split(os.sep)[-1] for d in possible_dirs]))) info('Will attempt to use SDK at {}'.format(possible_dirs[0])) warning('This SDK lookup is intended for debug only, if you ' 'use python-for-android much you should probably ' 'maintain your own SDK download.') sdk_dir = possible_dirs[0] if sdk_dir is None: warning('Android SDK dir was not specified, exiting.') exit(1) self.sdk_dir = realpath(sdk_dir) # Check what Android API we're using android_api = None if user_android_api: android_api = user_android_api if android_api is not None: info('Getting Android API version from user argument') if android_api is None: android_api = environ.get('ANDROIDAPI', None) if android_api is not None: info('Found Android API target in $ANDROIDAPI') if android_api is None: info('Android API target was not set manually, using ' 'the default of {}'.format(DEFAULT_ANDROID_API)) android_api = DEFAULT_ANDROID_API android_api = int(android_api) self.android_api = android_api if self.android_api >= 21 and self.archs[0