SDL_mixer: release: update scripts

From 269c637263b8c63b053d72b4dde4129b944d6e57 Mon Sep 17 00:00:00 2001
From: Anonymous Maarten <[EMAIL REDACTED]>
Date: Wed, 22 Jan 2025 20:38:48 +0100
Subject: [PATCH] release: update scripts

---
 .github/workflows/release.yml                 |  37 ++-
 CMakeLists.txt                                |   8 +-
 VisualC/native_midi/native_midi.vcxproj       |   8 +-
 VisualC/timidity/timidity.vcxproj             |   8 +-
 .../resources/CMake/SDL3_mixerConfig.cmake    |   2 +-
 .../cmake/SDL3_mixer/SDL3_mixerConfig.cmake   |   2 +-
 .../SDL3_mixer/SDL3_mixerConfigVersion.cmake  |   2 +-
 Xcode/xmp/xmp.xcodeproj/project.pbxproj       |   4 +-
 build-scripts/build-release.py                | 214 ++++++++++++++----
 .../android/{INSTALL.md.in => README.md.in}   |  24 +-
 .../android/{ => aar}/__main__.py.in          |   0
 .../{ => aar}/cmake/SDL3_mixerConfig.cmake    |  24 +-
 .../cmake/SDL3_mixerConfigVersion.cmake.in    |   0
 .../android/{ => aar}/description.json.in     |   0
 .../msvc/cmake/SDL3_mixerConfig.cmake.in      |   2 +-
 build-scripts/release-info.json               |  18 +-
 cmake/test/CMakeLists.txt                     |   2 +-
 17 files changed, 260 insertions(+), 95 deletions(-)
 rename build-scripts/pkg-support/android/{INSTALL.md.in => README.md.in} (65%)
 rename build-scripts/pkg-support/android/{ => aar}/__main__.py.in (100%)
 rename build-scripts/pkg-support/android/{ => aar}/cmake/SDL3_mixerConfig.cmake (83%)
 rename build-scripts/pkg-support/android/{ => aar}/cmake/SDL3_mixerConfigVersion.cmake.in (100%)
 rename build-scripts/pkg-support/android/{ => aar}/description.json.in (100%)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c42905a6..3e5fff72 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -51,6 +51,14 @@ jobs:
         with:
           name: sources
           path: '${{ github.workspace}}/dist'
+      - name: 'Generate summary'
+        run: |
+          echo "Run the following commands to download all artifacts:" >> $GITHUB_STEP_SUMMARY
+          echo '```' >> $GITHUB_STEP_SUMMARY
+          echo "mkdir -p /tmp/${{ steps.releaser.outputs.project }}-${{ steps.releaser.outputs.version }}" >> $GITHUB_STEP_SUMMARY
+          echo "cd /tmp/${{ steps.releaser.outputs.project }}-${{ steps.releaser.outputs.version }}" >> $GITHUB_STEP_SUMMARY
+          echo "gh run -R ${{ github.repository }} download ${{ github.run_id }}" >> $GITHUB_STEP_SUMMARY
+          echo '```' >> $GITHUB_STEP_SUMMARY
 
   linux-verify:
     needs: [src]
@@ -104,6 +112,22 @@ jobs:
             --root "${{ steps.tar.outputs.path }}" \
             --github \
             --debug
+      - name: 'Install Linux dependencies'
+        run: |
+          sudo apt-get update -y
+          sudo apt-get install -y \
+            gnome-desktop-testing libasound2-dev libpulse-dev libaudio-dev libjack-dev libsndio-dev \
+            libusb-1.0-0-dev libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev \
+            libxss-dev libwayland-dev libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
+            libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev \
+            libflac-dev \
+            fluidsynth libfluidsynth-dev \
+            libgme-dev \
+            libmpg123-dev \
+            libopusfile-dev \
+            libvorbis-dev \
+            libxmp-dev \
+            libwavpack-dev
       - name: 'Extract dependencies, build and install them'
         id: deps-build
         run: |
