SDL: Added infrastructure for renaming API functions

From b5a92406ebd6398e334c88303c2e6c83d3a3f806 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 22 Dec 2022 16:45:43 -0800
Subject: [PATCH] Added infrastructure for renaming API functions

You can rename APIs using rename.py and all the code and documentation will be updated, and entries will be added to WhatsNew.txt and docs/README-migration.md.
e.g.
rename.py SDL_foo.h function SDL_CreateFoo SDL_FooCreate

SDL_oldnames.h is included in the SDL header, and if you define SDL_ENABLE_OLD_NAMES, will redefine the old API functions to call the new ones, and if not, will define them as a symbol letting you what the new API function is.
---
 build-scripts/rename.py     | 234 ++++++++++++++++++++++++++++++++++++
 include/SDL3/SDL.h          |   3 +-
 include/SDL3/SDL_oldnames.h |  43 +++++++
 3 files changed, 279 insertions(+), 1 deletion(-)
 create mode 100755 build-scripts/rename.py
 create mode 100644 include/SDL3/SDL_oldnames.h

diff --git a/build-scripts/rename.py b/build-scripts/rename.py
new file mode 100755
index 000000000000..936a6474f6c3
--- /dev/null
+++ b/build-scripts/rename.py
@@ -0,0 +1,234 @@
+#!/usr/bin/env python3
+
+# WHAT IS THIS?
+#  This script renames symbols in the API, updating SDL_oldnames.h and
+#  adding documentation for the change.
+
+import argparse
+import os
+import pathlib
+import pprint
+import re
+
+
+SDL_ROOT = pathlib.Path(__file__).resolve().parents[1]
+
+SDL_INCLUDE_DIR = SDL_ROOT / "include/SDL3"
+
+
+def main():
+    # Check whether we can still modify the ABI
+    version_header = pathlib.Path( SDL_INCLUDE_DIR / "SDL_version.h" ).read_text()
+    if not re.search("SDL_MINOR_VERSION\s+[01]\s", version_header):
+        raise Exception("ABI is frozen, symbols cannot be renamed")
+
+    pattern = re.compile(r"\b%s\b" % args.oldname)
+
+    # Find the symbol in the headers
+    if pathlib.Path(args.header).is_file():
+        header = pathlib.Path(args.header)
+    else:
+        header = pathlib.Path(SDL_INCLUDE_DIR / args.header)
+
+    if not header.exists():
+        raise Exception("Couldn't find header %s" % header)
+
+    if not pattern.search(header.read_text()):
+        raise Exception("Couldn't find %s in %s" % (args.oldname, header))
+
+    # Replace the symbol in source code and documentation
+    for dir in ['src', 'test', 'include', 'docs']:
+        replace_symbol_recursive(SDL_ROOT / dir, pattern, args.newname)
+
+    add_symbol_to_oldnames(header.name, args.oldname, args.newname)
+    add_symbol_to_migration(header.name, args.type, args.oldname, args.newname)
+    add_symbol_to_whatsnew(args.type, args.oldname, args.newname)
+
+
+def replace_symbol_recursive(path, pattern, replacement):
+    for entry in path.glob("*"):
+        if entry.is_dir():
+            replace_symbol_recursive(entry, pattern, replacement)
+        elif not entry.name.endswith((".bmp", ".cur", ".dat", ".icns", ".png", ".strings", ".swp", ".wav")) and \
+             entry.name != "utf8.txt":
+            print("Processing %s" % entry)
+            with entry.open('r', encoding='UTF-8', newline='') as rfp:
+                contents = pattern.sub(replacement, rfp.read())
+                with entry.open('w', encoding='UTF-8', newline='') as wfp:
+                    wfp.write(contents)
+
+
+def add_line(lines, i, section):
+    lines.insert(i, section)
+    i += 1
+    return i
+
+
+def add_content(lines, i, content, add_trailing_line):
+    if lines[i - 1] == "":
+        lines[i - 1] = content
+    else:
+        i = add_line(lines, i, content)
+
+    if add_trailing_line:
+        i = add_line(lines, i, "")
+    return i
+
+
+def add_symbol_to_oldnames(header, oldname, newname):
+    file = (SDL_INCLUDE_DIR / "SDL_oldnames.h")
+    lines = file.read_text().splitlines()
+    mode = 0
+    i = 0
+    while i < len(lines):
+        line = lines[i]
+        if line == "#ifdef SDL_ENABLE_OLD_NAMES":
+            if mode == 0:
+                mode = 1
+                section = ("/* ##%s */" % header)
+                section_added = False
+                content = ("#define %s %s" % (oldname, newname))
+                content_added = False
+            else:
+                raise Exception("add_symbol_to_oldnames(): expected mode 0")
+        elif line == "#else /* !SDL_ENABLE_OLD_NAMES */":
+            if mode == 1:
+                if not section_added:
+                    i = add_line(lines, i, section)
+
+                if not content_added:
+                    i = add_content(lines, i, content, True)
+
+                mode = 2
+                section = ("/* ##%s */" % header)
+                section_added = False
+                content = ("#define %s %s_renamed_%s" % (oldname, oldname, newname))
+                content_added = False
+            else:
+                raise Exception("add_symbol_to_oldnames(): expected mode 1")
+        elif line == "#endif /* SDL_ENABLE_OLD_NAMES */":
+            if mode == 2:
+                if not section_added:
+                    i = add_line(lines, i, section)
+
+                if not content_added:
+                    i = add_content(lines, i, content, True)
+
+                mode = 3
+            else:
+                raise Exception("add_symbol_to_oldnames(): expected mode 2")
+        elif line != "" and (mode == 1 or mode == 2):
+            if line.startswith("/* ##"):
+                if section_added:
+                    if not content_added:
+                        i = add_content(lines, i, content, True)
+                        content_added = True
+                elif line == section:
+                    section_added = True
+                elif section < line:
+                    i = add_line(lines, i, section)
+                    section_added = True
+                    i = add_content(lines, i, content, True)
+                    content_added = True
+            elif line != "" and section_added and not content_added:
+                if content == line:
+                    content_added = True
+                elif content < line:
+                    i = add_content(lines, i, content, False)
+                    content_added = True
+        i += 1
+
+    file.write_text("\n".join(lines) + "\n")
+
+
+def add_symbol_to_migration(header, symbol_type, oldname, newname):
+    file = (SDL_ROOT / "docs/README-migration.md")
+    lines = file.read_text().splitlines()
+    section = ("## %s" % header)
+    section_added = False
+    note = ("The following %ss have been renamed:" % symbol_type)
+    note_added = False
+    content = ("* %s -> %s" % (oldname, newname))
+    content_added = False
+    mode = 0
+    i = 0
+    while i < len(lines):
+        line = lines[i]
+        if line.startswith("##") and line.endswith(".h"):
+            if line == section:
+                section_added = True
+            elif section < line:
+                break
+
+        elif section_added and not note_added:
+            if note == line:
+                note_added = True
+        elif note_added and not content_added:
+            if content == line:
+                content_added = True
+            elif line == "" or content < line:
+                i = add_line(lines, i, content)
+                content_added = True
+        i += 1
+
+    if not section_added:
+        i = add_line(lines, i, section)
+        i = add_line(lines, i, "")
+
+    if not note_added:
+        i = add_line(lines, i, note)
+
+    if not content_added:
+        i = add_content(lines, i, content, True)
+
+    file.write_text("\n".join(lines) + "\n")
+
+
+def add_symbol_to_whatsnew(symbol_type, oldname, newname):
+    file = (SDL_ROOT / "WhatsNew.txt")
+    lines = file.read_text().splitlines()
+    note = ("* The following %ss have been renamed:" % symbol_type)
+    note_added = False
+    content = ("    * %s -> %s" % (oldname, newname))
+    content_added = False
+    mode = 0
+    i = 0
+    while i < len(lines):
+        line = lines[i]
+        if not note_added:
+            if note == line:
+                note_added = True
+        elif not content_added:
+            if content == line:
+                content_added = True
+            elif not line.startswith("    *") or content < line:
+                i = add_line(lines, i, content)
+                content_added = True
+        i += 1
+
+    if not note_added:
+        i = add_line(lines, i, note)
+
+    if not content_added:
+        i = add_line(lines, i, content)
+
+    file.write_text("\r\n".join(lines) + "\r\n")
+
+
+if __name__ == '__main__':
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument('header');
+    parser.add_argument('type', choices=['function', 'enum']);
+    parser.add_argument('oldname');
+    parser.add_argument('newname');
+    args = parser.parse_args()
+
+    try:
+        main()
+    except Exception as e:
+        print(e)
+        exit(-1)
+
+    exit(0)
+
diff --git a/include/SDL3/SDL.h b/include/SDL3/SDL.h
index 9a96033e1d28..f1c0087600e9 100644
--- a/include/SDL3/SDL.h
+++ b/include/SDL3/SDL.h
@@ -46,6 +46,7 @@
 #include <SDL3/SDL_haptic.h>
 #include <SDL3/SDL_hidapi.h>
 #include <SDL3/SDL_hints.h>
