Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BuildManager - Building a .vcxproj from a .NET Framework project using the SDK-style #4375

Open
joaopgrassi opened this issue May 14, 2019 · 5 comments
Labels

Comments

@joaopgrassi
Copy link
Member

This is more of a question as I'm more or less lost and couldn't find much online. I'll try to describe what I have and want to achieve: Currently, I have a solution with:

  • A C++ project (.vcxproj) - ProjectA
  • A .NET Framework class library ProjectB that programmatically builds ProjectA using BuildManager
  • A NET Framework MSTest Project - ProjectC that references ProjectB and run tests.

The C++ project is more or less a "template" project that creates artifacts that simulate IoT devices. It offers APIs so we can "emulate" a physical device. During tests (ProjectC) we build multiple versions of the ProjectA and invoke the native code. ProjectB exists only as a "builder", meaning the MSBuild code that uses BuildManager lives there.

Both .NET Projects referenced the Microsoft.Build.* from the GAC:

<Reference Include="Microsoft.Build, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />

And in app.config we had the usual binding redirects:

<dependentAssembly>
    <assemblyIdentity name="Microsoft.Build" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
    <bindingRedirect oldVersion="0.0.0.0-99.9.9.9" newVersion="15.1.0.0"/>
</dependentAssembly>

After I migrated them to the "new" SDK-style, I couldn't reference them anymore from the GAC, so I installed the NuGet Packages on ProjectB, which is the one that uses it:

<ItemGroup>
  <PackageReference Include="Microsoft.Build" Version="15.1.548" ExcludeAssets="runtime" />
  <PackageReference Include="Microsoft.Build.Utilities" Version="15.1.548" ExcludeAssets="runtime" />
</ItemGroup>

I also went ahead and deleted the app.config since, (I assumed) the binding redirects were being generated. I have the <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> set on both of them. But, when I ran the tests, I ran on several issues. I pretty much had the same issues as described in this blog post: https://daveaglick.com/posts/running-a-design-time-build-with-msbuild-apis

First I got The tools version "15.0" is unrecognized. Available tools versions are "12.0", "14.0", "2.0", "3.5", "4.0". and then I pretty much did what Dave did on the blog post, but still ran into many other issues. At one point I just gave up.

Next day, after sleeping over it, I started again from scratch - I migrated again the test project to the new SDK-style, this time using this handy tool: https://github.com/hvanbakel/CsprojToVs2017 and this time I left the binding redirects there, on app.config. Now, I have:

  • ProjectB references the Microsoft.Build.* NuGet packages version 15.9.20
  • ProjectC (the test project) references ProjectB, does not have any Microsoft.Build.* dependency but has the binding redirects for them on app.config.

Both projects have these binding redirects:

<dependentAssembly>
    <assemblyIdentity name="Microsoft.Build.Framework" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
    <bindingRedirect oldVersion="0.0.0.0-99.9.9.9" newVersion="15.1.0.0"/>
</dependentAssembly>
<dependentAssembly>
    <assemblyIdentity name="Microsoft.Build" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
    <bindingRedirect oldVersion="0.0.0.0-99.9.9.9" newVersion="15.1.0.0"/>
</dependentAssembly>
<dependentAssembly>
    <assemblyIdentity name="Microsoft.Build.Tasks.Core" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
    <bindingRedirect oldVersion="0.0.0.0-99.9.9.9" newVersion="15.1.0.0"/>
</dependentAssembly>
<dependentAssembly>
    <assemblyIdentity name="Microsoft.Build.Utilities.Core" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
    <bindingRedirect oldVersion="0.0.0.0-99.9.9.9" newVersion="15.1.0.0"/>
</dependentAssembly>

I ran the tests and everything works again.. and I'm confused. Which msbuild is being used now? The one from Visual Studio installation, or the one from the NuGet packages? I'm somewhat lost and not sure if this is the correct way of doing it. Seems a bit strange to have these binding redirects or is it just my stupidity?

Also: We use VS 2017 today but.. we want to upgrade to 2019. What would be the way to do that? Just change the ToolsVersion="15.0" to ToolsVersion="16.0" on the .vcxproj and upgrade the NuGet packages? What about binding redirects?

Thanks!

@rainersigwald
Copy link
Member

Can you look at the documentation about using MSBuildLocator? If that doesn't help, please let me know. If it does, I'd appreciate any thoughts you have about how we could have made it easier for you to find.

@joaopgrassi
Copy link
Member Author

joaopgrassi commented May 14, 2019

Hi @rainersigwald . I'm trying to use the MSBuildLocator and I can't. I created a sample project and I'm trying to follow the commits from the sample project.

  • Add the Microsoft.Build.* NuGet packages
  • Installed MSBuildLocator NuGet package
  • Make sure no MSBuild dll's are copied when the project is built
  • Added a call to MSBuildLocator.RegisterDefaults() on Main

The project doen't even run. I get this exception:

System.IO.FileNotFoundException: 'Could not load file or assembly 'Microsoft.Build, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.'

No binding redirects are specified.

Update: As soon as I add ExcludeAssets="runtime" I get this exception. If I remove it, and add DisableMSBuildAssemblyCopyCheck=true the project runs, but then it fails when I call MSBuildLocator.RegisterDefaults();. If I remove that, then it fails with the initial issue The tools version "15.0" is unrecognized. Available tools versions are "12.0", "14.0", "2.0", "3.5", "4.0". =/

