Skip to content

Commit

Permalink
Refactored loading of assemblies
Browse files Browse the repository at this point in the history
There were problems with loading `SourceGenerator.Foundations.Windows` due to it being inside a dll that was not loaded in time. This would throw an runtime exception which was annoying. I refactored the dll loading to unzip on disk rather then use in memory to speed up generation time
  • Loading branch information
ByronMayne committed Oct 23, 2023
1 parent 642a1b5 commit 4f595b1
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 97 deletions.
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<Project>
<PropertyGroup>
<Version>1.2.0</Version>
<Version>1.2.1</Version>
</PropertyGroup>
</Project>
14 changes: 14 additions & 0 deletions src/Local.Reference.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,18 @@
<ProjectReference Include="$(MSBuildThisFileDirectory)SourceGenerator.Foundations\SourceGenerator.Foundations.csproj" ReferenceOutputAssembly="false" OutputItemType="Analyzer" />
</ItemGroup>
<Import Project="$(MSBuildThisFileDirectory)SourceGenerator.Foundations\SourceGenerator.Foundations.props" />
<!-- ==== Copy Plugin Assemblies to Resources ===
We inject for example 'SourceGenerator.Foundations.Windows' into the assembly that is referencing this file. This is
done because we need to resolve this assembly at runtime. You would assembly that the better place to put this would be
as a resource of `SourceGenerator.Foundations.Contracts` however since the plugin assemblies also refernece this, we end
up with circular dependencies. We could resolve this by doing IL editing with Cecil but that is making this already complex
project more complex. So instead we put them in our target assembly.
-->
<Target Name="SGFLocal_PackagePlugins" BeforeTargets="SGF_EmbedDependencies">
<ItemGroup>
<__pluginProjectName Include="SourceGenerator.Foundations.Windows"/>
<SGF_EmbeddedAssembly Include="@(__pluginProjectName->'$(MSBuildThisFileDirectory)/Plugins/%(Identity)/bin/$(Configuration)//netstandard2.0/%(Identity).dll')" />
</ItemGroup>
<Message Importance="high" Text="Importing: $(AssemblyName) @(SGF_EmbeddedAssembly)"/>
</Target>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ public class Payload
public string? Version { get; set; }
}

public ConsoleAppSourceGenerator() : base("ConsoleApp")
{

}

protected override void OnInitialize(IncrementalGeneratorInitializationContext context)
{
Payload payload = new Payload()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ static DevelopmentEnviroment()
.WriteTo.Sink(s_sinkAggregate)
.CreateLogger();

string assemblyVersion
= typeof(DevelopmentEnviroment)
.Assembly
.GetName()
.Version.ToString();

Instance = new GenericDevelopmentEnviroment();
AppDomain.CurrentDomain.UnhandledException += OnExceptionThrown;

Expand All @@ -56,7 +62,8 @@ static DevelopmentEnviroment()

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Assembly windowsEnvironmentAssembly = Assembly.Load(new AssemblyName("SourceGenerator.Foundations.Windows"));
AssemblyName windowsAssemblyName = new AssemblyName($"SourceGenerator.Foundations.Windows, Version={assemblyVersion}, Culture=neutral, PublicKeyToken=null");
Assembly windowsEnvironmentAssembly = Assembly.Load(windowsAssemblyName);

if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("VisualStudioVersion")))
{
Expand All @@ -77,6 +84,7 @@ static DevelopmentEnviroment()
}



