|
@@ -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())
|