Skip to content

Commit

Permalink
Add debug support for dynamic compiled apps (#637)
Browse files Browse the repository at this point in the history
* Added integration extension to default host

* Add support to debug dynamically compiled programs

* Change name to CompileSetting
  • Loading branch information
helto4real authored Jan 29, 2022
1 parent 1dc8628 commit 7794732
Show file tree
Hide file tree
Showing 9 changed files with 77 additions and 60 deletions.
4 changes: 3 additions & 1 deletion dev/DebugHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -29,4 +31,4 @@ await Host.CreateDefaultBuilder(args)
{
Console.WriteLine($"Failed to start host... {e}");
throw;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using NetDaemon.AppModel.Internal.Compiler;

namespace NetDaemon.AppModel.Tests.Internal.Compiler;
namespace NetDaemon.AppModel.Tests.Internal.CompilerTests;

public class CompilerIntegrationTests
{
Expand All @@ -19,8 +19,7 @@ public void TestDynamicCompileHasType()
serviceCollection.AddAppsFromSource();
var provider = serviceCollection.BuildServiceProvider();

var factory = provider.GetService<ICompilerFactory>();
using var compiler = factory?.New();
using var compiler = provider.GetService<ICompiler>();

// ACT
var (collectibleAssemblyLoadContext, compiledAssembly) = compiler?.Compile()
Expand All @@ -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);
}
}

[Fact]
public void TestDynamicCompileHasTypeUsingDebugFlag()
{
// ARRANGE
var serviceCollection = new ServiceCollection();

serviceCollection.AddLogging();
serviceCollection.AddOptions<AppConfigurationLocationSetting>()
.Configure(options =>
{
options.ApplicationConfigurationFolder = Path.Combine(AppContext.BaseDirectory, "Compiler", "Fixtures");
});
serviceCollection.AddAppsFromSource(true);
var provider = serviceCollection.BuildServiceProvider();

using var compiler = provider.GetService<ICompiler>();

// 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ public class FakeClass
serviceCollection.AddSingleton(_ => syntaxTreeResolverMock.Object);
serviceCollection.AddAppModelIfNotExist();
serviceCollection.AddAppTypeResolverIfNotExist();
serviceCollection.AddSingleton<CompilerFactory>();
serviceCollection.AddSingleton<ICompilerFactory>(s => s.GetRequiredService<CompilerFactory>());
serviceCollection.AddSingleton<Compiler>();
serviceCollection.AddSingleton<ICompiler>(s => s.GetRequiredService<Compiler>());
serviceCollection.AddSingleton<DynamicallyCompiledAssemblyResolver>();
serviceCollection.AddSingleton<IAssemblyResolver>(s =>
s.GetRequiredService<DynamicallyCompiledAssemblyResolver>());
Expand Down Expand Up @@ -128,8 +128,8 @@ public class FakeClass
serviceCollection.AddSingleton(_ => syntaxTreeResolverMock.Object);
serviceCollection.AddAppModelIfNotExist();
serviceCollection.AddAppTypeResolverIfNotExist();
serviceCollection.AddSingleton<CompilerFactory>();
serviceCollection.AddSingleton<ICompilerFactory>(s => s.GetRequiredService<CompilerFactory>());
serviceCollection.AddSingleton<Compiler>();
serviceCollection.AddSingleton<ICompiler>(s => s.GetRequiredService<Compiler>());
serviceCollection.AddSingleton<DynamicallyCompiledAssemblyResolver>();
serviceCollection.AddSingleton<IAssemblyResolver>(s =>
s.GetRequiredService<DynamicallyCompiledAssemblyResolver>());
Expand Down Expand Up @@ -186,8 +186,8 @@ public class FakeClass
serviceCollection.AddSingleton(_ => syntaxTreeResolverMock.Object);
serviceCollection.AddAppModelIfNotExist();
serviceCollection.AddAppTypeResolverIfNotExist();
serviceCollection.AddSingleton<CompilerFactory>();
serviceCollection.AddSingleton<ICompilerFactory>(s => s.GetRequiredService<CompilerFactory>());
serviceCollection.AddSingleton<Compiler>();
serviceCollection.AddSingleton<ICompiler>(s => s.GetRequiredService<Compiler>());
serviceCollection.AddSingleton<DynamicallyCompiledAssemblyResolver>();
serviceCollection.AddSingleton<IAssemblyResolver>(s =>
s.GetRequiredService<DynamicallyCompiledAssemblyResolver>());
Expand Down Expand Up @@ -224,4 +224,4 @@ public void TestAddAppFromTypeShouldLoadSingleApp()
appResolvers.Should().HaveCount(1);
appResolvers.First().GetTypes().Should().BeEquivalentTo(new[] {typeof(MyAppLocalApp)});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,17 @@ public static IServiceCollection AddAppFromType(this IServiceCollection services
/// Add apps from c# source code using the configuration source to find path
/// </summary>
/// <param name="services">Services</param>
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<CompilerFactory>()
.AddSingleton<ICompilerFactory>(s => s.GetRequiredService<CompilerFactory>())
.AddSingleton<Compiler>()
.AddSingleton<ICompiler>(s => s.GetRequiredService<Compiler>())
.AddSingleton<SyntaxTreeResolver>()
.AddSingleton<ISyntaxTreeResolver>(s => s.GetRequiredService<SyntaxTreeResolver>());
.AddSingleton<ISyntaxTreeResolver>(s => s.GetRequiredService<SyntaxTreeResolver>())
.AddOptions<CompileSettings>().Configure(settings => settings.UseDebug = useDebug);

// We need to compile it here so we can dynamically add the service providers
var assemblyResolver =
Expand Down Expand Up @@ -126,4 +126,4 @@ private static IServiceCollection AddConfigManagement(this IServiceCollection se
services.AddTransient(typeof(IAppConfig<>), typeof(AppConfig<>));
return services;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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;
Expand All @@ -55,4 +54,4 @@ public void Dispose()
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace NetDaemon.AppModel.Internal.Compiler;

/// <summary>
/// Used to set debug or release mode for dynamic compiled apps
/// </summary>
internal record CompileSettings
{
public bool UseDebug { get; set; } = false;
}
25 changes: 16 additions & 9 deletions src/AppModel/NetDaemon.AppModel/Internal/Compiler/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -17,35 +18,40 @@ internal record CompiledAssemblyResult(CollectibleAssemblyLoadContext AssemblyCo
internal class Compiler : ICompiler
{
private readonly ILogger<Compiler> _logger;
private readonly bool _useDebug;
private readonly ISyntaxTreeResolver _syntaxResolver;

public Compiler(
ISyntaxTreeResolver syntaxResolver,
ILogger<Compiler> logger
)
ILogger<Compiler> logger,
IOptions<CompileSettings> compileSettings)
{
_syntaxResolver = syntaxResolver;
_logger = logger;
_useDebug = compileSettings.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
Expand All @@ -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
)
);
}
Expand Down Expand Up @@ -112,4 +119,4 @@ private static string PrettyPrintCompileError(EmitResult emitResult)

return msg.ToString();
}
}
}

This file was deleted.

This file was deleted.

0 comments on commit 7794732

Please sign in to comment.