How to properly use ControlFlowGraph from roslyn code analysis in C# - c#

I cannot understand why I am getting an error (using VS2017) for the code in below related to not finding the class ControlFlowGraph which is supposed to be part of the package Microsoft.CodeAnalysis.FlowAnalysis:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.MSBuild;
using Microsoft.CodeAnalysis.FlowAnalysis;
namespace CodeAnalysisApp3
{
class Program
{
static async Task Main(string[] args)
{
// Attempt to set the version of MSBuild.
var visualStudioInstances = MSBuildLocator.QueryVisualStudioInstances().ToArray();
var instance = visualStudioInstances[0];
Console.WriteLine($"Using MSBuild at '{instance.MSBuildPath}' to load projects.");
// NOTE: Be sure to register an instance with the MSBuildLocator
// before calling MSBuildWorkspace.Create()
// otherwise, MSBuildWorkspace won't MEF compose.
MSBuildLocator.RegisterInstance(instance);
using (var workspace = MSBuildWorkspace.Create())
{
// Print message for WorkspaceFailed event to help diagnosing project load failures.
workspace.WorkspaceFailed += (o, e) => Console.WriteLine(e.Diagnostic.Message);
var solutionPath = args[0];
Console.WriteLine($"Loading solution '{solutionPath}'");
// Attach progress reporter so we print projects as they are loaded.
var solution = await workspace.OpenSolutionAsync(solutionPath, new ConsoleProgressReporter());
Console.WriteLine($"Finished loading solution '{solutionPath}'");
// TODO: Do analysis on the projects in the loaded solution
CSharpParseOptions options = CSharpParseOptions.Default
.WithFeatures(new[] { new KeyValuePair<string, string>("flow-analysis", "") });
var projIds = solution.ProjectIds;
var project = solution.GetProject(projIds[0]);
Compilation compilation = await project.GetCompilationAsync();
if (compilation != null && !string.IsNullOrEmpty(compilation.AssemblyName))
{
var mySyntaxTree = compilation.SyntaxTrees.First();
// get syntax nodes for methods
var methodNodes = from methodDeclaration in mySyntaxTree.GetRoot().DescendantNodes()
.Where(x => x is MethodDeclarationSyntax)
select methodDeclaration;
foreach (MethodDeclarationSyntax node in methodNodes)
{
var model = compilation.GetSemanticModel(node.SyntaxTree);
node.Identifier.ToString();
if (node.SyntaxTree.Options.Features.Any())
{
var graph = ControlFlowGraph.Create(node, model); // CFG is here
}
}
}
}
}
private class ConsoleProgressReporter : IProgress<ProjectLoadProgress>
{
public void Report(ProjectLoadProgress loadProgress)
{
var projectDisplay = Path.GetFileName(loadProgress.FilePath);
if (loadProgress.TargetFramework != null)
{
projectDisplay += $" ({loadProgress.TargetFramework})";
}
Console.WriteLine($"{loadProgress.Operation,-15} {loadProgress.ElapsedTime,-15:m\\:ss\\.fffffff} {projectDisplay}");
}
}
}
}
However, when I compile the above code I am getting the following error message with VS2017:
1>Program.cs(67,41,67,57): error CS0103: The name 'ControlFlowGraph' does not exist in the current context
1>Done building project "CodeAnalysisApp3.csproj" -- FAILED.
========== Rebuild All: 0 succeeded, 1 failed, 0 skipped ==========
Version used:
Microsoft (R) Visual C# Compiler version 4.8.3761.0
for C# 5

Based on my test, I find I can use class ControlFlowGraph.
I installed the following nugetpackage.
Microsoft.CodeAnalysis
Microsoft.Build.Locator
Then, you will see the following result.
Besides, I used .net framwork 4.6.1.

I was able to solve the problem when I used roslyn CodeAnalysis packages with the proper versions:
CodeAnalysis.CSharp.Workspaces (3.4.0)
CodeAnalysis.FlowAnalysis.Utilities (2.9.6)
CodeAnalysis.Workspaces.MSBuild (3.4.0)
The target framework is .NETFramework 4.7.2
A link to a closed issue created for this question on roslyn Github repo is here

Related

Codegeneration at runtime from a string to console exe is not working in C# .NET6

