How can I use an SLN file with MSBuild programmatically from C#? - c#

I am trying to create a custom MSBuild script in C#, using the newer Microsoft.Build.Evaluation API. The problem I have is that this newer API does not support .sln files. The older deprecated Microsoft.Build.Engine API does support .sln files, but I'd like to use the newer one because 1) it's not deprecated and 2) there seems to be more online documentation and usage to reference. I've seen that MSBuild can create a .metaproj file when is successfully compiles a solution, when this assignment is made in CMD: set MSBuildEmitSolution=1. I need the .metaproj file to be able to compile the solution in the first place. Is there anything in the API for converting .sln to .metaproj? Is there any library out there for parsing .sln files?

I figured it out after more searching. Finding good examples online is a little difficult because of the two different versions of the MSBuild API, and the popularity of just running MSBuild from the command line.
Here is the code that is now working for me, using the newer MSBuild API:
var pc = new ProjectCollection();
var parameters = new BuildParameters(pc)
{
Loggers = new[] { _logger } //Instance of ILogger instantiated earlier
};
var request = new BuildRequestData(
projectFullPath: pathToMySlnFile, //Solution file path
globalProperties: myPropertyDictionary,
toolsVersion: null,
targetsToBuild: myTargetsArray,
hostServices: null,
flags: BuildRequestDataFlags.ProvideProjectStateAfterBuild);
var buildResult = BuildManager.DefaultBuildManager.Build(parameters, request);

Related

Enumerate Files in a .csproj (dotnet core)

