From 70861870a353c3089907f9b885770111ea7b45c4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 7 Jul 2021 09:45:32 -0600 Subject: [PATCH] Changes the RoslynCompiler to use assemblies resolved from the DependencyContext --- .../ModelsBuilder/Building/Builder.cs | 27 ++++++------- .../ModelsBuilder/RoslynCompiler.cs | 39 +++++++++---------- .../ModelsBuilder/InMemoryModelFactory.cs | 3 +- 3 files changed, 32 insertions(+), 37 deletions(-) diff --git a/src/Umbraco.Infrastructure/ModelsBuilder/Building/Builder.cs b/src/Umbraco.Infrastructure/ModelsBuilder/Building/Builder.cs index bb157e5c7a..03fc701628 100644 --- a/src/Umbraco.Infrastructure/ModelsBuilder/Building/Builder.cs +++ b/src/Umbraco.Infrastructure/ModelsBuilder/Building/Builder.cs @@ -19,12 +19,10 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder.Building /// public abstract class Builder { - private readonly IList _typeModels; - protected Dictionary ModelsMap { get; } = new Dictionary(); // the list of assemblies that will be 'using' by default - protected readonly IList TypesUsing = new List + protected IList TypesUsing { get; } = new List { "System", "System.Linq.Expressions", @@ -50,16 +48,13 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder.Building /// Gets the list of models to generate. /// /// The models to generate - public IEnumerable GetModelsToGenerate() - { - return _typeModels; - } + public IEnumerable GetModelsToGenerate() => TypeModels; /// /// Gets the list of all models. /// /// Includes those that are ignored. - public IList TypeModels => _typeModels; + public IList TypeModels { get; } /// /// Initializes a new instance of the class with a list of models to generate, @@ -69,7 +64,7 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder.Building /// The models namespace. protected Builder(ModelsBuilderSettings config, IList typeModels) { - _typeModels = typeModels ?? throw new ArgumentNullException(nameof(typeModels)); + TypeModels = typeModels ?? throw new ArgumentNullException(nameof(typeModels)); Config = config ?? throw new ArgumentNullException(nameof(config)); @@ -91,7 +86,7 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder.Building /// private void Prepare() { - TypeModel.MapModelTypes(_typeModels, ModelsNamespace); + TypeModel.MapModelTypes(TypeModels, ModelsNamespace); var isInMemoryMode = Config.ModelsMode == ModelsMode.InMemoryAuto; @@ -101,20 +96,20 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder.Building // for the last one, don't throw in InMemory mode, see comment // ensure we have no duplicates type names - foreach (var xx in _typeModels.GroupBy(x => x.ClrName).Where(x => x.Count() > 1)) + foreach (var xx in TypeModels.GroupBy(x => x.ClrName).Where(x => x.Count() > 1)) throw new InvalidOperationException($"Type name \"{xx.Key}\" is used" + $" for types with alias {string.Join(", ", xx.Select(x => x.ItemType + ":\"" + x.Alias + "\""))}. Names have to be unique." + " Consider using an attribute to assign different names to conflicting types."); // ensure we have no duplicates property names - foreach (var typeModel in _typeModels) + foreach (var typeModel in TypeModels) foreach (var xx in typeModel.Properties.GroupBy(x => x.ClrName).Where(x => x.Count() > 1)) throw new InvalidOperationException($"Property name \"{xx.Key}\" in type {typeModel.ItemType}:\"{typeModel.Alias}\"" + $" is used for properties with alias {string.Join(", ", xx.Select(x => "\"" + x.Alias + "\""))}. Names have to be unique." + " Consider using an attribute to assign different names to conflicting properties."); // ensure content & property type don't have identical name (csharp hates it) - foreach (var typeModel in _typeModels) + foreach (var typeModel in TypeModels) { foreach (var xx in typeModel.Properties.Where(x => x.ClrName == typeModel.ClrName)) { @@ -142,7 +137,7 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder.Building // xx.Alias)); // discover interfaces that need to be declared / implemented - foreach (var typeModel in _typeModels) + foreach (var typeModel in TypeModels) { // collect all the (non-removed) types implemented at parent level // ie the parent content types and the mixins content types, recursively @@ -170,7 +165,7 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder.Building } // ensure elements don't inherit from non-elements - foreach (var typeModel in _typeModels.Where(x => x.IsElement)) + foreach (var typeModel in TypeModels.Where(x => x.IsElement)) { if (typeModel.BaseType != null && !typeModel.BaseType.IsElement) throw new InvalidOperationException($"Cannot generate model for type '{typeModel.Alias}' because it is an element type, but its parent type '{typeModel.BaseType.Alias}' is not."); @@ -196,7 +191,7 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder.Building return true; } - public string ModelsNamespaceForTests; + public string ModelsNamespaceForTests { get; set; } public string GetModelsNamespace() { diff --git a/src/Umbraco.Infrastructure/ModelsBuilder/RoslynCompiler.cs b/src/Umbraco.Infrastructure/ModelsBuilder/RoslynCompiler.cs index 15d2114f4d..fd4b4495d9 100644 --- a/src/Umbraco.Infrastructure/ModelsBuilder/RoslynCompiler.cs +++ b/src/Umbraco.Infrastructure/ModelsBuilder/RoslynCompiler.cs @@ -2,10 +2,10 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Reflection; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Text; +using Microsoft.Extensions.DependencyModel; namespace Umbraco.Cms.Infrastructure.ModelsBuilder { @@ -13,37 +13,36 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder { public const string GeneratedAssemblyName = "ModelsGeneratedAssembly"; - private OutputKind _outputKind; - private CSharpParseOptions _parseOptions; - private List _refs; + private readonly OutputKind _outputKind; + private readonly CSharpParseOptions _parseOptions; + private readonly IEnumerable _refs; /// /// Initializes a new instance of the class. /// - /// Referenced assemblies used in the source file /// /// Roslyn compiler which can be used to compile a c# file to a Dll assembly /// - public RoslynCompiler(IEnumerable referenceAssemblies) + public RoslynCompiler() { _outputKind = OutputKind.DynamicallyLinkedLibrary; _parseOptions = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest); // What languageversion should we default to? - // The references should be the same every time GetCompiledAssembly is called - // Making it kind of a waste to convert the Assembly types into MetadataReference - // every time GetCompiledAssembly is called, so that's why I do it in the ctor - _refs = new List(); - foreach (var assembly in referenceAssemblies.Where(x => !x.IsDynamic && !string.IsNullOrWhiteSpace(x.Location)).Distinct()) - { - _refs.Add(MetadataReference.CreateFromFile(assembly.Location)); - }; + // In order to dynamically compile the assembly, we need to add all refs from our current + // application. This will also add the correct framework dependencies and we won't have to worry + // about the specific framework that is currently being run. + // This was borrowed from: https://github.com/dotnet/core/issues/2082#issuecomment-442713181 + // because we were running into the same error as that thread because we were either: + // - not adding enough of the runtime dependencies OR + // - we were explicitly adding the wrong runtime dependencies + // ... at least that the gist of what I can tell. + MetadataReference[] refs = + DependencyContext.Default.CompileLibraries + .SelectMany(cl => cl.ResolveReferencePaths()) + .Select(asm => MetadataReference.CreateFromFile(asm)) + .ToArray(); - // Might have to do this another way, see - // see https://github.com/aspnet/RoslynCodeDomProvider/blob/master/src/Microsoft.CodeDom.Providers.DotNetCompilerPlatform/CSharpCompiler.cs: - // mentions "Bug 913691: Explicitly add System.Runtime as a reference." - // and explicitly adds System.Runtime to references - _refs.Add(MetadataReference.CreateFromFile(typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly.Location)); - _refs.Add(MetadataReference.CreateFromFile(typeof(System.CodeDom.Compiler.GeneratedCodeAttribute).Assembly.Location)); + _refs = refs.ToList(); } /// diff --git a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryModelFactory.cs b/src/Umbraco.Web.Common/ModelsBuilder/InMemoryModelFactory.cs index 5958316729..bf455aa000 100644 --- a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryModelFactory.cs +++ b/src/Umbraco.Web.Common/ModelsBuilder/InMemoryModelFactory.cs @@ -10,6 +10,7 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading; using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.Extensions.DependencyModel; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; @@ -128,7 +129,7 @@ namespace Umbraco.Cms.Web.Common.ModelsBuilder return _roslynCompiler; } - _roslynCompiler = new RoslynCompiler(AssemblyLoadContext.All.SelectMany(x => x.Assemblies)); + _roslynCompiler = new RoslynCompiler(); return _roslynCompiler; } }