I have some code which must be able to generated a console application at runtime (Codegeneration with System.CodeDom). I did this already a lot, but in NET 6 now I am struggling with that and the new API. In the code below I try to compile simply from a string. See below the static class with method Start() which then should generates the application.
The compilations seems fine, no errors at the end. But when starting the generated AppCodegenerated.exe, it shows some reference exception with System.Runtime.
Please help, any Idea? Already researched a lot but could not find any useful solution..
//-
I used the Visual Studio 2022 / NET 6 and theses Nuget's:
using Basic.Reference.Assemblies;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using System.Text;
namespace CompilerSimplified
{
public static class Compiler
{
public static bool Start()
{
string FileName = "AppCodegenerated";
string ExePath = AppDomain.CurrentDomain.BaseDirectory + #"\" + FileName + ".exe";
string code = #"using System; Console.WriteLine(""Hello.""); Console.ReadLine(); ";
// ------- References -------------
// .net platform references
List<MetadataReference> References = new List<MetadataReference>();
foreach (var item in ReferenceAssemblies.Net60) // ReferenceAssemblies from Nuget: Basic.Reference.Assemblies;
References.Add(item);
// or tried this: loop manually through system platform
//string[] fileEntries = Directory.GetFiles(#"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\6.0.0\ref\net6.0\", "*.dll");
//foreach (string fileName in fileEntries)
// references.Add(MetadataReference.CreateFromFile(fileName));MetadataReference.CreateFromFile(fileName));
// ------- References END -------------
// delete existing file
if (File.Exists(ExePath))
File.Delete(ExePath);
// compiler options
CSharpCompilationOptions DefaultCompilationOptions =
new CSharpCompilationOptions(outputKind: OutputKind.ConsoleApplication, platform: Platform.AnyCpu)
.WithOverflowChecks(true).WithOptimizationLevel(OptimizationLevel.Release);
// encode soucre code
string sourceCode = SourceText.From(code, Encoding.UTF8).ToString();
// CSharp options
var parsedSyntaxTree = Parse(sourceCode, "", CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp10));
// compilation
var compilation = CSharpCompilation.Create(FileName, new SyntaxTree[] { parsedSyntaxTree }, references: References, DefaultCompilationOptions);
var result = compilation.Emit(ExePath);
// return
if (result.Success)
return true;
else
return false;
}
private static SyntaxTree Parse(string text, string filename = "", CSharpParseOptions options = null)
{
var stringText = SourceText.From(text, Encoding.UTF8);
return SyntaxFactory.ParseSyntaxTree(stringText, options, filename);
}
}
}
Above code runs fine without error and exports the AppCodegenerated.exe into the project /bin folder.
Execution of this generated AppCodegenerated.exe shows following on the output console:
Unhandled exception: System.IO.FileNotFoundException:
The file or assembly "System.Runtime, Version = 6.0.0.0, Culture = neutral,
PublicKeyToken = b03f5f7f11d50a3a" or a dependency on it was not found.
The system can not find the stated file.
It is not possible to codegenerate directly a console application like the initial approach above. One possible solution is to generate first a dll (what I mentioned above in the example code is working fine), and from there include that .dll into a .exe, from where the functionality can run.

How to find out Target framework name and version from exe?

