How to dotnet restore a multi-target framework in CI for testing using old version of dotnet?

530 views Asked by At

I have a C# package which currently supports dotnet 8 and I'm trying to modify it to add dotnet 6 and 7 support as well. I've gotten it to build for all versions using dotnet 8 locally on my laptop, using <TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks> in the .csproj file and removing all C# features added after net6.0. However when I push it to GitHub and my workflow runs in Github Actions, I test it against dotnet 6 and 7 as well as 8, and it breaks on the dotnet restore step.

My workflow:

name: .NET

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

env:
  DOTNET_CLI_TELEMETRY_OPTOUT: true
  DOTNET_GENERATE_ASPNET_CERTIFICATE: false

jobs:
  build:

    runs-on: macos-latest
    name: .NET ${{ matrix.dotnet }}
    strategy:
      matrix:
        dotnet:
          - 8.0 # EOL: 2026-11-10
          - 7.0 # EOL: 2024-05-14
          - 6.0 # EOL: 2024-11-12
          # version support doc: https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core
    steps:
    - uses: actions/checkout@v3
      with:
        repository: getargv/getargv
        path: getargv
        token: ${{ secrets.GH_PAT }}
    - name: Build libgetargv
      run: make install_dylib
      working-directory: getargv
    - uses: actions/checkout@v3
      with:
        path: getargv.cs
    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      id: setup
      with:
        dotnet-version: ${{ matrix.dotnet }}.x
    - name: Create temporary global.json
      run: "echo '{\"sdk\":{\"version\": \"${{ steps.setup.outputs.dotnet-version }}\"}}' > ./global.json"
      working-directory: getargv.cs
    - name: Restore dependencies
      run: dotnet restore
      working-directory: getargv.cs
    - name: Build
      run: dotnet build --no-restore --framework net${{ matrix.dotnet }}
      working-directory: getargv.cs
    - name: Test
      run: dotnet test --no-build --verbosity normal
      working-directory: getargv.cs

I can set the framework when building with a flag, but the restore step has no such flag.

The error I get is fairly predictable:

Error: /Users/runner/.dotnet/sdk/7.0.404/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.TargetFrameworkInference.targets(160,5): error NETSDK1045: The current .NET SDK does not support targeting .NET 8.0.  Either target .NET 7.0 or lower, or use a version of the .NET SDK that supports .NET 8.0. Download the .NET SDK from https://aka.ms/dotnet/download [/Users/runner/work/getargv.cs/getargv.cs/getargv.cs/Getargv/Getargv.csproj::TargetFramework=net8.0]
Error: /Users/runner/.dotnet/sdk/7.0.404/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.TargetFrameworkInference.targets(160,5): error NETSDK1045: The current .NET SDK does not support targeting .NET 8.0.  Either target .NET 7.0 or lower, or use a version of the .NET SDK that supports .NET 8.0. Download the .NET SDK from https://aka.ms/dotnet/download [/Users/runner/work/getargv.cs/getargv.cs/getargv.cs/Getargv.Tool/Getargv.Tool.csproj::TargetFramework=net8.0]
Error: /Users/runner/.dotnet/sdk/7.0.404/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.TargetFrameworkInference.targets(160,5): error NETSDK1045: The current .NET SDK does not support targeting .NET 8.0.  Either target .NET 7.0 or lower, or use a version of the .NET SDK that supports .NET 8.0. Download the .NET SDK from https://aka.ms/dotnet/download [/Users/runner/work/getargv.cs/getargv.cs/getargv.cs/Getargv.Tests/Getargv.Tests.csproj::TargetFramework=net8.0]
Error: /Users/runner/.dotnet/sdk/6.0.417/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.TargetFrameworkInference.targets(144,5): error NETSDK1045: The current .NET SDK does not support targeting .NET 7.0.  Either target .NET 6.0 or lower, or use a version of the .NET SDK that supports .NET 7.0. [/Users/runner/work/getargv.cs/getargv.cs/getargv.cs/Getargv/Getargv.csproj]
Error: /Users/runner/.dotnet/sdk/6.0.417/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.TargetFrameworkInference.targets(144,5): error NETSDK1045: The current .NET SDK does not support targeting .NET 7.0.  Either target .NET 6.0 or lower, or use a version of the .NET SDK that supports .NET 7.0. [/Users/runner/work/getargv.cs/getargv.cs/getargv.cs/Getargv.Tool/Getargv.Tool.csproj]
Error: /Users/runner/.dotnet/sdk/6.0.417/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.TargetFrameworkInference.targets(144,5): error NETSDK1045: The current .NET SDK does not support targeting .NET 7.0.  Either target .NET 6.0 or lower, or use a version of the .NET SDK that supports .NET 7.0. [/Users/runner/work/getargv.cs/getargv.cs/getargv.cs/Getargv.Tests/Getargv.Tests.csproj]