@@ -235,7 +259,7 @@ jobs:
           echo "mount-point=${mount_point}">>$GITHUB_OUTPUT
       - name: 'Verify presence of optional frameworks'
         run: |
-          OPTIONAL_FRAMEWORKS="FLAC gme mpg123 ogg opus vorbis wavpack xmp"
+          OPTIONAL_FRAMEWORKS="gme opus wavpack xmp"
           rc=0
           for opt in $OPTIONAL_FRAMEWORKS; do
             fw_path="${{ steps.mount.outputs.mount-point }}/optional/${opt}.xcframework"
@@ -695,7 +719,9 @@ jobs:
       - name: 'Extract Android SDK from AAR'
         id: sdk
         run: |
-          python "${{ github.workspace }}/${{ needs.android.outputs.android-aar }}" -o /tmp/deps-android
+          unzip -o "${{ github.workspace }}/${{ needs.android.outputs.android-aar }}"
+          python "${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}.aar" -o /tmp/SDL3_mixer-android
+          echo "prefix=/tmp/SDL3_mixer-android" >>$GITHUB_OUTPUT
       - name: 'Download dependencies'
         id: deps
         env:
@@ -710,7 +736,9 @@ jobs:
       - name: 'Extract dependencies'
         id: deps-extract
         run: |
-          python "${{ steps.deps.outputs.dep-path }}/SDL3-${{ steps.deps.outputs.dep-sdl-version }}.aar" -o /tmp/deps-android
+          unzip -o "${{ steps.deps.outputs.dep-path }}/SDL3-devel-${{ steps.deps.outputs.dep-sdl-version }}-android.zip"
+          python "SDL3-${{ steps.deps.outputs.dep-sdl-version }}.aar" -o /tmp/SDL3-android
+          echo "sdl3-prefix=/tmp/SDL3-android" >>$GITHUB_OUTPUT
       - name: 'Install ninja'
         run: |
           sudo apt-get update -y
@@ -724,10 +752,9 @@ jobs:
               -GNinja \
               -DTEST_FULL=TRUE \
               -DTEST_STATIC=FALSE \
-              -DCMAKE_PREFIX_PATH="/tmp/deps-android" \
+              -DCMAKE_PREFIX_PATH="${{ steps.sdk.outputs.prefix }};${{ steps.deps-extract.outputs.sdl3-prefix }}" \
               -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake \
               -DANDROID_ABI=${android_abi} \
-              -Werror=dev \
               -DCMAKE_BUILD_TYPE=Release \
               -B "${android_abi}"
             echo "Building ${android_abi}..."
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ed1b20be..9c4ccaa7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.16)
+cmake_minimum_required(VERSION 3.16...3.28)
 
 if(NOT DEFINED CMAKE_BUILD_TYPE)
   set(cmake_build_type_undefined 1)
@@ -321,7 +321,7 @@ if(NOT ANDROID)
         VERSION "${SO_VERSION}"
     )
     if(APPLE)
