| File: src\GetCMakeArtifactsFromFileApi.cs | Web Access |
| Project: src\arcade\src\Microsoft.DotNet.CMake.Sdk\Microsoft.DotNet.CMake.Sdk.csproj (Microsoft.DotNet.CMake.Sdk) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using Microsoft.DotNet.Build.Tasks; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; namespace Microsoft.DotNet.CMake.Sdk { /// <summary> /// Reads CMake File API response to find artifacts for a specific source directory. /// </summary> public class GetCMakeArtifactsFromFileApi : BuildTask { /// <summary> /// The CMake build output directory containing the File API response. /// </summary> [Required] public string CMakeOutputDir { get; set; } /// <summary> /// The source directory of the CMakeLists.txt to find artifacts for. /// </summary> [Required] public string SourceDirectory { get; set; } /// <summary> /// The configuration name (e.g., Debug, Release). /// </summary> [Required] public string Configuration { get; set; } /// <summary> /// Output: The list of artifact file paths. /// </summary> [Output] public ITaskItem[] Artifacts { get; set; } public override bool Execute() { try { string replyDir = Path.Combine(CMakeOutputDir, ".cmake", "api", "v1", "reply"); if (!Directory.Exists(replyDir)) { Log.LogError("CMake File API reply directory does not exist: {0}", replyDir); return false; } // Find the latest index file var indexFiles = Directory.GetFiles(replyDir, "index-*.json"); if (indexFiles.Length == 0) { Log.LogError("No CMake File API index files found."); return false; } string indexFile = indexFiles.OrderByDescending(f => f).First(); Log.LogMessage(LogImportance.Low, "Reading CMake File API index: {0}", indexFile); string indexJson = File.ReadAllText(indexFile); var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; var index = JsonSerializer.Deserialize<CMakeFileApiIndex>(indexJson, options); if (index?.Reply?.ClientReply?.CodemodelV2?.JsonFile == null) { Log.LogError("No 'codemodel-v2' found in File API index reply."); return false; } string codeModelFile = Path.Combine(replyDir, index.Reply.ClientReply.CodemodelV2.JsonFile); if (!File.Exists(codeModelFile)) { Log.LogError("Codemodel file not found: {0}", codeModelFile); return false; } Log.LogMessage(LogImportance.Low, "Reading codemodel: {0}", codeModelFile); string codeModelJson = File.ReadAllText(codeModelFile); var codeModel = JsonSerializer.Deserialize<CMakeCodeModel>(codeModelJson, options); if (codeModel == null) { Log.LogError("Failed to deserialize codemodel."); return false; } // Get the source root from the codemodel string sourceRoot = codeModel.Paths?.Source?.Replace('\\', '/').TrimEnd('/') ?? ""; // Normalize source directory for comparison string normalizedSourceDir = Path.GetFullPath(SourceDirectory).Replace('\\', '/').TrimEnd('/'); // Find the configuration using LINQ var config = codeModel.Configurations?.FirstOrDefault(c => string.Equals(c.Name, Configuration, StringComparison.OrdinalIgnoreCase)); if (config == null) { Log.LogError("Configuration '{0}' not found in CMake File API response.", Configuration); return false; } Log.LogMessage(LogImportance.Low, "Found configuration: {0}", Configuration); if (config.Directories == null || config.Targets == null) { Log.LogError("Configuration '{0}' has no directories or targets.", Configuration); return false; } // Find the matching directory using LINQ var directory = config.Directories.FirstOrDefault(d => { string dirSource = d.Source?.Replace('\\', '/').TrimEnd('/') ?? ""; // Make the directory source path absolute if (!Path.IsPathRooted(dirSource)) { dirSource = Path.Combine(sourceRoot, dirSource); dirSource = Path.GetFullPath(dirSource).Replace('\\', '/').TrimEnd('/'); } return string.Equals(dirSource, normalizedSourceDir, StringComparison.OrdinalIgnoreCase); }); if (directory == null) { Log.LogError("Source directory '{0}' not found in CMake File API response.", SourceDirectory); return false; } Log.LogMessage(LogImportance.Low, "Found matching directory: {0}", SourceDirectory); // Get artifacts var artifacts = new List<ITaskItem>(); if (directory.TargetIndexes != null) { foreach (int targetIndex in directory.TargetIndexes) { if (targetIndex < 0 || targetIndex >= config.Targets.Count) { continue; } var target = config.Targets[targetIndex]; if (string.IsNullOrEmpty(target.JsonFile)) { continue; } string targetFile = Path.Combine(replyDir, target.JsonFile); if (!File.Exists(targetFile)) { continue; } Log.LogMessage(LogImportance.Low, "Reading target file: {0}", targetFile); // Read target details string targetJson = File.ReadAllText(targetFile); var targetDetails = JsonSerializer.Deserialize<CMakeTargetDetails>(targetJson, options); // Get artifacts if (targetDetails?.Artifacts != null) { foreach (var artifact in targetDetails.Artifacts) { if (!string.IsNullOrEmpty(artifact.Path)) { string fullPath = Path.Combine(CMakeOutputDir, artifact.Path); fullPath = Path.GetFullPath(fullPath); var item = new TaskItem(fullPath); artifacts.Add(item); Log.LogMessage(LogImportance.Low, "Found artifact: {0}", fullPath); } } } } } if (artifacts.Count == 0) { Log.LogWarning("No artifacts found for source directory '{0}' in configuration '{1}'.", SourceDirectory, Configuration); } Artifacts = artifacts.ToArray(); Log.LogMessage(LogImportance.Normal, "Found {0} artifact(s) for source directory '{1}' in configuration '{2}'", Artifacts.Length, SourceDirectory, Configuration); return true; } catch (Exception ex) { Log.LogErrorFromException(ex, showStackTrace: false); return false; } } } }