# Released under the MIT License. See LICENSE for details. # """Utilities for wrangling Android SDK bits.""" from __future__ import annotations import os import sys from pathlib import Path from typing import TYPE_CHECKING from efro.error import CleanError if TYPE_CHECKING: pass def _parse_lprop_file(local_properties_path: str) -> str: with open(local_properties_path, encoding='utf-8') as infile: lines = infile.read().splitlines() sdk_dir_lines = [l for l in lines if 'sdk.dir=' in l] if len(sdk_dir_lines) != 1: raise RuntimeError("Couldn't find sdk dir in local.properties.") sdk_dir = sdk_dir_lines[0].split('=')[1].strip() if not os.path.isdir(sdk_dir): raise RuntimeError( f'Sdk dir from local.properties not found: {sdk_dir}.' ) return sdk_dir def _gen_lprop_file(local_properties_path: str) -> str: os.makedirs(os.path.dirname(local_properties_path), exist_ok=True) # Ok, we've got no local.properties file; attempt to find # android sdk in standard locations and create a default # one if we can. found = False sdk_dir = None # First off, if they have ANDROID_SDK_ROOT set, use that. envvar = os.environ.get('ANDROID_SDK_ROOT') if envvar is not None: if os.path.isdir(envvar): sdk_dir = envvar found = True # Otherwise try some standard locations. if not found: home = os.getenv('HOME') assert home is not None test_paths = [home + '/Library/Android/sdk', home + '/AndroidSDK'] for sdk_dir in test_paths: if os.path.exists(sdk_dir): found = True break if not found: print('WOULD CHECK', os.environ.get('ANDROID_SDK_ROOT')) assert sdk_dir is not None if not found: if not os.path.exists(sdk_dir): raise CleanError( f'Android sdk not found at {sdk_dir}; install ' 'the android sdk and try again.', ) config = ( '\n# This file was automatically generated by ' + os.path.abspath(sys.argv[0]) + '\n' '# Feel free to override these paths if you have your android' ' sdk elsewhere\n' '\n' 'sdk.dir=' + sdk_dir + '\n' ) with open(local_properties_path, 'w', encoding='utf-8') as outfile: outfile.write(config) print( 'Generating local.properties file (found Android SDK at "' + sdk_dir + '")', file=sys.stderr, ) return sdk_dir def run(projroot: str, args: list[str]) -> None: """Main script entry point.""" # pylint: disable=too-many-branches # pylint: disable=too-many-locals if len(args) != 1: raise CleanError('Expected 1 arg') command = args[0] valid_args = ['check', 'get-sdk-path', 'get-ndk-path', 'get-adb-path'] if command not in valid_args: print('INVALID ARG; expected one of', valid_args, file=sys.stderr) sys.exit(255) # In all cases we make sure there's a local.properties in our android # dir that contains valid sdk path. If not, we attempt to create it. local_properties_path = os.path.join( projroot, 'ballisticakit-android', 'local.properties' ) if os.path.isfile(local_properties_path): sdk_dir = _parse_lprop_file(local_properties_path) else: sdk_dir = _gen_lprop_file(local_properties_path) # Sanity check; look for a few things in the sdk that we expect to # be there. if not os.path.isfile(sdk_dir + '/platform-tools/adb'): raise RuntimeError( 'ERROR: android sdk at "' + sdk_dir + '" does not seem valid' ) # Sanity check: if they've got ANDROID_HOME set, make sure it lines up with # what we're pointing at. android_home = os.getenv('ANDROID_HOME') if android_home is not None: if android_home != sdk_dir: print( 'ERROR: sdk dir mismatch; ANDROID_HOME is "' + android_home + '" but local.properties set to "' + sdk_dir + '"', file=sys.stderr, ) sys.exit(255) if command == 'get-sdk-path': print(sdk_dir) # We no longer add the ndk path to local.properties (doing so is # obsolete) but we still want to support returning the ndk path, as # some things such as external python builds still ask for this. So # now we just pull it from the project gradle file where we set it # explicitly. if command == 'get-ndk-path': gradlepath = Path( projroot, 'ballisticakit-android/BallisticaKit/build.gradle' ) with gradlepath.open(encoding='utf-8') as infile: lines = [ l for l in infile.readlines() if l.strip().startswith('ndkVersion ') ] if len(lines) != 1: raise RuntimeError( f'Expected exactly one ndkVersion line in build.gradle;' f' found {len(lines)}' ) ver = lines[0].strip().replace("'", '').replace('"', '').split()[-1] path = os.path.join(sdk_dir, 'ndk', ver) if not os.path.isdir(path): raise RuntimeError( f'NDK path listed in gradle file not found: {path}.' ) print(path) if command == 'get-adb-path': import subprocess adbpath = Path(sdk_dir, 'platform-tools/adb') if not os.path.exists(adbpath): raise RuntimeError(f"ADB not found at expected path '{adbpath}'.") # Ok, we've got a valid adb path. # Now, for extra credit, let's see if 'which adb' points to the # same one and simply return 'adb' if so. This makes our make # output nice and readable (and hopefully won't cause problems) result = subprocess.run( 'which adb', shell=True, capture_output=True, check=False ) if result.returncode == 0: wpath = result.stdout.decode().strip() if wpath == str(adbpath): print('adb') return print(adbpath)