feat(launcher): SimpleLite 进程独立化与 DLL 热更新
- FollowParent=false 时在 Windows 用 cmd /c start 脱离父进程组,关闭 MiGu.Server 不再连带结束 SimpleLite - 新增 SimpleLiteBuildSync:SimpleLite 未运行时把 obj/Debug 最新 DLL 同步到 bin/Debug,并探测「前往站点」API 是否可用 - 新增 RestartForUpdate 与 POST /api/health/simplelite/restart-for-update:一键关闭、同步、重启以加载新 API - 诊断增加 FollowParent / GotoSiteApiAvailable / DeployHint;新增 scripts/redeploy-simplelite.ps1 部署脚本 - Dispose 增加 _disposed 幂等保护,避免重复释放
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MiGu.Server.Launcher;
|
||||
|
||||
namespace MiGu.Server.Controllers;
|
||||
@@ -39,4 +40,16 @@ public class HealthController : ControllerBase
|
||||
/// </summary>
|
||||
[HttpGet("simplelite")]
|
||||
public IActionResult GetSimpleLiteDiagnostics() => Ok(_launcher.GetDiagnostics());
|
||||
|
||||
/// <summary>
|
||||
/// 关闭 SimpleLite、同步最新 DLL、重新拉起。用于「前往站点」API 缺失时一键更新。
|
||||
/// </summary>
|
||||
[HttpPost("simplelite/restart-for-update")]
|
||||
[Authorize]
|
||||
public IActionResult RestartSimpleLiteForUpdate([FromQuery] string launchMode = "webonly")
|
||||
{
|
||||
var result = _launcher.RestartForUpdate(launchMode);
|
||||
var diag = _launcher.GetDiagnostics();
|
||||
return Ok(new { restart = result, diagnostics = diag });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MiGu.Server.Launcher;
|
||||
|
||||
/// <summary>
|
||||
/// 将 <c>obj/Debug</c> 下最新编译的 SimpleLite 同步到 <c>bin/Debug</c>(仅当 SimpleLite 未运行时)。
|
||||
/// </summary>
|
||||
public static class SimpleLiteBuildSync
|
||||
{
|
||||
public static bool TrySyncFromObjToBin(string contentRoot, ILogger? log = null)
|
||||
{
|
||||
if (!TryResolvePaths(contentRoot, out var objDll, out var objExe, out var binDir))
|
||||
return false;
|
||||
|
||||
var binDll = Path.Combine(binDir, "SimpleLite.dll");
|
||||
var binExe = Path.Combine(binDir, "SimpleLite.exe");
|
||||
|
||||
if (!File.Exists(objDll))
|
||||
{
|
||||
log?.LogDebug("[SimpleLiteBuildSync] obj DLL 不存在: {Path}", objDll);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Process.GetProcessesByName("SimpleLite").Any(p => !p.HasExited))
|
||||
{
|
||||
log?.LogWarning("[SimpleLiteBuildSync] SimpleLite 仍在运行,跳过 DLL 同步。请先关闭 SimpleLite 窗口。");
|
||||
return false;
|
||||
}
|
||||
|
||||
var objTime = File.GetLastWriteTimeUtc(objDll);
|
||||
if (File.Exists(binDll) && File.GetLastWriteTimeUtc(binDll) >= objTime)
|
||||
{
|
||||
log?.LogDebug("[SimpleLiteBuildSync] bin 已是最新,无需同步");
|
||||
return false;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(binDir);
|
||||
File.Copy(objDll, binDll, true);
|
||||
log?.LogInformation("[SimpleLiteBuildSync] 已同步 {Src} → {Dst}", objDll, binDll);
|
||||
|
||||
if (File.Exists(objExe))
|
||||
{
|
||||
File.Copy(objExe, binExe, true);
|
||||
log?.LogInformation("[SimpleLiteBuildSync] 已同步 {Src} → {Dst}", objExe, binExe);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool? ProbeGotoSiteRoute(int port = 8222)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(2) };
|
||||
using var resp = client.PostAsync(
|
||||
$"http://127.0.0.1:{port}/projection/reflection/car/0/goto-site?siteId=0",
|
||||
null).GetAwaiter().GetResult();
|
||||
var body = resp.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||
if (resp.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
return body.Contains("<html", StringComparison.OrdinalIgnoreCase) ? false : true;
|
||||
return body.Contains("\"success\"", StringComparison.Ordinal);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryResolvePaths(string contentRoot, out string objDll, out string objExe, out string binDir)
|
||||
{
|
||||
objDll = objExe = "";
|
||||
binDir = "";
|
||||
var repo = FindRepoRoot(contentRoot);
|
||||
if (repo == null) return false;
|
||||
var sl = Path.Combine(repo, "Simple", "SimpleLite");
|
||||
objDll = Path.Combine(sl, "obj", "Debug", "SimpleLite.dll");
|
||||
objExe = Path.Combine(sl, "obj", "Debug", "SimpleLite.exe");
|
||||
binDir = Path.Combine(sl, "bin", "Debug");
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string? FindRepoRoot(string contentRoot)
|
||||
{
|
||||
var dir = new DirectoryInfo(contentRoot);
|
||||
while (dir != null)
|
||||
{
|
||||
if (Directory.Exists(Path.Combine(dir.FullName, "Simple", "SimpleLite")))
|
||||
return dir.FullName;
|
||||
dir = dir.Parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,9 @@ namespace MiGu.Server.Launcher;
|
||||
/// 设计要点(与原 <c>SimpleLite/Platform/PlatformLauncher.cs</c> 镜像):
|
||||
/// - 幂等:第一次登录拉起,后续登录复用同一个子进程(不会重复启)。
|
||||
/// - 子进程退出后下一次登录可以重新拉起(不阻塞用户重试)。
|
||||
/// - 生命周期跟随:Windows 上用 JobObject 把子进程绑定到 MiGu.Server 进程;
|
||||
/// MiGu.Server 被 kill -9 / 任务管理器 → 结束进程树 时,SimpleLite 一并被 OS SIGKILL。
|
||||
/// - 默认独立(<see cref="SimpleLiteOptions.FollowParent"/> = false):SimpleLite 与 MiGu.Server 互不影响,
|
||||
/// 关闭任一方不会 kill 另一方;Windows 上用 <c>cmd /c start</c> 脱离父进程组/控制台。
|
||||
/// - 可选跟随(FollowParent = true):Windows JobObject 绑定,MiGu.Server 退出时一并结束 SimpleLite。
|
||||
/// - 命令行透传:把 LaunchMode 翻成 SimpleLite 的 <c>--display-mode=web</c> / <c>--display-mode=web+local</c>。
|
||||
/// - 启动后阻塞等待 SimpleLite Projection :8222 端口可达(最长 ReadinessTimeoutMs),让前端 /api/sl/* 不再立刻 502。
|
||||
/// </summary>
|
||||
@@ -26,6 +27,7 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
private Process? _proc;
|
||||
private IntPtr _job = IntPtr.Zero;
|
||||
private string? _lastLaunchMode;
|
||||
private bool _disposed;
|
||||
|
||||
public SimpleLiteLauncher(IOptions<SimpleLiteOptions> opts, ILogger<SimpleLiteLauncher> log, IHostEnvironment env)
|
||||
{
|
||||
@@ -118,13 +120,21 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
// 推断 runMode 时不会落到 "web+local" 兜底,前端 RunMode 角标也不会与实际不符。
|
||||
_lastLaunchMode = ExternalReuseLaunchMode;
|
||||
|
||||
var reuseWarning = $"检测到 SimpleLite 已经在 :{_opts.ProjectionPort} 上运行(可能是 MiGu.Server 重启前残留)。" +
|
||||
$"本次选择的启动模式『{launchMode}』未被应用到既有实例。如需切换,请手动关闭旧 SimpleLite 窗口后重新登录。";
|
||||
if (SimpleLiteBuildSync.ProbeGotoSiteRoute(_opts.ProjectionPort) == false)
|
||||
{
|
||||
reuseWarning += " 当前 SimpleLite 版本过旧,缺少「前往站点」API;请关闭 SimpleLite 窗口后调用 POST /api/health/simplelite/restart-for-update,或运行 scripts/redeploy-simplelite.ps1。";
|
||||
}
|
||||
|
||||
return new LaunchResult(true, "ReusingExisting",
|
||||
$"projection :{_opts.ProjectionPort} reachable; requested displayMode={displayMode} not applied to existing instance",
|
||||
DisplayMode: ExternalReuseLaunchMode,
|
||||
Warning: $"检测到 SimpleLite 已经在 :{_opts.ProjectionPort} 上运行(可能是 MiGu.Server 重启前残留)。" +
|
||||
$"本次选择的启动模式『{launchMode}』未被应用到既有实例。如需切换,请手动关闭旧 SimpleLite 窗口后重新登录。");
|
||||
Warning: reuseWarning);
|
||||
}
|
||||
|
||||
SimpleLiteBuildSync.TrySyncFromObjToBin(_env.ContentRootPath, _log);
|
||||
|
||||
var resolved = ResolveExecutable(_opts.ExecutablePath);
|
||||
if (resolved == null)
|
||||
{
|
||||
@@ -143,52 +153,21 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
|
||||
var arguments = BuildArguments(displayMode, _opts.Arguments);
|
||||
|
||||
// 会话 N+2:把 SimpleLite 作为独立进程拉起,**带自己的控制台窗口**。
|
||||
// - UseShellExecute=true:交给 OS Shell 启动,生成新的进程组 + 独立 console,
|
||||
// MiGu.Server 关闭不会影响它(不再处于父进程的「controlling process」链上)。
|
||||
// - CreateNoWindow=false + 不重定向 stdout/stderr:SimpleLite 自带 OutputType=Exe 控制台,
|
||||
// 即便选 webonly 模式也会有一个黑底控制台显示日志,便于用户观察 / 关闭。
|
||||
// - 不能与 RedirectStandard* 共用,要让日志独立就只能让用户看 SimpleLite 自己的窗口。
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = resolved,
|
||||
Arguments = arguments,
|
||||
WorkingDirectory = workdir,
|
||||
UseShellExecute = true,
|
||||
CreateNoWindow = false,
|
||||
WindowStyle = ProcessWindowStyle.Normal,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var proc = new Process { StartInfo = psi, EnableRaisingEvents = true };
|
||||
proc.Exited += (_, _) =>
|
||||
var proc = StartSimpleLiteProcess(resolved, arguments, workdir);
|
||||
if (proc == null)
|
||||
{
|
||||
int exit;
|
||||
try { exit = proc.ExitCode; } catch { exit = -1; }
|
||||
_log.LogInformation("[SimpleLite] process exited code={Code}; next login will relaunch if needed", exit);
|
||||
lock (_sync)
|
||||
{
|
||||
if (ReferenceEquals(_proc, proc))
|
||||
{
|
||||
_proc = null;
|
||||
_lastLaunchMode = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!proc.Start())
|
||||
{
|
||||
_log.LogError("[SimpleLite] Process.Start returned false; exe={Exe} args={Args}", resolved, arguments);
|
||||
_log.LogError("[SimpleLite] Process.Start returned null; exe={Exe} args={Args}", resolved, arguments);
|
||||
return new LaunchResult(false, "ProcessStartFailed", $"exe={resolved}", DisplayMode: null,
|
||||
Warning: $"启动 SimpleLite 进程失败(Process.Start 返回 false);exe={resolved}");
|
||||
Warning: $"启动 SimpleLite 进程失败;exe={resolved}");
|
||||
}
|
||||
|
||||
WireProcessExitHandler(proc);
|
||||
|
||||
_proc = proc;
|
||||
_lastLaunchMode = displayMode;
|
||||
|
||||
// FollowParent 仅在用户显式 opt-in 时启用;UseShellExecute=true 之下 JobObject 仍可绑定 pid,
|
||||
// 但会破坏「独立程序」语义,因此默认 false(见 SimpleLiteOptions.FollowParent 说明)。
|
||||
if (_opts.FollowParent) AttachToJobObject(proc);
|
||||
|
||||
_log.LogInformation("[SimpleLite] launched pid={Pid} displayMode={Mode} exe={Exe} args=\"{Args}\" workdir={Workdir}; standalone={Standalone}",
|
||||
@@ -223,6 +202,8 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
IntPtr job;
|
||||
lock (_sync)
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
proc = _proc;
|
||||
job = _job;
|
||||
_proc = null;
|
||||
@@ -231,13 +212,12 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
|
||||
if (!_opts.FollowParent)
|
||||
{
|
||||
// 独立模式:不真的杀子进程,只释放本地引用让 GC 收 Process handle。
|
||||
// 这样 MiGu.Server 重启时下一次登录可以重新 MaybeStart 拉一个新的 SimpleLite,
|
||||
// 而老 SimpleLite 仍然在自己的窗口里跑。
|
||||
// 独立模式:不杀子进程,仅释放 MiGu.Server 侧 Process 句柄。
|
||||
if (proc is { HasExited: false })
|
||||
{
|
||||
_log.LogInformation("[SimpleLite] standalone mode: skip kill on Dispose; pid={Pid} keeps running", proc.Id);
|
||||
_log.LogInformation("[SimpleLite] standalone mode: MiGu.Server stopping — SimpleLite pid={Pid} keeps running (FollowParent=false)", proc.Id);
|
||||
}
|
||||
try { proc?.Dispose(); } catch { /* ignore */ }
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -273,13 +253,64 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
return args;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭所有 SimpleLite 进程、同步 obj→bin 最新 DLL,再按 launchMode 重新拉起。
|
||||
/// </summary>
|
||||
public LaunchResult RestartForUpdate(string launchMode = "webonly")
|
||||
{
|
||||
foreach (var proc in Process.GetProcessesByName("SimpleLite"))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!proc.HasExited)
|
||||
{
|
||||
proc.Kill(entireProcessTree: true);
|
||||
_log.LogInformation("[SimpleLite] restart-for-update: killed pid={Pid}", proc.Id);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.LogWarning("[SimpleLite] restart-for-update: kill pid={Pid} failed: {Msg}", proc.Id, ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
proc.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
Thread.Sleep(1500);
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
_proc = null;
|
||||
_lastLaunchMode = null;
|
||||
}
|
||||
|
||||
var synced = SimpleLiteBuildSync.TrySyncFromObjToBin(_env.ContentRootPath, _log);
|
||||
var result = MaybeStart(launchMode);
|
||||
if (!synced && result.Warning == null)
|
||||
{
|
||||
return result with
|
||||
{
|
||||
Warning = "未能从 obj/Debug 同步 DLL(可能未编译或 SimpleLite 仍占用文件)。请先运行 scripts/redeploy-simplelite.ps1。"
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>启动前诊断:当前配置、解析到的 exe、端口占用等(供 /api/health/simplelite 与启动日志)。</summary>
|
||||
public SimpleLiteDiagnostics GetDiagnostics()
|
||||
{
|
||||
var resolved = ResolveExecutable(_opts.ExecutablePath);
|
||||
var projectionUp = TryConnect("127.0.0.1", _opts.ProjectionPort, TimeSpan.FromMilliseconds(400));
|
||||
var gotoSite = projectionUp ? SimpleLiteBuildSync.ProbeGotoSiteRoute(_opts.ProjectionPort) : null;
|
||||
var deployHint = gotoSite == false
|
||||
? "关闭 SimpleLite 后:运行 Migu2.0/scripts/redeploy-simplelite.ps1,或 POST /api/health/simplelite/restart-for-update"
|
||||
: null;
|
||||
return new SimpleLiteDiagnostics(
|
||||
Enabled: _opts.Enabled,
|
||||
FollowParent: _opts.FollowParent,
|
||||
ConfiguredExecutablePath: _opts.ExecutablePath ?? "",
|
||||
ConfiguredWorkingDirectory: _opts.WorkingDirectory ?? "",
|
||||
ContentRootPath: _env.ContentRootPath,
|
||||
@@ -289,7 +320,116 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
LastLaunchMode: LastLaunchMode,
|
||||
ProjectionPort: _opts.ProjectionPort,
|
||||
ProjectionPortReachable: projectionUp,
|
||||
ConfigHint: "编辑 MiGu.Server/appsettings.json 的 SimpleLite 节点(所有环境生效);开发机可叠加 appsettings.Development.json");
|
||||
GotoSiteApiAvailable: gotoSite,
|
||||
DeployHint: deployHint,
|
||||
ConfigHint: "编辑 MiGu.Server/appsettings.json 的 SimpleLite 节点(所有环境生效);开发机可叠加 appsettings.Development.json。" +
|
||||
" FollowParent=false 时关闭 MiGu.Server 不会结束 SimpleLite。");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拉起 SimpleLite。FollowParent=false 时在 Windows 上用 <c>cmd /c start</c> 脱离父进程组,避免平台退出连带结束 SimpleLite。
|
||||
/// </summary>
|
||||
private Process? StartSimpleLiteProcess(string resolved, string arguments, string workdir)
|
||||
{
|
||||
if (_opts.FollowParent)
|
||||
return StartSimpleLiteDirect(resolved, arguments, workdir, useShellExecute: true);
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
return StartSimpleLiteDetachedWindows(resolved, arguments, workdir)
|
||||
?? StartSimpleLiteDirect(resolved, arguments, workdir, useShellExecute: true);
|
||||
|
||||
return StartSimpleLiteDirect(resolved, arguments, workdir, useShellExecute: true);
|
||||
}
|
||||
|
||||
private static Process? StartSimpleLiteDirect(string resolved, string arguments, string workdir, bool useShellExecute)
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = resolved,
|
||||
Arguments = arguments,
|
||||
WorkingDirectory = workdir,
|
||||
UseShellExecute = useShellExecute,
|
||||
CreateNoWindow = false,
|
||||
WindowStyle = ProcessWindowStyle.Normal,
|
||||
};
|
||||
var proc = new Process { StartInfo = psi, EnableRaisingEvents = true };
|
||||
return proc.Start() ? proc : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过 cmd start 在新进程组/新控制台中启动,与 MiGu.Server 控制台 Ctrl+C、进程树结束解耦。
|
||||
/// </summary>
|
||||
private Process? StartSimpleLiteDetachedWindows(string resolved, string arguments, string workdir)
|
||||
{
|
||||
var exeName = Path.GetFileNameWithoutExtension(resolved);
|
||||
var beforeIds = new HashSet<int>(
|
||||
Process.GetProcessesByName(exeName).Select(p => { try { return p.Id; } catch { return -1; } })
|
||||
.Where(id => id > 0));
|
||||
|
||||
var cmdArgs = $"/c start \"SimpleLite\" /D \"{workdir}\" \"{resolved}\" {arguments}";
|
||||
var shimPsi = new ProcessStartInfo
|
||||
{
|
||||
FileName = "cmd.exe",
|
||||
Arguments = cmdArgs,
|
||||
WorkingDirectory = workdir,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
};
|
||||
|
||||
using var shim = Process.Start(shimPsi);
|
||||
shim?.WaitForExit(8000);
|
||||
|
||||
for (var i = 0; i < 25; i++)
|
||||
{
|
||||
foreach (var p in Process.GetProcessesByName(exeName))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (p.HasExited) continue;
|
||||
if (beforeIds.Contains(p.Id)) continue;
|
||||
_log.LogInformation("[SimpleLite] detached start via cmd.exe; new pid={Pid}", p.Id);
|
||||
return AttachToExistingProcess(p.Id);
|
||||
}
|
||||
catch { /* ignore */ }
|
||||
}
|
||||
Thread.Sleep(200);
|
||||
}
|
||||
|
||||
_log.LogWarning("[SimpleLite] detached start: no new {Name} process observed after cmd.exe start", exeName);
|
||||
return null;
|
||||
}
|
||||
|
||||
private Process? AttachToExistingProcess(int pid)
|
||||
{
|
||||
try
|
||||
{
|
||||
var proc = Process.GetProcessById(pid);
|
||||
proc.EnableRaisingEvents = true;
|
||||
return proc;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.LogWarning("[SimpleLite] attach to pid={Pid} failed: {Msg}", pid, ex.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void WireProcessExitHandler(Process proc)
|
||||
{
|
||||
proc.Exited += (_, _) =>
|
||||
{
|
||||
int exit;
|
||||
try { exit = proc.ExitCode; } catch { exit = -1; }
|
||||
_log.LogInformation("[SimpleLite] process exited code={Code}; next login will relaunch if needed", exit);
|
||||
lock (_sync)
|
||||
{
|
||||
if (ReferenceEquals(_proc, proc))
|
||||
{
|
||||
_proc = null;
|
||||
_lastLaunchMode = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private string? ResolveExecutable(string configured)
|
||||
@@ -423,6 +563,7 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
|
||||
public sealed record SimpleLiteDiagnostics(
|
||||
bool Enabled,
|
||||
bool FollowParent,
|
||||
string ConfiguredExecutablePath,
|
||||
string ConfiguredWorkingDirectory,
|
||||
string ContentRootPath,
|
||||
@@ -432,6 +573,8 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
string? LastLaunchMode,
|
||||
int ProjectionPort,
|
||||
bool ProjectionPortReachable,
|
||||
bool? GotoSiteApiAvailable,
|
||||
string? DeployHint,
|
||||
string ConfigHint);
|
||||
|
||||
// ─── Windows JobObject:父进程被杀时 Job 内所有子进程一并 SIGKILL ────────────────────────
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# 将最新编译的 SimpleLite 部署到 bin\Debug 并提示重启。
|
||||
# 用法:先关闭 SimpleLite(任务管理器或托盘退出),再运行本脚本。
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
|
||||
$slProj = Join-Path $repoRoot 'Simple\SimpleLite\SimpleLite.csproj'
|
||||
$outDir = Join-Path $repoRoot 'Simple\SimpleLite\bin\Debug'
|
||||
$objDll = Join-Path $repoRoot 'Simple\SimpleLite\obj\Debug\SimpleLite.dll'
|
||||
|
||||
Write-Host '==> 编译 SimpleLite (DisableFody)...' -ForegroundColor Cyan
|
||||
dotnet build $slProj -c Debug -p:DisableFody=true
|
||||
if ($LASTEXITCODE -ne 0) { throw "dotnet build 失败 (exit $LASTEXITCODE)" }
|
||||
|
||||
$builtDll = if (Test-Path $objDll) { $objDll } else { Join-Path $outDir 'SimpleLite.dll' }
|
||||
|
||||
if (Get-Process SimpleLite -ErrorAction SilentlyContinue) {
|
||||
Write-Host ''
|
||||
Write-Host 'SimpleLite 仍在运行,无法覆盖 DLL。请先关闭 SimpleLite.exe,再重新运行本脚本。' -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
Copy-Item $builtDll (Join-Path $outDir 'SimpleLite.dll') -Force
|
||||
$objExe = Join-Path $repoRoot 'Simple\SimpleLite\obj\Debug\SimpleLite.exe'
|
||||
if (Test-Path $objExe) {
|
||||
Copy-Item $objExe (Join-Path $outDir 'SimpleLite.exe') -Force
|
||||
}
|
||||
|
||||
Write-Host '==> 已更新' $outDir -ForegroundColor Green
|
||||
Write-Host '请重新登录迷榖平台(或手动启动 SimpleLite.exe)以加载新 API。'
|
||||
Reference in New Issue
Block a user