I have some exe files which has been created using either .net framework 4.5 or .net core 2.1 or .net core 3.1.
I want to get framework name and version information from this DLL using only c# application.
I have written below piece of code which is beneficial and works great with DLL files but not with exe.
var dllInformation = Assembly.LoadFrom(#"D:\\MyProgram.dll");
Console.WriteLine(dllInformation.FullName);
Console.WriteLine(dllInformation.ImageRuntimeVersion);
Console.WriteLine(((TargetFrameworkAttribute)dllInformation.GetCustomAttributes(typeof(TargetFrameworkAttribute)).First()).FrameworkName);
I have also gone through these links but I didn't found them useful for exe files:
information from exe file
Determine .NET Framework version for dll
Please let me know if any suggestions available.
The following program should display the version of the assembly. The program
loads two assemblies during runtime using Assembly.LoadFrom method. 1) is a .NET Fx assembly and 2) is a .NET Core assembly. It loads both and displays the framework version without issues. This project is in github. If you are using the github project, you need to have .NET Core 3.1 installled.
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.Versioning;
namespace net007
{
class Program
{
static void Main(string[] args)
{
//A .net framwwork dll in the same output fodler
//as the current executable
var fxAssembly = Assembly.LoadFrom("fx.console.app.exe");
//A .net core dll in the same output fodler
//as the current executable
var netCoreAssembly = Assembly.LoadFrom("core.console.app.dll");
ShowFrameworkVersion(fxAssembly); //.NETFramework,Version=v4.7.2
ShowFrameworkVersion(netCoreAssembly);//.NETCoreApp,Version = v3.1
}
static void ShowFrameworkVersion(Assembly assembly)
{
var attributes = assembly.CustomAttributes;
foreach (var attribute in attributes)
{
if (attribute.AttributeType == typeof(TargetFrameworkAttribute))
{
var arg = attribute.ConstructorArguments.FirstOrDefault();
if (arg == null)
throw new NullReferenceException("Unable to read framework version");
Console.WriteLine(arg.Value);
}
}
}
}
}
You can use PowerShell to detect for the target framework version:
$path = "C:\your dll here.dll"
[Reflection.Assembly]::ReflectionOnlyLoadFrom($path).CustomAttributes |
Where-Object {$_.AttributeType.Name -eq "TargetFrameworkAttribute" } |
Select-Object -ExpandProperty ConstructorArguments |
Select-Object -ExpandProperty value
This is my full class to get an "Name.exe" target framework.
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Versioning;
public class TargetVersionChecker : MarshalByRefObject
{
public string GetTargetedFrameWork(string exePath)
{
Assembly fxAssembly;
try
{
fxAssembly = Assembly.ReflectionOnlyLoadFrom(exePath);
var targetFrameworkAttribute = fxAssembly.GetCustomAttributesData().FirstOrDefault(x => x.AttributeType == typeof(TargetFrameworkAttribute));
return targetFrameworkAttribute?.ConstructorArguments.FirstOrDefault().Value.ToString();
}
catch (Exception ex)
{
// I log here the error but is to specific to our system so I removed it to be more simple code.
return string.Empty;
}
}
}

Compile using babel in a C#/.NET project

Trying to use babel to compile some code in C#/.NET project, following the example https://babeljs.io/setup#installation for C#/.NET.
Installed the package React.Core using NuGet Package Manager (Install-Package React.Core) in a simple Hello world project (https://learn.microsoft.com/en-us/dotnet/core/tutorials/with-visual-studio-code).
using System;
using React;
//using React.Web;
//using React.TinyIoC;
//using React.Web.Mvc4;
//using React.Web.TinyIoC;
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
// Console.WriteLine("Hello World!");
var babel = ReactEnvironment.Current.Babel;
// Transpiles a file
// You can instead use `TransformFileWithSourceMap` if you want a source map too.
// var result = babel.TransformFile("foo.js");
// Transpiles a piece of code
var result = babel.Transform("class Foo { }");
Console.WriteLine(result);
}
}
}
Alls getting error at: Unhandled Exception: React.TinyIoC.TinyIoCResolutionException: Unable to resolve type: React.IReactEnvironment
at React.TinyIoC.TinyIoCContainer.ResolveInternal
following this post tried adding the package React.Web and React.Web.Mvc4 but still get the same error, am importing the wrong packages?? or what is it? the version of the packages React.Core and the other are 5.1.0

How do I compile a C# solution with Roslyn?

