122 lines
4.3 KiB
Python
122 lines
4.3 KiB
Python
#!/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()
|