dotnet / format

Home for the dotnet-format command

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Support restriction to specific target framework

zdenek-jelinek opened this issue · comments

I have an application with multiple target frameworks (TFMs). This serves as a step during migration to newer .NET. Each TFM is associated with a distinct set of package references, so that e.g. .NET 6 application binaries use Microsoft.Extensions.* packages of version 6.0.x while .NET 7 application binaries use Microsoft.Extensions.* packages of version 7.0.x.

When performing CI build, I have a pipeline linked with a specific target framework so that I have isolated artifacts and faster individual pipeline runs. Each such pipeline runs dotnet commands restricted to specific TFM using --framework parameter applicable to most dotnet-cli commands.

However, when the pipeline gets to dotnet format, it fails with errors such as IDE0005 Using directive is unneccessary. Upon deeper investigation, I have found that this is caused by dotnet format running against all TFMs. In the described CI pipeline scenario, some of the target frameworks did not have their corresponding packages intentionally restored. Using statements refering to those packages are invalid and reported as unneccessary which causes the pipeline to fail.

I have figured that I can work around this by using TagetFramework environment variable, although it feels quite hacky, especially since parametrization in the format -p:Property=Value does not seem to be supported either.

Could you please add support for the ability to limit dotnet-format to specific target framework officially? E.g. via --framework option as used by other dotnet tooling.

One solution could be to dynamically create/update a global.json, specially the version and rollForward members, to make sure to use the correct global editorconfig / analyzer-config per major version of SDK. Then make sure the dotnet command working dir is correctly set.

Thank you for getting back to me. Sorry for taking a bit to get back to you but the repro is not trivial.

To sum up, I think there is a misunderstanding - I am not looking to tie SDK version with my build, I am happy to run the latest SDK. I think the suggestion in the above comment is focused on stabilizing rulesets/behavior according to SDK version. While it is a related concept, it does not help with resolving the core issue.

What I have is a multi-target project (say .NET 6 + .NET 8) and I only want to run dotnet format for specific target framework (e. g. .NET 8) because in the specific context (CI run), I am not interested in .NET 6 version. It may not even compile for the matter and that's fine. I'm building .NET 8 in this run.

To reproduce what I mean, follow these steps:

  1. Create a new console app, let's say
    dotnet new console --language C# --name DotnetFormatExample --no-restore --framework net6.0 --use-program-main
  2. Update the resulting csproj to contain additional target framework, e.g. .NET 8 (I don't know of any way of doing this via CLI)
    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFrameworks>net6.0;net8.0</TargetFrameworks>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
      </PropertyGroup>
    
    </Project>
  3. Add a package reference under .NET 8 so that there is someting to restore
    Do this conditionally only for one TFM so that the restore for the other TFM fails (in this case, it will fail for .NET 6)
    I'd use CLI but the --framework switch there does not seem to work with SDK 8.0.100
    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFrameworks>net6.0;net8.0</TargetFrameworks>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
      </PropertyGroup>
    
      <ItemGroup Condition="'$(TargetFramework)'=='net8.0'">
        <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
      </ItemGroup>
    
    </Project>
  4. Use the package in code, e.g. Program.cs:
    using Newtonsoft.Json;
    
    namespace DotnetFormatExample;
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(JsonConvert.SerializeObject(new { Greeting = "Hello, World!" }));
        }
    }
  5. Ensure that unneeded usings are reported - add .editorconfig with
    [*.cs]
    dotnet_diagnostic.IDE0005.severity = warning
    
  6. Build and run the project for .NET 8
    • dotnet restore -p:TargetFramework=net8.0
    • dotnet build --framework net8.0 --no-restore
    • dotnet run --framework net8.0 --no-build
    • {"Greeting":"Hello, World!"}

  7. Attempt to verify code style, observe that there is no documented way to parametrize TFM
    dotnet format --verify-no-changes --no-restore --verbosity detailed
    The format runs for both .NET 8 and .NET 6 and fails for the latter, specifying that the Newtonsoft.Json using in Program.cs is unnecessary. I will not reproduce the actual message as it is localized and I have so far found no way of turning that off.

The suggestion in the comment above does not help with this:

  1. Use global.json
    {
      "sdk": {
        "version": "8.0.100"
      }
    }
  2. Check that .NET 8 SDK is in use
    dotnet --version

    8.0.100

  3. Run code style validation, observe that both .NET 6 and .NET 8 targets are evaluated and that the same problem persists
    dotnet format --verify-no-changes --no-restore --verbosity detailed
  4. Delete global.json to restore previous state

A workaround I am not entirely happy about:

  1. Set environment variable TargetFramework to net8.0
    SET TargetFramework=net8.0 (or whatever is the syntax in your environment of choice)
  2. Run code style validation
    dotnet format --verify-no-changes --no-restore --verbosity detailed
  3. Observe that there are no errors and that only .NET 8 target was considered

Now this was a contrived example but with private artifact feed and more complex projects structure, this reproduces in identical fashion - format is run without preceding restore for some target and subsequently fails.

I am not too worried by the errors produced being misleading. My primary goal is to be able to specify that I want to only run dotnet format for a specific target framework. This can be reproduced without any conditional references and such as in the example above. It is enough to have a multi-targeted project, run dotnet format with detailed output and see that it considers all targets without any documented option of choice.

My bad, in this case the only way to avoid this kind of error while using multi-targeting with tied diff. per framework would be to use either preprocessor (code part) or conditional includes of files (csproj part). (in my knowledge)

No worries. Just to clarify, the main goal is to be able to avoid running format for .NET 6 at all even if it's in TargetFrameworks list. The error is just a place where it manifests in a way that explicitly needs a workaround.