mirror of
				https://github.com/libsdl-org/SDL.git
				synced 2025-11-04 01:34:38 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			891 lines
		
	
	
		
			43 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			891 lines
		
	
	
		
			43 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
#!/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", "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():
 | 
						|
                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/SDL3/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_xcframework(self, configuration: str="Release") -> None:
 | 
						|
        dmg_in = self.root / f"Xcode/SDL/build/SDL3.dmg"
 | 
						|
        dmg_in.unlink(missing_ok=True)
 | 
						|
        self.executer.run(["xcodebuild", "-project", str(self.root / "Xcode/SDL/SDL.xcodeproj"), "-target", "SDL3.dmg", "-configuration", configuration])
 | 
						|
        if self.dry:
 | 
						|
            dmg_in.parent.mkdir(parents=True, exist_ok=True)
 | 
						|
            dmg_in.touch()
 | 
						|
 | 
						|
        assert dmg_in.is_file(), "SDL3.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 = (
 | 
						|
            ("build-scripts/pkg-support/mingw/INSTALL.txt", ""),
 | 
						|
            ("build-scripts/pkg-support/mingw/Makefile", ""),
 | 
						|
            ("build-scripts/pkg-support/mingw/cmake/SDL3Config.cmake", "cmake/"),
 | 
						|
            ("build-scripts/pkg-support/mingw/cmake/SDL3ConfigVersion.cmake", "cmake/"),
 | 
						|
            ("BUGS.txt", ""),
 | 
						|
            ("CREDITS.md", ""),
 | 
						|
            ("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 SDL3.dll debug information into debug library
 | 
						|
        # objcopy --only-keep-debug SDL3.dll SDL3.debug.dll
 | 
						|
        # objcopy --add-gnu-debuglink=SDL3.debug.dll SDL3.dll
 | 
						|
        # objcopy --strip-debug SDL3.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/SDL_test/{platform}/{configuration}/{self.project}_test.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)
 | 
						|
 | 
						|
        projects = [
 | 
						|
            self.root / "VisualC/SDL/SDL.vcxproj",
 | 
						|
            self.root / "VisualC/SDL_test/SDL_test.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()
 | 
						|
            test_path.parent.mkdir(parents=True, exist_ok=True)
 | 
						|
            test_path.touch()
 | 
						|
 | 
						|
        assert dll_path.is_file(), "SDL3.dll has not been created"
 | 
						|
        assert pdb_path.is_file(), "SDL3.pdb has not been created"
 | 
						|
        assert imp_path.is_file(), "SDL3.lib has not been created"
 | 
						|
        assert test_path.is_file(), "SDL3_test.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, test=test_path)
 | 
						|
 | 
						|
    def build_vs_cmake(self, arch: str, arch_cmake: str) -> VcArchDevel:
 | 
						|
        build_path = self.root / f"build-vs-{arch}"
 | 
						|
        install_path = build_path / "prefix"
 | 
						|
        dll_path = install_path / f"bin/{self.project}.dll"
 | 
						|
        pdb_path = install_path / f"bin/{self.project}.pdb"
 | 
						|
        imp_path = install_path / f"lib/{self.project}.lib"
 | 
						|
        test_path = install_path / f"lib/{self.project}_test.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)
 | 
						|
 | 
						|
        build_type = "Release"
 | 
						|
 | 
						|
        shutil.rmtree(install_path, ignore_errors=True)
 | 
						|
        build_path.mkdir(parents=True, exist_ok=True)
 | 
						|
        with self.section_printer.group(f"Configure VC CMake project for {arch}"):
 | 
						|
            self.executer.run([
 | 
						|
                "cmake", "-S", str(self.root), "-B", str(build_path),
 | 
						|
                "--fresh",
 | 
						|
                "-A", arch_cmake,
 | 
						|
                "-DSDL_SHARED=ON",
 | 
						|
                "-DSDL_STATIC=OFF",
 | 
						|
                "-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_INSTALL_PREFIX={install_path}",
 | 
						|
                # MSVC debug information format flags are selected by an abstraction
 | 
						|
                "-DCMAKE_POLICY_DEFAULT_CMP0141=NEW",
 | 
						|
                # MSVC debug information format
 | 
						|
                "-DCMAKE_MSVC_DEBUG_INFORMATION_FORMAT=ProgramDatabase",
 | 
						|
                # Linker flags for executables
 | 
						|
                "-DCMAKE_EXE_LINKER_FLAGS=-DEBUG",
 | 
						|
                # Linker flag for shared libraries
 | 
						|
                "-DCMAKE_SHARED_LINKER_FLAGS=-INCREMENTAL:NO -DEBUG -OPT:REF -OPT:ICF",
 | 
						|
                # MSVC runtime library flags are selected by an abstraction
 | 
						|
                "-DCMAKE_POLICY_DEFAULT_CMP0091=NEW",
 | 
						|
                # Use statically linked runtime (-MT) (ideally, should be "MultiThreaded$<$<CONFIG:Debug>:Debug>")
 | 
						|
                "-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded",
 | 
						|
            ])
 | 
						|
 | 
						|
        with self.section_printer.group(f"Build VC CMake project for {arch}"):
 | 
						|
            self.executer.run(["cmake", "--build", str(build_path), "--verbose", "--config", build_type])
 | 
						|
        with self.section_printer.group(f"Install VC CMake project for {arch}"):
 | 
						|
            self.executer.run(["cmake", "--install", str(build_path), "--config", build_type])
 | 
						|
 | 
						|
        assert dll_path.is_file(), "SDL3.dll has not been created"
 | 
						|
        assert pdb_path.is_file(), "SDL3.pdb has not been created"
 | 
						|
        assert imp_path.is_file(), "SDL3.lib has not been created"
 | 
						|
        assert test_path.is_file(), "SDL3_test.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, 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.test, arcrelpath=f"lib/{arch}/{binaries.test.name}")
 | 
						|
 | 
						|
            zip_directory(zf, directory=self.root / "include/SDL3", arcrelpath="include/SDL3")
 | 
						|
            zip_directory(zf, directory=self.root / "docs", arcrelpath="docs")
 | 
						|
            zip_directory(zf, directory=self.root / "VisualC/pkg-support/cmake", arcrelpath="cmake")
 | 
						|
            zip_file(zf, path=self.root / "cmake/sdlcpu.cmake", arcrelpath="cmake/sdlcpu.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
 | 
						|
 | 
						|
    def detect_android_api(self, android_home: str) -> typing.Optional[int]:
 | 
						|
        platform_dirs = list(Path(p) for p in glob.glob(f"{android_home}/platforms/android-*"))
 | 
						|
        re_platform = re.compile("android-([0-9]+)")
 | 
						|
        platform_versions = []
 | 
						|
        for platform_dir in platform_dirs:
 | 
						|
            logger.debug("Found Android Platform SDK: %s", platform_dir)
 | 
						|
            if m:= re_platform.match(platform_dir.name):
 | 
						|
                platform_versions.append(int(m.group(1)))
 | 
						|
        platform_versions.sort()
 | 
						|
        logger.info("Available platform versions: %s", platform_versions)
 | 
						|
        platform_versions = list(filter(lambda v: v >= ANDROID_MINIMUM_API, platform_versions))
 | 
						|
        logger.info("Valid platform versions (>=%d): %s", ANDROID_MINIMUM_API, platform_versions)
 | 
						|
        if not platform_versions:
 | 
						|
            return None
 | 
						|
        android_api = platform_versions[0]
 | 
						|
        logger.info("Selected API version %d", android_api)
 | 
						|
        return android_api
 | 
						|
 | 
						|
    def get_prefab_json_text(self) -> str:
 | 
						|
        return textwrap.dedent(f"""\
 | 
						|
            {{
 | 
						|
                "schema_version": 2,
 | 
						|
                "name": "{self.project}",
 | 
						|
                "version": "{self.version}",
 | 
						|
                "dependencies": []
 | 
						|
            }}
 | 
						|
        """)
 | 
						|
 | 
						|
    def get_prefab_module_json_text(self, library_name: str, extra_libs: list[str]) -> str:
 | 
						|
        export_libraries_str = ", ".join(f"\"-l{lib}\"" for lib in extra_libs)
 | 
						|
        return textwrap.dedent(f"""\
 | 
						|
            {{
 | 
						|
                "export_libraries": [{export_libraries_str}],
 | 
						|
                "library_name": "lib{library_name}"
 | 
						|
            }}
 | 
						|
        """)
 | 
						|
 | 
						|
    def get_prefab_abi_json_text(self, abi: str, cpp: bool, shared: bool) -> str:
 | 
						|
        return textwrap.dedent(f"""\
 | 
						|
            {{
 | 
						|
              "abi": "{abi}",
 | 
						|
              "api": {ANDROID_MINIMUM_API},
 | 
						|
              "ndk": {ANDROID_MINIMUM_NDK},
 | 
						|
              "stl": "{'c++_shared' if cpp else 'none'}",
 | 
						|
              "static": {'true' if not shared else 'false'}
 | 
						|
            }}
 | 
						|
        """)
 | 
						|
 | 
						|
    def get_android_manifest_text(self) -> str:
 | 
						|
        return textwrap.dedent(f"""\
 | 
						|
            <manifest
 | 
						|
                xmlns:android="http://schemas.android.com/apk/res/android"
 | 
						|
                package="org.libsdl.android.{self.project}" android:versionCode="1"
 | 
						|
                android:versionName="1.0">
 | 
						|
                <uses-sdk android:minSdkVersion="{ANDROID_MINIMUM_API}"
 | 
						|
                          android:targetSdkVersion="{ANDROID_TARGET_API}" />
 | 
						|
            </manifest>
 | 
						|
        """)
 | 
						|
 | 
						|
    def create_android_archives(self, android_api: int, android_home: Path, android_ndk_home: Path, android_abis: list[str]) -> None:
 | 
						|
        cmake_toolchain_file = Path(android_ndk_home) / "build/cmake/android.toolchain.cmake"
 | 
						|
        if not cmake_toolchain_file.exists():
 | 
						|
            logger.error("CMake toolchain file does not exist (%s)", cmake_toolchain_file)
 | 
						|
            raise SystemExit(1)
 | 
						|
        aar_path =  self.dist_path / f"{self.project}-{self.version}.aar"
 | 
						|
        added_global_files = False
 | 
						|
        with zipfile.ZipFile(aar_path, "w", compression=zipfile.ZIP_DEFLATED) as zip_object:
 | 
						|
            def configure_file(path: Path) -> str:
 | 
						|
                text = path.read_text()
 | 
						|
                text = text.replace("@PROJECT_VERSION@", self.version)
 | 
						|
                text = text.replace("@PROJECT_NAME@", self.project)
 | 
						|
                return text
 | 
						|
 | 
						|
            install_txt = configure_file(self.root / "build-scripts/pkg-support/android/INSTALL.md.in")
 | 
						|
            zip_object.writestr("INSTALL.md", install_txt)
 | 
						|
 | 
						|
            project_description = {
 | 
						|
                "name": self.project,
 | 
						|
                "version": self.version,
 | 
						|
                "git-hash": self.commit,
 | 
						|
            }
 | 
						|
            zip_object.writestr("description.json", json.dumps(project_description, indent=0))
 | 
						|
            main_py = configure_file(self.root / "build-scripts/pkg-support/android/__main__.py.in")
 | 
						|
            zip_object.writestr("__main__.py", main_py)
 | 
						|
 | 
						|
            zip_object.writestr("AndroidManifest.xml", self.get_android_manifest_text())
 | 
						|
            zip_object.write(self.root / "android-project/app/proguard-rules.pro", arcname="proguard.txt")
 | 
						|
            zip_object.write(self.root / "LICENSE.txt", arcname="META-INF/LICENSE.txt")
 | 
						|
            zip_object.write(self.root / "cmake/sdlcpu.cmake", arcname="cmake/sdlcpu.cmake")
 | 
						|
            zip_object.write(self.root / "build-scripts/pkg-support/android/cmake/SDL3Config.cmake", arcname="cmake/SDL3Config.cmake")
 | 
						|
            zip_object.write(self.root / "build-scripts/pkg-support/android/cmake/SDL3ConfigVersion.cmake", arcname="cmake/SDL3ConfigVersion.cmake")
 | 
						|
            zip_object.writestr("prefab/prefab.json", self.get_prefab_json_text())
 | 
						|
            self._zip_add_git_hash(zip_file=zip_object)
 | 
						|
 | 
						|
            for android_abi in android_abis:
 | 
						|
                with self.section_printer.group(f"Building for Android {android_api} {android_abi}"):
 | 
						|
                    build_dir = self.root / "build-android" / f"{android_abi}-build"
 | 
						|
                    install_dir = self.root / "install-android" / f"{android_abi}-install"
 | 
						|
                    shutil.rmtree(install_dir, ignore_errors=True)
 | 
						|
                    assert not install_dir.is_dir(), f"{install_dir} should not exist prior to build"
 | 
						|
                    cmake_args = [
 | 
						|
                        "cmake",
 | 
						|
                        "-S", str(self.root),
 | 
						|
                        "-B", str(build_dir),
 | 
						|
                        "--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}"''',
 | 
						|
                        "-DCMAKE_BUILD_TYPE=RelWithDebInfo",
 | 
						|
                        f"-DCMAKE_TOOLCHAIN_FILE={cmake_toolchain_file}",
 | 
						|
                        f"-DANDROID_PLATFORM={android_api}",
 | 
						|
                        f"-DANDROID_ABI={android_abi}",
 | 
						|
                        "-DCMAKE_POSITION_INDEPENDENT_CODE=ON",
 | 
						|
                        "-DSDL_SHARED=ON",
 | 
						|
                        "-DSDL_STATIC=OFF",
 | 
						|
                        "-DSDL_TEST_LIBRARY=ON",
 | 
						|
                        "-DSDL_DISABLE_ANDROID_JAR=OFF",
 | 
						|
                        "-DSDL_TESTS=OFF",
 | 
						|
                        f"-DCMAKE_INSTALL_PREFIX={install_dir}",
 | 
						|
                        "-DSDL_DISABLE_INSTALL=OFF",
 | 
						|
                        "-DSDL_DISABLE_INSTALL_DOCS=OFF",
 | 
						|
                        "-DCMAKE_INSTALL_INCLUDEDIR=include ",
 | 
						|
                        "-DCMAKE_INSTALL_LIBDIR=lib",
 | 
						|
                        "-DCMAKE_INSTALL_DATAROOTDIR=share",
 | 
						|
                        "-DCMAKE_BUILD_TYPE=Release",
 | 
						|
                        f"-DSDL_ANDROID_HOME={android_home}",
 | 
						|
                        f"-G{self.cmake_generator}",
 | 
						|
                    ]
 | 
						|
                    build_args = [
 | 
						|
                        "cmake",
 | 
						|
                        "--build", str(build_dir),
 | 
						|
                        "--config", "RelWithDebInfo",
 | 
						|
                    ]
 | 
						|
                    install_args = [
 | 
						|
                        "cmake",
 | 
						|
                        "--install", str(build_dir),
 | 
						|
                        "--config", "RelWithDebInfo",
 | 
						|
                    ]
 | 
						|
                    self.executer.run(cmake_args)
 | 
						|
                    self.executer.run(build_args)
 | 
						|
                    self.executer.run(install_args)
 | 
						|
 | 
						|
                    main_so_library = install_dir / "lib" / f"lib{self.project}.so"
 | 
						|
                    logger.debug("Expecting library %s", main_so_library)
 | 
						|
                    assert main_so_library.is_file(), "CMake should have built a shared library (e.g. libSDL3.so)"
 | 
						|
 | 
						|
                    test_library = install_dir / "lib" / f"lib{self.project}_test.a"
 | 
						|
                    logger.debug("Expecting library %s", test_library)
 | 
						|
                    assert test_library.is_file(), "CMake should have built a static test library (e.g. libSDL3_test.a)"
 | 
						|
 | 
						|
                    java_jar = install_dir / f"share/java/{self.project}/{self.project}-{self.version}.jar"
 | 
						|
                    logger.debug("Expecting java archive: %s", java_jar)
 | 
						|
                    assert java_jar.is_file(), "CMake should have compiled the java sources and archived them into a JAR"
 | 
						|
 | 
						|
                    javasources_jar = install_dir / f"share/java/{self.project}/{self.project}-{self.version}-sources.jar"
 | 
						|
                    logger.debug("Expecting java sources archive %s", javasources_jar)
 | 
						|
                    assert javasources_jar.is_file(), "CMake should have archived the java sources into a JAR"
 | 
						|
 | 
						|
                    javadoc_dir = install_dir / "share/javadoc" / self.project
 | 
						|
                    logger.debug("Expecting javadoc archive %s", javadoc_dir)
 | 
						|
                    assert javadoc_dir.is_dir(), "CMake should have built javadoc documentation for the java sources"
 | 
						|
                    if not added_global_files:
 | 
						|
                        zip_object.write(java_jar, arcname="classes.jar")
 | 
						|
                        zip_object.write(javasources_jar, arcname="classes-sources.jar", )
 | 
						|
                        doc_jar_path = install_dir / "classes-doc.jar"
 | 
						|
 | 
						|
                        javadoc_jar_args = ["jar", "--create", "--file", str(doc_jar_path)]
 | 
						|
                        for fn in javadoc_dir.iterdir():
 | 
						|
                            javadoc_jar_args.extend(["-C", str(javadoc_dir), fn.name])
 | 
						|
                        self.executer.run(javadoc_jar_args)
 | 
						|
                        zip_object.write(doc_jar_path, arcname="classes-doc.jar")
 | 
						|
 | 
						|
                        for header in (install_dir / "include" / self.project).iterdir():
 | 
						|
                            zip_object.write(header, arcname=f"prefab/modules/{self.project}/include/{self.project}/{header.name}")
 | 
						|
 | 
						|
                        zip_object.writestr(f"prefab/modules/{self.project}/module.json", self.get_prefab_module_json_text(library_name=self.project, extra_libs=[]))
 | 
						|
                        zip_object.writestr(f"prefab/modules/{self.project}_test/module.json", self.get_prefab_module_json_text(library_name=f"{self.project}_test", extra_libs=list()))
 | 
						|
                        added_global_files = True
 | 
						|
 | 
						|
                    zip_object.write(main_so_library, arcname=f"prefab/modules/{self.project}/libs/android.{android_abi}/lib{self.project}.so")
 | 
						|
                    zip_object.writestr(f"prefab/modules/{self.project}/libs/android.{android_abi}/abi.json", self.get_prefab_abi_json_text(abi=android_abi, cpp=False, shared=True))
 | 
						|
                    zip_object.write(test_library, arcname=f"prefab/modules/{self.project}_test/libs/android.{android_abi}/lib{self.project}_test.a")
 | 
						|
                    zip_object.writestr(f"prefab/modules/{self.project}_test/libs/android.{android_abi}/abi.json", self.get_prefab_abi_json_text(abi=android_abi, cpp=False, shared=False))
 | 
						|
 | 
						|
        self.artifacts[f"android-aar"] = aar_path
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def extract_sdl_version(cls, root: Path, project: str) -> str:
 | 
						|
        with open(root / f"include/{project}/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_MICRO_VERSION\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. SDL3")
 | 
						|
    parser.add_argument("--create", choices=["source", "mingw", "win32", "xcframework", "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 "xcframework" in args.actions:
 | 
						|
        if platform.system() != "Darwin" and not args.dry:
 | 
						|
            parser.error("xcframework artifact(s) can only be built on Darwin")
 | 
						|
 | 
						|
        releaser.create_xcframework()
 | 
						|
 | 
						|
    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)
 | 
						|
        arm64 = releaser.build_vs_cmake(arch="arm64", arch_cmake="ARM64")
 | 
						|
        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,
 | 
						|
                "arm64": arm64,
 | 
						|
            }
 | 
						|
            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())
 |