ballistica/tools/batools/androidsdkutils.py

181 lines
6.0 KiB
Python
Executable File

# 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 listed in gradle 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)