using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Build.Framework;
using Mono.Cecil;
using XamlX;
using XamlX.Ast;
using XamlX.IL;
using XamlX.Parsers;
using XamlX.TypeSystem;
namespace Robust.Xaml;
///
/// Utility class: holds scope information for a Microsoft.Build.Framework
/// build in order to AOT-compile the XAML resources for an assembly.
///
///
/// Also embed enough information to support future JIT attempts on those same resources.
///
/// Code primarily by Paul Ritter, touched by Pyrex in 2024.
///
/// Based on https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
/// Adjusted for our UI Framework
///
internal partial class XamlAotCompiler
{
///
/// Update the assembly whose name is , then
/// save an updated assembly to .
///
/// the Microsoft build engine (used for logging)
/// the input assembly by name
/// all the assemblies that the input Xaml is allowed to reference
/// the place to put the output assembly
///
/// a file to use in order to generate a "strong name" for the assembly
/// (https://learn.microsoft.com/en-us/dotnet/standard/assembly/strong-named)
///
///
/// true if this succeeds and
/// true if the result was written to
///
public static (bool success, bool writtentofile) Compile(IBuildEngine engine, string input, string[] references,
string output, string? strongNameKey)
{
var typeSystem = new CecilTypeSystem(references
.Where(r => !r.ToLowerInvariant().EndsWith("robust.build.tasks.dll"))
.Concat(new[] { input }), input);
var asm = typeSystem.TargetAssemblyDefinition;
if (asm.MainModule.GetType("CompiledRobustXaml", "XamlIlContext") != null)
{
// If this type exists, the assembly has already been processed by us.
// Do not run again, it would corrupt the file.
// This *shouldn't* be possible due to Inputs/Outputs dependencies in the build system,
// but better safe than sorry eh?
engine.LogWarningEvent(new BuildWarningEventArgs("XAMLIL", "", "", 0, 0, 0, 0, "Ran twice on same assembly file; ignoring.", "", ""));
return (true, false);
}
var compileRes = CompileCore(engine, typeSystem);
if (!compileRes)
return (false, false);
var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols };
if (!string.IsNullOrWhiteSpace(strongNameKey))
writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey);
asm.Write(output, writerParameters);
return (true, true);
}
///
/// For each XAML resource, identify its affiliated class, invoke the
/// AOT compiler, update the class to call into the generated code,
/// and write down metadata for future JIT compiles.
///
/// the Microsoft build engine (for logging)
/// the type system (which includes info about the target assembly)
/// true if compilation succeeded in every case
static bool CompileCore(IBuildEngine engine, CecilTypeSystem typeSystem)
{
var asm = typeSystem.TargetAssemblyDefinition;
var embrsc = new EmbeddedResources(asm);
var xaml = new XamlCustomizations(typeSystem, typeSystem.TargetAssembly);
var lowLevel = new LowLevelCustomizations(typeSystem);
var contextDef = new TypeDefinition("CompiledRobustXaml", "XamlIlContext",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
asm.MainModule.Types.Add(contextDef);
var contextClass = XamlILContextDefinition.GenerateContextClass(
typeSystem.CreateTypeBuilder(contextDef), typeSystem,
xaml.TypeMappings, xaml.EmitMappings
);
bool CompileGroup(IResourceGroup group)
{
var typeDef = new TypeDefinition("CompiledRobustXaml", "!" + group.Name, TypeAttributes.Class,
asm.MainModule.TypeSystem.Object);
asm.MainModule.Types.Add(typeDef);
foreach (var res in group.Resources.Where(CheckXamlName))
{
try
{
engine.LogMessage($"XAMLIL: {res.Name} -> {res.Uri}", MessageImportance.Low);
var xamlText = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd();
var parsed = XDocumentXamlParser.Parse(xamlText);
var initialRoot = (XamlAstObjectNode) parsed.Root;
var classDirective = initialRoot.Children.OfType()
.FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Class");
string classname;
if (classDirective != null && classDirective.Values[0] is XamlAstTextNode tn)
{
classname = tn.Text;
}
else
{
classname = res.Name.Replace(".xaml","");
}
var classType = typeSystem.TargetAssembly.FindType(classname);
if (classType == null)
throw new InvalidProgramException($"Unable to find type '{classname}'");
xaml.ILCompiler.Transform(parsed);
var populateName = $"Populate:{res.Name}";
var classTypeDefinition = typeSystem.GetTypeReference(classType).Resolve()!;
var populateBuilder = typeSystem.CreateTypeBuilder(classTypeDefinition);
xaml.ILCompiler.Compile(parsed, contextClass,
xaml.ILCompiler.DefinePopulateMethod(populateBuilder, parsed, populateName, true),
null,
null,
(closureName, closureBaseType) =>
populateBuilder.DefineSubType(closureBaseType, closureName, false),
res.Uri, res
);
var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve().Methods
.First(m => m.Name == populateName);
lowLevel.AddXamlMetadata(classTypeDefinition, new Uri(res.Uri), res.FilePath, xamlText);
var foundXamlLoader = lowLevel.TrampolineCallsToXamlLoader(classTypeDefinition, compiledPopulateMethod);
if (!foundXamlLoader)
{
throw new InvalidProgramException(
$"No call to RobustXamlLoader.Load(this) call found anywhere in the type {classType.FullName} and type seems to have custom constructors.");
}
}
catch (Exception e)
{
engine.LogErrorEvent(new BuildErrorEventArgs("XAMLIL", "", res.FilePath, 0, 0, 0, 0,
$"{res.FilePath}: {e.Message}", "", "CompileRobustXaml"));
}
res.Remove();
}
return true;
}
if (embrsc.Resources.Count(CheckXamlName) != 0)
{
if (!CompileGroup(embrsc))
{
return false;
}
}
return true;
}
}
///
/// This is from XamlX, augmented with the other
/// arguments that the XAML compiler wants.
///
///
/// We store these later in the build process inside a XamlMetadataAttribute,
/// in order to support JIT compilation.
///
interface IResource : IFileSource
{
string Uri { get; }
string Name { get; }
void Remove();
}
///
/// A named collection of s.
///
interface IResourceGroup
{
string Name { get; }
IEnumerable Resources { get; }
}