118 lines
6.8 KiB
C#
118 lines
6.8 KiB
C#
using System.Diagnostics;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Nodes;
|
|
|
|
static class Program
|
|
{
|
|
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
|
static extern int MessageBoxW(IntPtr hWnd, string text, string caption, uint type);
|
|
|
|
static int Main(string[] args)
|
|
{
|
|
// Preserve a drive root (for example F:\\ on a portable USB drive).
|
|
var root = Path.TrimEndingDirectorySeparator(AppContext.BaseDirectory);
|
|
var rootPrefix = Path.EndsInDirectorySeparator(root) ? root : root + Path.DirectorySeparatorChar;
|
|
var log = Path.Combine(root, "logs", "launcher.log");
|
|
try
|
|
{
|
|
Directory.CreateDirectory(Path.GetDirectoryName(log)!);
|
|
Directory.CreateDirectory(Path.Combine(root, "data"));
|
|
var marker = Path.Combine(root, "data", "runtime-path.txt");
|
|
var engine = File.Exists(marker)
|
|
? Path.GetFullPath(Path.Combine(root, File.ReadAllText(marker).Trim()))
|
|
: Directory.EnumerateFiles(Path.Combine(root, "runtime"), "FreeCAD.exe", SearchOption.AllDirectories)
|
|
.First(path => string.Equals(Path.GetFileName(Path.GetDirectoryName(path)), "bin", StringComparison.OrdinalIgnoreCase));
|
|
if (!engine.StartsWith(rootPrefix, StringComparison.OrdinalIgnoreCase) || !File.Exists(engine))
|
|
throw new IOException("Не найдены библиотеки CAD. Выполните scripts\\bootstrap.ps1.");
|
|
var nativeEngine = RuntimePath.ForEngine(root, engine);
|
|
var start = new ProcessStartInfo(nativeEngine) { UseShellExecute = false, WorkingDirectory = root };
|
|
start.Environment["QI_ROOT"] = root;
|
|
start.Environment.Remove("QI_TEST");
|
|
var selfTest = args.Contains("--self-test", StringComparer.Ordinal);
|
|
var profile = Path.Combine(root, "data");
|
|
if (selfTest)
|
|
{
|
|
start.Environment["QI_TEST"] = "1";
|
|
profile = Path.Combine(root, "artifacts", "test-profiles", Guid.NewGuid().ToString("N"));
|
|
Directory.CreateDirectory(profile);
|
|
var report = Path.Combine(root, "artifacts", "validation.json");
|
|
if (File.Exists(report))
|
|
File.Move(report, Path.Combine(root, "artifacts", $"validation-previous-{DateTime.UtcNow:yyyyMMdd-HHmmss-fffffff}.json"));
|
|
}
|
|
start.ArgumentList.Add("--user-cfg");
|
|
start.ArgumentList.Add(Path.Combine(profile, "user.cfg"));
|
|
start.ArgumentList.Add("--system-cfg");
|
|
start.ArgumentList.Add(Path.Combine(profile, "system.cfg"));
|
|
start.ArgumentList.Add("--write-log");
|
|
start.ArgumentList.Add("--log-file");
|
|
start.ArgumentList.Add(Path.Combine(root, "logs", "cad.log"));
|
|
// Apply first-run native view preferences before restored sketches
|
|
// exist. Setting them afterwards would mark clean projects dirty.
|
|
start.ArgumentList.Add(Path.Combine(root, "scripts", "startup.FCMacro"));
|
|
foreach (var arg in args)
|
|
{
|
|
if (arg != "--self-test") start.ArgumentList.Add(arg);
|
|
}
|
|
File.AppendAllText(log, $"{DateTimeOffset.Now:O} Starting QInventor {engine}\nNative runtime path: {nativeEngine}\n");
|
|
using var process = Process.Start(start) ?? throw new IOException("Не удалось запустить CAD-процесс.");
|
|
process.WaitForExit();
|
|
File.AppendAllText(log, $"{DateTimeOffset.Now:O} Exit {process.ExitCode}\n");
|
|
if (process.ExitCode != 0) throw new IOException($"CAD-процесс завершился с кодом {process.ExitCode}. Подробности: logs\\cad.log.");
|
|
if (selfTest)
|
|
{
|
|
using var report = JsonDocument.Parse(File.ReadAllText(Path.Combine(root, "artifacts", "validation.json")));
|
|
if (report.RootElement.GetProperty("status").GetString() != "passed")
|
|
{
|
|
File.AppendAllText(log, $"{DateTimeOffset.Now:O} Проверка приложения завершилась с ошибками; artifacts\\validation.json\n");
|
|
return 2;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
try { File.AppendAllText(log, $"{DateTimeOffset.Now:O} {ex}\n"); } catch { }
|
|
if (args.Contains("--self-test", StringComparer.Ordinal))
|
|
{
|
|
// Native failures can interrupt a suite before Python writes its
|
|
// final status. Preserve completed checks and make that failure
|
|
// explicit instead of leaving a misleading running report.
|
|
try
|
|
{
|
|
var reportPath = Path.Combine(root, "artifacts", "validation.json");
|
|
Directory.CreateDirectory(Path.GetDirectoryName(reportPath)!);
|
|
var report = new JsonObject();
|
|
if (File.Exists(reportPath))
|
|
{
|
|
try
|
|
{
|
|
report = JsonNode.Parse(File.ReadAllText(reportPath)) as JsonObject ?? new JsonObject();
|
|
}
|
|
catch (JsonException)
|
|
{
|
|
// A native crash can interrupt Python's live JSON
|
|
// write. Preserve its remaining bytes, then still
|
|
// produce a readable failure report.
|
|
var damaged = Path.Combine(root, "artifacts", $"validation-truncated-{DateTime.UtcNow:yyyyMMdd-HHmmss-fffffff}.json");
|
|
File.Copy(reportPath, damaged);
|
|
}
|
|
}
|
|
report["status"] = "failed";
|
|
var previousFailed = report["failed"] is JsonValue count && count.TryGetValue<int>(out var failures) ? failures : 0;
|
|
report["failed"] = Math.Max(1, previousFailed);
|
|
report["launcher_error"] = ex.Message;
|
|
report["finished"] = DateTime.UtcNow.ToString("o");
|
|
var staged = reportPath + ".launcher-tmp";
|
|
File.WriteAllText(staged, report.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));
|
|
File.Move(staged, reportPath, overwrite: true);
|
|
}
|
|
catch { }
|
|
return 1;
|
|
}
|
|
MessageBoxW(IntPtr.Zero, "Не удалось запустить QInventor.\n\nПроверьте, что приложение перенесено вместе с папками runtime, src и scripts. Подробности ошибки сохранены в журнале.\n\nЖурнал: " + log, "QInventor", 0x10);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|