mtots 0.1.2

The mtots scripting language
Documentation
import a.fs
import a.os
import a.time
import ..consts::_template_dir

class SDK {
    r###"
    Logically wraps the Android SDK
    "###
    [android_sdk_path]

    static def __call() = {
        "Do some minimal validity check on the sdk path"
        __malloc(SDK, [_guess_sdk_root()])
    }

    def _emulator(self) = self.android_sdk_path.join('emulator', 'emulator')

    def _adb(self) = self.android_sdk_path.join('platform-tools', 'adb')

    def materialize(self, outpath, project) {
        """
        Materialize an android project to the given path
        """
        fs::copy(_template_dir.join('base'), outpath)
    }

    def _run_args(self) = Table(
        encoding=:utf8,
        clear_envs=true,
        envs=[
            :ANDROID_SDK_ROOT: self.android_sdk_path,
            :PATH: os::env('PATH'),
        ],
    )

    def _run(self, *args, **kwargs) = {
        kwargs = (self._run_args() + kwargs)
        os::run(
            *args,
            **kwargs,
        )
    }

    def _gradlew(self, project_path) = {
        if os::name == "windows" {
            """
            process::Command can't really deal with UNC paths...
            So as a workaround, just strip the prefix for now
            """
            path_str = str(project_path.canon())
            path_str = path_str.lstrip(r'\\?\')
            path_str + r'\gradlew.bat'
        } else {
            project_path.canon().join('gradlew')
        }
    }

    def _run_gradlew(self, project_path, cmdargs, *args, **kwargs) = {
        if os::name == 'windows' {
            self._run(
                self._gradlew(project_path),
                cmdargs,
                dir=project_path,
                *args,
                **kwargs,
            )
        } else {
            r###"
            We call sh instead of the script directly so that
            gradlew doesn't need executable permissions
            "###
            self._run(
                'sh',
                [self._gradlew(project_path)] + cmdargs,
                dir=project_path,
                *args,
                **kwargs,
            )
        }
    }

    def _start(self, *args, **kwargs) = {
        os::Process(
            *args,
            **(self._run_args() + kwargs),
        )
    }

    def build(self, project_path) {
        r###"
        Builds the android app at the given path
        "###
        self._run_gradlew(
            project_path,
            ['build'],
        )
    }

    def install(self, project_path) {
        r###"
        Installs the android app at the given path
        "###
        self._run_gradlew(
            project_path,
            ['installDebug'],
        )
    }

    def _get_emulator_name(self) = {
        r###"
        Tries to get the name of some emulator available
        on the system
        "###
        self._run(
            self._emulator(),
            ['-list-avds'],
            stdout=:pipe,
        )[1].trim().lines().list()[-1]
    }

    def _start_emulator(self) = {
        emulator_name = self._get_emulator_name()
        self._start(
            self._emulator(),
            ['-avd', emulator_name, '-port', '5554'],
        )
        self._wait_for_emulator_ready()
    }

    def _wait_for_emulator_ready(self) {
        self._run(self._adb(), ['wait-for-device'])
    }

    def _emulator_started(self) = {
        [_status, stdout, _stderr] = self._run(
            self._adb(),
            ['devices', '-l'],
            stdout=:pipe,
            encoding=:utf8,
        )
        stdout.trim().lines().list().len() > 1
    }

    def start_emulator(self) = {
        r###"
        Starts an emulator if none found,
        and waits for it to be ready
        "###
        if self._emulator_started() {
            self._wait_for_emulator_ready()
            nil
        } else {
            self._start_emulator()
        }
    }
}

def _guess_sdk_root() = {
    def _failure() {
        __raise(
            RuntimeError,
            'Could not find android sdk root (maybe set ANDROID_SDK_ROOT?)',
        )
    }

    path = os::env(:ANDROID_SDK_ROOT)
    if path {
        Path(path)
    } elif os::name == 'windows' {
        path = Path((os::home() or _failure()).join(r'AppData\Local\Android\Sdk'))
        if path.is_dir() {
            path
        } else {
            _failure()
        }
    } elif os::name == 'macos' {
        path = Path((os::home() or _failure()).join(r'Library/Android/sdk'))
        if path.is_dir() {
            path
        } else {
            _failure()
        }
    } else {
        _failure()
    }
}