File: Utilities\ProjectFileReader.cs
Web Access
Project: src\src\Aspire.Hosting.Maui\Aspire.Hosting.Maui.csproj (Aspire.Hosting.Maui)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Text.Json;
 
namespace Aspire.Hosting.Maui.Utilities;
 
/// <summary>
/// Provides utilities for reading and parsing MAUI project files.
/// </summary>
internal static class ProjectFileReader
{
    // Cache results per project path to avoid repeated MSBuild invocations
    private static readonly ConcurrentDictionary<string, Dictionary<string, string?>> s_projectCache = new(StringComparer.OrdinalIgnoreCase);
 
    /// <summary>
    /// Gets the target framework matching the specified platform from the project file.
    /// </summary>
    /// <param name="projectPath">The path to the project file to parse.</param>
    /// <param name="platformIdentifier">The platform identifier to search for (e.g., "windows", "android", "ios", "maccatalyst").</param>
    /// <returns>The matching TFM if found, otherwise null.</returns>
    /// <remarks>
    /// This method uses MSBuild to evaluate the project and retrieve TargetFramework and TargetFrameworks properties.
    /// It searches for a target framework containing the specified platform identifier (case-insensitive) and returns the first match.
    /// Results are cached per project path to avoid repeated MSBuild evaluations.
    /// </remarks>
    public static string? GetPlatformTargetFramework(string projectPath, string platformIdentifier)
    {
        // Get or create cache entry for this project
        var platformCache = s_projectCache.GetOrAdd(projectPath, _ => new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase));
        
        // Check cache first
        lock (platformCache)
        {
            if (platformCache.TryGetValue(platformIdentifier, out var cachedTfm))
            {
                return cachedTfm;
            }
        }
 
        // Not cached, evaluate the project
        var tfm = EvaluateProjectForPlatform(projectPath, platformIdentifier);
        
        // Cache the result
        lock (platformCache)
        {
            platformCache[platformIdentifier] = tfm;
        }
        
        return tfm;
    }
 
    private static string? EvaluateProjectForPlatform(string projectPath, string platformIdentifier)
    {
        try
        {
            // Use dotnet msbuild to get both TargetFramework and TargetFrameworks properties
            var startInfo = new ProcessStartInfo
            {
                FileName = "dotnet",
                Arguments = $"msbuild \"{projectPath}\" -getProperty:TargetFramework,TargetFrameworks -nologo",
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                UseShellExecute = false,
                CreateNoWindow = true
            };
 
            using var process = Process.Start(startInfo);
            if (process is null)
            {
                return null;
            }
 
            var output = process.StandardOutput.ReadToEnd();
            process.WaitForExit();
 
            if (process.ExitCode != 0 || string.IsNullOrWhiteSpace(output))
            {
                return null;
            }
 
            // Parse the JSON output from msbuild -getProperty
            var jsonDoc = JsonDocument.Parse(output);
            var properties = jsonDoc.RootElement.GetProperty("Properties");
 
            // Check both TargetFramework and TargetFrameworks properties
            var targetFrameworksValue = string.Empty;
            
            if (properties.TryGetProperty("TargetFrameworks", out var targetFrameworks))
            {
                targetFrameworksValue = targetFrameworks.GetString() ?? string.Empty;
            }
            
            if (string.IsNullOrWhiteSpace(targetFrameworksValue) && 
                properties.TryGetProperty("TargetFramework", out var targetFramework))
            {
                targetFrameworksValue = targetFramework.GetString() ?? string.Empty;
            }
 
            if (string.IsNullOrWhiteSpace(targetFrameworksValue))
            {
                return null;
            }
 
            // Split by semicolon and find the first TFM containing the platform identifier
            var platformTfm = targetFrameworksValue.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
                .FirstOrDefault(tfm => tfm.Contains($"-{platformIdentifier}", StringComparison.OrdinalIgnoreCase));
 
            return platformTfm;
        }
        catch
        {
            // If we can't evaluate the project, return null to indicate unknown
            return null;
        }
    }
}