For test purposes, I need to get a System.Reflection.Assembly from a string source which contains a source code. I am using Roslyn:
SyntaxTree tree = CSharpSyntaxTree.ParseText(source);
CSharpCompilation compilation = CSharpCompilation.Create("TestCompilation", new[] { tree });
Assembly assembly = null;
using (var stream = new MemoryStream())
{
var emitResult = compilation.Emit(stream);
if (!emitResult.Success)
{
var message = emitResult.Diagnostics.Select(d => d.ToString())
.Aggregate((d1, d2) => $"{d1}{Environment.NewLine}{d2}");
throw new InvalidOperationException($"Errors!{Environment.NewLine}{message}");
}
stream.Seek(0, SeekOrigin.Begin);
assembly = Assembly.Load(stream.ToArray());
}
As you can see my attempt here is to emit a CSHarpCompilation object so that I can get the Assembly later. I am trying to do this with:
var source = #"
namespace Root.MyNamespace1 {
public class MyClass {
}
}
";
Emit errors
But I fail at var emitResult = compilation.Emit(stream) and enter the conditional which shows the error. I get 1 warning and 3 errors:
Warning CS8021: No value for RuntimeMetadataVersion found. No assembly containing System.Object was found nor was a value for RuntimeMetadataVersion specified through options.
(3,34): Error CS0518: Predefined type 'System.Object' is not defined or imported
(3,34): Error CS1729: 'object' does not contain a constructor that takes 0 arguments
Error CS5001: Program does not contain a static 'Main' method suitable for an entry point
So it seems I need to add reference to mscorelib and it also seems like I need to tell Roslyn that I want to emit a class library, not an executable assembly. How to do that?
You're missing a metadata reference to mscorlib and you can change the compilation options via CSharpCompilationOptions.
Create your compilation as follows:
var Mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
var compilation = CSharpCompilation.Create("TestCompilation",
syntaxTrees: new[] { tree }, references: new[] { Mscorlib }, options: options);
For creating a netstandard lib from not-netstandard code (in my case I create a netstandard lib from core3.1) the code should be
var compilation = CSharpCompilation.Create("TestCompilation",
syntaxTrees: new[] {
tree
},
references: new[] {
MetadataReference.CreateFromFile(#"C:\Users\YOURUSERNAME\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\netstandard.dll"
},
options:
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
A crux here is the path.
As the host code is core3.1 one cannot use MetadataReference.CreateFromFile(typeof(object).Assembly.Location) as it references a core3.1 object and not a netcore2.0 object.
As referencing a nuget package (nowadays) downloads them to the %USERPROFILE%\.nuget\packages folder it can be loaded from there. This does not hold for any other user though so a different solution must be designed. One could utilise System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile) but that probably won't hold for CI/CD.
Update:
System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile) does hold for CI/CD.
MetadataReference.CreateFromFile( Path.Combine(
UserProfilePath, ".nuget", "packages", "netstandard.library", "2.0.3", "build",
"netstandard2.0", "ref", "netstandard.dll"))
See LehmanLaidun builds.
Related
I'm using Roslyn to emit a CSharpCompilation object in Visual Studio to a file. The DLL that is generated does not contain any assembly info other than the assembly metadata, and I'd like to add the version and sign it if possible. How can these be done with Roslyn?
You need to include source code which sets the Assembly* attributes just like in the VS C# project templates. If you have done that, the .NET version info is set. You can read that information with Reflection or tools like ILSpy.
That way Explorer won't show any version info in its property page. Explorer is only showing Win32 VersionInfo not .NET version info. You need to emit Win32 resource code with Rosyln to set these values. Luckily there's a method to auto generate the Win32 info from the .NET ones: CreateDefaultWin32Resources.
Here's a complete and working code sample:
public void VersionInfoExample()
{
// 1. Generate AssemblyInfo.cs-like C# code and parse syntax tree
StringBuilder asmInfo = new StringBuilder();
asmInfo.AppendLine("using System.Reflection;");
asmInfo.AppendLine("[assembly: AssemblyTitle(\"Test\")]");
asmInfo.AppendLine("[assembly: AssemblyVersion(\"1.1.0\")]");
asmInfo.AppendLine("[assembly: AssemblyFileVersion(\"1.1.0\")]");
// Product Info
asmInfo.AppendLine("[assembly: AssemblyProduct(\"Foo\")]");
asmInfo.AppendLine("[assembly: AssemblyInformationalVersion(\"1.3.3.7\")]");
var syntaxTree = CSharpSyntaxTree.ParseText(asmInfo.ToString(), encoding: Encoding.Default);
// 2. Create compilation
string mscorlibPath = typeof(object).Assembly.Location;
MetadataReference mscorlib = MetadataReference.CreateFromFile(mscorlibPath, new MetadataReferenceProperties(MetadataImageKind.Assembly));
CSharpCompilationOptions options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
CSharpCompilation compilation = CSharpCompilation.Create("Test.dll",
references: new[] { mscorlib },
syntaxTrees: new[] { syntaxTree },
options: options);
// 3. Emit code including win32 version info
using (MemoryStream dllStream = new MemoryStream())
using (MemoryStream pdbStream = new MemoryStream())
using (Stream win32resStream = compilation.CreateDefaultWin32Resources(
versionResource: true, // Important!
noManifest: false,
manifestContents: null,
iconInIcoFormat: null))
{
EmitResult result = compilation.Emit(
peStream: dllStream,
pdbStream: pdbStream,
win32Resources: win32resStream);
System.IO.File.WriteAllBytes("Test.dll", dllStream.ToArray());
}
}
So i'm having this problem.
I'm trying to compile code in memory and adding namespace references by searching the syntax tree so i do not add them manually. Trying to simulate how Visual Studio maybe does it.
I'm a bit over my head in the compilation department. Even if i add a metadata reference to System while reading the syntax tree it does not find System.Console.
The key is that i want it to include the assemblies by itself, i do not want to add a "MetadataReference.CreateFromFile(....,"System.Console").
I explained the code below so that is clear what is happening.
class App
{
static void Main(string[] args)
{
//creating the syntax tree for the program
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(#"
namespace ns{
using System;
public class App{
public static void Main(string[] args){
Console.Write(""dada"");
}
}
}");
//creating options that tell the compiler to output a console application
var options = new CSharpCompilationOptions(
OutputKind.ConsoleApplication,
optimizationLevel: OptimizationLevel.Debug,
allowUnsafe: true);
//creating the compilation
var compilation = CSharpCompilation.Create(Path.GetRandomFileName(), options: options);
//adding the syntax tree
compilation = compilation.AddSyntaxTrees(syntaxTree);
//getting the local path of the assemblies
var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
List<MetadataReference> references = new List<MetadataReference>();
//adding the core dll containing object and other classes
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Private.CoreLib.dll")));
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "mscorlib.dll")));
//gathering all using directives in the compilation
var usings = compilation.SyntaxTrees.Select(tree => tree.GetRoot().ChildNodes().OfType<UsingDirectiveSyntax>()).SelectMany(s => s).ToArray();
//for each using directive add a metadatareference to it
foreach (var u in usings)
{
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, u.Name.ToString() + ".dll")));
}
//add the reference list to the compilation
compilation=compilation.AddReferences(references);
//compile
using (var ms = new MemoryStream())
{
EmitResult result = compilation.Emit(ms);
if (!result.Success)
{
IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error);
foreach (Diagnostic diagnostic in failures)
{
Console.Error.WriteLine("{0}: {1}, {2}", diagnostic.Id, diagnostic.GetMessage(), diagnostic.Location);
}
}
else
{
ms.Seek(0, SeekOrigin.Begin);
AssemblyLoadContext context = AssemblyLoadContext.Default;
Assembly assembly = context.LoadFromStream(ms);
assembly.EntryPoint.Invoke(null, new object[] { new string[] { "arg1", "arg2", "etc" } });
}
}
}
}
In the .net core System.Console lives in the System.Console.dll. So you need to add reference on it
You need to add reference on the System.Runtime.dll to correctly resolve the predefined types: object, bool and so on
SyntaxNode.ChildNodes() returns only child, that means it doesn't return the descendents nodes, so if you want to get all UsingDirectiveSyntax you should change your logic. As one of way just use SyntaxNode.DescendantNodes()
After applying all suggestions you just get something likes this (The parts that didn't change will skipped):
...
//adding the core dll containing object and other classes
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Private.CoreLib.dll")));
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Console.dll")));
references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Runtime.dll")));
//gathering all using directives in the compilation
var usings = compilation.SyntaxTrees.Select(tree => tree.GetRoot().DescendantNodes().OfType<UsingDirectiveSyntax>()).SelectMany(s => s).ToArray();
...
I'm currently writing an application that currently loads a project via Roslyn's workspace API, turns a specified C# file into a syntax tree then creates an in memory assembly form it, then eventually extracts the IL.
This is all working fine, however as soon as I reference any external libraries within the said C# file, the compilation fails as Roslyn doesn't know where to resolve those references.
Here's a simplified version of what I'm currently doing:
MetadataReference[] metaDatareferences = {
MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(Uri).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(DynamicAttribute).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(AssemblyMetadata).GetTypeInfo().Assembly.Location),
};
var sourceLanguage = new CSharpLanguage();
var syntaxTree = sourceLanguage.ParseText(sourceCode, SourceCodeKind.Regular);
var options = new CSharpCompilationOptions(
OutputKind.DynamicallyLinkedLibrary,
optimizationLevel: OptimizationLevel.Debug,
allowUnsafe: true
);
CSharpCompilation compilation = CSharpCompilation.Create("ExampleAssembly", options: options);
var stream = new MemoryStream();
var result = compilation.
AddReferences(metaDatareferences)
.AddSyntaxTrees(syntaxTree)
.Emit(stream);
// Success is false
if (!emitResult.Success)
{
foreach (var diagnostic in emitResult.Diagnostics)
{
Debug.WriteLine(diagnostic.ToString());
}
}
The output of the Debug.WriteLine is:
(1,7): error CS0246: The type or namespace name 'MediatR' could not be found (are you missing a using directive or an assembly reference?)
(9,32): error CS0246: The type or namespace name 'Mediator' could not be found (are you missing a using directive or an assembly reference?)
And the file my Roslyn project is reading is simply this:
using MediatR;
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
var mediator = new Mediator(null, null);
}
}
}
My question is, does Roslyn provide an API for automatically load any dependencies a file may have? I was hopeful that the Roslyn workspace would allow this to be done, but I've not been able to find anything.
If the MediatR console project is a project.json project, then you can use ProjectJsonWorkspace from "Microsoft.DotNet.ProjectModel.Workspaces": "1.0.0-preview2-1-003177". You can point it at your project.json and get a Compilation object, this will have done all the hard work for you of getting the project references, file references, etc... Then you can just emit your IL from here.
Here is an example:
var compilation = new ProjectJsonWorkspace(#"PathToYour\project.json").CurrentSolution.Projects.First().GetCompilationAsync().Result;
var stream = new MemoryStream();
var emitResult = compilation.Emit(stream);
Or if you need total control, you could continue to use CSharpCompilation.Create, copying in what you need from the compilation object here, and passing in a SyntaxTree.
Hope that helps.
I'm having an issue when compiling text into dynamic objects at runtime.
I wrote a simple piece of code to compile the text:
public class CompileFactory
{
public dynamic Compile(String classCode, String mainClass, Object[] requiredAssemblies)
{
CSharpCodeProvider provider = new CSharpCodeProvider(new Dictionary<string, string>
{
{ "CompilerVersion", "v4.0" }
});
CompilerParameters parameters = new CompilerParameters
{
GenerateExecutable = true, // Create a dll
GenerateInMemory = true, // Create it in memory
WarningLevel = 3, // Default warning level
CompilerOptions = "/optimize", // Optimize code
TreatWarningsAsErrors = false // Better be false to avoid break in warnings
};
// Add all extra assemblies required
foreach (var extraAsm in requiredAssemblies)
{
parameters.ReferencedAssemblies.Add(extraAsm as string);
}
CompilerResults results = provider.CompileAssemblyFromSource(parameters, classCode);
if (results.Errors.Count != 0)
{
return "FAILED";
}
return results.CompiledAssembly.CreateInstance(mainClass); ;
}
}
This is how I am using the Compile method.
List<string> assemblies = new List<string>{"System.Net.Mail.dll", "System.Net.dll"};
dynamic obj = compile.Compile(fileText, pluginName, assemblies.ToArray());
As you can see I'm adding references to extra assemblies at some point. For some reason when I add using System.Net; to the text file, it will not be referenced and I get errors. The text I'm compiling is literally a .cs file saved as text. I thought of working around this by extracting the using * and adding them separately, however for when adding System.Net.Mail.dll, the metadata file cannot be found.
Has anyone experienced something similar? I really would like to just add the using * to the file and be ready with it.
Any input would be greatly appreciated.
The issue here is that System.Net.dll does not exist. You can check in which assembly a .Net type is by right clicking somewhere it is referenced and choosing "Go to definition". This will bring up a tab with the class definition "from metadata". At the top of this file, you've got a #region showing where this type comes from. In the case of a TcpClient, we can see this:
#region Assembly System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2\System.dll
#endregion
Change your call to Compile with "System.dll" instead of "System.Net.dll" and it should work just fine
Edit/Clarification: It is not possible to get an assembly name from a using statement.
I'm using Roslyn to emit a CSharpCompilation object in Visual Studio to a file. The DLL that is generated does not contain any assembly info other than the assembly metadata, and I'd like to add the version and sign it if possible. How can these be done with Roslyn?
You need to include source code which sets the Assembly* attributes just like in the VS C# project templates. If you have done that, the .NET version info is set. You can read that information with Reflection or tools like ILSpy.
That way Explorer won't show any version info in its property page. Explorer is only showing Win32 VersionInfo not .NET version info. You need to emit Win32 resource code with Rosyln to set these values. Luckily there's a method to auto generate the Win32 info from the .NET ones: CreateDefaultWin32Resources.
Here's a complete and working code sample:
public void VersionInfoExample()
{
// 1. Generate AssemblyInfo.cs-like C# code and parse syntax tree
StringBuilder asmInfo = new StringBuilder();
asmInfo.AppendLine("using System.Reflection;");
asmInfo.AppendLine("[assembly: AssemblyTitle(\"Test\")]");
asmInfo.AppendLine("[assembly: AssemblyVersion(\"1.1.0\")]");
asmInfo.AppendLine("[assembly: AssemblyFileVersion(\"1.1.0\")]");
// Product Info
asmInfo.AppendLine("[assembly: AssemblyProduct(\"Foo\")]");
asmInfo.AppendLine("[assembly: AssemblyInformationalVersion(\"1.3.3.7\")]");
var syntaxTree = CSharpSyntaxTree.ParseText(asmInfo.ToString(), encoding: Encoding.Default);
// 2. Create compilation
string mscorlibPath = typeof(object).Assembly.Location;
MetadataReference mscorlib = MetadataReference.CreateFromFile(mscorlibPath, new MetadataReferenceProperties(MetadataImageKind.Assembly));
CSharpCompilationOptions options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
CSharpCompilation compilation = CSharpCompilation.Create("Test.dll",
references: new[] { mscorlib },
syntaxTrees: new[] { syntaxTree },
options: options);
// 3. Emit code including win32 version info
using (MemoryStream dllStream = new MemoryStream())
using (MemoryStream pdbStream = new MemoryStream())
using (Stream win32resStream = compilation.CreateDefaultWin32Resources(
versionResource: true, // Important!
noManifest: false,
manifestContents: null,
iconInIcoFormat: null))
{
EmitResult result = compilation.Emit(
peStream: dllStream,
pdbStream: pdbStream,
win32Resources: win32resStream);
System.IO.File.WriteAllBytes("Test.dll", dllStream.ToArray());
}
}