android: create android project in create-android-project.py python script

This script supersedes androidbuild.sh, and also supports using a SDL3 prefab archive
This commit is contained in:
Anonymous Maarten 2024-07-05 00:09:40 +02:00 committed by Anonymous Maarten
parent 2d05dcc1f7
commit 50ae47af5e
8 changed files with 303 additions and 125 deletions

View File

@ -40,7 +40,11 @@ jobs:
- name: Create Gradle project - name: Create Gradle project
if: ${{ matrix.platform.gradle }} if: ${{ matrix.platform.gradle }}
run: | run: |
build-scripts/androidbuild.sh org.libsdl.testspriteminimal test/testspriteminimal.c test/icon.h python build-scripts/create-android-project.py \
--output "build" \
--variant copy \
org.libsdl.testspriteminimal \
test/testspriteminimal.c test/icon.h
echo "" echo ""
echo "Project contents:" echo "Project contents:"
echo "" echo ""

View File

@ -461,7 +461,6 @@ jobs:
sparse-checkout: 'build-scripts/build-release.py' sparse-checkout: 'build-scripts/build-release.py'
- name: 'Setup Android NDK' - name: 'Setup Android NDK'
uses: nttld/setup-ndk@v1 uses: nttld/setup-ndk@v1
id: setup_ndk
with: with:
local-cache: true local-cache: true
ndk-version: r21e ndk-version: r21e
@ -500,3 +499,71 @@ jobs:
with: with:
name: android name: android
path: '${{ github.workspace }}/dist' path: '${{ github.workspace }}/dist'
android-verify:
needs: [android, src]
runs-on: ubuntu-latest
steps:
- name: 'Set up Python'
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: 'Setup Android NDK'
uses: nttld/setup-ndk@v1
with:
local-cache: true
ndk-version: r21e
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: 'Download source archives'
uses: actions/download-artifact@v4
with:
name: sources
path: '${{ github.workspace }}'
- name: 'Download Android .aar archive'
uses: actions/download-artifact@v4
with:
name: android
path: '${{ github.workspace }}'
- name: 'Untar ${{ needs.src.outputs.src-tar-gz }}'
id: src
run: |
mkdir -p /tmp/tardir
tar -C /tmp/tardir -v -x -f "${{ github.workspace }}/${{ needs.src.outputs.src-tar-gz }}"
echo "path=/tmp/tardir/${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}" >>$GITHUB_OUTPUT
- name: 'Create gradle project'
id: create-gradle-project
run: |
python ${{ steps.src.outputs.path }}/build-scripts/create-android-project.py \
org.libsdl.testspriteminimal \
${{ steps.src.outputs.path }}/test/testspriteminimal.c \
${{ steps.src.outputs.path }}/test/icon.h \
--variant aar \
--output "/tmp/projects"
echo "path=/tmp/projects/org.libsdl.testspriteminimal" >>$GITHUB_OUTPUT
echo ""
echo "Project contents:"
echo ""
find "/tmp/projects/org.libsdl.testspriteminimal"
- name: 'Remove SDL sources to make sure they are not used'
run: |
rm -rf "${{ steps.src.outputs.path }}"
- name: 'Copy SDL3 aar into Gradle project'
run: |
cp "${{ github.workspace }}/${{ needs.android.outputs.android-aar }}" "${{ steps.create-gradle-project.outputs.path }}/app/libs"
echo ""
echo "Project contents:"
echo ""
find "${{ steps.create-gradle-project.outputs.path }}"
- name: 'Build app (Gradle & ndk-build)'
run: |
cd "${{ steps.create-gradle-project.outputs.path }}"
./gradlew -i assembleRelease -PBUILD_WITH_CMAKE=1
- name: 'Build app (Gradle & CMake)'
run: |
cd "${{ steps.create-gradle-project.outputs.path }}"
./gradlew -i assembleRelease

View File

