diff options
author | 3gg <3gg@shellblade.net> | 2025-08-30 16:53:58 -0700 |
---|---|---|
committer | 3gg <3gg@shellblade.net> | 2025-08-30 16:53:58 -0700 |
commit | 6aaedb813fa11ba0679c3051bc2eb28646b9506c (patch) | |
tree | 34acbfc9840e02cb4753e6306ea7ce978bf8b58e /src/contrib/SDL-3.2.20/build-scripts/create-android-project.py | |
parent | 8f228ade99dd3d4c8da9b78ade1815c9adf85c8f (diff) |
Update to SDL3
Diffstat (limited to 'src/contrib/SDL-3.2.20/build-scripts/create-android-project.py')
-rwxr-xr-x | src/contrib/SDL-3.2.20/build-scripts/create-android-project.py | 241 |
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 | ||
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()) | ||