Skip to content

Commit 262a625

Browse files
authored
Merge pull request #53 from Mythetech/feat/nuget
feat: nuget support
2 parents 16d4cec + fa884a2 commit 262a625

File tree

30 files changed

+1685
-14
lines changed

30 files changed

+1685
-14
lines changed

Apollo.Analysis.Worker/Program.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,8 @@
117117
{
118118
Path = item.Path,
119119
Content = item.Content
120-
}).ToList()
120+
}).ToList(),
121+
NuGetReferences = request.Solution.NuGetReferences
121122
};
122123

123124
var currentFile = cleanSolution.Items.FirstOrDefault(i => i.Path == request.Uri);

Apollo.Analysis/MonacoService.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,11 @@ public async Task<byte[]> GetQuickInfoAsync(string quickInfoRequestString)
246246
public async Task<byte[]> GetDiagnosticsAsync(string uri, Solution solution)
247247
{
248248
_projectService.UpdateSolution(solution);
249+
250+
if (solution.NuGetReferences?.Count > 0)
251+
{
252+
_projectService.SetNuGetReferences(solution.NuGetReferences);
253+
}
249254

250255
if (_projectService.IsLoadingReferences)
251256
{
@@ -270,7 +275,7 @@ public async Task<byte[]> GetDiagnosticsAsync(string uri, Solution solution)
270275
"Temp" + Random.Shared.Next(),
271276
syntaxTrees,
272277
options: RoslynProject.CompilationDefaults.GetCompilationOptions(solution.Type),
273-
references: _projectService.GetSystemReferencesOnly()
278+
references: _projectService.GetAllReferences()
274279
);
275280

276281
var allDiagnostics = await GetAllDiagnosticsAsync(compilation, syntaxTrees);

Apollo.Analysis/RoslynProjectService.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public class RoslynProjectService
2727
private ProjectId _projectId;
2828
private string? _currentDocumentPath;
2929
private MetadataReference? _userAssemblyReference;
30+
private readonly List<MetadataReference> _nugetReferences = [];
3031
private bool _isInitialized;
3132
private bool _isLoadingReferences;
3233

@@ -343,11 +344,47 @@ public IEnumerable<MetadataReference> GetSystemReferencesOnly()
343344
public IEnumerable<MetadataReference> GetAllReferences()
344345
{
345346
var references = new List<MetadataReference>(_systemReferences);
347+
references.AddRange(_nugetReferences);
346348
if (_userAssemblyReference != null)
347349
{
348350
references.Add(_userAssemblyReference);
349351
}
350352
return references;
351353
}
354+
355+
public void SetNuGetReferences(IEnumerable<NuGetReference> nugetRefs)
356+
{
357+
_nugetReferences.Clear();
358+
foreach (var nugetRef in nugetRefs)
359+
{
360+
if (nugetRef.AssemblyData?.Length > 0)
361+
{
362+
try
363+
{
364+
var reference = MetadataReference.CreateFromImage(nugetRef.AssemblyData);
365+
_nugetReferences.Add(reference);
366+
_logger.LogTrace($"Added NuGet reference for analysis: {nugetRef.AssemblyName}");
367+
}
368+
catch (Exception ex)
369+
{
370+
_logger.LogWarning($"Failed to add NuGet reference {nugetRef.AssemblyName}: {ex.Message}");
371+
}
372+
}
373+
}
374+
375+
UpdateNugetProjectReferences();
376+
}
377+
378+
private void UpdateNugetProjectReferences()
379+
{
380+
var currentSolution = _workspace.CurrentSolution;
381+
var project = currentSolution.GetProject(_projectId);
382+
if (project == null) return;
383+
384+
var allReferences = GetAllReferences().ToList();
385+
var newSolution = currentSolution.WithProjectMetadataReferences(_projectId, allReferences);
386+
_workspace.TryApplyChanges(newSolution);
387+
_logger.LogTrace($"Updated project with {allReferences.Count} total references");
388+
}
352389
}
353390

Apollo.Client/Layout/MainLayout.razor

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
@using Apollo.Components
22
@using Apollo.Components.Code
33
@using Apollo.Components.Infrastructure.Keyboard
4+
@using Apollo.Components.NuGet
45
@using Apollo.Components.Settings
56
@using Apollo.Components.Theme
67
@using Microsoft.FluentUI.AspNetCore.Components
@@ -33,6 +34,8 @@
3334

3435
[Inject] public KeyBindingsState KeyBindings { get; set; } = default!;
3536

37+
[Inject] public NuGetState NuGetState { get; set; } = default!;
38+
3639
bool _drawerOpen = true;
3740

3841
void DrawerToggle()
@@ -71,6 +74,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
7174
await Settings.TryLoadSettingsFromStorageAsync();
7275
await Settings.TrySetSystemThemeAsync();
7376
await KeyBindings.LoadFromStorageAsync();
77+
await NuGetState.InitializeAsync();
7478
}
7579
}
7680

Apollo.Compilation.Worker/Program.cs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
using System.Buffers.Text;
22
using System.Collections.Concurrent;
33
using System.Reflection;
4+
using System.Runtime.Loader;
45
using System.Text.Json;
56
using Apollo.Compilation;
67
using Apollo.Compilation.Worker;
8+
using Apollo.Contracts.Solutions;
79
using Apollo.Contracts.Workers;
810
using Apollo.Infrastructure;
911
using Apollo.Infrastructure.Workers;
@@ -21,9 +23,25 @@
2123
var resolver = new MetadataReferenceResourceProvider(HostAddress.BaseUri);
2224

2325
byte[]? asmCache = [];
26+
List<NuGetReference> nugetAssemblyCache = [];
27+
Dictionary<string, Assembly> loadedNuGetAssemblies = new(StringComparer.OrdinalIgnoreCase);
2428

2529
ConcurrentBag<string> executionMessages = [];
2630

31+
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
32+
{
33+
var assemblyName = new AssemblyName(args.Name);
34+
LogMessageWriter.Log($"Assembly resolve requested: {assemblyName.Name}", LogSeverity.Debug);
35+
36+
if (loadedNuGetAssemblies.TryGetValue(assemblyName.Name ?? "", out var assembly))
37+
{
38+
LogMessageWriter.Log($"Resolved from NuGet cache: {assemblyName.Name}", LogSeverity.Debug);
39+
return assembly;
40+
}
41+
42+
return null;
43+
};
44+
2745
Console.SetOut(new WorkerConsoleWriter());
2846
Console.SetError(new WorkerConsoleWriter());
2947

@@ -46,6 +64,19 @@ await resolver.GetMetadataReferenceAsync("System.Console.wasm"),
4664
await resolver.GetMetadataReferenceAsync("xunit.assert.wasm"),
4765
await resolver.GetMetadataReferenceAsync("xunit.core.wasm")
4866
};
67+
68+
nugetAssemblyCache = solution.NuGetReferences ?? [];
69+
70+
foreach (var nugetRef in nugetAssemblyCache)
71+
{
72+
if (nugetRef.AssemblyData?.Length > 0)
73+
{
74+
var nugetReference = MetadataReference.CreateFromImage(nugetRef.AssemblyData);
75+
references.Add(nugetReference);
76+
LogMessageWriter.Log($"Added NuGet reference: {nugetRef.AssemblyName}", LogSeverity.Debug);
77+
}
78+
}
79+
4980
var result = new CompilationService().Compile(solution, references);
5081

5182
asmCache = result.Assembly;
@@ -60,7 +91,29 @@ await resolver.GetMetadataReferenceAsync("xunit.core.wasm")
6091
break;
6192

6293
case "execute":
63-
// Load assembly and run.
94+
// Load NuGet assemblies into runtime first and register for resolution
95+
foreach (var nugetRef in nugetAssemblyCache)
96+
{
97+
if (nugetRef.AssemblyData?.Length > 0)
98+
{
99+
try
100+
{
101+
var loadedAsm = Assembly.Load(nugetRef.AssemblyData);
102+
var asmName = loadedAsm.GetName().Name;
103+
if (!string.IsNullOrEmpty(asmName))
104+
{
105+
loadedNuGetAssemblies[asmName] = loadedAsm;
106+
}
107+
LogMessageWriter.Log($"Loaded NuGet assembly for runtime: {nugetRef.AssemblyName} ({asmName})", LogSeverity.Debug);
108+
}
109+
catch (Exception ex)
110+
{
111+
LogMessageWriter.Log($"Failed to load NuGet assembly {nugetRef.AssemblyName}: {ex.Message}", LogSeverity.Warning);
112+
}
113+
}
114+
}
115+
116+
// Load user assembly and run
64117
byte[]? asm = Convert.FromBase64String(message.Payload);
65118

66119
var assembly = Assembly.Load(asm.Length > 0 ? asm : asmCache);

Apollo.Compilation.Worker/WorkerConsoleWriter.cs

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,51 @@ namespace Apollo.Compilation.Worker;
1111

