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

Cannot use MSBuild API from a dotnet-CLI project tool #1097

Closed
natemcmaster opened this issue Sep 27, 2016 · 29 comments
Closed

Cannot use MSBuild API from a dotnet-CLI project tool #1097

natemcmaster opened this issue Sep 27, 2016 · 29 comments
Assignees
Labels

Comments

@natemcmaster
Copy link
Contributor

Using MSBuild API for in-proj project evaluation in a dotnet-CLI project tool throws this excpetion:

Unhandled Exception: System.TypeInitializationException: The type initializer for 'BuildEnvironmentHelperSingleton' threw an exception. ---> System.InvalidOperationException: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio.
   at Microsoft.Build.Shared.ErrorUtilities.ThrowInvalidOperation(String resourceName, Object[] args)
   at Microsoft.Build.Shared.BuildEnvironmentHelper.Initialize()
   at Microsoft.Build.Shared.BuildEnvironmentHelper.BuildEnvironmentHelperSingleton..cctor()

IIUC the problem is that BuildEnvironmentHelperSingleton looks in AppContext.BaseDirectory (and a few other locations) for MSBuild.exe. When dotnet-CLI invokes a project tool, the AppContext.BaseDirectory will be $(NuGetPackages)/.tools/$(ToolName)/$(ToolVersion)/netcoreapp1.0/. The only content NuGet/CLI will put into this directory is the *.deps.json file and project.lock.json file for the tool.

Using Microsoft.Build.Runtime 15.1.262-preview5.

cc @piotrpMSFT

@jeffkl jeffkl self-assigned this Sep 27, 2016
@jeffkl
Copy link
Contributor

jeffkl commented Sep 27, 2016

I'll need to know more about the layout of these project tools. Who is the best person to give me a primer in order to come up with a fix?

cc @eerhardt

@NTaylorMullen
Copy link

I'll ping you offline.

@rainersigwald
Copy link
Member

I see two options:

  1. Use the CLI's MSBuild toolset (this isn't currently easy to find, on purpose)
  2. Distribute an MSBuild toolset with the tool.

The latter is how standalone applications would work. What's creating this $(NuGetPackages)/.tools layout, and why doesn't it match what would happen with a published application?

@natemcmaster
Copy link
Contributor Author

What's creating this $(NuGetPackages)/.tools layout, and why doesn't it match what would happen with a published application?

NuGet creates the .tools folder during restore. CLI reads the csproj/.tools folder to launch the tool. AFAIK there aren't any plans to change this. cref NuGet/Home#3462. cc @emgarten @eerhardt

Regardless of how a CLI tools finds or distributes MSBuild, we need some sort of API so that BuildEnvironmentHelper.Initialize knows where to find MSBuild.exe.

@eerhardt
Copy link
Member

eerhardt commented Oct 7, 2016

@piotrpMSFT - do you have any opinions on option 1 above?

@jeffkl - we've discussed before enabling MSBuild to "run out of a NuGet cache", in order for every tool to not need its own private copy of MSBuild. Has any progress been made in this area?

@rainersigwald
Copy link
Member

@eerhardt The blockers for making MSBuild run out of the NuGet cache remain: either smarter loading behavior (load our assemblies out of a folder whose layout we totally control) or a full-featured "find files from the nuget cache" feature baked into the framework. Neither seems forthcoming.

@natemcmaster
Copy link
Contributor Author

There are possible changes coming soon to NuGet/CLI which may solve this issue independently of changes to MSBuild.

cc @yishaigalatzer @rrelyea - the discussion we had about location of a CLI tool's .deps.json file.

@eerhardt
Copy link
Member

In our meeting this morning, we discussed this and the answer was:

The CLI will set an environment variable when invoking a tool that will point to the location of it's MSBuild install. The CLI Tool can read this environment variable to find out the path to MSBuild's installation.

@mishra14
Copy link

🔔 Any update on this? Running into the same issue.

@natemcmaster
Copy link
Contributor Author