How do people test against older dotnet versions while also supporting newer versions?

Edit:

Changing the build command to: dotnet build --framework net${{ matrix.dotnet }} doesn't use the specified framework for restore.

3

There are 3 answers

0
GChuf On BEST ANSWER

You can specify the target framework for dotnet restore with dotnet restore -p:TargetFramework=net7.0.

dotnet restore and dotnet build are wrappers around dotnet msbuild, and dotnet restore is actually the same as calling dotnet msbuild -t:restore. Some additional information can be found in this question.

I would try changing your code like this:

    - name: Restore dependencies
      run: dotnet restore -p:TargetFramework=net${{ matrix.dotnet }}
3
Andrii Krot On
dotnet --version # Check the installed versions
dotnet --list-sdks # List all installed SDKs
dotnet --global install "desired_version" # Install the desired version

After specifying the .NET SDK version, execute the dotnet restore command for your multi-target framework project. This command will restore the NuGet packages required for your project.

# Navigate to your project directory
cd path/to/your/project

dotnet restore

Once the packages are restored, you can run your tests using the dotnet test command. This assumes you have unit tests set up for your project.

dotnet test

Make sure to adapt the version numbers and paths according to your project's requirements.

Example:

# Install .NET SDK 7.0.404
dotnet --global install 7.0.404

# Navigate to your project directory
cd path/to/your/project

# Restore packages
dotnet restore

# Run tests
dotnet test
3
stefan.seeland On

The common Github Actions way to go:

  1. Install all targeted SDKs
  2. Use the <TargetFrameworks> tag in the test csproj to pick the .net versions that should be tested. The testrunner will run each test in each .net version listed there.

Thats sufficient for every project I have seen so far.

Test Matrix approach

If your really really want to test, like you drafted, using a test matrix to ensure support / passed tests on lacking SDKs your approach seems to be reasonable. Things I fixed in your GHA workflow:

  1. In this POC it seems that the format of your matrix values get messed up -> avoid "." in the version.
  2. You need to pick the .net version on running the tests.
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        dotnet: [8, 7, 6]
    steps:
    - uses: actions/checkout@v4
    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: ${{ matrix.dotnet }}.0.x
    - name: Restore dependencies
      run: dotnet restore
    - name: Build
      run: dotnet build --configuration Release --no-restore --framework net${{ matrix.dotnet }}.0
    - name: Test
      run: dotnet test --no-build --verbosity normal --configuration Release --framework net${{ matrix.dotnet }}.0

Disclaimer

I skipped complexity of global.json and sdk pinning in Directory.Build.props. It does not really make sense in case of explicitly installing/omitting the SDK version anyway. And settings in global.json were related to NETSDK1045 in the past.

A starting point that does not fail (added latestMajor) is e.g.:

    - name: Create temporary global.json
      run: "echo '{\"sdk\":{\"version\": \"${{ matrix.dotnet }}.0.0\" ,\"rollForward\": \"latestMajor\" }}' > ./global.json"