1212
class WorkerConsoleWriter : TextWriter
1313
{
14+
private readonly StringBuilder _buffer = new();
15+
1416
public override Encoding Encoding => Encoding.UTF8;
1517

18+
public override void Write(char value)
19+
{
20+
if (value == '\n')
21+
{
22+
Flush();
23+
}
24+
else if (value != '\r')
25+
{
26+
_buffer.Append(value);
27+
}
28+
}
29+
30+
public override void Write(string? value)
31+
{
32+
if (value == null) return;
33+
34+
foreach (var c in value)
35+
{
36+
Write(c);
37+
}
38+
}
39+
1640
public override void WriteLine(string? value)
1741
{
18-
base.WriteLine(value);
42+
Write(value);
43+
Flush();
44+
}
1945

20-
var logModel = new CompilerLog(value, LogSeverity.Information);
21-
// Post the log message to the main thread
46+
public override void WriteLine()
47+
{
48+
Flush();
49+
}
50+
51+
public override void Flush()
52+
{
53+
if (_buffer.Length == 0) return;
54+
55+
var message = _buffer.ToString();
56+
_buffer.Clear();
57+
58+
var logModel = new CompilerLog(message, LogSeverity.Information);
2259
Imports.PostMessage(JsonSerializer.Serialize(new WorkerMessage
2360
{
2461
Action = "log",

Apollo.Compilation/CompilationService.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,12 @@ public ExecutionResult Execute(Assembly assembly, Action<string> logAction, Canc
7373
catch (Exception ex)
7474
{
7575
stopwatch.Stop();
76-
logAction?.Invoke($"Execution error: {ex.Message}");
76+
var actualException = ex is System.Reflection.TargetInvocationException tie ? tie.InnerException ?? ex : ex;
77+
logAction?.Invoke($"Execution error: {actualException.Message}");
78+
if (actualException.StackTrace != null)
79+
{
80+
logAction?.Invoke(actualException.StackTrace);
81+
}
7782
return new ExecutionResult
7883
{
7984
Error = true,

Apollo.Components/Analysis/CodeAnalysisState.cs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
using System.Text.Json;
22
using Apollo.Components.Console;
33
using Apollo.Components.Infrastructure.MessageBus;
4+
using Apollo.Components.NuGet;
45
using Apollo.Components.Solutions;
56
using Apollo.Contracts.Analysis;
7+
using Apollo.Contracts.Solutions;
68
using Apollo.Contracts.Workers;
79
using OmniSharp.Models.v1.Completion;
810
using Solution = Apollo.Contracts.Solutions.Solution;
@@ -16,6 +18,8 @@ public class CodeAnalysisState
1618
private readonly IMessageBus _messageBus;
1719
private readonly SolutionsState _solutionsState;
1820
private readonly UserAssemblyStore _userAssemblyStore;
21+
private readonly NuGetState _nuGetState;
22+
private readonly INuGetStorageService _nuGetStorageService;
1923
private bool _disabled;
2024

2125
public event Func<Task>? OnCodeAnalysisStateChanged;
@@ -65,13 +69,17 @@ public CodeAnalysisState(
6569
CodeAnalysisConsoleService console,
6670
IMessageBus messageBus,
6771
SolutionsState solutionsState,
68-
UserAssemblyStore userAssemblyStore)
72+
UserAssemblyStore userAssemblyStore,
73+
NuGetState nuGetState,
74+
INuGetStorageService nuGetStorageService)
6975
{
7076
_workerFactory = workerFactory;
7177
_console = console;
7278
_messageBus = messageBus;
7379
_solutionsState = solutionsState;
7480
_userAssemblyStore = userAssemblyStore;
81+
_nuGetState = nuGetState;
82+
_nuGetStorageService = nuGetStorageService;
7583

7684
_userAssemblyStore.OnAssemblyUpdated += HandleUserAssemblyUpdated;
7785
}
@@ -130,6 +138,8 @@ public async Task<List<Diagnostic>> GetDiagnosticsAsync(Solution solution, strin
130138
if (Disabled || !_workerReady || solution == null || _workerProxy == null)
131139
return [];
132140

141+
solution.NuGetReferences = await LoadNuGetReferencesAsync();
142+
133143
var request = new DiagnosticRequestWrapper
134144
{
135145
Solution = solution,
@@ -145,6 +155,35 @@ public async Task<List<Diagnostic>> GetDiagnosticsAsync(Solution solution, strin
145155

146156
return diagnostics?.ToList() ?? [];
147157
}
158+
159+
private async Task<List<NuGetReference>> LoadNuGetReferencesAsync()
160+
{
161+
var references = new List<NuGetReference>();
162+
163+
foreach (var package in _nuGetState.InstalledPackages)
164+
{
165+
foreach (var assemblyName in package.AssemblyNames)
166+
{
167+
var assemblyData = await _nuGetStorageService.GetAssemblyDataAsync(
168+
package.Id,
169+
package.Version,
170+
assemblyName);
171+
172+
if (assemblyData != null)
173+
{
174+
references.Add(new NuGetReference
175+
{
176+
PackageId = package.Id,
177+
AssemblyName = assemblyName,
178+
AssemblyData = assemblyData
179+
});
180+
_console.AddLog($"Loaded NuGet assembly: {assemblyName} from {package.Id}", ConsoleSeverity.Debug);
181+
}
182+
}
183+
}
184+
185+
return references;
186+
}
148187

149188
public async Task UpdateDocumentAsync(string path, string fullContent)
150189
{

0 commit comments

Comments
 (0)