| 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; } } }