File: src\GetCMakeArtifactsFromFileApi.cs
Web Access
Project: src\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;
            }
        }
    }
}