xref: /aosp_15_r20/external/angle/scripts/winappsdk_setup.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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