mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-10-20 03:47:12 +00:00
150 lines
6.9 KiB
Python
150 lines
6.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Prepare current Windows scripting dependencies and matching installer metadata."""
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
import urllib.request
|
|
from urllib.parse import urlsplit
|
|
import xml.etree.ElementTree as ET
|
|
import zipfile
|
|
|
|
RELEASES_URL = "https://strawberryperl.com/releases.json"
|
|
RELEASE_ROOT = "https://github.com/StrawberryPerl/Perl-Dist-Strawberry/releases/download/"
|
|
MSBUILD_NS = "http://schemas.microsoft.com/developer/msbuild/2003"
|
|
|
|
|
|
def select_perl(releases):
|
|
"""Compare versions, not publication dates: older branches get newer releases too."""
|
|
candidates = []
|
|
for release in releases:
|
|
version = release.get("version", "")
|
|
if not re.fullmatch(r"5\.\d+\.\d+\.\d+", version):
|
|
continue
|
|
parts = tuple(map(int, version.split(".")))
|
|
if parts[1] % 2 or release.get("archname") != "MSWin32-x64-multi-thread":
|
|
continue
|
|
if not all(kind in release.get("edition", {}) for kind in ("portable", "msi")):
|
|
continue
|
|
candidates.append((parts, release))
|
|
if not candidates:
|
|
raise ValueError("No stable x64 Strawberry Perl release with portable ZIP and MSI")
|
|
release = max(candidates, key=lambda item: item[0])[1]
|
|
for kind, suffix in (("portable", "portable.zip"), ("msi", "msi")):
|
|
package = release["edition"][kind]
|
|
expected_name = f"strawberry-perl-{release['version']}-64bit-{suffix}"
|
|
if kind == "msi":
|
|
expected_name = f"strawberry-perl-{release['version']}-64bit.msi"
|
|
url = package.get("url", "")
|
|
if not url.startswith(RELEASE_ROOT) or urlsplit(url).path.rsplit("/", 1)[-1] != expected_name:
|
|
raise ValueError(f"Unexpected Strawberry Perl {kind} URL: {url}")
|
|
if not re.fullmatch(r"[0-9a-fA-F]{64}", package.get("sha256", "")):
|
|
raise ValueError(f"Missing SHA-256 for Strawberry Perl {kind}")
|
|
return release
|
|
|
|
|
|
def download(url, destination):
|
|
for attempt in range(4):
|
|
try:
|
|
request = urllib.request.Request(url, headers={"User-Agent": "ZoiteChat-build"})
|
|
with urllib.request.urlopen(request, timeout=120) as response, destination.open("wb") as output:
|
|
while chunk := response.read(1024 * 1024):
|
|
output.write(chunk)
|
|
return
|
|
except (OSError, TimeoutError):
|
|
if attempt == 3:
|
|
raise
|
|
time.sleep(2 ** attempt)
|
|
|
|
|
|
def write_metadata(repo, perl_root, release, python_version):
|
|
parts = release["version"].split(".")
|
|
perl_lib = f"perl{parts[0]}{parts[1]}"
|
|
ET.register_namespace("", MSBUILD_NS)
|
|
project = ET.Element(f"{{{MSBUILD_NS}}}Project")
|
|
group = ET.SubElement(project, "PropertyGroup")
|
|
ET.SubElement(group, "PerlPath").text = str(perl_root)
|
|
ET.SubElement(group, "PerlLib").text = perl_lib
|
|
ET.ElementTree(project).write(repo / "win32/runtimes.generated.props", encoding="utf-8", xml_declaration=True)
|
|
# Only numeric versions and validated upstream URLs enter the Inno source.
|
|
definitions = {
|
|
"PythonRuntimeVersion": python_version,
|
|
"PythonRuntimeURL": f"https://www.python.org/ftp/python/{python_version}/python-{python_version}-amd64.exe",
|
|
"PerlRuntimeVersion": release["version"],
|
|
"PerlRuntimeURL": release["edition"]["msi"]["url"],
|
|
"PerlRuntimeDLL": f"{perl_lib}.dll",
|
|
}
|
|
(repo / "win32/installer/runtimes.generated.iss").write_text(
|
|
"// Generated by tools/prepare-windows-runtimes.py\n"
|
|
+ "".join(f'#define {key} "{value}"\n' for key, value in definitions.items()),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--deps-root", type=Path, default=Path("C:/gtk-build"))
|
|
args = parser.parse_args()
|
|
if sys.platform != "win32" or sys.version_info[:2] != (3, 14) or sys.version_info[:3] < (3, 14, 8) or sys.version_info.releaselevel != "final":
|
|
parser.error("Run with stable x64 Python 3.14.8 or newer within the 3.14 branch")
|
|
if sys.maxsize <= 2 ** 32:
|
|
parser.error("The Windows build requires x64 Python")
|
|
repo = Path(__file__).resolve().parents[1]
|
|
# Do not let an interrupted refresh leave metadata from an older dependency.
|
|
for relative in ("win32/runtimes.generated.props", "win32/installer/runtimes.generated.iss"):
|
|
(repo / relative).unlink(missing_ok=True)
|
|
args.deps_root.mkdir(parents=True, exist_ok=True)
|
|
with tempfile.TemporaryDirectory(dir=args.deps_root) as temporary:
|
|
temporary = Path(temporary)
|
|
metadata = temporary / "releases.json"
|
|
download(RELEASES_URL, metadata)
|
|
release = select_perl(json.loads(metadata.read_text(encoding="utf-8")))
|
|
archive = temporary / "perl.zip"
|
|
download(release["edition"]["portable"]["url"], archive)
|
|
with archive.open("rb") as source:
|
|
digest = hashlib.file_digest(source, "sha256").hexdigest()
|
|
if digest.lower() != release["edition"]["portable"]["sha256"].lower():
|
|
raise ValueError("Strawberry Perl portable ZIP SHA-256 mismatch")
|
|
# Keep the portable distribution intact, including its companion C DLLs.
|
|
target = args.deps_root / f"strawberry-perl-{release['version']}" / "x64"
|
|
extracted = temporary / "extracted"
|
|
with zipfile.ZipFile(archive) as package:
|
|
package.extractall(extracted)
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
if target.exists():
|
|
shutil.rmtree(target)
|
|
shutil.move(extracted, target)
|
|
perl_root = target / "perl"
|
|
environment = os.environ.copy()
|
|
paths = [str(perl_root / "bin"), str(target / "c/bin")]
|
|
environment["PATH"] = os.pathsep.join(paths + [environment.get("PATH", "")])
|
|
perl_lib = "perl" + "".join(release["version"].split(".")[:2])
|
|
for required in (perl_root / "bin/perl.exe", perl_root / f"bin/{perl_lib}.dll", perl_root / "lib/CORE/perl.h"):
|
|
if not required.is_file():
|
|
raise FileNotFoundError(f"Strawberry Perl is missing {required}")
|
|
actual = subprocess.check_output(
|
|
[str(perl_root / "bin/perl.exe"), "-MConfig", "-e", 'print "$Config{version}|$Config{archname}"'],
|
|
env=environment, text=True,
|
|
).strip()
|
|
expected = ".".join(release["version"].split(".")[:3]) + "|MSWin32-x64-multi-thread"
|
|
if actual != expected:
|
|
raise ValueError(f"Unexpected Perl runtime: {actual}; expected {expected}")
|
|
python_version = ".".join(map(str, sys.version_info[:3]))
|
|
write_metadata(repo, perl_root, release, python_version)
|
|
if os.environ.get("GITHUB_PATH"):
|
|
with open(os.environ["GITHUB_PATH"], "a", encoding="utf-8") as output:
|
|
output.write("\n".join(paths) + "\n")
|
|
print(f"Windows runtimes: Python {python_version}; Strawberry Perl {release['version']}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|