mirror of
https://github.com/waynebian01/Shigure.git
synced 2026-09-03 07:15:24 +08:00
添加 .NET SDK 依赖检测与自动安装功能,优化项目打包流程
This commit is contained in:
160
一键打包.py
160
一键打包.py
@@ -2,8 +2,11 @@ from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@@ -62,6 +65,11 @@ NAME_REPLACEMENT_PATTERN = re.compile(
|
||||
re.IGNORECASE,
|
||||
)
|
||||
SLASH_COMMAND_PATTERN = re.compile(r"/fu\b", re.IGNORECASE)
|
||||
TARGET_FRAMEWORK_PATTERN = re.compile(
|
||||
r"<TargetFramework>net(?P<major>\d+)\.(?P<minor>\d+)(?:-[^<]+)?</TargetFramework>",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
DOTNET_INSTALL_SCRIPT_URL = "https://dot.net/v1/dotnet-install.ps1"
|
||||
|
||||
|
||||
def configure_console() -> None:
|
||||
@@ -236,9 +244,137 @@ def apply_path_renames(
|
||||
completed_renames.append((old_path, new_path))
|
||||
|
||||
|
||||
def publish(root: Path, new_name: str) -> None:
|
||||
def get_required_dotnet_sdk(project_path: Path) -> tuple[str, int]:
|
||||
result = read_text(project_path)
|
||||
if result is None:
|
||||
raise RuntimeError(f"无法读取项目文件: {project_path}")
|
||||
|
||||
project_text, _ = result
|
||||
match = TARGET_FRAMEWORK_PATTERN.search(project_text)
|
||||
if match is None:
|
||||
raise RuntimeError("无法从项目文件的 TargetFramework 判断所需 .NET SDK 版本。")
|
||||
|
||||
major = int(match.group("major"))
|
||||
minor = int(match.group("minor"))
|
||||
return f"{major}.{minor}", major
|
||||
|
||||
|
||||
def get_user_dotnet_paths() -> tuple[Path, Path]:
|
||||
local_app_data = os.environ.get("LOCALAPPDATA")
|
||||
install_dir = (
|
||||
Path(local_app_data) / "Microsoft" / "dotnet"
|
||||
if local_app_data
|
||||
else Path.home() / ".dotnet"
|
||||
)
|
||||
return install_dir, install_dir / "dotnet.exe"
|
||||
|
||||
|
||||
def get_installed_sdk_versions(dotnet_command: str | Path) -> list[str]:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[str(dotnet_command), "--list-sdks"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
)
|
||||
except (OSError, subprocess.CalledProcessError):
|
||||
return []
|
||||
|
||||
versions: list[str] = []
|
||||
for line in result.stdout.splitlines():
|
||||
version = line.partition(" ")[0].strip()
|
||||
if version:
|
||||
versions.append(version)
|
||||
return versions
|
||||
|
||||
|
||||
def find_compatible_dotnet(required_major: int) -> tuple[str | None, list[str]]:
|
||||
_, user_dotnet = get_user_dotnet_paths()
|
||||
candidates = [shutil.which("dotnet"), str(user_dotnet)]
|
||||
checked: set[str] = set()
|
||||
|
||||
for candidate in candidates:
|
||||
if not candidate:
|
||||
continue
|
||||
|
||||
normalized = os.path.normcase(os.path.abspath(candidate))
|
||||
if normalized in checked:
|
||||
continue
|
||||
checked.add(normalized)
|
||||
|
||||
versions = get_installed_sdk_versions(candidate)
|
||||
if any(version.split(".", 1)[0] == str(required_major) for version in versions):
|
||||
return candidate, versions
|
||||
|
||||
return None, []
|
||||
|
||||
|
||||
def install_dotnet_sdk(channel: str) -> str:
|
||||
install_dir, dotnet_path = get_user_dotnet_paths()
|
||||
powershell = shutil.which("powershell") or shutil.which("pwsh")
|
||||
if powershell is None:
|
||||
raise RuntimeError("找不到 PowerShell,无法自动安装 .NET SDK。")
|
||||
|
||||
install_dir.mkdir(parents=True, exist_ok=True)
|
||||
with tempfile.TemporaryDirectory(prefix="shigure-dotnet-") as temp_dir:
|
||||
installer_path = Path(temp_dir) / "dotnet-install.ps1"
|
||||
print(f"正在从微软官网下载 .NET {channel} SDK 安装脚本……")
|
||||
urllib.request.urlretrieve(DOTNET_INSTALL_SCRIPT_URL, installer_path)
|
||||
|
||||
command = [
|
||||
powershell,
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
str(installer_path),
|
||||
"-Channel",
|
||||
channel,
|
||||
"-Quality",
|
||||
"GA",
|
||||
"-Architecture",
|
||||
"x64",
|
||||
"-InstallDir",
|
||||
str(install_dir),
|
||||
"-NoPath",
|
||||
]
|
||||
print(f"正在安装到当前用户目录: {install_dir}")
|
||||
subprocess.run(command, check=True)
|
||||
|
||||
if not dotnet_path.exists():
|
||||
raise RuntimeError(f"安装完成后仍找不到 dotnet: {dotnet_path}")
|
||||
return str(dotnet_path)
|
||||
|
||||
|
||||
def ensure_dotnet_sdk(project_path: Path) -> str:
|
||||
channel, required_major = get_required_dotnet_sdk(project_path)
|
||||
print()
|
||||
print(f"正在检测 .NET {channel} SDK 依赖……")
|
||||
|
||||
dotnet_command, versions = find_compatible_dotnet(required_major)
|
||||
if dotnet_command is not None:
|
||||
matching_versions = [
|
||||
version for version in versions
|
||||
if version.split(".", 1)[0] == str(required_major)
|
||||
]
|
||||
print(f"已安装兼容的 SDK: {', '.join(matching_versions)}")
|
||||
return dotnet_command
|
||||
|
||||
print(f"未检测到 .NET {channel} SDK,开始自动下载和安装。")
|
||||
dotnet_command = install_dotnet_sdk(channel)
|
||||
versions = get_installed_sdk_versions(dotnet_command)
|
||||
if not any(version.split(".", 1)[0] == str(required_major) for version in versions):
|
||||
raise RuntimeError(f".NET {channel} SDK 安装后验证失败。")
|
||||
|
||||
print(f".NET {channel} SDK 安装并验证成功。")
|
||||
return dotnet_command
|
||||
|
||||
|
||||
def publish(root: Path, new_name: str, dotnet_command: str) -> None:
|
||||
command = [
|
||||
"dotnet",
|
||||
dotnet_command,
|
||||
"publish",
|
||||
f".\\{new_name}.csproj",
|
||||
"-c",
|
||||
@@ -275,13 +411,12 @@ def main() -> int:
|
||||
|
||||
new_name = ask_new_name()
|
||||
should_rename = new_name != OLD_NAME
|
||||
source_csproj = root / f"{OLD_NAME}.csproj"
|
||||
if not source_csproj.exists():
|
||||
print(f"找不到需要打包的项目文件: {source_csproj}")
|
||||
return 1
|
||||
|
||||
if should_rename:
|
||||
source_csproj = root / f"{OLD_NAME}.csproj"
|
||||
if not source_csproj.exists():
|
||||
print(f"找不到需要打包的项目文件: {source_csproj}")
|
||||
return 1
|
||||
|
||||
backups = collect_replacements(root, script_path)
|
||||
preview_files = list(backups)
|
||||
rename_plan = collect_path_renames(root, script_path, new_name)
|
||||
@@ -313,6 +448,15 @@ def main() -> int:
|
||||
print("已取消。")
|
||||
return 0
|
||||
|
||||
try:
|
||||
dotnet_command = ensure_dotnet_sdk(source_csproj)
|
||||
except BaseException as exc:
|
||||
if isinstance(exc, KeyboardInterrupt):
|
||||
print("依赖安装已中断,项目名称尚未修改。")
|
||||
else:
|
||||
print(f"依赖检测或安装失败,项目名称尚未修改: {exc}")
|
||||
return 1
|
||||
|
||||
completed_renames: list[tuple[Path, Path]] = []
|
||||
try:
|
||||
if should_rename:
|
||||
@@ -323,7 +467,7 @@ def main() -> int:
|
||||
print(f"名称替换完成,已永久修改 {len(backups)} 个文本文件。")
|
||||
print(f"已重命名 {len(completed_renames)} 个文件或目录。")
|
||||
|
||||
publish(root, new_name)
|
||||
publish(root, new_name, dotnet_command)
|
||||
except BaseException as exc:
|
||||
if isinstance(exc, KeyboardInterrupt):
|
||||
print("执行已中断。")
|
||||
|
||||
Reference in New Issue
Block a user