dotnet / sdk

Core functionality needed to create .NET Core projects, that is shared between Visual Studio and CLI

Home Page:https://dot.net/core

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

dotnet cli passing -target:Restore to MSBuild instead of -restore

jonathanpeppers opened this issue · comments

As seen in: dotnet/maui-samples#52

We have a multi-targeted project with:

<TargetFrameworks>net6.0-android;net6.0-ios</TargetFrameworks>

To deploy to an Android device/emulator (and not iOS), you can run:

> dotnet build -t:Run -p:TargetFramework=net6.0-android

With .NET 6.0.100-alpha.1.21064.27, this command fails with:

error MSB4057: The target "Run" does not exist in the project.

The MSBuild arguments the dotnet cli is passing seem odd:

-target:Restore -t:Run

With .NET 6.0.100-alpha.1.20562.2, it used to pass:

-restore -t:Run

In fact, you can workaround the problem by doing:

> dotnet build -t:Run -p:TargetFramework=net6.0-android --no-restore

I tried creating other, simpler projects to reproduce this issue, but a console app with TargetFrameworks=net5.0;net6.0 works fine. I always see -restore being passed.

I think it is something to with .NET workloads and multi-targeting at the same time?

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@sfoslund Can you take a look?

@jonathanpeppers can you give some more information on how to repro this? The binlogs you provided are on two different platforms, does this only repro on windows or mac?

@sfoslund I think it happens on all platforms. My Mac has the older 6.0.100-alpha.1.20562.2 installed, so that is where I could grab the working .binlog the easiest.

You can compare main here:

https://github.com/xamarin/net6-samples

vs the branch on my PR:

dotnet/maui-samples#52

There are installation instructions on the README.md, but ping me on Teams if I can help further, thanks!

Okay I think I understand this issue now but I want to verify my understanding of the history with @dsplaisted.

The basic issue is with RestoringCommand, which has logic to decide if we can restore with -restore included in the msbuild arguments or if we need to use a separate command to restore. The deciding factor is if the target framework is specified on the command line (by either -f, --framework, or -p:TargetFramework=...). If it is, then we can't use -restore, for the reasons explained in this comment.

It looks like before the System.CommandLine change there was a bug and we were using -restore when the target framework was specified on the command line (see the command line args in your working binlog: -restore -consoleloggerparameters:Summary HelloForms -p:TargetFramework=net6.0-android...). This was working because there weren't any dependent projects with different target frameworks, but if there had been then the command line target framework would have overridden the dependency projects' tfm's and restore would have failed.

So theoretically I think switching to system.commandline "fixed" this bug in that now we try to use a separate restore command when the target framework is specified on the command line. However, this has created a bug here such that we pass the -t:run option to the separate restore command and it fails because we didn't specify a target framework.

@dsplaisted does this make sense to you? Do you know of any changes in the -restore behavior in regards to target frameworks since Nick's comment (which is from 2017).

After discussing with @dsplaisted, we do want the -target:restore behavior instead of -restore when the target framework is specified on the command line.

@jonathanpeppers is there a reason you need to specify the run target while building? dotnet run -f net6.0-android should be logically the same as it includes an implicit restore/build.

I still get this error with dotnet run:

> cd HelloForms
> dotnet run -f net6.0-android
Microsoft (R) Build Engine version 16.9.0-preview-21063-04+27981f15c for .NET
Copyright (C) Microsoft Corporation. All rights reserved.

  Determining projects to restore...
  All projects are up-to-date for restore.
C:\src\net6-samples\HelloForms\HelloForms.csproj : error MSB4057: The target "Run" does not exist in the project.

I also tried adding --no-restore to the dotnet run command, but I'm not sure if that actually does anything.

These commands both work for me:

> dotnet build -t:Run -f net6.0-android --no-restore
> dotnet build -t:Run -p:TargetFramework=net6.0-android --no-restore