I am working on a build tool in C# that should work with Visual Studio / MSBuild. I want to enumerate all files that are part of a C# project. The project format is the new (.NET Core) .csproj.
The documentation describing the Project System points at using MSBuild (file format) or Common Project System (project tree). I'm unfamiliar with both APIs. Looking at documentation for those respective projects is not immediately helpful.
As the expert probably knows, the new .csproj file does not list every file that is implicitly part of the project. On the other hand it may list a 'linked' file that is outside the project folder. I want to make sure I get all files that are considered part of the project.
Ultimately I want to focus on a particular file type (.json), but I thought the general question was worth asking.
To sum up: How can I write a C# library that leverages the appropriate packages to (hopefully easily) enumerate all the files in a csproj?
Buildalyzer is the easiest package to use, and it targets .NETStandard 2.0 making it cross-platform. (Omnisharp does not currently offer a NuGet package for working with the workspace. And Microsoft.CodeAnalysis poses a challenge to get the correct references in place, and is limited to net46.)
using Buildalyzer;
private static IList<string> InlcudedProjectKeys = new[] { "None", "Compile", "Content", "EmbeddedResource" };
private static IEnumerable<string> EnumerateProjectFiles(string projectPath)
{
AnalyzerManager manager = new AnalyzerManager();
ProjectAnalyzer analyzer = manager.GetProject(projectPath);
AnalyzerResults results = analyzer.Build();
AnalyzerResult result = results.Single();
// If only interested in C# files, check out:
//string[] sourceFiles = result.SourceFiles;
IReadOnlyDictionary<string, ProjectItem[]> items = result.Items;
foreach (var item in items)
{
// Skip keys like ProjectReference that aren't for files
if (!InlcudedProjectKeys.Contains(item.Key))
continue;
ProjectItem[] projectItems = item.Value;
foreach (var projectItem in projectItems)
{
// The item spec for files will be the path relative to the project directory
yield return projectItem.ItemSpec;
}
}
}
And for bonus points, to get only *.json files:
var jsonFiles = EnumerateProjectFiles(projectPath)
.Where(path => path.EndsWith(".json"))
.ToArray();
Thanks Hitesh for linking to relevant resources.
You can use Roslyn Analyzer Libraries to load csproject and access it's content as well as properties in program. you can follow instructions from this previous SO post, or use OpenProjectAsync(projectFilePath) method to load instance of Project class in Microsoft.CodeAnalysis namespace.
using Microsoft.CodeAnalysis.MSBuild;
AnalyzerManager manager = new AnalyzerManager();
ProjectAnalyzer analyzer = manager.GetProject(#"C:\MyCode\MyProject.csproj");
You can find more information on roslyn at Github.

Opening a solution with msbuildworkspace gives diagnostics errors without details

I am trying to analyse a solution with Roslyn, with MSBuildWorkspace.
The solution is a new solution, with 2 class library projects in them, one referencing the other.
They are created in Visual Studio 2017, .Net 4.6.2.
When I open the solution, I receive two generic errors in workspace.Diagnostics, both are :
Msbuild failed when processing the file 'PathToProject'
There is nothing more in the diagnostics or output window, to indicate WHY it failed to process the project file.
The code for opening the solution:
namespace RoslynAnalyse
{
class Program
{
static void Main(string[] args)
{
LocalAnalysis();
}
private static void LocalAnalysis()
{
var workspace = MSBuildWorkspace.Create();
var solution = workspace.OpenSolutionAsync(#"D:\Code\Roslyn\RoslynAnalyse\SolutionToAnalyse\SolutionToAnalyse.sln").Result;
var workspaceDiagnostics = workspace.Diagnostics;
}
}
}
The version of Microsoft.CodeAnalysis is 2.0.0.0.
Does anybody have any idea why MSBuild failed, how I can get more information ?
When MSBuildWorkspace fails to open a project or solution this way, it is almost always because the application using MSBuildWorkspace does not include the same binding redirects that msbuild.exe.config has in it.
MSBuild uses binding redirects to allow tasks (typically already compiled C# code using possibly different versions of msbuild API libraries) to all use the current msbuild API's. Otherwise, msbuild gets runtime load failures.
The solution is to add an app.config file to your project and copy the binding redirects (the assemblyBinding section of the msbuild.exe.config file) into your file.

Using Z3 with ASP.NET Core

Can the Microsoft Z3 .NET API handle .NET Core? We're using it in a scheduling algorithm for a school project, and we believe when the project was upgraded to .net core, z3 stopped working. We can't find any information on z3 being used with .net core.
Z3 uses code contracts, which are not available in .NET core. However, we have a dummy class that replaces them, and which comes with the source code, see src/api/dotnet/core/DummyContracts.cs.
At the moment, this is not tied into the rest of our build infrastructure, but you can build them thusly:
cd src/api/dotnet/core
dotnet restore
dotnet build
(Make sure you update your copy of the source code as I just committed a fix for the Core build.)
For Z3 to work in a .Net Core 2 project you need the following things:
The Microsoft.Z3.dll in your project and add a reference to it in the project. Place it in the project root if unsure.
The libz3.dll as well but this doesn't need to be referenced (won't work anyways).
Add the libz3.dll and z3.exe to your PATH, either through code or by your OS. (this part is the one often resulting in dll not found errors).
** My code in C#
// Convinient metod to decide OS. false => linux in this case.
public static bool IsWindows() =>
RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
private static void AddZ3ToProcessPath()
{
// We store our OS-dependent Z3 DLLs in diffrent folders in our root.
var solverZ3Path = IsWindows() ? "z3winx64_485" : "z3linuxx64_485";
var z3X64BinariesPath = "";
if (IsWindows())
{
z3X64BinariesPath = $"{solverZ3Path}"; // Windows friendly
}
else
{
z3X64BinariesPath =$"/{solverZ3Path}"; // Unix friendly
}
var path = Uri.UnescapeDataString(z3X64BinariesPath); // Escape chars
var name = "PATH"; // Add dlls to this environment variable
var target = EnvironmentVariableTarget.Process; // But only for this process and not entire machine or user
Environment.SetEnvironmentVariable(name, path, target);
}
What OS are you using, what version of .Net Core? Any link to code?

TuesPechkin unable to load DLL 'wkhtmltox.dll'

I've been using TuesPechkin for some time now and today I went to update the nuget package to the new version 2.0.0+ and noticed that Factory.Create() no longer resolved, so I went to read on the GitHub the changes made and noticed it now expects the path to the dll?
IConverter converter =
new ThreadSafeConverter(
new PdfToolset(
new StaticDeployment(DLL_FOLDER_PATH)));
For the past few hours I've tried almost all the paths I can think of, "\bin", "\app_data", "\app_start", etc and I can't seem to find or figure out what it wants for the path and what dll?
I can see the TuesPechkin dll in my bin folder and it was the first path I tried, but I got the following error:
Additional information: Unable to load DLL 'wkhtmltox.dll': The
specified module could not be found. (Exception from HRESULT:
0x8007007E)
Where is that dll and now can I get it as the library doesn't seem to contain it, I tried installing the TuesPechkin.Wkhtmltox.Win32 package but the dll still is nowhere to be found. Also I am using this in a asp.net website project so I assume that using the following should work for obtaining the path, right?
var path = HttpContext.Current.Server.MapPath(#"~\bin\TuesPechkin.dll");
Further information: https://github.com/tuespetre/TuesPechkin/issues/57
The Tuespechkin has a zip file as a resource in the Win32 and Win64 embedded packages for the 'wkhtmltox.dll' file.
What it does when you use the Win32 or Win64 Embedded package is unzips the file and places it in the directory that you specify.
I have been putting a copy of the wkhtmltox dll at the root portion of my web app directory and pointing the DLL_FOLDER_PATH to it using the server physical path of my web app to get to it.
According to the author, you must set the converter in a static field for best results.
I do that, but set the converter to null when I am finished using it, and that seems to work.
Tuespechkin is wrapper for the wmkhtmlox dll file.
The original file is written in C++ and so will not automatically be usable in C# or VB.NET or any of the other managed code domains.
The Tuespechkin.dll file DOES NOT contain a copy of 'wkhtmltox.dll'. You either have to use one of the other embedded deployment modules or install a copy of the 'wkhtmltox.dll' in your web app after downloading it from the internet. That is what I do, and it seems to work just fine.
I am using Team Foundation Server, and attempts to compile code after using the Tuespechkin routines will fail the first time because the 'wkhtmltox.dll' file gets locked, but all you have to do is simply retry your build and it will go through.
I had issues with the 32-bit routine not working in a 64-bit environment and the 64-bit environment not being testable on localhost. I went with the workaround I came up with after examining the source code for Tuespechkin and the Win32 and Win64 embedded deployment packages.
It works well as long as you specify a url for the input rather than raw html.
The older package didn't render css very well.
If you are using a print.aspx routine, you can create the url for it as an offset from your main url.
I don't have the source code I am using with me at this point to offset to your base url for your web application, but it is simply an offshoot of HttpRequest.
You have to use the physical path to find the .dll, but you can use a web path for the print routine.
I hope this answers your question a bit.
If you are getting this error -> Could not load file or assembly 'TuesPechkin.Wkhtmltox.Win64' or one of its dependencies. An attempt was made to load a program with an incorrect format.
In Visual Studio Go to -
Tools -> Options -> Projects and Solutions -> Web Projects -> Use the 64 bit version of IIS Express for web sites and projects.
I installed TuesPechkin.Wkhtmltox.Win64 Nuget package and used the following code in a singleton:
public class PechkinPDFConvertor : IPDFConvertor
{
IConverter converter =
new ThreadSafeConverter(
new RemotingToolset<PdfToolset>(
new Win64EmbeddedDeployment(
new TempFolderDeployment())));
public byte[] Convert(string html)
{
// return PechkinSync.Convert(new GlobalConfig(), html);
return converter.Convert(new HtmlToPdfDocument(html));
}
}
The web application then has to be run in x64 otherwise you will get an error about trying to load an x64 assembly in an x86 environment. Presumably you have to choose x64 or x86 at design time and use the corresponding nuget package, it would be nicer to choose this in the web.config.
EDIT: The above code failed on one server with the exact same message as yours - it was due to having not installed VC++ 2013. So the new code is running x86 as follows
try
{
string path = Path.Combine(Path.GetTempPath(), "MyApp_PDF_32");
Converter = new ThreadSafeConverter(
new RemotingToolset<PdfToolset>(
new Win32EmbeddedDeployment(
new StaticDeployment(path))));
}
catch (Exception e)
{
if (e.Message.StartsWith("Unable to load DLL 'wkhtmltox.dll'"))
{
throw new InvalidOperationException(
"Ensure the prerequisite C++ 2013 Redistributable is installed", e);
}
else
throw;
}
If you do not want run the installer for wkhtmltox just to get the dll, you can do the following:
As #Timothy suggests, if you use the embedded version of wkhtmltox.dll from TuesPechkin, it will unzip it and place it in a temp directory. I copied this dll and referenced it with the StaticDeployment option without any issues.
To find the exact location, I just used Process Monitor (procmon.exe). For me it was C:\Windows\Temp\-169958574\8\0.12.2.1\wkhtmltox.dll
In my case, I am deploying on a 64-bit VPS then I got this error. I have solved the problem by installing the wkhtmltopdf that I downloaded from http://wkhtmltopdf.org/downloads.html. I chose the 32-bit installer.
In my case, I have solved the problem by installing the Wkhtmltox for win32 at https://www.nuget.org/packages/TuesPechkin.Wkhtmltox.Win32/
This error: Unable to load DLL 'wkhtmltox.dll': The specified module could not be found. (Exception from HRESULT: 0x8007007E) is returned in two situations:
1- Deploy dependency not installed:
For solve this, you can install nuget package "TuesPechkin.Wkhtmltox.Win64" and use this code (for WebApplications running in IIS):
IConverter converter =
new ThreadSafeConverter(
new RemotingToolset<PdfToolset>(
new Win64EmbeddedDeployment(
new TempFolderDeployment())));
// Keep the converter somewhere static, or as a singleton instance!
// Do NOT run the above code more than once in the application lifecycle!
byte[] result = converter.Convert(document);
In runtime this code will copy the dependency "wkhtmltox.dll" in a temporary directory like: "C:\Windows\Temp\1402166677\8\0.12.2.1". It's possible to get the destination of file using:
var deployment = new Win64EmbeddedDeployment(new TempFolderDeployment());
Console.WriteLine(deployment.Path);
2- Microsoft Visual C++ 2013 Redistributable not installed:
As described here:
https://github.com/tuespetre/TuesPechkin/issues/65#issuecomment-71266114, the Visual C++ 2013 Runtime is required.
The solution from README is:
You must have Visual C++ 2013 runtime installed to use these packages. Otherwise, you will need to download the MingW build of wkhtmltopdf and its dependencies from their website and use that with the library. https://github.com/tuespetre/TuesPechkin#wkhtmltoxdll
or, you can install the Microsoft Visual C++ 2013 Redistributable:
choco install msvisualcplusplus2013-redist
Here is AnyCpu version, also support iis-base or winform application
using TuesPechkin.Wkhtmltox.AnyCPU;
...
var converter = PDFHelper.Factory.GetConverter();
var result = converter.Convert(This.Document);
Reference : https://github.com/tloy1966/TuesPechkin
Installing the Visual C++ Redistributable for Visual Studio 2013 resolved the error for me.
https://www.microsoft.com/en-us/download/details.aspx?id=40784

Code Contracts and Programmatically compiling projects

I have a bunch of legacy web projects that are compiled using a custom build application that we wrote. This because the depedencies were complex and the code... is... less than optimal. On the plus side it has recently been upgraded to .NET 4.5 and we are using Visual Studio 2013.
I recently started using code contracts in one of our other projects and really like the SoC it provides. I want to implement this in the legacy code that we are maintaining for new features (without doing a complete re-write). In development I have gotten it to work as long as I do a build from within VS and copy the compiled DLL files into the BIN folder of the web application (aps.net 32bit latest IIS).
I want to include the contracts in our release build as well using our Build tool. Otherwise I would have to use the tool and then use VS to create a release which is a 2 step process. I have complete source control over the tool (which is executed locally on my DEV PC) but I cannot get it to produce output with the code contracts. I read the Code Contract documentation and they mention the build script Microsoft.CodeContracts.targets and I have tried to add this into the build code without success. Admittedly my knowledge of the build process, build scripts, etc. is lacking at best. Any help / pointers in how to get this to work would be greatly appreciated. I would still like to normally compile my code in VS and have contracts work as well as use the build tool and and contracts work.
(i have not manually changed any of the CSPROJ files)
Build Code:
Build code is done using Microsoft.Build and Microsoft.Build.Framework assemblies.
var pc = new ProjectCollection();
var buildLogger = new FileLogger();
var logFilePath = #"MyLog.log";
buildLogger.Parameters = string.Format("logfile={0}", logFilePath);
var binDirectory = Path.Combine(outputWebDir, "Bin");
var globalProperty = new Dictionary<string, string>();
globalProperty.Add("OutputPath", binDirectory);
globalProperty.Add("Configuration", publishParams.ReleaseMode);
globalProperty.Add("Platform", publishParams.PlatformMode);
if (projectFileToPublish.IndexOf("SOTAQ.WebPoint.Web.csproj", StringComparison.OrdinalIgnoreCase) < 0)
globalProperty.Add("SolutionDir", publishParams.SparcoSolutionPath);
if (isWebSite)
{
globalProperty.Add("WebProjectOutputDir", outputWebDir);
globalProperty.Add("DeployOnBuild", "True");
}
globalProperty.Add("CodeContractsInstallDir", #"C:\Program Files (x86)\Microsoft\Contracts\");
globalProperty.Add("CodeContractRewriteCommand", #"C:\Program Files (x86)\Microsoft\Contracts\Bin\ccrewrite.exe");
globalProperty.Add("CodeContractAnalysisTargets", #"C:\Program Files (x86)\Microsoft\Contracts\MsBuild\v4.0\Microsoft.CodeContractAnalysis.targets");
globalProperty.Add("CodeContractsCCDocgenCommand", #"C:\Program Files (x86)\Microsoft\Contracts\Bin\ccdocgen.exe");
globalProperty.Add("CodeContractsCCRefgenCommand", #"C:\Program Files (x86)\Microsoft\Contracts\Bin\ccrefgen.exe");
string[] targets;
if (isWebSite)
targets= new[] { "Build", "ResolveReferences", "_CopyWebApplication" };
else
targets = new[] { "Build", "ResolveReferences" };
var buildRequestData = new BuildRequestData(projectFileToPublish, globalProperty, null, targets, null);
var buildParams = new BuildParameters(pc);
buildParams.Loggers = new[] {buildLogger};
BuildResult buildResult = BuildManager.DefaultBuildManager.Build(buildParams, buildRequestData);
Any help is greatly appreciated!!
Thanks in advance,
-Igor
After reading and re-reading the diagnostic output from Visual Studio and comparing it to my build project and trying many various possible solutions I found online the answer was to add the variable CodeContractsEnableRuntimeChecking with value true.
globalProperty.Add("CodeContractsEnableRuntimeChecking", "true");
Once that was added everything worked as expected.
-Igor

Categories