diff options
Diffstat (limited to 'contrib/SDL-3.2.8/build-scripts/create-android-project.py')
| -rwxr-xr-x | contrib/SDL-3.2.8/build-scripts/create-android-project.py | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/build-scripts/create-android-project.py b/contrib/SDL-3.2.8/build-scripts/create-android-project.py new file mode 100755 index 0000000..0b8994f --- /dev/null +++ b/contrib/SDL-3.2.8/build-scripts/create-android-project.py | |||
| @@ -0,0 +1,241 @@ | |||
| 1 | #!/usr/bin/env python3 | ||
| 2 | import os | ||
| 3 | from argparse import ArgumentParser | ||
| 4 | from pathlib import Path | ||
| 5 | import re | ||
| 6 | import shutil | ||
| 7 | import sys | ||
| 8 | import textwrap | ||
| 9 | |||
| 10 | |||
| 11 | SDL_ROOT = Path(__file__).resolve().parents[1] | ||
| 12 | |||
| 13 | def extract_sdl_version() -> str: | ||
| 14 | """ | ||
| 15 | Extract SDL version from SDL3/SDL_version.h | ||
| 16 | """ | ||
| 17 | |||
| 18 | with open(SDL_ROOT / "include/SDL3/SDL_version.h") as f: | ||
| 19 | data = f.read() | ||
| 20 | |||
| 21 | major = int(next(re.finditer(r"#define\s+SDL_MAJOR_VERSION\s+([0-9]+)", data)).group(1)) | ||
| 22 | minor = int(next(re.finditer(r"#define\s+SDL_MINOR_VERSION\s+([0-9]+)", data)).group(1)) | ||
| 23 | micro = int(next(re.finditer(r"#define\s+SDL_MICRO_VERSION\s+([0-9]+)", data)).group(1)) | ||
| 24 | return f"{major}.{minor}.{micro}" | ||
| 25 | |||
| 26 | def replace_in_file(path: Path, regex_what: str, replace_with: str) -> None: | ||
| 27 | with path.open("r") as f: | ||
| 28 | data = f.read() | ||
| 29 | |||
| 30 | new_data, count = re.subn(regex_what, replace_with, data) | ||
| 31 | |||
| 32 | assert count > 0, f"\"{regex_what}\" did not match anything in \"{path}\"" | ||
| 33 | |||
| 34 | with open(path, "w") as f: | ||
| 35 | f.write(new_data) | ||
| 36 | |||
| 37 | |||
| 38 | def android_mk_use_prefab(path: Path) -> None: | ||
| 39 | """ | ||
| 40 | Replace relative SDL inclusion with dependency on prefab package | ||
| 41 | """ | ||
| 42 | |||
| 43 | with path.open() as f: | ||
| 44 | data = "".join(line for line in f.readlines() if "# SDL" not in line) | ||
| 45 | |||
| 46 | data, _ = re.subn("[\n]{3,}", "\n\n", data) | ||
| 47 | |||
| 48 | data, count = re.subn(r"(LOCAL_SHARED_LIBRARIES\s*:=\s*SDL3)", "LOCAL_SHARED_LIBRARIES := SDL3 SDL3-Headers", data) | ||
| 49 | assert count == 1, f"Must have injected SDL3-Headers in {path} exactly once" | ||
| 50 | |||
| 51 | newdata = data + textwrap.dedent(""" | ||
| 52 | # https://google.github.io/prefab/build-systems.html | ||
| 53 | |||
| 54 | # Add the prefab modules to the import path. | ||
| 55 | $(call import-add-path,/out) | ||
| 56 | |||
| 57 | # Import SDL3 so we can depend on it. | ||
| 58 | $(call import-module,prefab/SDL3) | ||
| 59 | """) | ||
| 60 | |||
| 61 | with path.open("w") as f: | ||
| 62 | f.write(newdata) | ||
| 63 | |||
| 64 | |||
| 65 | def cmake_mk_no_sdl(path: Path) -> None: | ||
| 66 | """ | ||
| 67 | Don't add the source directories of SDL/SDL_image/SDL_mixer/... | ||
| 68 | """ | ||
| 69 | |||
| 70 | with path.open() as f: | ||
| 71 | lines = f.readlines() | ||
| 72 | |||
| 73 | newlines: list[str] = [] | ||
| 74 | for line in lines: | ||
| 75 | if "add_subdirectory(SDL" in line: | ||
| 76 | while newlines[-1].startswith("#"): | ||
| 77 | newlines = newlines[:-1] | ||
| 78 | continue | ||
| 79 | newlines.append(line) | ||
| 80 | |||
| 81 | newdata, _ = re.subn("[\n]{3,}", "\n\n", "".join(newlines)) | ||
| 82 | |||
| 83 | with path.open("w") as f: | ||
| 84 | f.write(newdata) | ||
| 85 | |||
| 86 | |||
| 87 | def gradle_add_prefab_and_aar(path: Path, aar: str) -> None: | ||
| 88 | with path.open() as f: | ||
| 89 | data = f.read() | ||
| 90 | |||
| 91 | data, count = re.subn("android {", textwrap.dedent(""" | ||
| 92 | android { | ||
| 93 | buildFeatures { | ||
| 94 | prefab true | ||
| 95 | }"""), data) | ||
| 96 | assert count == 1 | ||
| 97 | |||
| 98 | data, count = re.subn("dependencies {", textwrap.dedent(f""" | ||
| 99 | dependencies {{ | ||
| 100 | implementation files('libs/{aar}')"""), data) | ||
| 101 | assert count == 1 | ||
| 102 | |||
| 103 | with path.open("w") as f: | ||
| 104 | f.write(data) | ||
| 105 | |||
| 106 | |||
| 107 | def gradle_add_package_name(path: Path, package_name: str) -> None: | ||
| 108 | with path.open() as f: | ||
| 109 | data = f.read() | ||
| 110 | |||
| 111 | data, count = re.subn("org.libsdl.app", package_name, data) | ||
| 112 | assert count >= 1 | ||
| 113 | |||
| 114 | with path.open("w") as f: | ||
| 115 | f.write(data) | ||
| 116 | |||
| 117 | |||
| 118 | def main() -> int: | ||
| 119 | description = "Create a simple Android gradle project from input sources." | ||
| 120 | epilog = textwrap.dedent("""\ | ||
| 121 | You need to manually copy a prebuilt SDL3 Android archive into the project tree when using the aar variant. | ||
| 122 | |||
| 123 | Any changes you have done to the sources in the Android project will be lost | ||
| 124 | """) | ||
| 125 | parser = ArgumentParser(description=description, epilog=epilog, allow_abbrev=False) | ||
| 126 | parser.add_argument("package_name", metavar="PACKAGENAME", help="Android package name (e.g. com.yourcompany.yourapp)") | ||
| 127 | parser.add_argument("sources", metavar="SOURCE", nargs="*", help="Source code of your application. The files are copied to the output directory.") | ||
| 128 | 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)") | ||
| 129 | parser.add_argument("--output", "-o", default=SDL_ROOT / "build", type=Path, help="Location where to store the Android project") | ||
| 130 | parser.add_argument("--version", default=None, help="SDL3 version to use as aar dependency (only used for aar variant)") | ||
| 131 | |||
| 132 | args = parser.parse_args() | ||
| 133 | if not args.sources: | ||
| 134 | print("Reading source file paths from stdin (press CTRL+D to stop)") | ||
| 135 | args.sources = [path for path in sys.stdin.read().strip().split() if path] | ||
| 136 | if not args.sources: | ||
| 137 | parser.error("No sources passed") | ||
| 138 | |||
| 139 | if not os.getenv("ANDROID_HOME"): | ||
| 140 | print("WARNING: ANDROID_HOME environment variable not set", file=sys.stderr) | ||
| 141 | if not os.getenv("ANDROID_NDK_HOME"): | ||
| 142 | print("WARNING: ANDROID_NDK_HOME environment variable not set", file=sys.stderr) | ||
| 143 | |||
| 144 | args.sources = [Path(src) for src in args.sources] | ||
| 145 | |||
| 146 | build_path = args.output / args.package_name | ||
| 147 | |||
| 148 | # Remove the destination folder | ||
| 149 | shutil.rmtree(build_path, ignore_errors=True) | ||
| 150 | |||
| 151 | # Copy the Android project | ||
| 152 | shutil.copytree(SDL_ROOT / "android-project", build_path) | ||
| 153 | |||
| 154 | # Add the source files to the ndk-build and cmake projects | ||
| 155 | replace_in_file(build_path / "app/jni/src/Android.mk", r"YourSourceHere\.c", " \\\n ".join(src.name for src in args.sources)) | ||
| 156 | replace_in_file(build_path / "app/jni/src/CMakeLists.txt", r"YourSourceHere\.c", "\n ".join(src.name for src in args.sources)) | ||
| 157 | |||
| 158 | # Remove placeholder source "YourSourceHere.c" | ||
| 159 | (build_path / "app/jni/src/YourSourceHere.c").unlink() | ||
| 160 | |||
| 161 | # Copy sources to output folder | ||
| 162 | for src in args.sources: | ||
| 163 | if not src.is_file(): | ||
| 164 | parser.error(f"\"{src}\" is not a file") | ||
| 165 | shutil.copyfile(src, build_path / "app/jni/src" / src.name) | ||
| 166 | |||
| 167 | sdl_project_files = ( | ||
| 168 | SDL_ROOT / "src", | ||
| 169 | SDL_ROOT / "include", | ||
| 170 | SDL_ROOT / "LICENSE.txt", | ||
| 171 | SDL_ROOT / "README.md", | ||
| 172 | SDL_ROOT / "Android.mk", | ||
| 173 | SDL_ROOT / "CMakeLists.txt", | ||
| 174 | SDL_ROOT / "cmake", | ||
| 175 | ) | ||
| 176 | if args.variant == "copy": | ||
| 177 | (build_path / "app/jni/SDL").mkdir(exist_ok=True, parents=True) | ||
| 178 | for sdl_project_file in sdl_project_files: | ||
| 179 | # Copy SDL project files and directories | ||
| 180 | if sdl_project_file.is_dir(): | ||
| 181 | shutil.copytree(sdl_project_file, build_path / "app/jni/SDL" / sdl_project_file.name) | ||
| 182 | elif sdl_project_file.is_file(): | ||
| 183 | shutil.copyfile(sdl_project_file, build_path / "app/jni/SDL" / sdl_project_file.name) | ||
| 184 | elif args.variant == "symlink": | ||
| 185 | (build_path / "app/jni/SDL").mkdir(exist_ok=True, parents=True) | ||
| 186 | # Create symbolic links for all SDL project files | ||
| 187 | for sdl_project_file in sdl_project_files: | ||
| 188 | os.symlink(sdl_project_file, build_path / "app/jni/SDL" / sdl_project_file.name) | ||
| 189 | elif args.variant == "aar": | ||
| 190 | if not args.version: | ||
| 191 | args.version = extract_sdl_version() | ||
| 192 | |||
| 193 | major = args.version.split(".")[0] | ||
| 194 | aar = f"SDL{ major }-{ args.version }.aar" | ||
| 195 | |||
| 196 | # Remove all SDL java classes | ||
| 197 | shutil.rmtree(build_path / "app/src/main/java") | ||
| 198 | |||
| 199 | # Use prefab to generate include-able files | ||
| 200 | gradle_add_prefab_and_aar(build_path / "app/build.gradle", aar=aar) | ||
| 201 | |||
| 202 | # Make sure to use the prefab-generated files and not SDL sources | ||
| 203 | android_mk_use_prefab(build_path / "app/jni/src/Android.mk") | ||
| 204 | cmake_mk_no_sdl(build_path / "app/jni/CMakeLists.txt") | ||
| 205 | |||
| 206 | aar_libs_folder = build_path / "app/libs" | ||
| 207 | aar_libs_folder.mkdir(parents=True) | ||
| 208 | with (aar_libs_folder / "copy-sdl-aars-here.txt").open("w") as f: | ||
| 209 | f.write(f"Copy {aar} to this folder.\n") | ||
| 210 | |||
| 211 | print(f"WARNING: copy { aar } to { aar_libs_folder }", file=sys.stderr) | ||
| 212 | |||
| 213 | # Add the package name to build.gradle | ||
| 214 | gradle_add_package_name(build_path / "app/build.gradle", args.package_name) | ||
| 215 | |||
| 216 | # Create entry activity, subclassing SDLActivity | ||
| 217 | activity = args.package_name[args.package_name.rfind(".") + 1:].capitalize() + "Activity" | ||
| 218 | activity_path = build_path / "app/src/main/java" / args.package_name.replace(".", "/") / f"{activity}.java" | ||
| 219 | activity_path.parent.mkdir(parents=True) | ||
| 220 | with activity_path.open("w") as f: | ||
| 221 | f.write(textwrap.dedent(f""" | ||
| 222 | package {args.package_name}; | ||
| 223 | |||
| 224 | import org.libsdl.app.SDLActivity; | ||
| 225 | |||
| 226 | public class {activity} extends SDLActivity | ||
| 227 | {{ | ||
| 228 | }} | ||
| 229 | """)) | ||
| 230 | |||
| 231 | # Add the just-generated activity to the Android manifest | ||
| 232 | replace_in_file(build_path / "app/src/main/AndroidManifest.xml", 'name="SDLActivity"', f'name="{activity}"') | ||
| 233 | |||
| 234 | # Update project and build | ||
| 235 | print("To build and install to a device for testing, run the following:") | ||
| 236 | print(f"cd {build_path}") | ||
| 237 | print("./gradlew installDebug") | ||
| 238 | return 0 | ||
| 239 | |||
| 240 | if __name__ == "__main__": | ||
| 241 | raise SystemExit(main()) | ||