-        cmake_minimum_required(VERSION 3.17)
+        cmake_minimum_required(VERSION 3.17...3.28)
         set_target_properties(${sdl3_mixer_target_name} PROPERTIES
             MACHO_COMPATIBILITY_VERSION "${DYLIB_COMPAT_VERSION}"
             MACHO_CURRENT_VERSION "${DYLIB_CURRENT_VERSION}"
@@ -702,7 +702,9 @@ if(SDLMIXER_GME)
         sdl_check_project_in_subfolder(external/libgme libgme SDLMIXER_VENDORED)
         enable_language(CXX)
         add_subdirectory(external/libgme EXCLUDE_FROM_ALL)
-        add_library(gme::gme ALIAS gme)
+        if(NOT TARGET gme::gme)
+            add_library(gme::gme ALIAS gme)
+        endif()
         if(SDLMIXER_GME_SHARED OR NOT SDLMIXER_BUILD_SHARED_LIBS)
             list(APPEND INSTALL_EXTRA_TARGETS gme)
         endif()
diff --git a/VisualC/native_midi/native_midi.vcxproj b/VisualC/native_midi/native_midi.vcxproj
index 6ef2a1ec..2c03554a 100644
--- a/VisualC/native_midi/native_midi.vcxproj
+++ b/VisualC/native_midi/native_midi.vcxproj
@@ -102,7 +102,7 @@
     <ClCompile>
       <AdditionalIncludeDirectories>..\..\include;..\..\src\codecs\native_midi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
-      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
       <WarningLevel>Level3</WarningLevel>
       <EnableEnhancedInstructionSet>StreamingSIMDExtensions</EnableEnhancedInstructionSet>
     </ClCompile>
@@ -116,7 +116,7 @@
       <Optimization>Disabled</Optimization>
       <AdditionalIncludeDirectories>..\..\include;..\..\src\codecs\native_midi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
-      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
       <WarningLevel>Level3</WarningLevel>
       <DebugInformationFormat>OldStyle</DebugInformationFormat>
       <EnableEnhancedInstructionSet>StreamingSIMDExtensions</EnableEnhancedInstructionSet>
@@ -133,7 +133,7 @@
     <ClCompile>
       <AdditionalIncludeDirectories>..\..\include;..\..\src\codecs\native_midi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
-      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
       <WarningLevel>Level3</WarningLevel>
       <EnableEnhancedInstructionSet>StreamingSIMDExtensions</EnableEnhancedInstructionSet>
     </ClCompile>
@@ -150,7 +150,7 @@
       <Optimization>Disabled</Optimization>
       <AdditionalIncludeDirectories>..\..\include;..\..\src\codecs\native_midi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
-      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
       <WarningLevel>Level3</WarningLevel>
       <DebugInformationFormat>OldStyle</DebugInformationFormat>
       <EnableEnhancedInstructionSet>StreamingSIMDExtensions</EnableEnhancedInstructionSet>
diff --git a/VisualC/timidity/timidity.vcxproj b/VisualC/timidity/timidity.vcxproj
index 20eed668..3c4d695f 100644
--- a/VisualC/timidity/timidity.vcxproj
+++ b/VisualC/timidity/timidity.vcxproj
@@ -126,7 +126,7 @@
     <ClCompile>
       <AdditionalIncludeDirectories>$(ProjectDir)..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
-      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
       <WarningLevel>Level3</WarningLevel>
       <EnableEnhancedInstructionSet>StreamingSIMDExtensions</EnableEnhancedInstructionSet>
     </ClCompile>
@@ -140,7 +140,7 @@
       <Optimization>Disabled</Optimization>
       <AdditionalIncludeDirectories>$(ProjectDir)..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
-      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
       <WarningLevel>Level3</WarningLevel>
       <DebugInformationFormat>OldStyle</DebugInformationFormat>
       <EnableEnhancedInstructionSet>StreamingSIMDExtensions</EnableEnhancedInstructionSet>
@@ -157,7 +157,7 @@
     <ClCompile>
       <AdditionalIncludeDirectories>$(ProjectDir)..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
-      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
       <WarningLevel>Level3</WarningLevel>
       <EnableEnhancedInstructionSet>StreamingSIMDExtensions</EnableEnhancedInstructionSet>
     </ClCompile>
@@ -174,7 +174,7 @@
       <Optimization>Disabled</Optimization>
       <AdditionalIncludeDirectories>$(ProjectDir)..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
-      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
       <WarningLevel>Level3</WarningLevel>
       <DebugInformationFormat>OldStyle</DebugInformationFormat>
       <EnableEnhancedInstructionSet>StreamingSIMDExtensions</EnableEnhancedInstructionSet>
diff --git a/Xcode/pkg-support/resources/CMake/SDL3_mixerConfig.cmake b/Xcode/pkg-support/resources/CMake/SDL3_mixerConfig.cmake
index 2194d704..2fc55846 100644
--- a/Xcode/pkg-support/resources/CMake/SDL3_mixerConfig.cmake
+++ b/Xcode/pkg-support/resources/CMake/SDL3_mixerConfig.cmake
@@ -2,7 +2,7 @@
 # This file is meant to be placed in Resources/CMake of a SDL3_mixer framework
 
 # INTERFACE_LINK_OPTIONS needs CMake 3.12
-cmake_minimum_required(VERSION 3.12)
+cmake_minimum_required(VERSION 3.12...3.28)
 
 include(FeatureSummary)
 set_package_properties(SDL3_mixer PROPERTIES
diff --git a/Xcode/pkg-support/share/cmake/SDL3_mixer/SDL3_mixerConfig.cmake b/Xcode/pkg-support/share/cmake/SDL3_mixer/SDL3_mixerConfig.cmake
index b524e4b8..d6474c3d 100644
--- a/Xcode/pkg-support/share/cmake/SDL3_mixer/SDL3_mixerConfig.cmake
+++ b/Xcode/pkg-support/share/cmake/SDL3_mixer/SDL3_mixerConfig.cmake
@@ -2,7 +2,7 @@
 # This file is meant to be placed in share/cmake/SDL3_mixer, next to SDL3_mixer.xcframework
 
 # INTERFACE_LINK_OPTIONS needs CMake 3.12
-cmake_minimum_required(VERSION 3.12)
+cmake_minimum_required(VERSION 3.12...3.28)
 
 include(FeatureSummary)
 set_package_properties(SDL3_mixer PROPERTIES
diff --git a/Xcode/pkg-support/share/cmake/SDL3_mixer/SDL3_mixerConfigVersion.cmake b/Xcode/pkg-support/share/cmake/SDL3_mixer/SDL3_mixerConfigVersion.cmake
index 71c45bf1..9449e6f1 100644
--- a/Xcode/pkg-support/share/cmake/SDL3_mixer/SDL3_mixerConfigVersion.cmake
+++ b/Xcode/pkg-support/share/cmake/SDL3_mixer/SDL3_mixerConfigVersion.cmake
@@ -3,7 +3,7 @@
 # SDL CMake version configuration file:
 # This file is meant to be placed in share/cmake/SDL3_mixer, next to SDL3_mixer.xcframework
 
-cmake_minimum_required(VERSION 3.12)
+cmake_minimum_required(VERSION 3.12...3.28)
 
 get_filename_component(_sdl3_mixer_xcframework_parent_path "${CMAKE_CURRENT_LIST_DIR}" REALPATH)                    # /share/cmake/SDL3_mixer/
 get_filename_component(_sdl3_mixer_xcframework_parent_path "${_sdl3_mixer_xcframework_parent_path}" REALPATH)       # /share/cmake/SDL3_mixer/
diff --git a/Xcode/xmp/xmp.xcodeproj/project.pbxproj b/Xcode/xmp/xmp.xcodeproj/project.pbxproj
index 52a58b18..6557f0ab 100644
--- a/Xcode/xmp/xmp.xcodeproj/project.pbxproj
+++ b/Xcode/xmp/xmp.xcodeproj/project.pbxproj
@@ -199,8 +199,8 @@
 		F33AA32D2881F70D00F836D8 /* it_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = it_load.c; path = ../../external/libxmp/src/loaders/it_load.c; sourceTree = "<group>"; };
 		F33AA32E2881F70D00F836D8 /* xm_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = xm_load.c; path = ../../external/libxmp/src/loaders/xm_load.c; sourceTree = "<group>"; };
 		F33AA3302881F70D00F836D8 /* s3m_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = s3m_load.c; path = ../../external/libxmp/src/loaders/s3m_load.c; sourceTree = "<group>"; };
-		F341229E2D406C2000D6C2B7 /* rng.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = rng.c; path = /Users/valve/projects/SDL_mixer/external/libxmp/src/rng.c; sourceTree = "<absolute>"; };
-		F34122A02D406C6500D6C2B7 /* flow.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = flow.c; path = /Users/valve/projects/SDL_mixer/external/libxmp/src/flow.c; sourceTree = "<absolute>"; };
+		F341229E2D406C2000D6C2B7 /* rng.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = rng.c; path = ../../external/libxmp/src/rng.c; sourceTree = "<absolute>"; };
+		F34122A02D406C6500D6C2B7 /* flow.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = flow.c; path = ../../external/libxmp/src/flow.c; sourceTree = "<absolute>"; };
 		F3968D85281FBB1900661875 /* xmp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = xmp.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		F3E29CE52881FB370006D108 /* README */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = README; path = ../../external/libxmp/README; sourceTree = "<group>"; };
 		F3E29CE72881FB610006D108 /* format.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = format.c; path = ../../external/libxmp/src/format.c; sourceTree = "<group>"; };
diff --git a/build-scripts/build-release.py b/build-scripts/build-release.py
index fe4db21f..f3faa06c 100755
--- a/build-scripts/build-release.py
+++ b/build-scripts/build-release.py
@@ -330,6 +330,10 @@ def configure_text(text: str, context: dict[str, str]) -> str:
     return text
 
 
+def configure_text_list(text_list: list[str], context: dict[str, str]) -> list[str]:
+    return [configure_text(text=e, context=context) for e in text_list]
+
+
 class ArchiveFileTree:
     def __init__(self):
         self._tree: dict[str, NodeInArchive] = {}
@@ -337,6 +341,12 @@ def __init__(self):
     def add_file(self, file: NodeInArchive):
         self._tree[file.arcpath] = file
 
+    def __iter__(self) -> typing.Iterable[NodeInArchive]:
+        yield from self._tree.values()
+
+    def __contains__(self, value: str) -> bool:
+        return value in self._tree
+
     def get_latest_mod_time(self) -> datetime.datetime:
         return max(item.time for item in self._tree.values() if item.time)
 
@@ -345,13 +355,12 @@ def add_to_archiver(self, archive_base: str, archiver: Archiver):
         added_files = dict()
 
         def calculate_symlink_target(s: NodeInArchive) -> str:
-            dest_dir = os.path.dirname(s.path)
+            dest_dir = os.path.dirname(s.arcpath)
             if dest_dir:
                 dest_dir += "/"
             target = dest_dir + s.symtarget
             while True:
                 new_target, n = re.subn(r"([^/]+/+[.]{2}/)", "", target)
-                print(f"{target=} {new_target=}")
                 target = new_target
                 if not n:
                     break
@@ -359,12 +368,15 @@ def calculate_symlink_target(s: NodeInArchive) -> str:
 
         # Add files in first pass
         for arcpath, node in self._tree.items():
+            assert node is not None, f"{arcpath} -> node"
             if node.data is not None:
                 archiver.add_file_data(arcpath=arc_join(archive_base, arcpath), data=node.data, time=node.time, mode=node.mode)
-                added_files[node.path] = node
+                assert node.arcpath is not None, f"{node=}"
+                added_files[node.arcpath] = node
             elif node.path is not None:
                 archiver.add_file_path(arcpath=arc_join(archive_base, arcpath), path=node.path)
-                added_files[node.path] = node
+                assert node.arcpath is not None, f"{node=}"
+                added_files[node.arcpath] = node
             elif node.symtarget is not None:
                 remaining_symlinks.add(node)
             elif node.directory:
@@ -372,6 +384,8 @@ def calculate_symlink_target(s: NodeInArchive) -> str:
             else:
                 raise ValueError(f"Invalid Archive Node: {repr(node)}")
 
+        assert None not in added_files
+
         # Resolve symlinks in second pass: zipfile does not support symlinks, so add files to zip archive
         while True:
             if not remaining_symlinks:
@@ -382,18 +396,18 @@ def calculate_symlink_target(s: NodeInArchive) -> str:
                 symlink_files_for_zip = {}
                 symlink_target_path = calculate_symlink_target(symlink)
                 if symlink_target_path in added_files:
-                    symlink_files_for_zip[symlink.path] = added_files[symlink_target_path]
+                    symlink_files_for_zip[symlink.arcpath] = added_files[symlink_target_path]
                 else:
                     symlink_target_path_slash = symlink_target_path + "/"
                     for added_file in added_files:
                         if added_file.startswith(symlink_target_path_slash):
-                            path_in_symlink = symlink.path + "/" + added_file.removeprefix(symlink_target_path_slash)
+                            path_in_symlink = symlink.arcpath + "/" + added_file.removeprefix(symlink_target_path_slash)
                             symlink_files_for_zip[path_in_symlink] = added_files[added_file]
                 if symlink_files_for_zip:
                     symlinks_this_time.add(symlink)
                     extra_added_files.update(symlink_files_for_zip)
                     files_for_zip = [{"arcpath": f"{archive_base}/{sym_path}", "data": sym_info.data, "mode": sym_info.mode} for sym_path, sym_info in symlink_files_for_zip.items()]
-                    archiver.add_symlink(arcpath=f"{archive_base}/{symlink.path}", target=symlink.symtarget, time=symlink.time, files_for_zip=files_for_zip)
+                    archiver.add_symlink(arcpath=f"{archive_base}/{symlink.arcpath}", target=symlink.symtarget, time=symlink.time, files_for_zip=files_for_zip)
             # if not symlinks_this_time:
             #     logger.info("files added: %r", set(path for path in added_files.keys()))
             assert symlinks_this_time, f"No targets found for symlinks: {remaining_symlinks}"
@@ -520,6 +534,15 @@ def _get_file_times(self, paths: tuple[str, ...]) -> dict[str, datetime.datetime
         return path_times
 
 
+class AndroidApiVersion:
+    def __init__(self, name: str, ints: tuple[int, ...]):
+        self.name = name
+        self.ints = ints
+
+    def __repr__(self) -> str:
+        return f"<{self.name} ({'.'.join(str(v) for v in self.ints)})>"
+
+
 class Releaser:
     def __init__(self, release_info: dict, commit: str, revision: str, root: Path, dist_path: Path, section_printer: SectionPrinter, executer: Executer, cmake_generator: str, deps_path: Path, overwrite: bool, github: bool, fast: bool):
         self.release_info = release_info
@@ -547,6 +570,7 @@ def get_context(self, extra_context: typing.Optional[dict[str, str]]=None) -> di
             "PROJECT_VERSION": self.version,
             "PROJECT_COMMIT": self.commit,
             "PROJECT_REVISION": self.revision,
+            "PROJECT_ROOT": str(self.root),
         }
         if extra_context:
             ctx.update(extra_context)
@@ -579,12 +603,22 @@ def _external_repo_path_filter(cls, path: str) -> bool:
     def create_source_archives(self) -> None:
         source_collector = SourceCollector(root=self.root, commit=self.commit, executer=self.executer, filter=self._path_filter)
         print(f"Collecting sources of {self.project}...")
-        archive_tree = source_collector.get_archive_file_tree()
+        archive_tree: ArchiveFileTree = source_collector.get_archive_file_tree()
         latest_mod_time = archive_tree.get_latest_mod_time()
         archive_tree.add_file(NodeInArchive.from_text(arcpath=REVISION_TXT, text=f"{self.revision}\n", time=latest_mod_time))
         archive_tree.add_file(NodeInArchive.from_text(arcpath=f"{GIT_HASH_FILENAME}", text=f"{self.commit}\n", time=latest_mod_time))
         archive_tree.add_file_mapping(arc_dir="", file_mapping=self.release_info["source"].get("files", {}), file_mapping_root=self.root, context=self.get_context(), time=latest_mod_time)
 
+        if "Makefile.am" in archive_tree:
+            patched_time = latest_mod_time + datetime.timedelta(minutes=1)
+            print(f"Makefile.am detected -> touching aclocal.m4, */Makefile.in, configure")
+            for node_data in archive_tree:
+                arc_name = os.path.basename(node_data.arcpath)
+                arc_name_we, arc_name_ext = os.path.splitext(arc_name)
+                if arc_name in ("aclocal.m4", "configure", "Makefile.in"):
+                    print(f"Bumping time of {node_data.arcpath}")
+                    node_data.time = patched_time
+
         archive_base = f"{self.project}-{self.version}"
         zip_path = self.dist_path / f"{archive_base}.zip"
         tgz_path = self.dist_path / f"{archive_base}.tar.gz"
@@ -752,24 +786,38 @@ def extract_filter(member: tarfile.TarInfo, path: str, /):
                 install_path = build_parent_dir / f"install-{triplet}"
                 shutil.rmtree(install_path, ignore_errors=True)
                 build_path.mkdir(parents=True, exist_ok=True)
+                context = self.get_context({
+                    "ARCH": arch,
+                    "DEP_PREFIX": str(mingw_deps_path / triplet),
+                })
+                extra_args = configure_text_list(text_list=self.release_info["mingw"]["autotools"]["args"], context=context)
+
                 with self.section_printer.group(f"Configuring MinGW {triplet} (autotools)"):
-                    extra_args = [arg.replace("@DEP_PREFIX@", str(mingw_deps_path / triplet)) for arg in self.release_info["mingw"]["autotools"]["args"]]
                     assert "@" not in " ".join(extra_args), f"@ should not be present in extra arguments ({extra_args})"
                     self.executer.run([
                         self.root / "configure",
                         f"--prefix={install_path}",
-                        f"--includedir={install_path}/include",
-                        f"--libdir={install_path}/lib",
-                        f"--bindir={install_path}/bin",
+                        f"--includedir=${{prefix}}/include",
+                        f"--libdir=${{prefix}}/lib",
+                        f"--bindir=${{prefix}}/bin",
                         f"--host={triplet}",
                         f"--build=x86_64-none-linux-gnu",
+                        "CFLAGS=-O2",
+                        "CXXFLAGS=-O2",
+                        "LDFLAGS=-Wl,-s",
                     ] + extra_args, cwd=build_path, env=new_env)
                 with self.section_printer.group(f"Build MinGW {triplet} (autotools)"):
                     self.executer.run(["make", f"-j{self.cpu_count}"], cwd=build_path, env=new_env)
                 with self.section_printer.group(f"Install MinGW {triplet} (autotools)"):
                     self.executer.run(["make", "install"], cwd=build_path, env=new_env)
                 self.verify_mingw_library(triplet=ARCH_TO_TRIPLET[arch], path=install_path / "bin" / f"{self.project}.dll")
-                archive_file_tree.add_directory_tree(arc_dir=arc_join(arc_root, triplet), path=install_path)
+                archive_file_tree.add_directory_tree(arc_dir=arc_join(arc_root, triplet), path=install_path, time=self.arc_time)
+
+                print("Recording arch-dependent extra files for MinGW development archive ...")
+                extra_context = {
+                    "TRIPLET": ARCH_TO_TRIPLET[arch],
+                }
+                archive_file_tree.add_file_mapping(arc_dir=arc_root, file_mapping=self.release_info["mingw"]["autotools"].get("files", {}), file_mapping_root=self.root, context=self.get_context(extra_context=extra_context), time=self.arc_time)
 
         if "cmake" in self.release_info["mingw"]:
             assert self.release_info["mingw"]["cmake"]["shared-static"] in ("args", "both")
@@ -782,6 +830,12 @@ def extract_filter(member: tarfile.TarInfo, path: str, /):
                 assert arch not in mingw_archs
                 mingw_archs.add(arch)
 
+                context = self.get_context({
+                    "ARCH": arch,
+                    "DEP_PREFIX": str(mingw_deps_path / triplet),
+                })
+                extra_args = configure_text_list(text_list=self.release_info["mingw"]["cmake"]["args"], context=context)
+
                 build_path = build_parent_dir / f"build-{triplet}"
                 install_path = build_parent_dir / f"install-{triplet}"
                 shutil.rmtree(install_path, ignore_errors=True)
@@ -792,7 +846,6 @@ def extract_filter(member: tarfile.TarInfo, path: str, /):
                     args_for_shared_static = (["-DBUILD_SHARED_LIBS=ON"], ["-DBUILD_SHARED_LIBS=OFF"])
                 for arg_for_shared_static in args_for_shared_static:
                     with self.section_printer.group(f"Configuring MinGW {triplet} (CMake)"):
-                        extra_args = [arg.replace("@DEP_PREFIX@", str(mingw_deps_path / triplet)) for arg in self.release_info["mingw"]["cmake"]["args"]]
                         assert "@" not in " ".join(extra_args), f"@ should not be present in extra arguments ({extra_args})"
                         self.executer.run([
                             f"cmake",
@@ -816,6 +869,13 @@ def extract_filter(member: tarfile.TarInfo, path: str, /):
                 self.verify_mingw_library(triplet=ARCH_TO_TRIPLET[arch], path=install_path / "bin" / f"{self.project}.dll")
                 archive_file_tree.add_directory_tree(arc_dir=arc_join(arc_root, triplet), path=install_path, time=self.arc_time)
 
+                print("Recording arch-dependent extra files for MinGW development archive ...")
+                extra_context = {
+                    "TRIPLET": ARCH_TO_TRIPLET[arch],
+                }
+                archive_file_tree.add_file_mapping(arc_dir=arc_root, file_mapping=self.release_info["mingw"]["cmake"].get("files", {}), file_mapping_root=self.root, context=self.get_context(extra_context=extra_context), time=self.arc_time)
+                print("... done")
+
         print("Recording extra files for MinGW development archive ...")
         archive_file_tree.add_file_mapping(arc_dir=arc_root, file_mapping=self.release_info["mingw"].get("files", {}), file_mapping_root=self.root, context=self.get_context(), time=self.arc_time)
         print("... done")
@@ -834,22 +894,25 @@ def extract_filter(member: tarfile.TarInfo, path: str, /):
         self.artifacts["mingw-devel-tar-gz"] = tgz_path
         self.artifacts["mingw-devel-tar-xz"] = txz_path
 
-    def _detect_android_api(self, android_home: str) -> typing.Optional[int]:
+    def _detect_android_api(self, android_home: str) -> typing.Optional[AndroidApiVersion]:
         platform_dirs = list(Path(p) for p in glob.glob(f"{android_home}/platforms/android-*"))
-        re_platform = re.compile("android-([0-9]+)")
-        platform_versions = []
+        re_platform = re.compile("^android-([0-9]+)(?:-ext([0-9]+))?$")
+        platform_versions: list[AndroidApiVersion] = []
         for platform_dir in platform_dirs:
             logger.debug("Found Android Platform SDK: %s", platform_dir)
+            if not (platform_dir / "android.jar").is_file():
+                logger.debug("Skipping SDK, missing android.jar")
+                continue
             if m:= re_platform.match(platform_dir.name):
-                platform_versions.append(int(m.group(1)))
-        platform_versions.sort()
+                platform_versions.append(AndroidApiVersion(name=platform_dir.name, ints=(int(m.group(1)), int(m.group(2) or 0))))
+        platform_versions.sort(key=lambda v: v.ints)
         logger.info("Available platform versions: %s", platform_versions)
-        platform_versions = list(filter(lambda v: v >= self._android_api_minimum, platform_versions))
-        logger.info("Valid platform versions (>=%d): %s", self._android_api_minimum, platform_versions)
+        platform_versions = list(filter(lambda v: v.ints >= self._android_api_minimum.ints, platform_versions))
+        logger.info("Valid platform versions (>=%s): %s", self._android_api_minimum.ints, platform_versions)
         if not platform_versions:
             return None
         android_api = platform_versions[0]
-        logger.info("Selected API version %d", android_api)
+        lo

(Patch may be truncated, please check the link at the top of this post.)