+#include <SDL3/SDL_init.h>
 #include <SDL3/SDL_joystick.h>
 #include <SDL3/SDL_keyboard.h>
 #include <SDL3/SDL_keycode.h>
@@ -75,7 +76,7 @@
 #include <SDL3/SDL_touch.h>
 #include <SDL3/SDL_version.h>
 #include <SDL3/SDL_video.h>
-#include <SDL3/SDL_init.h>
+#include <SDL3/SDL_oldnames.h>
 
 #endif /* SDL_h_ */
 
diff --git a/include/SDL3/SDL_oldnames.h b/include/SDL3/SDL_oldnames.h
new file mode 100644
index 000000000000..2da92c12192c
--- /dev/null
+++ b/include/SDL3/SDL_oldnames.h
@@ -0,0 +1,43 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 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.
+*/
+
+/**
+ *  \file SDL_oldnames.h
+ *
+ *  Definitions to ease transition from SDL2 code
+ */
+
+#ifndef SDL_oldnames_h_
+#define SDL_oldnames_h_
+
+/* The new function names are recommended, but if you want to have the
+ * old names available while you are in the process of migrating code
+ * to SDL3, you can define `SDL_ENABLE_OLD_NAMES` in your project.
+ */
+#ifdef SDL_ENABLE_OLD_NAMES
+
+#else /* !SDL_ENABLE_OLD_NAMES */
+
+#endif /* SDL_ENABLE_OLD_NAMES */
+
+#endif /* SDL_oldnames_h_ */
+
+/* vi: set ts=4 sw=4 expandtab: */