@ -2,11 +2,6 @@ cmake_minimum_required(VERSION 3.6)
project(GAME) project(GAME)
# armeabi-v7a requires cpufeatures library
# include(AndroidNdkModules)
# android_ndk_import_module_cpufeatures()
# SDL sources are in a subfolder named "SDL" # SDL sources are in a subfolder named "SDL"
add_subdirectory(SDL) add_subdirectory(SDL)

View File

@ -4,15 +4,16 @@ include $(CLEAR_VARS)
LOCAL_MODULE := main LOCAL_MODULE := main
SDL_PATH := ../SDL
LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include
# Add your application source files here... # Add your application source files here...
LOCAL_SRC_FILES := YourSourceHere.c LOCAL_SRC_FILES := \
YourSourceHere.c
SDL_PATH := ../SDL # SDL
LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include # SDL
LOCAL_SHARED_LIBRARIES := SDL3 LOCAL_SHARED_LIBRARIES := SDL3
LOCAL_LDLIBS := -lGLESv1_CM -lGLESv2 -lOpenSLES -llog -landroid LOCAL_LDLIBS := -lGLESv1_CM -lGLESv2 -lOpenSLES -llog -landroid # SDL
include $(BUILD_SHARED_LIBRARY) include $(BUILD_SHARED_LIBRARY)

View File

