def test_get_virtualenv_executable(self, mock_sh_which): """ Test method :meth:`~pythonforandroid.util.get_virtualenv_executable`. In here we test: - that all calls to `sh.which` are performed, so we expect the first two `sh.which` calls should be None and the last one should return the expected virtualenv (the python3 one) - that we don't have virtualenv installed, so all calls to `sh.which` should return None """ expected_venv = os.path.join(os.path.expanduser("~"), ".local/bin/virtualenv") mock_sh_which.side_effect = [None, None, expected_venv] self.assertEqual(util.get_virtualenv_executable(), expected_venv) mock_sh_which.assert_has_calls([ mock.call("virtualenv2"), mock.call("virtualenv-2.7"), mock.call("virtualenv"), ]) self.assertEqual(mock_sh_which.call_count, 3) mock_sh_which.reset_mock() # Now test that we don't have virtualenv installed, so all calls to # `sh.which` should return None mock_sh_which.side_effect = [None, None, None] self.assertIsNone(util.get_virtualenv_executable()) self.assertEqual(mock_sh_which.call_count, 3) mock_sh_which.assert_has_calls([ mock.call("virtualenv2"), mock.call("virtualenv-2.7"), mock.call("virtualenv"), ])
def prepare_build_environment(self, user_sdk_dir, user_ndk_dir, user_android_api, user_ndk_api): '''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 # This is the old P4A-specific var if sdk_dir is None: sdk_dir = environ.get('ANDROIDSDK', None) # This seems used more conventionally if sdk_dir is None: sdk_dir = environ.get('ANDROID_HOME', None) # Checks in the buildozer SDK dir, useful for debug tests of p4a if sdk_dir is None: 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: raise BuildInterruptingException( 'Android SDK dir was not specified, exiting.') 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 info('Getting Android API version from user argument: {}'.format( android_api)) elif 'ANDROIDAPI' in environ: android_api = environ['ANDROIDAPI'] info('Found Android API target in $ANDROIDAPI: {}'.format( android_api)) else: info('Android API target was not set manually, using ' 'the default of {}'.format(RECOMMENDED_TARGET_API)) android_api = RECOMMENDED_TARGET_API android_api = int(android_api) self.android_api = android_api check_target_api(android_api, self.archs[0].arch) if exists(join(sdk_dir, 'tools', 'bin', 'avdmanager')): avdmanager = sh.Command(join(sdk_dir, 'tools', 'bin', 'avdmanager')) targets = avdmanager('list', 'target').stdout.decode('utf-8').split('\n') elif exists(join(sdk_dir, 'tools', 'android')): android = sh.Command(join(sdk_dir, 'tools', 'android')) targets = android('list').stdout.decode('utf-8').split('\n') else: raise BuildInterruptingException( 'Could not find `android` or `sdkmanager` binaries in Android SDK', instructions='Make sure the path to the Android SDK is correct' ) apis = [s for s in targets if re.match(r'^ *API level: ', s)] apis = [re.findall(r'[0-9]+', s) for s in apis] apis = [int(s[0]) for s in apis if s] info('Available Android APIs are ({})'.format(', '.join(map(str, apis)))) if android_api in apis: info(('Requested API target {} is available, ' 'continuing.').format(android_api)) else: raise BuildInterruptingException( ('Requested API target {} is not available, install ' 'it with the SDK android tool.').format(android_api)) # Find the Android NDK # Could also use ANDROID_NDK, but doesn't look like many tools use this ndk_dir = None if user_ndk_dir: ndk_dir = user_ndk_dir info('Getting NDK dir from from user argument') if ndk_dir is None: # The old P4A-specific dir ndk_dir = environ.get('ANDROIDNDK', None) if ndk_dir is not None: info('Found NDK dir in $ANDROIDNDK: {}'.format(ndk_dir)) if ndk_dir is None: # Apparently the most common convention ndk_dir = environ.get('NDK_HOME', None) if ndk_dir is not None: info('Found NDK dir in $NDK_HOME: {}'.format(ndk_dir)) if ndk_dir is None: # Another convention (with maven?) ndk_dir = environ.get('ANDROID_NDK_HOME', None) if ndk_dir is not None: info('Found NDK dir in $ANDROID_NDK_HOME: {}'.format(ndk_dir)) if ndk_dir is None: # Checks in the buildozer NDK dir, useful # # for debug tests of p4a possible_dirs = glob.glob( expanduser( join('~', '.buildozer', 'android', 'platform', 'android-ndk-r*'))) if possible_dirs: info('Found possible NDK dirs in buildozer dir: {}'.format( ', '.join([d.split(os.sep)[-1] for d in possible_dirs]))) info('Will attempt to use NDK at {}'.format(possible_dirs[0])) warning('This NDK lookup is intended for debug only, if you ' 'use python-for-android much you should probably ' 'maintain your own NDK download.') ndk_dir = possible_dirs[0] if ndk_dir is None: raise BuildInterruptingException( 'Android NDK dir was not specified') self.ndk_dir = realpath(ndk_dir) check_ndk_version(ndk_dir) ndk_api = None if user_ndk_api: ndk_api = user_ndk_api info( 'Getting NDK API version (i.e. minimum supported API) from user argument' ) elif 'NDKAPI' in environ: ndk_api = environ.get('NDKAPI', None) info('Found Android API target in $NDKAPI') else: ndk_api = min(self.android_api, RECOMMENDED_NDK_API) warning( 'NDK API target was not set manually, using ' 'the default of {} = min(android-api={}, default ndk-api={})'. format(ndk_api, self.android_api, RECOMMENDED_NDK_API)) ndk_api = int(ndk_api) self.ndk_api = ndk_api check_ndk_api(ndk_api, self.android_api) virtualenv = get_virtualenv_executable() if virtualenv is None: raise IOError('Couldn\'t find a virtualenv executable, ' 'you must install this to use p4a.') self.virtualenv = virtualenv info('Found virtualenv at {}'.format(virtualenv)) # path to some tools self.ccache = sh.which("ccache") if not self.ccache: info('ccache is missing, the build will not be optimized in the ' 'future.') for cython_fn in ("cython", "cython3", "cython2", "cython-2.7"): cython = sh.which(cython_fn) if cython: self.cython = cython break else: raise BuildInterruptingException('No cython binary found.') if not self.cython: ok = False warning("Missing requirement: cython is not installed") # This would need to be changed if supporting multiarch APKs arch = self.archs[0] platform_dir = arch.platform_dir toolchain_prefix = arch.toolchain_prefix toolchain_version = None self.ndk_platform = join(self.ndk_dir, 'platforms', 'android-{}'.format(self.ndk_api), platform_dir) if not exists(self.ndk_platform): warning('ndk_platform doesn\'t exist: {}'.format( self.ndk_platform)) ok = False py_platform = sys.platform if py_platform in ['linux2', 'linux3']: py_platform = 'linux' toolchain_versions = [] toolchain_path = join(self.ndk_dir, 'toolchains') if isdir(toolchain_path): toolchain_contents = glob.glob('{}/{}-*'.format( toolchain_path, toolchain_prefix)) toolchain_versions = [ split(path)[-1][len(toolchain_prefix) + 1:] for path in toolchain_contents ] else: warning('Could not find toolchain subdirectory!') ok = False toolchain_versions.sort() toolchain_versions_gcc = [] for toolchain_version in toolchain_versions: if toolchain_version[0].isdigit(): # GCC toolchains begin with a number toolchain_versions_gcc.append(toolchain_version) if toolchain_versions: info('Found the following toolchain versions: {}'.format( toolchain_versions)) info('Picking the latest gcc toolchain, here {}'.format( toolchain_versions_gcc[-1])) toolchain_version = toolchain_versions_gcc[-1] else: warning('Could not find any toolchain for {}!'.format( toolchain_prefix)) ok = False self.toolchain_prefix = toolchain_prefix self.toolchain_version = toolchain_version # Modify the path so that sh finds modules appropriately environ['PATH'] = ( '{ndk_dir}/toolchains/{toolchain_prefix}-{toolchain_version}/' 'prebuilt/{py_platform}-x86/bin/:{ndk_dir}/toolchains/' '{toolchain_prefix}-{toolchain_version}/prebuilt/' '{py_platform}-x86_64/bin/:{ndk_dir}:{sdk_dir}/' 'tools:{path}').format(sdk_dir=self.sdk_dir, ndk_dir=self.ndk_dir, toolchain_prefix=toolchain_prefix, toolchain_version=toolchain_version, py_platform=py_platform, path=environ.get('PATH')) for executable in ("pkg-config", "autoconf", "automake", "libtoolize", "tar", "bzip2", "unzip", "make", "gcc", "g++"): if not sh.which(executable): warning("Missing executable: {} is not installed".format( executable)) if not ok: raise BuildInterruptingException( 'python-for-android cannot continue due to the missing executables above' )
def prepare_build_environment(self, user_sdk_dir, user_ndk_dir, user_android_api, user_ndk_api): '''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 # This is the old P4A-specific var if sdk_dir is None: sdk_dir = environ.get('ANDROIDSDK', None) # This seems used more conventionally if sdk_dir is None: sdk_dir = environ.get('ANDROID_HOME', None) # Checks in the buildozer SDK dir, useful for debug tests of p4a if sdk_dir is None: 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: raise BuildInterruptingException('Android SDK dir was not specified, exiting.') 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 info('Getting Android API version from user argument: {}'.format(android_api)) elif 'ANDROIDAPI' in environ: android_api = environ['ANDROIDAPI'] info('Found Android API target in $ANDROIDAPI: {}'.format(android_api)) else: info('Android API target was not set manually, using ' 'the default of {}'.format(RECOMMENDED_TARGET_API)) android_api = RECOMMENDED_TARGET_API android_api = int(android_api) self.android_api = android_api check_target_api(android_api, self.archs[0].arch) if exists(join(sdk_dir, 'tools', 'bin', 'avdmanager')): avdmanager = sh.Command(join(sdk_dir, 'tools', 'bin', 'avdmanager')) targets = avdmanager('list', 'target').stdout.decode('utf-8').split('\n') elif exists(join(sdk_dir, 'tools', 'android')): android = sh.Command(join(sdk_dir, 'tools', 'android')) targets = android('list').stdout.decode('utf-8').split('\n') else: raise BuildInterruptingException( 'Could not find `android` or `sdkmanager` binaries in Android SDK', instructions='Make sure the path to the Android SDK is correct') apis = [s for s in targets if re.match(r'^ *API level: ', s)] apis = [re.findall(r'[0-9]+', s) for s in apis] apis = [int(s[0]) for s in apis if s] info('Available Android APIs are ({})'.format( ', '.join(map(str, apis)))) if android_api in apis: info(('Requested API target {} is available, ' 'continuing.').format(android_api)) else: raise BuildInterruptingException( ('Requested API target {} is not available, install ' 'it with the SDK android tool.').format(android_api)) # Find the Android NDK # Could also use ANDROID_NDK, but doesn't look like many tools use this ndk_dir = None if user_ndk_dir: ndk_dir = user_ndk_dir info('Getting NDK dir from from user argument') if ndk_dir is None: # The old P4A-specific dir ndk_dir = environ.get('ANDROIDNDK', None) if ndk_dir is not None: info('Found NDK dir in $ANDROIDNDK: {}'.format(ndk_dir)) if ndk_dir is None: # Apparently the most common convention ndk_dir = environ.get('NDK_HOME', None) if ndk_dir is not None: info('Found NDK dir in $NDK_HOME: {}'.format(ndk_dir)) if ndk_dir is None: # Another convention (with maven?) ndk_dir = environ.get('ANDROID_NDK_HOME', None) if ndk_dir is not None: info('Found NDK dir in $ANDROID_NDK_HOME: {}'.format(ndk_dir)) if ndk_dir is None: # Checks in the buildozer NDK dir, useful # # for debug tests of p4a possible_dirs = glob.glob(expanduser(join( '~', '.buildozer', 'android', 'platform', 'android-ndk-r*'))) if possible_dirs: info('Found possible NDK dirs in buildozer dir: {}'.format( ', '.join([d.split(os.sep)[-1] for d in possible_dirs]))) info('Will attempt to use NDK at {}'.format(possible_dirs[0])) warning('This NDK lookup is intended for debug only, if you ' 'use python-for-android much you should probably ' 'maintain your own NDK download.') ndk_dir = possible_dirs[0] if ndk_dir is None: raise BuildInterruptingException('Android NDK dir was not specified') self.ndk_dir = realpath(ndk_dir) check_ndk_version(ndk_dir) ndk_api = None if user_ndk_api: ndk_api = user_ndk_api info('Getting NDK API version (i.e. minimum supported API) from user argument') elif 'NDKAPI' in environ: ndk_api = environ.get('NDKAPI', None) info('Found Android API target in $NDKAPI') else: ndk_api = min(self.android_api, RECOMMENDED_NDK_API) warning('NDK API target was not set manually, using ' 'the default of {} = min(android-api={}, default ndk-api={})'.format( ndk_api, self.android_api, RECOMMENDED_NDK_API)) ndk_api = int(ndk_api) self.ndk_api = ndk_api check_ndk_api(ndk_api, self.android_api) virtualenv = get_virtualenv_executable() if virtualenv is None: raise IOError('Couldn\'t find a virtualenv executable, ' 'you must install this to use p4a.') self.virtualenv = virtualenv info('Found virtualenv at {}'.format(virtualenv)) # path to some tools self.ccache = sh.which("ccache") if not self.ccache: info('ccache is missing, the build will not be optimized in the ' 'future.') for cython_fn in ("cython", "cython3", "cython2", "cython-2.7"): cython = sh.which(cython_fn) if cython: self.cython = cython break else: raise BuildInterruptingException('No cython binary found.') if not self.cython: ok = False warning("Missing requirement: cython is not installed") # This would need to be changed if supporting multiarch APKs arch = self.archs[0] platform_dir = arch.platform_dir toolchain_prefix = arch.toolchain_prefix toolchain_version = None self.ndk_platform = join( self.ndk_dir, 'platforms', 'android-{}'.format(self.ndk_api), platform_dir) if not exists(self.ndk_platform): warning('ndk_platform doesn\'t exist: {}'.format( self.ndk_platform)) ok = False py_platform = sys.platform if py_platform in ['linux2', 'linux3']: py_platform = 'linux' toolchain_versions = [] toolchain_path = join(self.ndk_dir, 'toolchains') if isdir(toolchain_path): toolchain_contents = glob.glob('{}/{}-*'.format(toolchain_path, toolchain_prefix)) toolchain_versions = [split(path)[-1][len(toolchain_prefix) + 1:] for path in toolchain_contents] else: warning('Could not find toolchain subdirectory!') ok = False toolchain_versions.sort() toolchain_versions_gcc = [] for toolchain_version in toolchain_versions: if toolchain_version[0].isdigit(): # GCC toolchains begin with a number toolchain_versions_gcc.append(toolchain_version) if toolchain_versions: info('Found the following toolchain versions: {}'.format( toolchain_versions)) info('Picking the latest gcc toolchain, here {}'.format( toolchain_versions_gcc[-1])) toolchain_version = toolchain_versions_gcc[-1] else: warning('Could not find any toolchain for {}!'.format( toolchain_prefix)) ok = False self.toolchain_prefix = toolchain_prefix self.toolchain_version = toolchain_version # Modify the path so that sh finds modules appropriately environ['PATH'] = ( '{ndk_dir}/toolchains/{toolchain_prefix}-{toolchain_version}/' 'prebuilt/{py_platform}-x86/bin/:{ndk_dir}/toolchains/' '{toolchain_prefix}-{toolchain_version}/prebuilt/' '{py_platform}-x86_64/bin/:{ndk_dir}:{sdk_dir}/' 'tools:{path}').format( sdk_dir=self.sdk_dir, ndk_dir=self.ndk_dir, toolchain_prefix=toolchain_prefix, toolchain_version=toolchain_version, py_platform=py_platform, path=environ.get('PATH')) for executable in ("pkg-config", "autoconf", "automake", "libtoolize", "tar", "bzip2", "unzip", "make", "gcc", "g++"): if not sh.which(executable): warning("Missing executable: {} is not installed".format( executable)) if not ok: raise BuildInterruptingException( 'python-for-android cannot continue due to the missing executables above')