Forge
csharp4405de34
1#nullable enable
2using System.Text.Json;
3
4namespace CascadeIDE.Services;
5
6/// <summary>Определяет путь к выходной сборке проекта для отладки через <c>dotnet msbuild -getProperty</c>.</summary>
7public static class MsBuildDebugTargetResolver
8{
9 /// <summary>
10 /// Возвращает полный путь к основной сборке (обычно .dll) и проверяет, что проект исполняемый (не Library).
11 /// </summary>
12 public static async Task<(string? TargetPath, string? Error)> TryResolveAsync(
13 string csprojFullPath,
14 IDotnetCommandRunner dotnet,
15 string configuration = "Debug",
16 CancellationToken cancellationToken = default)
17 {
18 if (string.IsNullOrWhiteSpace(csprojFullPath) || !File.Exists(csprojFullPath))
19 return (null, "Файл проекта не найден.");
20
21 var projectDir = Path.GetDirectoryName(CanonicalFilePath.Normalize(csprojFullPath));
22 if (string.IsNullOrEmpty(projectDir))
23 return (null, "Не удалось определить каталог проекта.");
24
25 var config = NormalizeConfiguration(configuration);
26 var args = BuildMsBuildGetPropertyArguments(csprojFullPath, config);
27
28 var (success, exitCode, output) = await dotnet.RunAsync(args, projectDir, cancellationToken).ConfigureAwait(false);
29 if (!success)
30 return (null, $"msbuild завершился с кодом {exitCode}: {output}");
31
32 if (!TryParsePropertiesOutput(output, out var outputType, out var targetPath) || string.IsNullOrWhiteSpace(targetPath))
33 return (null, "Не удалось разобрать вывод msbuild. Вывод: " + Truncate(output, 500));
34
35 if (string.Equals(outputType, "Library", StringComparison.OrdinalIgnoreCase))
36 return (null, "Проект с выходом Library не подходит как стартовый для отладки (нужен Exe / WinExe).");
37
38 return ValidateBuiltDllExists(targetPath!);
39 }
40
41 private static string NormalizeConfiguration(string configuration) =>
42 string.IsNullOrWhiteSpace(configuration) ? "Debug" : configuration.Trim();
43
44 private static string[] BuildMsBuildGetPropertyArguments(string csprojFullPath, string configuration) =>
45 [
46 "msbuild",
47 csprojFullPath,
48 "-nologo",
49 "-p:Configuration=" + configuration,
50 "-getProperty:OutputType",
51 "-getProperty:TargetPath"
52 ];
53
54 private static (string? TargetPath, string? Error) ValidateBuiltDllExists(string targetPathFromMsbuild)
55 {
56 var full = CanonicalFilePath.Normalize(targetPathFromMsbuild);
57 if (!File.Exists(full))
58 return (null, $"Сборка ещё не найдена: {full}. Собери решение (Собрать) и повтори.");
59 return (full, null);
60 }
61
62 /// <summary>Разбор JSON-вывода <c>dotnet msbuild -getProperty</c>.</summary>
63 public static bool TryParsePropertiesOutput(string stdout, out string? outputType, out string? targetPath)
64 {
65 outputType = null;
66 targetPath = null;
67 var s = stdout.Trim();
68 if (s.Length == 0)
69 return false;
70
71 var start = s.IndexOf('{');
72
73 if (start < 0)
74 return false;
75
76 try
77 {
78 using var doc = JsonDocument.Parse(s[start..]);
79 if (!doc.RootElement.TryGetProperty("Properties", out var props))
80 return false;
81 if (props.TryGetProperty("OutputType", out var ot))
82 outputType = ot.GetString();
83 if (props.TryGetProperty("TargetPath", out var tp))
84 targetPath = tp.GetString();
85 return !string.IsNullOrEmpty(targetPath);
86 }
87 catch
88 {
89 return false;
90 }
91 }
92
93 private static string Truncate(string s, int max)
94 {
95 if (s.Length <= max)
96 return s;
97 return s[..max] + "…";
98 }
99}
100
View only · write via MCP/CIDE