From 6b26332785cb67a7380429ec4b8358c99c3773f1 Mon Sep 17 00:00:00 2001
From: Anonymous Maarten <[EMAIL REDACTED]>
Date: Thu, 14 May 2026 19:15:42 +0200
Subject: [PATCH] ci+release: build with gameinput support
---
.github/workflows/create-test-plan.py | 9 ++++
.github/workflows/generic.yml | 8 +++
.github/workflows/release.yml | 6 +--
build-scripts/build-release.py | 54 +++++++++++++++++----
build-scripts/download-gameinput-headers.py | 44 +++++++++++++++++
build-scripts/release-info.json | 12 +++++
build-scripts/setup-gdk-desktop.py | 2 -
7 files changed, 120 insertions(+), 15 deletions(-)
create mode 100755 build-scripts/download-gameinput-headers.py
diff --git a/.github/workflows/create-test-plan.py b/.github/workflows/create-test-plan.py
index c0e847c05f10f..8d5bdf621d472 100755
--- a/.github/workflows/create-test-plan.py
+++ b/.github/workflows/create-test-plan.py
@@ -12,6 +12,7 @@
logger = logging.getLogger(__name__)
+WINDOWS_GAMEINPUT_VERSION = "v3.3.195.0 "
class AppleArch(Enum):
Aarch64 = "aarch64"
@@ -217,6 +218,7 @@ class JobDetails:
msys2_msystem: str = ""
msys2_packages: list[str] = dataclasses.field(default_factory=list)
werror: bool = True
+ microsoft_gameinput_version: str = ""
msvc_vcvars_arch: str = ""
msvc_vcvars_sdk: str = ""
msvc_project: str = ""
@@ -286,6 +288,7 @@ def to_workflow(self, enable_artifacts: bool) -> dict[str, str|bool]:
"android-mk": self.android_mk,
"werror": self.werror,
"sudo": self.sudo,
+ "microsoft-gameinput-version": self.microsoft_gameinput_version,
"msvc-vcvars-arch": self.msvc_vcvars_arch,
"msvc-vcvars-sdk": self.msvc_vcvars_sdk,
"msvc-project": self.msvc_project,
@@ -437,6 +440,9 @@ def spec_to_job(spec: JobSpec, key: str, trackmem_symbol_names: bool, ctest_args
job.setup_libusb_arch = "x86"
case MsvcArch.X64:
job.setup_libusb_arch = "x64"
+ job.microsoft_gameinput_version = WINDOWS_GAMEINPUT_VERSION
+ job.cflags.append("-I$GAMEINPUT_INCLUDE")
+ job.cxxflags.append("-I$GAMEINPUT_INCLUDE")
case SdlPlatform.Linux:
if spec.name.startswith("Ubuntu"):
assert spec.os.value.startswith("ubuntu-")
@@ -764,6 +770,9 @@ def spec_to_job(spec: JobSpec, key: str, trackmem_symbol_names: bool, ctest_args
job.msys2_packages.append(f"{msys2_env}-clang-tools-extra")
if job.ccache:
job.msys2_packages.append(f"{msys2_env}-ccache")
+ job.microsoft_gameinput_version = WINDOWS_GAMEINPUT_VERSION
+ job.cflags.append("-I$GAMEINPUT_INCLUDE")
+ job.cxxflags.append("-I$GAMEINPUT_INCLUDE")
case SdlPlatform.Riscos:
job.ccache = False # FIXME: enable when container gets upgrade
# FIXME: Enable SDL_WERROR
diff --git a/.github/workflows/generic.yml b/.github/workflows/generic.yml
index 2ac0e307434e5..8250e50e726ee 100644
--- a/.github/workflows/generic.yml
+++ b/.github/workflows/generic.yml
@@ -167,6 +167,14 @@ jobs:
echo '#error "System SDL headers must not be used by build system"' >"$dest"
done
done
+ - name: 'Set up Microsoft.GameInput headers'
+ if: ${{ matrix.platform.microsoft-gameinput-version != '' }}
+ run: |
+ python build-scripts/download-gameinput-headers.py \
+ --version ${{ matrix.platform.microsoft-gameinput-version }} \
+ -o $HOME/gameinput
+ echo "GAMEINPUT_INCLUDE=$(cygpath -w "$HOME/gameinput")" >>$GITHUB_ENV
+ echo "INCLUDE=$INCLUDE;$(cygpath -w "$HOME/gameinput")" >>$GITHUB_ENV
- name: 'Calculate ccache key'
if: ${{ matrix.platform.ccache }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 6929f152d354f..b619a514b954e 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -345,7 +345,7 @@ jobs:
id: releaser
run: |
python build-scripts/build-release.py `
- --actions msvc `
+ --actions download msvc `
--commit ${{ inputs.commit }} `
--root "${{ steps.zip.outputs.path }}" `
--github `
@@ -507,7 +507,7 @@ jobs:
id: releaser
run: |
python build-scripts/build-release.py \
- --actions mingw \
+ --actions download mingw \
--commit ${{ inputs.commit }} \
--root "${{ steps.tar.outputs.path }}" \
--github \
@@ -619,7 +619,7 @@ jobs:
id: releaser
run: |
python build-scripts/build-release.py \
- --actions android \
+ --actions download android \
--android-api 21 \
--android-ndk-home "${{ steps.setup-ndk.outputs.ndk-path }}" \
--commit ${{ inputs.commit }} \
diff --git a/build-scripts/build-release.py b/build-scripts/build-release.py
index d3725382ce102..4e153a1bf996e 100755
--- a/build-scripts/build-release.py
+++ b/build-scripts/build-release.py
@@ -175,12 +175,15 @@ def find_msbuild(self) -> typing.Optional[Path]:
assert msbuild_path.is_file(), "MSBuild.exe does not exist"
return msbuild_path
- def build(self, arch_platform: VsArchPlatformConfig, projects: list[Path]):
+ def build(self, arch_platform: VsArchPlatformConfig, projects: list[Path], include_paths: list[str]):
assert projects, "Need at least one project to build"
vsdev_cmd_str = f"\"{self.vsdevcmd}\" -arch={arch_platform.arch}"
msbuild_cmd_str = " && ".join([f"\"{self.msbuild}\" \"{project}\" /m /p:BuildInParallel=true /p:Platform={arch_platform.platform} /p:Configuration={arch_platform.configuration}" for project in projects])
- bat_contents = f"{vsdev_cmd_str} && {msbuild_cmd_str}\n"
+ include_contents = "%INCLUDE%"
+ if include_paths:
+ include_contents = f"{';'.join(str(p) for p in include_paths)};" + include_contents
+ bat_contents = textwrap.dedent(f"{vsdev_cmd_str} && set INCLUDE={include_contents} && {msbuild_cmd_str}")
bat_path = Path(tempfile.gettempdir()) / "cmd.bat"
with bat_path.open("w") as f:
f.write(bat_contents)
@@ -740,6 +743,12 @@ def extract_filter(member: tarfile.TarInfo, path: str, /):
member.name = "/".join(Path(member.name).parts[1:])
return member
for dep in self.release_info.get("dependencies", {}):
+ if "command" in self.release_info["dependencies"][dep]:
+ for arch, triplet in ARCH_TO_TRIPLET.items():
+ shutil.copytree(self.deps_path / dep, str(mingw_deps_path / triplet), dirs_exist_ok=True)
+ (mingw_deps_path / triplet / "bin").mkdir(exist_ok=True)
+ (mingw_deps_path / triplet / "lib/pkgconfig").mkdir(exist_ok=True, parents=True)
+ continue
extract_path = mingw_deps_path / f"extract-{dep}"
extract_path.mkdir()
with chdir(extract_path):
@@ -856,8 +865,8 @@ def extract_filter(member: tarfile.TarInfo, path: str, /):
f"cmake",
f"-S", str(self.root), "-B", str(build_path),
f"-DCMAKE_BUILD_TYPE={build_type}",
- f'''-DCMAKE_C_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}"''',
- f'''-DCMAKE_CXX_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}"''',
+ f'''-DCMAKE_C_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}" "-I{str(mingw_deps_path / triplet)}/include"''',
+ f'''-DCMAKE_CXX_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}" "-I{str(mingw_deps_path / triplet)}/include"''',
f"-DCMAKE_PREFIX_PATH={mingw_deps_path / triplet}",
f"-DCMAKE_INSTALL_PREFIX={install_path}",
f"-DCMAKE_INSTALL_INCLUDEDIR=include",
@@ -1129,6 +1138,15 @@ def download_dependencies(self):
f.write(f"dep-path={self.deps_path.absolute()}\n")
for dep, depinfo in self.release_info.get("dependencies", {}).items():
+ if "command" in depinfo:
+ (self.deps_path / dep).mkdir(exist_ok=True, parents=True)
+ command_args = configure_text_list(shlex.split(depinfo["command"]), context=self.get_context(extra_context={
+ "DEPS_PATH": str(self.deps_path / dep),
+ }))
+ if command_args[0].endswith(".py"):
+ command_args.insert(0, sys.executable)
+ self.executer.run(command_args)
+ continue
startswith = depinfo["startswith"]
dep_repo = depinfo["repo"]
dep_string_data = self.executer.check_output(["gh", "-R", dep_repo, "release", "list", "--exclude-drafts", "--exclude-pre-releases", "--json", "name,createdAt,tagName", "--jq", f'[.[]|select(.name|startswith("{startswith}"))]|max_by(.createdAt)']).strip()
@@ -1143,6 +1161,10 @@ def download_dependencies(self):
def verify_dependencies(self):
for dep, depinfo in self.release_info.get("dependencies", {}).items():
+ if "command" in depinfo:
+ command_matches = glob.glob(depinfo["artifact"], root_dir=self.deps_path)
+ assert len(command_matches) == 1, f"Exactly one archive matches command {dep} dependency: {command_matches}"
+ continue
if "mingw" in self.release_info:
mingw_matches = glob.glob(self.release_info["mingw"]["dependencies"][dep]["artifact"], root_dir=self.deps_path)
assert len(mingw_matches) == 1, f"Exactly one archive matches mingw {dep} dependency: {mingw_matches}"
@@ -1174,7 +1196,12 @@ def build_msvc(self):
deps_path = self.root / "msvc-deps"
shutil.rmtree(deps_path, ignore_errors=True)
dep_roots = []
- for dep, depinfo in self.release_info["msvc"].get("dependencies", {}).items():
+ dep_includes = []
+ for dep in self.release_info.get("dependencies"):
+ if "command" in self.release_info["dependencies"][dep]:
+ dep_includes.append(self.deps_path / dep / "include")
+ continue
+ depinfo = self.release_info["msvc"]["dependencies"][dep]
dep_extract_path = deps_path / f"extract-{dep}"
msvc_zip = self.deps_path / glob.glob(depinfo["artifact"], root_dir=self.deps_path)[0]
with zipfile.ZipFile(msvc_zip, "r") as zf:
@@ -1184,13 +1211,18 @@ def build_msvc(self):
dep_roots.append(contents_msvc_zip[0])
for arch in self.release_info["msvc"].get("cmake", {}).get("archs", []):
- self._build_msvc_cmake(arch_platform=self._arch_to_vs_platform(arch=arch), dep_roots=dep_roots)
+ self._build_msvc_cmake(arch_platform=self._arch_to_vs_platform(arch=arch), dep_roots=dep_roots, dep_includes=dep_includes)
with self.section_printer.group("Create SDL VC development zip"):
self._build_msvc_devel()
def _build_msvc_msbuild(self, arch_platform: VsArchPlatformConfig, vs: VisualStudio):
platform_context = self.get_context(arch_platform.extra_context())
- for dep, depinfo in self.release_info["msvc"].get("dependencies", {}).items():
+ include_paths = []
+ for dep in self.release_info.get("dependencies", {}):#release_info["msvc"].get("dependencies", {}).items():
+ if "command" in self.release_info["dependencies"][dep]:
+ include_paths.append(self.deps_path / dep / "include")
+ continue
+ depinfo = self.release_info["msvc"]["dependencies"][dep]
msvc_zip = self.deps_path / glob.glob(depinfo["artifact"], root_dir=self.deps_path)[0]
src_globs = [configure_text(instr["src"], context=platform_context) for instr in depinfo["copy"]]
@@ -1238,7 +1270,7 @@ def _build_msvc_msbuild(self, arch_platform: VsArchPlatformConfig, vs: VisualStu
shutil.copy(src=src, dst=dir_b_props)
with self.section_printer.group(f"Build {arch_platform.arch} VS binary"):
- vs.build(arch_platform=arch_platform, projects=projects)
+ vs.build(arch_platform=arch_platform, projects=projects, include_paths=include_paths)
if self.dry:
for b in built_paths:
@@ -1274,7 +1306,7 @@ def _arch_platform_to_build_path(self, arch_platform: VsArchPlatformConfig) -> P
def _arch_platform_to_install_path(self, arch_platform: VsArchPlatformConfig) -> Path:
return self._arch_platform_to_build_path(arch_platform) / "prefix"
- def _build_msvc_cmake(self, arch_platform: VsArchPlatformConfig, dep_roots: list[Path]):
+ def _build_msvc_cmake(self, arch_platform: VsArchPlatformConfig, dep_roots: list[Path], dep_includes: list[Path]):
build_path = self._arch_platform_to_build_path(arch_platform)
install_path = self._arch_platform_to_install_path(arch_platform)
platform_context = self.get_context(extra_context=arch_platform.extra_context())
@@ -1290,7 +1322,7 @@ def _build_msvc_cmake(self, arch_platform: VsArchPlatformConfig, dep_roots: list
if not self.fast:
for b in built_paths:
b.unlink(missing_ok=True)
-
+ cppflags = list(f"-I{inc}" for inc in dep_includes)
shutil.rmtree(install_path, ignore_errors=True)
build_path.mkdir(parents=True, exist_ok=True)
with self.section_printer.group(f"Configure VC CMake project for {arch_platform.arch}"):
@@ -1303,6 +1335,8 @@ def _build_msvc_cmake(self, arch_platform: VsArchPlatformConfig, dep_roots: list
"-DCMAKE_INSTALL_LIBDIR=lib",
f"-DCMAKE_BUILD_TYPE={build_type}",
f"-DCMAKE_INSTALL_PREFIX={install_path}",
+ f"-DCMAKE_C_FLAGS={shlex.join(cppflags)}",
+ f"-DCMAKE_CXX_FLAGS={shlex.join(cppflags)}",
# MSVC debug information format flags are selected by an abstraction
"-DCMAKE_POLICY_DEFAULT_CMP0141=NEW",
# MSVC debug information format
diff --git a/build-scripts/download-gameinput-headers.py b/build-scripts/download-gameinput-headers.py
new file mode 100755
index 0000000000000..13d50028c0859
--- /dev/null
+++ b/build-scripts/download-gameinput-headers.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+
+import argparse
+import logging
+from pathlib import Path
+import urllib.request
+
+logger = logging.getLogger(__name__)
+
+def download_headers(tag: str, lowercase: bool, output: Path):
+ base_url = f"https://raw.githubusercontent.com/microsoftconnect/GameInput/refs/tags/{tag}/"
+ url_relpaths = (
+ "include/GameInput.h",
+ "include/v0/GameInput.h",
+ "include/v1/GameInput.h",
+ "include/v2/GameInput.h",
+ )
+ remove_prefix = "include/"
+ for url_relpath in url_relpaths:
+ url = base_url + url_relpath
+ local_relpath = url_relpath.removeprefix(remove_prefix)
+ if lowercase:
+ local_relpath = local_relpath.lower()
+ local_path = output / local_relpath
+
+ local_dirpath = local_path.parent
+ local_dirpath.mkdir(parents=False, exist_ok=True)
+
+ logger.info("Downloading %s to %s...", url, local_path)
+ urllib.request.urlretrieve(url, local_path)
+ logger.info("... done")
+
+def main():
+ logging.basicConfig(level=logging.INFO)
+
+ parser = argparse.ArgumentParser(description="Download Microsoft.GameInput headers", allow_abbrev=False)
+ parser.add_argument("--version", required=True, help="GameInput release tag (see https://github.com/microsoftconnect/GameInput/tags)")
+ parser.add_argument("--no-lowercase", action="store_false", dest="lowercase", help="Don't lowercase downloaded headers")
+ parser.add_argument("-o", "--output", type=Path, default=Path.cwd(), help="Headers will be stored here (subdirectories created as ")
+ args = parser.parse_args()
+ download_headers(tag=args.version, lowercase=args.lowercase, output=args.output)
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/build-scripts/release-info.json b/build-scripts/release-info.json
index 48477514142aa..e70683bc3a35b 100644
--- a/build-scripts/release-info.json
+++ b/build-scripts/release-info.json
@@ -1,6 +1,12 @@
{
"name": "SDL3",
"remote": "libsdl-org/SDL",
+ "dependencies": {
+ "gameinput": {
+ "command": "build-scripts/download-gameinput-headers.py -o @<@DEPS_PATH@>@/include --version v3.3.195.0",
+ "artifact": "gameinput/include/gameinput.h"
+ }
+ },
"version": {
"file": "include/SDL3/SDL_version.h",
"re_major": "^#define SDL_MAJOR_VERSION\\s+([0-9]+)$",
@@ -54,6 +60,9 @@
"build-scripts/pkg-support/mingw/cmake/SDL3Config.cmake",
"build-scripts/pkg-support/mingw/cmake/SDL3ConfigVersion.cmake"
]
+ },
+ "dependencies": {
+ "gameinput": {}
}
},
"msvc": {
@@ -129,6 +138,9 @@
"include/SDL3": [
"include/SDL3/*.h"
]
+ },
+ "dependencies": {
+ "gameinput": {}
}
},
"android": {
diff --git a/build-scripts/setup-gdk-desktop.py b/build-scripts/setup-gdk-desktop.py
index d2309a0e3119e..8dc259d11bf1b 100755
--- a/build-scripts/setup-gdk-desktop.py
+++ b/build-scripts/setup-gdk-desktop.py
@@ -262,8 +262,6 @@ def main():
parser.add_argument("--no-user-props", required=False, dest="user_props", action="store_false", help="Don't ")
args = parser.parse_args()
- logging.basicConfig(level=logging.INFO)
-
git_ref = None
gdk_edition = None
if args.ref_edition is not None: