From 902655d4e80426d7c19fd7406418ff65d6414cec Mon Sep 17 00:00:00 2001
From: Anonymous Maarten <[EMAIL REDACTED]>
Date: Mon, 17 Nov 2025 19:44:53 +0100
Subject: [PATCH] cmake+xcode: add exports file for Apple
(cherry picked from commit ad4e3eeeeb3caf3c19fd6469bf69f59227514702)
---
.github/workflows/main.yml | 8 +-
CMakeLists.txt | 5 +-
Xcode/SDL_ttf.xcodeproj/project.pbxproj | 2 +
.../CMake/SDL3_ttfConfigVersion.cmake | 2 +-
src/SDL_ttf.exports | 119 ++++
src/SDL_ttf.sym | 1 +
src/genexports.py | 574 ++++++++++++++++++
7 files changed, 708 insertions(+), 3 deletions(-)
create mode 100644 src/SDL_ttf.exports
create mode 100755 src/genexports.py
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 15eb4685..ab0696bf 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -18,7 +18,7 @@ jobs:
- { name: Windows (MSVC+CMake), os: windows-latest, shell: sh, cmake: '-DPerl_ROOT=C:/Strawberry/perl/bin/ -DSDLTTF_VENDORED=ON -GNinja', msvc: 1, shared: 1, static: 0, artifact: 'SDL3_ttf-VC-x64' }
- { name: Windows (mingw64+CMake), os: windows-latest, shell: 'msys2 {0}', msystem: mingw64, msys-env: mingw-w64-x86_64, shared: 1, static: 0,
cmake: '-DSDLTTF_VENDORED=OFF -G "Ninja Multi-Config"', artifact: 'SDL3_ttf-mingw64' }
- - { name: Linux, os: ubuntu-latest, shell: sh, cmake: '-DSDLTTF_VENDORED=ON -GNinja', shared: 1, static: 0, artifact: 'SDL3_image-linux-x64' }
+ - { name: Linux, os: ubuntu-latest, shell: sh, cmake: '-DSDLTTF_VENDORED=ON -GNinja', shared: 1, static: 0, test-exports: true, artifact: 'SDL3_image-linux-x64' }
- { name: 'Linux (static)', os: ubuntu-latest, shell: sh, cmake: '-GNinja -DBUILD_SHARED_LIBS=OFF', shared: 0, static: 1, artifact: 'SDL3_Image-static-linux-x64' }
- { name: Macos, os: macos-latest, shell: sh, cmake: '-DSDLTTF_VENDORED=ON -GNinja', shared: 1, static: 0, artifact: 'SDL3_image-macos' }
@@ -116,6 +116,12 @@ jobs:
# ... but we can at least assert that it outputs a .bmp
file build/glyph-100.bmp
+ - name: Verify exports files
+ if: ${{ matrix.platform.test-exports }}
+ run: |
+ set -e
+ python src/genexports.py
+ git diff --exit-code -- src/SDL_image.sym src/SDL_image.exports
- name: Verify CMake configuration files
run: |
cmake -S cmake/test -B cmake_config_build \
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 327013d4..e9031ef2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -192,11 +192,14 @@ if(NOT ANDROID)
VERSION "${SO_VERSION}"
)
if(APPLE)
- cmake_minimum_required(VERSION 3.17...3.28)
set_target_properties(${sdl3_ttf_target_name} PROPERTIES
MACHO_COMPATIBILITY_VERSION "${DYLIB_COMPAT_VERSION}"
MACHO_CURRENT_VERSION "${DYLIB_CURRENT_VERSION}"
)
+ set_property(TARGET ${sdl3_ttf_target_name} APPEND PROPERTY LINK_DEPENDS
+ "${PROJECT_SOURCE_DIR}/src/SDL_ttf.exports")
+ target_link_options(${sdl3_ttf_target_name} PRIVATE
+ "SHELL:-Wl,-exported_symbols_list,${PROJECT_SOURCE_DIR}/src/SDL_ttf.exports")
endif()
endif()
if(SDLTTF_BUILD_SHARED_LIBS)
diff --git a/Xcode/SDL_ttf.xcodeproj/project.pbxproj b/Xcode/SDL_ttf.xcodeproj/project.pbxproj
index c631cf80..b91af340 100644
--- a/Xcode/SDL_ttf.xcodeproj/project.pbxproj
+++ b/Xcode/SDL_ttf.xcodeproj/project.pbxproj
@@ -657,6 +657,7 @@
"$(inherited)",
"$(PROJECT_DIR)",
);
+ EXPORTED_SYMBOLS_FILE = "$(SRCROOT)/../src/SDL_ttf.exports";
OTHER_LDFLAGS = "$(CONFIG_FRAMEWORK_LDFLAGS)";
};
name = Debug;
@@ -668,6 +669,7 @@
"$(inherited)",
"$(PROJECT_DIR)",
);
+ EXPORTED_SYMBOLS_FILE = "$(SRCROOT)/../src/SDL_ttf.exports";
OTHER_LDFLAGS = "$(CONFIG_FRAMEWORK_LDFLAGS)";
};
name = Release;
diff --git a/Xcode/pkg-support/resources/CMake/SDL3_ttfConfigVersion.cmake b/Xcode/pkg-support/resources/CMake/SDL3_ttfConfigVersion.cmake
index bc859e17..ab56f263 100644
--- a/Xcode/pkg-support/resources/CMake/SDL3_ttfConfigVersion.cmake
+++ b/Xcode/pkg-support/resources/CMake/SDL3_ttfConfigVersion.cmake
@@ -6,7 +6,7 @@
cmake_minimum_required(VERSION 3.12...4.0)
-# Find SDL_ttf.h.h
+# Find SDL_ttf.h.
set(_sdl_ttf_h_path "")
if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../Headers/SDL_ttf.h")
set(_sdl_ttf_h_path "${CMAKE_CURRENT_LIST_DIR}/../../Headers/SDL_ttf.h")
diff --git a/src/SDL_ttf.exports b/src/SDL_ttf.exports
new file mode 100644
index 00000000..89d01947
--- /dev/null
+++ b/src/SDL_ttf.exports
@@ -0,0 +1,119 @@
+# SDL3_ttf.dylib exports
+_TTF_AddFallbackFont
+_TTF_AppendTextString
+_TTF_ClearFallbackFonts
+_TTF_CloseFont
+_TTF_CopyFont
+_TTF_CreateGPUTextEngine
+_TTF_CreateGPUTextEngineWithProperties
+_TTF_CreateRendererTextEngine
+_TTF_CreateRendererTextEngineWithProperties
+_TTF_CreateSurfaceTextEngine
+_TTF_CreateText
+_TTF_DeleteTextString
+_TTF_DestroyGPUTextEngine
+_TTF_DestroyRendererTextEngine
+_TTF_DestroySurfaceTextEngine
+_TTF_DestroyText
+_TTF_DrawRendererText
+_TTF_DrawSurfaceText
+_TTF_FontHasGlyph
+_TTF_FontIsFixedWidth
+_TTF_FontIsScalable
+_TTF_GetFontAscent
+_TTF_GetFontDPI
+_TTF_GetFontDescent
+_TTF_GetFontDirection
+_TTF_GetFontFamilyName
+_TTF_GetFontGeneration
+_TTF_GetFontHeight
+_TTF_GetFontHinting
+_TTF_GetFontKerning
+_TTF_GetFontLineSkip
+_TTF_GetFontOutline
+_TTF_GetFontProperties
+_TTF_GetFontScript
+_TTF_GetFontSDF
+_TTF_GetFontSize
+_TTF_GetFontStyle
+_TTF_GetFontStyleName
+_TTF_GetFontWeight
+_TTF_GetFontWrapAlignment
+_TTF_GetFreeTypeVersion
+_TTF_GetGlyphImage
+_TTF_GetGlyphImageForIndex
+_TTF_GetGlyphKerning
+_TTF_GetGlyphMetrics
+_TTF_GetGlyphScript
+_TTF_GetGPUTextDrawData
+_TTF_GetGPUTextEngineWinding
+_TTF_GetHarfBuzzVersion
+_TTF_GetNextTextSubString
+_TTF_GetNumFontFaces
+_TTF_GetPreviousTextSubString
+_TTF_GetStringSize
+_TTF_GetStringSizeWrapped
+_TTF_GetTextColor
+_TTF_GetTextColorFloat
+_TTF_GetTextDirection
+_TTF_GetTextEngine
+_TTF_GetTextFont
+_TTF_GetTextPosition
+_TTF_GetTextProperties
+_TTF_GetTextScript
+_TTF_GetTextSize
+_TTF_GetTextSubString
+_TTF_GetTextSubStringForLine
+_TTF_GetTextSubStringForPoint
+_TTF_GetTextSubStringsForRange
+_TTF_GetTextWrapWidth
+_TTF_Init
+_TTF_InsertTextString
+_TTF_MeasureString
+_TTF_OpenFont
+_TTF_OpenFontIO
+_TTF_OpenFontWithProperties
+_TTF_Quit
+_TTF_RemoveFallbackFont
+_TTF_RenderGlyph_Blended
+_TTF_RenderGlyph_LCD
+_TTF_RenderGlyph_Shaded
+_TTF_RenderGlyph_Solid
+_TTF_RenderText_Blended
+_TTF_RenderText_Blended_Wrapped
+_TTF_RenderText_LCD
+_TTF_RenderText_LCD_Wrapped
+_TTF_RenderText_Shaded
+_TTF_RenderText_Shaded_Wrapped
+_TTF_RenderText_Solid
+_TTF_RenderText_Solid_Wrapped
+_TTF_SetFontDirection
+_TTF_SetFontHinting
+_TTF_SetFontKerning
+_TTF_SetFontLanguage
+_TTF_SetFontLineSkip
+_TTF_SetFontOutline
+_TTF_SetFontSDF
+_TTF_SetFontScript
+_TTF_SetFontSize
+_TTF_SetFontSizeDPI
+_TTF_SetFontStyle
+_TTF_SetFontWrapAlignment
+_TTF_SetGPUTextEngineWinding
+_TTF_SetTextColor
+_TTF_SetTextColorFloat
+_TTF_SetTextDirection
+_TTF_SetTextEngine
+_TTF_SetTextFont
+_TTF_SetTextPosition
+_TTF_SetTextScript
+_TTF_SetTextString
+_TTF_SetTextWrapWhitespaceVisible
+_TTF_SetTextWrapWidth
+_TTF_StringToTag
+_TTF_TagToString
+_TTF_TextWrapWhitespaceVisible
+_TTF_UpdateText
+_TTF_Version
+_TTF_WasInit
+# extra symbols go here (don't modify this line)
diff --git a/src/SDL_ttf.sym b/src/SDL_ttf.sym
index 564eaf36..f9b0c5b3 100644
--- a/src/SDL_ttf.sym
+++ b/src/SDL_ttf.sym
@@ -117,5 +117,6 @@ SDL3_ttf_0.0.0 {
TTF_UpdateText;
TTF_Version;
TTF_WasInit;
+ # extra symbols go here (don't modify this line)
local: *;
};
diff --git a/src/genexports.py b/src/genexports.py
new file mode 100755
index 00000000..a3d84267
--- /dev/null
+++ b/src/genexports.py
@@ -0,0 +1,574 @@
+#!/usr/bin/env python3
+
+# Simple DirectMedia Layer
+# Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
+#
+# This software is provided 'as-is', without any express or implied
+# warranty. In no event will the authors be held liable for any damages
+# arising from the use of this software.
+#
+# Permission is granted to anyone to use this software for any purpose,
+# including commercial applications, and to alter it and redistribute it
+# freely, subject to the following restrictions:
+#
+# 1. The origin of this software must not be misrepresented; you must not
+# claim that you wrote the original software. If you use this software
+# in a product, an acknowledgment in the product documentation would be
+# appreciated but is not required.
+# 2. Altered source versions must be plainly marked as such, and must not be
+# misrepresented as being the original software.
+# 3. This notice may not be removed or altered from any source distribution.
+
+# WHAT IS THIS?
+# When you add a public API to a SDL library, please run this script, make sure the
+# output looks sane (git diff, it adds to existing files), and commit it.
+# It keeps the export lists in sync with the API.
+#
+
+import argparse
+import dataclasses
+import json
+import logging
+import os
+from pathlib import Path
+import pprint
+import re
+
+RE_EXTERN_C = re.compile(r'.*extern[ "]*C[ "].*')
+RE_COMMENT_REMOVE_CONTENT = re.compile(r'\/\*.*\*/')
+RE_PARSING_FUNCTION = re.compile(r'(.*SDLCALL[^\(\)]*) ([a-zA-Z0-9_]+) *\((.*)\) *;.*')
+
+#eg:
+# void (SDLCALL *callback)(void*, int)
+# \1(\2)\3
+RE_PARSING_CALLBACK = re.compile(r'([^\(\)]*)\(([^\(\)]+)\)(.*)')
+
+
+logger = logging.getLogger(__name__)
+
+
+@dataclasses.dataclass(frozen=True)
+class SdlProjectSymbolProperties:
+ include_dir: Path
+ version_export_path: Path
+ macos_exports_path: Path
+ re_symbol: str
+
+ @classmethod
+ def from_root(cls, project_root: Path) -> "SdlProjectSymbolProperties":
+ wikiheaders_options = {}
+ with (project_root / ".wikiheaders-options").open("r", newline="\n") as f:
+ for line in f.readlines():
+ key, value = line.split("=", 1)
+ key, value = key.strip(), value.strip()
+ wikiheaders_options[key] = value
+ return cls(
+ include_dir=project_root / wikiheaders_options["incsubdir"],
+ version_export_path=project_root / "src" / (wikiheaders_options["projectfullname"] + ".sym"),
+ macos_exports_path=project_root / "src" / (wikiheaders_options["projectfullname"] + ".exports"),
+ re_symbol= wikiheaders_options["apiprefixregex"],
+ )
+
+@dataclasses.dataclass(frozen=True)
+class SdlProcedure:
+ retval: str
+ name: str
+ parameter: list[str]
+ parameter_name: list[str]
+ header: str
+ comment: str
+
+ @property
+ def variadic(self) -> bool:
+ return "..." in self.parameter
+
+
+def parse_header(header_path: Path) -> list[SdlProcedure]:
+ logger.debug("Parse header: %s", header_path)
+
+ header_procedures = []
+
+ parsing_function = False
+ current_func = ""
+ parsing_comment = False
+ current_comment = ""
+ ignore_wiki_documentation = False
+
+ with header_path.open() as f:
+ for line in f:
+
+ # Skip lines if we're in a wiki documentation block.
+ if ignore_wiki_documentation:
+ if line.startswith("#endif"):
+ ignore_wiki_documentation = False
+ continue
+
+ # Discard wiki documentations blocks.
+ if line.startswith("#ifdef SDL_WIKI_DOCUMENTATION_SECTION"):
+ ignore_wiki_documentation = True
+ continue
+
+ # Discard pre-processor directives ^#.*
+ if line.startswith("#"):
+ continue
+
+ # Discard "extern C" line
+ match = RE_EXTERN_C.match(line)
+ if match:
+ continue
+
+ # Remove one line comment // ...
+ # eg: extern SDL_DECLSPEC SDL_hid_device * SDLCALL SDL_hid_open_path(const char *path, int bExclusive /* = false */)
+ line = RE_COMMENT_REMOVE_CONTENT.sub('', line)
+
+ # Get the comment block /* ... */ across several lines
+ match_start = "/*" in line
+ match_end = "*/" in line
+ if match_start and match_end:
+ continue
+ if match_start:
+ parsing_comment = True
+ current_comment = line
+ continue
+ if match_end:
+ parsing_comment = False
+ current_comment += line
+ continue
+ if parsing_comment:
+ current_comment += line
+ continue
+
+ # Get the function prototype across several lines
+ if parsing_function:
+ # Append to the current function
+ current_func += " "
+ current_func += line.strip()
+ else:
+ # if is contains "extern", start grabbing
+ if "extern" not in line:
+ continue
+ # Start grabbing the new function
+ current_func = line.strip()
+ parsing_function = True
+
+ # If it contains ';', then the function is complete
+ if ";" not in current_func:
+ continue
+
+ # Got function/comment, reset vars
+ parsing_function = False
+ func = current_func
+ comment = current_comment
+ current_func = ""
+ current_comment = ""
+
+ # Discard if it doesn't contain 'SDLCALL'
+ if "SDLCALL" not in func:
+ logger.debug(" Discard, doesn't have SDLCALL: %r", func)
+ continue
+
+ # Discard if it contains 'SDLMAIN_DECLSPEC' (these are not SDL symbols).
+ if "SDLMAIN_DECLSPEC" in func:
+ logger.debug(" Discard, has SDLMAIN_DECLSPEC: %r", func)
+ continue
+
+ logger.debug("Raw data: %r", func)
+
+ # Replace unusual stuff...
+ func = func.replace(" SDL_PRINTF_VARARG_FUNC(1)", "")
+ func = func.replace(" SDL_PRINTF_VARARG_FUNC(2)", "")
+ func = func.replace(" SDL_PRINTF_VARARG_FUNC(3)", "")
+ func = func.replace(" SDL_PRINTF_VARARG_FUNC(4)", "")
+ func = func.replace(" SDL_PRINTF_VARARG_FUNCV(1)", "")
+ func = func.replace(" SDL_PRINTF_VARARG_FUNCV(2)", "")
+ func = func.replace(" SDL_PRINTF_VARARG_FUNCV(3)", "")
+ func = func.replace(" SDL_PRINTF_VARARG_FUNCV(4)", "")
+ func = func.replace(" SDL_WPRINTF_VARARG_FUNC(3)", "")
+ func = func.replace(" SDL_WPRINTF_VARARG_FUNCV(3)", "")
+ func = func.replace(" SDL_SCANF_VARARG_FUNC(2)", "")
+ func = func.replace(" SDL_SCANF_VARARG_FUNCV(2)", "")
+ func = func.replace(" SDL_ANALYZER_NORETURN", "")
+ func = func.replace(" SDL_MALLOC", "")
+ func = func.replace(" SDL_ALLOC_SIZE2(1, 2)", "")
+ func = func.replace(" SDL_ALLOC_SIZE(2)", "")
+ func = re.sub(r" SDL_ACQUIRE\(.*\)", "", func)
+ func = re.sub(r" SDL_ACQUIRE_SHARED\(.*\)", "", func)
+ func = re.sub(r" SDL_TRY_ACQUIRE\(.*\)", "", func)
+ func = re.sub(r" SDL_TRY_ACQUIRE_SHARED\(.*\)", "", func)
+ func = re.sub(r" SDL_RELEASE\(.*\)", "", func)
+ func = re.sub(r" SDL_RELEASE_SHARED\(.*\)", "", func)
+ func = re.sub(r" SDL_RELEASE_GENERIC\(.*\)", "", func)
+ func = re.sub(r"([ (),])(SDL_IN_BYTECAP\([^)]*\))", r"\1", func)
+ func = re.sub(r"([ (),])(SDL_OUT_BYTECAP\([^)]*\))", r"\1", func)
+ func = re.sub(r"([ (),])(SDL_INOUT_Z_CAP\([^)]*\))", r"\1", func)
+ func = re.sub(r"([ (),])(SDL_OUT_Z_CAP\([^)]*\))", r"\1", func)
+
+ # Should be a valid function here
+ match = RE_PARSING_FUNCTION.match(func)
+ if not match:
+ logger.error("Cannot parse: %s", func)
+ raise ValueError(func)
+
+ func_ret = match.group(1)
+ func_name = match.group(2)
+ func_params = match.group(3)
+
+ #
+ # Parse return value
+ #
+ func_ret = func_ret.replace('extern', ' ')
+ func_ret = func_ret.replace('SDLCALL', ' ')
+ func_ret = func_ret.replace('SDL_DECLSPEC', ' ')
+ func_ret, _ = re.subn('([ ]{2,})', ' ', func_ret)
+ # Remove trailing spaces in front of '*'
+ func_ret = func_ret.replace(' *', '*')
+ func_ret = func_ret.strip()
+
+ #
+ # Parse parameters
+ #
+ func_params = func_params.strip()
+ if func_params == "":
+ func_params = "void"
+
+ # Identify each function parameters with type and name
+ # (eventually there are callbacks of several parameters)
+ tmp = func_params.split(',')
+ tmp2 = []
+ param = ""
+ for t in tmp:
+ if param == "":
+ param = t
+ else:
+ param = param + "," + t
+ # Identify a callback or parameter when there is same count of '(' and ')'
+ if param.count('(') == param.count(')'):
+ tmp2.append(param.strip())
+ param = ""
+
+ # Process each parameters, separation name and type
+ func_param_type = []
+ func_param_name = []
+ for t in tmp2:
+ if t == "void":
+ func_param_type.append(t)
+ func_param_name.append("")
+ continue
+
+ if t == "...":
+ func_param_type.append(t)
+ func_param_name.append("")
+ continue
+
+ param_name = ""
+
+ # parameter is a callback
+ if '(' in t:
+ match = RE_PARSING_CALLBACK.match(t)
+ if not match:
+ logger.error("cannot parse callback: %s", t)
+ raise ValueError(t)
+ a = match.group(1).strip()
+ b = match.group(2).strip()
+ c = match.group(3).strip()
+
+ try:
+ (param_type, param_name) = b.rsplit('*', 1)
+ except:
+ param_type = t
+ param_name = "param_name_not_specified"
+
+ # bug rsplit ??
+ if param_name == "":
+ param_name = "param_name_not_specified"
+
+ # reconstruct a callback name for future parsing
+ func_param_type.append(a + " (" + param_type.strip() + " *REWRITE_NAME)" + c)
+ func_param_name.append(param_name.strip())
+
+ continue
+
+ # array like "char *buf[]"
+ has_array = False
+ if t.endswith("[]"):
+ t = t.replace("[]", "")
+ has_array = True
+
+ # pointer
+ if '*' in t:
+ try:
+ (param_type, param_name) = t.rsplit('*', 1)
+ except:
+ param_type = t
+ param_name = "param_name_not_specified"
+
+ # bug rsplit ??
+ if param_name == "":
+ param_name = "param_name_not_specified"
+
+ val = param_type.strip() + "*REWRITE_NAME"
+
+ # Remove trailing spaces in front of '*'
+ tmp = ""
+ while val != tmp:
+ tmp = val
+ val = val.replace(' ', ' ')
+ val = val.replace(' *', '*')
+ # first occurrence
+ val = val.replace('*', ' *', 1)
+ val = val.strip()
+
+ else: # non pointer
+ # cut-off last word on
+ try:
+ (param_type, param_name) = t.rsplit(' ', 1)
+ except:
+ param_type = t
+ param_name = "param_name_not_specified"
+
+ val = param_type.strip() + " REWRITE_NAME"
+
+ # set back array
+ if has_array:
+ val += "[]"
+
+ func_param_type.append(val)
+ func_param_name.append(param_name.strip())
+
+ new_proc = SdlProcedure(
+ retval=func_ret, # Return value type
+ name=func_name, # Function name
+ comment=comment, # Function comment
+ header=header_path.name, # Header file
+ parameter=func_param_type, # List of parameters (type + anonymized param name 'REWRITE_NAME')
+ parameter_name=func_param_name, # Real parameter name, or 'param_name_not_specified'
+ )
+
+ header_procedures.append(new_proc)
+
+ if logger.getEffectiveLevel() <= logging.DEBUG:
+ logger.debug("%s", pprint.pformat(new_proc))
+
+ return header_procedures
+
+
+# Dump API into a json file
+def full_API_json(path: Path, procedures: list[SdlProcedure]):
+ with path.open('w', newline='') as f:
+ json.dump([dataclasses.asdict(proc) for proc in procedures], f, indent=4, sort_keys=True)
+ logger.info("dump API to '%s'", path)
+
+
+class CallOnce:
+ def __init__(self, cb):
+ self._cb = cb
+ self._called = False
+ def __call__(self, *args, **kwargs):
+ if self._called:
+ return
+ self._called = True
+ self._cb(*args, **kwargs)
+
+
+# Check public function comments are correct
+def print_check_comment_header():
+ logger.warning("")
+ logger.warning("Please fix following warning(s):")
+ logger.warning("--------------------------------")
+
+
+def check_documentations(procedures: list[SdlProcedure]) -> None:
+
+ check_comment_header = CallOnce(print_check_comment_header)
+
+ warning_header_printed = False
+
+ # Check \param
+ for proc in procedures:
+ expected = len(proc.parameter)
+ if expected == 1:
+ if proc.parameter[0] == 'void':
+ expected = 0
+ count = proc.comment.count("\\param")
+ if count != expected:
+ # skip SDL_stdinc.h
+ if proc.header != 'SDL_stdinc.h':
+ # Warning mismatch \param and function prototype
+ check_comment_header()
+ logger.warning(" In file %s: function %s() has %d '\\param' but expected %d", proc.header, proc.name, count, expected)
+
+ # Warning check \param uses the correct parameter name
+ # skip SDL_stdinc.h
+ if proc.header != 'SDL_stdinc.h':
+ for n in proc.parameter_name:
+ if n != "" and "\\param " + n not in proc.comment and "\\param[out] " + n not in proc.comment:
+ check_comment_header()
+ logger.warning(" In file %s: function %s() missing '\\param %s'", proc.header, proc.name, n)
+
+ # Check \returns
+ for proc in procedures:
+ expected = 1
+ if proc.retval == 'void':
+ expected = 0
+
+ count = proc.comment.count("\\returns")
+ if count != expected:
+ # skip SDL_stdinc.h
+ if proc.header != 'SDL_stdinc.h':
+ # Warning mismatch \param and function prototype
+ check_comment_header()
+ logger.warning(" In file %s: function %s() has %d '\\returns' but expected %d" % (proc.header, proc.name, count, expected))
+
+ # Check \since
+ for proc in procedures:
+ expected = 1
+ count = proc.comment.count("\\since")
+ if count != expected:
+ # skip SDL_stdinc.h
+ if proc.header != 'SDL_stdinc.h':
+ # Warning mismatch \param and function prototype
+ check_comment_header()
+ logger.warning(" In file %s: function %s() has %d '\\since' but expected %d" % (proc.header, proc.name, count, expected))
+
+
+# Parse 'sdl_dynapi_procs_h' file to find existing functions
+def find_existing_proc_names(project_properties: SdlProjectSymbolProperties) -> set[str]:
+ versioned_symbols = set()
+ re_version_export_symbol = re.compile(r'\s*(' + project_properties.re_symbol + r"[a-zA-Z0-9_]+);\s*")
+ with project_properties.version_export_path.open() as f:
+ for line in f:
+ match = re_version_export_symbol.match(line)
+ if not match:
+ continue
+ existing_func = match.group(1)
+ versioned_symbols.add(existing_func)
+ logger.debug("symbols from version script: %r", versioned_symbols)
+
+ macos_symbols = set()
+ re_macos_export_symbol = re.compile(r'\s*_(' + project_properties.re_symbol + r"[a-zA-Z0-9_]+)\s*")
+ with project_properties.macos_exports_path.open() as f:
+ for line in f:
+ match = re_macos_export_symbol.match(line)
+ if not match:
+ continue
+ existing_func = match.group(1)
+ macos_symbols.add(existing_func)
+ logger.debug("symbols from macos exports file: %r", macos_symbols)
+
+ non_matching_symbols = versioned_symbols.difference(macos_symbols)
+ if non_matching_symbols:
+ logger.error("Following symbols do not match: %r", non_matching_symbols)
+ raise RuntimeError("Non-matching symbols", non_matching_symbols)
+ return versioned_symbols
+
+# Get list of SDL headers
+def get_header_list(project_properties: SdlProjectSymbolProperties) -> list[Path]:
+ ret = []
+
+ for f in project_properties.include_dir.iterdir():
+ # Only *.h files
+ if f.is_file() and f.suffix == ".h":
+ ret.append(f)
+ else:
+ logger.debug("Skip %s", f)
+
+ # Order headers for reproducible behavior
+ ret.sort()
+
+ return ret
+
+# Write the new API in files: _procs.h _overrides.h and .sym
+def add_dyn_api(proc: SdlProcedure, project_properties:SdlProjectSymbolProperties) -> None:
+ decl_args: list[str] = []
+ call_args = []
+ for i, argtype in enumerate(proc.parameter):
+ # Special case, void has no parameter name
+ if argtype == "void":
+ assert len(decl_args) == 0
+ assert len(proc.parameter) == 1
+ decl_args.append("void")
+ continue
+
+ # Var name: a, b, c, ...
+ varname = chr(ord('a') + i)
+
+ decl_args.append(argtype.replace("REWRITE_NAME", varname))
+ if argtype != "...":
+ call_args.append(varname)
+
+ macro_args = (
+ proc.retval,
+ proc.name,
+ "({})".format(",".join(decl_args)),
+ "({})".format(",".join(call_args)),
+ "" if proc.retval == "void" else "return",
+ )
+
+ # File: SDL_{image,mixer,net,ttf}.sym
+ #
+ # Add before "extra symbols go here" line
+ with project_properties.version_export_path.open(newline="\n") as f:
+ new_input = []
+ for line in f:
+ if "extra symbols go here" in line:
+ new_input.append(f" {proc.name};\n")
+ new_input.append(line)
+
+ with project_properties.version_export_path.open('w', newline='') as f:
+ for line in new_input:
+ f.write(line)
+
+ # File: SDL_{image,mixer,net,ttf}.exports
+ #
+ # Add before "extra symbols go here" line
+ with project_properties.macos_exports_path.open(newline="\n") as f:
+ new_input = []
+ for line in f:
+ if "extra symbols go here" in line:
+ new_input.append(f"_{proc.name}\n")
+ new_input.append(line)
+
+ with project_properties.macos_exports_path.open("w", newline="\n") as f:
+ for line in new_input:
+ f.write(line)
+
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.set_defaults(loglevel=logging.INFO)
+ parser.add_argument('--dump', nargs='?', default=None, const="sdl.json", metavar="JSON", help='output all API into a .json file')
+ parser.add_argument('--debug', action='store_const', const=logging.DEBUG, dest="loglevel", help='add debug traces')
+ args = parser.parse_args()
+
+ logging.basicConfig(level=args.loglevel, format='[%(levelname)s] %(message)s')
+
+ root = Path(__file__).resolve().parents[1]
+
+ project_properties = SdlProjectSymbolProperties.from_root(root)
+ logger.debug("project_properties=%r", project_properties)
+
+ # Get list of SDL headers
+ sdl_list_includes = get_header_list(project_properties)
+ procedures = []
+ for filename in sdl_list_includes:
+ header_procedures = parse_header(filename)
+ procedures.extend(header_procedures)
+
+ # Parse 'SDL_{image,mixer,ttf}.sym' file to find existing functions
+ existing_proc_names = find_existing_proc_names(project_properties)
+ for procedure in procedures:
+ if procedure.name not in existing_proc_names:
+ logger.info("NEW %s", procedure.name)
+ add_dyn_api(procedure, project_properties)
+
+ if args.dump:
+ # Dump API into a json file
+ full_API_json(path=Path(args.dump), procedures=procedures)
+
+ # Check comment formatting
+ check_documentations(procedures)
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())