Files
AutoDeploy/deploy_tool/installer.py
2026-07-14 16:31:23 +08:00

178 lines
5.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
import re
import subprocess
from dataclasses import dataclass
from .scanner import ScanResult
JDK_PACKAGES = {
"8": "EclipseAdoptium.Temurin.8.JDK",
"11": "EclipseAdoptium.Temurin.11.JDK",
"17": "EclipseAdoptium.Temurin.17.JDK",
"21": "EclipseAdoptium.Temurin.21.JDK",
}
WINGET_PACKAGES = {
"Maven": "Apache.Maven",
"Gradle": "Gradle.Gradle",
"Node.js": "OpenJS.NodeJS.LTS",
"Git": "Git.Git",
"MySQL": "Oracle.MySQL",
}
ANSI_PATTERN = re.compile(r"\x1b\[[0-?]*[ -/]*[@-~]")
PERCENT_PATTERN = re.compile(r"(\d{1,3})\s*%")
PROGRESS_CHARS = "█▓▒░▏▎▍▌▋▊▉■□▪▫▬─━═|/-\\"
@dataclass(frozen=True)
class JdkRecommendation:
version: str
reason: str
def recommend_jdk_version(scan: ScanResult | None) -> JdkRecommendation:
if not scan or not scan.backend:
return JdkRecommendation("8", "\u672a\u626b\u63cf\u5230\u540e\u7aef\u7248\u672c\u4fe1\u606f\uff0c\u9ed8\u8ba4\u63a8\u8350 JDK 8\u3002")
metadata = scan.backend.metadata
java_version = metadata.get("java_version")
if java_version:
mapped = _map_java_version(java_version)
source = metadata.get("java_version_source", "\u9879\u76ee\u914d\u7f6e")
return JdkRecommendation(mapped, f"\u6839\u636e {source}={java_version} \u63a8\u8350 JDK {mapped}\u3002")
boot_version = metadata.get("spring_boot_version", "")
if boot_version.startswith("3."):
return JdkRecommendation("17", f"Spring Boot 3.x \u9700\u8981 Java 17+\uff0c\u68c0\u6d4b\u5230 Spring Boot {boot_version}\uff0c\u63a8\u8350 JDK 17\u3002")
if boot_version.startswith("2."):
return JdkRecommendation("8", f"\u68c0\u6d4b\u5230 Spring Boot {boot_version}\uff0c\u9ed8\u8ba4\u63a8\u8350\u517c\u5bb9\u6027\u66f4\u597d\u7684 JDK 8\u3002")
return JdkRecommendation("8", "\u672a\u53d1\u73b0\u660e\u786e Java \u7248\u672c\uff0c\u9ed8\u8ba4\u63a8\u8350 JDK 8\u3002")
def build_install_command(tool_name: str, version: str | None = None) -> list[str]:
if tool_name == "JDK":
package_id = JDK_PACKAGES.get(version or "8")
if not package_id:
raise ValueError(f"\u4e0d\u652f\u6301\u7684 JDK \u7248\u672c: {version}")
else:
package_id = WINGET_PACKAGES.get(tool_name)
if not package_id:
raise ValueError(f"\u4e0d\u652f\u6301\u81ea\u52a8\u5b89\u88c5: {tool_name}")
return ["winget", "install", "-e", "--id", package_id]
def run_install_command(command: list[str], on_output) -> int:
output_lines: list[str] = []
process = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
shell=False,
bufsize=0,
)
assert process.stdout is not None
last_line = ""
for raw_line in process.stdout:
for line in format_install_output(raw_line):
if line and line != last_line:
on_output(line)
output_lines.append(line)
last_line = line
exit_code = process.wait()
failure_hint = explain_install_failure(_tool_name_from_command(command), exit_code, output_lines)
if failure_hint:
on_output(failure_hint)
return exit_code
def explain_install_failure(tool_name: str | None, exit_code: int, output_lines: list[str]) -> str:
if exit_code == 0:
return ""
output_text = "\n".join(output_lines)
if tool_name == "Maven" and ("找不到与输入条件匹配的程序包" in output_text or "No package found" in output_text):
return (
"winget 当前源找不到 Maven 包。请手动下载 Apache Maven解压后在工具里选择 "
"apache-maven 的 bin\\mvn.cmd或把该 bin 目录加入 PATH。"
)
return ""
def format_install_output(raw: bytes) -> list[str]:
text = _decode_process_bytes(raw)
text = ANSI_PATTERN.sub("", text).replace("\b", "")
lines: list[str] = []
for part in re.split(r"[\r\n]+", text):
cleaned = _clean_install_line(part)
if cleaned:
lines.append(cleaned)
return lines
def _decode_process_bytes(raw: bytes) -> str:
for encoding in ("utf-8", "utf-16", "gb18030", "mbcs"):
try:
return raw.decode(encoding)
except (LookupError, UnicodeDecodeError):
continue
return raw.decode("utf-8", errors="replace")
def _clean_install_line(line: str) -> str:
text = line.strip()
if not text:
return ""
percent = PERCENT_PATTERN.search(text)
progress_only = _looks_like_progress_line(text)
if percent and progress_only:
value = min(int(percent.group(1)), 100)
return f"\u5b89\u88c5\u8fdb\u5ea6: {value}%"
if progress_only:
return ""
return text
def _looks_like_progress_line(text: str) -> bool:
if not text:
return False
progress_count = sum(1 for char in text if char in PROGRESS_CHARS)
percent = bool(PERCENT_PATTERN.search(text))
if percent and progress_count:
return True
visible = [char for char in text if not char.isspace()]
if not visible:
return False
return progress_count / len(visible) > 0.45
def _tool_name_from_command(command: list[str]) -> str | None:
package_ids = {package_id: name for name, package_id in WINGET_PACKAGES.items()}
package_ids.update({package_id: "JDK" for package_id in JDK_PACKAGES.values()})
for part in command:
name = package_ids.get(part)
if name:
return name
return None
def _map_java_version(version: str) -> str:
try:
major = int(version.split(".", 1)[0])
except ValueError:
return "8"
if major <= 8:
return "8"
if major <= 11:
return "11"
if major <= 17:
return "17"
return "21"