cb43b84249
Пока MCP-сервер работал прямо из src\...\bin, он держал открытым kompas-mcp.exe
и Kompas.Mcp.Core.dll — dotnet build падал с MSB3027 от каждой живой сессии.
Лаунчер копирует сборку в %LOCALAPPDATA%\kompas-mcp\dev\<отпечаток> и запускает
её оттуда, поэтому bin свободен всегда.
- tools/dev/KompasMcpDev.psm1 — отпечаток каталога сборки, теневая копия через
атомарный Directory.Move, уборка старых копий мимо занятых живым процессом,
сессионный mutex на одновременный старт нескольких сессий;
- tools/dev/launch-dev-mcp.ps1 — точка входа для .mcp.json, инвариант «в stdout
до старта сервера не уходит ни байта»;
- tools/dev/rebuild-mcp.ps1 — -Check компилирует мимо bin (~2 с при живом
сервере), обычный режим отказывается заранее и называет блокирующие PID;
- tools/tests/DevLauncher.Tests.ps1 — Pester на все три файла.
Раздел «The rebuild cycle» в CLAUDE.md (2c56511) уже описывал эту схему.
103 lines
5.3 KiB
PowerShell
103 lines
5.3 KiB
PowerShell
#requires -Version 7.0
|
||
<#
|
||
.SYNOPSIS
|
||
Сборка MCP-сервера для dev-цикла: с диагностикой блокировки bin живыми сессиями.
|
||
|
||
.DESCRIPTION
|
||
Три режима, по возрастанию стоимости:
|
||
|
||
-Check компиляция мимо bin (сервер можно не останавливать) — «оно вообще собирается?»
|
||
(по умолчанию) полная сборка в bin + юнит-тесты — новый бинарь для теневого запуска
|
||
-NoTest то же без юнит-тестов
|
||
|
||
Каталог bin занят, пока хотя бы одна сессия MCP запущена ПРЯМО из него (см. .mcp.json).
|
||
С tools/dev/launch-dev-mcp.ps1 сервер работает из теневой копии, и bin свободен всегда.
|
||
|
||
.EXAMPLE
|
||
pwsh -NoProfile -File tools/dev/rebuild-mcp.ps1 -Check
|
||
.EXAMPLE
|
||
pwsh -NoProfile -File tools/dev/rebuild-mcp.ps1
|
||
#>
|
||
[CmdletBinding()]
|
||
param(
|
||
[switch] $Check,
|
||
[switch] $NoTest
|
||
)
|
||
|
||
Set-StrictMode -Version Latest
|
||
$ErrorActionPreference = 'Stop'
|
||
|
||
$repo = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
|
||
$binDir = Join-Path $repo 'src\Kompas.Mcp.Host\bin\x64\Release\net8.0-windows'
|
||
|
||
function Get-BlockingProcess {
|
||
param([Parameter(Mandatory)] [string] $Directory)
|
||
|
||
$target = $Directory.TrimEnd('\').ToLowerInvariant()
|
||
$found = @()
|
||
foreach ($p in (Get-Process -Name 'kompas-mcp' -ErrorAction SilentlyContinue)) {
|
||
try {
|
||
if (-not $p.Path) { continue }
|
||
if ((Split-Path -Parent $p.Path).TrimEnd('\').ToLowerInvariant() -eq $target) { $found += $p }
|
||
}
|
||
catch {
|
||
# .Path недоступен (чужой пользователь / процесс уже умер) — считать такой
|
||
# процесс блокирующим нельзя, но и молчать нехорошо: сборка ниже всё равно
|
||
# покажет настоящего держателя файла в тексте MSB3027.
|
||
Write-Verbose "не удалось прочитать путь процесса $($p.Id): $($_.Exception.Message)"
|
||
}
|
||
}
|
||
return $found
|
||
}
|
||
|
||
if ($Check) {
|
||
# Стабильный (не случайный) каталог — чтобы работала инкрементальная сборка.
|
||
# Перенаправляем ТОЛЬКО BaseOutputPath: если увести ещё и BaseIntermediateOutputPath,
|
||
# старый obj\ в дереве остаётся видимым для glob'а и даёт CS0579 «Повторяющийся
|
||
# атрибут» на сгенерированном AssemblyInfo. Обратная сторона — obj\ остаётся общим
|
||
# с обычной сборкой, поэтому два параллельных прогона топчут инкрементальный кэш
|
||
# друг друга (безопасно, но лишняя перекомпиляция).
|
||
$stamp = [System.BitConverter]::ToString(
|
||
[System.Security.Cryptography.SHA256]::HashData(
|
||
[System.Text.Encoding]::UTF8.GetBytes($repo.ToLowerInvariant()))[0..3]).Replace('-', '')
|
||
$out = Join-Path ([System.IO.Path]::GetTempPath()) "kompas-mcp-check\$stamp\"
|
||
|
||
Write-Host "Проверка компиляции (bin не трогаем, выход → $out)" -ForegroundColor Cyan
|
||
dotnet build -c Release -p:BaseOutputPath=$out
|
||
exit $LASTEXITCODE
|
||
}
|
||
|
||
$blocking = Get-BlockingProcess -Directory $binDir
|
||
if ($blocking.Count -gt 0) {
|
||
Write-Host 'Сборка невозможна: bin занят живыми сессиями MCP.' -ForegroundColor Red
|
||
foreach ($p in $blocking) {
|
||
Write-Host (" PID {0}, запущен {1}" -f $p.Id, $p.StartTime.ToString('HH:mm:ss'))
|
||
}
|
||
Write-Host ''
|
||
Write-Host 'Варианты:' -ForegroundColor Yellow
|
||
Write-Host ' 1. Перевести .mcp.json на tools/dev/launch-dev-mcp.ps1 (теневой запуск) — тогда'
|
||
Write-Host ' bin свободен всегда, и эта ситуация больше не повторится.'
|
||
Write-Host ' 2. Закрыть все сессии Claude Code с этим MCP и повторить.'
|
||
Write-Host ' 3. Разовая проверка компиляции без остановки сервера: -Check'
|
||
exit 2
|
||
}
|
||
|
||
Write-Host 'Сборка (Release)…' -ForegroundColor Cyan
|
||
dotnet build -c Release
|
||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||
|
||
if (-not $NoTest) {
|
||
Write-Host 'Юнит-тесты…' -ForegroundColor Cyan
|
||
dotnet test --filter 'Category=Unit' --nologo
|
||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||
}
|
||
|
||
Import-Module (Join-Path $PSScriptRoot 'KompasMcpDev.psm1') -Force
|
||
$fp = Get-KompasMcpBuildFingerprint -Directory $binDir
|
||
|
||
Write-Host ''
|
||
Write-Host "Готово. Отпечаток сборки: $fp" -ForegroundColor Green
|
||
Write-Host 'Сервер подхватит её при следующем запуске процесса MCP:'
|
||
Write-Host ' /mcp → переподключить kompas (если клиент умеет), иначе — перезапуск сессии.'
|
||
Write-Host 'После переподключения: kompas_status покажет живое соединение; эскизы по id теряются.'
|