summaryrefslogtreecommitdiff
path: root/src/contrib/SDL-3.2.20/build-scripts/create-android-project.py
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-08-30 16:53:58 -0700
committer3gg <3gg@shellblade.net>2025-08-30 16:53:58 -0700
commit6aaedb813fa11ba0679c3051bc2eb28646b9506c (patch)
tree34acbfc9840e02cb4753e6306ea7ce978bf8b58e /src/contrib/SDL-3.2.20/build-scripts/create-android-project.py
parent8f228ade99dd3d4c8da9b78ade1815c9adf85c8f (diff)
Update to SDL3
Diffstat (limited to 'src/contrib/SDL-3.2.20/build-scripts/create-android-project.py')
-rwxr-xr-xsrc/contrib/SDL-3.2.20/build-scripts/create-android-project.py241
1 files changed, 241 insertions, 0 deletions
diff --git a/src/contrib/SDL-3.2.20/build-scripts/create-android-project.py b/src/contrib/SDL-3.2.20/build-scripts/create-android-project.py
new file mode 100755
index 0000000..0b8994f
--- /dev/null
+++ b/src/contrib/SDL-3.2.20/build-scripts/create-android-project.py
@@ -0,0 +1,241 @@
1#!/usr/bin/env python3
2import os
3from argparse import ArgumentParser
4from pathlib import Path
5import re
6import shutil
7import sys
8import textwrap
9
10
11SDL_ROOT = Path(__file__).resolve().parents[1]
12
13def 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
26def 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
38def 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
65def 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
87def 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
107def 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
118def 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
240if __name__ == "__main__":
241 raise SystemExit(main())