In preview3, the SDK will set the env variable MSBUILD_EXE_PATH to the location of MSBuild.dll.

FWIW, in most cases, CLI tools are better off invoking "dotnet-msbuild" with a custom target instead of invoking MSBuild APIs directly.

@mishra14
Copy link

Got it to work, thanks to @rainersigwald :)

In powershell -

  1. $ENV:MSBUILD_EXE_PATH="C:\Program Files\dotnet\sdk\1.0.0-preview4-004079\MSBuild.dll"
  2. $ENV:MSBUILDEXTENSIONSPATH="C:\Program Files\dotnet\sdk\1.0.0-preview4-004079"

Here C:\Program Files\dotnet\sdk\1.0.0-preview4-004079 is the install path for dotnet sdk on my machine

@TheRealPiotrP
Copy link
Contributor

@mishra14 this is very fragile. The code makes assumptions on where CLI installs MSBuild.dll which are quite certain to be broken in upcoming releases. As @natemcmaster says, the CLI already has an environment variable which it passes to processes it creates to identify the msbuild exe path. Even this should only be used in critical scenarios where invoking dotnet msbuild simply won't work.

I know the self-created environment variable you suggest is straightforward and works at the moment, but do expect it to fail from release to release.

@mishra14
Copy link

@piotrpMSFT : @rainersigwald mentioned that the workaround $ENV:MSBUILDEXTENSIONSPATH="C:\Program Files\dotnet\sdk\1.0.0-preview4-004079" is needed due to a bug in current msbuild release but should be fixed in the next release (rc2?).

$ENV:MSBUILD_EXE_PATH="C:\Program Files\dotnet\sdk\1.0.0-preview4-004079\MSBuild.dll" on the otherhand should be set by dotnet sdk. But did not on my machine. Maybe, I am missing something?

@TheRealPiotrP
Copy link
Contributor

It's a subtlety. Dotnet SDK does not set any persistent environment variables. It only passes some environment variables to processes that it itself creates. Tools that need access to this value from the CLI need to be invoked by the CLI, giving the product the opportunity to redefine its own internal layout/implementation without breaking existing extensions :)

@rainersigwald
Copy link
Member

@piotrpMSFT It's worse than that. When you're developing the tool, you can't rely on the CLI setting that variable, because it's not set in dotnet run invocations, just tool invocations. Once the tool is packaged, referred to as a tool, and run through the CLI, it should get the CLI's preferred MSBUILD_EXE_PATH. But is there an easy way to edit-compile-debug in that environment?

