Stage failing in Azure DevOps pipeline

How to avoid global.json version conflicts in Azure DevOps

3 min read.

Sometimes in Azure DevOps, you have a project that’s being built with certain .NET version. It’ll require that particular SDK version, and most typically that is defined in global.json file.

However, if you also need to install a certain .NET tool, running dotnet tool install MyBuildTool will fail. And it’ll fail before it actually tries to install the version of the tool you wanted to!

This article explains how to work around the .NET version conflicts when using Azure DevOps, you custom tooling (installed by dotnet tool install) and global.json

Problem

The error message your running into looks like this:

A compatible SDK version for global.json version: [5.0.100] from [D:\...\global.json] was not found Did you mean to run dotnet SDK commands? Please install dotnet SDK from: https://go.microsoft.com/fwlink/?LinkID=798306&clcid=0x409

But whatever version of the tooling you need, you can’t install because it’s imcompatible with the global.json version!

So this becomes kind of an chicken-and-egg problem. If you want to use your tooling and build your project with the .NET SDK version set in the global.json, you need to either always keep the tooling in sync (which at least for us was NOT feasible), or you’ll need to find out a way to ignore the global.json file in this particular regard.

Solution

Well, of course the solution is to ignore global.json. But how?

Luckily, I’m not the first person to run into this. This discussion on GitHub talks about the same issue: https://github.com/dotnet/sdk/issues/10311

It has this script sample:

mv global.json disabled-global.json
[Some dotnet command allowed any sdk, for example dotnet tool install]
mv disabled-global.json global.json
[Some dotnet command not allowed any sdk, for example dotnet build]

So, in short the solution goes somewhat like this:

Time needed: 10 minutes.

How to allow dotnet commands to bypass the global.json version check?

  1. Rename global.json

    If you rename global.json to anything else, it won’t be checked anymore! In an Azure DevOps task, you can use mv to “move” files or directories – including renaming your global.json!

  2. Run your commands

    Now you can run your dotnet commands ignoring global.json! In my case, I’d install build tooling that was “incompatible” with the global.json file.

  3. Rename the file back to global.json

    Depending on what you’re doing in your next steps, this might or might not be required – but it’s probably a good idea to do if you actually need the SDK version from the global.json to matter for the rest of your build!


For the TL;RD -version, just jump here: my YAML


How to use this in practice?

I adjusted the script I found on GitHub for my use like this:

# This was my initial implementation - don't use this!
- task: [email protected]
  displayName: Install Build Tools
  inputs:
    targetType: inline
    pwsh: true
    failOnStderr: true
    script: |
      # https://github.com/dotnet/sdk/issues/10311#issuecomment-1254912466

      try {
        try {
          mv global.json disabled-global.json
        } catch {
          Write-Host "Moving global.json failed, but we don't care"
        }

        dotnet tool install --global MyBuildTool --version $(CliVersion)

      } catch {
        Write-Host "Installing Tooling failed"
        Exit 1;
      } finally {
        try {
          mv disabled-global.json global.json
        } catch {
          Write-Host "Moving global.json back failed, but we don't care"
        }
      }

      Exit 0;

But this script sample is annoying as it uses mv – a bash command – to move stuff around. It kind of works, sure, but the try-catch -block around mv will not end up in the catch block.

Why? Well, PowerShell try-catch works a bit differently than what you maybe expect: You’ll only end up in catch if a terminating-error gets thrown in the try-block! And mv failing to move something will not throw in PowerShell.

So I wanted to turn it into a pure PowerShell solution instead. PowerShell has Move-Item commandlet, which you can luckily use. This turned into something like below:

My current Azure DevOps PowerShell task to run dotnet commands disregarding the .NET SDK version in global.json

# A far better implementation that actually catches the exceptions (and simply logs them, but you can change this if you need to)
- task: [email protected]
  displayName: Install Build Tools
  inputs:
    targetType: inline
    pwsh: true
    failOnStderr: false # We might get the global.json move exceptions, but we can ignore them
    script: |
      # https://github.com/dotnet/sdk/issues/10311#issuecomment-1254912466
      # https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/move-item?view=powershell-7.2

      try {
        try {
          Move-Item -Path global.json -Destination disabled-global.json
        } catch {
          Write-Host "Moving global.json failed, but we don't care"
        }

        dotnet tool install --global MyBuildTool --version $(CliVersion)

      } catch {
        Write-Host "Installing Tooling failed"
        Exit 1;
      } finally {
        try {
          Move-Item -Path disabled-global.json -Destination global.json
        } catch {
          Write-Host "Moving global.json back failed, but we don't care at this point"
        }
      }

      Exit 0;

References

mm
0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments