mirror of
				https://github.com/libsdl-org/SDL.git
				synced 2025-10-26 12:27:44 +00:00 
			
		
		
		
	Build Android prefab in releaser.py script
[ci skip]
This commit is contained in:
		 Anonymous Maarten
					Anonymous Maarten
				
			
				
					committed by
					
						 Anonymous Maarten
						Anonymous Maarten
					
				
			
			
				
	
			
			
			 Anonymous Maarten
						Anonymous Maarten
					
				
			
						parent
						
							1f89c407e0
						
					
				
				
					commit
					45081db9d4
				
			| @@ -4,6 +4,7 @@ import argparse | ||||
| import collections | ||||
| import contextlib | ||||
| import datetime | ||||
| import glob | ||||
| import io | ||||
| import json | ||||
| import logging | ||||
| @@ -26,6 +27,24 @@ logger = logging.getLogger(__name__) | ||||
| VcArchDevel = collections.namedtuple("VcArchDevel", ("dll", "imp", "test")) | ||||
| GIT_HASH_FILENAME = ".git-hash" | ||||
|  | ||||
| ANDROID_AVAILABLE_ABIS = [ | ||||
|     "armeabi-v7a", | ||||
|     "arm64-v8a", | ||||
|     "x86", | ||||
|     "x86_64", | ||||
| ] | ||||
| ANDROID_MINIMUM_API = 19 | ||||
| ANDROID_TARGET_API = 29 | ||||
| ANDROID_MINIMUM_NDK = 21 | ||||
| ANDROID_LIBRARIES = [ | ||||
|     "dl", | ||||
|     "GLESv1_CM", | ||||
|     "GLESv2", | ||||
|     "log", | ||||
|     "android", | ||||
|     "OpenSLES", | ||||
| ] | ||||
|  | ||||
|  | ||||
| def itertools_batched(iterator: typing.Iterable, count: int): | ||||
|     iterator = iter(iterator) | ||||
| @@ -373,6 +392,8 @@ class Releaser: | ||||
|                 self.executer.run([ | ||||
|                     "cmake", "-S", str(self.root), "-B", str(build_path), | ||||
|                     "--fresh", | ||||
|                     f'''-DCMAKE_C_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}"''', | ||||
|                     f'''-DCMAKE_CXX_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}"''', | ||||
|                     "-DSDL_SHARED=ON", | ||||
|                     "-DSDL_STATIC=ON", | ||||
|                     "-DSDL_DISABLE_INSTALL_DOCS=ON", | ||||
| @@ -507,6 +528,178 @@ class Releaser: | ||||
|             self._zip_add_git_hash(zip_file=zf, root=archive_prefix) | ||||
|         self.artifacts["VC-devel"] = zip_path | ||||
|  | ||||
|     def detect_android_api(self, android_home: str) -> typing.Optional[int]: | ||||
|         platform_dirs = list(Path(p) for p in glob.glob(f"{android_home}/platforms/android-*")) | ||||
|         re_platform = re.compile("android-([0-9]+)") | ||||
|         platform_versions = [] | ||||
|         for platform_dir in platform_dirs: | ||||
|             logger.debug("Found Android Platform SDK: %s", platform_dir) | ||||
|             if m:= re_platform.match(platform_dir.name): | ||||
|                 platform_versions.append(int(m.group(1))) | ||||
|         platform_versions.sort() | ||||
|         logger.info("Available platform versions: %s", platform_versions) | ||||
|         platform_versions = list(filter(lambda v: v >= ANDROID_MINIMUM_API, platform_versions)) | ||||
|         logger.info("Valid platform versions (>=%d): %s", ANDROID_MINIMUM_API, platform_versions) | ||||
|         if not platform_versions: | ||||
|             return None | ||||
|         android_api = platform_versions[0] | ||||
|         logger.info("Selected API version %d", android_api) | ||||
|         return android_api | ||||
|  | ||||
|     def get_prefab_json_text(self): | ||||
|         return textwrap.dedent(f"""\ | ||||
|             {{ | ||||
|                 "schema_version": 2, | ||||
|                 "name": "{self.project}", | ||||
|                 "version": "{self.version}", | ||||
|                 "dependencies": [] | ||||
|             }} | ||||
|         """) | ||||
|  | ||||
|     def get_prefab_module_json_text(self, library_name: str, extra_libs: list[str]): | ||||
|         export_libraries_str = ", ".join(f"\"-l{lib}\"" for lib in extra_libs) | ||||
|         return textwrap.dedent(f"""\ | ||||
|             {{ | ||||
|                 "export_libraries": [{export_libraries_str}], | ||||
|                 "library_name": "lib{library_name}" | ||||
|             }} | ||||
|         """) | ||||
|  | ||||
|     def get_prefab_abi_json_text(self, abi: str, cpp: bool, shared: bool): | ||||
|         return textwrap.dedent(f"""\ | ||||
|             {{ | ||||
|               "abi": "{abi}", | ||||
|               "api": {ANDROID_MINIMUM_API}, | ||||
|               "ndk": {ANDROID_MINIMUM_NDK}, | ||||
|               "stl": "{'c++_shared' if cpp else 'none'}", | ||||
|               "static": {'true' if not shared else 'false'} | ||||
|             }} | ||||
|         """) | ||||
|  | ||||
|     def get_android_manifest_text(self): | ||||
|         return textwrap.dedent(f"""\ | ||||
|             <manifest | ||||
|                 xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|                 package="org.libsdl.android.{self.project}" android:versionCode="1" | ||||
|                 android:versionName="1.0"> | ||||
|                 <uses-sdk android:minSdkVersion="{ANDROID_MINIMUM_API}" | ||||
|                           android:targetSdkVersion="{ANDROID_TARGET_API}" /> | ||||
|             </manifest> | ||||
|         """) | ||||
|  | ||||
|     def create_android_archives(self, android_api: int, android_home: Path, android_ndk_home: Path, android_abis: list[str]): | ||||
|         cmake_toolchain_file = Path(android_ndk_home) / "build/cmake/android.toolchain.cmake" | ||||
|         if not cmake_toolchain_file.exists(): | ||||
|             logger.error("CMake toolchain file does not exist (%s)", cmake_toolchain_file) | ||||
|             raise SystemExit(1) | ||||
|         aar_path =  self.dist_path / f"{self.project}-{self.version}.aar" | ||||
|         added_global_files = False | ||||
|         with zipfile.ZipFile(aar_path, "w", compression=zipfile.ZIP_DEFLATED) as zip_object: | ||||
|             zip_object.writestr("AndroidManifest.xml", self.get_android_manifest_text()) | ||||
|             zip_object.write(self.root / "android-project/app/proguard-rules.pro", arcname="proguard.txt") | ||||
|             zip_object.write(self.root / "LICENSE.txt", arcname="META-INF/LICENSE.txt") | ||||
|             zip_object.writestr("prefab/prefab.json", self.get_prefab_json_text()) | ||||
|             self._zip_add_git_hash(zip_file=zip_object) | ||||
|  | ||||
|             for android_abi in android_abis: | ||||
|                 with self.section_printer.group(f"Building for Android {android_api} {android_abi}"): | ||||
|                     build_dir = self.root / "build-android" / f"{android_abi}-build" | ||||
|                     install_dir = self.root / "install-android" / f"{android_abi}-install" | ||||
|                     shutil.rmtree(install_dir, ignore_errors=True) | ||||
|                     assert not install_dir.is_dir(), f"{install_dir} should not exist prior to build" | ||||
|                     cmake_args = [ | ||||
|                         "cmake", | ||||
|                         "-S", str(self.root), | ||||
|                         "-B", str(build_dir), | ||||
|                         "--fresh", | ||||
|                         f'''-DCMAKE_C_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}"''', | ||||
|                         f'''-DCMAKE_CXX_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}"''', | ||||
|                         "-DCMAKE_BUILD_TYPE=RelWithDebInfo", | ||||
|                         f"-DCMAKE_TOOLCHAIN_FILE={cmake_toolchain_file}", | ||||
|                         f"-DANDROID_PLATFORM={android_api}", | ||||
|                         f"-DANDROID_ABI={android_abi}", | ||||
|                         f"-DCMAKE_POSITION_INDEPENDENT_CODE=ON", | ||||
|                         "-DSDL_SHARED=ON", | ||||
|                         "-DSDL_STATIC=ON", | ||||
|                         "-DSDL_STATIC_PIC=ON", | ||||
|                         "-DSDL_TEST_LIBRARY=ON", | ||||
|                         "-DSDL_DISABLE_ANDROID_JAR=OFF", | ||||
|                         "-DSDL_TESTS=OFF", | ||||
|                         f"-DCMAKE_INSTALL_PREFIX={install_dir}", | ||||
|                         "-DSDL_DISABLE_INSTALL=OFF", | ||||
|                         "-DSDL_DISABLE_INSTALL_DOCS=OFF", | ||||
|                         "-DCMAKE_INSTALL_INCLUDEDIR=include ", | ||||
|                         "-DCMAKE_INSTALL_LIBDIR=lib", | ||||
|                         "-DCMAKE_INSTALL_DATAROOTDIR=share", | ||||
|                         "-DCMAKE_BUILD_TYPE=Release", | ||||
|                         f"-G{self.cmake_generator}", | ||||
|                     ] | ||||
|                     build_args = [ | ||||
|                         "cmake", | ||||
|                         "--build", str(build_dir), | ||||
|                         "--config", "RelWithDebInfo", | ||||
|                     ] | ||||
|                     install_args = [ | ||||
|                         "cmake", | ||||
|                         "--install", str(build_dir), | ||||
|                         "--config", "RelWithDebInfo", | ||||
|                     ] | ||||
|                     self.executer.run(cmake_args) | ||||
|                     self.executer.run(build_args) | ||||
|                     self.executer.run(install_args) | ||||
|  | ||||
|                     main_so_library = install_dir / "lib" / f"lib{self.project}.so" | ||||
|                     logger.debug("Expecting library %s", main_so_library) | ||||
|                     assert main_so_library.is_file(), "CMake should have built a shared library (e.g. libSDL3.so)" | ||||
|  | ||||
|                     main_static_library = install_dir / "lib" / f"lib{self.project}.a" | ||||
|                     logger.debug("Expecting library %s", main_static_library) | ||||
|                     assert main_static_library.is_file(), "CMake should have built a static library (e.g. libSDL3.a)" | ||||
|  | ||||
|                     test_library = install_dir / "lib" / f"lib{self.project}_test.a" | ||||
|                     logger.debug("Expecting library %s", test_library) | ||||
|                     assert test_library.is_file(), "CMake should have built a static test library (e.g. libSDL3_test.a)" | ||||
|  | ||||
|                     java_jar = install_dir / f"share/java/{self.project}/{self.project}-{self.version}.jar" | ||||
|                     logger.debug("Expecting java archive: %s", java_jar) | ||||
|                     assert java_jar.is_file(), "CMake should have compiled the java sources and archived them into a JAR" | ||||
|  | ||||
|                     javasources_jar = install_dir / f"share/java/{self.project}/{self.project}-{self.version}-sources.jar" | ||||
|                     logger.debug("Expecting java sources archive %s", javasources_jar) | ||||
|                     assert javasources_jar.is_file(), "CMake should have archived the java sources into a JAR" | ||||
|  | ||||
|                     javadoc_dir = install_dir / "share/javadoc" / self.project | ||||
|                     logger.debug("Expecting javadoc archive %s", javadoc_dir) | ||||
|                     assert javadoc_dir.is_dir(), "CMake should have built javadoc documentation for the java sources" | ||||
|                     if not added_global_files: | ||||
|                         zip_object.write(java_jar, arcname="classes.jar") | ||||
|                         zip_object.write(javasources_jar, arcname="classes-sources.jar", ) | ||||
|                         doc_jar_path = install_dir / "classes-doc.jar" | ||||
|  | ||||
|                         javadoc_jar_args = ["jar", "--create", "--file", str(doc_jar_path)] | ||||
|                         for fn in javadoc_dir.iterdir(): | ||||
|                             javadoc_jar_args.extend(["-C", str(javadoc_dir), fn.name]) | ||||
|                         self.executer.run(javadoc_jar_args) | ||||
|                         zip_object.write(doc_jar_path, arcname="classes-doc.jar") | ||||
|  | ||||
|                         for header in (install_dir / "include" / self.project).iterdir(): | ||||
|                             zip_object.write(header, arcname=f"prefab/modules/{self.project}-shared/include/{self.project}/{header.name}") | ||||
|                             zip_object.write(header, arcname=f"prefab/modules/{self.project}-static/include/{self.project}/{header.name}") | ||||
|  | ||||
|                         zip_object.writestr(f"prefab/modules/{self.project}-shared/module.json", self.get_prefab_module_json_text(library_name=self.project, extra_libs=[])) | ||||
|                         zip_object.writestr(f"prefab/modules/{self.project}-static/module.json", self.get_prefab_module_json_text(library_name=self.project, extra_libs=list(ANDROID_LIBRARIES))) | ||||
|                         zip_object.writestr(f"prefab/modules/{self.project}_test/module.json", self.get_prefab_module_json_text(library_name=f"{self.project}_test", extra_libs=list())) | ||||
|                         added_global_files = True | ||||
|  | ||||
|                     zip_object.write(main_so_library, arcname=f"prefab/modules/{self.project}-shared/libs/android.{android_abi}/lib{self.project}.so") | ||||
|                     zip_object.writestr(f"prefab/modules/{self.project}-shared/libs/android.{android_abi}/abi.json", self.get_prefab_abi_json_text(abi=android_abi, cpp=False, shared=True)) | ||||
|                     zip_object.write(main_static_library, arcname=f"prefab/modules/{self.project}-static/libs/android.{android_abi}/lib{self.project}.a") | ||||
|                     zip_object.writestr(f"prefab/modules/{self.project}-static/libs/android.{android_abi}/abi.json", self.get_prefab_abi_json_text(abi=android_abi, cpp=False, shared=False)) | ||||
|                     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)) | ||||
|  | ||||
|         self.artifacts[f"android-prefab-aar"] = aar_path | ||||
|  | ||||
|     @classmethod | ||||
|     def extract_sdl_version(cls, root: Path, project: str): | ||||
|         with open(root / f"include/{project}/SDL_version.h", "r") as f: | ||||
| @@ -523,10 +716,14 @@ def main(argv=None): | ||||
|     parser.add_argument("--out", "-o", metavar="DIR", dest="dist_path", type=Path, default="dist", help="Output directory") | ||||
|     parser.add_argument("--github", action="store_true", help="Script is running on a GitHub runner") | ||||
|     parser.add_argument("--commit", default="HEAD", help="Git commit/tag of which a release should be created") | ||||
|     parser.add_argument("--project", required=True, help="Name of the project") | ||||
|     parser.add_argument("--create", choices=["source", "mingw", "win32", "xcframework"], required=True,action="append", dest="actions", help="SDL version") | ||||
|     parser.add_argument("--project", required=True, help="Name of the project (e.g. SDL3") | ||||
|     parser.add_argument("--create", choices=["source", "mingw", "win32", "xcframework", "android"], required=True, action="append", dest="actions", help="What to do") | ||||
|     parser.set_defaults(loglevel=logging.INFO) | ||||
|     parser.add_argument('--vs-year', dest="vs_year", help="Visual Studio year") | ||||
|     parser.add_argument('--android-api', type=int, dest="android_api", help="Android API version") | ||||
|     parser.add_argument('--android-home', dest="android_home", default=os.environ.get("ANDROID_HOME"), help="Android Home folder") | ||||
|     parser.add_argument('--android-ndk-home', dest="android_ndk_home", default=os.environ.get("ANDROID_NDK_HOME"), help="Android NDK Home folder") | ||||
|     parser.add_argument('--android-abis', dest="android_abis", nargs="*", choices=ANDROID_AVAILABLE_ABIS, default=list(ANDROID_AVAILABLE_ABIS), help="Android NDK Home folder") | ||||
|     parser.add_argument('--cmake-generator', dest="cmake_generator", default="Ninja", help="CMake Generator") | ||||
|     parser.add_argument('--debug', action='store_const', const=logging.DEBUG, dest="loglevel", help="Print script debug information") | ||||
|     parser.add_argument('--dry-run', action='store_true', dest="dry", help="Don't execute anything") | ||||
| @@ -581,14 +778,14 @@ def main(argv=None): | ||||
|                 raise Exception("The git repo contains modified and/or non-committed files. Run with --force to ignore.") | ||||
|  | ||||
|     with section_printer.group("Arguments"): | ||||
|         print(f"project         = {args.project}") | ||||
|         print(f"version         = {releaser.version}") | ||||
|         print(f"commit          = {args.commit}") | ||||
|         print(f"out             = {args.dist_path}") | ||||
|         print(f"actions         = {args.actions}") | ||||
|         print(f"dry             = {args.dry}") | ||||
|         print(f"force           = {args.force}") | ||||
|         print(f"cmake_generator = {args.cmake_generator}") | ||||
|         print(f"project          = {args.project}") | ||||
|         print(f"version          = {releaser.version}") | ||||
|         print(f"commit           = {args.commit}") | ||||
|         print(f"out              = {args.dist_path}") | ||||
|         print(f"actions          = {args.actions}") | ||||
|         print(f"dry              = {args.dry}") | ||||
|         print(f"force            = {args.force}") | ||||
|         print(f"cmake_generator  = {args.cmake_generator}") | ||||
|  | ||||
|     releaser.prepare() | ||||
|  | ||||
| @@ -623,6 +820,31 @@ def main(argv=None): | ||||
|     if "mingw" in args.actions: | ||||
|         releaser.create_mingw_archives() | ||||
|  | ||||
|     if "android" in args.actions: | ||||
|         if args.android_home is None or not Path(args.android_home).is_dir(): | ||||
|             parser.error("Invalid $ANDROID_HOME or --android-home: must be a directory containing the Android SDK") | ||||
|         if args.android_ndk_home is None or not Path(args.android_ndk_home).is_dir(): | ||||
|             parser.error("Invalid $ANDROID_NDK_HOME or --android_ndk_home: must be a directory containing the Android NDK") | ||||
|         if args.android_api is None: | ||||
|             with section_printer.group("Detect Android APIS"): | ||||
|                 args.android_api = releaser.detect_android_api(android_home=args.android_home) | ||||
|         if args.android_api is None or not (Path(args.android_home) / f"platforms/android-{args.android_api}").is_dir(): | ||||
|             parser.error("Invalid --android-api, and/or could not be detected") | ||||
|         if not args.android_abis: | ||||
|             parser.error("Need at least one Android ABI") | ||||
|         with section_printer.group("Android arguments"): | ||||
|             print(f"android_home     = {args.android_home}") | ||||
|             print(f"android_ndk_home = {args.android_ndk_home}") | ||||
|             print(f"android_api      = {args.android_api}") | ||||
|             print(f"android_abis     = {args.android_abis}") | ||||
|         releaser.create_android_archives( | ||||
|             android_api=args.android_api, | ||||
|             android_home=args.android_home, | ||||
|             android_ndk_home=args.android_ndk_home, | ||||
|             android_abis=args.android_abis, | ||||
|         ) | ||||
|  | ||||
|  | ||||
|     with section_printer.group("Summary"): | ||||
|         print(f"artifacts = {releaser.artifacts}") | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user