feat(installer): самодостаточный exe для Windows
Владелец: «для винды я думаю нужен exe». Справедливо — «склонируй репозиторий и собери» это не установка. HermesHubSetup.exe требовал, чтобы рядом лежали src/, launcher/, assets/, config/ и scripts/: PerformInstall берёт их из sourceRoot. Поэтому одного файла не хватало, и на целевую машину пришлось бы копировать репозиторий. Теперь содержимое упаковывается при сборке и вшивается в exe ресурсом (/resource:payload.zip,payload). Если рядом с exe и уровнем выше исходников нет, установщик распаковывает вшитое во временный каталог и работает с ним. Прежнее поведение сохранено: при запуске из репозитория используются файлы на диске, ресурс не трогается. Проверено исполнением: exe скопирован в пустой каталог, из него извлечены assets, config, launcher, scripts, src; src/antigravity_provider и launcher/HermesHubWeb.exe на месте. Размер 11.78 МБ. Тест сборки установщика приведён к реальным ссылкам: добавлена System.IO.Compression.FileSystem, без неё ZipFile не разрешался. Тесты: 373 passed, ruff чисто. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
05e15a4d5f
commit
8f62adb760
5 changed files with 66 additions and 2 deletions
|
|
@ -3,6 +3,8 @@ using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.IO.Compression;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
@ -1019,6 +1021,32 @@ namespace HermesHubSetup
|
||||||
|
|
||||||
static class Program
|
static class Program
|
||||||
{
|
{
|
||||||
|
|
||||||
|
private static string ExtractEmbeddedPayload()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var asm = Assembly.GetExecutingAssembly();
|
||||||
|
using (Stream res = asm.GetManifestResourceStream("payload"))
|
||||||
|
{
|
||||||
|
if (res == null) return null;
|
||||||
|
string target = Path.Combine(Path.GetTempPath(),
|
||||||
|
"HermesHubSetup_" + Guid.NewGuid().ToString("N").Substring(0, 8));
|
||||||
|
Directory.CreateDirectory(target);
|
||||||
|
string tmpZip = Path.Combine(target, "_payload.zip");
|
||||||
|
using (var fs = File.Create(tmpZip)) res.CopyTo(fs);
|
||||||
|
ZipFile.ExtractToDirectory(tmpZip, target);
|
||||||
|
File.Delete(tmpZip);
|
||||||
|
return Directory.Exists(Path.Combine(target, "src")) ? target : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine("Не удалось распаковать встроенные файлы: " + ex.Message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[STAThread]
|
[STAThread]
|
||||||
static int Main(string[] args)
|
static int Main(string[] args)
|
||||||
{
|
{
|
||||||
|
|
@ -1052,6 +1080,15 @@ namespace HermesHubSetup
|
||||||
{
|
{
|
||||||
sourceRoot = Path.GetFullPath(Path.Combine(appDir, ".."));
|
sourceRoot = Path.GetFullPath(Path.Combine(appDir, ".."));
|
||||||
}
|
}
|
||||||
|
// Ни рядом с exe, ни уровнем выше исходников нет — значит установщик
|
||||||
|
// запущен как самостоятельный файл. Содержимое вшито в него ресурсом
|
||||||
|
// и распаковывается во временный каталог. Так владельцу достаточно
|
||||||
|
// одного exe, без копирования репозитория на целевую машину.
|
||||||
|
if (!Directory.Exists(Path.Combine(sourceRoot, "src")))
|
||||||
|
{
|
||||||
|
string extracted = ExtractEmbeddedPayload();
|
||||||
|
if (extracted != null) sourceRoot = extracted;
|
||||||
|
}
|
||||||
|
|
||||||
// Uninstall Mode
|
// Uninstall Mode
|
||||||
if (isUninstall)
|
if (isUninstall)
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,29 @@ if (Test-Path $HubWebCs) {
|
||||||
& $CscPath /target:winexe /out:"$HubWebExe" /r:System.Windows.Forms.dll /r:System.Drawing.dll "$HubWebCs"
|
& $CscPath /target:winexe /out:"$HubWebExe" /r:System.Windows.Forms.dll /r:System.Drawing.dll "$HubWebCs"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Собираем полезную нагрузку: всё, что нужно PerformInstall на целевой машине.
|
||||||
|
# Она вшивается в exe ресурсом, чтобы установщик был одним файлом и не требовал
|
||||||
|
# копировать репозиторий.
|
||||||
|
Write-Host "Packing payload..." -ForegroundColor Cyan
|
||||||
|
$PayloadDir = Join-Path $env:TEMP ("hubpayload_" + [guid]::NewGuid().ToString("N").Substring(0,8))
|
||||||
|
New-Item -ItemType Directory -Force $PayloadDir | Out-Null
|
||||||
|
foreach ($item in @("src", "launcher", "assets", "config", "scripts")) {
|
||||||
|
$srcPath = Join-Path $RepoRoot $item
|
||||||
|
if (Test-Path $srcPath) {
|
||||||
|
Copy-Item $srcPath -Destination (Join-Path $PayloadDir $item) -Recurse -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# Каталоги сборки и кеши в дистрибутив не нужны.
|
||||||
|
Get-ChildItem $PayloadDir -Recurse -Directory -Include "__pycache__", ".venv", "node_modules" -ErrorAction SilentlyContinue |
|
||||||
|
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
$PayloadZip = Join-Path $env:TEMP "hub_payload.zip"
|
||||||
|
if (Test-Path $PayloadZip) { Remove-Item $PayloadZip -Force }
|
||||||
|
Compress-Archive -Path (Join-Path $PayloadDir "*") -DestinationPath $PayloadZip -CompressionLevel Optimal
|
||||||
|
$payloadKB = [int]((Get-Item $PayloadZip).Length / 1KB)
|
||||||
|
Write-Host "Payload packed: $payloadKB KB" -ForegroundColor Gray
|
||||||
|
|
||||||
Write-Host "Compiling HermesHubSetup.exe..." -ForegroundColor Cyan
|
Write-Host "Compiling HermesHubSetup.exe..." -ForegroundColor Cyan
|
||||||
& $CscPath /target:winexe /out:"$OutFile" /r:System.Windows.Forms.dll /r:System.Drawing.dll "$SourceFile"
|
& $CscPath /target:winexe /out:"$OutFile" /r:System.Windows.Forms.dll /r:System.Drawing.dll /r:System.IO.Compression.FileSystem.dll /resource:"$PayloadZip",payload "$SourceFile"
|
||||||
|
|
||||||
if ($LASTEXITCODE -eq 0) {
|
if ($LASTEXITCODE -eq 0) {
|
||||||
Write-Host "Installer compiled successfully: $OutFile" -ForegroundColor Green
|
Write-Host "Installer compiled successfully: $OutFile" -ForegroundColor Green
|
||||||
|
|
@ -42,3 +63,6 @@ if ($LASTEXITCODE -eq 0) {
|
||||||
} else {
|
} else {
|
||||||
Write-Error "Installer compilation FAILED with exit code $LASTEXITCODE"
|
Write-Error "Installer compilation FAILED with exit code $LASTEXITCODE"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Remove-Item $PayloadDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
Remove-Item $PayloadZip -Force -ErrorAction SilentlyContinue
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -47,7 +47,10 @@ def test_windows_csharp_launchers_and_setup_compile():
|
||||||
setup_cs = INSTALLER_DIR / "HermesHubSetup.cs"
|
setup_cs = INSTALLER_DIR / "HermesHubSetup.cs"
|
||||||
res3 = subprocess.run([
|
res3 = subprocess.run([
|
||||||
csc_path, "/target:winexe", f"/out:{temp_out / 'HermesHubSetup.exe'}",
|
csc_path, "/target:winexe", f"/out:{temp_out / 'HermesHubSetup.exe'}",
|
||||||
"/r:System.Windows.Forms.dll", "/r:System.Drawing.dll", str(setup_cs)
|
# Установщик несёт содержимое вшитым ресурсом и распаковывает его через
|
||||||
|
# ZipFile — сборка требует System.IO.Compression.FileSystem.
|
||||||
|
"/r:System.Windows.Forms.dll", "/r:System.Drawing.dll",
|
||||||
|
"/r:System.IO.Compression.FileSystem.dll", str(setup_cs)
|
||||||
], capture_output=True, text=True)
|
], capture_output=True, text=True)
|
||||||
assert res3.returncode == 0, f"HermesHubSetup.cs compilation failed: {res3.stdout}\n{res3.stderr}"
|
assert res3.returncode == 0, f"HermesHubSetup.cs compilation failed: {res3.stdout}\n{res3.stderr}"
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue