# Released under the MIT License. See LICENSE for details.#"""Utilities for wrangling Android SDK bits."""from__future__importannotationsimportosimportsysfrompathlibimportPathfromtypingimportTYPE_CHECKINGfromefro.errorimportCleanErrorifTYPE_CHECKING:passdef_parse_lprop_file(local_properties_path:str)->str:withopen(local_properties_path,encoding='utf-8')asinfile:lines=infile.read().splitlines()sdk_dir_lines=[lforlinlinesif'sdk.dir='inl]iflen(sdk_dir_lines)!=1:raiseRuntimeError("Couldn't find sdk dir in local.properties.")sdk_dir=sdk_dir_lines[0].split('=')[1].strip()ifnotos.path.isdir(sdk_dir):raiseRuntimeError(f'Sdk dir from local.properties not found: {sdk_dir}.')returnsdk_dirdef_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=Falsesdk_dir=None# First off, if they have ANDROID_SDK_ROOT set, use that.envvar=os.environ.get('ANDROID_SDK_ROOT')ifenvvarisnotNone:ifos.path.isdir(envvar):sdk_dir=envvarfound=True# Otherwise try some standard locations.ifnotfound:home=os.getenv('HOME')asserthomeisnotNonetest_paths=[home+'/Library/Android/sdk',home+'/AndroidSDK']forsdk_dirintest_paths:ifos.path.exists(sdk_dir):found=Truebreakifnotfound:print('WOULD CHECK',os.environ.get('ANDROID_SDK_ROOT'))assertsdk_dirisnotNoneifnotfound:ifnotos.path.exists(sdk_dir):raiseCleanError(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')withopen(local_properties_path,'w',encoding='utf-8')asoutfile:outfile.write(config)print('Generating local.properties file (found Android SDK at "'+sdk_dir+'")',file=sys.stderr,)returnsdk_dir
[docs]defrun(projroot:str,args:list[str])->None:"""Main script entry point."""# pylint: disable=too-many-branches# pylint: disable=too-many-localsiflen(args)!=1:raiseCleanError('Expected 1 arg')command=args[0]valid_args=['check','get-sdk-path','get-ndk-path','get-adb-path']ifcommandnotinvalid_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')ifos.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.ifnotos.path.isfile(sdk_dir+'/platform-tools/adb'):raiseRuntimeError('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')ifandroid_homeisnotNone:ifandroid_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)ifcommand=='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.ifcommand=='get-ndk-path':gradlepath=Path(projroot,'ballisticakit-android/BallisticaKit/build.gradle')withgradlepath.open(encoding='utf-8')asinfile:lines=[lforlininfile.readlines()ifl.strip().startswith('ndkVersion ')]iflen(lines)!=1:raiseRuntimeError(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)ifnotos.path.isdir(path):raiseRuntimeError(f'NDK path listed in gradle file not found: {path}.')print(path)ifcommand=='get-adb-path':importsubprocessadbpath=Path(sdk_dir,'platform-tools/adb')ifnotos.path.exists(adbpath):raiseRuntimeError(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)ifresult.returncode==0:wpath=result.stdout.decode().strip()ifwpath==str(adbpath):print('adb')returnprint(adbpath)