Skip to content

Commit 7794732

Browse files
authored
Add debug support for dynamic compiled apps (net-daemon#637)
* Added integration extension to default host * Add support to debug dynamically compiled programs * Change name to CompileSetting
1 parent 1dc8628 commit 7794732

File tree

9 files changed

+77
-60
lines changed

9 files changed

+77
-60
lines changed

dev/DebugHost/Program.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ await Host.CreateDefaultBuilder(args)
1717
.UseNetDaemonTextToSpeech()
1818
.ConfigureServices((_, services) =>
1919
services
20+
// change type of compilation here
21+
// .AddAppsFromSource(true)
2022
.AddAppsFromAssembly(Assembly.GetEntryAssembly()!)
2123
// Remove this is you are not running the integration!
2224
.AddNetDaemonStateManager()
@@ -29,4 +31,4 @@ await Host.CreateDefaultBuilder(args)
2931
{
3032
Console.WriteLine($"Failed to start host... {e}");
3133
throw;
32-
}
34+
}

src/AppModel/NetDaemon.AppModel.Tests/Compiler/CompilerIntegrationTests.cs

+31-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using NetDaemon.AppModel.Internal.Compiler;
22

3-
namespace NetDaemon.AppModel.Tests.Internal.Compiler;
3+
namespace NetDaemon.AppModel.Tests.Internal.CompilerTests;
44

55
public class CompilerIntegrationTests
66
{
@@ -19,8 +19,7 @@ public void TestDynamicCompileHasType()
1919
serviceCollection.AddAppsFromSource();
2020
var provider = serviceCollection.BuildServiceProvider();
2121

22-
var factory = provider.GetService<ICompilerFactory>();
23-
using var compiler = factory?.New();
22+
using var compiler = provider.GetService<ICompiler>();
2423

2524
// ACT
2625
var (collectibleAssemblyLoadContext, compiledAssembly) = compiler?.Compile()
@@ -32,4 +31,32 @@ public void TestDynamicCompileHasType()
3231
var types = collectibleAssemblyLoadContext.Assemblies.SelectMany(n => n.GetTypes()).ToList();
3332
types.Where(n => n.Name == "SimpleApp").Should().HaveCount(1);
3433
}
35-
}
34+
35+
[Fact]
36+
public void TestDynamicCompileHasTypeUsingDebugFlag()
37+
{
38+
// ARRANGE
39+
var serviceCollection = new ServiceCollection();
40+
41+
serviceCollection.AddLogging();
42+
serviceCollection.AddOptions<AppConfigurationLocationSetting>()
43+
.Configure(options =>
44+
{
45+
options.ApplicationConfigurationFolder = Path.Combine(AppContext.BaseDirectory, "Compiler", "Fixtures");
46+
});
47+
serviceCollection.AddAppsFromSource(true);
48+
var provider = serviceCollection.BuildServiceProvider();
49+
50+
using var compiler = provider.GetService<ICompiler>();
51+
52+
// ACT
53+
var (collectibleAssemblyLoadContext, compiledAssembly) = compiler?.Compile()
54+
?? throw new NullReferenceException(
55+
"Not expected null");
56+
57+
// CHECK
58+
compiledAssembly.FullName.Should().StartWith("daemon_apps_");
59+
var types = collectibleAssemblyLoadContext.Assemblies.SelectMany(n => n.GetTypes()).ToList();
60+
types.Where(n => n.Name == "SimpleApp").Should().HaveCount(1);
61+
}
62+
}

src/AppModel/NetDaemon.AppModel.Tests/TypeResolver/TypeResolverTests.cs