@ -1,106 +0,0 @@
#!/bin/bash
SOURCES=()
MKSOURCES=""
CURDIR=`pwd -P`
# Fetch sources
if [[ $# -ge 2 ]]; then
for src in ${@:2}
do
SOURCES+=($src)
MKSOURCES="$MKSOURCES $(basename $src)"
done
else
if [ -n "$1" ]; then
while read src
do
SOURCES+=($src)
MKSOURCES="$MKSOURCES $(basename $src)"
done
fi
fi
if [ -z "$1" ] || [ -z "$SOURCES" ]; then
echo "Usage: androidbuild.sh com.yourcompany.yourapp < sources.list"
echo "Usage: androidbuild.sh com.yourcompany.yourapp source1.c source2.c ...sourceN.c"
echo "To copy SDL source instead of symlinking: COPYSOURCE=1 androidbuild.sh ... "
exit 1
fi
SDLPATH="$( cd "$(dirname "$0")/.." ; pwd -P )"
if [ -z "$ANDROID_HOME" ];then
echo "Please set the ANDROID_HOME directory to the path of the Android SDK"
exit 1
fi
if [ ! -d "$ANDROID_HOME/ndk-bundle" -a -z "$ANDROID_NDK_HOME" ]; then
echo "Please set the ANDROID_NDK_HOME directory to the path of the Android NDK"
exit 1
fi
APP="$1"
APPARR=(${APP//./ })
BUILDPATH="$SDLPATH/build/$APP"
# Start Building
rm -rf $BUILDPATH
mkdir -p $BUILDPATH
cp -r $SDLPATH/android-project/* $BUILDPATH
# Copy SDL sources
mkdir -p $BUILDPATH/app/jni/SDL
if [ -z "$COPYSOURCE" ]; then
ln -s $SDLPATH/src $BUILDPATH/app/jni/SDL
ln -s $SDLPATH/include $BUILDPATH/app/jni/SDL
else
cp -r $SDLPATH/src $BUILDPATH/app/jni/SDL
cp -r $SDLPATH/include $BUILDPATH/app/jni/SDL
fi
cp -r $SDLPATH/LICENSE.txt $BUILDPATH/app/jni/SDL
cp -r $SDLPATH/README.md $BUILDPATH/app/jni/SDL
cp -r $SDLPATH/Android.mk $BUILDPATH/app/jni/SDL
cp -r $SDLPATH/CMakeLists.txt $BUILDPATH/app/jni/SDL
cp -r $SDLPATH/cmake $BUILDPATH/app/jni/SDL
sed -i -e "s|YourSourceHere.c|$MKSOURCES|g" $BUILDPATH/app/jni/src/Android.mk
sed -i -e "s|YourSourceHere.c|$MKSOURCES|g" $BUILDPATH/app/jni/src/CMakeLists.txt
sed -i -e "s|org\.libsdl\.app|$APP|g" $BUILDPATH/app/build.gradle
sed -i -e "s|org\.libsdl\.app|$APP|g" $BUILDPATH/app/src/main/AndroidManifest.xml
# Copy user sources
for src in "${SOURCES[@]}"
do
cp $src $BUILDPATH/app/jni/src
done
# Create an inherited Activity
cd $BUILDPATH/app/src/main/java
for folder in "${APPARR[@]}"
do
mkdir -p $folder
cd $folder
done
# Uppercase the first char in the activity class name because it's Java
ACTIVITY="$(echo $folder | awk '{$1=toupper(substr($1,0,1))substr($1,2)}1')Activity"
sed -i -e "s|\"SDLActivity\"|\"$ACTIVITY\"|g" $BUILDPATH/app/src/main/AndroidManifest.xml
# Fill in a default Activity
cat >"$ACTIVITY.java" <<__EOF__
package $APP;
import org.libsdl.app.SDLActivity;
public class $ACTIVITY extends SDLActivity
{
}
__EOF__
# Update project and build
echo "To build and install to a device for testing, run the following:"
echo "cd $BUILDPATH"
echo "./gradlew installDebug"

View File

@ -694,7 +694,7 @@ class Releaser:
zip_object.write(test_library, arcname=f"prefab/modules/{self.project}_test/libs/android.{android_abi}/lib{self.project}_test.a") zip_object.write(test_library, arcname=f"prefab/modules/{self.project}_test/libs/android.{android_abi}/lib{self.project}_test.a")
zip_object.writestr(f"prefab/modules/{self.project}_test/libs/android.{android_abi}/abi.json", self.get_prefab_abi_json_text(abi=android_abi, cpp=False, shared=False)) zip_object.writestr(f"prefab/modules/{self.project}_test/libs/android.{android_abi}/abi.json", self.get_prefab_abi_json_text(abi=android_abi, cpp=False, shared=False))
self.artifacts[f"android-prefab-aar"] = aar_path self.artifacts[f"android-aar"] = aar_path
@classmethod @classmethod
def extract_sdl_version(cls, root: Path, project: str): def extract_sdl_version(cls, root: Path, project: str):

View File

@ -0,0 +1,217 @@
#!/usr/bin/env python
import os
from argparse import ArgumentParser
from pathlib import Path
import re
import shutil
import sys
import textwrap
SDL_ROOT = Path(__file__).resolve().parents[1]
def extract_sdl_version():
"""
Extract SDL version from SDL3/SDL_version.h
"""
with open(SDL_ROOT / "include/SDL3/SDL_version.h") as f:
data = f.read()
major = int(next(re.finditer(r"#define\s+SDL_MAJOR_VERSION\s+([0-9]+)", data)).group(1))
minor = int(next(re.finditer(r"#define\s+SDL_MINOR_VERSION\s+([0-9]+)", data)).group(1))
micro = int(next(re.finditer(r"#define\s+SDL_MICRO_VERSION\s+([0-9]+)", data)).group(1))
return f"{major}.{minor}.{micro}"
def replace_in_file(path, regex_what, replace_with):
with open(path, "r") as f:
data = f.read()
new_data, count = re.subn(regex_what, replace_with, data)
assert count > 0, f"\"{regex_what}\" did not match anything in \"{path}\""
with open(path, "w") as f:
f.write(new_data)
def android_mk_use_prefab(path):
"""
Replace relative SDL inclusion with dependency on prefab package
"""
with open(path) as f:
data = "".join(line for line in f.readlines() if "# SDL" not in line)
data, _ = re.subn("[\n]{3,}", "\n\n", data)
newdata = data + textwrap.dedent("""
# https://google.github.io/prefab/build-systems.html
# Add the prefab modules to the import path.
$(call import-add-path,/out)
# Import SDL3 so we can depend on it.
$(call import-module,prefab/SDL3)
""")
with open(path, "w") as f:
f.write(newdata)
def cmake_mk_no_sdl(path):
"""
Don't add the source directories of SDL/SDL_image/SDL_mixer/...
"""
with open(path) as f:
lines = f.readlines()
newlines = []
for line in lines:
if "add_subdirectory(SDL" in line:
while newlines[-1].startswith("#"):
newlines = newlines[:-1]
continue
newlines.append(line)
newdata, _ = re.subn("[\n]{3,}", "\n\n", "".join(newlines))
with open(path, "w") as f:
f.write(newdata)
def gradle_add_prefab_and_aar(path, aar):
with open(path) as f:
data = f.read()
data, count = re.subn("android {", textwrap.dedent("""
android {
buildFeatures {
prefab true
}"""), data)
assert count == 1
data, count = re.subn("dependencies {", textwrap.dedent(f"""
dependencies {{
implementation files('libs/{aar}')"""), data)
assert count == 1
with open(path, "w") as f:
f.write(data)
def main():
description = "Create a simple Android gradle project from input sources."
epilog = "You need to manually copy a prebuilt SDL3 Android archive into the project tree when using the aar variant."
parser = ArgumentParser(description=description, allow_abbrev=False)
parser.add_argument("package_name", metavar="PACKAGENAME", help="Android package name e.g. com.yourcompany.yourapp")
parser.add_argument("sources", metavar="SOURCE", nargs="*", help="Source code of your application. The files are copied to the output directory.")
parser.add_argument("--variant", choices=["copy", "symlink", "aar"], default="copy", help="Choose variant of SDL project (copy: copy SDL sources, symlink: symlink SDL sources, aar: use Android aar archive)")
parser.add_argument("--output", "-o", default=SDL_ROOT / "build", type=Path, help="Location where to store the Android project")
parser.add_argument("--version", default=None, help="SDL3 version to use as aar dependency (only used for aar variant)")
args = parser.parse_args()
if not args.sources:
print("Reading source file paths from stdin (press CTRL+D to stop)")
args.sources = [path for path in sys.stdin.read().strip().split() if path]
if not args.sources:
parser.error("No sources passed")
if not os.getenv("ANDROID_HOME"):
print("WARNING: ANDROID_HOME environment variable not set", file=sys.stderr)
if not os.getenv("ANDROID_NDK_HOME"):
print("WARNING: ANDROID_NDK_HOME environment variable not set", file=sys.stderr)
args.sources = [Path(src) for src in args.sources]
build_path = args.output / args.package_name
# Remove the destination folder
shutil.rmtree(build_path, ignore_errors=True)
# Copy the Android project
shutil.copytree(SDL_ROOT / "android-project", build_path)
# Add the source files to the ndk-build and cmake projects
replace_in_file(build_path / "app/jni/src/Android.mk", r"YourSourceHere\.c", " \\\n ".join(src.name for src in args.sources))
replace_in_file(build_path / "app/jni/src/CMakeLists.txt", r"YourSourceHere\.c", "\n ".join(src.name for src in args.sources))
# Remove placeholder source "YourSourceHere.c"
(build_path / "app/jni/src/YourSourceHere.c").unlink()
# Copy sources to output folder
for src in args.sources:
if not src.is_file():
parser.error(f"\"{src}\" is not a file")
shutil.copyfile(src, build_path / "app/jni/src" / src.name)
sdl_project_files = (
SDL_ROOT / "src",
SDL_ROOT / "include",
SDL_ROOT / "LICENSE.txt",
SDL_ROOT / "README.md",
SDL_ROOT / "Android.mk",
SDL_ROOT / "CMakeLists.txt",
SDL_ROOT / "cmake",
)
if args.variant == "copy":
(build_path / "app/jni/SDL").mkdir(exist_ok=True, parents=True)
for sdl_project_file in sdl_project_files:
# Copy SDL project files and directories
if sdl_project_file.is_dir():
shutil.copytree(sdl_project_file, build_path / "app/jni/SDL" / sdl_project_file.name)
elif sdl_project_file.is_file():
shutil.copyfile(sdl_project_file, build_path / "app/jni/SDL" / sdl_project_file.name)
elif args.variant == "symlink":
(build_path / "app/jni/SDL").mkdir(exist_ok=True, parents=True)
# Create symbolic links for all SDL project files
for sdl_project_file in sdl_project_files:
os.symlink(sdl_project_file, build_path / "app/jni/SDL" / sdl_project_file.name)
elif args.variant == "aar":
if not args.version:
args.version = extract_sdl_version()
major = args.version.split(".")[0]
aar = f"SDL{ major }-{ args.version }.aar"
# Remove all SDL java classes
shutil.rmtree(build_path / "app/src/main/java")
# Use prefab to generate include-able files
gradle_add_prefab_and_aar(build_path / "app/build.gradle", aar=aar)
# Make sure to use the prefab-generated files and not SDL sources
android_mk_use_prefab(build_path / "app/jni/src/Android.mk")
cmake_mk_no_sdl(build_path / "app/jni/CMakeLists.txt")
aar_libs_folder = build_path / "app/libs"
aar_libs_folder.mkdir(parents=True)
with (aar_libs_folder / "copy-sdl-aars-here.txt").open("w") as f:
f.write(f"Copy {aar} to this folder.\n")
print(f"WARNING: copy { aar } to { aar_libs_folder }", file=sys.stderr)
# Create entry activity, subclassing SDLActivity
activity = args.package_name[args.package_name.rfind(".") + 1:].capitalize() + "Activity"
activity_path = build_path / "app/src/main/java" / args.package_name.replace(".", "/") / f"{activity}.java"
activity_path.parent.mkdir(parents=True)
with activity_path.open("w") as f:
f.write(textwrap.dedent(f"""
package {args.package_name};
import org.libsdl.app.SDLActivity;
public class {activity} extends SDLActivity
{{
}}
"""))
# Add the just-generated activity to the Android manifest
replace_in_file(build_path / "app/src/main/AndroidManifest.xml", "SDLActivity", activity)
# Update project and build
print("To build and install to a device for testing, run the following:")
print(f"cd {build_path}")
print("./gradlew installDebug")
if __name__ == "__main__":
raise SystemExit(main())

View File

@ -40,19 +40,19 @@ src/core/android/SDL_android.c
Building an app Building an app
================================================================================ ================================================================================
For simple projects you can use the script located at build-scripts/androidbuild.sh For simple projects you can use the script located at build-scripts/create-android-project.py
There's two ways of using it: There's two ways of using it:
androidbuild.sh com.yourcompany.yourapp < sources.list ./create-android-project.py com.yourcompany.yourapp < sources.list
androidbuild.sh com.yourcompany.yourapp source1.c source2.c ...sourceN.c ./create-android-project.py com.yourcompany.yourapp source1.c source2.c ...sourceN.c
sources.list should be a text file with a source file name in each line sources.list should be a text file with a source file name in each line
Filenames should be specified relative to the current directory, for example if Filenames should be specified relative to the current directory, for example if
you are in the build-scripts directory and want to create the testgles.c test, you'll you are in the build-scripts directory and want to create the testgles.c test, you'll
run: run:
./androidbuild.sh org.libsdl.testgles ../test/testgles.c ./create-android-project.py org.libsdl.testgles ../test/testgles.c
One limitation of this script is that all sources provided will be aggregated into One limitation of this script is that all sources provided will be aggregated into
a single directory, thus all your source files should have a unique name. a single directory, thus all your source files should have a unique name.
@ -61,7 +61,7 @@ Once the project is complete the script will tell you where the debug APK is loc
If you want to create a signed release APK, you can use the project created by this If you want to create a signed release APK, you can use the project created by this
utility to generate it. utility to generate it.
Finally, a word of caution: re running androidbuild.sh wipes any changes you may have Finally, a word of caution: re running create-android-project.py wipes any changes you may have
done in the build directory for the app! done in the build directory for the app!