// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Xml.Linq; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; namespace RepoTasks; public class CreateFrameworkListFile : Microsoft.Build.Utilities.Task { /// /// Files to extract basic information from and include in the list. /// [Required] public ITaskItem[] Files { get; set; } [Required] public string TargetFile { get; set; } /// /// Extra attributes to place on the root node. /// /// %(Identity): Attribute name. /// %(Value): Attribute value. /// public ITaskItem[] RootAttributes { get; set; } public override bool Execute() { XAttribute[] rootAttributes = RootAttributes ?.Select(item => new XAttribute(item.ItemSpec, item.GetMetadata("Value"))) .ToArray(); var frameworkManifest = new XElement("FileList", rootAttributes); var usedFileProfiles = new HashSet(); foreach (var f in Files .Select(item => new { Item = item, Filename = Path.GetFileName(item.ItemSpec), AssemblyName = FileUtilities.GetAssemblyName(item.ItemSpec), FileVersion = FileUtilities.GetFileVersion(item.ItemSpec), IsNative = item.GetMetadata("IsNativeImage") == "true", IsSymbolFile = item.GetMetadata("IsSymbolFile") == "true", PackagePath = GetPackagePath(item) }) .Where(f => !f.IsSymbolFile && (f.Filename.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) || f.IsNative)) .OrderBy(f => f.Filename, StringComparer.Ordinal)) { string path = Path.Combine(f.PackagePath, f.Filename).Replace('\\', '/'); string type = f.IsNative ? "Native" : "Managed"; var element = new XElement("File", new XAttribute("Path", path)); if (path.StartsWith("analyzers/", StringComparison.Ordinal)) { type = "Analyzer"; if (path.EndsWith(".resources.dll", StringComparison.Ordinal)) { // omit analyzer resources continue; } var pathParts = path.Split('/'); if (pathParts.Length < 3 || !pathParts[1].Equals("dotnet", StringComparison.Ordinal) || pathParts.Length > 5) { Log.LogError($"Unexpected analyzer path format {path}. Expected 'analyzers/dotnet(/roslyn)(/language)/analyzer.dll"); } // Check if we have enough parts for language directory and include it. // There could be a roslyn folder before the language folder. Check for it. bool hasRoslynVersion = pathParts[2].StartsWith("roslyn", StringComparison.Ordinal); int languageLengthCheck = hasRoslynVersion ? 4 : 3; int potentialLanguageIndex = hasRoslynVersion ? 3 : 2; if (pathParts.Length > languageLengthCheck) { element.Add(new XAttribute("Language", pathParts[potentialLanguageIndex])); } } element.Add(new XAttribute("Type", type)); if (f.AssemblyName != null) { byte[] publicKeyToken = f.AssemblyName.GetPublicKeyToken(); string publicKeyTokenHex; if (publicKeyToken != null) { publicKeyTokenHex = BitConverter.ToString(publicKeyToken) .ToLowerInvariant() .Replace("-", ""); } else { Log.LogError($"No public key token found for assembly {f.Item.ItemSpec}"); publicKeyTokenHex = ""; } element.Add( new XAttribute("AssemblyName", f.AssemblyName.Name), new XAttribute("PublicKeyToken", publicKeyTokenHex), new XAttribute("AssemblyVersion", f.AssemblyName.Version)); } else if (!f.IsNative) { // This file isn't managed and isn't native. Leave it off the list. continue; } element.Add(new XAttribute("FileVersion", f.FileVersion)); frameworkManifest.Add(element); } Directory.CreateDirectory(Path.GetDirectoryName(TargetFile)); File.WriteAllText(TargetFile, frameworkManifest.ToString()); return !Log.HasLoggedErrors; } private static string GetPackagePath(ITaskItem item) { string packagePath = item.GetMetadata("PackagePath"); // replicate the logic used by PackTask https://github.com/NuGet/NuGet.Client/blob/f24bad0668193ce21a1db8cabd1ce95ba509c7f0/src/NuGet.Core/NuGet.Build.Tasks.Pack/PackTaskLogic.cs#L644-L647 string recursiveDir = item.GetMetadata("RecursiveDir"); recursiveDir = string.IsNullOrEmpty(recursiveDir) ? item.GetMetadata("NuGetRecursiveDir") : recursiveDir; return string.IsNullOrEmpty(recursiveDir) ? packagePath : Path.Combine(packagePath, recursiveDir); } }