diff --git a/README.md b/README.md index 5887c538..f085b44e 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ here](https://github.com/vlang/vab/blob/3091ade4c9792c6a37596ccfa9299fb269d3160e In either case the following dependencies is required before `vab` will work as intented. -Dependencies: +## Runtime dependencies * V * Java (JDK) >= 8 (>= 9 on Windows) * Android SDK @@ -127,7 +127,8 @@ EMULATOR # Absolute path to the emulator to use ``` ```bash -VAB_FLAGS # Use to pass flags to vab. Command-line flags overwrites any flags/values set via VAB_FLAGS. +VAB_EXE # Absolute path to a vab executable (Used in tests and sub-cmd execution) +VAB_FLAGS # Used to pass flags to vab. Command-line flags overwrites any flags/values set via VAB_FLAGS. VAB_KILL_ADB # Set to let vab kill adb after use. This is useful on some hosts. ``` @@ -203,6 +204,12 @@ The accompaning script used in the video can be found here: See [*"Where is the `examples` folder?"*](docs/FAQ.md#where-is-the-examples-folder) in the [FAQ](docs/FAQ.md). +# Tests + +`vab`, like many other V modules, can be tested with `v test .`. +Note that `vab` has *runtime* tests that requires all [runtime dependencies](#runtime-dependencies) +to be installed in order for the tests to run correctly. + # Notes `vab` targets as low an API level as possible by default for maximum diff --git a/cli/cli.v b/cli/cli.v index 93217a19..0792ba42 100644 --- a/cli/cli.v +++ b/cli/cli.v @@ -53,6 +53,7 @@ pub const vab_env_vars = [ 'AAPT2', 'JAVA_HOME', 'VEXE', + 'VAB_EXE', 'VMODULES', ] diff --git a/tests/at-runtime/vab_build_apk_test.v b/tests/at-runtime/vab_build_apk_test.v new file mode 100644 index 00000000..a01afbaf --- /dev/null +++ b/tests/at-runtime/vab_build_apk_test.v @@ -0,0 +1,85 @@ +import os +import vab.vabxt +import vab.vxt +import vab.android.util + +const test_dir_base = os.join_path(os.vtmp_dir(), 'vab', 'tests', 'runtime') +const apk_arch_dirs = ['arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64', 'armeabi'] + +fn setup_apk_build(id string) (string, string) { + test_dir := os.join_path(test_dir_base, id) + os.rm(test_dir) or {} + os.mkdir_all(test_dir) or { panic('mkdir_all failed making "${test_dir}": ${err}') } + + // vab (per design) implicitly deploys to any devices sat via `--device-id`. + // Make sure no deployment is done after build if CI/other sets `ANDROID_SERIAL` + os.unsetenv('ANDROID_SERIAL') + vab := vabxt.vabexe() + assert vab != '', 'vab needs to be installed to run this test' + return vab, test_dir +} + +fn v_example(path string) string { + v_root := vxt.home() + examples_root := os.join_path(v_root, 'examples') + example := os.join_path(examples_root, ...path.split('/')) + assert os.is_file(example) || os.is_dir(example) == true, 'example not found. Ensure a full V source install (with examples) is present' + return example +} + +fn run(cmd string) { + eprintln('running: ${cmd}') + res := os.execute(cmd) + if res.exit_code != 0 { + dump(res.output) + } + assert res.exit_code == 0 +} + +fn extract_and_check_apk(libname string, path string) { + expected_lib_name := libname + expected_apk := os.join_path(path, '${expected_lib_name}.apk') + assert os.is_file(expected_apk) + + extract_dir := os.join_path(path, 'extracted') + extracted_apk_path := os.join_path(extract_dir, expected_lib_name) + util.unzip(expected_apk, extracted_apk_path) or { + panic('unzip failed extracting "${expected_apk}": ${err}') + } + + dump(os.ls(extracted_apk_path) or { panic('ls failed on "${extracted_apk_path}": ${err}') }) + // test that expected libs are actually present in the apk + for arch in apk_arch_dirs { + lib_dir := os.join_path(extracted_apk_path, 'lib', arch) + dump(os.ls(lib_dir) or { panic('ls failed on "${lib_dir}": ${err}') }) + assert os.is_file(os.join_path(lib_dir, 'lib${expected_lib_name}.so')) + } +} + +fn test_build_apk_way_1() { + vab, test_dir := setup_apk_build(@FN) + + vab_cmd := [vab, '-o', test_dir, v_example('gg/worker_thread.v')].join(' ') + run(vab_cmd) + + extract_and_check_apk('v_test_app', test_dir) +} + +fn test_build_apk_way_2() { + vab, test_dir := setup_apk_build(@FN) + + vab_cmd := [vab, v_example('sokol/particles'), '-o', test_dir].join(' ') + run(vab_cmd) + + extract_and_check_apk('v_test_app', test_dir) +} + +fn test_build_apk_way_3() { + vab, test_dir := setup_apk_build(@FN) + + vab_cmd := [vab, '-f "-d trace_moves_spool_to_sbin"', v_example('sokol/particles'), + '-o', test_dir].join(' ') + run(vab_cmd) + + extract_and_check_apk('v_test_app', test_dir) +} diff --git a/vabxt/vabxt.v b/vabxt/vabxt.v new file mode 100644 index 00000000..06463952 --- /dev/null +++ b/vabxt/vabxt.v @@ -0,0 +1,122 @@ +// Copyright(C) 2019-2022 Lars Pontoppidan. All rights reserved. +// Use of this source code is governed by an MIT license file distributed with this software package +module vabxt + +import os +import regex + +// vabexe returns the path to the `vab` executable if found +// on the host platform, otherwise a blank `string`. +pub fn vabexe() string { + mut exe := os.getenv('VAB_EXE') + $if !windows { + if os.is_executable(exe) { + return os.real_path(exe) + } + possible_symlink := os.find_abs_path_of_executable('vab') or { '' } + if os.is_executable(possible_symlink) { + return os.real_path(possible_symlink) + } + vmodules_path := vmodules() or { '' } + if os.is_file(os.join_path(vmodules_path, 'vab', 'vab')) { + return os.join_path(vmodules_path, 'vab', 'vab') + } + } $else { + if os.exists(exe) { + return exe + } + system_path := os.find_abs_path_of_executable('vab') or { '' } + if os.exists(system_path) { + exe = system_path + } + if !os.exists(exe) { + res := os.execute('where.exe vab') + if res.exit_code != 0 { + exe = '' + } else { + return res.output.trim('\n\r') + } + } + vmodules_path := vmodules() or { '' } + if os.is_file(os.join_path(vmodules_path, 'vab', 'vab.exe')) { + return os.join_path(vmodules_path, 'vab', 'vab.exe') + } + } + + return exe +} + +// vmodules returns the path to the `.vmodules` folder if found +pub fn vmodules() !string { + mut vmodules_path := os.getenv('VMODULES') + if !os.is_dir(vmodules_path) { + vmodules_path = os.join_path(os.home_dir(), '.vmodules') + } + if !os.is_dir(vmodules_path) { + return error(@MOD + '.' + @FN + ': no valid v modules path found at "${vmodules_path}"') + } + return vmodules_path +} + +pub fn found() bool { + return home() != '' +} + +pub fn home() string { + // credits to @spytheman: + // https://discord.com/channels/592103645835821068/592294828432424960/746040606358503484 + mut exe := vabexe() + $if !windows { + if os.is_executable(exe) { + return os.dir(exe) + } + } $else { + if os.exists(exe) { + exe = exe.replace('/', os.path_separator) + // Skip the `.bin\` dir + if os.dir(exe).ends_with('.bin') { + exe = os.dir(exe) + } + return os.dir(exe) + } + } + return '' +} + +pub fn version() string { + mut version := '' + vab := vabexe() + if vab != '' { + vab_version := os.execute(vab + ' --version') + if vab_version.exit_code != 0 { + return version + } + output := vab_version.output + mut re := regex.regex_opt(r'.*(\d+\.?\d*\.?\d*)') or { panic(err) } + start, _ := re.match_string(output) + if start >= 0 && re.groups.len > 0 { + version = output[re.groups[0]..re.groups[1]] + } + return version + } + return '0.0.0' +} + +pub fn version_commit_hash() string { + mut hash := '' + vab := vabexe() + if vab != '' { + vab_version := os.execute(vab + ' --version') + if vab_version.exit_code != 0 { + return '' + } + output := vab_version.output + mut re := regex.regex_opt(r'.*\d+\.?\d*\.?\d* ([a-fA-F0-9]{7,})') or { panic(err) } + start, _ := re.match_string(output) + if start >= 0 && re.groups.len > 0 { + hash = output[re.groups[0]..re.groups[1]] + } + return hash + } + return 'deadbeef' +}