Skip to content

Commit

Permalink
CodeGen: support builds which use reference assemblies (#3753)
Browse files Browse the repository at this point in the history
  • Loading branch information
ReubenBond authored and sergeybykov committed Dec 7, 2017
1 parent b59e309 commit a2ad7e3
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 72 deletions.
129 changes: 129 additions & 0 deletions src/ClientGenerator/AssemblyResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.DependencyModel;
using Microsoft.Extensions.DependencyModel.Resolution;
using Orleans.Runtime;

namespace Orleans.CodeGeneration
{
internal class AssemblyResolver
{
/// <summary>
/// Needs to be public so can be serialized accross the the app domain.
/// </summary>
public Dictionary<string, string> ReferenceAssemblyPaths { get; } = new Dictionary<string, string>();

private readonly ICompilationAssemblyResolver assemblyResolver;

private readonly DependencyContext dependencyContext;
private readonly DependencyContext resolverRependencyContext;

public AssemblyResolver(string path, List<string> referencedAssemblies)
{
if (Path.GetFileName(path) == "Orleans.dll")
this.Assembly = typeof(RuntimeVersion).Assembly;
else
this.Assembly = Assembly.LoadFrom(path);
this.dependencyContext = DependencyContext.Load(this.Assembly);
this.resolverRependencyContext = DependencyContext.Load(typeof(AssemblyResolver).Assembly);
var codegenPath = Path.GetDirectoryName(new Uri(typeof(AssemblyResolver).Assembly.CodeBase).LocalPath);
this.assemblyResolver = new CompositeCompilationAssemblyResolver(
new ICompilationAssemblyResolver[]
{
new AppBaseCompilationAssemblyResolver(codegenPath),
new AppBaseCompilationAssemblyResolver(Path.GetDirectoryName(path)),
new ReferenceAssemblyPathResolver(),
new PackageCompilationAssemblyResolver()
});

foreach (var assemblyPath in referencedAssemblies)
{
var libName = Path.GetFileNameWithoutExtension(assemblyPath);
if (!string.IsNullOrWhiteSpace(libName)) this.ReferenceAssemblyPaths[libName] = assemblyPath;
var asmName = AssemblyName.GetAssemblyName(assemblyPath);
this.ReferenceAssemblyPaths[asmName.FullName] = assemblyPath;
}
}

public Assembly Assembly { get; }

/// <summary>
/// Handles System.AppDomain.AssemblyResolve event of an System.AppDomain
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="args">The event data.</param>
/// <returns>The assembly that resolves the type, assembly, or resource;
/// or null if theassembly cannot be resolved.
/// </returns>
public Assembly ResolveAssembly(object sender, ResolveEventArgs args)
{
var name = new AssemblyName(args.Name);

// Attempt to resolve the library from one of the dependency contexts.
var library = this.resolverRependencyContext?.RuntimeLibraries?.FirstOrDefault(NamesMatch)
?? this.dependencyContext?.RuntimeLibraries?.FirstOrDefault(NamesMatch);
if (library != null)
{
var wrapper = new CompilationLibrary(
library.Type,
library.Name,
library.Version,
library.Hash,
library.RuntimeAssemblyGroups.SelectMany(g => g.AssetPaths),
library.Dependencies,
library.Serviceable);

var assemblies = new List<string>();
if (this.assemblyResolver.TryResolveAssemblyPaths(wrapper, assemblies))
{
foreach (var asm in assemblies)
{
var assembly = TryLoadAssemblyFromPath(asm);
if (assembly != null) return assembly;
}
}
}

if (this.ReferenceAssemblyPaths.TryGetValue(name.FullName, out var pathByFullName))
{
var assembly = TryLoadAssemblyFromPath(pathByFullName);
if (assembly != null) return assembly;
}

if (this.ReferenceAssemblyPaths.TryGetValue(name.Name, out var pathByName))
{
//
// Only try to load it if the resolved path is different than from before
//

if (String.Compare(pathByFullName, pathByName, StringComparison.OrdinalIgnoreCase) != 0)
{
var assembly = TryLoadAssemblyFromPath(pathByName);
if (assembly != null) return assembly;
}
}

return null;

bool NamesMatch(RuntimeLibrary runtime)
{
return string.Equals(runtime.Name, name.Name, StringComparison.OrdinalIgnoreCase);
}
}

private Assembly TryLoadAssemblyFromPath(string path)
{
try
{
return Assembly.LoadFrom(path);
}
catch
{
return null;
}
}
}
}
77 changes: 8 additions & 69 deletions src/ClientGenerator/ClientGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,6 @@ private static bool CreateGrainClientAssembly(CodeGenOptions options)
};
appDomain = AppDomain.CreateDomain("Orleans-CodeGen Domain", null, appDomainSetup);