/// <summary>
/// Attaches the debugger to the given process Id
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
</PackageReference>
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
</ItemGroup>
<Target Name="SGF_AddPluginDependedencies" BeforeTargets="SGF_EmbedDependencies">
<!--<Target Name="SGF_AddPluginDependedencies" BeforeTargets="SGF_EmbedDependencies">
<ItemGroup>
<SGF_DevelopmentPlugin Include="SourceGenerator.Foundations.Windows" />
<SGF_EmbeddedAssembly Include="@(SGF_DevelopmentPlugin->'..\Plugins\%(Identity)\bin\$(Configuration)\$(TargetFramework)\%(Identity).dll')"/>
</ItemGroup>
</Target>
</Target>-->
<Import Project="..\SourceGenerator.Foundations\SourceGenerator.Foundations.props" />
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ namespace SGF.Generators
[Generator]
internal class ScriptInjectorGenerator : IncrementalGenerator
{
public ScriptInjectorGenerator() : base("ScriptInjector")
{ }

protected override void OnInitialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterSourceOutput(context
Expand Down Expand Up @@ -40,7 +43,6 @@ private void CompilerSource(SourceProductionContext context, (Compilation compil
{
continue;
}

string hintName = resourceName.Replace($"{ResourceConfiguration.ScriptPrefix}", "");
hintName = Path.ChangeExtension(hintName, ".generated.cs");
using Stream stream = assembly.GetManifestResourceStream(resourceName);
Expand Down
11 changes: 7 additions & 4 deletions src/SourceGenerator.Foundations/IncrementalGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#nullable enable
using Microsoft.CodeAnalysis;
using Serilog;
using SGF.Reflection;
using System;
using System.Diagnostics;

Expand All @@ -18,14 +19,16 @@ internal abstract class IncrementalGenerator : IIncrementalGenerator
/// </summary>
public ILogger Logger { get; }

static IncrementalGenerator()
{
AssemblyResolver.Initialize();
}

/// <summary>
/// Initializes a new instance of the incremental generator with an optional name
/// </summary>
protected IncrementalGenerator(string? name = null)
protected IncrementalGenerator(string? name)
{
Type type = GetType();
if (string.IsNullOrWhiteSpace(name)) name = type.FullName;

Logger = DevelopmentEnviroment.Logger.ForContext(GetType());
Logger.Information("Initalizing {GeneratorName}", name ?? GetType().Name);
}
Expand Down
39 changes: 15 additions & 24 deletions src/SourceGenerator.Foundations/Reflection/AssemblyResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,17 @@ private enum LogLevel

private static readonly IList<Assembly> s_assemblies;
private static readonly AssemblyName s_contractsAssemblyName;
private static readonly string s_unpackDirectory;

static AssemblyResolver()
{
s_assemblies = new List<Assembly>();
s_unpackDirectory = Path.Combine(Path.GetTempPath(), "SourceGenerator.Foundations", "Assemblies");
s_contractsAssemblyName = new AssemblyName();
if (!Directory.Exists(s_unpackDirectory))
{
Directory.CreateDirectory(s_unpackDirectory);
}
}

[ModuleInitializer]
Expand Down Expand Up @@ -98,40 +104,25 @@ private static void OnAssemblyLoaded(object sender, AssemblyLoadEventArgs args)
ManifestResourceInfo resourceInfo = assembly.GetManifestResourceInfo(resourceName);
if (resourceInfo != null)
{
using Stream stream = assembly.GetManifestResourceStream(resourceName);
byte[] data = new byte[stream.Length];
_ = stream.Read(data, 0, data.Length);
try
{
Assembly resolvedAssembly = AppDomain.CurrentDomain.Load(data);

if (resolvedAssembly != null)
{
if (!s_assemblies.Contains(resolvedAssembly))
{
s_assemblies.Add(resolvedAssembly);
}
string assemblyPath = Path.Combine(s_unpackDirectory, $"{assemblyName.Name}-{assemblyName.Version}.dll");

return resolvedAssembly;
}
}
catch (Exception exception)
if (!File.Exists(assemblyPath))
{
if (assemblyName != s_contractsAssemblyName)
using (Stream resourceStream = assembly.GetManifestResourceStream(resourceName))
using (FileStream fileStream = new FileStream(assemblyPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read))
{
// This is redirected to a metho so that it does not attempt to
// load the assembly if it has failed.
Log(exception, LogLevel.Error, "Failed to load assembly {Assembly} due to exception", assemblyName);
resourceStream.CopyTo(fileStream);
fileStream.Flush();
}
return null;
}
Assembly resolvedAssembly = Assembly.LoadFile(assemblyPath);
s_assemblies.Add(resolvedAssembly);
return resolvedAssembly;
}
}
return null;
}



/// <summary>
/// Wrapper around the logging implemention to handle the case where loading the contracts library can actually fail
/// </summary>
Expand Down
117 changes: 58 additions & 59 deletions src/SourceGenerator.Foundations/SourceGenerator.Foundations.csproj
Original file line number Diff line number Diff line change
@@ -1,76 +1,75 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>9.0</LangVersion>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<IncludeBuildOutput>false</IncludeBuildOutput>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>9.0</LangVersion>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<IncludeBuildOutput>false</IncludeBuildOutput>
<IsRoslynComponent>true</IsRoslynComponent>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<IncludeSymbols>false</IncludeSymbols>
<Title>Source Generator Foundations</Title>
<Authors>Byron Mayne</Authors>
<Description>A Source Generator for Source Generators to smooth out the bumps in development. A foucs on removing the boilerplate and improving the debugging experince.</Description>
<PackageProjectUrl></PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/ByronMayne/SourceGenerator.Foudations</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>source;generator;csharp</PackageTags>
<RootNamespace>SGF</RootNamespace>
<NoWarn>$(NoWarn);NU5128</NoWarn>
<TargetsForTfmSpecificContentInPackage>$(TargetsForTfmSpecificContentInPackage);Nuget_AppendContent</TargetsForTfmSpecificContentInPackage>
<NoWarn>$(NoWarn);NU5100</NoWarn>
</PropertyGroup>
<ItemGroup>
<None Update="$(AssemblyName).props" PackagePath="build" />
<None Include="$(OutputPath)$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.1" PrivateAssets="all" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<IncludeSymbols>false</IncludeSymbols>
<Title>Source Generator Foundations</Title>
<Authors>Byron Mayne</Authors>
<Description>A Source Generator for Source Generators to smooth out the bumps in development. A foucs on removing the boilerplate and improving the debugging experince.</Description>
<PackageProjectUrl></PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/ByronMayne/SourceGenerator.Foudations</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>source;generator;csharp</PackageTags>
<RootNamespace>SGF</RootNamespace>
<NoWarn>$(NoWarn);NU5128</NoWarn>
<TargetsForTfmSpecificContentInPackage>$(TargetsForTfmSpecificContentInPackage);Nuget_AppendContent</TargetsForTfmSpecificContentInPackage>
<NoWarn>$(NoWarn);NU5100</NoWarn>
</PropertyGroup>
<ItemGroup>
<None Update="$(AssemblyName).props" PackagePath="build" />
<None Include="$(OutputPath)$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.1" PrivateAssets="all" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>


<!--=================================================================
<!--=================================================================
SourceGenerator.Foundations.* aka Developement assemblies
=================================================================-->

<!--=================================================================
<!--=================================================================
SourceGenerator.Foundations.Contracts Attributes
Private: Don't copy to output directory
PrivateAssets: Don't add depedency to generated NuGet package
Pack: Add dll to nuget package
PackagePath: Add to the dll to the generated nuget package
=================================================================-->
<ProjectReference Include="..\SourceGenerator.Foundations.Contracts\SourceGenerator.Foundations.Contracts.csproj" Pack="True" PackagePath="lib/netstandard2.0" Private="False" PrivateAssets="All" />

<SGF_EmbeddedScript Include="Reflection\AssemblyResolver.cs" />
<SGF_EmbeddedScript Include="IncrementalGenerator.cs" />
<SGF_EmbeddedScript Include="Configuration\ResourceConfiguration.cs" />
<SGF_EmbeddedScript Include="Runtime\ModuleInitializerAttribute.cs" />
<SGF_EmbeddedScript Include="Generators\ScriptInjectorGenerator.cs" />
<ProjectReference Include="..\SourceGenerator.Foundations.Contracts\SourceGenerator.Foundations.Contracts.csproj" Pack="True" PackagePath="lib/netstandard2.0" Private="False" PrivateAssets="All" />

<None Include="..\..\README.md">
<Pack>True</Pack>
<PackagePath>/</PackagePath>
</None>
<ProjectReference Include="..\Plugins\SourceGenerator.Foundations.Windows\SourceGenerator.Foundations.Windows.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<Private>True</Private>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Analyzer\" />
</ItemGroup>
<Target Name="Nuget_AppendContent">
<ItemGroup>
<TfmSpecificPackageFile Include="$(MSBuildThisFileDirectory)$(AssemblyName).props">
<PackagePath>build/$(AssemblyName).props</PackagePath>
</TfmSpecificPackageFile>
<!--<TfmSpecificPackageFile Include="$(OutputPath)$(AssemblyName).???">
<!-- Put all plugins under a `sgf/embedded/assemblies` folder. Every assembly in here will be auto copied forward -->
<ProjectReference Include="..\Plugins\SourceGenerator.Foundations.Windows\SourceGenerator.Foundations.Windows.csproj" Pack="True" PackagePath="sgf/embedded/assemblies" Private="False" PrivateAssets="All" />

<SGF_EmbeddedScript Include="Reflection\AssemblyResolver.cs" />
<SGF_EmbeddedScript Include="IncrementalGenerator.cs" />
<SGF_EmbeddedScript Include="Configuration\ResourceConfiguration.cs" />
<SGF_EmbeddedScript Include="Runtime\ModuleInitializerAttribute.cs" />
<SGF_EmbeddedScript Include="Generators\ScriptInjectorGenerator.cs" />

<None Include="..\..\README.md">
<Pack>True</Pack>
<PackagePath>/</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="Analyzer\" />
</ItemGroup>
<Target Name="Nuget_AppendContent">
<ItemGroup>
<TfmSpecificPackageFile Include="$(MSBuildThisFileDirectory)$(AssemblyName).props">
<PackagePath>build/$(AssemblyName).props</PackagePath>
</TfmSpecificPackageFile>
<!--<TfmSpecificPackageFile Include="$(OutputPath)$(AssemblyName).???">
<PackagePath>lib\netstandard2.0</PackagePath>
</TfmSpecificPackageFile>-->
</ItemGroup>
</Target>
<Import Project="$(MSBuildThisFileDirectory)$(AssemblyName).props" />
</ItemGroup>
</Target>
<Import Project="$(MSBuildThisFileDirectory)$(AssemblyName).props" />
</Project>
20 changes: 15 additions & 5 deletions src/SourceGenerator.Foundations/SourceGenerator.Foundations.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,30 @@
<IsRoslynComponent>true</IsRoslynComponent>
<IsInNugetPackage>false</IsInNugetPackage>
<IsInNugetPackage Condition="Exists('$(MSBuildThisFileDirectory)..\.nupkg.metadata')">true</IsInNugetPackage>
<SGF_NuGetEnvironmentsDir>$(MSBuildThisFileDirectory)..\environments\</SGF_NuGetEnvironmentsDir>
<SGF_NugGetEmbeddedAssemblyDir>$(MSBuildThisFileDirectory)..\sgf\embedded\assemblies\</SGF_NugGetEmbeddedAssemblyDir>
<SGF_NugGetScriptsAssemblyDir>$(MSBuildThisFileDirectory)..\sgf\embedded\scripts\</SGF_NugGetScriptsAssemblyDir>
<SGF_NetGetNetStandardDir>$(MSBuildThisFileDirectory)..\lib\netstandard2.0\</SGF_NetGetNetStandardDir>
<SGF_IsMainAssembly>false</SGF_IsMainAssembly>
<SGF_IsMainAssembly Condition="'$(MSBuildProjectName)' == 'SourceGenerator.Foundations'">True</SGF_IsMainAssembly>
</PropertyGroup>


<!-- Exposed Compiler Flags -->
<ItemGroup>
<CompilerVisibleProperty Include="RootNamespace" />
<CompilerVisibleProperty Include="AssemblyName" />
<CompilerVisibleProperty Include="BaseOutputPath "/>
</ItemGroup>

<!-- Shared References -->
<ItemGroup>
<PackageReference Include="Serilog" Version="2.12.0" />
</ItemGroup>

<!-- When running as NuGet package, copy all dlls in the lib folder -->
<!--When inside a NuGet package copy all the sgf/embeded/scripts and sgf/embedded/assemblies if they exist -->
<ItemGroup Condition="$(IsInNugetPackage)">
<SGF_EmbeddedAssembly Include="$(SGF_NetGetNetStandardDir)*.*"/>
<SGF_EmbeddedAssembly Include="$(SGF_NuGetEnvironmentsDir)*.*"/>
<SGF_EmbeddedAssembly Include="$(SGF_NugGetEmbeddedAssemblyDir)*.*"/>
<SGF_EmbeddedScript Include="$(SGF_NugGetScriptsAssemblyDir)*.*"/>
</ItemGroup>

<!-- Embedded Assemblies -->
Expand All @@ -38,7 +47,8 @@
<LogicalName>SGF.Assembly::%(FileName)%(Extension)</LogicalName>
</EmbeddedResource>
</ItemGroup>
<!-- Embeded Scripts -->

<!-- Embeded Scripts -->
<ItemGroup>
<EmbeddedResource Include="@(SGF_EmbeddedScript)">
<LogicalName>SGF.Script::%(RecursiveDir)%(FileName)%(Extension)</LogicalName>
Expand Down

0 comments on commit 4f595b1

Please sign in to comment.