Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions LearnJsonEverything.LessonEditor/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using LearnJsonEverything.Services;

namespace LearnJsonEverything.LessonEditor;

Expand All @@ -20,7 +19,5 @@ public partial class MainWindow : Window
public MainWindow()
{
InitializeComponent();

CompilationHelpers.TestOnly_SetReferences(ReferenceLoader.Load());
}
}
47 changes: 0 additions & 47 deletions LearnJsonEverything.LessonEditor/ReferenceLoader.cs

This file was deleted.

6 changes: 0 additions & 6 deletions LearnJsonEverything.Tests/ProvidedSolutionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,6 @@ public class ProvidedSolutionTests
PropertyNameCaseInsensitive = true
};

[OneTimeSetUp]
public void Setup()
{
CompilationHelpers.TestOnly_SetReferences(ReferenceLoader.Load());
}

private static IEnumerable<TestCaseData> GetLessons(string filename)
{
var json = File.ReadAllText(filename);
Expand Down
49 changes: 0 additions & 49 deletions LearnJsonEverything.Tests/ReferenceLoader.cs

This file was deleted.

6 changes: 3 additions & 3 deletions LearnJsonEverything/LearnJsonEverything.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<NoWarn>NU1701;BL0007</NoWarn>
<LangVersion>latest</LangVersion>
<WasmEnableWebcil>false</WasmEnableWebcil>
<NoWarn>NU1701;BL0007</NoWarn>
<LangVersion>latest</LangVersion>
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
<PublishTrimmed>false</PublishTrimmed>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 0 additions & 2 deletions LearnJsonEverything/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
var host = builder.Build();
_ = host.Services.GetService<HttpClient>();

_ = CompilationHelpers.LoadAssemblyReferences(host.Services.GetService<HttpClient>()!);

// Initialize theme before the UI renders so controls get the correct initial theme.
var localStorage = host.Services.GetRequiredService<ILocalStorageService>();
var savedTheme = await localStorage.GetItemAsync<string>("theme");
Expand Down
116 changes: 44 additions & 72 deletions LearnJsonEverything/Services/CompilationHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
using Json.Schema.Generation.XmlComments;
using Json.Logic;
using Json.JsonE;
using Json.More;
using Json.Path;
using Json.Schema;
using Json.Schema.Generation;
using Json.Schema.Generation.XmlComments;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System.Reflection;
using System.Reflection.Metadata;
using System.Runtime.Loader;
using Microsoft.CodeAnalysis.Emit;
using static LearnJsonEverything.Services.Iconography;
Expand All @@ -11,95 +18,60 @@ namespace LearnJsonEverything.Services;
public static class CompilationHelpers
{
private static AssemblyLoadContext? _assemblyLoadContext;
private static MetadataReference[]? _references;
private static bool _isLoading;
private static readonly bool IsBrowserRuntime = OperatingSystem.IsBrowser();

private static readonly string[] EnsuredAssemblies =
[
"Json.More",
"JsonE.Net",
"JsonLogic",
"JsonPatch.Net",
"JsonPath.Net",
"JsonPointer.Net",
"JsonSchema.Net",
"JsonSchema.Net.Generation",
"LearnJsonEverything.Template",
//"Yaml2JsonNode",
];

public static void TestOnly_SetReferences(MetadataReference[] references) => _references = references;

public static async Task<MetadataReference[]?> LoadAssemblyReferences(HttpClient client)
private static readonly Compilation _baseCompilation = CSharpCompilation.Create("BaseCompilation")
.WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
.AddReferences(GetAssemblyReferences());

static CompilationHelpers()
{
if (_references is null)
{
if (_isLoading) return null;
_isLoading = true;
// force some assemblies to load
_ = typeof(ILessonRunner<int>);
_ = typeof(EnumStringConverter<DayOfWeek>);
_ = typeof(JsonSchema);
_ = typeof(MinimumAttribute);
_ = typeof(JsonPath);
_ = typeof(JsonFunction);
_ = typeof(Rule);
}

try
{
var refs = AppDomain.CurrentDomain.GetAssemblies();
var names = refs
.Where(x => !x.IsDynamic)
.Select(x => x.FullName!.Split(',')[0])
.Concat(EnsuredAssemblies)
.Distinct()
.OrderBy(x => x)
.ToArray();

var references = new List<MetadataReference>(names.Length);
foreach (var assemblyName in names)
{
var source = $"/_framework/{assemblyName}.dll";
try
{
Console.WriteLine($"Loading {assemblyName}...");
var bytes = await client.GetByteArrayAsync(source);
using var stream = new MemoryStream(bytes, writable: false);
references.Add(MetadataReference.CreateFromStream(stream));
}
catch (Exception e)
{
Console.WriteLine(e);
Console.WriteLine(source);
}
}

_references = [.. references];
}
finally
{
_isLoading = false;
}
private static unsafe MetadataReference? TryCreateReferenceFromRawMetadata(Assembly assembly)
{
if (!assembly.TryGetRawMetadata(out var metadataBlob, out var metadataLength))
{
return null;
}

return _references;
var metadata = ModuleMetadata.CreateFromMetadata((nint)metadataBlob, metadataLength, () => GC.KeepAlive(assembly));
var location = assembly.Location;
if (location == "")
{
location = null;
}
return AssemblyMetadata.Create(metadata).GetReference(filePath: location, display: assembly.GetName().Name);
}

public static IEnumerable<MetadataReference> GetAssemblyReferences() => AppDomain.CurrentDomain.GetAssemblies()
.Where(assembly => !assembly.IsDynamic)
.Select(TryCreateReferenceFromRawMetadata)
.Where(reference => reference is not null)!;

public static (ILessonRunner<T>?, string[]) GetRunner<T>(LessonData lesson)
{
if (_references is null)
return (null, ["Compilation assemblies still loading. Please wait until complete and try again."]);

var fullSource = lesson.UserCode ?? string.Empty;

Console.WriteLine($"Compiling...\n\n{fullSource}");

var syntaxTree = CSharpSyntaxTree.ParseText(fullSource, new CSharpParseOptions(LanguageVersion.Latest));
var assemblyPath = Path.ChangeExtension(Path.GetTempFileName(), "dll");

var compilation = CSharpCompilation.Create(Path.GetFileName(assemblyPath))
.WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
.AddReferences(_references)
var compilation = _baseCompilation
.WithAssemblyName(Path.GetFileName(assemblyPath))
.AddSyntaxTrees(syntaxTree);

using var dllStream = new MemoryStream();
using var pdbStream = new MemoryStream();
using var xmlStream = new MemoryStream();
EmitResult emitResult;
if (IsBrowserRuntime)
if (OperatingSystem.IsBrowser())
{
// Avoid debugger-agent assertions in WASM by not emitting debug symbols.
emitResult = compilation.Emit(dllStream, xmlDocumentationStream: xmlStream);
Expand Down Expand Up @@ -127,14 +99,14 @@ public static (ILessonRunner<T>?, string[]) GetRunner<T>(LessonData lesson)

dllStream.Position = 0;
xmlStream.Position = 0;
if (!IsBrowserRuntime)
if (!OperatingSystem.IsBrowser())
pdbStream.Position = 0;

#pragma warning disable IL2026
#pragma warning disable IL2072
#pragma warning disable IL2070
Assembly assembly;
if (IsBrowserRuntime)
if (OperatingSystem.IsBrowser())
{
assembly = Assembly.Load(dllStream.ToArray());
}
Expand Down
Loading