create-test-plan.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
  1. #!/usr/bin/env python
  2. import argparse
  3. import dataclasses
  4. import fnmatch
  5. from enum import Enum
  6. import json
  7. import logging
  8. import os
  9. import re
  10. from typing import Optional
  11. logger = logging.getLogger(__name__)
  12. class AppleArch(Enum):
  13. Aarch64 = "aarch64"
  14. X86_64 = "x86_64"
  15. class MsvcArch(Enum):
  16. X86 = "x86"
  17. X64 = "x64"
  18. Arm32 = "arm"
  19. Arm64 = "arm64"
  20. class JobOs(Enum):
  21. WindowsLatest = "windows-latest"
  22. UbuntuLatest = "ubuntu-latest"
  23. MacosLatest = "macos-latest"
  24. Ubuntu20_04 = "ubuntu-20.04"
  25. Ubuntu22_04 = "ubuntu-22.04"
  26. Ubuntu24_04 = "ubuntu-24.04"
  27. Macos12 = "macos-12"
  28. class SdlPlatform(Enum):
  29. Android = "android"
  30. Emscripten = "emscripten"
  31. Haiku = "haiku"
  32. LoongArch64 = "loongarch64"
  33. Msys2 = "msys2"
  34. Linux = "linux"
  35. MacOS = "macos"
  36. Ios = "ios"
  37. Tvos = "tvos"
  38. Msvc = "msvc"
  39. N3ds = "n3ds"
  40. PowerPC64 = "powerpc64"
  41. Ps2 = "ps2"
  42. Psp = "psp"
  43. Riscos = "riscos"
  44. FreeBSD = "freebsd"
  45. NetBSD = "netbsd"
  46. class Msys2Platform(Enum):
  47. Mingw32 = "mingw32"
  48. Mingw64 = "mingw64"
  49. Clang32 = "clang32"
  50. Clang64 = "clang64"
  51. Ucrt64 = "ucrt64"
  52. class IntelCompiler(Enum):
  53. Icc = "icc"
  54. Icx = "icx"
  55. @dataclasses.dataclass(slots=True)
  56. class JobSpec:
  57. name: str
  58. os: JobOs
  59. platform: SdlPlatform
  60. artifact: Optional[str]
  61. container: Optional[str] = None
  62. no_cmake: bool = False
  63. android_mk: bool = False
  64. android_gradle: bool = False
  65. lean: bool = False
  66. android_arch: Optional[str] = None
  67. android_abi: Optional[str] = None
  68. android_platform: Optional[int] = None
  69. msys2_platform: Optional[Msys2Platform] = None
  70. intel: Optional[IntelCompiler] = None
  71. apple_framework: Optional[bool] = None
  72. apple_archs: Optional[set[AppleArch]] = None
  73. msvc_project: Optional[str] = None
  74. msvc_arch: Optional[MsvcArch] = None
  75. clang_cl: bool = False
  76. uwp: bool = False
  77. JOB_SPECS = {
  78. "msys2-mingw32": JobSpec(name="Windows (msys2, mingw32)", os=JobOs.WindowsLatest, platform=SdlPlatform.Msys2, artifact="SDL-mingw32", msys2_platform=Msys2Platform.Mingw32, ),
  79. "msys2-mingw64": JobSpec(name="Windows (msys2, mingw64)", os=JobOs.WindowsLatest, platform=SdlPlatform.Msys2, artifact="SDL-mingw64", msys2_platform=Msys2Platform.Mingw64, ),
  80. "msys2-clang32": JobSpec(name="Windows (msys2, clang32)", os=JobOs.WindowsLatest, platform=SdlPlatform.Msys2, artifact="SDL-mingw32-clang", msys2_platform=Msys2Platform.Clang32, ),
  81. "msys2-clang64": JobSpec(name="Windows (msys2, clang64)", os=JobOs.WindowsLatest, platform=SdlPlatform.Msys2, artifact="SDL-mingw64-clang", msys2_platform=Msys2Platform.Clang64, ),
  82. "msys2-ucrt64": JobSpec(name="Windows (msys2, ucrt64)", os=JobOs.WindowsLatest, platform=SdlPlatform.Msys2, artifact="SDL-mingw64-ucrt", msys2_platform=Msys2Platform.Ucrt64, ),
  83. "msvc-x64": JobSpec(name="Windows (MSVC, x64)", os=JobOs.WindowsLatest, platform=SdlPlatform.Msvc, artifact="SDL-VC-x64", msvc_arch=MsvcArch.X64, msvc_project="VisualC/SDL.sln", ),
  84. "msvc-x86": JobSpec(name="Windows (MSVC, x86)", os=JobOs.WindowsLatest, platform=SdlPlatform.Msvc, artifact="SDL-VC-x86", msvc_arch=MsvcArch.X86, msvc_project="VisualC/SDL.sln", ),
  85. "msvc-clang-x64": JobSpec(name="Windows (MSVC, clang-cl x64)", os=JobOs.WindowsLatest, platform=SdlPlatform.Msvc, artifact="SDL-clang-cl-x64", msvc_arch=MsvcArch.X64, clang_cl=True, ),
  86. "msvc-clang-x86": JobSpec(name="Windows (MSVC, clang-cl x86)", os=JobOs.WindowsLatest, platform=SdlPlatform.Msvc, artifact="SDL-clang-cl-x86", msvc_arch=MsvcArch.X86, clang_cl=True, ),
  87. "msvc-arm32": JobSpec(name="Windows (MSVC, ARM)", os=JobOs.WindowsLatest, platform=SdlPlatform.Msvc, artifact="SDL-VC-arm32", msvc_arch=MsvcArch.Arm32, ),
  88. "msvc-arm64": JobSpec(name="Windows (MSVC, ARM64)", os=JobOs.WindowsLatest, platform=SdlPlatform.Msvc, artifact="SDL-VC-arm64", msvc_arch=MsvcArch.Arm64, ),
  89. "msvc-uwp-x64": JobSpec(name="UWP (MSVC, x64)", os=JobOs.WindowsLatest, platform=SdlPlatform.Msvc, artifact="SDL-VC-UWP", msvc_arch=MsvcArch.X64, msvc_project="VisualC-WinRT/SDL-UWP.sln", uwp=True, ),
  90. "ubuntu-20.04": JobSpec(name="Ubuntu 20.04", os=JobOs.Ubuntu20_04, platform=SdlPlatform.Linux, artifact="SDL-ubuntu20.04", ),
  91. "ubuntu-22.04": JobSpec(name="Ubuntu 22.04", os=JobOs.Ubuntu22_04, platform=SdlPlatform.Linux, artifact="SDL-ubuntu22.04", ),
  92. "ubuntu-intel-icx": JobSpec(name="Ubuntu 20.04 (Intel oneAPI)", os=JobOs.Ubuntu20_04, platform=SdlPlatform.Linux, artifact="SDL-ubuntu20.04-oneapi", intel=IntelCompiler.Icx, ),
  93. "ubuntu-intel-icc": JobSpec(name="Ubuntu 20.04 (Intel Compiler)", os=JobOs.Ubuntu20_04, platform=SdlPlatform.Linux, artifact="SDL-ubuntu20.04-icc", intel=IntelCompiler.Icc, ),
  94. "macos-framework-x64": JobSpec(name="MacOS (Framework) (x86_64)", os=JobOs.Macos12, platform=SdlPlatform.MacOS, artifact="SDL-macos-framework", apple_framework=True, apple_archs={AppleArch.Aarch64, AppleArch.X86_64, }, ),
  95. "macos-framework-arm64": JobSpec(name="MacOS (Framework) (arm64)", os=JobOs.MacosLatest, platform=SdlPlatform.MacOS, artifact=None, apple_framework=True, apple_archs={AppleArch.Aarch64, AppleArch.X86_64, }, ),
  96. "amcos-gnu-arm64": JobSpec(name="MacOS (GNU prefix)", os=JobOs.MacosLatest, platform=SdlPlatform.MacOS, artifact="SDL-macos-arm64-gnu", apple_framework=False, apple_archs={AppleArch.Aarch64, }, ),
  97. "android-cmake": JobSpec(name="Android (CMake)", os=JobOs.UbuntuLatest, platform=SdlPlatform.Android, artifact="SDL-android-arm64", android_abi="arm64-v8a", android_arch="aarch64", android_platform=23, ),
  98. "android-cmake-lean": JobSpec(name="Android (CMake, lean)", os=JobOs.UbuntuLatest, platform=SdlPlatform.Android, artifact="SDL-lean-android-arm64", android_abi="arm64-v8a", android_arch="aarch64", android_platform=23, lean=True, ),
  99. "android-mk": JobSpec(name="Android (Android.mk)", os=JobOs.UbuntuLatest, platform=SdlPlatform.Android, artifact=None, no_cmake=True, android_mk=True, ),
  100. "android-gradle": JobSpec(name="Android (Gradle)", os=JobOs.UbuntuLatest, platform=SdlPlatform.Android, artifact=None, no_cmake=True, android_gradle=True, ),
  101. "ios-xcode": JobSpec(name="iOS (xcode)", os=JobOs.MacosLatest, platform=SdlPlatform.Ios, artifact=None, no_cmake=True, ),
  102. "tvos-xcode": JobSpec(name="tvOS (xcode)", os=JobOs.MacosLatest, platform=SdlPlatform.Tvos, artifact=None, no_cmake=True, ),
  103. "emscripten": JobSpec(name="Emscripten", os=JobOs.UbuntuLatest, platform=SdlPlatform.Emscripten, artifact="SDL-emscripten", ),
  104. "haiku": JobSpec(name="Haiku", os=JobOs.UbuntuLatest, platform=SdlPlatform.Haiku, artifact="SDL-haiku-x64", container="haiku/cross-compiler:x86_64-r1beta4", ),
  105. "loongarch64": JobSpec(name="LoongArch64", os=JobOs.UbuntuLatest, platform=SdlPlatform.LoongArch64, artifact="SDL-loongarch64", ),
  106. "n3ds": JobSpec(name="Nintendo 3DS", os=JobOs.UbuntuLatest, platform=SdlPlatform.N3ds, artifact="SDL-n3ds", container="devkitpro/devkitarm:latest", ),
  107. "ppc64": JobSpec(name="PowerPC64", os=JobOs.UbuntuLatest, platform=SdlPlatform.PowerPC64, artifact="SDL-ppc64le", container="dockcross/linux-ppc64le:latest", ),
  108. "ps2": JobSpec(name="Sony PlayStation 2", os=JobOs.UbuntuLatest, platform=SdlPlatform.Ps2, artifact="SDL-ps2", container="ps2dev/ps2dev:latest", ),
  109. "psp": JobSpec(name="Sony PlayStation Portable", os=JobOs.UbuntuLatest, platform=SdlPlatform.Psp, artifact="SDL-psp", container="pspdev/pspdev:latest", ),
  110. "riscos": JobSpec(name="RISC OS", os=JobOs.UbuntuLatest, platform=SdlPlatform.Riscos, artifact="SDL-riscos", container="riscosdotinfo/riscos-gccsdk-4.7:latest", ),
  111. "netbsd": JobSpec(name="NetBSD", os=JobOs.UbuntuLatest, platform=SdlPlatform.NetBSD, artifact="SDL-netbsd-x64", ),
  112. "freebsd": JobSpec(name="FreeBSD", os=JobOs.UbuntuLatest, platform=SdlPlatform.FreeBSD, artifact="SDL-freebsd-x64", ),
  113. }
  114. @dataclasses.dataclass(slots=True)
  115. class JobDetails:
  116. name: str
  117. os: str
  118. platform: str
  119. artifact: str
  120. no_cmake: bool
  121. build_tests: bool = True
  122. container: str = ""
  123. cmake_build_type: str = "Release"
  124. shell: str = "sh"
  125. sudo: str = "sudo"
  126. cmake_config_emulator: str = ""
  127. apk_packages: list[str] = dataclasses.field(default_factory=list)
  128. apt_packages: list[str] = dataclasses.field(default_factory=list)
  129. brew_packages: list[str] = dataclasses.field(default_factory=list)
  130. cmake_toolchain_file: str = ""
  131. cmake_arguments: list[str] = dataclasses.field(default_factory=list)
  132. cmake_build_arguments: list[str] = dataclasses.field(default_factory=list)
  133. clang_tidy: bool = True
  134. cppflags: list[str] = dataclasses.field(default_factory=list)
  135. cc: str = ""
  136. cxx: str = ""
  137. cflags: list[str] = dataclasses.field(default_factory=list)
  138. cxxflags: list[str] = dataclasses.field(default_factory=list)
  139. ldflags: list[str] = dataclasses.field(default_factory=list)
  140. pollute_directories: list[str] = dataclasses.field(default_factory=list)
  141. use_cmake: bool = True
  142. shared: bool = True
  143. static: bool = True
  144. run_tests: bool = True
  145. test_pkg_config: bool = True
  146. cc_from_cmake: bool = False
  147. source_cmd: str = ""
  148. java: bool = False
  149. android_apks: list[str] = dataclasses.field(default_factory=list)
  150. android_ndk: bool = False
  151. android_mk: bool = False
  152. android_gradle: bool = False
  153. minidump: bool = False
  154. intel: bool = False
  155. msys2_msystem: str = ""
  156. msys2_env: str = ""
  157. msys2_no_perl: bool = False
  158. werror: bool = True
  159. msvc_vcvars: str = ""
  160. msvc_project: str = ""
  161. msvc_project_flags: list[str] = dataclasses.field(default_factory=list)
  162. setup_ninja: bool = False
  163. setup_libusb_arch: str = ""
  164. xcode_sdk: str = ""
  165. cpactions: bool = False
  166. cpactions_os: str = ""
  167. cpactions_version: str = ""
  168. cpactions_arch: str = ""
  169. cpactions_setup_cmd: str = ""
  170. cpactions_install_cmd: str = ""
  171. def to_workflow(self) -> dict[str, str|bool]:
  172. data = {
  173. "name": self.name,
  174. "os": self.os,
  175. "container": self.container if self.container else "",
  176. "platform": self.platform,
  177. "artifact": self.artifact,
  178. "shell": self.shell,
  179. "msys2-msystem": self.msys2_msystem,
  180. "msys2-env": self.msys2_env,
  181. "msys2-no-perl": self.msys2_no_perl,
  182. "android-ndk": self.android_ndk,
  183. "java": self.java,
  184. "intel": self.intel,
  185. "apk-packages": my_shlex_join(self.apk_packages),
  186. "apt-packages": my_shlex_join(self.apt_packages),
  187. "test-pkg-config": self.test_pkg_config,
  188. "brew-packages": my_shlex_join(self.brew_packages),
  189. "pollute-directories": my_shlex_join(self.pollute_directories),
  190. "no-cmake": self.no_cmake,
  191. "build-tests": self.build_tests,
  192. "source-cmd": self.source_cmd,
  193. "cmake-config-emulator": self.cmake_config_emulator,
  194. "cc": self.cc,
  195. "cxx": self.cxx,
  196. "cflags": my_shlex_join(self.cppflags + self.cflags),
  197. "cxxflags": my_shlex_join(self.cppflags + self.cxxflags),
  198. "ldflags": my_shlex_join(self.ldflags),
  199. "cmake-toolchain-file": self.cmake_toolchain_file,
  200. "clang-tidy": self.clang_tidy,
  201. "cmake-arguments": my_shlex_join(self.cmake_arguments),
  202. "cmake-build-arguments": my_shlex_join(self.cmake_build_arguments),
  203. "shared": self.shared,
  204. "static": self.static,
  205. "cmake-build-type": self.cmake_build_type,
  206. "run-tests": self.run_tests,
  207. "android-apks": my_shlex_join(self.android_apks),
  208. "android-gradle": self.android_gradle,
  209. "android-mk": self.android_mk,
  210. "werror": self.werror,
  211. "sudo": self.sudo,
  212. "msvc-vcvars": self.msvc_vcvars,
  213. "msvc-project": self.msvc_project,
  214. "msvc-project-flags": my_shlex_join(self.msvc_project_flags),
  215. "setup-ninja": self.setup_ninja,
  216. "setup-libusb-arch": self.setup_libusb_arch,
  217. "cc-from-cmake": self.cc_from_cmake,
  218. "xcode-sdk": self.xcode_sdk,
  219. "cpactions": self.cpactions,
  220. "cpactions-os": self.cpactions_os,
  221. "cpactions-version": self.cpactions_version,
  222. "cpactions-arch": self.cpactions_arch,
  223. "cpactions-setup-cmd": self.cpactions_setup_cmd,
  224. "cpactions-install-cmd": self.cpactions_install_cmd,
  225. }
  226. return {k: v for k, v in data.items() if v != ""}
  227. def my_shlex_join(s):
  228. def escape(s):
  229. if s[:1] == "'" and s[-1:] == "'":
  230. return s
  231. if set(s).intersection(set("; \t")):
  232. return f'"{s}"'
  233. return s
  234. return " ".join(escape(s))
  235. def spec_to_job(spec: JobSpec) -> JobDetails:
  236. job = JobDetails(
  237. name=spec.name,
  238. os=spec.os.value,
  239. artifact=spec.artifact or "",
  240. container=spec.container or "",
  241. platform=spec.platform.value,
  242. sudo="sudo",
  243. no_cmake=spec.no_cmake,
  244. )
  245. if job.os.startswith("ubuntu"):
  246. job.apt_packages.extend([
  247. "ninja-build",
  248. "pkg-config",
  249. ])
  250. win32 = spec.platform in (SdlPlatform.Msys2, SdlPlatform.Msvc)
  251. fpic = None
  252. build_parallel = True
  253. if spec.lean:
  254. job.cppflags.append("-DSDL_LEAN_AND_MEAN=1")
  255. if win32:
  256. job.cmake_arguments.append("-DSDLTEST_PROCDUMP=ON")
  257. job.minidump = True
  258. if spec.intel is not None:
  259. match spec.intel:
  260. case IntelCompiler.Icx:
  261. job.cc = "icx"
  262. job.cxx = "icpx"
  263. case IntelCompiler.Icc:
  264. job.cc = "icc"
  265. job.cxx = "icpc"
  266. job.cppflags.append("-diag-disable=10441")
  267. job.clang_tidy = False
  268. case _:
  269. raise ValueError(f"Invalid intel={spec.intel}")
  270. job.source_cmd = f"source /opt/intel/oneapi/setvars.sh;"
  271. job.intel = True
  272. job.shell = "bash"
  273. job.cmake_arguments.extend((
  274. f"-DCMAKE_C_COMPILER={job.cc}",
  275. f"-DCMAKE_CXX_COMPILER={job.cxx}",
  276. "-DCMAKE_SYSTEM_NAME=Linux",
  277. ))
  278. match spec.platform:
  279. case SdlPlatform.Msvc:
  280. job.setup_ninja = True
  281. job.clang_tidy = False # complains about \threadsafety: "unknown command tag name [clang-diagnostic-documentation-unknown-command]"
  282. job.msvc_project = spec.msvc_project if spec.msvc_project else ""
  283. job.test_pkg_config = False
  284. job.cmake_arguments.extend((
  285. "-DCMAKE_MSVC_DEBUG_INFORMATION_FORMAT=ProgramDatabase",
  286. "-DCMAKE_EXE_LINKER_FLAGS=-DEBUG",
  287. "-DCMAKE_SHARED_LINKER_FLAGS=-DEBUG",
  288. ))
  289. if spec.uwp:
  290. job.cmake_arguments.append("'-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded$<$<CONFIG:Debug>:Debug>DLL'")
  291. else:
  292. job.cmake_arguments.append("'-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded$<$<CONFIG:Debug>:Debug>'")
  293. if spec.clang_cl:
  294. job.cmake_arguments.extend((
  295. "-DCMAKE_C_COMPILER=clang-cl",
  296. "-DCMAKE_CXX_COMPILER=clang-cl",
  297. ))
  298. match spec.msvc_arch:
  299. case MsvcArch.X86:
  300. job.cflags.append("/clang:-m32")
  301. job.ldflags.append("/MACHINE:X86")
  302. case MsvcArch.X64:
  303. job.cflags.append("/clang:-m64")
  304. job.ldflags.append("/MACHINE:X64")
  305. case _:
  306. raise ValueError(f"Unsupported clang-cl architecture (arch={spec.msvc_arch})")
  307. if spec.msvc_project:
  308. match spec.msvc_arch:
  309. case MsvcArch.X86:
  310. job.msvc_project_flags.append("-p:Platform=Win32")
  311. case MsvcArch.X64:
  312. job.msvc_project_flags.append("-p:Platform=x64")
  313. case _:
  314. raise ValueError(f"Unsupported vcxproj architecture (arch={spec.msvc_arch})")
  315. match spec.msvc_arch:
  316. case MsvcArch.X86:
  317. job.msvc_vcvars = "x64_x86"
  318. case MsvcArch.X64:
  319. job.msvc_vcvars = "x64"
  320. case MsvcArch.Arm32:
  321. job.msvc_vcvars = "x64_arm"
  322. job.run_tests = False
  323. case MsvcArch.Arm64:
  324. job.msvc_vcvars = "x64_arm64"
  325. job.run_tests = False
  326. if spec.uwp:
  327. job.build_tests = False
  328. job.cmake_arguments.extend((
  329. "-DCMAKE_SYSTEM_NAME=WindowsStore",
  330. "-DCMAKE_SYSTEM_VERSION=10.0",
  331. ))
  332. job.msvc_project_flags.append("-p:WindowsTargetPlatformVersion=10.0.17763.0")
  333. else:
  334. match spec.msvc_arch:
  335. case MsvcArch.X86:
  336. job.setup_libusb_arch = "x86"
  337. case MsvcArch.X64:
  338. job.setup_libusb_arch = "x64"
  339. case SdlPlatform.Linux:
  340. job.apt_packages.extend((
  341. "gnome-desktop-testing",
  342. "libasound2-dev",
  343. "libpulse-dev",
  344. "libaudio-dev",
  345. "libjack-dev",
  346. "libsndio-dev",
  347. "libusb-1.0-0-dev",
  348. "libx11-dev",
  349. "libxext-dev",
  350. "libxrandr-dev",
  351. "libxcursor-dev",
  352. "libxfixes-dev",
  353. "libxi-dev",
  354. "libxss-dev",
  355. "libwayland-dev",
  356. "libxkbcommon-dev",
  357. "libdrm-dev",
  358. "libgbm-dev",
  359. "libgl1-mesa-dev",
  360. "libgles2-mesa-dev",
  361. "libegl1-mesa-dev",
  362. "libdbus-1-dev",
  363. "libibus-1.0-dev",
  364. "libudev-dev",
  365. "fcitx-libs-dev",
  366. ))
  367. fpic = True
  368. assert spec.os.value.startswith("ubuntu-")
  369. ubuntu_year, ubuntu_month = [int(v) for v in spec.os.value.removeprefix("ubuntu-").split(".", 1)]
  370. if ubuntu_year >= 22:
  371. job.apt_packages.extend(("libpipewire-0.3-dev", "libdecor-0-dev"))
  372. case SdlPlatform.Ios | SdlPlatform.Tvos:
  373. match spec.platform:
  374. case SdlPlatform.Ios:
  375. job.xcode_sdk = 'iphoneos'
  376. case SdlPlatform.Tvos:
  377. job.xcode_sdk = 'appletvos'
  378. case SdlPlatform.MacOS:
  379. if spec.apple_framework:
  380. job.static = False
  381. job.clang_tidy = False
  382. job.test_pkg_config = False
  383. job.cmake_arguments.extend((
  384. "'-DCMAKE_OSX_ARCHITECTURES=x86_64;arm64'",
  385. "-DSDL_FRAMEWORK=ON",
  386. ))
  387. else:
  388. job.clang_tidy = True
  389. job.cmake_arguments.extend((
  390. "-DCMAKE_OSX_ARCHITECTURES=arm64",
  391. "-DCLANG_TIDY_BINARY=$(brew --prefix llvm)/bin/clang-tidy",
  392. ))
  393. job.apt_packages = []
  394. job.brew_packages.append("ninja")
  395. if job.test_pkg_config:
  396. job.brew_packages.append("pkg-config")
  397. if job.clang_tidy:
  398. job.brew_packages.append("llvm")
  399. case SdlPlatform.Android:
  400. job.android_gradle = spec.android_gradle
  401. job.android_mk = spec.android_mk
  402. job.run_tests = False
  403. if spec.android_mk or not spec.no_cmake:
  404. job.android_ndk = True
  405. if spec.android_gradle or not spec.no_cmake:
  406. job.java = True
  407. if spec.android_mk or spec.android_gradle:
  408. job.apt_packages = []
  409. if not spec.no_cmake:
  410. job.cmake_arguments.extend((
  411. f"-DANDROID_PLATFORM={spec.android_platform}",
  412. f"-DANDROID_ABI={spec.android_abi}",
  413. ))
  414. job.cmake_toolchain_file = "${ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake"
  415. job.cc = f"${{ANDROID_NDK_HOME}}/toolchains/llvm/prebuilt/linux-x86_64/bin/clang --target={spec.android_arch}-none-linux-androideabi{spec.android_platform}"
  416. job.android_apks = [
  417. "testaudiorecording-apk",
  418. "testautomation-apk",
  419. "testcontroller-apk",
  420. "testmultiaudio-apk",
  421. "testsprite-apk",
  422. ]
  423. case SdlPlatform.Emscripten:
  424. job.run_tests = False
  425. job.shared = False
  426. job.cmake_config_emulator = "emcmake"
  427. job.cmake_build_type = "Debug"
  428. job.test_pkg_config = False
  429. case SdlPlatform.Ps2:
  430. build_parallel = False
  431. job.shared = False
  432. job.sudo = ""
  433. job.apt_packages = []
  434. job.apk_packages = ["cmake", "gmp", "mpc1", "mpfr4", "ninja", "pkgconf", "git", ]
  435. job.cmake_toolchain_file = "${PS2DEV}/ps2sdk/ps2dev.cmake"
  436. job.clang_tidy = False
  437. job.run_tests = False
  438. job.shared = False
  439. job.cc = "mips64r5900el-ps2-elf-gcc"
  440. job.ldflags = ["-L${PS2DEV}/ps2sdk/ee/lib", "-L${PS2DEV}/gsKit/lib", "-L${PS2DEV}/ps2sdk/ports/lib", ]
  441. case SdlPlatform.Psp:
  442. build_parallel = False
  443. job.sudo = ""
  444. job.apt_packages = []
  445. job.apk_packages = ["cmake", "gmp", "mpc1", "mpfr4", "ninja", "pkgconf", ]
  446. job.cmake_toolchain_file = "${PSPDEV}/psp/share/pspdev.cmake"
  447. job.clang_tidy = False
  448. job.run_tests = False
  449. job.shared = False
  450. job.cc = "psp-gcc"
  451. job.ldflags = ["-L${PSPDEV}/lib", "-L${PSPDEV}/psp/lib", "-L${PSPDEV}/psp/sdk/lib", ]
  452. job.pollute_directories = ["${PSPDEV}/include", "${PSPDEV}/psp/include", "${PSPDEV}/psp/sdk/include", ]
  453. case SdlPlatform.Haiku:
  454. fpic = False
  455. job.run_tests = False
  456. job.cc = "x86_64-unknown-haiku-gcc"
  457. job.cxx = "x86_64-unknown-haiku-g++"
  458. job.sudo = ""
  459. job.cmake_arguments.extend((
  460. f"-DCMAKE_C_COMPILER={job.cc}",
  461. f"-DCMAKE_CXX_COMPILER={job.cxx}",
  462. "-DCMAKE_SYSTEM_NAME=Haiku",
  463. ))
  464. case SdlPlatform.PowerPC64:
  465. # FIXME: Enable SDL_WERROR
  466. job.werror = False
  467. job.clang_tidy = False
  468. job.run_tests = False
  469. job.sudo = ""
  470. job.apt_packages = []
  471. case SdlPlatform.LoongArch64:
  472. job.run_tests = False
  473. job.cc = "${LOONGARCH64_CC}"
  474. job.cxx = "${LOONGARCH64_CXX}"
  475. job.cmake_arguments.extend((
  476. f"-DCMAKE_C_COMPILER={job.cc}",
  477. f"-DCMAKE_CXX_COMPILER={job.cxx}",
  478. "-DCMAKE_SYSTEM_NAME=Linux",
  479. ))
  480. case SdlPlatform.N3ds:
  481. job.shared = False
  482. job.apt_packages = ["ninja-build"]
  483. job.clang_tidy = False
  484. job.run_tests = False
  485. job.cc_from_cmake = True
  486. job.cmake_toolchain_file = "${DEVKITPRO}/cmake/3DS.cmake"
  487. case SdlPlatform.Msys2:
  488. job.shell = "msys2 {0}"
  489. assert spec.msys2_platform
  490. job.msys2_msystem = spec.msys2_platform.value
  491. job.msys2_env = {
  492. "mingw32": "mingw-w64-i686",
  493. "mingw64": "mingw-w64-x86_64",
  494. "clang32": "mingw-w64-clang-i686",
  495. "clang64": "mingw-w64-clang-x86_64",
  496. "ucrt64": "mingw-w64-ucrt-x86_64",
  497. }[spec.msys2_platform.value]
  498. job.msys2_no_perl = spec.msys2_platform in (Msys2Platform.Mingw32, Msys2Platform.Clang32)
  499. case SdlPlatform.Riscos:
  500. # FIXME: Enable SDL_WERROR
  501. job.werror = False
  502. job.apt_packages = ["cmake", "ninja-build"]
  503. job.test_pkg_config = False
  504. job.shared = False
  505. job.run_tests = False
  506. job.sudo = ""
  507. job.cmake_arguments.extend((
  508. "-DRISCOS:BOOL=ON",
  509. "-DCMAKE_DISABLE_PRECOMPILE_HEADERS:BOOL=ON",
  510. "-DSDL_GCC_ATOMICS:BOOL=OFF",
  511. ))
  512. job.cmake_toolchain_file = "/home/riscos/env/toolchain-riscos.cmake"
  513. case SdlPlatform.FreeBSD | SdlPlatform.NetBSD:
  514. job.cpactions = True
  515. job.no_cmake = True
  516. job.run_tests = False
  517. job.apt_packages = []
  518. match spec.platform:
  519. case SdlPlatform.FreeBSD:
  520. job.cpactions_os = "freebsd"
  521. job.cpactions_version = "13.3"
  522. job.cpactions_arch = "x86-64"
  523. job.cpactions_setup_cmd = "sudo pkg update"
  524. job.cpactions_install_cmd = "sudo pkg install -y cmake ninja pkgconf libXcursor libXext libXinerama libXi libXfixes libXrandr libXScrnSaver libXxf86vm wayland wayland-protocols libxkbcommon mesa-libs libglvnd evdev-proto libinotify alsa-lib jackit pipewire pulseaudio sndio dbus zh-fcitx ibus libudev-devd"
  525. job.cmake_arguments.extend((
  526. "-DSDL_CHECK_REQUIRED_INCLUDES=/usr/local/include",
  527. "-DSDL_CHECK_REQUIRED_LINK_OPTIONS=-L/usr/local/lib",
  528. ))
  529. case SdlPlatform.NetBSD:
  530. job.cpactions_os = "netbsd"
  531. job.cpactions_version = "10.0"
  532. job.cpactions_arch = "x86-64"
  533. job.cpactions_setup_cmd = "export PATH=\"/usr/pkg/sbin:/usr/pkg/bin:/sbin:$PATH\"; export PKG_CONFIG_PATH=\"/usr/pkg/lib/pkgconfig\";export PKG_PATH=\"https://cdn.netBSD.org/pub/pkgsrc/packages/NetBSD/$(uname -p)/$(uname -r|cut -f \"1 2\" -d.)/All/\";echo \"PKG_PATH=$PKG_PATH\";echo \"uname -a -> \"$(uname -a)\"\";sudo -E sysctl -w security.pax.aslr.enabled=0;sudo -E sysctl -w security.pax.aslr.global=0;sudo -E pkgin clean;sudo -E pkgin update"
  534. job.cpactions_install_cmd = "sudo -E pkgin -y install cmake dbus pkgconf ninja-build pulseaudio libxkbcommon wayland wayland-protocols libinotify libusb1"
  535. case _:
  536. raise ValueError(f"Unsupported platform={spec.platform}")
  537. if not build_parallel:
  538. job.cmake_build_arguments.append("-j1")
  539. if job.cflags:
  540. job.cmake_arguments.append(f"-DCMAKE_C_FLAGS={my_shlex_join(job.cflags)}")
  541. if job.cxxflags:
  542. job.cmake_arguments.append(f"-DCMAKE_CXX_FLAGS={my_shlex_join(job.cxxflags)}")
  543. if job.ldflags:
  544. job.cmake_arguments.append(f"-DCMAKE_SHARED_LINKER_FLAGS=\"{my_shlex_join(job.ldflags)}\"")
  545. job.cmake_arguments.append(f"-DCMAKE_EXE_LINKER_FLAGS=\"{my_shlex_join(job.ldflags)}\"")
  546. def tf(b):
  547. return "ON" if b else "OFF"
  548. if fpic is not None:
  549. job.cmake_arguments.append(f"-DCMAKE_POSITION_INDEPENDENT_CODE={tf(fpic)}")
  550. return job
  551. def specs_to_plaform(specs: tuple[JobSpec, ...]) -> list[dict[str, str|bool]]:
  552. result = []
  553. for spec in specs:
  554. logger.info("spec=%r", spec)
  555. job = spec_to_job(spec)
  556. logger.info("job=%r", job)
  557. platform = job.to_workflow()
  558. result.append(platform)
  559. logger.info("platform=%r", platform)
  560. return result
  561. def main():
  562. parser = argparse.ArgumentParser(allow_abbrev=False)
  563. parser.add_argument("--github-variable-prefix", default="platforms")
  564. parser.add_argument("--github-ci", action="store_true")
  565. parser.add_argument("--verbose", action="store_true")
  566. parser.add_argument("--commit-message-file")
  567. args = parser.parse_args()
  568. logging.basicConfig(level=logging.INFO if args.verbose else logging.WARNING)
  569. remaining_keys = set(JOB_SPECS.keys())
  570. all_level_keys = (
  571. # Level 1
  572. (
  573. "haiku",
  574. ),
  575. )
  576. filters = []
  577. if args.commit_message_file:
  578. with open(args.commit_message_file, "r") as f:
  579. commit_message = f.read()
  580. for m in re.finditer(r"\[sdl-ci-filter (.*)]", commit_message, flags=re.M):
  581. filters.append(m.group(1).strip(" \t\n\r\t'\""))
  582. if not filters:
  583. filters.append("*")
  584. logger.info("filters: %r", filters)
  585. all_level_platforms = {}
  586. for level_i, level_keys in enumerate(all_level_keys, 1):
  587. level_key = f"level{level_i}"
  588. logger.info("Level %d: keys=%r", level_i, level_keys)
  589. assert all(k in remaining_keys for k in level_keys)
  590. level_specs = tuple(JOB_SPECS[key] for key in level_keys)
  591. remaining_keys.difference_update(level_keys)
  592. all_level_platforms[level_key] = specs_to_plaform(level_specs)
  593. logger.info("=" * 80)
  594. logger.info("Keys before filter: %r", remaining_keys)
  595. filtered_remaining_keys = set()
  596. for filter in filters:
  597. filtered_remaining_keys.update(fnmatch.filter(remaining_keys, filter))
  598. logger.info("Keys after filter: %r", filtered_remaining_keys)
  599. remaining_keys = filtered_remaining_keys
  600. logger.info("Remaining:")
  601. remaining_specs = tuple(JOB_SPECS[key] for key in remaining_keys)
  602. all_level_platforms["others"] = specs_to_plaform(remaining_specs)
  603. if args.github_ci:
  604. for level, platforms in all_level_platforms.items():
  605. platforms_json = json.dumps(platforms)
  606. txt = f"{args.github_variable_prefix}-{level}={platforms_json}"
  607. logger.info("%s", txt)
  608. if "GITHUB_OUTPUT" in os.environ:
  609. with open(os.environ["GITHUB_OUTPUT"], "a") as f:
  610. f.write(txt)
  611. f.write("\n")
  612. else:
  613. logger.warning("GITHUB_OUTPUT not defined")
  614. return 0
  615. if __name__ == "__main__":
  616. raise SystemExit(main())