+7-7
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ public class FakeClass
7272
serviceCollection.AddSingleton(_ => syntaxTreeResolverMock.Object);
7373
serviceCollection.AddAppModelIfNotExist();
7474
serviceCollection.AddAppTypeResolverIfNotExist();
75-
serviceCollection.AddSingleton<CompilerFactory>();
76-
serviceCollection.AddSingleton<ICompilerFactory>(s => s.GetRequiredService<CompilerFactory>());
75+
serviceCollection.AddSingleton<Compiler>();
76+
serviceCollection.AddSingleton<ICompiler>(s => s.GetRequiredService<Compiler>());
7777
serviceCollection.AddSingleton<DynamicallyCompiledAssemblyResolver>();
7878
serviceCollection.AddSingleton<IAssemblyResolver>(s =>
7979
s.GetRequiredService<DynamicallyCompiledAssemblyResolver>());
@@ -128,8 +128,8 @@ public class FakeClass
128128
serviceCollection.AddSingleton(_ => syntaxTreeResolverMock.Object);
129129
serviceCollection.AddAppModelIfNotExist();
130130
serviceCollection.AddAppTypeResolverIfNotExist();
131-
serviceCollection.AddSingleton<CompilerFactory>();
132-
serviceCollection.AddSingleton<ICompilerFactory>(s => s.GetRequiredService<CompilerFactory>());
131+
serviceCollection.AddSingleton<Compiler>();
132+
serviceCollection.AddSingleton<ICompiler>(s => s.GetRequiredService<Compiler>());
133133
serviceCollection.AddSingleton<DynamicallyCompiledAssemblyResolver>();
134134
serviceCollection.AddSingleton<IAssemblyResolver>(s =>
135135
s.GetRequiredService<DynamicallyCompiledAssemblyResolver>());
@@ -186,8 +186,8 @@ public class FakeClass
186186
serviceCollection.AddSingleton(_ => syntaxTreeResolverMock.Object);
187187
serviceCollection.AddAppModelIfNotExist();
188188
serviceCollection.AddAppTypeResolverIfNotExist();
189-
serviceCollection.AddSingleton<CompilerFactory>();
190-
serviceCollection.AddSingleton<ICompilerFactory>(s => s.GetRequiredService<CompilerFactory>());
189+
serviceCollection.AddSingleton<Compiler>();
190+
serviceCollection.AddSingleton<ICompiler>(s => s.GetRequiredService<Compiler>());
191191
serviceCollection.AddSingleton<DynamicallyCompiledAssemblyResolver>();
192192
serviceCollection.AddSingleton<IAssemblyResolver>(s =>
193193
s.GetRequiredService<DynamicallyCompiledAssemblyResolver>());
@@ -224,4 +224,4 @@ public void TestAddAppFromTypeShouldLoadSingleApp()
224224
appResolvers.Should().HaveCount(1);
225225
appResolvers.First().GetTypes().Should().BeEquivalentTo(new[] {typeof(MyAppLocalApp)});
226226
}
227-
}
227+
}

src/AppModel/NetDaemon.AppModel/Common/Extensions/ServiceCollectionExtension.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,17 @@ public static IServiceCollection AddAppFromType(this IServiceCollection services
3838
/// Add apps from c# source code using the configuration source to find path
3939
/// </summary>
4040
/// <param name="services">Services</param>
41-
public static IServiceCollection AddAppsFromSource(this IServiceCollection services)
41+
public static IServiceCollection AddAppsFromSource(this IServiceCollection services, bool useDebug = false)
4242
{
4343
// We make sure we only add AppModel services once
44-
4544
services
4645
.AddAppModelIfNotExist()
4746
.AddAppTypeResolverIfNotExist()
48-
.AddSingleton<CompilerFactory>()
49-
.AddSingleton<ICompilerFactory>(s => s.GetRequiredService<CompilerFactory>())
47+
.AddSingleton<Compiler>()
48+
.AddSingleton<ICompiler>(s => s.GetRequiredService<Compiler>())
5049
.AddSingleton<SyntaxTreeResolver>()
51-
.AddSingleton<ISyntaxTreeResolver>(s => s.GetRequiredService<SyntaxTreeResolver>());
50+
.AddSingleton<ISyntaxTreeResolver>(s => s.GetRequiredService<SyntaxTreeResolver>())
51+
.AddOptions<CompileSettings>().Configure(settings => settings.UseDebug = useDebug);
5252

5353
// We need to compile it here so we can dynamically add the service providers
5454
var assemblyResolver =
@@ -126,4 +126,4 @@ private static IServiceCollection AddConfigManagement(this IServiceCollection se
126126
services.AddTransient(typeof(IAppConfig<>), typeof(AppConfig<>));
127127
return services;
128128
}
129-
}
129+
}

src/AppModel/NetDaemon.AppModel/Internal/AssemblyResolver/AssemblyResolver.cs

+5-6
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@ public Assembly GetResolvedAssembly()
2222