// Set up assembly resolver
var refResolver = new ReferenceResolver(options.ReferencedAssemblies);
appDomain.AssemblyResolve += refResolver.ResolveAssembly;

// Create an instance
var generator =
(GrainClientGenerator)
Expand All @@ -89,13 +85,11 @@ private static bool CreateGrainClientAssembly(CodeGenOptions options)
/// </summary>
private bool CreateGrainClient(CodeGenOptions options)
{
// Load input assembly
// special case Orleans.dll because there is a circular dependency.
var assemblyName = AssemblyName.GetAssemblyName(options.InputAssembly.FullName);
var grainAssembly = (Path.GetFileName(options.InputAssembly.FullName) != "Orleans.dll")
? Assembly.LoadFrom(options.InputAssembly.FullName)
: Assembly.Load(assemblyName);

// Set up assembly resolver
var inputAssemblyPath = options.InputAssembly.FullName;
var resolver = new AssemblyResolver(inputAssemblyPath, options.ReferencedAssemblies);
AppDomain.CurrentDomain.AssemblyResolve += resolver.ResolveAssembly;

// Create directory for output file if it does not exist
var outputFileDirectory = Path.GetDirectoryName(options.OutputFileName);

Expand All @@ -110,17 +104,19 @@ private bool CreateGrainClient(CodeGenOptions options)
// Generate source
ConsoleText.WriteStatus("Orleans-CodeGen - Generating file {0}", options.OutputFileName);

var source = codeGenerator.GenerateSourceForAssembly(resolver.Assembly);
using (var sourceWriter = new StreamWriter(options.OutputFileName))
{
sourceWriter.WriteLine("#if !EXCLUDE_CODEGEN");
DisableWarnings(sourceWriter, suppressCompilerWarnings);
sourceWriter.WriteLine(codeGenerator.GenerateSourceForAssembly(grainAssembly));
sourceWriter.WriteLine(source);
RestoreWarnings(sourceWriter, suppressCompilerWarnings);
sourceWriter.WriteLine("#endif");
}

ConsoleText.WriteStatus("Orleans-CodeGen - Generated file written {0}", options.OutputFileName);

AppDomain.CurrentDomain.AssemblyResolve -= resolver.ResolveAssembly;
return true;
}

Expand Down Expand Up @@ -286,62 +282,5 @@ private static void CheckPath(string path, Func<string, bool> condition, string
Console.WriteLine("CODEGEN-ERROR: " + errMsg);
throw new ArgumentException("FAILED: " + errMsg);
}


/// <summary>
/// Simple class that loads the reference assemblies upon the AppDomain.AssemblyResolve
/// </summary>
[Serializable]
internal class ReferenceResolver
{
/// <summary>
/// Dictionary : Assembly file name without extension -> full path
/// </summary>
private Dictionary<string, string> referenceAssemblyPaths = new Dictionary<string, string>();

/// <summary>
/// Needs to be public so can be serialized accross the the app domain.
/// </summary>
public Dictionary<string, string> ReferenceAssemblyPaths
{
get
{
return referenceAssemblyPaths;
}
set
{
referenceAssemblyPaths = value;
}
}

/// <summary>
/// Inits the resolver
/// </summary>
/// <param name="referencedAssemblies">Full paths of referenced assemblies</param>
public ReferenceResolver(IEnumerable<string> referencedAssemblies)
{
if (null == referencedAssemblies) return;

foreach (var assemblyPath in referencedAssemblies) referenceAssemblyPaths[Path.GetFileNameWithoutExtension(assemblyPath)] = assemblyPath;
}

/// <summary>
/// Handles System.AppDomain.AssemblyResolve event of an System.AppDomain
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="args">The event data.</param>
/// <returns>The assembly that resolves the type, assembly, or resource;
/// or null if theassembly cannot be resolved.
/// </returns>
public Assembly ResolveAssembly(object sender, ResolveEventArgs args)
{
Assembly assembly = null;
string path;
var asmName = new AssemblyName(args.Name);
if (referenceAssemblyPaths.TryGetValue(asmName.Name, out path)) assembly = Assembly.LoadFrom(path);
else ConsoleText.WriteStatus("Could not resolve {0}:", asmName.Name);
return assembly;
}
}
}
}
1 change: 1 addition & 0 deletions src/ClientGenerator/ClientGenerator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="AssemblyResolver.cs" />
<Compile Include="ClientGenerator.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
Expand Down
1 change: 1 addition & 0 deletions src/ClientGenerator/project.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"dependencies": {
"Microsoft.Extensions.DependencyModel": "2.0.3"
},
"frameworks": {
"net461": {}
Expand Down
2 changes: 2 additions & 0 deletions src/NuGet/Microsoft.Orleans.OrleansCodeGenerator.Build.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
<file src="OrleansCodeGenerator.pdb" target="tools" />
<file src="Microsoft.CodeAnalysis.CSharp.dll" target="tools" />
<file src="Microsoft.CodeAnalysis.dll" target="tools" />
<file src="Microsoft.DotNet.PlatformAbstractions.dll" target="tools" />
<file src="Microsoft.Extensions.DependencyModel.dll" target="tools" />
<file src="Microsoft.Extensions.DependencyInjection.Abstractions.dll" target="tools" />
<file src="Microsoft.Extensions.DependencyInjection.dll" target="tools" />
<file src="Microsoft.Win32.Primitives.dll" target="tools" />
Expand Down
7 changes: 4 additions & 3 deletions src/Orleans.SDK.targets
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ Copyright (C) Microsoft Corporation. All rights reserved.

<!-- This target is run just before Compile for an Orleans Grain Interface Project -->
<Target Name="OrleansCodeGeneration"
AfterTargets="BeforeCompile"
BeforeTargets="CoreCompile"
AfterTargets="GenerateBuildDependencyFile"
BeforeTargets="AssignTargetPaths"
Condition="'$(OrleansCodeGenPrecompile)'!='true'"
Inputs="@(Compile);@(ReferencePath)"
Outputs="$(IntermediateOutputPath)$(TargetName).orleans.g.cs">
Expand All @@ -62,7 +62,8 @@ Copyright (C) Microsoft Corporation. All rights reserved.
<CodeGenArgs Include="/out:$(CodeGenFilename)"/>
<CodeGenArgs Include="@(ReferencePath->'/r:%(Identity)')"/>
</ItemGroup>
<MSBuild Projects="$(MSBuildProjectFullPath)" Targets="Build" Properties="OrleansCodeGenPrecompile=true;DefineConstants=$(ExcludeCodeGen);DesignTimeBuild=true" UnloadProjectsOnCompletion="true" UseResultsCache="false" />
<Copy SourceFiles="$(ProjectDepsFilePath)" DestinationFolder="$(CodeGenDirectory)" ContinueOnError="WarnAndContinue" Condition="Exists('$(ProjectDepsFilePath)')" />
<MSBuild Projects="$(MSBuildProjectFullPath)" Targets="Build" Properties="OrleansCodeGenPrecompile=true;DefineConstants=$(ExcludeCodeGen);DesignTimeBuild=true;PreserveCompilationContext=true" UnloadProjectsOnCompletion="true" UseResultsCache="false" />
<Message Text="[OrleansCodeGeneration] - Code-gen args file=$(ArgsFile)"/>
<WriteLinesToFile Overwrite="true" File="$(ArgsFile)" Lines="@(CodeGenArgs)"/>
<Message Text="[OrleansCodeGeneration] - Precompiled assembly"/>
Expand Down

0 comments on commit a2ad7e3

Please sign in to comment.