diff --git a/Maekfile.js b/Maekfile.js index fbb69f0..57ec696 100644 --- a/Maekfile.js +++ b/Maekfile.js @@ -182,44 +182,69 @@ const android_options = { }; -maek.CPP('main.cpp', android_options); - const android_game_objs = game_sources.map((x) => maek.CPP(x, undefined, android_options)); const android_common_objs = common_sources.map((x) => maek.CPP(x, undefined, android_options)); -const android_game_so = maek.LINK([...android_game_objs, ...android_common_objs], 'dist-android/apk/lib/arm64-v8a/libgame.so', android_options); +const android_game_so = maek.LINK([...android_game_objs, ...android_common_objs], 'objs/android/apk/lib/arm64-v8a/libgame.so', android_options); -//quick generic "RULE" for running commands with maek: -// [outFiles] = maek.RULE([outFile0, outFile1, ...], [inFile0, inFile1, ...], command, [desc]) -maek.RULE = (outFiles, inFiles, command, desc = "run") => { - const task = async () => { - for (const outFile of outFiles) { - await fsPromises.mkdir(path.dirname(outFile), { recursive: true }); - } - await run(command, `${task.label}: ${desc} [${idx} of ${commands.length+1}]`, - async () => { - return { - read:[...inFiles], - written:[...outFiles] - }; - } - ); - }; +const ovr_openxr_loader_so = maek.COPY('../ovr-openxr-sdk/OpenXR/Libs/Android/arm64-v8a/Release/libopenxr_loader.so', 'objs/android/apk/lib/arm64-v8a/libopenxr_loader.so'); - task.depends = [...inFiles]; - task.label = `RULE {${inFiles.join(", ")}}`; - for (outFile of outFiles) { - if (outFile in maek.tasks) { - throw new Error(`Task ${task.label} purports to create ${outFile}, but ${maek.tasks[outFile].label} already creates that file.`); +const AAPT2 = `../android-sdk/build-tools/30.0.3/aapt2`; + +const apkFile = `android/game.apk`; + +const android_icons = [ ]; +for (const dpi of ['mdpi', 'hdpi', 'xhdpi', 'xxhdpi', 'xxxhdpi']) { + const inFile = `android/res/mipmap-${dpi}/gp-icon.png`; + const outFile = `objs/android/apk/mipmap-${dpi}_gp-icon.png.flat`; + const command = [AAPT2, 'compile', '-o', 'objs/android/apk', '-v', inFile]; + android_icons.push( ...maek.RULE( [outFile], [inFile], command, "aapt compile" ) ); +} + +//basically just the shell script, transcribed: + +const package_apk_task = async () => { + + const proc = child_process.spawnSync( + [AAPT2, + ); + //make object file: + await fsPromises.mkdir(path.dirname(objFile), { recursive: true }); + await fsPromises.mkdir(path.dirname(depsFile), { recursive: true }); + await run(command, `${task.label}: compile + prerequisites`, + async () => { + return { + read:[...await loadDeps()], + written:[objFile, depsFile] + }; } - maek.tasks[outFile] = task; - } - return outFiles; + ); }; +package_apk_task.depends = [android_game_so, ovr_openxr_loader_so, android_icons]; +package_apk_task.label = `PACKAGE ${apkFile}`; +maek.tasks[apkFile] = task; -maek.TARGETS = [android_game_so]; //DEBUG + + +const android_apk = maek.RULE( + [`dist-android/game.apk`], + [android_game_so, ...android_icons, 'android/AndroidManifest.xml'], + ['../../../' + AAPT2, 'link', + '-o', 'dist-android/game.apk', + '-I', `../android-sdk/platforms/android-29/android.jar`, + ...android_icons, + android_game_so, + '--manifest', 'android/AndroidManifest.xml', + //'-A', 'objs/android/apk', + '-v' + ], + {label:"aapt link", cwd:'objs/android/apk'} +); + + +maek.TARGETS = [android_apk]; //DEBUG //====================================================================== @@ -350,6 +375,42 @@ function init_maek() { return dstFile; }; + //RULE adds a generic task: + // outFiles is an array of output files + // inFiles is an array of input files + // command is a command (as an array of [program, arg1, arg2, ...] + // options: + // label: printed during rule execution + // cwd: working directory for command (if not this directory) + //returns a copy of outFiles + // [outFiles] = maek.RULE([outFile0, outFile1, ...], [inFile0, inFile1, ...], command, [desc]) + maek.RULE = (outFiles, inFiles, command, {label='run', cwd=''}) => { + const task = async () => { + for (const outFile of outFiles) { + await fsPromises.mkdir(path.dirname(outFile), { recursive: true }); + } + await run(command, `${task.label}: ${label}`, + async () => { + return { + read:[...inFiles], + written:[...outFiles] + }; + }, + cwd + ); + }; + + task.depends = [...inFiles]; + task.label = `RULE {${outFiles.join(", ")}}`; + for (outFile of outFiles) { + if (outFile in maek.tasks) { + throw new Error(`Task ${task.label} purports to create ${outFile}, but ${maek.tasks[outFile].label} already creates that file.`); + } + maek.tasks[outFile] = task; + } + return outFiles; + }; + //maek.CPP makes an object from a c++ source file: // cppFile is the source file name @@ -526,13 +587,14 @@ function init_maek() { //runs a shell command (presented as an array) // 'message' will be displayed above the command // 'cacheInfoFn', if provided, will be called after function is run to determine which files to hash when caching the result - async function run(command, message, cacheInfoFn) { + // 'cwd' is the working directory for the command + async function run(command, message, cacheInfoFn, cwd='.') { //cache key for the command -- encoded command name: - const cacheKey = JSON.stringify(command); + const cacheKey = JSON.stringify([cwd, ...command]); //executable for the command: - const exe = await findExe(command); + const exe = await findExe(command, cwd); //if no cache info function, remove any existing cache entry: if (!cacheInfoFn) { @@ -577,7 +639,8 @@ function init_maek() { await new Promise((resolve, reject) => { const proc = child_process.spawn(command[0], command.slice(1), { shell: false, - stdio: ['ignore', 'inherit', 'inherit'] + stdio: ['ignore', 'inherit', 'inherit'], + cwd: cwd }); proc.on('exit', (code, signal) => { if (code !== 0) { @@ -665,13 +728,13 @@ function init_maek() { //find an executable in the system path // (used by run to figure out what to hash) - async function findExe(command) { + async function findExe(command, cwd='.') { const osPath = require('path'); let PATH; //any command with a path separator is looked up directly: if (command[0].includes(osPath.sep)) { - PATH = ['']; + PATH = [cwd]; } else { if (maek.OS === 'windows') { PATH = process.env.PATH.split(';'); diff --git a/README.md b/README.md index 255b219..9c8ef96 100644 --- a/README.md +++ b/README.md @@ -96,10 +96,12 @@ $ ./platform-tools/adb devices # shows "unauthorized" and causes the "allow debugging" dialog to pop on the headset # if dialog accepted, says "device" next to the device (maybe that's the device name?) $ ./platform-tools/adb devices + # if you like to explore a bit, get a shell on your device: $ ./platform-tools/adb shell # interesting things include `top` (see what's running), `cat /proc/cpuinfo` (learn more about the cpu) #(ctrl-d to exit) + ``` ## Building @@ -109,6 +111,34 @@ $ ./platform-tools/adb shell ## OpenXR Notes +## Installing on Android + + +Basic install + run workflow: +``` +#from this folder, after building and packaging: + +#install the app: +$ ../android-sdk/platform-tools/adb install -r dist-android/game.apk + +#start the app: +$ ../android-sdk/platform-tools/adb shell am start --activity-clear-top -n "com.tchow.game/android.app.NativeActivity" +``` + +For checking things are okay: +``` +#print system log: (note -- can filter) +$ ../android-sdk/platform-tools/adb logcat +#version with some filtering: +$ ../android-sdk/platform-tools/adb logcat *:E OpenXR:I + +#see what's installed: +$ ../android-sdk/platform-tools/adb shell pm list packages + +#uninstall the app: +$ ../android-sdk/platform-tools/adb uninstall com.tchow.com +``` + ## EXTRA NOTES diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index c30607f..219ae5a 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -50,7 +50,7 @@ android:allowBackup="true" android:hasCode="false" android:icon="@mipmap/gp-icon" - android:label="@string/app_name" + android:label="gp23 openxr example" android:roundIcon="@mipmap/gp-icon"> diff --git a/android/build-apk.sh b/android/build-apk.sh new file mode 100644 index 0000000..6e2655a --- /dev/null +++ b/android/build-apk.sh @@ -0,0 +1,48 @@ +#!/usr/bin/sh + +#based on https://stackoverflow.com/questions/59504840/create-jni-ndk-apk-only-command-line-without-gradle-ant-or-cmake + +ANDROID_SDK=../../android-sdk +AAPT2=$ANDROID_SDK/build-tools/30.0.3/aapt2 +AAPT=$ANDROID_SDK/build-tools/30.0.3/aapt +ZIPALIGN=$ANDROID_SDK/build-tools/30.0.3/zipalign +APKSIGNER=$ANDROID_SDK/build-tools/30.0.3/apksigner + +mkdir -p ../objs/android/apk + +"$AAPT2" compile -o ../objs/android/apk -v res/mipmap-mdpi/gp-icon.png +"$AAPT2" compile -o ../objs/android/apk -v res/mipmap-hdpi/gp-icon.png +"$AAPT2" compile -o ../objs/android/apk -v res/mipmap-xhdpi/gp-icon.png +"$AAPT2" compile -o ../objs/android/apk -v res/mipmap-xxhdpi/gp-icon.png +"$AAPT2" compile -o ../objs/android/apk -v res/mipmap-xxxhdpi/gp-icon.png + +rm -f ../objs/android/unsigned.apk +rm -f ../objs/android/aligned.apk +rm -f ../dist-android/game.apk + +"$AAPT2" link \ + -o ../objs/android/unsigned.apk \ + -I $ANDROID_SDK/platforms/android-29/android.jar \ + ../objs/android/apk/mipmap-mdpi_gp-icon.png.flat \ + ../objs/android/apk/mipmap-hdpi_gp-icon.png.flat \ + ../objs/android/apk/mipmap-xhdpi_gp-icon.png.flat \ + ../objs/android/apk/mipmap-xxhdpi_gp-icon.png.flat \ + ../objs/android/apk/mipmap-xxxhdpi_gp-icon.png.flat \ + --manifest AndroidManifest.xml \ + -v + +cp ../../ovr-openxr-sdk/OpenXR/Libs/Android/arm64-v8a/Release/libopenxr_loader.so ../objs/android/apk/lib/arm64-v8a/ + +cd ../objs/android/apk +"../../../android/$AAPT" add ../unsigned.apk lib/arm64-v8a/libgame.so +"../../../android/$AAPT" add ../unsigned.apk lib/arm64-v8a/libopenxr_loader.so +cd ../../../android + +"$ZIPALIGN" -f -p 4 "../objs/android/unsigned.apk" "../objs/android/aligned.apk" + +#keytool -genkeypair -keystore "../objs/android/keystore.jks" -alias androidkey -validity 10000 -keyalg RSA -keysize 2048 -storepass 'android' + + +mkdir -p ../dist-android + +"$APKSIGNER" sign --ks ../objs/android/keystore.jks --ks-key-alias androidkey --out ../dist-android/game.apk ../objs/android/aligned.apk diff --git a/android/export-icons.sh b/android/export-icons.sh index fb0de88..482169f 100644 --- a/android/export-icons.sh +++ b/android/export-icons.sh @@ -2,10 +2,10 @@ INKSCAPE=inkscape BASENAME=gp-icon -mkdir -p mipmap-mdpi mipmap-hdpi mipmap-xhdpi mipmap-xxhdpi mipmap-xxxhdpi +mkdir -p res/mipmap-mdpi res/mipmap-hdpi res/mipmap-xhdpi res/mipmap-xxhdpi res/mipmap-xxxhdpi -"$INKSCAPE" "$BASENAME.svg" -o "mipmap-mdpi/$BASENAME.png" --export-type=png --export-area-page --export-width=48 --export-height=48 --export-background='#000000' --export-background-opacity=0.0 -"$INKSCAPE" "$BASENAME.svg" -o "mipmap-hdpi/$BASENAME.png" --export-type=png --export-area-page --export-width=72 --export-height=72 --export-background='#000000' --export-background-opacity=0.0 -"$INKSCAPE" "$BASENAME.svg" -o "mipmap-xhdpi/$BASENAME.png" --export-type=png --export-area-page --export-width=96 --export-height=96 --export-background='#000000' --export-background-opacity=0.0 -"$INKSCAPE" "$BASENAME.svg" -o "mipmap-xxhdpi/$BASENAME.png" --export-type=png --export-area-page --export-width=144 --export-height=144 --export-background='#000000' --export-background-opacity=0.0 -"$INKSCAPE" "$BASENAME.svg" -o "mipmap-xxxhdpi/$BASENAME.png" --export-type=png --export-area-page --export-width=192 --export-height=192 --export-background='#000000' --export-background-opacity=0.0 +"$INKSCAPE" "$BASENAME.svg" -o "res/mipmap-mdpi/$BASENAME.png" --export-type=png --export-area-page --export-width=48 --export-height=48 --export-background='#000000' --export-background-opacity=0.0 +"$INKSCAPE" "$BASENAME.svg" -o "res/mipmap-hdpi/$BASENAME.png" --export-type=png --export-area-page --export-width=72 --export-height=72 --export-background='#000000' --export-background-opacity=0.0 +"$INKSCAPE" "$BASENAME.svg" -o "res/mipmap-xhdpi/$BASENAME.png" --export-type=png --export-area-page --export-width=96 --export-height=96 --export-background='#000000' --export-background-opacity=0.0 +"$INKSCAPE" "$BASENAME.svg" -o "res/mipmap-xxhdpi/$BASENAME.png" --export-type=png --export-area-page --export-width=144 --export-height=144 --export-background='#000000' --export-background-opacity=0.0 +"$INKSCAPE" "$BASENAME.svg" -o "res/mipmap-xxxhdpi/$BASENAME.png" --export-type=png --export-area-page --export-width=192 --export-height=192 --export-background='#000000' --export-background-opacity=0.0 diff --git a/android/mipmap-hdpi/gp-icon.png b/android/res/mipmap-hdpi/gp-icon.png similarity index 100% rename from android/mipmap-hdpi/gp-icon.png rename to android/res/mipmap-hdpi/gp-icon.png diff --git a/android/mipmap-mdpi/gp-icon.png b/android/res/mipmap-mdpi/gp-icon.png similarity index 100% rename from android/mipmap-mdpi/gp-icon.png rename to android/res/mipmap-mdpi/gp-icon.png diff --git a/android/mipmap-xhdpi/gp-icon.png b/android/res/mipmap-xhdpi/gp-icon.png similarity index 100% rename from android/mipmap-xhdpi/gp-icon.png rename to android/res/mipmap-xhdpi/gp-icon.png diff --git a/android/mipmap-xxhdpi/gp-icon.png b/android/res/mipmap-xxhdpi/gp-icon.png similarity index 100% rename from android/mipmap-xxhdpi/gp-icon.png rename to android/res/mipmap-xxhdpi/gp-icon.png diff --git a/android/mipmap-xxxhdpi/gp-icon.png b/android/res/mipmap-xxxhdpi/gp-icon.png similarity index 100% rename from android/mipmap-xxxhdpi/gp-icon.png rename to android/res/mipmap-xxxhdpi/gp-icon.png diff --git a/dist-android/AndroidManifest.xml b/dist-android/AndroidManifest.xml deleted file mode 100644 index 4a8a57b..0000000 --- a/dist-android/AndroidManifest.xml +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -