Source code for batools.androidsdkutils

# 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


[docs] 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)