2323
internal class DynamicallyCompiledAssemblyResolver : IAssemblyResolver, IDisposable
2424
{
25-
private readonly ICompilerFactory _compilerFactory;
25+
private readonly ICompiler _compiler;
2626
private Assembly? _compiledAssembly;
2727
private CollectibleAssemblyLoadContext? _currentContext;
2828

2929
public DynamicallyCompiledAssemblyResolver(
30-
ICompilerFactory compilerFactory
30+
ICompiler compiler
3131
)
3232
{
33-
_compilerFactory = compilerFactory;
33+
_compiler = compiler;
3434
}
3535

3636
public Assembly GetResolvedAssembly()
@@ -40,8 +40,7 @@ public Assembly GetResolvedAssembly()
4040
if (_compiledAssembly is not null)
4141
return _compiledAssembly;
4242

43-
var compiler = _compilerFactory.New();
44-
var (loadContext, compiledAssembly) = compiler.Compile();
43+
var (loadContext, compiledAssembly) = _compiler.Compile();
4544
_currentContext = loadContext;
4645
_compiledAssembly = compiledAssembly;
4746
return compiledAssembly;
@@ -55,4 +54,4 @@ public void Dispose()
5554
GC.Collect();
5655
GC.WaitForPendingFinalizers();
5756
}
58-
}
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace NetDaemon.AppModel.Internal.Compiler;
2+
3+
/// <summary>
4+
/// Used to set debug or release mode for dynamic compiled apps
5+
/// </summary>
6+
internal record CompileSettings
7+
{
8+
public bool UseDebug { get; set; } = false;
9+
}

src/AppModel/NetDaemon.AppModel/Internal/Compiler/Compiler.cs

+16-9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Linq.Expressions;
44
using System.Reflection;
55
using System.Runtime;
6+
using System.Runtime.Loader;
67
using System.Text.RegularExpressions;
78
using Microsoft.CodeAnalysis;
89
using Microsoft.CodeAnalysis.CSharp;
@@ -17,35 +18,40 @@ internal record CompiledAssemblyResult(CollectibleAssemblyLoadContext AssemblyCo
1718
internal class Compiler : ICompiler
1819
{
1920
private readonly ILogger<Compiler> _logger;
21+
private readonly bool _useDebug;
2022
private readonly ISyntaxTreeResolver _syntaxResolver;
2123

2224
public Compiler(
2325
ISyntaxTreeResolver syntaxResolver,
24-
ILogger<Compiler> logger
25-
)
26+
ILogger<Compiler> logger,
27+
IOptions<CompileSettings> compileSettings)
2628
{
2729
_syntaxResolver = syntaxResolver;
2830
_logger = logger;
31+
_useDebug = compileSettings.Value.UseDebug;
2932
}
3033

3134
public CompiledAssemblyResult Compile()
3235
{
3336
CollectibleAssemblyLoadContext context = new();
37+
3438
var compilation = GetSharpCompilation();
3539

3640
using var peStream = new MemoryStream();
37-
var emitResult = compilation.Emit(peStream);
41+
using MemoryStream? symStream = _useDebug ? new MemoryStream() : null;
42+
43+
var emitResult = compilation.Emit(peStream, symStream);
3844

3945
if (emitResult.Success)
4046
{
4147
peStream.Seek(0, SeekOrigin.Begin);
42-
var assembly = context.LoadFromStream(peStream);
48+
symStream?.Seek(0, SeekOrigin.Begin);
49+
var assembly = context.LoadFromStream(peStream, symStream);
4350
return new CompiledAssemblyResult(context, assembly);
4451
}
4552

4653
var error = PrettyPrintCompileError(emitResult);
47-
48-
_logger.LogError("Failed to compile applications\n{error}", error);
54+
_logger.LogError("Failed to compile applications\n{Error}", error);
4955

5056
context.Unload();
5157
// Finally do cleanup and release memory
@@ -69,8 +75,9 @@ private CSharpCompilation GetSharpCompilation()
6975
metaDataReference.ToArray(),
7076
new CSharpCompilationOptions(
7177
OutputKind.DynamicallyLinkedLibrary,
72-
optimizationLevel: OptimizationLevel.Release,
73-
assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default
78+
optimizationLevel: _useDebug ? OptimizationLevel.Debug : OptimizationLevel.Release,
79+
assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default,
80+
platform: Platform.AnyCpu
7481
)
7582
);
7683
}
@@ -112,4 +119,4 @@ private static string PrettyPrintCompileError(EmitResult emitResult)
112119

113120
return msg.ToString();
114121
}
115-
}
122+
}

src/AppModel/NetDaemon.AppModel/Internal/Compiler/CompilerFactory.cs

-21
This file was deleted.

src/AppModel/NetDaemon.AppModel/Internal/Compiler/ICompilerFactory.cs

-6
This file was deleted.

0 commit comments

Comments
 (0)