File size: 5,203 Bytes
5f25733
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
"""Prepare verified Windows audio DLLs in one private development runtime directory."""

import argparse
from pathlib import Path
import re
import subprocess
import sys

# Import only this script's siblings, including under PYTHONSAFEPATH.
sys.path.insert(0, str(Path(__file__).resolve().parent))
from runtime import Binary, PLUGINS, RuntimeFormat, prepare, required_library_paths

# VCRUNTIME140 is a development prerequisite, not a guaranteed Windows component.
EXTERNAL_IMPORTS = frozenset(
    {
        "advapi32.dll",
        "dnsapi.dll",
        "iphlpapi.dll",
        "kernel32.dll",
        "ole32.dll",
        "shell32.dll",
        "shlwapi.dll",
        "user32.dll",
        "ws2_32.dll",
        "vcruntime140.dll",
        *(
            f"api-ms-win-crt-{part}-l1-1-0.dll"
            for part in (
                "conio",
                "convert",
                "environment",
                "filesystem",
                "heap",
                "locale",
                "math",
                "process",
                "runtime",
                "stdio",
                "string",
                "time",
                "utility",
            )
        ),
    }
)


def inspect(path, target):
    machine = {"x86_64-pc-windows-msvc": "8664", "aarch64-pc-windows-msvc": "AA64"}[
        target
    ]
    if not 64 <= path.stat().st_size <= 64 * 1024 * 1024:
        raise ValueError("invalid PE file size")
    result = subprocess.run(
        ["dumpbin", "/nologo", "/headers", "/dependents", "/exports", str(path)],
        capture_output=True,
        timeout=30,
    )
    output = result.stdout.decode("ascii", errors="backslashreplace").replace(
        "\r\n", "\n"
    )
    if (
        result.returncode
        or result.stderr
        or re.search(r"(?:fatal error|warning) LNK[0-9]+", output)
    ):
        raise ValueError("dumpbin rejected the PE library")
    headers = output.split("SECTION HEADER #", 1)[0]
    if (
        "File Type: DLL\n" not in headers
        or not re.search(r"^ +" + machine + r" machine \(", headers, re.M)
        or not re.search(r"^ +20B magic # \(PE32\+\)$", headers, re.M)
    ):
        raise ValueError(f"expected a {target} PE32+ DLL")
    count = re.search(r"^ +([0-9A-F]+) number of sections$", headers, re.M)
    if not count or not 1 <= int(count[1], 16) <= 96:
        raise ValueError("invalid PE section table")
    directories = {
        name: (int(address, 16), int(size, 16))
        for address, size, name in re.findall(
            r"^ +([0-9A-F]+) \[ *([0-9A-F]+)\] RVA \[size\] of ([^\n]+ Directory)$",
            headers,
            re.M,
        )
    }
    if len(directories) != 16:
        raise ValueError("unsupported PE data directories")
    if any(
        directories.get(name) != (0, 0)
        for name in ("Delay Import Directory", "COM Descriptor Directory")
    ):
        raise ValueError("delay-load and managed DLL dependencies are unsupported")
    if "(forwarded to " in output:
        raise ValueError("forwarded DLL exports are unsupported loader dependencies")
    groups = re.findall(
        r"\n  Image has the following dependencies:\n\n(.*?)(?:\n\n|\Z)", output, re.S
    )
    if len(groups) > 1:
        raise ValueError("ambiguous dumpbin dependencies")
    imports = (
        tuple(line.strip().lower() for line in groups[0].splitlines()) if groups else ()
    )
    if any(not re.fullmatch(r"[a-z0-9_+.-]+\.dll", name) for name in imports):
        raise ValueError("PE dependencies must be plain DLL names")
    address, size = directories["Import Directory"]
    # A descriptor per DLL plus its null terminator; reject truncated/injected output.
    if (address, size) != (0, 0) and (
        not address or size != 20 * (len(imports) + 1) or len(imports) > 128
    ):
        raise ValueError("inconsistent PE import directory")
    return Binary(path.name.lower(), imports, ())


def finalize_copy(destination, metadata, dependency_paths):
    # DLL import names already resolve among siblings; do not alter signed bytes.
    return metadata


def project(prefix, receipts, target, output):
    if sys.platform != "win32" or target not in (
        "x86_64-pc-windows-msvc",
        "aarch64-pc-windows-msvc",
    ):
        raise ValueError(
            "runtime preparation requires Windows and an explicit MSVC target"
        )
    format = RuntimeFormat(
        tuple(Path(f"lib/gstreamer-1.0/gst{name}.dll") for name in sorted(PLUGINS)),
        EXTERNAL_IMPORTS,
        inspect,
        finalize_copy,
        library_dir="bin",
        plugin_dir="bin",
        required_libraries=tuple(
            Path(path).name for path in required_library_paths(target)
        ),
    )
    prepare(prefix, receipts, target, output, format)


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--prefix", type=Path, required=True)
    parser.add_argument("--receipts", type=Path, required=True)
    parser.add_argument("--target", required=True)
    parser.add_argument("--output", type=Path, required=True)
    args = parser.parse_args()
    project(args.prefix, args.receipts, args.target, args.output)