File: NativeContractCompilation.cs
Web Access
Project: src\src\Aspire.Hosting.Native.CodeGeneration\Aspire.Hosting.Native.CodeGeneration.csproj (Aspire.Hosting.Native.CodeGeneration)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Reflection;
using System.Collections.Immutable;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Aspire.Hosting.Native.Model;
 
namespace Aspire.Hosting.Native.CodeGeneration;
 
/// <summary>Compiles the native declarations for build-time ATS scanning without a server project dependency.</summary>
internal static class NativeContractCompilation
{
    public static Assembly Compile(string outputDirectory)
    {
        // The server needs generated dispatch before it can compile, so the generator
        // cannot reference it. Compile its exact API sources against the kernel
        // with the server's assembly identity, preserving the exported type IDs.
        const string assemblyName = "Aspire.Hosting.Native.Server";
        var tool = typeof(NativeContractCompilation).Assembly;
        var parseOptions = new CSharpParseOptions(LanguageVersion.Preview, DocumentationMode.Diagnose);
        var sources = tool.GetManifestResourceNames()
            .Where(name => name.StartsWith("NativeContract/", StringComparison.Ordinal))
            .Order(StringComparer.Ordinal)
            .Select(name =>
            {
                using var stream = tool.GetManifestResourceStream(name)!;
                using var reader = new StreamReader(stream);
 
                return CSharpSyntaxTree.ParseText(reader.ReadToEnd(), parseOptions, name, Encoding.UTF8);
            }).ToList();
        if (sources.Count == 0)
        {
            throw new InvalidOperationException("The native contract sources are missing.");
        }
        sources.Add(CSharpSyntaxTree.ParseText("""
            global using System;
            global using System.Collections.Generic;
            global using System.IO;
            global using System.Linq;
            global using System.Net.Http;
            global using System.Threading;
            global using System.Threading.Tasks;
            """, parseOptions, encoding: Encoding.UTF8));
        var references = GetRuntimeAssemblyPaths()
            .Append(typeof(AspireExportAttribute).Assembly.Location)
            .Append(typeof(ApplicationModel).Assembly.Location)
            .Select(path => MetadataReference.CreateFromFile(path));
        var compilation = CSharpCompilation.Create(assemblyName, sources, references,
            new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary,
                nullableContextOptions: NullableContextOptions.Enable,
                // The declarations use the same friend access as the real server.
                // Only its public identity is needed for build-time scanning.
                cryptoPublicKey: ImmutableArray.CreateRange(typeof(ApplicationModel).Assembly.GetName().GetPublicKey()!),
                delaySign: true));
        var directory = Directory.CreateDirectory(Path.Combine(outputDirectory, "contract"));
        var assemblyPath = Path.Combine(directory.FullName, $"{assemblyName}.dll");
        using (var assembly = File.Create(assemblyPath))
        using (var documentation = File.Create(Path.ChangeExtension(assemblyPath, ".xml")))
        {
            var result = compilation.Emit(assembly, xmlDocumentationStream: documentation);
            if (!result.Success)
            {
                throw new InvalidOperationException(string.Join(Environment.NewLine,
                    result.Diagnostics.Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)));
            }
        }
 
        return AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
    }
 
    internal static IEnumerable<string> GetRuntimeAssemblyPaths()
    {
        var platformAssemblies = AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") as string
            ?? throw new InvalidOperationException("The runtime's trusted platform assembly list is missing.");
        var runtimeDirectory = RuntimeEnvironment.GetRuntimeDirectory();
        // The Windows runtime directory also contains native DLLs such as coreclr.dll.
        // The runtime's trusted assembly list contains only managed compiler inputs.
        return platformAssemblies.Split(Path.PathSeparator)
            .Where(path => path.StartsWith(runtimeDirectory, StringComparison.Ordinal));
    }
}