From 00699b3290b85a24e3e59d3791650b7d1b9672ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomas=20Hellstr=C3=B6m?= Date: Fri, 28 Jan 2022 22:28:29 +0100 Subject: [PATCH 1/3] Added integration extension to default host --- src/Host/NetDaemon.Host.Default/NetDaemon.Host.Default.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Host/NetDaemon.Host.Default/NetDaemon.Host.Default.csproj b/src/Host/NetDaemon.Host.Default/NetDaemon.Host.Default.csproj index 9d862c5cc..cc6db3b6f 100644 --- a/src/Host/NetDaemon.Host.Default/NetDaemon.Host.Default.csproj +++ b/src/Host/NetDaemon.Host.Default/NetDaemon.Host.Default.csproj @@ -27,6 +27,7 @@ + From ceedf0dde9629e4cae22320520571959a8380add Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomas=20Hellstr=C3=B6m?= Date: Sat, 29 Jan 2022 01:16:37 +0100 Subject: [PATCH 2/3] Add support to debug dynamically compiled programs --- dev/DebugHost/Program.cs | 4 ++- .../Compiler/CompilerIntegrationTests.cs | 35 ++++++++++++++++--- .../TypeResolver/TypeResolverTests.cs | 14 ++++---- .../Extensions/ServiceCollectionExtension.cs | 12 +++---- .../AssemblyResolver/AssemblyResolver.cs | 11 +++--- .../Internal/Compiler/Compiler.cs | 25 ++++++++----- .../Internal/Compiler/CompilerFactory.cs | 21 ----------- .../Internal/Compiler/DebugSettings.cs | 9 +++++ .../Internal/Compiler/ICompilerFactory.cs | 6 ---- 9 files changed, 77 insertions(+), 60 deletions(-) delete mode 100644 src/AppModel/NetDaemon.AppModel/Internal/Compiler/CompilerFactory.cs create mode 100644 src/AppModel/NetDaemon.AppModel/Internal/Compiler/DebugSettings.cs delete mode 100644 src/AppModel/NetDaemon.AppModel/Internal/Compiler/ICompilerFactory.cs diff --git a/dev/DebugHost/Program.cs b/dev/DebugHost/Program.cs index 3ce94efd0..a581449f6 100644 --- a/dev/DebugHost/Program.cs +++ b/dev/DebugHost/Program.cs @@ -17,6 +17,8 @@ await Host.CreateDefaultBuilder(args) .UseNetDaemonTextToSpeech() .ConfigureServices((_, services) => services + // change type of compilation here + // .AddAppsFromSource(true) .AddAppsFromAssembly(Assembly.GetEntryAssembly()!) // Remove this is you are not running the integration! .AddNetDaemonStateManager() @@ -29,4 +31,4 @@ await Host.CreateDefaultBuilder(args) { Console.WriteLine($"Failed to start host... {e}"); throw; -} \ No newline at end of file +} diff --git a/src/AppModel/NetDaemon.AppModel.Tests/Compiler/CompilerIntegrationTests.cs b/src/AppModel/NetDaemon.AppModel.Tests/Compiler/CompilerIntegrationTests.cs index deff1c639..bee135c10 100644 --- a/src/AppModel/NetDaemon.AppModel.Tests/Compiler/CompilerIntegrationTests.cs +++ b/src/AppModel/NetDaemon.AppModel.Tests/Compiler/CompilerIntegrationTests.cs @@ -1,6 +1,6 @@ using NetDaemon.AppModel.Internal.Compiler; -namespace NetDaemon.AppModel.Tests.Internal.Compiler; +namespace NetDaemon.AppModel.Tests.Internal.CompilerTests; public class CompilerIntegrationTests { @@ -19,8 +19,7 @@ public void TestDynamicCompileHasType() serviceCollection.AddAppsFromSource(); var provider = serviceCollection.BuildServiceProvider(); - var factory = provider.GetService(); - using var compiler = factory?.New(); + using var compiler = provider.GetService(); // ACT var (collectibleAssemblyLoadContext, compiledAssembly) = compiler?.Compile() @@ -32,4 +31,32 @@ public void TestDynamicCompileHasType() var types = collectibleAssemblyLoadContext.Assemblies.SelectMany(n => n.GetTypes()).ToList(); types.Where(n => n.Name == "SimpleApp").Should().HaveCount(1); } -} \ No newline at end of file + + [Fact] + public void TestDynamicCompileHasTypeUsingDebugFlag() + { + // ARRANGE + var serviceCollection = new ServiceCollection(); + + serviceCollection.AddLogging(); + serviceCollection.AddOptions() + .Configure(options => + { + options.ApplicationConfigurationFolder = Path.Combine(AppContext.BaseDirectory, "Compiler", "Fixtures"); + }); + serviceCollection.AddAppsFromSource(true); + var provider = serviceCollection.BuildServiceProvider(); + + using var compiler = provider.GetService(); + + // ACT + var (collectibleAssemblyLoadContext, compiledAssembly) = compiler?.Compile() + ?? throw new NullReferenceException( + "Not expected null"); + + // CHECK + compiledAssembly.FullName.Should().StartWith("daemon_apps_"); + var types = collectibleAssemblyLoadContext.Assemblies.SelectMany(n => n.GetTypes()).ToList(); + types.Where(n => n.Name == "SimpleApp").Should().HaveCount(1); + } +} diff --git a/src/AppModel/NetDaemon.AppModel.Tests/TypeResolver/TypeResolverTests.cs b/src/AppModel/NetDaemon.AppModel.Tests/TypeResolver/TypeResolverTests.cs index cbc061504..aa61154b7 100644 --- a/src/AppModel/NetDaemon.AppModel.Tests/TypeResolver/TypeResolverTests.cs +++ b/src/AppModel/NetDaemon.AppModel.Tests/TypeResolver/TypeResolverTests.cs @@ -72,8 +72,8 @@ public class FakeClass serviceCollection.AddSingleton(_ => syntaxTreeResolverMock.Object); serviceCollection.AddAppModelIfNotExist(); serviceCollection.AddAppTypeResolverIfNotExist(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(s => s.GetRequiredService()); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(s => s.GetRequiredService()); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(s => s.GetRequiredService()); @@ -128,8 +128,8 @@ public class FakeClass serviceCollection.AddSingleton(_ => syntaxTreeResolverMock.Object); serviceCollection.AddAppModelIfNotExist(); serviceCollection.AddAppTypeResolverIfNotExist(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(s => s.GetRequiredService()); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(s => s.GetRequiredService()); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(s => s.GetRequiredService()); @@ -186,8 +186,8 @@ public class FakeClass serviceCollection.AddSingleton(_ => syntaxTreeResolverMock.Object); serviceCollection.AddAppModelIfNotExist(); serviceCollection.AddAppTypeResolverIfNotExist(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(s => s.GetRequiredService()); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(s => s.GetRequiredService()); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(s => s.GetRequiredService()); @@ -224,4 +224,4 @@ public void TestAddAppFromTypeShouldLoadSingleApp() appResolvers.Should().HaveCount(1); appResolvers.First().GetTypes().Should().BeEquivalentTo(new[] {typeof(MyAppLocalApp)}); } -} \ No newline at end of file +} diff --git a/src/AppModel/NetDaemon.AppModel/Common/Extensions/ServiceCollectionExtension.cs b/src/AppModel/NetDaemon.AppModel/Common/Extensions/ServiceCollectionExtension.cs index 541ebe642..2c7b1d722 100644 --- a/src/AppModel/NetDaemon.AppModel/Common/Extensions/ServiceCollectionExtension.cs +++ b/src/AppModel/NetDaemon.AppModel/Common/Extensions/ServiceCollectionExtension.cs @@ -38,17 +38,17 @@ public static IServiceCollection AddAppFromType(this IServiceCollection services /// Add apps from c# source code using the configuration source to find path /// /// Services - public static IServiceCollection AddAppsFromSource(this IServiceCollection services) + public static IServiceCollection AddAppsFromSource(this IServiceCollection services, bool useDebug = false) { // We make sure we only add AppModel services once - services .AddAppModelIfNotExist() .AddAppTypeResolverIfNotExist() - .AddSingleton() - .AddSingleton(s => s.GetRequiredService()) + .AddSingleton() + .AddSingleton(s => s.GetRequiredService()) .AddSingleton() - .AddSingleton(s => s.GetRequiredService()); + .AddSingleton(s => s.GetRequiredService()) + .AddOptions().Configure(settings => settings.UseDebug = useDebug); // We need to compile it here so we can dynamically add the service providers var assemblyResolver = @@ -126,4 +126,4 @@ private static IServiceCollection AddConfigManagement(this IServiceCollection se services.AddTransient(typeof(IAppConfig<>), typeof(AppConfig<>)); return services; } -} \ No newline at end of file +} diff --git a/src/AppModel/NetDaemon.AppModel/Internal/AssemblyResolver/AssemblyResolver.cs b/src/AppModel/NetDaemon.AppModel/Internal/AssemblyResolver/AssemblyResolver.cs index bef34e979..c6a90f110 100644 --- a/src/AppModel/NetDaemon.AppModel/Internal/AssemblyResolver/AssemblyResolver.cs +++ b/src/AppModel/NetDaemon.AppModel/Internal/AssemblyResolver/AssemblyResolver.cs @@ -22,15 +22,15 @@ public Assembly GetResolvedAssembly() internal class DynamicallyCompiledAssemblyResolver : IAssemblyResolver, IDisposable { - private readonly ICompilerFactory _compilerFactory; + private readonly ICompiler _compiler; private Assembly? _compiledAssembly; private CollectibleAssemblyLoadContext? _currentContext; public DynamicallyCompiledAssemblyResolver( - ICompilerFactory compilerFactory + ICompiler compiler ) { - _compilerFactory = compilerFactory; + _compiler = compiler; } public Assembly GetResolvedAssembly() @@ -40,8 +40,7 @@ public Assembly GetResolvedAssembly() if (_compiledAssembly is not null) return _compiledAssembly; - var compiler = _compilerFactory.New(); - var (loadContext, compiledAssembly) = compiler.Compile(); + var (loadContext, compiledAssembly) = _compiler.Compile(); _currentContext = loadContext; _compiledAssembly = compiledAssembly; return compiledAssembly; @@ -55,4 +54,4 @@ public void Dispose() GC.Collect(); GC.WaitForPendingFinalizers(); } -} \ No newline at end of file +} diff --git a/src/AppModel/NetDaemon.AppModel/Internal/Compiler/Compiler.cs b/src/AppModel/NetDaemon.AppModel/Internal/Compiler/Compiler.cs index a5d855aae..ed33650f3 100644 --- a/src/AppModel/NetDaemon.AppModel/Internal/Compiler/Compiler.cs +++ b/src/AppModel/NetDaemon.AppModel/Internal/Compiler/Compiler.cs @@ -3,6 +3,7 @@ using System.Linq.Expressions; using System.Reflection; using System.Runtime; +using System.Runtime.Loader; using System.Text.RegularExpressions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -17,35 +18,40 @@ internal record CompiledAssemblyResult(CollectibleAssemblyLoadContext AssemblyCo internal class Compiler : ICompiler { private readonly ILogger _logger; + private readonly bool _useDebug; private readonly ISyntaxTreeResolver _syntaxResolver; public Compiler( ISyntaxTreeResolver syntaxResolver, - ILogger logger - ) + ILogger logger, + IOptions debugSettings) { _syntaxResolver = syntaxResolver; _logger = logger; + _useDebug = debugSettings.Value.UseDebug; } public CompiledAssemblyResult Compile() { CollectibleAssemblyLoadContext context = new(); + var compilation = GetSharpCompilation(); using var peStream = new MemoryStream(); - var emitResult = compilation.Emit(peStream); + using MemoryStream? symStream = _useDebug ? new MemoryStream() : null; + + var emitResult = compilation.Emit(peStream, symStream); if (emitResult.Success) { peStream.Seek(0, SeekOrigin.Begin); - var assembly = context.LoadFromStream(peStream); + symStream?.Seek(0, SeekOrigin.Begin); + var assembly = context.LoadFromStream(peStream, symStream); return new CompiledAssemblyResult(context, assembly); } var error = PrettyPrintCompileError(emitResult); - - _logger.LogError("Failed to compile applications\n{error}", error); + _logger.LogError("Failed to compile applications\n{Error}", error); context.Unload(); // Finally do cleanup and release memory @@ -69,8 +75,9 @@ private CSharpCompilation GetSharpCompilation() metaDataReference.ToArray(), new CSharpCompilationOptions( OutputKind.DynamicallyLinkedLibrary, - optimizationLevel: OptimizationLevel.Release, - assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default + optimizationLevel: _useDebug ? OptimizationLevel.Debug : OptimizationLevel.Release, + assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default, + platform: Platform.AnyCpu ) ); } @@ -112,4 +119,4 @@ private static string PrettyPrintCompileError(EmitResult emitResult) return msg.ToString(); } -} \ No newline at end of file +} diff --git a/src/AppModel/NetDaemon.AppModel/Internal/Compiler/CompilerFactory.cs b/src/AppModel/NetDaemon.AppModel/Internal/Compiler/CompilerFactory.cs deleted file mode 100644 index 3ae82722a..000000000 --- a/src/AppModel/NetDaemon.AppModel/Internal/Compiler/CompilerFactory.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace NetDaemon.AppModel.Internal.Compiler; - -internal class CompilerFactory : ICompilerFactory -{ - private readonly ILogger _logger; - private readonly ISyntaxTreeResolver _syntaxResolver; - - public CompilerFactory( - ISyntaxTreeResolver syntaxResolver, - ILogger logger - ) - { - _syntaxResolver = syntaxResolver; - _logger = logger; - } - - public ICompiler New() - { - return new Compiler(_syntaxResolver, _logger); - } -} \ No newline at end of file diff --git a/src/AppModel/NetDaemon.AppModel/Internal/Compiler/DebugSettings.cs b/src/AppModel/NetDaemon.AppModel/Internal/Compiler/DebugSettings.cs new file mode 100644 index 000000000..c7dc7c21f --- /dev/null +++ b/src/AppModel/NetDaemon.AppModel/Internal/Compiler/DebugSettings.cs @@ -0,0 +1,9 @@ +namespace NetDaemon.AppModel.Internal.Compiler; + +/// +/// Used to set debug or release mode for dynamic compiled apps +/// +internal record DebugSettings +{ + public bool UseDebug { get; set; } = false; +} diff --git a/src/AppModel/NetDaemon.AppModel/Internal/Compiler/ICompilerFactory.cs b/src/AppModel/NetDaemon.AppModel/Internal/Compiler/ICompilerFactory.cs deleted file mode 100644 index a77a0418f..000000000 --- a/src/AppModel/NetDaemon.AppModel/Internal/Compiler/ICompilerFactory.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace NetDaemon.AppModel.Internal.Compiler; - -internal interface ICompilerFactory -{ - ICompiler New(); -} \ No newline at end of file From 0dd685605d79a8cc62de08b158a44db6835a2221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomas=20Hellstr=C3=B6m?= Date: Sat, 29 Jan 2022 09:40:25 +0100 Subject: [PATCH 3/3] Change name to CompileSetting --- .../Common/Extensions/ServiceCollectionExtension.cs | 2 +- .../Compiler/{DebugSettings.cs => CompileSettings.cs} | 2 +- src/AppModel/NetDaemon.AppModel/Internal/Compiler/Compiler.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename src/AppModel/NetDaemon.AppModel/Internal/Compiler/{DebugSettings.cs => CompileSettings.cs} (86%) diff --git a/src/AppModel/NetDaemon.AppModel/Common/Extensions/ServiceCollectionExtension.cs b/src/AppModel/NetDaemon.AppModel/Common/Extensions/ServiceCollectionExtension.cs index 2c7b1d722..25c904951 100644 --- a/src/AppModel/NetDaemon.AppModel/Common/Extensions/ServiceCollectionExtension.cs +++ b/src/AppModel/NetDaemon.AppModel/Common/Extensions/ServiceCollectionExtension.cs @@ -48,7 +48,7 @@ public static IServiceCollection AddAppsFromSource(this IServiceCollection servi .AddSingleton(s => s.GetRequiredService()) .AddSingleton() .AddSingleton(s => s.GetRequiredService()) - .AddOptions().Configure(settings => settings.UseDebug = useDebug); + .AddOptions().Configure(settings => settings.UseDebug = useDebug); // We need to compile it here so we can dynamically add the service providers var assemblyResolver = diff --git a/src/AppModel/NetDaemon.AppModel/Internal/Compiler/DebugSettings.cs b/src/AppModel/NetDaemon.AppModel/Internal/Compiler/CompileSettings.cs similarity index 86% rename from src/AppModel/NetDaemon.AppModel/Internal/Compiler/DebugSettings.cs rename to src/AppModel/NetDaemon.AppModel/Internal/Compiler/CompileSettings.cs index c7dc7c21f..c49e57b52 100644 --- a/src/AppModel/NetDaemon.AppModel/Internal/Compiler/DebugSettings.cs +++ b/src/AppModel/NetDaemon.AppModel/Internal/Compiler/CompileSettings.cs @@ -3,7 +3,7 @@ /// /// Used to set debug or release mode for dynamic compiled apps /// -internal record DebugSettings +internal record CompileSettings { public bool UseDebug { get; set; } = false; } diff --git a/src/AppModel/NetDaemon.AppModel/Internal/Compiler/Compiler.cs b/src/AppModel/NetDaemon.AppModel/Internal/Compiler/Compiler.cs index ed33650f3..2c33c8e2c 100644 --- a/src/AppModel/NetDaemon.AppModel/Internal/Compiler/Compiler.cs +++ b/src/AppModel/NetDaemon.AppModel/Internal/Compiler/Compiler.cs @@ -24,11 +24,11 @@ internal class Compiler : ICompiler public Compiler( ISyntaxTreeResolver syntaxResolver, ILogger logger, - IOptions debugSettings) + IOptions compileSettings) { _syntaxResolver = syntaxResolver; _logger = logger; - _useDebug = debugSettings.Value.UseDebug; + _useDebug = compileSettings.Value.UseDebug; } public CompiledAssemblyResult Compile()