dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
7.06k stars 1.17k forks source link

Cannot multi-target to netcoreapp < 3.0 and use WindowsDesktop SDK unconditionally #327

Closed nguerrera closed 5 years ago

nguerrera commented 5 years ago

If a project targets netcoreapp1.x or netcoreapp2.x in addition to netcoreapp3.0, and uses WindowsDesktop SDK in order to use WPF conditionally on 3.0, it fails to build. (Importing SDKs conditionally is possible, but difficult to get right and hard to maintain.)

The implicit framework reference is conditioned only on TargetFrameworkIdentifier being .NETCoreApp. It ignores TargetFrameworkVersion.

Minimal repro: dotnet build this:

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
  <PropertyGroup>
   <TargetFrameworks>netcoreapp1.0;netcoreapp3.0</TargetFrameworks>
   <UseWPF Condition="'$(TargetFramework)' == 'netcoreapp3.0'">true</UseWPF>
  </PropertyGroup>
</Project>

Actual behavior:

error NETSDK1073: The FrameworkReference 'Microsoft.WindowsDesktop.App' was not recognized

Expected behavior:

Successful build

cc @AArnott @dsplaisted @vatsan-madhavan

vatsan-madhavan commented 5 years ago

Looks to me like the project is using WindowsDesktop Sdk unconditionally.

This seems to work for me though.

<Project Sdk="Microsoft.NET.Sdk">
  <Import Sdk="Microsoft.NET.Sdk.WindowsDesktop" Project="Sdk.props" Condition="'$(TargetFramework)'=='netcoreapp3.0'"/>
  <PropertyGroup>
    <TargetFrameworks>netcoreapp1.0;netcoreapp3.0</TargetFrameworks>
    <UseWpf>true</UseWpf>
  </PropertyGroup>
  <Import Sdk="Microsoft.NET.Sdk.WindowsDesktop" Project="Sdk.targets" Condition="'$(TargetFramework)'=='netcoreapp3.0'"/>
</Project>
clairernovotny commented 5 years ago

This also relates to the composability of the WindowsDesktop SDK so things like my Extras can use them....

nguerrera commented 5 years ago

@vatsan-madhavan That is true. Consider that the project in the repro steps is easier to read and maintain, and this tracks a feature request to allow it. I think this is a natural thing that users would expect to work. Even I had to look at the code to figure it out, and the error today does not provide a lot of insight into a solution. Also, having a good error will be complicated to implement. I think it would be simpler and better to allow this.

nguerrera commented 5 years ago

Also, you will get duplicate import warnings with that:

warning MSB4011: "C:\Program Files\dotnet\sdk\3.0.100-preview-010149\Sdks\Microsoft.NET.Sdk\Sdk\Sdk.props" cannot be imported again. It was already imported at "D:\Temp\wpfmt\wpfmt.csproj". This is most likely a build authoring error. This subsequent import will be ignored.
clairernovotny commented 5 years ago

Related: https://github.com/dotnet/core-sdk/issues/181

nguerrera commented 5 years ago

To avoid the duplicate import warnings, you need:

<Project>
  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" Condition="'$(TargetFramework)' != 'netcoreapp3.0'" />
  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk.WindowsDesktop" Condition="'$(TargetFramework)' == 'netcoreapp3.0'" />

  <PropertyGroup>
   <TargetFrameworks>netcoreapp1.0;netcoreapp3.0</TargetFrameworks>
   <UseWPF>true</UseWPF>
  </PropertyGroup>

 <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" Condition="'$(TargetFramework)' != 'netcoreapp3.0'" />
 <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk.WindowsDesktop" Condition="'$(TargetFramework)' == 'netcoreapp3.0'" />
</Project>

I tried the less verbose <Sdk Name="..."> form which can inject top and bottom imports, but it didn't seem to evaluate the conditions correctly.

nguerrera commented 5 years ago

Updated title and description accordingly.

vatsan-madhavan commented 5 years ago

I agree with your point about maintainability @nguerrera, and I hadn't originally considered the problem with double-import of Sdk.props. It makes my workaround more complex and even harder to maintain, I think.

I feel like it would be odd to be able to write <Project Sdk=”Microsoft.NET.Sdk.WindowsDesktop”> + <TargetFramwork>netcoreapp2.0<TargetFramework> and have that project function as a regular .NET Core project (and not as a WPF/WinForms project).

We have seen real-world examples of developers inadvertently trying to build WPF applications but getting the Sdk wrong (using Microsoft.NET.Sdk instead of Microsoft.NET.Sdk.WindowsDesktop). If we don’t have a clear error/warning as we do now, I worry that we will start seeing complaints like “My netcoreapp2.0 WindowsDesktop Sdk WPF/WinFroms project is not building correctly - help!”. I’d like to avoid generating this support/bug tail if at all possible.

Would we be ok supporting this with a suppressible warning (assuming it can be implemented easily enough) – something like “Warning XXX – you are no longer building a WindowsDesktop Sdk/WPF/WinForms application because the TargetFramework is <netcoreapp2.0>. You can suppress this warning by setting UseWindowsDesktopSdkAsNetSdkOnUnsupportedFrameworks property to true” ?

vatsan-madhavan commented 5 years ago

cc @merriemcgaw

AArnott commented 5 years ago

I'd rather not have to set some new property to suppress a warning. Why not just use NoWarn?

But ya, the error that is printed by the build gives no indication regarding the target framework being built at the time. This is in fact a general problem with multi-targeting today: when a warning/error is printed, MSBuild only tells us the project that produced it--not it's target framework, which is all-too-relevant for many issues, especially this one. I honestly thought it was the netcoreapp3.0 target that was complaining since that was the one I was working on upgrading when I hit this. Until such time as MSBuild fixes this so that $(TargetFramework) is included in all errors/warnings, whatever comes up when the user tries what is in the original repro steps should be very clear about what target framework is failing and what the user should do to correct the problem (assuming at least they are multitargeting).

Also: is it really necessary to even have a special SDK just for WindowsDesktop? Can't WPF/WinForms support be built into the regular SDK? I'm not sure which way is the right way here, but it seems that a single SDK would make the resolution to this much easier to identify.

vatsan-madhavan commented 5 years ago

NoWarn over a new property seems reasonable.

/cc @DustinCampbell

nguerrera commented 5 years ago

NoWarn today doesn't work for arbitrary MSBuild warnings, each tool has to consume it manually. There is a MSBuildWarningsAsMessages that does almost the same thing. I have argued NoWarn should have the same impact in informal conversation, but I don't think there's a tracking issue. cc @rainersigwald

stevenbrix commented 5 years ago

@nguerrera is this the right repo for this issue? if i understand this correctly, this belongs in the msbuild repo, yeah?

vatsan-madhavan commented 5 years ago

Related: #255