SDL_image: Synchronize files with SDL3

From bb404a6066df4331f15a39bd086b5a48771a61a7 Mon Sep 17 00:00:00 2001
From: Anonymous Maarten <[EMAIL REDACTED]>
Date: Fri, 4 Sep 2026 23:39:23 +0200
Subject: [PATCH] Synchronize files with SDL3

---
 build-scripts/build-release.py | 60 ++++++++++++++++++++++++++++------
 build-scripts/wikiheaders.pl   |  6 ++--
 2 files changed, 53 insertions(+), 13 deletions(-)

diff --git a/build-scripts/build-release.py b/build-scripts/build-release.py
index d3725382c..faf0dd496 100755
--- a/build-scripts/build-release.py
+++ b/build-scripts/build-release.py
@@ -175,12 +175,18 @@ 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], lib_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
+        lib_contents = "%LIB%"
+        if lib_paths:
+            lib_contents = f"{';'.join(str(p) for p in lib_paths)};" + lib_contents
+        bat_contents = textwrap.dedent(f"{vsdev_cmd_str} && set INCLUDE={include_contents} && set LIB={lib_paths} && {msbuild_cmd_str}")
         bat_path = Path(tempfile.gettempdir()) / "cmd.bat"
         with bat_path.open("w") as f:
             f.write(bat_contents)
@@ -740,6 +746,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 +868,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 +1141,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 +1164,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 +1199,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 +1214,21 @@ 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 = []
+        lib_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")
+                lib_paths.append(self.deps_path / dep / "lib")
+                lib_paths.append(self.deps_path / dep / "lib" / arch_platform.arch)
+                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 +1276,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, lib_paths=lib_paths)
 
         if self.dry:
             for b in built_paths:
@@ -1274,7 +1312,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 +1328,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 +1341,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/wikiheaders.pl b/build-scripts/wikiheaders.pl
index 933156ea7..801f7ea83 100755
--- a/build-scripts/wikiheaders.pl
+++ b/build-scripts/wikiheaders.pl
@@ -1375,7 +1375,7 @@ sub generate_envvar_wiki_page {
                 if (($p eq 'void') || ($p eq '')) {
                     die("Void parameter in a function with multiple params?! ('$sym' in '$incpath/$dent')") if (scalar(@params) != 1);
                 } elsif ($p eq '...') {
-                    die("Mutiple '...' params?! ('$sym' in '$incpath/$dent')") if ($dotdotdot);
+                    die("Multiple '...' params?! ('$sym' in '$incpath/$dent')") if ($dotdotdot);
                     $dotdotdot = 1;
                     push @paraminfo, '...';
                     push @paraminfo, '...';
@@ -1686,7 +1686,7 @@ sub generate_envvar_wiki_page {
                 if ($has_doxygen) {
                     print STDERR "WARNING: Symbol '$sym' appears to be documented in multiple locations. Only keeping the first one we saw!\n";
                 }
-                push @contents, join("\n", @decllines) if (scalar(@decllines) > 0);  # just put the existing declation in as-is.
+                push @contents, join("\n", @decllines) if (scalar(@decllines) > 0);  # just put the existing declaration in as-is.
             }
         }
 
@@ -3073,7 +3073,7 @@ sub generate_envvar_wiki_page {
         if (defined $returns) {
             # Check for md link in return type: ([SDL_Renderer](SDL_Renderer) *)
             # This would've prevented the next regex from working properly (it'd leave " *)")
-            $returns =~ s/\A\(\[.*?\]\((.*?)\)/\($1/ms;
+            $returns =~ s/\A\((const|)\s*\[.*?\]\((.*?)\)/\($2/ms;
             # Chop datatype in parentheses off the front.
             $returns =~ s/\A\(.*?\) //;