File: SlnfFileHelper.cs
Web Access
Project: src\src\sdk\src\Cli\dotnet\dotnet.csproj (dotnet)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

using System;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.DotNet.Cli.Utils;

namespace Microsoft.DotNet.Cli;

[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.Never)]
[JsonSerializable(typeof(SlnfFileHelper.SlnfRoot))]
internal partial class SlnfJsonSerializerContext : JsonSerializerContext;

/// <summary>
/// Utilities for working with solution filter (.slnf) files
/// </summary>
public static class SlnfFileHelper
{
    /// <summary>
    /// File extension for solution filter files
    /// </summary>
    public const string SlnfExtension = ".slnf";

    /// <summary>
    /// Normalizes path separators from backslashes to the OS-specific directory separator
    /// </summary>
    /// <param name="path">The path to normalize</param>
    /// <returns>Path with OS-specific separators</returns>
    public static string NormalizePathSeparatorsToOS(string path)
    {
        return path.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar);
    }

    /// <summary>
    /// Normalizes path separators to backslashes (as used in .slnf files)
    /// </summary>
    /// <param name="path">The path to normalize</param>
    /// <returns>Path with backslash separators</returns>
    public static string NormalizePathSeparatorsToBackslash(string path)
    {
        return path.Replace(Path.DirectorySeparatorChar, '\\');
    }

    internal class SlnfSolution
    {
        [JsonPropertyName("path")]
        public string Path { get; set; }

        [JsonPropertyName("projects")]
        public List<string> Projects { get; set; } = new();
    }

    internal class SlnfRoot
    {
        [JsonPropertyName("solution")]
        public SlnfSolution Solution { get; set; } = new();
    }

    /// <summary>
    /// Creates a new solution filter file
    /// </summary>
    /// <param name="slnfPath">Path to the solution filter file to create</param>
    /// <param name="parentSolutionPath">Path to the parent solution file</param>
    /// <param name="projects">List of project paths to include (relative to the parent solution)</param>
    public static void CreateSolutionFilter(string slnfPath, string parentSolutionPath, IEnumerable<string> projects = null)
    {
        var slnfDirectory = Path.GetDirectoryName(Path.GetFullPath(slnfPath)) ?? string.Empty;
        var parentSolutionFullPath = Path.GetFullPath(parentSolutionPath, slnfDirectory);
        var relativeSolutionPath = Path.GetRelativePath(slnfDirectory, parentSolutionFullPath);

        // Normalize path separators to backslashes (as per slnf format)
        relativeSolutionPath = NormalizePathSeparatorsToBackslash(relativeSolutionPath);

        var root = new SlnfRoot
        {
            Solution = new SlnfSolution
            {
                Path = relativeSolutionPath,
                Projects = projects?.Select(NormalizePathSeparatorsToBackslash).ToList() ?? new List<string>()
            }
        };

        var json = JsonSerializer.Serialize(root, SlnfJsonSerializerContext.Default.SlnfRoot);
        File.WriteAllText(slnfPath, json);
    }

    /// <summary>
    /// Saves a solution filter file with the given projects
    /// </summary>
    /// <param name="slnfPath">Path to the solution filter file</param>
    /// <param name="parentSolutionPath">Path to the parent solution (stored in the slnf file)</param>
    /// <param name="projects">List of project paths (relative to the parent solution)</param>
    public static void SaveSolutionFilter(string slnfPath, string parentSolutionPath, IEnumerable<string> projects)
    {
        var slnfDirectory = Path.GetDirectoryName(Path.GetFullPath(slnfPath)) ?? string.Empty;

        // Normalize the parent solution path to be relative to the slnf file
        var relativeSolutionPath = parentSolutionPath;
        if (Path.IsPathRooted(parentSolutionPath))
        {
            relativeSolutionPath = Path.GetRelativePath(slnfDirectory, parentSolutionPath);
        }

        // Normalize path separators to backslashes (as per slnf format)
        relativeSolutionPath = NormalizePathSeparatorsToBackslash(relativeSolutionPath);

        var root = new SlnfRoot
        {
            Solution = new SlnfSolution
            {
                Path = relativeSolutionPath,
                Projects = projects.Select(NormalizePathSeparatorsToBackslash).ToList()
            }
        };

        var json = JsonSerializer.Serialize(root, SlnfJsonSerializerContext.Default.SlnfRoot);
        File.WriteAllText(slnfPath, json);
    }
}