1#!/usr/bin/python3 2# 3# Copyright 2024 The ANGLE Project Authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6# 7# winappsdk_setup.py: 8# Downloads and processes a specific version of Windows App SDK 9# for use when building ANGLE for WinUI 3 apps. 10# 11# Usage: 12# python3 winappsdk_setup.py [--version <package-version>] [--output /path/to/dest] [--force] 13 14import argparse 15import fnmatch 16import io 17import os 18import subprocess 19import urllib.request 20import zipfile 21 22package_id = "Microsoft.WindowsAppSDK" 23uap_version = "10.0.18362" 24 25default_version = "1.3.230724000" 26default_output = "third_party\WindowsAppSDK" 27 28 29def download_and_extract_nuget_package(force, version, output_path): 30 # first check if the last download was successful 31 stamp = os.path.join(output_path, f"{version}.stamp") 32 if os.path.exists(stamp) and not force: 33 return 34 35 # download the package 36 nuget_url = f"https://www.nuget.org/api/v2/package/{package_id}/{version}" 37 with urllib.request.urlopen(nuget_url) as response: 38 if response.status == 200: 39 package_bytes = io.BytesIO(response.read()) 40 # extract 41 os.makedirs(output_path, exist_ok=True) 42 with zipfile.ZipFile(package_bytes, 'r') as zip_ref: 43 zip_ref.extractall(output_path) 44 # make a stamp to avoid re-downloading 45 with open(stamp, 'w') as file: 46 pass 47 else: 48 print( 49 f"Failed to download the {package_id} NuGet package. Status code: {response.status}" 50 ) 51 sys.exit(1) 52 53 54def get_win_sdk(): 55 p = os.getenv("ProgramFiles(x86)", "C:\\Program Files (x86)") 56 win_sdk = os.getenv("WindowsSdkDir") 57 if not win_sdk: 58 win_sdk = os.path.join(p, "Windows Kits", "10") 59 print("%WindowsSdkDir% not set. Defaulting to", win_sdk) 60 print("You might want to run this from a Visual Studio cmd prompt.") 61 return win_sdk 62 63 64def get_latest_sdk_version(win_sdk): 65 files = os.listdir(os.path.join(win_sdk, "bin")) 66 filtered_files = fnmatch.filter(files, "10.0.*") 67 sorted_files = sorted(filtered_files, reverse=True) 68 return sorted_files[0] 69 70 71def run_winmdidl(force, win_sdk_bin, output_path, winmd, stamp_filename=None): 72 include = os.path.join(output_path, "include") 73 lib = os.path.join(output_path, "lib") 74 75 if stamp_filename: 76 stamp = os.path.join(include, stamp_filename) 77 else: 78 no_ext = os.path.splitext(os.path.basename(winmd))[0] 79 stamp = os.path.join(include, no_ext + ".idl") 80 if os.path.exists(stamp) and not force: 81 return 82 83 winmdidl = os.path.join(win_sdk_bin, "winmdidl.exe") 84 command = [ 85 winmdidl, 86 os.path.join(lib, winmd), "/metadata_dir:C:\\Windows\\System32\\WinMetadata", 87 "/metadata_dir:" + os.path.join(lib, f"uap{uap_version}"), 88 "/metadata_dir:" + os.path.join(lib, "uap10.0"), "/outdir:" + include, "/nologo" 89 ] 90 subprocess.run(command, check=True, cwd=include) 91 92 93def run_midlrt(force, win_sdk_bin, output_path, idl): 94 include = os.path.join(output_path, "include") 95 lib = os.path.join(output_path, "lib") 96 97 no_ext = os.path.splitext(os.path.basename(idl))[0] 98 stamp = os.path.join(include, no_ext + ".h") 99 if os.path.exists(stamp) and not force: 100 return 101 102 midlrt = os.path.join(win_sdk_bin, "midlrt.exe") 103 command = [ 104 midlrt, 105 os.path.join(include, idl), "/metadata_dir", "C:\\Windows\\System32\\WinMetadata", 106 "/ns_prefix", "/nomidl", "/nologo" 107 ] 108 subprocess.run(command, check=True, cwd=include) 109 110 111if __name__ == "__main__": 112 113 parser = argparse.ArgumentParser(description="Setup the Windows App SDK.") 114 parser.add_argument( 115 "--version", default=default_version, help="the package version to download") 116 parser.add_argument( 117 "--output", default=default_output, help="the destination path for extracted contents") 118 parser.add_argument( 119 "--force", action="store_true", help="ignore existing files and re-download") 120 args = parser.parse_args() 121 122 if os.path.isabs(args.output): 123 output_path = args.output 124 else: 125 output_path = os.path.join(os.getcwd(), args.output) 126 127 win_sdk = get_win_sdk() 128 latest_sdk = get_latest_sdk_version(win_sdk) 129 arch = "x64" 130 win_sdk_bin = os.path.join(win_sdk, "bin", latest_sdk, arch) 131 132 winmd_files = { 133 f"uap{uap_version}\\Microsoft.Foundation.winmd": None, 134 f"uap{uap_version}\\Microsoft.Graphics.winmd": "Microsoft.Graphics.DirectX.idl", 135 f"uap{uap_version}\\Microsoft.UI.winmd": None, 136 "uap10.0\\Microsoft.UI.Text.winmd": None, 137 "uap10.0\\Microsoft.UI.Xaml.winmd": None, 138 "uap10.0\\Microsoft.Web.WebView2.Core.winmd": None, 139 } 140 141 idl_files = [ 142 "Microsoft.Foundation.idl", 143 "Microsoft.Graphics.DirectX.idl", 144 "Microsoft.UI.Composition.idl", 145 "Microsoft.UI.Composition.SystemBackdrops.idl", 146 "Microsoft.UI.Dispatching.idl", 147 "Microsoft.UI.idl", 148 "Microsoft.UI.Input.idl", 149 "Microsoft.UI.Text.idl", 150 "Microsoft.UI.Windowing.idl", 151 "Microsoft.UI.Xaml.Automation.idl", 152 "Microsoft.UI.Xaml.Automation.Peers.idl", 153 "Microsoft.UI.Xaml.Automation.Provider.idl", 154 "Microsoft.UI.Xaml.Automation.Text.idl", 155 "Microsoft.UI.Xaml.Controls.idl", 156 "Microsoft.UI.Xaml.Controls.Primitives.idl", 157 "Microsoft.UI.Xaml.Data.idl", 158 "Microsoft.UI.Xaml.Documents.idl", 159 "Microsoft.UI.Xaml.idl", 160 "Microsoft.UI.Xaml.Input.idl", 161 "Microsoft.UI.Xaml.Interop.idl", 162 "Microsoft.UI.Xaml.Media.Animation.idl", 163 "Microsoft.UI.Xaml.Media.idl", 164 "Microsoft.UI.Xaml.Media.Imaging.idl", 165 "Microsoft.UI.Xaml.Media.Media3D.idl", 166 "Microsoft.UI.Xaml.Navigation.idl", 167 "Microsoft.Web.WebView2.Core.idl", 168 ] 169 170 progress = 1 171 total = len(winmd_files) + len(idl_files) + 1 172 173 # Download the NuGet package that contains all the files we need 174 print( 175 f"[{progress}/{total}] Downloading {package_id} NuGet package version {args.version} to {output_path}..." 176 ) 177 download_and_extract_nuget_package(args.force, args.version, output_path) 178 179 # Generate .idl files from the .winmd files 180 for winmd, header in winmd_files.items(): 181 progress += 1 182 print(f"[{progress}/{total}] Processing WINMD {winmd}...") 183 run_winmdidl(args.force, win_sdk_bin, output_path, winmd, header) 184 185 # Generate all the C++ headers and related files from the .idl files 186 for idl in idl_files: 187 progress += 1 188 print(f"[{progress}/{total}] Processing IDL {idl}...") 189 run_midlrt(args.force, win_sdk_bin, output_path, idl) 190 191 print("Setup is complete.") 192 print("") 193 print(f"Pass winappsdk_dir=\"{output_path}\" to gn gen or ninja.") 194