Yes, I'm seeing that error with 6.0.100-alpha.1.20562.2 (the version you're currently using which doesn't have the system.commandLine change). It's odd because in the binlog I can see the run target from Microsoft.NET.Sdk.targets is being imported and overriding the MSBuild target. I'm not able to repro this issue with simpler projects, do you have any custom logic for running?

I just updated to 6.0.100-preview.1.21081.5 and seeing the same thing there, too.

One thing, is we completely replace the Run target with our own:

https://github.com/xamarin/xamarin-android/blob/aad2c29e30e362b19bf526eb14075d8325a1f2d8/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Application.targets#L26

We also set $(RunCommand) and $(RunArguments):

https://github.com/xamarin/xamarin-android/blob/aad2c29e30e362b19bf526eb14075d8325a1f2d8/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Application.targets#L12-L13

Maybe we need to specify the arguments here in a better way?

If I comment out where we set $(RunCommand) and $(RunArguments), it tries to run the app locally, which is not what we want:

> dotnet run -f net6.0-android
System.ComponentModel.Win32Exception (2): The system cannot find the file specified.
   at System.Diagnostics.Process.StartWithCreateProcess(ProcessStartInfo startInfo)
   at System.Diagnostics.Process.Start()
   at Microsoft.DotNet.Cli.Utils.Command.Execute(Action`1 processStarted)
   at Microsoft.DotNet.Cli.Utils.Command.Execute()
   at Microsoft.DotNet.Tools.Run.RunCommand.Execute()
   at Microsoft.DotNet.Tools.Run.RunCommand.Run(String[] args)
   at Microsoft.DotNet.Cli.Program.ProcessArgs(String[] args, ITelemetry telemetryClient)
   at Microsoft.DotNet.Cli.Program.Main(String[] args)

Huh, it seems like something with the MSBuild logic then. In the binlog I'm seeing that file imported and $(RunCommand) and $(RunArguments) being reset but the run target is not being overridden, possibly because Microsoft.NET.Sdk.targets is being imported after your custom file, so the run target there overwrites yours. I would dig into the binlog and ensure that the logic is behaving as you expect.

Huh, it seems like something with the MSBuild logic then. In the binlog I'm seeing that file imported and $(RunCommand) and $(RunArguments) being reset but the run target is not being overridden, possibly because Microsoft.NET.Sdk.targets is being imported after your custom file, so the run target there overwrites yours. I would dig into the binlog and ensure that the logic is behaving as you expect.

Ah, it sounds like the reordering of the workload targets (from #14393) impacted this then.

Targets that need to override targets from the .NET SDK should be refactored into a separate .targets file, which should be added to the AfterMicrosoftNETSdkTargets property in order to have them imported in the correct location. For example, this is how it was being done for WindowsDesktop:

  <PropertyGroup Condition=" '$(ImportWindowsDesktopTargets)' == 'true'">
    <AfterMicrosoftNETSdkTargets>$(AfterMicrosoftNETSdkTargets);$(MSBuildThisFileDirectory)../../Microsoft.NET.Sdk.WindowsDesktop/targets/Microsoft.NET.Sdk.WindowsDesktop.targets</AfterMicrosoftNETSdkTargets>
  </PropertyGroup>

So theoretically I think switching to system.commandline "fixed" this bug in that now we try to use a separate restore command when the target framework is specified on the command line. However, this has created a bug here such that we pass the -t:run option to the separate restore command and it fails because we didn't specify a target framework.

It seems like Sarah was correct here. Fixing one bug (It sounds like RestoringCommand wasn't correctly detecting when it needed to run a separate restore command) exposed another bug. The new bug is that additional targets specified (such as Run) get passed through to the restore command, when they should be filtered out and only passed to the actual command run after the restore.

@sfoslund Could you work on fixing this? I tried, but ran into issues where an argument such as -t:Run got interpreted as the SlnOrProjectArgument by the parser, and thus wasn't handled correctly by RestoringCommand.

Here is the change I made, which added some test coverage and tried to fix RestoringCommand: dsplaisted@ad79b3f

commented

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.