File: src\ExtractNgenMethodList.cs
Web Access
Project: src\src\Microsoft.DotNet.Arcade.Sdk\Microsoft.DotNet.Arcade.Sdk.csproj (Microsoft.DotNet.Arcade.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 System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Xml.Linq;
 
namespace Microsoft.DotNet.Arcade.Sdk
{
    /// <summary>
    /// Used to convert a raw XML dump from IBCMerge into the set of methods which will be NGEN'd when 
    /// partial NGEN is enabled
    /// </summary>
    public sealed class ExtractNgenMethodList : Microsoft.Build.Utilities.Task
    {
        /// <summary>
        /// This is the XML file produced by passing -dxml to ibcmerge. It will be transformed into the set of
        /// methods marked for NGEN in the assembly
        /// </summary>
        [Required]
        public string IbcXmlFilePath { get; set; }
 
        /// <summary>
        /// This is the name of the assembly for when IBC is being run. 
        /// </summary>
        [Required]
        public string AssemblyFilePath { get; set; }
 
        /// <summary>
        /// The current target framework for the assembly being compiled.
        /// </summary>
        public string AssemblyTargetFramework { get; set; }
 
        /// <summary>
        /// This is the directory the NGEN list should be output to
        /// </summary>
        [Required]
        public string OutputDirectory { get; set; }
 
        /// <summary>
        /// From IBCMerge.MethodProfilingDataFlags
        /// </summary>
        private const int ReadMethodCode = 0x1;
 
        public override bool Execute()
        {
            var document = XDocument.Load(IbcXmlFilePath);
            var items = new List<string>();
            var parentElement = document.Root.Element("MethodProfilingData");
            if (parentElement != null)
            {
                foreach (var child in parentElement.Elements("Item"))
                {
                    var flagsInt = child.Attribute("flagsInt");
                    var name = child.Attribute("name");
                    if (int.TryParse(flagsInt?.Value.Substring(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int parsedFlagsInt) 
                        && (parsedFlagsInt & ReadMethodCode) == 1)
                    {
                        items.Add(name.Value);
                    }
                }
            }
 
            items.Sort();
 
            Directory.CreateDirectory(OutputDirectory);
 
            // When AssemblyTargetFramework is set then this is an assembly that is being built by the current
            // build. Appending the target framework means we will avoid name clashes. When it's not set then
            // this is a binary that is included in the build but not actually built here. Possible, remotely, 
            // that there will be multiple versions with the same target framework. Hence use the MVID as the 
            // suffix here to avoid clashes.
            var outputFileNameSuffix = string.IsNullOrEmpty(AssemblyTargetFramework)
                ? GetAssemblyMvid().ToString()
                : AssemblyTargetFramework;
            var outputFileName = $"{Path.GetFileNameWithoutExtension(AssemblyFilePath)}-{outputFileNameSuffix}.ngen.txt";
            var outputFilePath = Path.Combine(OutputDirectory, outputFileName);
            using (var outputFileStream = new StreamWriter(outputFilePath, append: false))
            {
                foreach (var item in items)
                {
                    outputFileStream.WriteLine(item);
                }
            }
 
            return true;
        } 
 
        private Guid GetAssemblyMvid()
        {
            using (var stream = File.Open(AssemblyFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                var peReader = new PEReader(stream);
                var metadataReader = peReader.GetMetadataReader();
                return metadataReader.GetGuid(metadataReader.GetModuleDefinition().Mvid);
            }
        }
    }
}