Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.