diff options
Diffstat (limited to 'contrib/SDL-3.2.8/build-scripts/setup-gdk-desktop.py')
| -rwxr-xr-x | contrib/SDL-3.2.8/build-scripts/setup-gdk-desktop.py | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/build-scripts/setup-gdk-desktop.py b/contrib/SDL-3.2.8/build-scripts/setup-gdk-desktop.py new file mode 100755 index 0000000..d2309a0 --- /dev/null +++ b/contrib/SDL-3.2.8/build-scripts/setup-gdk-desktop.py | |||
| @@ -0,0 +1,303 @@ | |||
| 1 | #!/usr/bin/env python | ||
| 2 | |||
| 3 | import argparse | ||
| 4 | import functools | ||
| 5 | import logging | ||
| 6 | import os | ||
| 7 | from pathlib import Path | ||
| 8 | import re | ||
| 9 | import shutil | ||
| 10 | import subprocess | ||
| 11 | import tempfile | ||
| 12 | import textwrap | ||
| 13 | import urllib.request | ||
| 14 | import zipfile | ||
| 15 | |||
| 16 | # Update both variables when updating the GDK | ||
| 17 | GIT_REF = "June_2024_Update_1" | ||
| 18 | GDK_EDITION = "240601" # YYMMUU | ||
| 19 | |||
| 20 | logger = logging.getLogger(__name__) | ||
| 21 | |||
| 22 | class GdDesktopConfigurator: | ||
| 23 | def __init__(self, gdk_path, arch, vs_folder, vs_version=None, vs_toolset=None, temp_folder=None, git_ref=None, gdk_edition=None): | ||
| 24 | self.git_ref = git_ref or GIT_REF | ||
| 25 | self.gdk_edition = gdk_edition or GDK_EDITION | ||
| 26 | self.gdk_path = gdk_path | ||
| 27 | self.temp_folder = temp_folder or Path(tempfile.gettempdir()) | ||
| 28 | self.dl_archive_path = Path(self.temp_folder) / f"{ self.git_ref }.zip" | ||
| 29 | self.gdk_extract_path = Path(self.temp_folder) / f"GDK-{ self.git_ref }" | ||
| 30 | self.arch = arch | ||
| 31 | self.vs_folder = vs_folder | ||
| 32 | self._vs_version = vs_version | ||
| 33 | self._vs_toolset = vs_toolset | ||
| 34 | |||
| 35 | def download_archive(self) -> None: | ||
| 36 | gdk_url = f"https://github.com/microsoft/GDK/archive/refs/tags/{ GIT_REF }.zip" | ||
| 37 | logger.info("Downloading %s to %s", gdk_url, self.dl_archive_path) | ||
| 38 | urllib.request.urlretrieve(gdk_url, self.dl_archive_path) | ||
| 39 | assert self.dl_archive_path.is_file() | ||
| 40 | |||
| 41 | def extract_zip_archive(self) -> None: | ||
| 42 | extract_path = self.gdk_extract_path.parent | ||
| 43 | assert self.dl_archive_path.is_file() | ||
| 44 | logger.info("Extracting %s to %s", self.dl_archive_path, extract_path) | ||
| 45 | with zipfile.ZipFile(self.dl_archive_path) as zf: | ||
| 46 | zf.extractall(extract_path) | ||
| 47 | assert self.gdk_extract_path.is_dir(), f"{self.gdk_extract_path} must exist" | ||
| 48 | |||
| 49 | def extract_development_kit(self) -> None: | ||
| 50 | extract_dks_cmd = self.gdk_extract_path / "SetupScripts/ExtractXboxOneDKs.cmd" | ||
| 51 | assert extract_dks_cmd.is_file() | ||
| 52 | logger.info("Extracting GDK Development Kit: running %s", extract_dks_cmd) | ||
| 53 | cmd = ["cmd.exe", "/C", str(extract_dks_cmd), str(self.gdk_extract_path), str(self.gdk_path)] | ||
| 54 | logger.debug("Running %r", cmd) | ||
| 55 | subprocess.check_call(cmd) | ||
| 56 | |||
| 57 | def detect_vs_version(self) -> str: | ||
| 58 | vs_regex = re.compile("VS([0-9]{4})") | ||
| 59 | supported_vs_versions = [] | ||
| 60 | for p in self.gaming_grdk_build_path.iterdir(): | ||
| 61 | if not p.is_dir(): | ||
| 62 | continue | ||
| 63 | if m := vs_regex.match(p.name): | ||
| 64 | supported_vs_versions.append(m.group(1)) | ||
| 65 | logger.info(f"Supported Visual Studio versions: {supported_vs_versions}") | ||
| 66 | vs_versions = set(self.vs_folder.parts).intersection(set(supported_vs_versions)) | ||
| 67 | if not vs_versions: | ||
| 68 | raise RuntimeError("Visual Studio version is incompatible") | ||
| 69 | if len(vs_versions) > 1: | ||
| 70 | raise RuntimeError(f"Too many compatible VS versions found ({vs_versions})") | ||
| 71 | vs_version = vs_versions.pop() | ||
| 72 | logger.info(f"Used Visual Studio version: {vs_version}") | ||
| 73 | return vs_version | ||
| 74 | |||
| 75 | def detect_vs_toolset(self) -> str: | ||
| 76 | toolset_paths = [] | ||
| 77 | for ts_path in self.gdk_toolset_parent_path.iterdir(): | ||
| 78 | if not ts_path.is_dir(): | ||
| 79 | continue | ||
| 80 | ms_props = ts_path / "Microsoft.Cpp.props" | ||
| 81 | if not ms_props.is_file(): | ||
| 82 | continue | ||
| 83 | toolset_paths.append(ts_path.name) | ||
| 84 | logger.info("Detected Visual Studio toolsets: %s", toolset_paths) | ||
| 85 | assert toolset_paths, "Have we detected at least one toolset?" | ||
| 86 | |||
| 87 | def toolset_number(toolset: str) -> int: | ||
| 88 | if m:= re.match("[^0-9]*([0-9]+).*", toolset): | ||
| 89 | return int(m.group(1)) | ||
| 90 | return -9 | ||
| 91 | |||
| 92 | return max(toolset_paths, key=toolset_number) | ||
| 93 | |||
| 94 | @property | ||
| 95 | def vs_version(self) -> str: | ||
| 96 | if self._vs_version is None: | ||
| 97 | self._vs_version = self.detect_vs_version() | ||
| 98 | return self._vs_version | ||
| 99 | |||
| 100 | @property | ||
| 101 | def vs_toolset(self) -> str: | ||
| 102 | if self._vs_toolset is None: | ||
| 103 | self._vs_toolset = self.detect_vs_toolset() | ||
| 104 | return self._vs_toolset | ||
| 105 | |||
| 106 | @staticmethod | ||
| 107 | def copy_files_and_merge_into(srcdir: Path, dstdir: Path) -> None: | ||
| 108 | logger.info(f"Copy {srcdir} to {dstdir}") | ||
| 109 | for root, _, files in os.walk(srcdir): | ||
| 110 | dest_root = dstdir / Path(root).relative_to(srcdir) | ||
| 111 | if not dest_root.is_dir(): | ||
| 112 | dest_root.mkdir() | ||
| 113 | for file in files: | ||
| 114 | srcfile = Path(root) / file | ||
| 115 | dstfile = dest_root / file | ||
| 116 | shutil.copy(srcfile, dstfile) | ||
| 117 | |||
| 118 | def copy_msbuild(self) -> None: | ||
| 119 | vc_toolset_parent_path = self.vs_folder / "MSBuild/Microsoft/VC" | ||
| 120 | if 1: | ||
| 121 | logger.info(f"Detected compatible Visual Studio version: {self.vs_version}") | ||
| 122 | srcdir = vc_toolset_parent_path | ||
| 123 | dstdir = self.gdk_toolset_parent_path | ||
| 124 | assert srcdir.is_dir(), "Source directory must exist" | ||
| 125 | assert dstdir.is_dir(), "Destination directory must exist" | ||
| 126 | |||
| 127 | self.copy_files_and_merge_into(srcdir=srcdir, dstdir=dstdir) | ||
| 128 | |||
| 129 | @property | ||
| 130 | def game_dk_path(self) -> Path: | ||
| 131 | return self.gdk_path / "Microsoft GDK" | ||
| 132 | |||
| 133 | @property | ||
| 134 | def game_dk_latest_path(self) -> Path: | ||
| 135 | return self.game_dk_path / self.gdk_edition | ||
| 136 | |||
| 137 | @property | ||
| 138 | def windows_sdk_path(self) -> Path: | ||
| 139 | return self.gdk_path / "Windows Kits/10" | ||
| 140 | |||
| 141 | @property | ||
| 142 | def gaming_grdk_build_path(self) -> Path: | ||
| 143 | return self.game_dk_latest_path / "GRDK" | ||
| 144 | |||
| 145 | @property | ||
| 146 | def gdk_toolset_parent_path(self) -> Path: | ||
| 147 | return self.gaming_grdk_build_path / f"VS{self.vs_version}/flatDeployment/MSBuild/Microsoft/VC" | ||
| 148 | |||
| 149 | @property | ||
| 150 | def env(self) -> dict[str, str]: | ||
| 151 | game_dk = self.game_dk_path | ||
| 152 | game_dk_latest = self.game_dk_latest_path | ||
| 153 | windows_sdk_dir = self.windows_sdk_path | ||
| 154 | gaming_grdk_build = self.gaming_grdk_build_path | ||
| 155 | |||
| 156 | return { | ||
| 157 | "GRDKEDITION": f"{self.gdk_edition}", | ||
| 158 | "GameDK": f"{game_dk}\\", | ||
| 159 | "GameDKLatest": f"{ game_dk_latest }\\", | ||
| 160 | "WindowsSdkDir": f"{ windows_sdk_dir }\\", | ||
| 161 | "GamingGRDKBuild": f"{ gaming_grdk_build }\\", | ||
| 162 | "VSInstallDir": f"{ self.vs_folder }\\", | ||
| 163 | } | ||
| 164 | |||
| 165 | def create_user_props(self, path: Path) -> None: | ||
| 166 | vc_targets_path = self.gaming_grdk_build_path / f"VS{ self.vs_version }/flatDeployment/MSBuild/Microsoft/VC/{ self.vs_toolset }" | ||
| 167 | vc_targets_path16 = self.gaming_grdk_build_path / f"VS2019/flatDeployment/MSBuild/Microsoft/VC/{ self.vs_toolset }" | ||
| 168 | vc_targets_path17 = self.gaming_grdk_build_path / f"VS2022/flatDeployment/MSBuild/Microsoft/VC/{ self.vs_toolset }" | ||
| 169 | additional_include_directories = ";".join(str(p) for p in self.gdk_include_paths) | ||
| 170 | additional_library_directories = ";".join(str(p) for p in self.gdk_library_paths) | ||
| 171 | durango_xdk_install_path = self.gdk_path / "Microsoft GDK" | ||
| 172 | with path.open("w") as f: | ||
| 173 | f.write(textwrap.dedent(f"""\ | ||
| 174 | <?xml version="1.0" encoding="utf-8"?> | ||
| 175 | <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
| 176 | <PropertyGroup> | ||
| 177 | <VCTargetsPath>{ vc_targets_path }\\</VCTargetsPath> | ||
| 178 | <VCTargetsPath16>{ vc_targets_path16 }\\</VCTargetsPath16> | ||
| 179 | <VCTargetsPath17>{ vc_targets_path17 }\\</VCTargetsPath17> | ||
| 180 | <BWOI_GDK_Path>{ self.gaming_grdk_build_path }\\</BWOI_GDK_Path> | ||
| 181 | <Platform Condition="'$(Platform)' == ''">Gaming.Desktop.x64</Platform> | ||
| 182 | <Configuration Condition="'$(Configuration)' == ''">Debug</Configuration> | ||
| 183 | <XdkEditionTarget>{ self.gdk_edition }</XdkEditionTarget> | ||
| 184 | <DurangoXdkInstallPath>{ durango_xdk_install_path }</DurangoXdkInstallPath> | ||
| 185 | |||
| 186 | <DefaultXdkEditionRootVS2019>$(DurangoXdkInstallPath)\\{self.gdk_edition}\\GRDK\\VS2019\\flatDeployment\\MSBuild\\Microsoft\\VC\\{self.vs_toolset}\\Platforms\\$(Platform)\\</DefaultXdkEditionRootVS2019> | ||
| 187 | <XdkEditionRootVS2019>$(DurangoXdkInstallPath)\\{self.gdk_edition}\\GRDK\\VS2019\\flatDeployment\\MSBuild\\Microsoft\\VC\\{self.vs_toolset}\\Platforms\\$(Platform)\\</XdkEditionRootVS2019> | ||
| 188 | <DefaultXdkEditionRootVS2022>$(DurangoXdkInstallPath)\\{self.gdk_edition}\\GRDK\\VS2022\\flatDeployment\\MSBuild\\Microsoft\\VC\\{self.vs_toolset}\\Platforms\\$(Platform)\\</DefaultXdkEditionRootVS2022> | ||
| 189 | <XdkEditionRootVS2022>$(DurangoXdkInstallPath)\\{self.gdk_edition}\\GRDK\\VS2022\\flatDeployment\\MSBuild\\Microsoft\\VC\\{self.vs_toolset}\\Platforms\\$(Platform)\\</XdkEditionRootVS2022> | ||
| 190 | |||
| 191 | <Deterministic>true</Deterministic> | ||
| 192 | <DisableInstalledVCTargetsUse>true</DisableInstalledVCTargetsUse> | ||
| 193 | <ClearDevCommandPromptEnvVars>true</ClearDevCommandPromptEnvVars> | ||
| 194 | </PropertyGroup> | ||
| 195 | <ItemDefinitionGroup Condition="'$(Platform)' == 'Gaming.Desktop.x64'"> | ||
| 196 | <ClCompile> | ||
| 197 | <AdditionalIncludeDirectories>{ additional_include_directories };%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | ||
| 198 | </ClCompile> | ||
| 199 | <Link> | ||
| 200 | <AdditionalLibraryDirectories>{ additional_library_directories };%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> | ||
| 201 | </Link> | ||
| 202 | </ItemDefinitionGroup> | ||
| 203 | </Project> | ||
| 204 | """)) | ||
| 205 | |||
| 206 | @property | ||
| 207 | def gdk_include_paths(self) -> list[Path]: | ||
| 208 | return [ | ||
| 209 | self.gaming_grdk_build_path / "gamekit/include", | ||
| 210 | ] | ||
| 211 | |||
| 212 | @property | ||
| 213 | def gdk_library_paths(self) -> list[Path]: | ||
| 214 | return [ | ||
| 215 | self.gaming_grdk_build_path / f"gamekit/lib/{self.arch}", | ||
| 216 | ] | ||
| 217 | |||
| 218 | @property | ||
| 219 | def gdk_binary_path(self) -> list[Path]: | ||
| 220 | return [ | ||
| 221 | self.gaming_grdk_build_path / "bin", | ||
| 222 | self.game_dk_path / "bin", | ||
| 223 | ] | ||
| 224 | |||
| 225 | @property | ||
| 226 | def build_env(self) -> dict[str, str]: | ||
| 227 | gdk_include = ";".join(str(p) for p in self.gdk_include_paths) | ||
| 228 | gdk_lib = ";".join(str(p) for p in self.gdk_library_paths) | ||
| 229 | gdk_path = ";".join(str(p) for p in self.gdk_binary_path) | ||
| 230 | return { | ||
| 231 | "GDK_INCLUDE": gdk_include, | ||
| 232 | "GDK_LIB": gdk_lib, | ||
| 233 | "GDK_PATH": gdk_path, | ||
| 234 | } | ||
| 235 | |||
| 236 | def print_env(self) -> None: | ||
| 237 | for k, v in self.env.items(): | ||
| 238 | print(f"set \"{k}={v}\"") | ||
| 239 | print() | ||
| 240 | for k, v in self.build_env.items(): | ||
| 241 | print(f"set \"{k}={v}\"") | ||
| 242 | print() | ||
| 243 | print(f"set \"PATH=%GDK_PATH%;%PATH%\"") | ||
| 244 | print(f"set \"LIB=%GDK_LIB%;%LIB%\"") | ||
| 245 | print(f"set \"INCLUDE=%GDK_INCLUDE%;%INCLUDE%\"") | ||
| 246 | |||
| 247 | |||
| 248 | def main(): | ||
| 249 | logging.basicConfig(level=logging.INFO) | ||
| 250 | parser = argparse.ArgumentParser(allow_abbrev=False) | ||
| 251 | parser.add_argument("--arch", choices=["amd64"], default="amd64", help="Architecture") | ||
| 252 | parser.add_argument("--download", action="store_true", help="Download GDK") | ||
| 253 | parser.add_argument("--extract", action="store_true", help="Extract downloaded GDK") | ||
| 254 | parser.add_argument("--copy-msbuild", action="store_true", help="Copy MSBuild files") | ||
| 255 | parser.add_argument("--temp-folder", help="Temporary folder where to download and extract GDK") | ||
| 256 | parser.add_argument("--gdk-path", required=True, type=Path, help="Folder where to store the GDK") | ||
| 257 | parser.add_argument("--ref-edition", type=str, help="Git ref and GDK edition separated by comma") | ||
| 258 | parser.add_argument("--vs-folder", required=True, type=Path, help="Installation folder of Visual Studio") | ||
| 259 | parser.add_argument("--vs-version", required=False, type=int, help="Visual Studio version") | ||
| 260 | parser.add_argument("--vs-toolset", required=False, help="Visual Studio toolset (e.g. v150)") | ||
| 261 | parser.add_argument("--props-folder", required=False, type=Path, default=Path(), help="Visual Studio toolset (e.g. v150)") | ||
| 262 | parser.add_argument("--no-user-props", required=False, dest="user_props", action="store_false", help="Don't ") | ||
| 263 | args = parser.parse_args() | ||
| 264 | |||
| 265 | logging.basicConfig(level=logging.INFO) | ||
| 266 | |||
| 267 | git_ref = None | ||
| 268 | gdk_edition = None | ||
| 269 | if args.ref_edition is not None: | ||
| 270 | git_ref, gdk_edition = args.ref_edition.split(",", 1) | ||
| 271 | try: | ||
| 272 | int(gdk_edition) | ||
| 273 | except ValueError: | ||
| 274 | parser.error("Edition should be an integer (YYMMUU) (Y=year M=month U=update)") | ||
| 275 | |||
| 276 | configurator = GdDesktopConfigurator( | ||
| 277 | arch=args.arch, | ||
| 278 | git_ref=git_ref, | ||
| 279 | gdk_edition=gdk_edition, | ||
| 280 | vs_folder=args.vs_folder, | ||
| 281 | vs_version=args.vs_version, | ||
| 282 | vs_toolset=args.vs_toolset, | ||
| 283 | gdk_path=args.gdk_path, | ||
| 284 | temp_folder=args.temp_folder, | ||
| 285 | ) | ||
| 286 | |||
| 287 | if args.download: | ||
| 288 | configurator.download_archive() | ||
| 289 | |||
| 290 | if args.extract: | ||
| 291 | configurator.extract_zip_archive() | ||
| 292 | |||
| 293 | configurator.extract_development_kit() | ||
| 294 | |||
| 295 | if args.copy_msbuild: | ||
| 296 | configurator.copy_msbuild() | ||
| 297 | |||
| 298 | if args.user_props: | ||
| 299 | configurator.print_env() | ||
| 300 | configurator.create_user_props(args.props_folder / "Directory.Build.props") | ||
| 301 | |||
| 302 | if __name__ == "__main__": | ||
| 303 | raise SystemExit(main()) | ||
