mirror of
https://github.com/mirkobrombin/raven.git
synced 2026-11-05 21:56:33 +00:00
scripts/generate-winrt-header.py writes, from the bindings windows-rs generates from Microsoft's Windows metadata (pinned revision, each file SHA-256 checked), the C declarations raven-web and the shared raven-winrt plumbing use: - native/upstream/raven-web/windows.web.http.h, the Windows.Web.Http, Headers and Filters namespaces, with what they need of other namespaces at its minimum (the certificate and socket enumerations, forward declarations of the classes they pass); - native/upstream/raven-winrt/foundation.h, what they need of Windows.Foundation, its collections and Windows.Storage.Streams, and the generic instances of those types alone. They hold each interface's identifier, its methods in vtable order with their types, the structures and enumerations, and the generic instances used, whose identifiers are computed from their type signatures as the Windows Runtime type system specifies; the generic interfaces come from windows-rs's collections, future and reference crates. Every method is checked against its raw vtable entry, for as many arguments and as indirect, before anything is written. Nothing depends on widl or on Wine's IDL files, nor on MinGW's headers for these namespaces, whose declarations differ between its releases (13.0 lacks IInputStream, which 14.0 has); only its classic COM headers are included. Each interface is guarded as widl's headers guard it. --check compares the committed headers with what the pinned sources give. Assisted-by: Claude Code:claude-opus-5-5 AI-Scope: the generator and the headers it writes, checked against widl's header from the previous IDL and MinGW's headers
558 lines
31 KiB
Python
558 lines
31 KiB
Python
#!/usr/bin/env python3
|
|
"""Writes native/upstream/raven-winrt/foundation.h and native/upstream/raven-web/windows.web.http.h, the Windows Runtime types Raven's
|
|
components use as C declarations, from Microsoft's Windows metadata as the windows-rs project distributes it (MIT OR Apache-2.0, used
|
|
under MIT). Its generated bindings give each interface's identifier, the order of its methods and their signatures, the raw vtable layout
|
|
each method is checked against, and the type signatures the identifiers of generic instances are computed from, as the Windows Runtime
|
|
type system specifies; its windows-collections, windows-future and windows-reference crates give the generic interfaces.
|
|
|
|
windows.web.http.h declares the Windows.Web.Http, Windows.Web.Http.Headers and Windows.Web.Http.Filters namespaces, and what they need of
|
|
others that are not Windows.Foundation or Windows.Storage.Streams at their minimum. foundation.h, which raven-winrt and so each component
|
|
includes, declares what they need of Windows.Foundation, its collections and Windows.Storage.Streams, with the generic instances of those
|
|
types alone; the declarations of MinGW's headers for those namespaces differ between its releases, and only its classic COM headers are
|
|
included. Each interface sits under the guard widl's headers give it. With --check, the committed headers are compared with what the
|
|
pinned sources give instead of being written."""
|
|
import argparse
|
|
import hashlib
|
|
from pathlib import Path
|
|
import re
|
|
import sys
|
|
import urllib.request
|
|
|
|
REVISION = "58217a690d6256ee229044a174de67d72d28bc35"
|
|
SOURCE = f"https://raw.githubusercontent.com/microsoft/windows-rs/{REVISION}/crates/libs"
|
|
# The windows crate's modules: the namespaces declared here, then those whose types they name.
|
|
MODULES = (("Windows.Web.Http", "2d16c9b0e362b66443d58837588385e62c46c2dc548375992d692c2db5051626"),
|
|
("Windows.Web.Http.Headers", "08e2a3ac344c76759bb08d9fc56a1585570578777244231054ba5e86b0b5d637"),
|
|
("Windows.Web.Http.Filters", "270aa3244d5dd26fc57330cf046a79cbf8389168d6a54960d7c574656ecbb638"),
|
|
("Windows.Foundation", "1e9ed32ba69839fab0eb9ade6acc399480cc6a09c096ab5fdda74392f6195cc5"),
|
|
("Windows.Storage.Streams", "e5f4de19dc1a5ae60541221eaba5bab262c5ff8bb85ac8ebaad7cb426102ca6b"),
|
|
("Windows.Networking", "59f69e79980463a2c164580c2d51ab07c1fae4cb41053da33cdb7ce6346b9edc"),
|
|
("Windows.Networking.Sockets", "f8f73eeb72966b7549e604d7f9cc63b10e37323939855b4bac89739bb257e2d1"),
|
|
("Windows.Security.Cryptography.Certificates", "0c55a5435898dad5483fb1d0fb92ececcadce8bdb1de718901e73827d8fc9575"),
|
|
("Windows.Security.Credentials", "c21c1d45d201258bae67fe23a6ca94a83093a2e595aa8fe91735317d7bc13efc"),
|
|
("Windows.Globalization", "d2d392f8c526241975bff30e2eef19e65d9387632d1b369429c99b361f602b25"),
|
|
("Windows.System", "c19d46414f859385bd9adc9685e566dddce051c36e25003609a061f7e535a3c2"))
|
|
DECLARED = ("Windows.Web.Http", "Windows.Web.Http.Headers", "Windows.Web.Http.Filters")
|
|
# The crates of the generic interfaces, the asynchronous ones' base and the time structures, with the namespace each serves.
|
|
CRATES = (("collections", "Windows.Foundation.Collections", "45989f164dc1154d2afd15a507d088eda1d5eb0addfb82a600912dd839be1419"),
|
|
("future", "Windows.Foundation", "1997b5af103d19e3ddde11970d61ef82dd006b836c9495e20aaeb0aa13fdf9be"),
|
|
("reference", "Windows.Foundation", "6353cc1760afeb9d3120e1ec764d267369d0171d3891611eacbb20f8df54d1ca"),
|
|
("time", "Windows.Foundation", "25a8e2bf9bb05364b189f32f1d2323ecb51c47c5f5a43cffd52d5b8c7a6a2401"))
|
|
# The namespaces foundation.h declares in full, as far as the declared ones reach into them.
|
|
FOUNDATION = ("Windows.Foundation", "Windows.Foundation.Collections", "Windows.Storage.Streams")
|
|
HEADERS = ("inspectable.h", "asyncinfo.h", "eventtoken.h")
|
|
# Types of the classic COM headers rather than of a namespace.
|
|
GLOBAL = {"Windows.Foundation.AsyncStatus": "AsyncStatus", "Windows.Foundation.IAsyncInfo": "IAsyncInfo",
|
|
"Windows.Foundation.EventRegistrationToken": "EventRegistrationToken"}
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
FOUNDATION_TARGET = ROOT / "native/upstream/raven-winrt/foundation.h"
|
|
TARGET = ROOT / "native/upstream/raven-web/windows.web.http.h"
|
|
|
|
PRIM = {"bool": ("boolean", "b1"), "u8": ("BYTE", "u1"), "i8": ("INT8", "i1"), "u16": ("UINT16", "u2"), "i16": ("INT16", "i2"),
|
|
"u32": ("UINT32", "u4"), "i32": ("INT32", "i4"), "u64": ("UINT64", "u8"), "i64": ("INT64", "i8"), "f32": ("FLOAT", "f4"),
|
|
"f64": ("DOUBLE", "f8"), "windows_core::GUID": ("GUID", "g16"), "windows_core::HSTRING": ("HSTRING", "string"),
|
|
"windows_core::HRESULT": ("HRESULT", "struct(Windows.Foundation.HResult;i4)")}
|
|
TOKEN = ("struct", "Windows.Foundation.EventRegistrationToken")
|
|
OBJECT = ("object",)
|
|
# Methods without arguments that return a value but are not properties.
|
|
METHODS = {"EnsureSuccessStatusCode", "ToString", "First", "MoveNext", "GetDeferral", "GetView", "GetResults"}
|
|
# The namespace identifier of the generic instances' identifiers.
|
|
PINTERFACE = bytes.fromhex("11f47ad57b7342c0abae878b1e16adee")
|
|
|
|
|
|
def split_args(text):
|
|
out, depth, cur = [], 0, ""
|
|
for ch in text:
|
|
if ch in "<([":
|
|
depth += 1
|
|
elif ch in ">)]":
|
|
depth -= 1
|
|
if ch == "," and depth == 0:
|
|
out.append(cur.strip())
|
|
cur = ""
|
|
else:
|
|
cur += ch
|
|
if cur.strip():
|
|
out.append(cur.strip())
|
|
return out
|
|
|
|
|
|
def guid(raw):
|
|
raw = raw.replace("_", "").replace("-", "").lower()
|
|
return f"{raw[0:8]}-{raw[8:12]}-{raw[12:16]}-{raw[16:20]}-{raw[20:32]}"
|
|
|
|
|
|
def from_signature(signature):
|
|
"""The identifier of a generic instance: a name based (SHA-1) identifier of its signature, as the type system specifies."""
|
|
digest = bytearray(hashlib.sha1(PINTERFACE + signature.encode()).digest()[:16])
|
|
digest[6] = (digest[6] & 0x0f) | 0x50
|
|
digest[8] = (digest[8] & 0x3f) | 0x80
|
|
return guid(digest.hex())
|
|
|
|
|
|
class Source:
|
|
"""One file of the bindings, with its whitespace made single spaces, and what it declares for the namespace it serves."""
|
|
|
|
def __init__(self, namespace, text, crate):
|
|
self.ns = namespace
|
|
self.crate = crate
|
|
# One space for any whitespace, none inside the parentheses rustfmt breaks across lines, and no trailing commas there.
|
|
self.text = re.sub(r",? \)", ")", re.sub(r"\( ", "(", re.sub(r"\s+", " ", text)))
|
|
t = self.text
|
|
self.interfaces = {} # name -> (iid, base, [(field, [ABI argument])])
|
|
for name, iid in re.findall(r"define_interface!\((\w+), \w+_Vtbl, 0x([0-9a-f_]+)\);", t):
|
|
self.interfaces[name] = (guid(iid),) + self.vtable(name)
|
|
self.templates = {} # name -> (piid, [parameter], base, [(field, [ABI argument])])
|
|
for name, params in re.findall(r"pub struct (\w+)<([\w, ]+)>\( ?windows_core::IUnknown,", t):
|
|
piid = re.search(r"RuntimeType for " + name + r"<[\w, ]+> \{ const SIGNATURE: windows_core::imp::ConstBuffer = "
|
|
r"windows_core::imp::ConstBuffer::new\(\) ?\.push_slice\(b\"pinterface\(\{([0-9a-f-]{36})\}\"\)", t)
|
|
if piid:
|
|
self.templates[name] = (piid.group(1), [p.strip() for p in params.split(",")]) + self.vtable(name)
|
|
self.enums = {} # name -> [(value, number)]
|
|
for name, body in re.findall(r"pub struct (\w+)\(pub (?:i32|u32)\); impl \1 \{ (.*?) \}", t):
|
|
self.enums[name] = re.findall(r"pub const (\w+): Self = Self\((-?\d+)\);", body)
|
|
self.structs = {} # name -> [(field, Rust type)]
|
|
for name, body in re.findall(r"pub struct (\w+) \{ ((?:pub \w+: [^{}]*?, )+)\}", t):
|
|
if not name.endswith("_Vtbl"):
|
|
fields = re.findall(r"pub (\w+): ([^{}]*?), ", body + " ")
|
|
# The crates name the fields in Rust's snake case; the metadata's names are their Pascal case.
|
|
self.structs[name] = [("".join(part.title() for part in field.split("_")) if crate else field, ty) for field, ty in fields]
|
|
self.signatures = dict(re.findall(r"RuntimeType for (\w+) \{ const SIGNATURE: windows_core::imp::ConstBuffer = "
|
|
r"windows_core::imp::ConstBuffer::from_slice\(b\"((?:enum|struct)\(.*?\))\"\);", t))
|
|
self.classes = dict(re.findall(r"RuntimeType for (\w+) \{ const SIGNATURE: windows_core::imp::ConstBuffer = "
|
|
r"windows_core::imp::ConstBuffer::for_class::<Self, (.*?)>\(\);", t))
|
|
self.requires = {} # interface, class or template -> [Rust type]
|
|
for name, required in re.findall(r"required_hierarchy!\((\w+), (.*?)\);", t):
|
|
self.requires.setdefault(name, []).extend(split_args(required))
|
|
for required, name in re.findall(r"CanInto<([^{]*?)> for (\w+)<[\w, ]+> \{ const QUERY: bool = true;", t):
|
|
self.requires.setdefault(name, []).append(required)
|
|
self.methods = {} # (interface, field) -> (owner, parameters, result, where, handler)
|
|
self.parse_methods()
|
|
|
|
def vtable(self, name):
|
|
body = re.search(r"pub struct " + name + r"_Vtbl(?:<[\w, ]+>)?(?: where [^{]*?)? \{ (?:pub )?base__: windows_core::(\w+)_Vtbl, (.*?)\}", self.text)
|
|
fields, seen = [], set()
|
|
for field, abi in re.findall(r"(?:pub )?(\w+): unsafe extern \"system\" fn\((.*?)\) -> windows_core::HRESULT,", body.group(2)):
|
|
if field not in seen: # a field for a feature left out appears twice, the second time as usize
|
|
seen.add(field)
|
|
fields.append((field, [a.split(": ", 1)[-1] if re.match(r"\w+: ", a) else a for a in split_args(abi)[1:]]))
|
|
return body.group(1), fields
|
|
|
|
def parse_methods(self):
|
|
t = self.text
|
|
heads = [(m.start(), m.end(), m.group(1)) for m in re.finditer(r"\bimpl(?:<[^{]*?>)? (\w+)(?:<[\w, ]+>)? \{", t)]
|
|
bounds = [m.start() for m in re.finditer(r"\bimpl\b", t)] + [len(t)]
|
|
for start, end, owner in heads:
|
|
stop = next(b for b in bounds if b > start)
|
|
for fn in re.finditer(r"pub fn (\w+)(?:<(?:[^(){}]|\([^(){}]*\))*?>)?\((?:&self(?:, )?)?([^{}]*?)\) -> windows_core::Result<([^{}]*?)> ?(?:where (.*?))?\{(.*?)(?= pub fn |$)",
|
|
t[end:stop]):
|
|
params, result, where, body = fn.group(2), fn.group(3), fn.group(4) or "", fn.group(5)
|
|
field = re.search(r"vtable\((?:this|self)\)\.(\w+)\)", body)
|
|
if not field:
|
|
continue
|
|
cast = re.search(r"cast::<([\w:]+)>", body)
|
|
static = re.search(r"Self::(\w+)\(\|this\|", body)
|
|
if cast:
|
|
if "::" in cast.group(1):
|
|
continue # an interface of another namespace: declared there
|
|
interface = cast.group(1)
|
|
elif static:
|
|
interface = static.group(1)
|
|
elif owner in self.interfaces or owner in self.templates:
|
|
interface = owner
|
|
elif owner in self.classes:
|
|
interface = self.classes[owner]
|
|
else:
|
|
continue
|
|
handler = re.search(r"<([\w:]+<.*?>)>::new\(move \|", body)
|
|
self.methods.setdefault((interface, field.group(1)), (owner, params, result, where, handler.group(1) if handler else None))
|
|
# An interface programs implement has a trait of its methods, typed as the wrappers are; it serves those without one.
|
|
for trait in re.finditer(r"pub trait (\w+)_Impl(?:<[^{]*?>)?(?:: [^{]*?)? \{ (.*?) \}", t):
|
|
for fn in re.finditer(r"fn (\w+)\(&self(?:, )?(.*?)\) -> windows_core::Result<(.*?)>;", trait.group(2)):
|
|
self.methods.setdefault((trait.group(1), fn.group(1)), (trait.group(1), fn.group(2), fn.group(3), "", None))
|
|
|
|
def qualify(self, path):
|
|
"""A path of the bindings as a dotted metadata name."""
|
|
path = path.strip()
|
|
for prefix, ns in (("windows_future::", "Windows.Foundation."), ("windows_collections::", "Windows.Foundation.Collections."),
|
|
("windows_reference::", "Windows.Foundation."), ("windows_time::", "Windows.Foundation.")):
|
|
if path.startswith(prefix):
|
|
return ns + path[len(prefix):]
|
|
parts = self.ns.split(".")
|
|
if self.crate and "::" not in path:
|
|
return self.ns + "." + path
|
|
while path.startswith("super::"):
|
|
parts = parts[:-1]
|
|
path = path[len("super::"):]
|
|
return ".".join(parts + path.split("::"))
|
|
|
|
|
|
class Model:
|
|
def __init__(self, sources):
|
|
self.sources = sources
|
|
self.kinds, self.iids, self.home, self.templates = {}, {}, {}, {}
|
|
for source in sources:
|
|
for name, (iid, base, fields) in source.interfaces.items():
|
|
dotted = f"{source.ns}.{name}"
|
|
self.kinds[dotted] = "delegate" if base == "IUnknown" else "iface"
|
|
self.iids[dotted] = iid
|
|
self.home[dotted] = source
|
|
for name in source.templates:
|
|
self.templates[f"{source.ns}.{name}"] = source
|
|
for name in source.enums:
|
|
self.kinds[f"{source.ns}.{name}"] = "enum"
|
|
self.home[f"{source.ns}.{name}"] = source
|
|
for name in source.structs:
|
|
if name in source.signatures:
|
|
self.kinds[f"{source.ns}.{name}"] = "struct"
|
|
self.home[f"{source.ns}.{name}"] = source
|
|
for name in source.classes:
|
|
self.kinds[f"{source.ns}.{name}"] = "class"
|
|
self.home[f"{source.ns}.{name}"] = source
|
|
self.kinds["Windows.Foundation.EventRegistrationToken"] = "struct"
|
|
self.used = []
|
|
|
|
def resolve(self, rust, source, binding, where=None):
|
|
"""A Rust type of the bindings as a type of the model: a tuple of its kind and name."""
|
|
rust = re.sub(r"\s*([<>])\s*", r"\1", rust.strip()).removeprefix("&").strip()
|
|
for wrapper in (r"Option<(.*)>", r"windows_core::Ref<(.*)>", r"<(\w+) as windows_core::imp::Type<\1>>::Default"):
|
|
m = re.fullmatch(wrapper, rust)
|
|
if m:
|
|
return self.resolve(m.group(1), source, binding, where)
|
|
if rust in binding:
|
|
return binding[rust]
|
|
if where and rust in where:
|
|
return self.resolve(where[rust], source, binding)
|
|
if rust in PRIM:
|
|
return ("prim",) + PRIM[rust]
|
|
if rust == "windows_core::IInspectable":
|
|
return OBJECT
|
|
g = re.fullmatch(r"([\w:]+)<(.*)>", rust)
|
|
if g:
|
|
return ("generic", source.qualify(g.group(1)), tuple(self.resolve(a, source, binding, where) for a in split_args(g.group(2))))
|
|
dotted = source.qualify(rust)
|
|
if dotted not in self.kinds:
|
|
raise RuntimeError(f"{source.ns}: no type for {rust}")
|
|
return (self.kinds[dotted], dotted)
|
|
|
|
def default(self, dotted):
|
|
source = self.home[dotted]
|
|
return self.resolve(source.classes[dotted.rsplit(".", 1)[1]], source, {})
|
|
|
|
def signature(self, ty):
|
|
kind = ty[0]
|
|
if kind == "prim":
|
|
return ty[2]
|
|
if kind == "object":
|
|
return "cinterface(IInspectable)"
|
|
if ty == TOKEN:
|
|
return "struct(Windows.Foundation.EventRegistrationToken;i8)"
|
|
if kind in ("enum", "struct"):
|
|
return self.home[ty[1]].signatures[ty[1].rsplit(".", 1)[1]]
|
|
if kind == "iface":
|
|
return "{" + self.iids[ty[1]] + "}"
|
|
if kind == "delegate":
|
|
return "delegate({" + self.iids[ty[1]] + "})"
|
|
if kind == "class":
|
|
return f"rc({ty[1]};{self.signature(self.default(ty[1]))})"
|
|
source = self.templates[ty[1]]
|
|
piid = source.templates[ty[1].rsplit(".", 1)[1]][0]
|
|
return "pinterface({" + piid + "};" + ";".join(self.signature(a) for a in ty[2]) + ")"
|
|
|
|
def name(self, ty):
|
|
"""The C name of an interface, a generic instance, a structure or an enumeration."""
|
|
if ty[0] == "generic":
|
|
local = ty[1].rsplit(".", 1)[1]
|
|
if self.templates[ty[1]].templates[local][2] == "IUnknown" and not local.startswith("I"):
|
|
local = "I" + local
|
|
return f"__F{local}_{len(ty[2])}_" + "_".join(self.mangle(a) for a in ty[2])
|
|
if ty[1] in GLOBAL:
|
|
return GLOBAL[ty[1]]
|
|
namespace, local = ty[1].rsplit(".", 1)
|
|
return "__x_ABI_C" + "_C".join(namespace.split(".")) + "_C" + ("I" + local if ty[0] == "delegate" and not local.startswith("I") else local)
|
|
|
|
def mangle(self, ty):
|
|
"""A type as part of a generic instance's name."""
|
|
kind = ty[0]
|
|
if kind == "prim":
|
|
return ty[1]
|
|
if kind == "object":
|
|
return "IInspectable"
|
|
if kind in ("enum", "struct"):
|
|
return ty[1].rsplit(".", 1)[1]
|
|
if kind == "generic":
|
|
return self.name(ty)
|
|
return "__C".join(ty[1].split("."))
|
|
|
|
def c(self, ty):
|
|
"""A type as C passes it; the type is noted among those the header declares."""
|
|
self.note(ty)
|
|
kind = ty[0]
|
|
if kind == "prim":
|
|
return ty[1]
|
|
if kind == "object":
|
|
return "IInspectable *"
|
|
if kind in ("enum", "struct"):
|
|
return self.name(ty)
|
|
if kind == "class":
|
|
return self.c(self.default(ty[1]))
|
|
return self.name(ty) + " *"
|
|
|
|
def note(self, ty):
|
|
if ty not in self.used:
|
|
self.used.append(ty)
|
|
return ty
|
|
|
|
def is_value(self, ty):
|
|
return ty[0] in ("prim", "enum", "struct") and ty[1] != "HSTRING"
|
|
|
|
def members(self, interface, source, fields, binding, itself=None):
|
|
"""An interface's methods after those of IUnknown and IInspectable: (name, [(C type, parameter)])."""
|
|
names = {field for field, _ in fields}
|
|
out = []
|
|
for field, abi in fields:
|
|
if field.startswith("Remove") and field[6:] in names:
|
|
out.append((f"remove_{field[6:]}", [("EventRegistrationToken", "token")]))
|
|
continue
|
|
sig = source.methods.get((interface, field))
|
|
if not sig:
|
|
raise RuntimeError(f"{interface}.{field}: signature not found")
|
|
owner, params, result, where_text, handler = sig
|
|
where = {}
|
|
for clause in split_args(where_text):
|
|
m = re.fullmatch(r"(P\d+): windows_core::Param<(.*)>", clause.strip())
|
|
if m:
|
|
where[m.group(1)] = m.group(2)
|
|
local = dict(binding)
|
|
if owner in source.classes:
|
|
local["Self"] = self.resolve(owner, source, binding)
|
|
elif itself:
|
|
local["Self"] = itself
|
|
if "Remove" + field in names:
|
|
ty = self.resolve(handler, source, local) if handler else self.resolve(split_args(params)[0].split(":", 1)[1], source, local, where)
|
|
out.append((f"add_{field}", [(self.c(ty), "handler"), ("EventRegistrationToken *", "token")]))
|
|
continue
|
|
args, position = [], 0
|
|
for p in split_args(params):
|
|
if ":" not in p:
|
|
raise RuntimeError(f"{interface}.{field}: parameter {p!r} of {params!r}")
|
|
pname, ptype = (part.strip() for part in p.split(":", 1))
|
|
arr = re.fullmatch(r"&(mut )?\[(.*)\]", ptype)
|
|
if arr:
|
|
args += [("UINT32", f"{pname}_size"), (pointer(self.c(self.resolve(arr.group(2), source, local, where))), pname)]
|
|
position += 2
|
|
continue
|
|
if ptype.startswith("&mut "):
|
|
args.append((pointer(self.c(self.resolve(ptype[5:], source, local, where))), pname))
|
|
else:
|
|
ty = self.resolve(ptype, source, local, where)
|
|
if self.is_value(ty) and position < len(abi) and abi[position] == "*mut core::ffi::c_void":
|
|
ty = ("generic", "Windows.Foundation.IReference", (ty,))
|
|
args.append((self.c(ty), pname))
|
|
position += 1
|
|
if result != "()":
|
|
arr = re.fullmatch(r"windows_core::Array<(.*)>", result)
|
|
if arr:
|
|
args += [("UINT32 *", "value_size"), (pointer(pointer(self.c(self.resolve(arr.group(1), source, local, where)))), "value")]
|
|
else:
|
|
ty = self.resolve("Self" if result == "Self" else result, source, local, where)
|
|
if self.is_value(ty) and abi and abi[-1] == "*mut *mut core::ffi::c_void":
|
|
ty = ("generic", "Windows.Foundation.IReference", (ty,))
|
|
taken = any(p.split(":", 1)[0].strip() == "value" for p in split_args(params))
|
|
args.append((pointer(self.c(ty)), "result" if taken else "value"))
|
|
name = field
|
|
if field.startswith("Set") and field[3:] in names and len(split_args(params)) == 1 and result == "()":
|
|
name = "put_" + field[3:]
|
|
elif not params and result != "()" and not field.endswith("Async") and field not in METHODS:
|
|
name = "get_" + field
|
|
if len(args) != len(abi):
|
|
raise RuntimeError(f"{interface}.{field}: {len(args)} arguments for {len(abi)}")
|
|
for (ctype, pname), entry in zip(args, abi):
|
|
# A string handle and an interface pointer are as indirect as each other in the ABI.
|
|
depth = ctype.count("*") + ctype.startswith("HSTRING")
|
|
if depth != entry.count("*mut") + entry.count("*const") and "AbiType" not in entry:
|
|
raise RuntimeError(f"{interface}.{field}: {ctype} {pname} against {entry}")
|
|
out.append((name, args))
|
|
return out
|
|
|
|
def generic_members(self, ty):
|
|
source = self.templates[ty[1]]
|
|
name = ty[1].rsplit(".", 1)[1]
|
|
piid, params, base, fields = source.templates[name]
|
|
return base, self.members(name, source, fields, dict(zip(params, ty[2])), ty)
|
|
|
|
def requirements(self, ty):
|
|
"""The generic instances an interface, a class or a generic instance requires."""
|
|
name = ty[1].rsplit(".", 1)[1]
|
|
if ty[0] == "generic":
|
|
source = self.templates[ty[1]]
|
|
binding = dict(zip(source.templates[name][1], ty[2]))
|
|
else:
|
|
source, binding = self.home[ty[1]], {}
|
|
return [self.resolve(r, source, binding) for r in source.requires.get(name, [])
|
|
if r not in ("windows_core::IUnknown", "windows_core::IInspectable")]
|
|
|
|
def class_interfaces(self, dotted):
|
|
"""A class's interfaces: its default, those it requires, and the factories and statics its methods go through."""
|
|
source, local = self.home[dotted], dotted.rsplit(".", 1)[1]
|
|
found = [self.default(dotted)] + self.requirements(("class", dotted))
|
|
for (interface, _), (owner, *_) in source.methods.items():
|
|
ty = ("iface", f"{source.ns}.{interface}")
|
|
if owner == local and f"{source.ns}.{interface}" in self.kinds and ty not in found:
|
|
found.append(ty)
|
|
return found
|
|
|
|
def is_foundation(self, ty):
|
|
"""Whether foundation.h declares a type: one of its namespaces', or a generic instance of such types alone."""
|
|
if ty[0] in ("prim", "object"):
|
|
return True
|
|
if ty[0] == "generic":
|
|
return all(self.is_foundation(a) for a in ty[2])
|
|
return ty[1].rsplit(".", 1)[0] in FOUNDATION
|
|
|
|
|
|
def pointer(ctype):
|
|
return ctype + ("*" if ctype.endswith("*") else " *")
|
|
|
|
|
|
def vtable(lines, name, base, members):
|
|
lines.append(f"typedef struct {name}Vtbl {{")
|
|
lines.append(f" HRESULT (STDMETHODCALLTYPE *QueryInterface)({name} *This, REFIID riid, void **object);")
|
|
lines.append(f" ULONG (STDMETHODCALLTYPE *AddRef)({name} *This);")
|
|
lines.append(f" ULONG (STDMETHODCALLTYPE *Release)({name} *This);")
|
|
if base == "IInspectable":
|
|
lines.append(f" HRESULT (STDMETHODCALLTYPE *GetIids)({name} *This, ULONG *count, IID **iids);")
|
|
lines.append(f" HRESULT (STDMETHODCALLTYPE *GetRuntimeClassName)({name} *This, HSTRING *name);")
|
|
lines.append(f" HRESULT (STDMETHODCALLTYPE *GetTrustLevel)({name} *This, TrustLevel *level);")
|
|
for method, args in members:
|
|
rendered = "".join(f", {ctype}{'' if ctype.endswith('*') else ' '}{pname}" for ctype, pname in args)
|
|
lines.append(f" HRESULT (STDMETHODCALLTYPE *{method})({name} *This{rendered});")
|
|
lines.append(f"}} {name}Vtbl;")
|
|
lines.append(f"struct {name} {{ const {name}Vtbl *lpVtbl; }};")
|
|
|
|
|
|
def define_guid(name, iid):
|
|
raw = iid.replace("-", "")
|
|
return f"DEFINE_GUID({name}, 0x{raw[0:8]}, 0x{raw[8:12]}, 0x{raw[12:16]}, " + ", ".join(f"0x{raw[i:i + 2]}" for i in range(16, 32, 2)) + ");"
|
|
|
|
|
|
def render(model):
|
|
"""The two headers' lines: foundation.h's, then windows.web.http.h's."""
|
|
full = DECLARED + FOUNDATION
|
|
interfaces, enums, structs, generics, foreign = [], [], [], [], []
|
|
for source in model.sources:
|
|
if not source.crate and source.ns in DECLARED:
|
|
for name in [*source.interfaces, *source.enums, *[s for s in source.structs if s in source.signatures], *source.classes]:
|
|
model.note((model.kinds[f"{source.ns}.{name}"], f"{source.ns}.{name}"))
|
|
# Every type a declared one names, through its methods, fields, requirements and, for a class, its interfaces.
|
|
done = set()
|
|
while len(done) < len(model.used):
|
|
for ty in list(model.used):
|
|
if ty in done:
|
|
continue
|
|
done.add(ty)
|
|
if ty[0] in ("prim", "object") or ty == TOKEN or ty[0] != "generic" and ty[1] in GLOBAL:
|
|
continue
|
|
if ty[0] == "generic":
|
|
for a in ty[2]:
|
|
model.note(a)
|
|
generics.append((ty, *model.generic_members(ty)))
|
|
for required in model.requirements(ty):
|
|
model.note(required)
|
|
continue
|
|
namespace, local = ty[1].rsplit(".", 1)
|
|
source = model.home[ty[1]]
|
|
if ty[0] == "class":
|
|
for interface in model.class_interfaces(ty[1]) if namespace in full else [model.default(ty[1])]:
|
|
model.note(interface)
|
|
elif ty[0] in ("iface", "delegate"):
|
|
if namespace in full:
|
|
iid, base, fields = source.interfaces[local]
|
|
interfaces.append((ty, iid, base, model.members(local, source, fields, {})))
|
|
for required in model.requirements(ty):
|
|
model.note(required)
|
|
else:
|
|
foreign.append(ty)
|
|
elif ty[0] == "enum":
|
|
enums.append((ty, source.enums[local]))
|
|
else:
|
|
structs.append((ty, [(field, model.note(model.resolve(rust, source, {}))) for field, rust in source.structs[local]]))
|
|
order = lambda entries: sorted(entries, key=lambda entry: model.name(entry[0]))
|
|
parts = []
|
|
for foundation in (True, False):
|
|
mine = lambda entries: order([e for e in entries if model.is_foundation(e[0]) == foundation])
|
|
guard = "RAVEN_WINRT_FOUNDATION_H" if foundation else "RAVEN_WINDOWS_WEB_HTTP_H"
|
|
lines = ["/* Generated by scripts/generate-winrt-header.py from windows-rs " + REVISION + " (Microsoft's Windows metadata, MIT): do not edit. */",
|
|
f"#ifndef {guard}", f"#define {guard}", ""]
|
|
lines += [f"#include <{h}>" for h in HEADERS] if foundation else ['#include "../raven-winrt/foundation.h"']
|
|
lines.append("")
|
|
for ty in [e[0] for e in mine(generics)] + sorted(t for t in foreign if not foundation) + [e[0] for e in mine(interfaces)]:
|
|
name = model.name(ty)
|
|
lines += [f"#ifndef __{name}_FWD_DEFINED__", f"#define __{name}_FWD_DEFINED__", f"typedef struct {name} {name};", "#endif"]
|
|
lines.append("")
|
|
for ty, values in mine(enums):
|
|
name, local = model.name(ty), ty[1].rsplit(".", 1)[1]
|
|
lines.append(f"typedef enum {name} {{")
|
|
lines.append(",\n".join(f" {local}_{value} = {number}" for value, number in values))
|
|
lines.append(f"}} {name};")
|
|
lines.append("")
|
|
for ty, fields in mine(structs):
|
|
name = model.name(ty)
|
|
lines.append(f"typedef struct {name} {{")
|
|
for field, fty in fields:
|
|
ctype = model.c(fty)
|
|
lines.append(f" {ctype}{'' if ctype.endswith('*') else ' '}{field};")
|
|
lines.append(f"}} {name};")
|
|
for ty, base, members in mine(generics):
|
|
name = model.name(ty)
|
|
lines += ["", f"#ifndef __{name}_INTERFACE_DEFINED__", f"#define __{name}_INTERFACE_DEFINED__",
|
|
define_guid("IID_" + name, from_signature(model.signature(ty)))]
|
|
vtable(lines, name, base, members)
|
|
lines.append("#endif")
|
|
for ty, iid, base, members in mine(interfaces):
|
|
name = model.name(ty)
|
|
lines += ["", f"#ifndef __{name}_INTERFACE_DEFINED__", f"#define __{name}_INTERFACE_DEFINED__", define_guid("IID_" + name, iid)]
|
|
vtable(lines, name, base, members)
|
|
lines.append("#endif")
|
|
lines += ["", "#endif", ""]
|
|
parts.append("\n".join(lines))
|
|
return parts
|
|
|
|
|
|
def load(path, digest, cache, url):
|
|
cached = cache / path if cache else None
|
|
if cached and cached.exists():
|
|
data = cached.read_bytes()
|
|
else:
|
|
data = urllib.request.urlopen(url).read()
|
|
if cached:
|
|
cached.parent.mkdir(parents=True, exist_ok=True)
|
|
cached.write_bytes(data)
|
|
if hashlib.sha256(data).hexdigest() != digest:
|
|
raise RuntimeError(f"windows-rs source checksum differs: {path}")
|
|
return data.decode()
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
parser.add_argument("--cache", type=Path, help="directory holding (or to keep) the pinned windows-rs files")
|
|
parser.add_argument("--check", action="store_true", help="compare with the committed header instead of writing it")
|
|
args = parser.parse_args()
|
|
sources = []
|
|
for namespace, digest in MODULES:
|
|
path = namespace.removeprefix("Windows.").replace(".", "/")
|
|
text = load(path.replace("/", "_") + ".rs", digest, args.cache, f"{SOURCE}/windows/src/Windows/{path}/mod.rs")
|
|
sources.append(Source(namespace, text, False))
|
|
for crate, namespace, digest in CRATES:
|
|
sources.append(Source(namespace, load(f"crate_{crate}.rs", digest, args.cache, f"{SOURCE}/{crate}/src/bindings.rs"), True))
|
|
for target, header in zip((FOUNDATION_TARGET, TARGET), render(Model(sources))):
|
|
if args.check:
|
|
if target.read_text() != header:
|
|
sys.exit(f"{target} differs from what windows-rs {REVISION} gives")
|
|
else:
|
|
target.write_text(header)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|