diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000000..f6f0c56665a8d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,406 @@ +name: 'release' +run-name: 'Create SDL release artifacts for ${{ inputs.commit }}' + +on: + workflow_dispatch: + inputs: + commit: + description: 'Commit of SDL' + required: true + +jobs: + + src: + runs-on: ubuntu-latest + outputs: + project: ${{ steps.releaser.outputs.project }} + version: ${{ steps.releaser.outputs.version }} + src-tar-gz: ${{ steps.releaser.outputs.src-tar-gz }} + src-tar-xz: ${{ steps.releaser.outputs.src-tar-xz }} + src-zip: ${{ steps.releaser.outputs.src-zip }} + steps: + - name: 'Set up Python' + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: 'Fetch build-release.py' + uses: actions/checkout@v4 + with: + sparse-checkout: 'build-scripts/build-release.py' + - name: 'Set up SDL sources' + uses: actions/checkout@v4 + with: + path: 'SDL' + fetch-depth: 0 + - name: 'Build Source archive' + id: releaser + shell: bash + run: | + python build-scripts/build-release.py \ + --create source \ + --commit ${{ inputs.commit }} \ + --project SDL2 \ + --root "${{ github.workspace }}/SDL" \ + --github \ + --debug + - name: 'Store source archives' + uses: actions/upload-artifact@v4 + with: + name: sources + path: '${{ github.workspace}}/dist' + + linux-verify: + needs: [src] + runs-on: ubuntu-latest + steps: + - name: 'Download source archives' + uses: actions/download-artifact@v4 + with: + name: sources + path: '${{ github.workspace }}' + - name: 'Unzip ${{ needs.src.outputs.src-zip }}' + id: zip + run: | + mkdir /tmp/zipdir + cd /tmp/zipdir + unzip "${{ github.workspace }}/${{ needs.src.outputs.src-zip }}" + echo "path=/tmp/zipdir/${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}" >>$GITHUB_OUTPUT + - name: 'Untar ${{ needs.src.outputs.src-tar-gz }}' + id: tar + run: | + mkdir -p /tmp/tardir + tar -C /tmp/tardir -v -x -f "${{ github.workspace }}/${{ needs.src.outputs.src-tar-gz }}" + echo "path=/tmp/tardir/${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}" >>$GITHUB_OUTPUT + - name: 'Compare contents of ${{ needs.src.outputs.src-zip }} and ${{ needs.src.outputs.src-tar-gz }}' + run: | + diff /tmp/zipdir /tmp/tardir + - name: 'Test versioning' + shell: bash + run: | + ${{ steps.tar.outputs.path }}/build-scripts/test-versioning.sh + - name: 'CMake (configure + build + tests + examples)' + run: | + cmake -S ${{ steps.tar.outputs.path }} -B /tmp/build -DSDL_TEST_LIBRARY=TRUE -DSDL_TESTS=TRUE -DSDL_EXAMPLES=TRUE + cmake --build /tmp/build --verbose + ctest --test-dir /tmp/build --no-tests=error --output-on-failure + + dmg: + needs: [src] + runs-on: macos-latest + outputs: + dmg: ${{ steps.releaser.outputs.dmg }} + steps: + - name: 'Set up Python' + uses: actions/setup-python@v5 + with: + python-version: '3.10' + - name: 'Fetch build-release.py' + uses: actions/checkout@v4 + with: + sparse-checkout: 'build-scripts/build-release.py' + - name: 'Download source archives' + uses: actions/download-artifact@v4 + with: + name: sources + path: '${{ github.workspace }}' + - name: 'Untar ${{ needs.src.outputs.src-tar-gz }}' + id: tar + run: | + mkdir -p "${{ github.workspace }}/tardir" + tar -C "${{ github.workspace }}/tardir" -v -x -f "${{ github.workspace }}/${{ needs.src.outputs.src-tar-gz }}" + echo "path=${{ github.workspace }}/tardir/${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}" >>$GITHUB_OUTPUT + - name: 'Build SDL2.dmg' + id: releaser + shell: bash + run: | + python build-scripts/build-release.py \ + --create framework \ + --commit ${{ inputs.commit }} \ + --project SDL2 \ + --root "${{ steps.tar.outputs.path }}" \ + --github \ + --debug + - name: 'Store DMG image file' + uses: actions/upload-artifact@v4 + with: + name: dmg + path: '${{ github.workspace }}/dist' + + dmg-verify: + needs: [dmg, src] + runs-on: macos-latest + steps: + - name: 'Download source archives' + uses: actions/download-artifact@v4 + with: + name: sources + path: '${{ github.workspace }}' + - name: 'Download ${{ needs.dmg.outputs.dmg }}' + uses: actions/download-artifact@v4 + with: + name: dmg + path: '${{ github.workspace }}' + - name: 'Untar ${{ needs.src.outputs.src-tar-gz }}' + id: src + run: | + mkdir -p /tmp/tardir + tar -C /tmp/tardir -v -x -f "${{ github.workspace }}/${{ needs.src.outputs.src-tar-gz }}" + echo "path=/tmp/tardir/${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}" >>$GITHUB_OUTPUT + - name: 'Mount ${{ needs.dmg.outputs.dmg }}' + id: mount + run: | + hdiutil attach '${{ github.workspace }}/${{ needs.dmg.outputs.dmg }}' + mount_point="/Volumes/${{ needs.src.outputs.project }}" + if [ ! -d "$mount_point/${{ needs.src.outputs.project }}.framework" ]; then + echo "Cannot find ${{ needs.src.outputs.project }}.framework!" + exit 1 + fi + echo "mount_point=$mount_point">>$GITHUB_OUTPUT + - name: 'CMake (configure + build) Darwin' + run: | + set -e + cmake -S "${{ steps.src.outputs.path }}/cmake/test" \ + -DTEST_FULL=FALSE \ + -DTEST_STATIC=FALSE \ + -DTEST_TEST=FALSE \ + -DCMAKE_PREFIX_PATH="${{ steps.mount.outputs.mount_point }}" \ + -DCMAKE_SYSTEM_NAME=Darwin \ + -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \ + -Werror=dev \ + -B build_darwin + cmake --build build_darwin --config Release --verbose + + cmake -S "${{ steps.src.outputs.path }}/cmake/test" \ + -DTEST_FULL=FALSE \ + -DTEST_STATIC=FALSE \ + -DTEST_TEST=FALSE \ + -DCMAKE_PREFIX_PATH="${{ steps.mount.outputs.mount_point }}" \ + -DCMAKE_SYSTEM_NAME=Darwin \ + -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \ + -Werror=dev \ + -B build_darwin_2 + cmake --build build_darwin --config Release --verbose + + msvc: + needs: [src] + runs-on: windows-2019 + outputs: + VC-x86: ${{ steps.releaser.outputs.VC-x86 }} + VC-x64: ${{ steps.releaser.outputs.VC-x64 }} + VC-devel: ${{ steps.releaser.outputs.VC-devel }} + steps: + - name: 'Set up Python' + uses: actions/setup-python@v5 + with: + python-version: '3.10' + - name: 'Fetch build-release.py' + uses: actions/checkout@v4 + with: + sparse-checkout: 'build-scripts/build-release.py' + - name: 'Download source archives' + uses: actions/download-artifact@v4 + with: + name: sources + path: '${{ github.workspace }}' + - name: 'Unzip ${{ needs.src.outputs.src-zip }}' + id: zip + run: | + New-Item C:\temp -ItemType Directory -ErrorAction SilentlyContinue + cd C:\temp + unzip "${{ github.workspace }}/${{ needs.src.outputs.src-zip }}" + echo "path=C:\temp\${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}" >>$Env:GITHUB_OUTPUT + - name: 'Build MSVC binary archives' + id: releaser + run: | + python build-scripts/build-release.py ` + --create win32 ` + --commit ${{ inputs.commit }} ` + --project SDL2 ` + --root "${{ steps.zip.outputs.path }}" ` + --github ` + --debug + - name: 'Store MSVC archives' + uses: actions/upload-artifact@v4 + with: + name: win32 + path: '${{ github.workspace }}/dist' + + msvc-verify: + needs: [msvc, src] + runs-on: windows-latest + steps: + - name: 'Fetch .github/actions/setup-ninja/action.yml' + uses: actions/checkout@v4 + with: + sparse-checkout: '.github/actions/setup-ninja/action.yml' + - name: 'Download source archives' + uses: actions/download-artifact@v4 + with: + name: sources + path: '${{ github.workspace }}' + - name: 'Download MSVC binaries' + uses: actions/download-artifact@v4 + with: + name: win32 + path: '${{ github.workspace }}' + - name: 'Unzip ${{ needs.src.outputs.src-zip }}' + id: src + run: | + mkdir '${{ github.workspace }}/sources' + cd '${{ github.workspace }}/sources' + unzip "${{ github.workspace }}/${{ needs.src.outputs.src-zip }}" + echo "path=${{ github.workspace }}/sources/${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}" >>$env:GITHUB_OUTPUT + - name: 'Unzip ${{ needs.msvc.outputs.VC-devel }}' + id: bin + run: | + mkdir '${{ github.workspace }}/vc' + cd '${{ github.workspace }}/vc' + unzip "${{ github.workspace }}/${{ needs.msvc.outputs.VC-devel }}" + echo "path=${{ github.workspace }}/vc/${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}" >>$env:GITHUB_OUTPUT + - name: Set up ninja + uses: ./.github/actions/setup-ninja + - name: 'Configure vcvars x86' + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64_x86 + - name: 'CMake (configure + build + tests) x86' + run: | + cmake -S "${{ steps.src.outputs.path }}/cmake/test" ` + -B build_x86 ` + -GNinja ` + -DCMAKE_BUILD_TYPE=Debug ` + -Werror=dev ` + -DTEST_FULL=FALSE ` + -DTEST_STATIC=FALSE ` + -DTEST_SHARED=TRUE ` + -DTEST_TEST=TRUE ` + -DCMAKE_SUPPRESS_REGENERATION=TRUE ` + -DCMAKE_PREFIX_PATH="${{ steps.bin.outputs.path }}" + Start-Sleep -Seconds 2 + cmake --build build_x86 --config Release --verbose + #ctest --test-dir build_x86 --no-tests=error -C Release --output-on-failure + - name: 'Configure vcvars x64' + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 + - name: 'CMake (configure + build + tests) x64' + run: | + cmake -S "${{ steps.src.outputs.path }}/cmake/test" ` + -B build_x64 ` + -GNinja ` + -DCMAKE_BUILD_TYPE=Debug ` + -Werror=dev ` + -DTEST_FULL=FALSE ` + -DTEST_STATIC=FALSE ` + -DTEST_SHARED=TRUE ` + -DTEST_TEST=TRUE ` + -DCMAKE_SUPPRESS_REGENERATION=TRUE ` + -DCMAKE_PREFIX_PATH="${{ steps.bin.outputs.path }}" + Start-Sleep -Seconds 2 + cmake --build build_x64 --config Release --verbose + #ctest --test-dir build_x64 --no-tests=error -C Release --output-on-failure + + mingw: + needs: [src] + runs-on: ubuntu-24.04 # FIXME: current ubuntu-latest ships an outdated mingw, replace with ubuntu-latest once 24.04 becomes the new default + outputs: + mingw-devel-tar-gz: ${{ steps.releaser.outputs.mingw-devel-tar-gz }} + mingw-devel-tar-xz: ${{ steps.releaser.outputs.mingw-devel-tar-xz }} + steps: + - name: 'Set up Python' + uses: actions/setup-python@v5 + with: + python-version: '3.10' + - name: 'Fetch build-release.py' + uses: actions/checkout@v4 + with: + sparse-checkout: 'build-scripts/build-release.py' + - name: 'Install Mingw toolchain' + run: | + sudo apt-get update -y + sudo apt-get install -y gcc-mingw-w64 g++-mingw-w64 ninja-build + - name: 'Download source archives' + uses: actions/download-artifact@v4 + with: + name: sources + path: '${{ github.workspace }}' + - name: 'Untar ${{ needs.src.outputs.src-tar-gz }}' + id: tar + run: | + mkdir -p /tmp/tardir + tar -C /tmp/tardir -v -x -f "${{ github.workspace }}/${{ needs.src.outputs.src-tar-gz }}" + echo "path=/tmp/tardir/${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}" >>$GITHUB_OUTPUT + - name: 'Build MinGW binary archives' + id: releaser + run: | + python build-scripts/build-release.py \ + --create mingw \ + --commit ${{ inputs.commit }} \ + --project SDL2 \ + --root "${{ steps.tar.outputs.path }}" \ + --github \ + --debug + - name: 'Store MinGW archives' + uses: actions/upload-artifact@v4 + with: + name: mingw + path: '${{ github.workspace }}/dist' + + mingw-verify: + needs: [mingw, src] + runs-on: ubuntu-latest + steps: + - name: 'Install Mingw toolchain' + run: | + sudo apt-get update -y + sudo apt-get install -y gcc-mingw-w64 g++-mingw-w64 ninja-build + - name: 'Download source archives' + uses: actions/download-artifact@v4 + with: + name: sources + path: '${{ github.workspace }}' + - name: 'Download MinGW binaries' + uses: actions/download-artifact@v4 + with: + name: mingw + path: '${{ github.workspace }}' + - name: 'Untar ${{ needs.src.outputs.src-tar-gz }}' + id: src + run: | + mkdir -p /tmp/tardir + tar -C /tmp/tardir -v -x -f "${{ github.workspace }}/${{ needs.src.outputs.src-tar-gz }}" + echo "path=/tmp/tardir/${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}" >>$GITHUB_OUTPUT + - name: 'Untar ${{ needs.mingw.outputs.mingw-devel-tar-gz }}' + id: bin + run: | + mkdir -p /tmp/mingw-tardir + tar -C /tmp/mingw-tardir -v -x -f "${{ github.workspace }}/${{ needs.mingw.outputs.mingw-devel-tar-gz }}" + echo "path=/tmp/mingw-tardir/${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}" >>$GITHUB_OUTPUT + - name: 'CMake (configure + build) i686' + run: | + set -e + cmake -S "${{ steps.src.outputs.path }}/cmake/test" \ + -DCMAKE_BUILD_TYPE="Release" \ + -DTEST_FULL=FALSE \ + -DTEST_STATIC=TRUE \ + -DTEST_TEST=TRUE \ + -DCMAKE_PREFIX_PATH="${{ steps.bin.outputs.path }}" \ + -DCMAKE_TOOLCHAIN_FILE="${{ steps.src.outputs.path }}/build-scripts/cmake-toolchain-mingw64-i686.cmake" \ + -DCMAKE_C_FLAGS="-DSDL_DISABLE_SSE4_2" \ + -Werror=dev \ + -B build_x86 + cmake --build build_x86 --config Release --verbose + - name: 'CMake (configure + build) x86_64' + run: | + set -e + cmake -S "${{ steps.src.outputs.path }}/cmake/test" \ + -DCMAKE_BUILD_TYPE="Release" \ + -DTEST_FULL=FALSE \ + -DTEST_STATIC=TRUE \ + -DTEST_TEST=TRUE \ + -DCMAKE_PREFIX_PATH="${{ steps.bin.outputs.path }}" \ + -DCMAKE_TOOLCHAIN_FILE="${{ steps.src.outputs.path }}/build-scripts/cmake-toolchain-mingw64-x86_64.cmake" \ + -DCMAKE_C_FLAGS="-DSDL_DISABLE_SSE4_2" \ + -Werror=dev \ + -B build_x64 + cmake --build build_x64 --config Release --verbose diff --git a/.gitignore b/.gitignore index cced2f8ed8b53..0af2680e36b56 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ gen Build buildbot /VERSION.txt +dist *.so *.so.* diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj index a798514a1b5e0..b068b3330c326 100644 --- a/VisualC/SDL/SDL.vcxproj +++ b/VisualC/SDL/SDL.vcxproj @@ -44,22 +44,6 @@ - - - - - - - - - - - - - - - - <_ProjectFileVersion>10.0.40219.1 diff --git a/VisualC/pkg-support/cmake/sdl2-config.cmake b/VisualC/pkg-support/cmake/sdl2-config.cmake index e54f470008e31..411b78a5152d0 100644 --- a/VisualC/pkg-support/cmake/sdl2-config.cmake +++ b/VisualC/pkg-support/cmake/sdl2-config.cmake @@ -79,6 +79,8 @@ endif() unset(_sdl2_library) unset(_sdl2_dll_library) +set(SDL2_SDL2-static_FOUND FALSE) + set(_sdl2main_library "${SDL2_LIBDIR}/SDL2main.lib") if(EXISTS "${_sdl2main_library}") if(NOT TARGET SDL2::SDL2main) @@ -110,7 +112,7 @@ if(EXISTS "${_sdl2test_library}") endif() set(SDL2_SDL2test_FOUND TRUE) else() - set(SDL2_SDL2_FOUND FALSE) + set(SDL2_SDL2test_FOUND FALSE) endif() unset(_sdl2test_library) diff --git a/build-scripts/build-release.py b/build-scripts/build-release.py new file mode 100755 index 0000000000000..3e94b66fc549a --- /dev/null +++ b/build-scripts/build-release.py @@ -0,0 +1,640 @@ +#!/usr/bin/env python + +import argparse +import collections +import contextlib +import datetime +import glob +import io +import json +import logging +import os +from pathlib import Path +import platform +import re +import shutil +import subprocess +import sys +import tarfile +import tempfile +import textwrap +import typing +import zipfile + +logger = logging.getLogger(__name__) + + +VcArchDevel = collections.namedtuple("VcArchDevel", ("dll", "pdb", "imp", "main", "test")) +GIT_HASH_FILENAME = ".git-hash" + +ANDROID_AVAILABLE_ABIS = [ + "armeabi-v7a", + "arm64-v8a", + "x86", + "x86_64", +] +ANDROID_MINIMUM_API = 19 +ANDROID_TARGET_API = 29 +ANDROID_MINIMUM_NDK = 21 + + +class Executer: + def __init__(self, root: Path, dry: bool=False): + self.root = root + self.dry = dry + + def run(self, cmd, stdout=False, dry_out=None, force=False): + sys.stdout.flush() + logger.info("Executing args=%r", cmd) + if self.dry and not force: + if stdout: + return subprocess.run(["echo", "-E", dry_out or ""], stdout=subprocess.PIPE if stdout else None, text=True, check=True, cwd=self.root) + else: + return subprocess.run(cmd, stdout=subprocess.PIPE if stdout else None, text=True, check=True, cwd=self.root) + + +class SectionPrinter: + @contextlib.contextmanager + def group(self, title: str): + print(f"{title}:") + yield + + +class GitHubSectionPrinter(SectionPrinter): + def __init__(self): + super().__init__() + self.in_group = False + + @contextlib.contextmanager + def group(self, title: str): + print(f"::group::{title}") + assert not self.in_group, "Can enter a group only once" + self.in_group = True + yield + self.in_group = False + print("::endgroup::") + + +class VisualStudio: + def __init__(self, executer: Executer, year: typing.Optional[str]=None): + self.executer = executer + self.vsdevcmd = self.find_vsdevcmd(year) + self.msbuild = self.find_msbuild() + + @property + def dry(self) -> bool: + return self.executer.dry + + VS_YEAR_TO_VERSION = { + "2022": 17, + "2019": 16, + "2017": 15, + "2015": 14, + "2013": 12, + } + + def find_vsdevcmd(self, year: typing.Optional[str]=None) -> typing.Optional[Path]: + vswhere_spec = ["-latest"] + if year is not None: + try: + version = self.VS_YEAR_TO_VERSION[year] + except KeyError: + logger.error("Invalid Visual Studio year") + return None + vswhere_spec.extend(["-version", f"[{version},{version+1})"]) + vswhere_cmd = ["vswhere"] + vswhere_spec + ["-property", "installationPath"] + vs_install_path = Path(self.executer.run(vswhere_cmd, stdout=True, dry_out="/tmp").stdout.strip()) + logger.info("VS install_path = %s", vs_install_path) + assert vs_install_path.is_dir(), "VS installation path does not exist" + vsdevcmd_path = vs_install_path / "Common7/Tools/vsdevcmd.bat" + logger.info("vsdevcmd path = %s", vsdevcmd_path) + if self.dry: + vsdevcmd_path.parent.mkdir(parents=True, exist_ok=True) + vsdevcmd_path.touch(exist_ok=True) + assert vsdevcmd_path.is_file(), "vsdevcmd.bat batch file does not exist" + return vsdevcmd_path + + def find_msbuild(self) -> typing.Optional[Path]: + vswhere_cmd = ["vswhere", "-latest", "-requires", "Microsoft.Component.MSBuild", "-find", r"MSBuild\**\Bin\MSBuild.exe"] + msbuild_path = Path(self.executer.run(vswhere_cmd, stdout=True, dry_out="/tmp/MSBuild.exe").stdout.strip()) + logger.info("MSBuild path = %s", msbuild_path) + if self.dry: + msbuild_path.parent.mkdir(parents=True, exist_ok=True) + msbuild_path.touch(exist_ok=True) + assert msbuild_path.is_file(), "MSBuild.exe does not exist" + return msbuild_path + + def build(self, arch: str, platform: str, configuration: str, projects: list[Path]): + assert projects, "Need at least one project to build" + + vsdev_cmd_str = f"\"{self.vsdevcmd}\" -arch={arch}" + msbuild_cmd_str = " && ".join([f"\"{self.msbuild}\" \"{project}\" /m /p:BuildInParallel=true /p:Platform={platform} /p:Configuration={configuration}" for project in projects]) + bat_contents = f"{vsdev_cmd_str} && {msbuild_cmd_str}\n" + bat_path = Path(tempfile.gettempdir()) / "cmd.bat" + with bat_path.open("w") as f: + f.write(bat_contents) + + logger.info("Running cmd.exe script (%s): %s", bat_path, bat_contents) + cmd = ["cmd.exe", "/D", "/E:ON", "/V:OFF", "/S", "/C", f"CALL {str(bat_path)}"] + self.executer.run(cmd) + + +class Releaser: + def __init__(self, project: str, commit: str, root: Path, dist_path: Path, section_printer: SectionPrinter, executer: Executer, cmake_generator: str): + self.project = project + self.version = self.extract_sdl_version(root=root, project=project) + self.root = root + self.commit = commit + self.dist_path = dist_path + self.section_printer = section_printer + self.executer = executer + self.cmake_generator = cmake_generator + + self.artifacts: dict[str, Path] = {} + + @property + def dry(self) -> bool: + return self.executer.dry + + def prepare(self): + logger.debug("Creating dist folder") + self.dist_path.mkdir(parents=True, exist_ok=True) + + TreeItem = collections.namedtuple("TreeItem", ("path", "mode", "data", "time")) + def _get_file_times(self, paths: tuple[str, ...]) -> dict[str, datetime.datetime]: + dry_out = textwrap.dedent("""\ + time=2024-03-14T15:40:25-07:00 + + M\tCMakeLists.txt + """) + git_log_out = self.executer.run(["git", "log", "--name-status", '--pretty=time=%cI', self.commit], stdout=True, dry_out=dry_out).stdout.splitlines(keepends=False) + current_time = None + set_paths = set(paths) + path_times: dict[str, datetime.datetime] = {} + for line in git_log_out: + if not line: + continue + if line.startswith("time="): + current_time = datetime.datetime.fromisoformat(line.removeprefix("time=")) + continue + mod_type, file_paths = line.split(maxsplit=1) + assert current_time is not None + for file_path in file_paths.split("\t"): + if file_path in set_paths and file_path not in path_times: + path_times[file_path] = current_time + assert set(path_times.keys()) == set_paths + return path_times + + @staticmethod + def _path_filter(path: str): + if path.startswith(".git"): + return False + return True + + def _get_git_contents(self) -> dict[str, TreeItem]: + contents_tgz = subprocess.check_output(["git", "archive", "--format=tar.gz", self.commit, "-o", "/dev/stdout"], text=False) + contents = tarfile.open(fileobj=io.BytesIO(contents_tgz), mode="r:gz") + filenames = tuple(m.name for m in contents if m.isfile()) + assert "src/SDL.c" in filenames + assert "include/SDL.h" in filenames + file_times = self._get_file_times(filenames) + git_contents = {} + for ti in contents: + if not ti.isfile(): + continue + if not self._path_filter(ti.name): + continue + contents_file = contents.extractfile(ti.name) + assert contents_file, f"{ti.name} is not a file" + git_contents[ti.name] = self.TreeItem(path=ti.name, mode=ti.mode, data=contents_file.read(), time=file_times[ti.name]) + return git_contents + + def create_source_archives(self) -> None: + archive_base = f"{self.project}-{self.version}" + + git_contents = self._get_git_contents() + git_files = list(git_contents.values()) + assert len(git_contents) == len(git_files) + + latest_mod_time = max(item.time for item in git_files) + + git_files.append(self.TreeItem(path="VERSION.txt", data=f"{self.version}\n".encode(), mode=0o100644, time=latest_mod_time)) + git_files.append(self.TreeItem(path=GIT_HASH_FILENAME, data=f"{self.commit}\n".encode(), mode=0o100644, time=latest_mod_time)) + + git_files.sort(key=lambda v: v.time) + + zip_path = self.dist_path / f"{archive_base}.zip" + logger.info("Creating .zip source archive (%s)...", zip_path) + if self.dry: + zip_path.touch() + else: + with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as zip_object: + for git_file in git_files: + file_data_time = (git_file.time.year, git_file.time.month, git_file.time.day, git_file.time.hour, git_file.time.minute, git_file.time.second) + zip_info = zipfile.ZipInfo(filename=f"{archive_base}/{git_file.path}", date_time=file_data_time) + zip_info.external_attr = git_file.mode << 16 + zip_info.compress_type = zipfile.ZIP_DEFLATED + zip_object.writestr(zip_info, data=git_file.data) + self.artifacts["src-zip"] = zip_path + + tar_types = ( + (".tar.gz", "gz"), + (".tar.xz", "xz"), + ) + for ext, comp in tar_types: + tar_path = self.dist_path / f"{archive_base}{ext}" + logger.info("Creating %s source archive (%s)...", ext, tar_path) + if self.dry: + tar_path.touch() + else: + with tarfile.open(tar_path, f"w:{comp}") as tar_object: + for git_file in git_files: + tar_info = tarfile.TarInfo(f"{archive_base}/{git_file.path}") + tar_info.mode = git_file.mode + tar_info.size = len(git_file.data) + tar_info.mtime = git_file.time.timestamp() + tar_object.addfile(tar_info, fileobj=io.BytesIO(git_file.data)) + + if tar_path.suffix == ".gz": + # Zero the embedded timestamp in the gzip'ed tarball + with open(tar_path, "r+b") as f: + f.seek(4, 0) + f.write(b"\x00\x00\x00\x00") + + self.artifacts[f"src-tar-{comp}"] = tar_path + + def create_framework(self, configuration: str="Release") -> None: + dmg_in = self.root / f"Xcode/SDL/build/{self.project}.dmg" + dmg_in.unlink(missing_ok=True) + self.executer.run(["xcodebuild", "-project", str(self.root / "Xcode/SDL/SDL.xcodeproj"), "-target", "Standard DMG", "-configuration", configuration]) + if self.dry: + dmg_in.parent.mkdir(parents=True, exist_ok=True) + dmg_in.touch() + + assert dmg_in.is_file(), f"{self.project}.dmg was not created by xcodebuild" + + dmg_out = self.dist_path / f"{self.project}-{self.version}.dmg" + shutil.copy(dmg_in, dmg_out) + self.artifacts["dmg"] = dmg_out + + @property + def git_hash_data(self) -> bytes: + return f"{self.commit}\n".encode() + + def _tar_add_git_hash(self, tar_object: tarfile.TarFile, root: typing.Optional[str]=None, time: typing.Optional[datetime.datetime]=None): + if not time: + time = datetime.datetime(year=2024, month=4, day=1) + path = GIT_HASH_FILENAME + if root: + path = f"{root}/{path}" + + tar_info = tarfile.TarInfo(path) + tar_info.mode = 0o100644 + tar_info.size = len(self.git_hash_data) + tar_info.mtime = int(time.timestamp()) + tar_object.addfile(tar_info, fileobj=io.BytesIO(self.git_hash_data)) + + def _zip_add_git_hash(self, zip_file: zipfile.ZipFile, root: typing.Optional[str]=None, time: typing.Optional[datetime.datetime]=None): + if not time: + time = datetime.datetime(year=2024, month=4, day=1) + path = GIT_HASH_FILENAME + if root: + path = f"{root}/{path}" + + file_data_time = (time.year, time.month, time.day, time.hour, time.minute, time.second) + zip_info = zipfile.ZipInfo(filename=path, date_time=file_data_time) + zip_info.external_attr = 0o100644 << 16 + zip_info.compress_type = zipfile.ZIP_DEFLATED + zip_file.writestr(zip_info, data=self.git_hash_data) + + def create_mingw_archives(self) -> None: + build_type = "Release" + mingw_archs = ("i686", "x86_64") + build_parent_dir = self.root / "build-mingw" + + zip_path = self.dist_path / f"{self.project}-devel-{self.version}-mingw.zip" + tar_exts = ("gz", "xz") + tar_paths = { ext: self.dist_path / f"{self.project}-devel-{self.version}-mingw.tar.{ext}" for ext in tar_exts} + + arch_install_paths = {} + arch_files = {} + + for arch in mingw_archs: + build_path = build_parent_dir / f"build-{arch}" + install_path = build_parent_dir / f"install-{arch}" + arch_install_paths[arch] = install_path + shutil.rmtree(install_path, ignore_errors=True) + build_path.mkdir(parents=True, exist_ok=True) + with self.section_printer.group(f"Configuring MinGW {arch}"): + self.executer.run([ + "cmake", "-S", str(self.root), "-B", str(build_path), + "--fresh", + f'''-DCMAKE_C_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}"''', + f'''-DCMAKE_CXX_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}"''', + "-DSDL_SHARED=ON", + "-DSDL_STATIC=ON", + "-DSDL_DISABLE_INSTALL_DOCS=ON", + "-DSDL_TEST_LIBRARY=ON", + "-DSDL_TESTS=OFF", + "-DCMAKE_INSTALL_BINDIR=bin", + "-DCMAKE_INSTALL_DATAROOTDIR=share", + "-DCMAKE_INSTALL_INCLUDEDIR=include", + "-DCMAKE_INSTALL_LIBDIR=lib", + f"-DCMAKE_BUILD_TYPE={build_type}", + f"-DCMAKE_TOOLCHAIN_FILE={self.root}/build-scripts/cmake-toolchain-mingw64-{arch}.cmake", + f"-G{self.cmake_generator}", + f"-DCMAKE_INSTALL_PREFIX={install_path}", + ]) + with self.section_printer.group(f"Build MinGW {arch}"): + self.executer.run(["cmake", "--build", str(build_path), "--verbose", "--config", build_type]) + with self.section_printer.group(f"Install MinGW {arch}"): + self.executer.run(["cmake", "--install", str(build_path), "--strip", "--config", build_type]) + arch_files[arch] = list(Path(r) / f for r, _, files in os.walk(install_path) for f in files) + + extra_files = ( + ("mingw/pkg-support/INSTALL.txt", ""), + ("mingw/pkg-support/Makefile", ""), + ("mingw/pkg-support/cmake/sdl2-config.cmake", "cmake/"), + ("mingw/pkg-support/cmake/sdl2-config-version.cmake", "cmake/"), + ("BUGS.txt", ""), + ("CREDITS.txt", ""), + ("README-SDL.txt", ""), + ("WhatsNew.txt", ""), + ("LICENSE.txt", ""), + ("README.md", ""), + ) + test_files = list(Path(r) / f for r, _, files in os.walk(self.root / "test") for f in files) + + # FIXME: split SDL2.dll debug information into debug library + # objcopy --only-keep-debug SDL2.dll SDL2.debug.dll + # objcopy --add-gnu-debuglink=SDL2.debug.dll SDL2.dll + # objcopy --strip-debug SDL2.dll + + for comp in tar_exts: + logger.info("Creating %s...", tar_paths[comp]) + with tarfile.open(tar_paths[comp], f"w:{comp}") as tar_object: + arc_root = f"{self.project}-{self.version}" + for file_path, arcdirname in extra_files: + assert not arcdirname or arcdirname[-1] == "/" + arcname = f"{arc_root}/{arcdirname}{Path(file_path).name}" + tar_object.add(self.root / file_path, arcname=arcname) + for arch in mingw_archs: + install_path = arch_install_paths[arch] + arcname_parent = f"{arc_root}/{arch}-w64-mingw32" + for file in arch_files[arch]: + arcname = os.path.join(arcname_parent, file.relative_to(install_path)) + tar_object.add(file, arcname=arcname) + for test_file in test_files: + arcname = f"{arc_root}/test/{test_file.relative_to(self.root/'test')}" + tar_object.add(test_file, arcname=arcname) + self._tar_add_git_hash(tar_object=tar_object, root=arc_root) + + self.artifacts[f"mingw-devel-tar-{comp}"] = tar_paths[comp] + + def build_vs(self, arch: str, platform: str, vs: VisualStudio, configuration: str="Release") -> VcArchDevel: + dll_path = self.root / f"VisualC/SDL/{platform}/{configuration}/{self.project}.dll" + pdb_path = self.root / f"VisualC/SDL/{platform}/{configuration}/{self.project}.pdb" + imp_path = self.root / f"VisualC/SDL/{platform}/{configuration}/{self.project}.lib" + test_path = self.root / f"VisualC/SDLtest/{platform}/{configuration}/{self.project}test.lib" + main_path = self.root / f"VisualC/SDLmain/{platform}/{configuration}/{self.project}main.lib" + + dll_path.unlink(missing_ok=True) + pdb_path.unlink(missing_ok=True) + imp_path.unlink(missing_ok=True) + test_path.unlink(missing_ok=True) + main_path.unlink(missing_ok=True) + + projects = [ + self.root / "VisualC/SDL/SDL.vcxproj", + self.root / "VisualC/SDLmain/SDLmain.vcxproj", + self.root / "VisualC/SDLtest/SDLtest.vcxproj", + ] + + with self.section_printer.group(f"Build {arch} VS binary"): + vs.build(arch=arch, platform=platform, configuration=configuration, projects=projects) + + if self.dry: + dll_path.parent.mkdir(parents=True, exist_ok=True) + dll_path.touch() + pdb_path.touch() + imp_path.touch() + main_path.parent.mkdir(parents=True, exist_ok=True) + main_path.touch() + test_path.parent.mkdir(parents=True, exist_ok=True) + test_path.touch() + + assert dll_path.is_file(), f"{self.project}.dll has not been created" + assert pdb_path.is_file(), f"{self.project}.pdb has not been created" + assert imp_path.is_file(), f"{self.project}.lib has not been created" + assert main_path.is_file(), f"{self.project}main.lib has not been created" + assert test_path.is_file(), f"{self.project}est.lib has not been created" + + zip_path = self.dist_path / f"{self.project}-{self.version}-win32-{arch}.zip" + zip_path.unlink(missing_ok=True) + logger.info("Creating %s", zip_path) + with zipfile.ZipFile(zip_path, mode="w", compression=zipfile.ZIP_DEFLATED) as zf: + logger.debug("Adding %s", dll_path.name) + zf.write(dll_path, arcname=dll_path.name) + logger.debug("Adding %s", "README-SDL.txt") + zf.write(self.root / "README-SDL.txt", arcname="README-SDL.txt") + self._zip_add_git_hash(zip_file=zf) + self.artifacts[f"VC-{arch}"] = zip_path + + return VcArchDevel(dll=dll_path, pdb=pdb_path, imp=imp_path, main=main_path, test=test_path) + + + def build_vs_devel(self, arch_vc: dict[str, VcArchDevel]) -> None: + zip_path = self.dist_path / f"{self.project}-devel-{self.version}-VC.zip" + archive_prefix = f"{self.project}-{self.version}" + + def zip_file(zf: zipfile.ZipFile, path: Path, arcrelpath: str): + arcname = f"{archive_prefix}/{arcrelpath}" + logger.debug("Adding %s to %s", path, arcname) + zf.write(path, arcname=arcname) + + def zip_directory(zf: zipfile.ZipFile, directory: Path, arcrelpath: str): + for f in directory.iterdir(): + if f.is_file(): + arcname = f"{archive_prefix}/{arcrelpath}/{f.name}" + logger.debug("Adding %s to %s", f, arcname) + zf.write(f, arcname=arcname) + + with zipfile.ZipFile(zip_path, mode="w", compression=zipfile.ZIP_DEFLATED) as zf: + for arch, binaries in arch_vc.items(): + zip_file(zf, path=binaries.dll, arcrelpath=f"lib/{arch}/{binaries.dll.name}") + zip_file(zf, path=binaries.imp, arcrelpath=f"lib/{arch}/{binaries.imp.name}") + zip_file(zf, path=binaries.pdb, arcrelpath=f"lib/{arch}/{binaries.pdb.name}") + zip_file(zf, path=binaries.main, arcrelpath=f"lib/{arch}/{binaries.main.name}") + zip_file(zf, path=binaries.test, arcrelpath=f"lib/{arch}/{binaries.test.name}") + + zip_directory(zf, directory=self.root / "include", arcrelpath="include") + zip_directory(zf, directory=self.root / "docs", arcrelpath="docs") + zip_directory(zf, directory=self.root / "VisualC/pkg-support/cmake", arcrelpath="cmake") + + for txt in ("BUGS.txt", "README-SDL.txt", "WhatsNew.txt"): + zip_file(zf, path=self.root / txt, arcrelpath=txt) + zip_file(zf, path=self.root / "LICENSE.txt", arcrelpath="COPYING.txt") + zip_file(zf, path=self.root / "README.md", arcrelpath="README.txt") + + self._zip_add_git_hash(zip_file=zf, root=archive_prefix) + self.artifacts["VC-devel"] = zip_path + + @classmethod + def extract_sdl_version(cls, root: Path, project: str) -> str: + with open(root / f"include/SDL_version.h", "r") as f: + text = f.read() + major = next(re.finditer(r"^#define SDL_MAJOR_VERSION\s+([0-9]+)$", text, flags=re.M)).group(1) + minor = next(re.finditer(r"^#define SDL_MINOR_VERSION\s+([0-9]+)$", text, flags=re.M)).group(1) + micro = next(re.finditer(r"^#define SDL_PATCHLEVEL\s+([0-9]+)$", text, flags=re.M)).group(1) + return f"{major}.{minor}.{micro}" + + +def main(argv=None) -> int: + parser = argparse.ArgumentParser(allow_abbrev=False, description="Create SDL release artifacts") + parser.add_argument("--root", metavar="DIR", type=Path, default=Path(__file__).absolute().parents[1], help="Root of SDL") + parser.add_argument("--out", "-o", metavar="DIR", dest="dist_path", type=Path, default="dist", help="Output directory") + parser.add_argument("--github", action="store_true", help="Script is running on a GitHub runner") + parser.add_argument("--commit", default="HEAD", help="Git commit/tag of which a release should be created") + parser.add_argument("--project", required=True, help="Name of the project (e.g. SDL2") + parser.add_argument("--create", choices=["source", "mingw", "win32", "framework", "android"], required=True, action="append", dest="actions", help="What to do") + parser.set_defaults(loglevel=logging.INFO) + parser.add_argument('--vs-year', dest="vs_year", help="Visual Studio year") + parser.add_argument('--android-api', type=int, dest="android_api", help="Android API version") + parser.add_argument('--android-home', dest="android_home", default=os.environ.get("ANDROID_HOME"), help="Android Home folder") + parser.add_argument('--android-ndk-home', dest="android_ndk_home", default=os.environ.get("ANDROID_NDK_HOME"), help="Android NDK Home folder") + parser.add_argument('--android-abis', dest="android_abis", nargs="*", choices=ANDROID_AVAILABLE_ABIS, default=list(ANDROID_AVAILABLE_ABIS), help="Android NDK Home folder") + parser.add_argument('--cmake-generator', dest="cmake_generator", default="Ninja", help="CMake Generator") + parser.add_argument('--debug', action='store_const', const=logging.DEBUG, dest="loglevel", help="Print script debug information") + parser.add_argument('--dry-run', action='store_true', dest="dry", help="Don't execute anything") + parser.add_argument('--force', action='store_true', dest="force", help="Ignore a non-clean git tree") + + args = parser.parse_args(argv) + logging.basicConfig(level=args.loglevel, format='[%(levelname)s] %(message)s') + args.actions = set(args.actions) + args.dist_path = args.dist_path.absolute() + args.root = args.root.absolute() + args.dist_path = args.dist_path.absolute() + if args.dry: + args.dist_path = args.dist_path / "dry" + + if args.github: + section_printer: SectionPrinter = GitHubSectionPrinter() + else: + section_printer = SectionPrinter() + + executer = Executer(root=args.root, dry=args.dry) + + root_git_hash_path = args.root / GIT_HASH_FILENAME + root_is_maybe_archive = root_git_hash_path.is_file() + if root_is_maybe_archive: + logger.warning("%s detected: Building from archive", GIT_HASH_FILENAME) + archive_commit = root_git_hash_path.read_text().strip() + if args.commit != archive_commit: + logger.warning("Commit argument is %s, but archive commit is %s. Using %s.", args.commit, archive_commit, archive_commit) + args.commit = archive_commit + else: + args.commit = executer.run(["git", "rev-parse", args.commit], stdout=True, dry_out="e5812a9fd2cda317b503325a702ba3c1c37861d9").stdout.strip() + logger.info("Using commit %s", args.commit) + + releaser = Releaser( + project=args.project, + commit=args.commit, + root=args.root, + dist_path=args.dist_path, + executer=executer, + section_printer=section_printer, + cmake_generator=args.cmake_generator, + ) + + if root_is_maybe_archive: + logger.warning("Building from archive. Skipping clean git tree check.") + else: + porcelain_status = executer.run(["git", "status", "--ignored", "--porcelain"], stdout=True, dry_out="\n").stdout.strip() + if porcelain_status: + print(porcelain_status) + logger.warning("The tree is dirty! Do not publish any generated artifacts!") + if not args.force: + raise Exception("The git repo contains modified and/or non-committed files. Run with --force to ignore.") + + with section_printer.group("Arguments"): + print(f"project = {args.project}") + print(f"version = {releaser.version}") + print(f"commit = {args.commit}") + print(f"out = {args.dist_path}") + print(f"actions = {args.actions}") + print(f"dry = {args.dry}") + print(f"force = {args.force}") + print(f"cmake_generator = {args.cmake_generator}") + + releaser.prepare() + + if "source" in args.actions: + if root_is_maybe_archive: + raise Exception("Cannot build source archive from source archive") + with section_printer.group("Create source archives"): + releaser.create_source_archives() + + if "framework" in args.actions: + if platform.system() != "Darwin" and not args.dry: + parser.error("framework artifact(s) can only be built on Darwin") + + releaser.create_framework() + + if "win32" in args.actions: + if platform.system() != "Windows" and not args.dry: + parser.error("win32 artifact(s) can only be built on Windows") + with section_printer.group("Find Visual Studio"): + vs = VisualStudio(executer=executer) + x86 = releaser.build_vs(arch="x86", platform="Win32", vs=vs) + x64 = releaser.build_vs(arch="x64", platform="x64", vs=vs) + with section_printer.group("Create SDL VC development zip"): + arch_vc = { + "x86": x86, + "x64": x64, + } + releaser.build_vs_devel(arch_vc) + + if "mingw" in args.actions: + releaser.create_mingw_archives() + + if "android" in args.actions: + if args.android_home is None or not Path(args.android_home).is_dir(): + parser.error("Invalid $ANDROID_HOME or --android-home: must be a directory containing the Android SDK") + if args.android_ndk_home is None or not Path(args.android_ndk_home).is_dir(): + parser.error("Invalid $ANDROID_NDK_HOME or --android_ndk_home: must be a directory containing the Android NDK") + if args.android_api is None: + with section_printer.group("Detect Android APIS"): + args.android_api = releaser.detect_android_api(android_home=args.android_home) + if args.android_api is None or not (Path(args.android_home) / f"platforms/android-{args.android_api}").is_dir(): + parser.error("Invalid --android-api, and/or could not be detected") + if not args.android_abis: + parser.error("Need at least one Android ABI") + with section_printer.group("Android arguments"): + print(f"android_home = {args.android_home}") + print(f"android_ndk_home = {args.android_ndk_home}") + print(f"android_api = {args.android_api}") + print(f"android_abis = {args.android_abis}") + releaser.create_android_archives( + android_api=args.android_api, + android_home=args.android_home, + android_ndk_home=args.android_ndk_home, + android_abis=args.android_abis, + ) + + + with section_printer.group("Summary"): + print(f"artifacts = {releaser.artifacts}") + + if args.github: + if args.dry: + os.environ["GITHUB_OUTPUT"] = "/tmp/github_output.txt" + with open(os.environ["GITHUB_OUTPUT"], "a") as f: + f.write(f"project={releaser.project}\n") + f.write(f"version={releaser.version}\n") + for k, v in releaser.artifacts.items(): + f.write(f"{k}={v.name}\n") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/build-scripts/cmake-toolchain-mingw64-i686.cmake b/build-scripts/cmake-toolchain-mingw64-i686.cmake new file mode 100644 index 0000000000000..8be7b3a80fe07 --- /dev/null +++ b/build-scripts/cmake-toolchain-mingw64-i686.cmake @@ -0,0 +1,18 @@ +set(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_SYSTEM_PROCESSOR x86) + +find_program(CMAKE_C_COMPILER NAMES i686-w64-mingw32-gcc) +find_program(CMAKE_CXX_COMPILER NAMES i686-w64-mingw32-g++) +find_program(CMAKE_RC_COMPILER NAMES i686-w64-mingw32-windres windres) + +if(NOT CMAKE_C_COMPILER) + message(FATAL_ERROR "Failed to find CMAKE_C_COMPILER.") +endif() + +if(NOT CMAKE_CXX_COMPILER) + message(FATAL_ERROR "Failed to find CMAKE_CXX_COMPILER.") +endif() + +if(NOT CMAKE_RC_COMPILER) + message(FATAL_ERROR "Failed to find CMAKE_RC_COMPILER.") +endif() diff --git a/build-scripts/cmake-toolchain-mingw64-x86_64.cmake b/build-scripts/cmake-toolchain-mingw64-x86_64.cmake new file mode 100644 index 0000000000000..8bf436695e1fd --- /dev/null +++ b/build-scripts/cmake-toolchain-mingw64-x86_64.cmake @@ -0,0 +1,18 @@ +set(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_SYSTEM_PROCESSOR x86_64) + +find_program(CMAKE_C_COMPILER NAMES x86_64-w64-mingw32-gcc) +find_program(CMAKE_CXX_COMPILER NAMES x86_64-w64-mingw32-g++) +find_program(CMAKE_RC_COMPILER NAMES x86_64-w64-mingw32-windres windres) + +if(NOT CMAKE_C_COMPILER) + message(FATAL_ERROR "Failed to find CMAKE_C_COMPILER.") +endif() + +if(NOT CMAKE_CXX_COMPILER) + message(FATAL_ERROR "Failed to find CMAKE_CXX_COMPILER.") +endif() + +if(NOT CMAKE_RC_COMPILER) + message(FATAL_ERROR "Failed to find CMAKE_RC_COMPILER.") +endif() diff --git a/build-scripts/create-release.py b/build-scripts/create-release.py new file mode 100755 index 0000000000000..b919c1050f762 --- /dev/null +++ b/build-scripts/create-release.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python + +import argparse +from pathlib import Path +import logging +import re +import subprocess + +ROOT = Path(__file__).resolve().parents[1] + + +def determine_project() -> str: + text = (ROOT / "CMakeLists.txt").read_text() + match = next(re.finditer(r"project\((?P[a-zA-Z0-9_]+)\s+", text, flags=re.M)) + project_with_version = match["project"] + project, _ = re.subn("([^a-zA-Z_])", "", project_with_version) + return project + + +def main(): + project = determine_project() + default_remote = f"libsdl-org/{project}" + + current_commit = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=ROOT, text=True).strip() + + parser = argparse.ArgumentParser(allow_abbrev=False) + parser.add_argument("--ref", required=True, help=f"Name of branch or tag containing release.yml") + parser.add_argument("--remote", "-R", default=default_remote, help=f"Remote repo (default={default_remote})") + parser.add_argument("--commit", default=current_commit, help=f"Commit (default={current_commit})") + args = parser.parse_args() + + + print(f"Running release.yml workflow:") + print(f" commit = {args.commit}") + print(f" remote = {args.remote}") + + subprocess.check_call(["gh", "-R", args.remote, "workflow", "run", "release.yml", "--ref", args.ref, "-f", f"commit={args.commit}"], cwd=ROOT) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/cmake/test/CMakeLists.txt b/cmake/test/CMakeLists.txt index 388e86c54ce42..a9c0c498a9716 100644 --- a/cmake/test/CMakeLists.txt +++ b/cmake/test/CMakeLists.txt @@ -27,6 +27,12 @@ add_feature_info("TEST_SHARED" TEST_SHARED "Test linking with shared library") option(TEST_STATIC "Test linking to static SDL2 library" ON) add_feature_info("TEST_STATIC" TEST_STATIC "Test linking with static library") +option(TEST_TEST "Test linking to SDL3_test library" ON) +add_feature_info("TEST_TEST" TEST_STATIC "Test linking to SDL test library") + +option(TEST_FULL "Run complete SDL test suite" OFF) +add_feature_info("TEST_FULL" TEST_FULL "Build full SDL testsuite") + if(TEST_SHARED) find_package(SDL2 REQUIRED CONFIG COMPONENTS SDL2) if(EMSCRIPTEN OR (WIN32 AND NOT WINDOWS_STORE)) @@ -75,6 +81,11 @@ if(TEST_SHARED) generate_export_header(sharedlib-shared-vars EXPORT_MACRO_NAME MYLIBRARY_EXPORT) target_compile_definitions(sharedlib-shared-vars PRIVATE "EXPORT_HEADER=\"${CMAKE_CURRENT_BINARY_DIR}/sharedlib-shared-vars_export.h\"") set_target_properties(sharedlib-shared-vars PROPERTIES C_VISIBILITY_PRESET "hidden") + + if(TEST_TEST) + add_executable(sdltest-shared sdltest.c) + target_link_libraries(sdltest-shared PRIVATE SDL2::SDL2main SDL2::SDL2test SDL2::SDL2) + endif() endif() if(TEST_STATIC) @@ -111,6 +122,21 @@ if(TEST_STATIC) target_link_libraries(cli-static-vars PRIVATE ${SDL2_STATIC_LIBRARIES}) target_include_directories(cli-static-vars PRIVATE ${SDL2_INCLUDE_DIRS}) endif() + + if(CMAKE_Swift_COMPILER) + add_executable(swift-static main.swift) + target_include_directories(swift-static PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/swift") + target_link_libraries(swift-static PRIVATE SDL2::SDL2-static) + endif() +endif() + +if(TEST_FULL) + enable_testing() + set(SDL_TESTS_TIMEOUT_MULTIPLIER "1" CACHE STRING "Test timeout multiplier") + set(SDL_TESTS_LINK_SHARED ${TEST_SHARED}) + + add_definitions(-DNO_BUILD_CONFIG) + add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/../../test" SDL_test) endif() message(STATUS "SDL2_PREFIX: ${SDL2_PREFIX}") diff --git a/cmake/test/sdltest.c b/cmake/test/sdltest.c new file mode 100644 index 0000000000000..5c6d6972f0093 --- /dev/null +++ b/cmake/test/sdltest.c @@ -0,0 +1,9 @@ +#include "SDL.h" +#include "SDL_test.h" + + +int main(int argc, char *argv[]) { + SDLTest_CommonState state; + SDLTest_CommonDefaultArgs(&state, argc, argv); + return 0; +} diff --git a/mingw/pkg-support/INSTALL.txt b/mingw/pkg-support/INSTALL.txt new file mode 100644 index 0000000000000..607dafd0ee0a7 --- /dev/null +++ b/mingw/pkg-support/INSTALL.txt @@ -0,0 +1,18 @@ + +The 32-bit files are in i686-w64-mingw32 +The 64-bit files are in x86_64-w64-mingw32 + +To install SDL for native development: + make native + +To install SDL for cross-compiling development: + make cross + +Look at the example programs in ./test, and check out online documentation: + http://wiki.libsdl.org/ + +Join the SDL developer mailing list if you want to join the community: + http://www.libsdl.org/mailing-list.php + +That's it! +Sam Lantinga diff --git a/mingw/pkg-support/Makefile b/mingw/pkg-support/Makefile new file mode 100644 index 0000000000000..9edfd003e83e0 --- /dev/null +++ b/mingw/pkg-support/Makefile @@ -0,0 +1,30 @@ +# +# Makefile for installing the mingw32 version of the SDL library + +CROSS_PATH := /usr/local +ARCHITECTURES := i686-w64-mingw32 x86_64-w64-mingw32 + +all install: + @echo "Type \"make native\" to install 32-bit to /usr" + @echo "Type \"make cross\" to install 32-bit and 64-bit to $(CROSS_PATH)" + +native: + make install-package arch=i686-w64-mingw32 prefix=/usr + +cross: + for arch in $(ARCHITECTURES); do \ + make install-package arch=$$arch prefix=$(CROSS_PATH)/$$arch; \ + done + +install-package: + @if test -d $(arch) && test -d $(prefix); then \ + (cd $(arch) && cp -rv bin include lib share $(prefix)/); \ + sed "s|^prefix=.*|prefix=$(prefix)|" <$(arch)/bin/sdl2-config >$(prefix)/bin/sdl2-config; \ + chmod 755 $(prefix)/bin/sdl2-config; \ + sed "s|^libdir=.*|libdir=\'$(prefix)/lib\'|" <$(arch)/lib/libSDL2.la >$(prefix)/lib/libSDL2.la; \ + sed "s|^libdir=.*|libdir=\'$(prefix)/lib\'|" <$(arch)/lib/libSDL2main.la >$(prefix)/lib/libSDL2main.la; \ + sed "s|^prefix=.*|prefix=$(prefix)|" <$(arch)/lib/pkgconfig/sdl2.pc >$(prefix)/lib/pkgconfig/sdl2.pc; \ + else \ + echo "*** ERROR: $(arch) or $(prefix) does not exist!"; \ + exit 1; \ + fi diff --git a/mingw/pkg-support/cmake/sdl2-config-version.cmake b/mingw/pkg-support/cmake/sdl2-config-version.cmake index 9f7a8b34dc724..307063048f993 100644 --- a/mingw/pkg-support/cmake/sdl2-config-version.cmake +++ b/mingw/pkg-support/cmake/sdl2-config-version.cmake @@ -2,9 +2,9 @@ # This file is meant to be placed in a cmake subfolder of SDL2-devel-2.x.y-mingw if(CMAKE_SIZEOF_VOID_P EQUAL 4) - set(sdl2_config_path "${CMAKE_CURRENT_LIST_DIR}/../i686-w64-mingw32/lib/cmake/SDL2/sdl2-config-version.cmake") + set(sdl2_config_path "${CMAKE_CURRENT_LIST_DIR}/../i686-w64-mingw32/lib/cmake/SDL2/SDL2ConfigVersion.cmake") elseif(CMAKE_SIZEOF_VOID_P EQUAL 8) - set(sdl2_config_path "${CMAKE_CURRENT_LIST_DIR}/../x86_64-w64-mingw32/lib/cmake/SDL2/sdl2-config-version.cmake") + set(sdl2_config_path "${CMAKE_CURRENT_LIST_DIR}/../x86_64-w64-mingw32/lib/cmake/SDL2/SDL2ConfigVersion.cmake") else() set(PACKAGE_VERSION_UNSUITABLE TRUE) return() diff --git a/mingw/pkg-support/cmake/sdl2-config.cmake b/mingw/pkg-support/cmake/sdl2-config.cmake index 3c0799fbcaf30..f512814d78200 100644 --- a/mingw/pkg-support/cmake/sdl2-config.cmake +++ b/mingw/pkg-support/cmake/sdl2-config.cmake @@ -2,9 +2,9 @@ # This file is meant to be placed in a cmake subfolder of SDL2-devel-2.x.y-mingw if(CMAKE_SIZEOF_VOID_P EQUAL 4) - set(sdl2_config_path "${CMAKE_CURRENT_LIST_DIR}/../i686-w64-mingw32/lib/cmake/SDL2/sdl2-config.cmake") + set(sdl2_config_path "${CMAKE_CURRENT_LIST_DIR}/../i686-w64-mingw32/lib/cmake/SDL2/SDL2Config.cmake") elseif(CMAKE_SIZEOF_VOID_P EQUAL 8) - set(sdl2_config_path "${CMAKE_CURRENT_LIST_DIR}/../x86_64-w64-mingw32/lib/cmake/SDL2/sdl2-config.cmake") + set(sdl2_config_path "${CMAKE_CURRENT_LIST_DIR}/../x86_64-w64-mingw32/lib/cmake/SDL2/SDL2Config.cmake") else() set(SDL2_FOUND FALSE) return()