(the need for MSBUILDEXTENSIONSPATH is fixed by #1336)

@jeffkl
Copy link
Contributor

jeffkl commented Nov 18, 2016

@mishra14 using dotnet run should work if your app is a netcoreapp1.0. Are you getting error when running your app via dotnet run or when you run it as a tool?

Are you getting the error that MSBuild cannot find itself?

System.InvalidOperationException: Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio.

Or are you getting a different error?

The dotnet run scenario should work because we look in the AppContext.BaseDirectory and should be able to locate assembly dependencies via NuGet. However, as a tool, the AppContext.BaseDirectory is where the tool marker was written to and is not correct. But I believe the dotnet CLI will set MSBUILD_EXE_PATH when the tool is run which means it should work too. I need more info on what error you're getting...

@mishra14
Copy link

@jeffkl : I have dotnet core sdk preview4 installed.

PS C:\Users\anmishr\Documents\visual studio 2017\Projects\ConsoleApp1\ConsoleApp1> dotnet --info
.NET Command Line Tools (1.0.0-preview4-004079)

Product Information:
 Version:            1.0.0-preview4-004079
 Commit SHA-1 hash:  43dfa6b8ba

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.14393
 OS Platform: Windows
 RID:         win10-x64

Are you getting the error that MSBuild cannot find itself? Yes

@TheRealPiotrP
Copy link
Contributor

@livarcocc can comment on where we expose the env var. If adding it to run prevents folks from assuming things about the CLI install layout, maybe we need to do that...

@jeffkl
Copy link
Contributor

jeffkl commented Nov 18, 2016

@mishra14 are you getting the error when you do dotnet run? Can you check to see if files like MSBuild.dll, Microsoft.Common.targets are in your output folder.

@livarcocc
Copy link
Contributor

We only expose it in the ProjectToolsCommandResolver and ProjectDependenciesCommandResolver right now. I think exposing it during run may make sense for people developing tools.

@mishra14
Copy link

@jeffkl :

are you getting the error when you do dotnet run?: Yes on dotnet run and also from within dev15.

Can you check to see if files like MSBuild.dll, Microsoft.Common.targets are in your output folder: No. I thinks that why I needed to set the env vars. So that dotnet could pick up the correcxt place where msbuild exists.

@jeffkl
Copy link
Contributor

jeffkl commented Nov 18, 2016

@mishra14 What packages are you referencing? The Microsoft.Build.Runtime package should place stuff in your output folder so that dotnet run works. If those files aren't there, this might be a bug in NuGet.

@mishra14
Copy link

@jeffkl : I do have runtime.

<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp1.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="**\*.cs" />
    <EmbeddedResource Include="**\*.resx" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Build.Runtime">
      <Version>15.1.0-preview-000370-00</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.NETCore.App">
      <Version>1.0.1</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.NET.Sdk">
      <Version>1.0.0-alpha-20161104-2</Version>
      <PrivateAssets>All</PrivateAssets>
    </PackageReference>
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

@jeffkl
Copy link
Contributor

jeffkl commented Nov 18, 2016

@emgarten is this a bug in NuGet? The project.assets.json has the correct stuff:

"Microsoft.Build.Runtime/15.1.0-preview-000370-00": {
  "type": "package",
  "dependencies": {
    "Microsoft.Build": "[15.1.0-preview-000370-00]",
    "Microsoft.Build.Framework": "[15.1.0-preview-000370-00]",
    "Microsoft.Build.Tasks.Core": "[15.1.0-preview-000370-00]",
    "Microsoft.Build.Utilities.Core": "[15.1.0-preview-000370-00]"
  },
  "contentFiles": {
    "contentFiles/any/netcoreapp1.0/15.0/Microsoft.Common.props": {
      "buildAction": "None",
      "codeLanguage": "any",
      "copyToOutput": true,
      "outputPath": "15.0/Microsoft.Common.props"
    },
    "contentFiles/any/netcoreapp1.0/MSBuild.dll": {
      "buildAction": "None",
      "codeLanguage": "any",
      "copyToOutput": true,
      "outputPath": "MSBuild.dll"
    },
    "contentFiles/any/netcoreapp1.0/MSBuild.runtimeconfig.json": {
      "buildAction": "None",
      "codeLanguage": "any",
      "copyToOutput": true,
      "outputPath": "MSBuild.runtimeconfig.json"
    },
    "contentFiles/any/netcoreapp1.0/Microsoft.CSharp.CrossTargeting.targets": {
      "buildAction": "None",
      "codeLanguage": "any",
      "copyToOutput": true,
      "outputPath": "Microsoft.CSharp.CrossTargeting.targets"
    },
    // (etc)
  }
},

But the files are not in the output folder after doing dotnet build. Or is this logic part of the SDK?

C:\Users\jeffkl\Downloads\msbuildruntimerepro>dotnet new
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp1.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <Compile Include="**\*.cs" />
    <EmbeddedResource Include="**\*.resx" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.NETCore.App">
      <Version>1.0.1</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.NET.Sdk">
      <Version>1.0.0-alpha-20161104-2</Version>
      <PrivateAssets>All</PrivateAssets>
    </PackageReference>
+   <PackageReference Include="Microsoft.Build.Runtime">
+     <Version>15.1.0-preview-000370-00</Version>
+   </PackageReference>
  </ItemGroup>

  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
C:\Users\jeffkl\Downloads\msbuildruntimerepro>dotnet restore
  Restoring packages for C:\Users\jeffkl\Downloads\msbuildruntimerepro\msbuildruntimerepro.csproj...
  Writing lock file to disk. Path: C:\Users\jeffkl\Downloads\msbuildruntimerepro\obj\project.assets.json
  Generating MSBuild file C:\Users\jeffkl\Downloads\msbuildruntimerepro\obj\msbuildruntimerepro.csproj.nuget.g.targets.
  Generating MSBuild file C:\Users\jeffkl\Downloads\msbuildruntimerepro\obj\msbuildruntimerepro.csproj.nuget.g.props.
  Restore completed in 1124.0981ms for C:\Users\jeffkl\Downloads\msbuildruntimerepro\msbuildruntimerepro.csproj.

  NuGet Config files used:
      C:\Users\jeffkl\AppData\Roaming\NuGet\NuGet.Config

  Feeds used:
      https://api.nuget.org/v3/index.json

C:\Users\jeffkl\Downloads\msbuildruntimerepro>dotnet build /v:m
Microsoft (R) Build Engine version 15.1.0.0
Copyright (C) Microsoft Corporation. All rights reserved.

  msbuildruntimerepro -> C:\Users\jeffkl\Downloads\msbuildruntimerepro\bin\Debug\netcoreapp1.0\msbuildruntimerepro.dll

C:\Users\jeffkl\Downloads\msbuildruntimerepro>tree /f bin\Debug\netcoreapp1.0
Folder PATH listing
Volume serial number is 4E82-251B
C:\USERS\JEFFKL\DOWNLOADS\MSBUILDRUNTIMEREPRO\BIN\DEBUG\NETCOREAPP1.0
    msbuildruntimerepro.deps.json
    msbuildruntimerepro.dll
    msbuildruntimerepro.pdb
    msbuildruntimerepro.runtimeconfig.dev.json
    msbuildruntimerepro.runtimeconfig.json

No subfolders exist

@jeffkl
Copy link
Contributor

jeffkl commented Nov 18, 2016

Okay this is currently blocked by NuGet/Home#3683 because our contentFiles are not being copied to the output directory. The workaround is to set MSBUILD_EXE_PATH for now...

@rainersigwald
Copy link
Member

@jeffkl What's the current state of this for a tool? My understanding is that it should work fine since dotnet will set MSBUILD_EXE_PATH to its MSBuild root and allow a project to have standard dependencies on our DLLs and evaluate a project using the CLI's toolset--right? I think @natemcmaster thinks otherwise . . .

@natemcmaster
Copy link
Contributor Author

I haven't tested lately. We decided that ASP.NET Core CLI tools should not use MSBuild APIs for project evaluation. AFAIK the only tool using MSBuild object model programmatically is dotnet-prop (https://github.com/simonech/dotnet-prop).

Instead, ASP.NET Core CLI tools accomplish indirect project evaluation by abusing the imports from MSBuildProjectExtensionsPath . This allows us to inject targets into a project. See aspnet/DotNetTools#206 for a brief description of how dotnet-watch implements that.

@jeffkl
Copy link
Contributor

jeffkl commented Dec 8, 2016

I'm not sure, I should probably set up a repro so I can test solutions.

We didn't really want people taking a dependency on MSBUILD_EXE_PATH but it should fix the problem for now. Longer term we talked about NuGet being able to resolve assets other than reference assemblies. Another option is to put MSBuild.exe next to Microsoft.Build.dll in the appropriate package. I just need to take the time to set this up so I can test it all...

@natemcmaster
Copy link
Contributor Author

Ping on a really old thread. At this point, all of the tools I have created workaround this limitation by invoking a new MSBuild process instead of calling on MSBuild API directly. It's a less-than-ideal programming experience though.

I recently saw this: https://github.com/daveaglick/Buildalyzer. Is this kind of API something MSBuild would every provide as a 1st class thing? If not, I suugest we just close this as "wontfix" and invite tool authors to use something like https://github.com/daveaglick/Buildalyzer if they want to use MSBuild in-proc.

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

9 participants