If you want, I prepared this sample app on a branch: https://github.com/joaopgrassi/msbuild-cpp-designtime/tree/msbuildlocator. It has more or less my use case, and using the MSBuildLocator package.

@rainersigwald
Copy link
Member

Are there calls to MSBuild APIs in Main after RegisterDefaults? .NET tries to load assemblies when it first encounters a method that uses a type from the assembly, so you have to have the call to RegisterDefaults run before calling any method that uses MSBuild types. Unfortunately, we haven't been able to figure out a nice way to generate an understandable error for that (since we haven't had a chance to run any code yet). Although, now that I think about it: microsoft/MSBuildLocator#67 could help.

@dasMulli
Copy link
Contributor

So I played around with this and made it "work on my machine"..

See joaopgrassi/msbuild-cpp-designtime@master...dasMulli:master

  1. Updated MSBuild references to the latest 15.* stuff since the VS cpp targets used new intrinsic functions
  2. Used MSBuildLocator to find the VS 15 installation and add it (since I also have 16). Disabling the ExcludeAssets =runtime check (though it worked both with and without the ExcludeAssets=runtime)
  3. Removed calls to the custom tool resolving logic.
  4. Move the other stuff from Main() to a different method so MSBuildLocator can do its magic.

@joaopgrassi
Copy link
Member Author

joaopgrassi commented May 14, 2019

Thanks, guys for the help, really appreciate it. I didn't know that I wasn't supposed to have anything on my main block. Perhaps we can make that more clear (and state why) in the docs so others can avoid running into this.

@dasMulli, I looked at your changes and I applied them on the "msbuildlocator" branch (https://github.com/joaopgrassi/msbuild-cpp-designtime/tree/msbuildlocator). Your fork builds on top of my initial version, which has a lot of extra code to load the tools, find the SDK tools and so on., that I basically copied from that blog post I mentioned initially.

Ultimately it would be nice to not to have all that extra stuff. That's why I created the branch msbuildlocator that has the minimum code to build the project.

@rainersigwald this is my whole Main with some tweaks that I copied from @dasMulli fork

static void Main(string[] args)
{
	var vsVersion = MSBuildLocator.QueryVisualStudioInstances().First(i => i.Version.Major == 15);
	MSBuildLocator.RegisterInstance(vsVersion);
	BuildProject();
}

public static void BuildProject()
{
	var logBuilder = new StringBuilder();
	var logger = new ConsoleLogger(LoggerVerbosity.Normal, x => logBuilder.Append(x), null, null);

	var cppProjectPath = Path.GetFullPath(@"..\LockTools\LockTools.vcxproj");

	var globalProps = new Dictionary<string, string>()
	{
		{ "BuildProjectReferences", "false" },
	};

	var projectInstance = new ProjectInstance(cppProjectPath, globalProps, null);
	var requestData = new BuildRequestData(projectInstance, new[] { "Clean", "Build", "BuiltProjectOutputGroup" });

	var par = new BuildParameters()
	{
		DetailedSummary = true,
		Loggers = new[] { logger },
	};

	var buildResult = BuildManager.DefaultBuildManager.Build(par, requestData);

	if (buildResult.OverallResult != BuildResultCode.Success)
	{
		Console.WriteLine(logBuilder);
		throw new Exception($"Overall build result: {buildResult.OverallResult}");
	}

	var targetResult = buildResult.ResultsByTarget["BuiltProjectOutputGroup"];
	var artifact = targetResult.Items.Single().ToString();
	var artifactPdb = Path.ChangeExtension(artifact, ".pdb");
}

My findings:

  1. If I run this with ExcludeAssets="runtime" in the MSBuild NuGet packages I get:
System.IO.FileNotFoundException: 'Could not load file or assembly 'Microsoft.Build.Locator, 
Version=1.0.0.0, Culture=neutral, PublicKeyToken=9dff12846e04bfbd' or one of its dependencies. 
The system cannot find the file specified.'
  1. I then remove ExcludeAssets="runtime" and add <DisableMSBuildAssemblyCopyCheck>true</DisableMSBuildAssemblyCopyCheck> then it works. I can build the project and see the outputs.

  2. Just out of curiosity, I commented out the call to MSBuildLocator.RegisterInstance(vsVersion); from Main and it also works, which makes me wonder now which assembly it is using? The ones copied to the output?

  3. I investigated a little more and I removed the Microsoft.Build.Locator package completely. The build fails (loading project works) with:

The "CppClean" task could not be loaded from the assembly 
C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\VC\VCTargets\Microsoft.Build.CppTasks.Common.dll.
Could not load file or assembly 'Microsoft.Build.Utilities.Core, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' 
or one of its dependencies. The system cannot find the file specified. 
Confirm that the <UsingTask> declaration is correct, that the assembly and all its dependencies are available, 
and that the task contains a public class that implements Microsoft.Build.Framework.ITask.
Done building target "CoreCppClean" in project "LockTools.vcxproj" -- FAILED.

So, even if I don't call MSBuildLocator.RegisterInstance it works, but as soon as I remove the package it fails.

@AR-May AR-May added the triaged label Feb 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants