117 lines
3.5 KiB
Python
117 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
BEGIN_RES = "# >>> CRT_RESOURCES >>>"
|
|
END_RES = "# <<< CRT_RESOURCES <<<"
|
|
BEGIN_SEC = "; >>> VC_REDIST_SECTION >>>"
|
|
END_SEC = "; <<< VC_REDIST_SECTION <<<"
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
DEFAULT_CARGO_TOML = REPO_ROOT / "packages" / "oneclient_app" / "Cargo.toml"
|
|
DEFAULT_OUT = REPO_ROOT / "target" / "crt"
|
|
|
|
VSWHERE = Path(
|
|
r"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe"
|
|
)
|
|
CRT_GLOB = r"VC\Redist\MSVC\**\x64\Microsoft.VC*.CRT\*.dll"
|
|
|
|
SECTION_REPLACEMENT = """; >>> VC_REDIST_SECTION >>>
|
|
; <<< VC_REDIST_SECTION <<<"""
|
|
|
|
|
|
def region(begin: str, end: str) -> re.Pattern:
|
|
return re.compile(
|
|
r"^[ \t]*" + re.escape(begin) + r".*?^[ \t]*" + re.escape(end) + r"[ \t]*$",
|
|
re.DOTALL | re.MULTILINE,
|
|
)
|
|
|
|
|
|
REGION_RES = region(BEGIN_RES, END_RES)
|
|
REGION_SEC = region(BEGIN_SEC, END_SEC)
|
|
|
|
|
|
def find_crt_dir() -> Path:
|
|
"""Locate the newest x64 Microsoft.VC*.CRT redist directory via vswhere."""
|
|
if not VSWHERE.is_file():
|
|
sys.exit(f"error: vswhere not found at {VSWHERE}; pass --crt-dir explicitly")
|
|
|
|
out = subprocess.run(
|
|
[str(VSWHERE), "-latest", "-products", "*", "-find", CRT_GLOB],
|
|
capture_output=True,
|
|
text=True,
|
|
check=True,
|
|
).stdout
|
|
dirs = {Path(line).parent for line in out.splitlines() if line.strip()}
|
|
if not dirs:
|
|
sys.exit(f"error: vswhere found no DLLs matching {CRT_GLOB}")
|
|
|
|
newest = sorted(dirs, key=lambda p: p.parts)[-1]
|
|
print(f"CRT redist dir: {newest}")
|
|
return newest
|
|
|
|
|
|
def collect(crt_dir: Path, out_dir: Path) -> list[Path]:
|
|
dlls = sorted(crt_dir.glob("*.dll"))
|
|
if not dlls:
|
|
sys.exit(f"error: no DLLs in {crt_dir}")
|
|
|
|
if out_dir.exists():
|
|
shutil.rmtree(out_dir)
|
|
out_dir.mkdir(parents=True)
|
|
|
|
copied = []
|
|
for dll in dlls:
|
|
dest = out_dir / dll.name
|
|
shutil.copy2(dll, dest)
|
|
copied.append(dest)
|
|
print(f" {dll.name} ({dest.stat().st_size} bytes)")
|
|
return copied
|
|
|
|
|
|
def patch(cargo_toml: Path, dlls: list[Path]) -> None:
|
|
text = cargo_toml.read_text(encoding="utf-8")
|
|
for pat, name in ((REGION_RES, "CRT_RESOURCES"), (REGION_SEC, "VC_REDIST_SECTION")):
|
|
if not pat.search(text):
|
|
sys.exit(f"error: {name} region not found in {cargo_toml}")
|
|
|
|
entries = "\n".join(
|
|
f' {{ src = "{str(d).replace(chr(92), chr(92) * 2)}", target = "{d.name}" }},'
|
|
for d in dlls
|
|
)
|
|
block = (
|
|
f" {BEGIN_RES}\n"
|
|
f" # Generated by distribution/windows/bundle-crt.py -- do not edit by hand.\n"
|
|
f"{entries}\n"
|
|
f" {END_RES}"
|
|
)
|
|
|
|
text = REGION_RES.sub(lambda _: block, text, count=1)
|
|
text = REGION_SEC.sub(lambda _: SECTION_REPLACEMENT, text, count=1)
|
|
cargo_toml.write_text(text, encoding="utf-8")
|
|
print(f"patched {cargo_toml} with {len(dlls)} app-local CRT DLLs")
|
|
|
|
|
|
def main() -> None:
|
|
ap = argparse.ArgumentParser(description=__doc__)
|
|
ap.add_argument("--cargo-toml", type=Path, default=DEFAULT_CARGO_TOML)
|
|
ap.add_argument("--out-dir", type=Path, default=DEFAULT_OUT)
|
|
ap.add_argument(
|
|
"--crt-dir",
|
|
type=Path,
|
|
help="Microsoft.VC*.CRT directory to copy from (default: ask vswhere)",
|
|
)
|
|
args = ap.parse_args()
|
|
|
|
crt_dir = args.crt_dir.resolve() if args.crt_dir else find_crt_dir()
|
|
dlls = collect(crt_dir, args.out_dir.resolve())
|
|
patch(args.cargo_toml.resolve(), dlls)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|