This commit is contained in:
jze9
2026-06-01 17:05:07 +05:00
parent 89c3500151
commit 070261e947
7 changed files with 1288 additions and 0 deletions

121
fetch_vendor.py Normal file
View File

@@ -0,0 +1,121 @@
#!/usr/bin/env python3
"""
Скачивает портативный Firefox и geckodriver (Windows) в папку vendor/,
чтобы build.py / build_wine.sh встроили их в .exe. Тогда конечному
пользователю нужно установить только КриптоПро — Firefox и драйвер уже внутри.
Запуск (на машине сборки, можно на Linux):
python fetch_vendor.py # win64 (по умолчанию)
python fetch_vendor.py --arch win32
python fetch_vendor.py --lang en-US
Для распаковки Firefox нужен 7-Zip:
Linux: sudo apt install p7zip-full
Windows: установите 7-Zip и добавьте 7z в PATH
"""
from __future__ import annotations
import argparse
import io
import json
import shutil
import subprocess
import sys
import urllib.request
import zipfile
from pathlib import Path
ROOT = Path(__file__).parent
VENDOR = ROOT / "vendor"
GECKO_API = "https://api.github.com/repos/mozilla/geckodriver/releases/latest"
def _download(url: str) -> bytes:
print(f"{url}")
req = urllib.request.Request(url, headers={"User-Agent": "fetch-vendor"})
with urllib.request.urlopen(req, timeout=120) as resp:
return resp.read()
def _which_7z() -> str | None:
for name in ("7z", "7za", "7zr"):
if shutil.which(name):
return name
return None
def fetch_geckodriver(arch: str) -> None:
print("geckodriver:")
data = json.loads(_download(GECKO_API).decode("utf-8"))
asset = next(
(a for a in data["assets"] if f"{arch}.zip" in a["name"] and a["name"].endswith(".zip")),
None,
)
if not asset:
names = [a["name"] for a in data["assets"]]
sys.exit(f" Не найден ассет для {arch}. Доступно: {names}")
blob = _download(asset["browser_download_url"])
with zipfile.ZipFile(io.BytesIO(blob)) as z:
member = next(n for n in z.namelist() if n.endswith("geckodriver.exe"))
VENDOR.mkdir(exist_ok=True)
with z.open(member) as src, open(VENDOR / "geckodriver.exe", "wb") as dst:
shutil.copyfileobj(src, dst)
print(f"{VENDOR / 'geckodriver.exe'} ({data['tag_name']})")
def fetch_firefox(arch: str, lang: str) -> None:
print("Firefox (портативный):")
sevenzip = _which_7z()
if not sevenzip:
print(
" ✗ 7-Zip не найден — не могу распаковать установщик Firefox.\n"
" Установите 7-Zip (Linux: sudo apt install p7zip-full), либо вручную\n"
" распакуйте Firefox так, чтобы получилось vendor/firefox/firefox.exe"
)
return
os_param = "win64" if arch == "win64" else "win"
url = f"https://download.mozilla.org/?product=firefox-latest&os={os_param}&lang={lang}"
installer = VENDOR / "_firefox_setup.exe"
VENDOR.mkdir(exist_ok=True)
installer.write_bytes(_download(url))
extract_dir = VENDOR / "_ff_extract"
if extract_dir.exists():
shutil.rmtree(extract_dir)
# Установщик Firefox — это 7-Zip SFX; внутри папка core/ с firefox.exe.
subprocess.run(
[sevenzip, "x", "-y", f"-o{extract_dir}", str(installer)],
check=True, stdout=subprocess.DEVNULL,
)
core = extract_dir / "core"
if not (core / "firefox.exe").exists():
sys.exit(f" В установщике не найден core/firefox.exe (распаковано в {extract_dir})")
target = VENDOR / "firefox"
if target.exists():
shutil.rmtree(target)
shutil.move(str(core), str(target))
installer.unlink(missing_ok=True)
shutil.rmtree(extract_dir, ignore_errors=True)
print(f"{target / 'firefox.exe'}")
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--arch", choices=("win64", "win32"), default="win64")
ap.add_argument("--lang", default="ru")
args = ap.parse_args()
print(f"Папка: {VENDOR} (arch={args.arch}, lang={args.lang})\n")
fetch_geckodriver(args.arch)
print()
fetch_firefox(args.arch, args.lang)
print("\nГотово. Теперь соберите: python build.py")
if __name__ == "__main__":
main()