Skip to content

Instantly share code, notes, and snippets.

@baronfel
Created June 19, 2025 21:45
Show Gist options
  • Save baronfel/73011ef73deea591704c10ee54fd8ff9 to your computer and use it in GitHub Desktop.
Save baronfel/73011ef73deea591704c10ee54fd8ff9 to your computer and use it in GitHub Desktop.
SDK selection guided by global.json

.NET SDK Acquisition Guide for Visual Studio Tooling

This document explains how .NET tooling within Visual Studio can help users download and install specific versions of the .NET SDK by integrating with the .NET host infrastructure and release management systems.

Overview

The process consists of three main steps:

  1. Locate and evaluate global.json status in the workspace using hostfxr native library
  2. Locate and evaluate available .NET SDK installers using the Microsoft.Deployment.DotNet.Releases library
  3. Apply global.json constraints to installer information to determine the best SDK for download

Step 1: Resolving global.json and SDK Version Information

Using the hostfxr Native Library

The .NET CLI uses the hostfxr native library to resolve SDK versions and global.json files. Visual Studio tooling can leverage the same approach through P/Invoke interop.

Key hostfxr APIs

Based on the Interop.cs implementation, the following APIs are essential:

1. hostfxr_resolve_sdk2

[DllImport(Constants.HostFxr, CharSet = UTF16, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
internal static extern int hostfxr_resolve_sdk2(
    string? exe_dir,
    string? working_dir,
    hostfxr_resolve_sdk2_flags_t flags,
    hostfxr_resolve_sdk2_result_fn result);

This API resolves the SDK version based on:

  • Current working directory
  • global.json file presence and content
  • Rollforward policy specified in global.json

2. hostfxr_get_available_sdks

[DllImport(Constants.HostFxr, CharSet = UTF16, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
internal static extern int hostfxr_get_available_sdks(
    string? exe_dir,
    hostfxr_get_available_sdks_result_fn result);

This API enumerates all locally installed SDKs.

3. hostfxr_get_dotnet_environment_info

[DllImport(Constants.HostFxr, CharSet = UTF16, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
internal static extern int hostfxr_get_dotnet_environment_info(
    string dotnet_root,
    IntPtr reserved,
    hostfxr_get_dotnet_environment_info_result_fn result,
    IntPtr result_context);

This API provides comprehensive environment information including installed SDKs and frameworks.

Implementation Pattern

public class SdkResolver
{
    public struct SdkResolutionResult
    {
        public string ResolvedSdkDir { get; set; }
        public string GlobalJsonPath { get; set; }
        public string RequestedVersion { get; set; }
        public bool SdkFound { get; set; }
    }

    public static SdkResolutionResult ResolveSdk(string workingDirectory)
    {
        var result = new SdkResolutionResult();
        
        // Callback to capture resolution results
        void ResultCallback(hostfxr_resolve_sdk2_result_key_t key, string value)
        {
            switch (key)
            {
                case hostfxr_resolve_sdk2_result_key_t.resolved_sdk_dir:
                    result.ResolvedSdkDir = value;
                    result.SdkFound = !string.IsNullOrEmpty(value);
                    break;
                case hostfxr_resolve_sdk2_result_key_t.global_json_path:
                    result.GlobalJsonPath = value;
                    break;
                case hostfxr_resolve_sdk2_result_key_t.requested_version:
                    result.RequestedVersion = value;
                    break;
            }
        }

        // Call hostfxr to resolve SDK
        int hr = Interop.Windows.hostfxr_resolve_sdk2(
            null, // exe_dir - use default
            workingDirectory,
            hostfxr_resolve_sdk2_flags_t.disallow_prerelease, // or 0 to allow prerelease
            ResultCallback);

        return result;
    }
}

global.json Processing

When a global.json file is found, parse it to extract:

{
  "sdk": {
    "version": "8.0.100",
    "rollForward": "latestMinor",
    "allowPrerelease": false
  }
}

Key properties:

  • version: Exact or minimum version required
  • rollForward: Policy for version selection (patch, feature, minor, major, latestPatch, latestFeature, latestMinor, latestMajor, disable)
  • allowPrerelease: Whether prerelease versions are acceptable

Step 2: Discovering Available .NET SDK Installers

Using Microsoft.Deployment.DotNet.Releases

The Microsoft.Deployment.DotNet.Releases NuGet package provides access to official .NET release manifests.

Installation

<PackageReference Include="Microsoft.Deployment.DotNet.Releases" Version="1.0.0" />

Key APIs

1. ProductCollection - Get All Products

using Microsoft.Deployment.DotNet.Releases;

// Get all .NET products (versions/channels)
var products = await ProductCollection.GetAsync();

foreach (var product in products)
{
    Console.WriteLine($"Product: {product.ProductName} {product.ProductVersion}");
    Console.WriteLine($"  Latest SDK: {product.LatestSdkVersion}");
    Console.WriteLine($"  Latest Runtime: {product.LatestRuntimeVersion}");
    Console.WriteLine($"  Support Phase: {product.SupportPhase}");
    Console.WriteLine($"  End of Life: {product.EndOfLifeDate}");
}

2. ProductRelease - Get Specific Release Information

// Get releases for a specific product
var releases = await product.GetReleasesAsync();

foreach (var release in releases)
{
    Console.WriteLine($"Release: {release.Version}");
    
    // SDKs in this release
    foreach (var sdk in release.Sdks)
    {
        Console.WriteLine($"  SDK: {sdk.Version}");
        Console.WriteLine($"  C# Version: {sdk.CSharpVersion}");
        Console.WriteLine($"  F# Version: {sdk.FSharpVersion}");
        
        // Download files for this SDK
        foreach (var file in sdk.Files)
        {
            Console.WriteLine($"    File: {file.Name}");
            Console.WriteLine($"    URL: {file.Address}");
            Console.WriteLine($"    Platform: {file.Platform}");
            Console.WriteLine($"    Architecture: {file.Architecture}");
            Console.WriteLine($"    Hash: {file.Hash}");
        }
    }
}

3. ReleaseFile - Download SDK Installers

// Find the appropriate installer for current platform
var currentPlatform = GetCurrentPlatform(); // "win", "linux", "osx"
var currentArchitecture = GetCurrentArchitecture(); // "x64", "arm64", etc.

var sdkFile = sdk.Files.FirstOrDefault(f => 
    f.Platform == currentPlatform && 
    f.Architecture == currentArchitecture);

if (sdkFile != null)
{
    // Download the installer
    string downloadPath = Path.Combine(Path.GetTempPath(), sdkFile.Name);
    await sdkFile.DownloadAsync(downloadPath);
    
    // of course do hash verification here of the downloaded file -
    // the hash is available in sdkFile.Hash
    
    Console.WriteLine($"Downloaded SDK installer: {downloadPath}");
    Console.WriteLine($"Verified hash: {sdkFile.Hash}");
}

Implementation Pattern

public class SdkDiscoveryService
{
    public async Task<IEnumerable<AvailableSdk>> GetAvailableSdksAsync()
    {
        var products = await ProductCollection.GetAsync();
        var availableSdks = new List<AvailableSdk>();

        foreach (var product in products)
        {
            var releases = await product.GetReleasesAsync();
            
            foreach (var release in releases)
            {
                foreach (var sdk in release.Sdks)
                {
                    availableSdks.Add(new AvailableSdk
                    {
                        Version = sdk.Version,
                        Product = product,
                        Release = release,
                        SdkComponent = sdk,
                        IsPrerelease = sdk.Version.IsPrerelease,
                        IsSupported = product.IsSupported
                    });
                }
            }
        }

        return availableSdks.OrderByDescending(s => s.Version);
    }
}

public class AvailableSdk
{
    public ReleaseVersion Version { get; set; }
    public Product Product { get; set; }
    public ProductRelease Release { get; set; }
    public SdkReleaseComponent SdkComponent { get; set; }
    public bool IsPrerelease { get; set; }
    public bool IsSupported { get; set; }
}

Step 3: Applying global.json Constraints

Version Selection Algorithm

Step 1 gives you a user-requested Sdk version + rollforward policy, and Step 2 gives you a list of available SDKs. The next step is to apply the constraints from global.json to select the best SDK.

The combination of version + optional rollforward policy sort-of create a SemVer Version Range. Create that range and then filter the available SDKs based on that range.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment