"""Поиск и выбор Java.""" import json import os import re import shutil import struct import subprocess import sys CREATE_NO_WINDOW = 0x08000000 if os.name == "nt" else 0 def is64(): return struct.calcsize("P") * 8 == 64 def _exe(name="java"): return name + (".exe" if os.name == "nt" else "") def java_major(path): """Версия Java в виде числа (8, 11, 17, 21...), 0 если не определилась.""" try: p = subprocess.run( [path, "-version"], capture_output=True, text=True, timeout=20, creationflags=CREATE_NO_WINDOW, ) text = (p.stderr or "") + (p.stdout or "") except Exception: return 0 m = re.search(r'version\s+"(\d+)(?:\.(\d+))?(?:_(\d+))?', text) if not m: return 0 major = int(m.group(1)) if major == 1: # старый формат 1.8.0_382 return int(m.group(2) or 0) return major def _add(cands, home): if not home: return exe = os.path.join(home, "bin", _exe()) if os.path.isfile(exe) and exe not in cands: cands.append(exe) def find_javas(): cands = [] # Переменные окружения _add(cands, os.environ.get("JAVA_HOME")) for k, v in os.environ.items(): if k.upper().startswith("JAVA_HOME") and v: _add(cands, v) # PATH w = shutil.which(_exe()) if w: cands.append(w) if os.name == "nt": pf = [ r"C:\Program Files\Java", r"C:\Program Files (x86)\Java", r"C:\Program Files\Eclipse Adoptium", r"C:\Program Files\AdoptOpenJDK", r"C:\Program Files\Microsoft", r"C:\Program Files\Amazon Corretto", r"C:\Program Files\Zulu", r"C:\Program Files\BellSoft", r"C:\Program Files\GraalVM", r"C:\Program Files\JetBrains", ] home_jdks = os.path.join(os.path.expanduser("~"), ".jdks") pf.append(home_jdks) for root in pf: if not os.path.isdir(root): continue try: for name in os.listdir(root): _add(cands, os.path.join(root, name)) except OSError: pass elif sys.platform == "darwin": for root in ("/Library/Java/JavaVirtualMachines", os.path.expanduser("~/Library/Java/JavaVirtualMachines")): if not os.path.isdir(root): continue try: for name in os.listdir(root): _add(cands, os.path.join(root, name, "Contents", "Home")) except OSError: pass else: jvm_dir = "/usr/lib/jvm" if os.path.isdir(jvm_dir): try: for name in os.listdir(jvm_dir): _add(cands, os.path.join(jvm_dir, name)) except OSError: pass return cands def pick_java(required=8, preferred="", cache_path=""): """Выбирает Java: (путь, major, подходит_ли). Из подходящих (major >= required) берётся самая старая — максимальная совместимость со старыми версиями Minecraft. cache_path — файл кэша с результатами определения версии. Пока файл java.exe не менялся, `java -version` повторно не запускается. """ if preferred and os.path.isfile(preferred): major = java_major(preferred) return preferred, major, (major == 0 or major >= required) cache = {} if cache_path: try: with open(cache_path, encoding="utf-8") as f: cache = json.load(f) if not isinstance(cache, dict): cache = {} except Exception: # noqa: BLE001 cache = {} changed = False cands = find_javas() scored = [] for exe in cands: try: mtime = int(os.path.getmtime(exe)) except OSError: continue ent = cache.get(exe) if isinstance(ent, list) and len(ent) == 2 and ent[1] == mtime: major = int(ent[0]) # кэш валиден: файл не менялся else: major = java_major(exe) cache[exe] = [major, mtime] changed = True if major > 0: scored.append((exe, major)) # выбывшие установки — убрать из кэша alive = set(cands) for exe in [e for e in cache if e not in alive]: del cache[exe] changed = True if cache_path and changed: try: os.makedirs(os.path.dirname(cache_path), exist_ok=True) with open(cache_path, "w", encoding="utf-8") as f: json.dump(cache, f) except OSError: pass if not scored: return "", 0, False suitable = [s for s in scored if s[1] >= required] if suitable: suitable.sort(key=lambda s: s[1]) exe, major = suitable[0] return exe, major, True # Ничего подходящего — берём самую новую и предупредим scored.sort(key=lambda s: s[1]) exe, major = scored[-1] return exe, major, False