I have a piece of software that generates code for a C# project based on user actions. I would like to create a GUI to automatically compile the solution so I don't have to load up Visual Studio just to trigger a recompile.
I've been looking for a chance to play with Roslyn a bit and decided to try and use Roslyn instead of msbuild to do this. Unfortunately, I can't seem to find any good resources on using Roslyn in this fashion.
Can anyone point me in the right direction?
You can load the solution by using Roslyn.Services.Workspace.LoadSolution. Once you have done so, you need to go through each of the projects in dependency order, get the Compilation for the project and call Emit on it.
You can get the compilations in dependency order with code like below. (Yes, I know that having to cast to IHaveWorkspaceServices sucks. It'll be better in the next public release, I promise).
using Roslyn.Services;
using Roslyn.Services.Host;
using System;
using System.Collections.Generic;
using System.IO;
class Program
{
static void Main(string[] args)
{
var solution = Solution.Create(SolutionId.CreateNewId()).AddCSharpProject("Foo", "Foo").Solution;
var workspaceServices = (IHaveWorkspaceServices)solution;
var projectDependencyService = workspaceServices.WorkspaceServices.GetService<IProjectDependencyService>();
var assemblies = new List<Stream>();
foreach (var projectId in projectDependencyService.GetDependencyGraph(solution).GetTopologicallySortedProjects())
{
using (var stream = new MemoryStream())
{
solution.GetProject(projectId).GetCompilation().Emit(stream);
assemblies.Add(stream);
}
}
}
}
Note1: LoadSolution still does use msbuild under the covers to parse the .csproj files and determine the files/references/compiler options.
Note2: As Roslyn is not yet language complete, there will likely be projects that don't compile successfully when you attempt this.
I also wanted to compile a full solution on the fly. Building from Kevin Pilch-Bisson's answer and Josh E's comment, I wrote code to compile itself and write it to files.
Software Used
Visual Studio Community 2015 Update 1
Microsoft.CodeAnalysis v1.1.0.0 (Installed using Package Manager Console with command Install-Package Microsoft.CodeAnalysis).
Code
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.MSBuild;
namespace Roslyn.TryItOut
{
class Program
{
static void Main(string[] args)
{
string solutionUrl = "C:\\Dev\\Roslyn.TryItOut\\Roslyn.TryItOut.sln";
string outputDir = "C:\\Dev\\Roslyn.TryItOut\\output";
if (!Directory.Exists(outputDir))
{
Directory.CreateDirectory(outputDir);
}
bool success = CompileSolution(solutionUrl, outputDir);
if (success)
{
Console.WriteLine("Compilation completed successfully.");
Console.WriteLine("Output directory:");
Console.WriteLine(outputDir);
}
else
{
Console.WriteLine("Compilation failed.");
}
Console.WriteLine("Press the any key to exit.");
Console.ReadKey();
}
private static bool CompileSolution(string solutionUrl, string outputDir)
{
bool success = true;
MSBuildWorkspace workspace = MSBuildWorkspace.Create();
Solution solution = workspace.OpenSolutionAsync(solutionUrl).Result;
ProjectDependencyGraph projectGraph = solution.GetProjectDependencyGraph();
Dictionary<string, Stream> assemblies = new Dictionary<string, Stream>();
foreach (ProjectId projectId in projectGraph.GetTopologicallySortedProjects())
{
Compilation projectCompilation = solution.GetProject(projectId).GetCompilationAsync().Result;
if (null != projectCompilation && !string.IsNullOrEmpty(projectCompilation.AssemblyName))
{
using (var stream = new MemoryStream())
{
EmitResult result = projectCompilation.Emit(stream);
if (result.Success)
{
string fileName = string.Format("{0}.dll", projectCompilation.AssemblyName);
using (FileStream file = File.Create(outputDir + '\\' + fileName))
{
stream.Seek(0, SeekOrigin.Begin);
stream.CopyTo(file);
}
}
else
{
success = false;
}
}
}
else
{
success = false;
}
}
return success;
}
}
}

Using Microsoft.Build.Evaluation to publish a database project (.sqlproj)

I need to be able to publish an SSDT project programmatically. I am looking at using Microsoft.Build to do so but can not find any documentation. It seems pretty simple to create the .dacpac, but how would I either publish to an existing database or at the very least to a .sql file. The idea is to have it do what it does when I right click on the project and select publish. It should compare with a selected database and generate an upgrade script.
This is what I have so far to create the .dacpac:
partial class DBDeploy
{
Project project;
internal void publishChanges()
{
Console.WriteLine("Building project " + ProjectPath);
Stopwatch sw = new Stopwatch();
sw.Start();
project = ProjectCollection.GlobalProjectCollection.LoadProject(ProjectPath);
project.Build();
//at this point the .dacpac is built and put in the debug folder for the project
sw.Stop();
Console.WriteLine("Project build Complete. Total time: {0}", sw.Elapsed.ToString());
}
}
Essentially I am trying to do what this MSBuild Example shows but in code.
Sorry that this is all I have. The doecumentation on the Build classes is very poor. Any help would be appreciated.
Thanks.
I had to do something similar to this because VSDBCMD which we previously used does not deploy to SQL Server 2012 and we needed to support it. What I found was the Microsoft.SqlServer.Dac assembly which seems to come as part of the SQL Server data tools (http://msdn.microsoft.com/en-us/data/tools.aspx)
When you run this on the client machine you will need the full version of the .NET 4 framework and the SQL CLR types and SQL T-SQL ScriptDOM pack found here: http://www.microsoft.com/en-us/download/details.aspx?id=29065
Code below is from a mockup I made for testing the new deployment method and deploys a given .dacpac file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SqlServer.Dac;
using System.IO;
namespace ConsoleApplication3
{
class Program
{
private static TextWriter output = new StreamWriter("output.txt", false);
static void Main(string[] args)
{
Console.Write("Connection String:");
//Class responsible for the deployment. (Connection string supplied by console input for now)
DacServices dbServices = new DacServices(Console.ReadLine());
//Wire up events for Deploy messages and for task progress (For less verbose output, don't subscribe to Message Event (handy for debugging perhaps?)
dbServices.Message += new EventHandler<DacMessageEventArgs>(dbServices_Message);
dbServices.ProgressChanged += new EventHandler<DacProgressEventArgs>(dbServices_ProgressChanged);
//This Snapshot should be created by our build process using MSDeploy
Console.WriteLine("Snapshot Path:");
DacPackage dbPackage = DacPackage.Load(Console.ReadLine());
DacDeployOptions dbDeployOptions = new DacDeployOptions();
//Cut out a lot of options here for configuring deployment, but are all part of DacDeployOptions
dbDeployOptions.SqlCommandVariableValues.Add("debug", "false");
dbServices.Deploy(dbPackage, "trunk", true, dbDeployOptions);
output.Close();
}
static void dbServices_Message(object sender, DacMessageEventArgs e)
{
output.WriteLine("DAC Message: {0}", e.Message);
}
static void dbServices_ProgressChanged(object sender, DacProgressEventArgs e)
{
output.WriteLine(e.Status + ": " + e.Message);
}
}
}
This seems to work on all versions of SQL Server from 2005 and up. There is a similar set of objects available in Microsoft.SqlServer.Management.Dac, however I believe this is in the previous version of DACFx and is not included in the latest version. So use the latest version if you can.
We need a way tell msbuild how and where to publish. Open your project in Visual Studio and begin to Publish it. Enter all needed info in the dialog, including your DB connection info and any custom SQLCMD variable values. Save Profile As... to a file, e.g. Northwind.publish.xml. (You may then Cancel.) Now we can use this and the project file to build and publish:
// Create a logger.
FileLogger logger = new FileLogger();
logger.Parameters = #"logfile=Northwind.msbuild.log";
// Set up properties.
var projects = ProjectCollection.GlobalProjectCollection;
projects.SetGlobalProperty("Configuration", "Debug");
projects.SetGlobalProperty("SqlPublishProfilePath", #"Northwind.publish.xml");
// Load and build project.
var dbProject = ProjectCollection.GlobalProjectCollection.LoadProject(#"Northwind.sqlproj");
dbProject.Build(new[]{"Build", "Publish"}, new[]{logger});
This can take awhile and may appear to get stuck. Be patient. :)
You should use SqlPackage.exe to publish your dacpac.
SqlPackage.exe
/Action:Publish
/SourceFile:C:/file.dacpac
/TargetConnectionString:[Connection string]
Also instead of passing too many parameters you could save your settings into DAC Publish Profile (this can be done from visual studio)
I wanted to build and publish a database based on a sqlproj file and log helpful information to console. Here's what I arrived at:
using Microsoft.Build.Framework;
using Microsoft.Build.Execution;
public void UpdateSchema() {
var props = new Dictionary<string, string> {
{ "UpdateDatabase", "True" },
{ "PublishScriptFileName", "schema-update.sql" },
{ "SqlPublishProfilePath", "path/to/publish.xml") }
};
var projPath = "path/to/database.sqlproj";
var result = BuildManager.DefaultBuildManager.Build(
new BuildParameters { Loggers = new[] { new ConsoleLogger() } },
new BuildRequestData(new ProjectInstance(projPath, props, null), new[] { "Publish" }));
if (result.OverallResult == BuildResultCode.Success) {
Console.WriteLine("Schema update succeeded!");
}
else {
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Schema update failed!");
Console.ResetColor();
}
}
private class ConsoleLogger : ILogger
{
public void Initialize(IEventSource eventSource) {
eventSource.ErrorRaised += (sender, e) => {
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(e.Message);
Console.ResetColor();
};
eventSource.MessageRaised += (sender, e) => {
if (e.Importance != MessageImportance.Low)
Console.WriteLine(e.Message);
};
}
public void Shutdown() { }
public LoggerVerbosity Verbosity { get; set; }
public string Parameters { get; set; }
}
This is for .NET 4 and above. Be sure and include assembly references to Microsoft.Build and Microsoft.Build.Framework.

Categories