Skip to content

Commit 046f7da

Browse files
authored
Add .net10 support, remove dead code, rework how dlls are retrieved (#190)
1 parent 6a2752d commit 046f7da

13 files changed

Lines changed: 164 additions & 127 deletions

File tree

src/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project>
22

33
<PropertyGroup>
4-
<TargetFramework>net9.0</TargetFramework>
4+
<TargetFramework>net10.0</TargetFramework>
55
<LangVersion>latest</LangVersion>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<UsingBrowserRuntimeWorkload>false</UsingBrowserRuntimeWorkload>

src/Try.Core/CompilationService.cs

Lines changed: 90 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Linq;
99
using System.Net.Http;
1010
using System.Net.Http.Json;
11+
using System.Reflection.Metadata;
1112
using System.Runtime;
1213
using System.Text;
1314
using System.Threading.Tasks;
@@ -48,14 +49,21 @@ public class CompilationService
4849
private static CSharpCompilation _baseCompilation;
4950
private static CSharpParseOptions _cSharpParseOptions;
5051

52+
private int _lastCodeHash;
53+
private CompileToAssemblyResult _cachedResult;
54+
5155
private readonly RazorProjectFileSystem fileSystem = new VirtualRazorProjectFileSystem();
5256
private readonly RazorConfiguration configuration = new(
5357
RazorLanguageVersion.Latest,
5458
ConfigurationName: "Blazor",
5559
Extensions: ImmutableArray<RazorExtension>.Empty);
5660

57-
public static async Task InitAsync(Func<IReadOnlyCollection<string>, ValueTask<IReadOnlyList<byte[]>>> getReferencedDllsBytesFunc)
61+
private RazorProjectEngine _declarationProjectEngine;
62+
63+
public static unsafe Task InitAsync()
5864
{
65+
if (_baseCompilation != null) return Task.CompletedTask;
66+
5967
var basicReferenceAssemblyRoots = new[]
6068
{
6169
typeof(Console).Assembly, // System.Console
@@ -71,22 +79,32 @@ public static async Task InitAsync(Func<IReadOnlyCollection<string>, ValueTask<I
7179
typeof(WebAssemblyHostBuilder).Assembly, // Microsoft.AspNetCore.Components.WebAssembly
7280
typeof(FluentValidation.AbstractValidator<>).Assembly,
7381
};
74-
var assemblyNames = await getReferencedDllsBytesFunc(basicReferenceAssemblyRoots
75-
.SelectMany(assembly => assembly.GetReferencedAssemblies().Concat(
76-
[
77-
assembly.GetName()
78-
]))
79-
.Select(assemblyName => assemblyName.Name)
80-
.ToHashSet());
81-
82-
var basicReferenceAssemblies = assemblyNames
83-
.Select(peImage => MetadataReference.CreateFromImage(peImage, MetadataReferenceProperties.Assembly))
84-
.ToList();
82+
83+
var assemblyNames = basicReferenceAssemblyRoots
84+
.SelectMany(assembly => assembly.GetReferencedAssemblies().Concat([assembly.GetName()]))
85+
.Select(assemblyName => assemblyName.Name!)
86+
.ToHashSet();
87+
88+
// netstandard facade is needed for libraries that target netstandard2.0
89+
assemblyNames.Add("netstandard");
90+
91+
var loadedByName = AppDomain.CurrentDomain.GetAssemblies()
92+
.Where(a => !a.IsDynamic && a.GetName().Name != null)
93+
.ToDictionary(a => a.GetName().Name!, StringComparer.OrdinalIgnoreCase);
94+
95+
var references = new List<MetadataReference>();
96+
foreach (var name in assemblyNames)
97+
{
98+
if (!loadedByName.TryGetValue(name, out var assembly)) continue;
99+
if (!assembly.TryGetRawMetadata(out byte* blob, out int length)) continue;
100+
var moduleMetadata = ModuleMetadata.CreateFromMetadata((IntPtr)blob, length);
101+
references.Add(AssemblyMetadata.Create(moduleMetadata).GetReference());
102+
}
85103

86104
_baseCompilation = CSharpCompilation.Create(
87105
DefaultRootNamespace,
88106
Array.Empty<SyntaxTree>(),
89-
basicReferenceAssemblies,
107+
references,
90108
new CSharpCompilationOptions(
91109
OutputKind.DynamicallyLinkedLibrary,
92110
optimizationLevel: OptimizationLevel.Release,
@@ -99,6 +117,7 @@ public static async Task InitAsync(Func<IReadOnlyCollection<string>, ValueTask<I
99117
}));
100118

101119
_cSharpParseOptions = new CSharpParseOptions(LanguageVersion.Preview);
120+
return Task.CompletedTask;
102121
}
103122

104123
public async Task<CompileToAssemblyResult> CompileToAssemblyAsync(
@@ -107,14 +126,60 @@ public async Task<CompileToAssemblyResult> CompileToAssemblyAsync(
107126
{
108127
ArgumentNullException.ThrowIfNull(codeFiles);
109128

129+
var codeHash = ComputeCodeHash(codeFiles);
130+
if (_cachedResult != null && _lastCodeHash == codeHash)
131+
{
132+
return _cachedResult;
133+
}
134+
110135
var cSharpResults = await this.CompileToCSharpAsync(codeFiles, updateStatusFunc);
111136

112137
await (updateStatusFunc?.Invoke("Compiling Assembly") ?? Task.CompletedTask);
113138
var result = CompileToAssembly(cSharpResults);
114139

140+
_lastCodeHash = codeHash;
141+
_cachedResult = result;
142+
115143
return result;
116144
}
117145

146+
private static int ComputeCodeHash(ICollection<CodeFile> codeFiles)
147+
{
148+
var hash = new HashCode();
149+
foreach (var file in codeFiles)
150+
{
151+
hash.Add(file.Path);
152+
hash.Add(file.Content);
153+
}
154+
return hash.ToHashCode();
155+
}
156+
157+
// Compiles to a Roslyn compilation reference without emitting a PE image.
158+
// Used for the declaration (phase 1) pass where we only need component type info.
159+
private static (CompileToCSharpResult error, MetadataReference metadataRef) CompileToMetadataReference(
160+
IReadOnlyList<CompileToCSharpResult> cSharpResults)
161+
{
162+
if (cSharpResults.Any(r => r.Diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error)))
163+
{
164+
return (new CompileToCSharpResult { Diagnostics = cSharpResults.SelectMany(r => r.Diagnostics).ToList() }, null);
165+
}
166+
167+
var syntaxTrees = new SyntaxTree[cSharpResults.Count];
168+
for (var i = 0; i < cSharpResults.Count; i++)
169+
{
170+
syntaxTrees[i] = CSharpSyntaxTree.ParseText(cSharpResults[i].Code, _cSharpParseOptions, cSharpResults[i].FilePath);
171+
}
172+
173+
var compilation = _baseCompilation.AddSyntaxTrees(syntaxTrees);
174+
var diagnostics = compilation.GetDiagnostics().Where(d => d.Severity > DiagnosticSeverity.Info).ToList();
175+
if (diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error))
176+
{
177+
return (new CompileToCSharpResult { Diagnostics = diagnostics.Select(CompilationDiagnostic.FromCSharpDiagnostic).ToList() }, null);
178+
}
179+
180+
return (null, compilation.ToMetadataReference());
181+
}
182+
118183
private static CompileToAssemblyResult CompileToAssembly(IReadOnlyList<CompileToCSharpResult> cSharpResults)
119184
{
120185
if (cSharpResults.Any(r => r.Diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error)))
@@ -144,7 +209,7 @@ private static CompileToAssemblyResult CompileToAssembly(IReadOnlyList<CompileTo
144209

145210
if (result.Diagnostics.All(x => x.Severity != DiagnosticSeverity.Error))
146211
{
147-
using var peStream = new MemoryStream();
212+
using var peStream = new MemoryStream(capacity: 512 * 1024);
148213
finalCompilation.Emit(peStream);
149214

150215
result.AssemblyBytes = peStream.ToArray();
@@ -164,7 +229,10 @@ private static VirtualProjectItem CreateRazorProjectItem(string fileName, string
164229
filePath = '/' + filePath;
165230
}
166231

167-
fileContent = fileContent.Replace("\r", string.Empty);
232+
if (fileContent.Contains('\r'))
233+
{
234+
fileContent = fileContent.Replace("\r", string.Empty);
235+
}
168236

169237
return new VirtualProjectItem(
170238
WorkingDirectory,
@@ -180,7 +248,8 @@ private async Task<IReadOnlyList<CompileToCSharpResult>> CompileToCSharpAsync(
180248
Func<string, Task> updateStatusFunc)
181249
{
182250
// The first phase won't include any metadata references for component discovery. This mirrors what the build does.
183-
var projectEngine = this.CreateRazorProjectEngine(Array.Empty<MetadataReference>());
251+
_declarationProjectEngine ??= this.CreateRazorProjectEngine(Array.Empty<MetadataReference>());
252+
var projectEngine = _declarationProjectEngine;
184253

185254
// Result of generating declarations
186255
var declarations = new CompileToCSharpResult[codeFiles.Count];
@@ -217,15 +286,15 @@ private async Task<IReadOnlyList<CompileToCSharpResult>> CompileToCSharpAsync(
217286
index++;
218287
}
219288

220-
// Result of doing 'temp' compilation
221-
var tempAssembly = CompileToAssembly(declarations);
222-
if (tempAssembly.Diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error))
289+
// Result of doing 'temp' compilation (no emit needed — only used as a metadata reference)
290+
var (tempErrors, tempRef) = CompileToMetadataReference(declarations);
291+
if (tempErrors != null)
223292
{
224-
return [new CompileToCSharpResult { Diagnostics = tempAssembly.Diagnostics }];
293+
return [tempErrors];
225294
}
226295

227296
// Add the 'temp' compilation as a metadata reference
228-
var references = new List<MetadataReference>(_baseCompilation.References) { tempAssembly.Compilation.ToMetadataReference() };
297+
var references = new List<MetadataReference>(_baseCompilation.References) { tempRef };
229298
projectEngine = CreateRazorProjectEngine(references);
230299

231300
await (updateStatusFunc?.Invoke("Preparing Project") ?? Task.CompletedTask);

src/Try.Core/Try.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<PropertyGroup>
44
<AssemblyName>Microsoft.CodeAnalysis.Razor.Test</AssemblyName>
55
<KeyOriginatorFile>$(PkgMicrosoft_DotNet_Arcade_Sdk)\tools\snk\AspNetCore.snk</KeyOriginatorFile>
6+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
67
</PropertyGroup>
78

89
<ItemGroup>

src/Try.Tests/SnippetsServiceTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public void Setup()
4242
[Ignore("Investigate failure")]
4343
public async Task TestGet()
4444
{
45-
var snippetService = new SnippetsService(snippetsOptions, new System.Net.Http.HttpClient(), new MockNavigationManager());
45+
var snippetService = new SnippetsService(snippetsOptions, new HttpClient(), new MockNavigationManager());
4646
var codeFiles = await snippetService.GetSnippetContentAsync("2021020540572059");
4747
Assert.That(codeFiles, Is.Not.Null);
4848
}
@@ -51,7 +51,7 @@ public async Task TestGet()
5151
[Ignore("Investigate failure")]
5252
public async Task TestPut()
5353
{
54-
var snippetService = new SnippetsService(snippetsOptions, new System.Net.Http.HttpClient(), new MockNavigationManager());
54+
var snippetService = new SnippetsService(snippetsOptions, new HttpClient(), new MockNavigationManager());
5555
var id = await snippetService.SaveSnippetAsync(codeFiles);
5656
Assert.That(id, Is.Not.Null);
5757
Console.WriteLine(id);

src/TryMudBlazor.Client/Components/SaveSnippetPopup.razor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ private async Task SaveAsync()
7575
}
7676
}
7777

78-
private async void OnClose()
78+
private async Task OnClose()
7979
{
8080
Loading = false;
8181
SnippetLink = string.Empty;

src/TryMudBlazor.Client/Models/TryConstants.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ public static class Editor
1919

2020
public static class CodeExecution
2121
{
22-
public const string GetCompilationDlls = "Try.CodeExecution.getCompilationDlls";
2322
public const string UpdateUserComponentsDll = "Try.CodeExecution.updateUserComponentsDll";
2423
}
2524
}

src/TryMudBlazor.Client/Pages/Repl.razor.cs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ private async Task CompileAsync()
163163
this.Loading = true;
164164
this.LoaderText = "Processing";
165165

166-
await Task.Delay(10); // Ensure rendering has time to be called
166+
await Task.Yield();
167167

168168
CompileToAssemblyResult compilationResult = null;
169169
CodeFile mainComponent = null;
@@ -204,7 +204,7 @@ private async Task CompileAsync()
204204
if (compilationResult?.AssemblyBytes?.Length > 0)
205205
{
206206
// Make sure the DLL is updated before reloading the user page
207-
await this.JsRuntime.InvokeVoidAsync(Try.CodeExecution.UpdateUserComponentsDll, compilationResult.AssemblyBytes);
207+
this.JsRuntime.InvokeVoid(Try.CodeExecution.UpdateUserComponentsDll, compilationResult.AssemblyBytes);
208208

209209
// TODO: Add error page in iframe
210210
this.JsRuntime.InvokeVoid(Try.ReloadIframe, "user-page-window", MainUserPagePath);
@@ -271,29 +271,28 @@ private void UpdateActiveCodeFileContent()
271271
this.activeCodeFile.Content = this.CodeEditorComponent.GetCode();
272272
}
273273

274-
private Task UpdateLoaderTextAsync(string loaderText)
274+
private async Task UpdateLoaderTextAsync(string loaderText)
275275
{
276276
this.LoaderText = loaderText;
277277

278278
this.StateHasChanged();
279279

280-
return Task.Delay(10); // Ensure rendering has time to be called
280+
await Task.Yield();
281281
}
282282

283-
private async void UpdateTheme()
283+
private async Task UpdateTheme()
284284
{
285285
await LayoutService.ToggleDarkMode();
286286
string theme = LayoutService.IsDarkMode ? "vs-dark" : "default";
287-
this.JsRuntime.InvokeVoid(Try.Editor.SetTheme, theme);
287+
await JsRuntime.InvokeVoidAsync(Try.Editor.SetTheme, theme);
288288
// LayoutService calls StateHasChanged, we need the updated <style> tags for updateIframeTheme to work
289289
await Task.Yield();
290290
await JsRuntime.InvokeVoidAsync("updateIframeTheme");
291291
}
292292

293-
private async Task ClearCache()
293+
private void ClearCache()
294294
{
295-
await JsRuntime.InvokeVoidAsync("Try.clearCache");
296-
NavigationManager.NavigateTo(NavigationManager.BaseUri, true);
295+
NavigationManager.NavigateTo(NavigationManager.BaseUri, forceLoad: true);
297296
}
298297
}
299298
}

src/TryMudBlazor.Client/Shared/MainLayout.razor.cs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@
33
using System;
44
using System.Threading.Tasks;
55
using Microsoft.AspNetCore.Components;
6-
using Microsoft.JSInterop;
76
using MudBlazor;
87
using Services;
98
using Try.Core;
10-
using static TryMudBlazor.Client.Models.Try;
119

1210
public partial class MainLayout : LayoutComponentBase, IDisposable
1311
{
@@ -16,9 +14,6 @@ public partial class MainLayout : LayoutComponentBase, IDisposable
1614
[Inject]
1715
private LayoutService LayoutService { get; set; }
1816

19-
[Inject]
20-
private IJSRuntime JsRuntime { get; set; }
21-
2217
protected override void OnInitialized()
2318
{
2419
LayoutService.MajorUpdateOccured += LayoutServiceOnMajorUpdateOccured;
@@ -32,16 +27,11 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
3227
if (firstRender)
3328
{
3429
await ApplyUserPreferences();
35-
await CompilationService.InitAsync(GetReferenceAssembliesStreamsAsync);
30+
await CompilationService.InitAsync();
3631
StateHasChanged();
3732
}
3833
}
3934

40-
private ValueTask<IReadOnlyList<byte[]>> GetReferenceAssembliesStreamsAsync(IReadOnlyCollection<string> referenceAssemblyNames)
41-
{
42-
return JsRuntime.InvokeAsync<IReadOnlyList<byte[]>>(CodeExecution.GetCompilationDlls, new List<string>(referenceAssemblyNames) { "netstandard" });
43-
}
44-
4535
private async Task ApplyUserPreferences()
4636
{
4737
var defaultDarkMode = await _mudThemeProvider.GetSystemDarkModeAsync();

src/TryMudBlazor.Client/TryMudBlazor.Client.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22

33
<PropertyGroup>
44
<PublishTrimmed>false</PublishTrimmed>
5-
<WasmEnableWebcil>false</WasmEnableWebcil>
5+
<WasmEnableWebcil>true</WasmEnableWebcil>
66
</PropertyGroup>
77

88
<ItemGroup>
99
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
10-
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.0" />
11-
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.0" PrivateAssets="all" />
12-
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="9.0.0" />
10+
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.0" />
11+
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.0" PrivateAssets="all" />
12+
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="10.0.0" />
1313
</ItemGroup>
1414

1515
<ItemGroup>

0 commit comments

Comments
 (0)