|
// 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.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Xml;
using Microsoft.Build.Shared;
using Microsoft.Build.Shared.FileSystem;
#nullable disable
namespace Microsoft.Build.Utilities
{
/// <summary>
/// What should happen if multiple versions of a given productfamily or sdk name are found
/// </summary>
public enum MultipleVersionSupport
{
/// <summary>
/// No action should be taken if multiple versions are detected
/// </summary>
Allow = 0,
/// <summary>
/// Log warning
/// </summary>
Warning = 1,
/// <summary>
/// Log an error
/// </summary>
Error = 2
}
/// <summary>
/// Structure to represent the information contained in SDKManifest.xml
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SDK", Justification = "Not worth breaking partners")]
public class SDKManifest
{
/// <summary>
/// Pattern in path to extension SDK used to help determine if manifest is from a framework SDK
/// </summary>
private static string s_extensionSDKPathPattern = @"\MICROSOFT SDKS\WINDOWS\V8.0\EXTENSIONSDKS";
/// <summary>
/// Default version of MaxPlatformVersion in framework extension SDKs with manifest not containing such a property
/// </summary>
private static string s_defaultMaxPlatformVersion = "8.0";
/// <summary>
/// Default version of MinOSVersion in framework extension SDKs with manifest not containing such a property
/// </summary>
private static string s_defaultMinOSVersion = "6.2.1";
/// <summary>
/// Default version of MaxOSVersionTested in framework extension SDKs with manifest not containing such a property
/// </summary>
private static string s_defaultMaxOSVersionTested = "6.2.1";
/// <summary>
/// What should happen if this sdk is resolved with other sdks of the same productfamily or same sdk name.
/// </summary>
private MultipleVersionSupport _supportsMultipleVersions;
/// <summary>
/// Path to where the file SDKManifest.xml is stored
/// </summary>
private readonly string _pathToSdk;
/// <summary>
/// Whatever appx locations we found in the manifest
/// </summary>
private IDictionary<string, string> _appxLocations;
/// <summary>
/// Whatever framework identities we found in the manifest.
/// </summary>
private IDictionary<string, string> _frameworkIdentities;
/// <summary>
/// Whatever MaxOSVersionTested we found in the manifest.
/// </summary>
private string _maxOSVersionTested;
/// <summary>
/// Whatever MinOSVersion we found in the manifest
/// </summary>
private string _minOSVersion;
/// <summary>
/// Whatever MaxPlatformVersion we found in the manifest
/// </summary>
private string _maxPlatformVersion;
/// <summary>
/// The SDKType, default of unspecified
/// </summary>
private SDKType _sdkType = SDKType.Unspecified;
/// <summary>
/// Constructor
/// Takes the path to SDKManifest.xml and populates the structure with manifest data
/// </summary>
public SDKManifest(string pathToSdk)
{
ErrorUtilities.VerifyThrowArgumentLength(pathToSdk);
_pathToSdk = pathToSdk;
LoadManifestFile();
}
/// <summary>
/// Whatever information regarding support for multiple versions is found in the manifest
/// </summary>
public MultipleVersionSupport SupportsMultipleVersions => _supportsMultipleVersions;
/// <summary>
/// Whatever framework identities we found in the manifest.
/// </summary>
public IDictionary<string, string> FrameworkIdentities => _frameworkIdentities != null ? new ReadOnlyDictionary<string, string>(_frameworkIdentities) : null;
/// <summary>
/// Whatever appx locations we found in the manifest
/// </summary>
public IDictionary<string, string> AppxLocations => _appxLocations != null ? new ReadOnlyDictionary<string, string>(_appxLocations) : null;
/// <summary>
/// PlatformIdentity if it exists in the appx manifest for this sdk.
/// </summary>
public string PlatformIdentity { get; private set; }
/// <summary>
/// The FrameworkIdentity for the sdk, this may be a single name or a | delimited name
/// </summary>
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Necessary for compatibility with specs of SDKManifest.xml")]
public string FrameworkIdentity { get; private set; }
/// <summary>
/// Support Prefer32bit found in the sdk manifest
/// </summary>
public string SupportPrefer32Bit { get; private set; }
/// <summary>
/// SDKType found in the sdk manifest
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SDK", Justification = "Want to keep same case as the attribute in SDKManifest.xml")]
public SDKType SDKType => _sdkType;
/// <summary>
/// CopyRedistToSubDirectory specifies where the redist files should be copied to relative to the root of the package.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "SubDirectory", Justification = "Want to keep case compliant with the attributes in the SDKManifest.xml")]
public string CopyRedistToSubDirectory { get; private set; }
/// <summary>
/// Supported Architectures is a semicolon delimited list of architectures that the SDK supports.
/// </summary>
public string SupportedArchitectures { get; private set; }
/// <summary>
/// DependsOnSDK is a semicolon delimited list of SDK identities that the SDK requires be resolved in order to function.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SDK", Justification = "Not worth breaking consumers")]
public string DependsOnSDK { get; private set; }
/// <summary>
/// ProductFamilyName specifies the product family for the SDK. This is offered up as metadata on the resolved sdkreference and is used to detect sdk conflicts.
/// </summary>
public string ProductFamilyName { get; private set; }
/// <summary>
/// The platform the SDK targets.
/// </summary>
public string TargetPlatform { get; private set; }
/// <summary>
/// Minimum version of the platform the SDK supports.
/// </summary>
public string TargetPlatformMinVersion { get; private set; }
/// <summary>
/// Maximum version of the platform that the SDK supports.
/// </summary>
public string TargetPlatformVersion { get; private set; }
/// <summary>
/// DisplayName found in the sdk manifest
/// </summary>
public string DisplayName { get; private set; }
/// <summary>
/// MinVSVersion found in the sdk manifest
/// </summary>
public string MinVSVersion { get; private set; }
/// <summary>
/// MinOSVersion found in the sdk manifest, defaults to 6.2.1 for framework extension SDKs when manifest does not have this property set
/// </summary>
public string MinOSVersion => _minOSVersion == null && IsFrameworkExtensionSdkManifest
? s_defaultMinOSVersion
: _minOSVersion;
/// <summary>
/// MaxPlatformVersion found in the sdk manifest, defaults to 8.0 for framework extension SDKs when manifest does not have this property set
/// </summary>
public string MaxPlatformVersion => _maxPlatformVersion == null && IsFrameworkExtensionSdkManifest
? s_defaultMaxPlatformVersion
: _maxPlatformVersion;
/// <summary>
/// MaxOSVersionTested found in the sdk manifest, defaults to 6.2.1 for framework extension SDKs when manifest does not have this property set
/// </summary>
public string MaxOSVersionTested => _maxOSVersionTested == null && IsFrameworkExtensionSdkManifest
? s_defaultMaxOSVersionTested
: _maxOSVersionTested;
/// <summary>
/// MoreInfo as found in the sdk manifest
/// </summary>
public string MoreInfo { get; private set; }
/// <summary>
/// Flag set to true if an exception occurred while reading the manifest
/// </summary>
public bool ReadError { get; private set; }
/// <summary>
/// Message from exception thrown while reading manifest
/// </summary>
public string ReadErrorMessage { get; private set; }
/// <summary>
/// The contracts contained by this manifest, if any
/// Item1: Contract name
/// Item2: Contract version
/// </summary>
internal ICollection<ApiContract> ApiContracts { get; private set; }
/// <summary>
/// Decide on whether it is a framework extension sdk based on manifest's FrameworkIdentify and path
/// </summary>
private bool IsFrameworkExtensionSdkManifest => _frameworkIdentities?.Count > 0
&& _pathToSdk?.ToUpperInvariant().Contains(s_extensionSDKPathPattern) == true;
/// <summary>
/// Load content of SDKManifest.xml
/// </summary>
private void LoadManifestFile()
{
/*
Extension SDK Manifest:
<FileList
TargetPlatform="UAP"
TargetPlatformMinVersion="1.0.0.0"
TargetPlatformVersion="1.0.0.0"
SDKType = "Platform" | "Framework" | "External"
DisplayName = ""My SDK""
ProductFamilyName = ""UnitTest SDKs""
FrameworkIdentity-Debug = ""Name=MySDK.10.Debug, MinVersion=1.0.0.0""
FrameworkIdentity-Retail = ""Name=MySDK.10, MinVersion=1.0.0.0""
TargetFramework = "".NETCore, version=v4.5; .NETFramework, version=v4.5""
MinVSVersion = ""11.0""
AppliesTo = ""WindowsAppContainer + WindowsXAML""
SupportPrefer32Bit = ""True""
SupportedArchitectures = ""x86;x64;ARM""
SupportsMultipleVersions = ""Error""
AppX-Debug-x86 = "".\AppX\Debug\x86\Microsoft.MySDK.x86.Debug.1.0.appx""
AppX-Debug-x64 = "".\AppX\Debug\x64\Microsoft.MySDK.x64.Debug.1.0.appx""
AppX-Debug-ARM = "".\AppX\Debug\ARM\Microsoft.MySDK.ARM.Debug.1.0.appx""
AppX-Retail-x86 = "".\AppX\Retail\x86\Microsoft.MySDK.x86.1.0.appx""
AppX-Retail-x64 = "".\AppX\Retail\x64\Microsoft.MySDK.x64.1.0.appx""
AppX-Retail-ARM = "".\AppX\Retail\ARM\Microsoft.MySDK.ARM.1.0.appx""
CopyRedistToSubDirectory = "".""
DependsOn = ""SDKB, version=2.0""
MoreInfo = ""http://msdn.microsoft.com/MySDK""
MaxPlatformVersion = ""8.0""
MinOSVersion = ""6.2.1""
MaxOSVersionTested = ""6.2.1"">
<!-- New Style -->
<ContainedApiContracts>
<ApiContract name="UAP" version="1.0.0.0" />
</ContainedApiContracts>
<File Reference = ""MySDK.Sprint.winmd"" Implementation = ""XNASprintImpl.dll"">
<Registration Type = ""Flipper"" Implementation = ""XNASprintFlipperImpl.dll"" />
<Registration Type = ""Flexer"" Implementation = ""XNASprintFlexerImpl.dll"" />
<ToolboxItems VSCategory = ""Toolbox.Default"" />
</File>
</FileList>
Platform SDK Manifest:
<FileList
DisplayName = ""Windows""
PlatformIdentity = ""Windows, version=8.0""
TargetFramework = "".NETCore, version=v4.5; .NETFramework, version=v4.5""
MinVSVersion = ""11.0""
MinOSVersion = ""6.2.1""
MaxOSVersionTested = ""6.2.1""
UnsupportedDowntarget = ""Windows, version=8.0"">
<File Reference = ""Windows"">
<ToolboxItems VSCategory = ""Toolbox.Default""/>
</File>
</FileList>
*/
string sdkManifestPath = Path.Combine(_pathToSdk, "SDKManifest.xml");
try
{
if (FileSystems.Default.FileExists(sdkManifestPath))
{
XmlDocument doc = new XmlDocument();
XmlReaderSettings readerSettings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore, CloseInput = true };
FileStream fs = File.OpenRead(sdkManifestPath);
using (XmlReader xmlReader = XmlReader.Create(fs, readerSettings))
{
doc.Load(xmlReader);
}
XmlElement rootElement = null;
foreach (XmlNode childNode in doc.ChildNodes)
{
if (childNode.NodeType == XmlNodeType.Element &&
string.Equals(childNode.Name, Elements.FileList, StringComparison.Ordinal))
{
rootElement = (XmlElement)childNode;
break;
}
}
if (rootElement != null)
{
ReadFileListAttributes(rootElement.Attributes);
foreach (XmlNode childNode in rootElement.ChildNodes)
{
XmlElement childElement = childNode as XmlElement;
if (childElement == null)
{
continue;
}
if (ApiContract.IsContainedApiContractsElement(childElement.Name))
{
ApiContracts = new List<ApiContract>();
ApiContract.ReadContractsElement(childElement, ApiContracts);
}
}
}
}
}
catch (Exception e) when (!ExceptionHandling.IsCriticalException(e))
{
ReadError = true;
ReadErrorMessage = e.Message;
}
}
/// <summary>
/// Reads the attributes from the "FileList" element of the SDK manifest.
/// </summary>
private void ReadFileListAttributes(XmlAttributeCollection attributes)
{
foreach (XmlAttribute attribute in attributes.OfType<XmlAttribute>())
{
string value = attribute.Value.Trim();
if (value.Length > 0)
{
if (attribute.Name.StartsWith(Attributes.FrameworkIdentity, StringComparison.OrdinalIgnoreCase))
{
if (_frameworkIdentities == null)
{
_frameworkIdentities = new Dictionary<string, string>();
}
_frameworkIdentities.Add(attribute.Name, value);
continue;
}
if (attribute.Name.StartsWith(Attributes.APPX, StringComparison.OrdinalIgnoreCase))
{
if (_appxLocations == null)
{
_appxLocations = new Dictionary<string, string>();
}
_appxLocations.Add(attribute.Name, value);
continue;
}
switch (attribute.Name)
{
case Attributes.TargetPlatform:
TargetPlatform = value;
break;
case Attributes.TargetPlatformMinVersion:
TargetPlatformMinVersion = value;
break;
case Attributes.TargetPlatformVersion:
TargetPlatformVersion = value;
break;
case Attributes.MinVSVersion:
MinVSVersion = value;
break;
case Attributes.MinOSVersion:
_minOSVersion = value;
break;
case Attributes.MaxOSVersionTested:
_maxOSVersionTested = value;
break;
case Attributes.MaxPlatformVersion:
_maxPlatformVersion = value;
break;
case Attributes.PlatformIdentity:
PlatformIdentity = value;
break;
case Attributes.SupportPrefer32Bit:
SupportPrefer32Bit = value;
break;
case Attributes.SupportsMultipleVersions:
_supportsMultipleVersions = ParseSupportMultipleVersions(value);
break;
case Attributes.SDKType:
Enum.TryParse(value, out _sdkType);
break;
case Attributes.DisplayName:
DisplayName = value;
break;
case Attributes.MoreInfo:
MoreInfo = value;
break;
case Attributes.CopyRedistToSubDirectory:
CopyRedistToSubDirectory = value;
break;
case Attributes.SupportedArchitectures:
SupportedArchitectures = value;
break;
case Attributes.DependsOnSDK:
DependsOnSDK = value;
break;
case Attributes.ProductFamilyName:
ProductFamilyName = value;
break;
}
}
}
}
/// <summary>
/// Parse the multipleversions string. Returns MultipleVersionSupport.Allow if it cannot be parsed correctly.
/// </summary>
private static MultipleVersionSupport ParseSupportMultipleVersions(string multipleVersionsValue)
=> !string.IsNullOrEmpty(multipleVersionsValue) && Enum.TryParse(multipleVersionsValue, /*ignoreCase*/true, out MultipleVersionSupport supportsMultipleVersions)
? supportsMultipleVersions
: MultipleVersionSupport.Allow;
/// <summary>
/// Helper class with attributes of SDKManifest.xml
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Scope = "type", Target = "Microsoft.Build.Utilities.SDKManifest+Attributes", Justification = "Not worth breaking customers / spending resources to fix.")]
public static class Attributes
{
/// <summary>
/// Platform that the SDK targets
/// </summary>
public const string TargetPlatform = "TargetPlatform";
/// <summary>
/// The minimum version of the platform that the SDK targets
/// </summary>
public const string TargetPlatformMinVersion = "TargetPlatformMinVersion";
/// <summary>
/// The max version of the platform that the SDK targets
/// </summary>
public const string TargetPlatformVersion = "TargetPlatformVersion";
/// <summary>
/// Framework Identity metadata name and manifest attribute
/// </summary>
public const string FrameworkIdentity = "FrameworkIdentity";
/// <summary>
/// Supported Architectures metadata name and manifest attribute
/// </summary>
public const string SupportedArchitectures = "SupportedArchitectures";
/// <summary>
/// Prefer32BitSupport metadata name and manifest attribute
/// </summary>
public const string SupportPrefer32Bit = "SupportPrefer32Bit";
/// <summary>
/// AppxLocation metadata
/// </summary>
public const string AppxLocation = "AppxLocation";
/// <summary>
/// APPX manifest attribute
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "APPX", Justification = "Want to keep same case as the attribute in SDKManifest.xml")]
public const string APPX = "APPX";
/// <summary>
/// PlatformIdentity metadata name and manifest attribute
/// </summary>
public const string PlatformIdentity = "PlatformIdentity";
/// <summary>
/// SDKType metadata name and manifest attribute
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SDK", Justification = "Want to keep same case as the attribute in SDKManifest.xml")]
public const string SDKType = "SDKType";
/// <summary>
/// DisplayName metadata name and manifest attribute
/// </summary>
public const string DisplayName = "DisplayName";
/// <summary>
/// CopyRedistToSubDirectory metadata name and manifest attribute
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "SubDirectory", Justification = "Want to keep same case as in SDKManifest.sdk")]
public const string CopyRedistToSubDirectory = "CopyRedistToSubDirectory";
/// <summary>
/// ProductFamilyName metadata name and manifest attribute
/// </summary>
public const string ProductFamilyName = "ProductFamilyName";
/// <summary>
/// SupportsMultipleVersions metadata name and manifest attribute
/// </summary>
public const string SupportsMultipleVersions = "SupportsMultipleVersions";
/// <summary>
/// TargetedSDKArchitecture metadata name
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SDK", Justification = "Not worth breaking customers")]
public const string TargetedSDK = "TargetedSDKArchitecture";
/// <summary>
/// TargetedSDKConfiguration metadata name
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SDK", Justification = "Not worth breaking customers")]
public const string TargetedSDKConfiguration = "TargetedSDKConfiguration";
/// <summary>
/// ExpandReferenceAssemblies metadata name
/// </summary>
public const string ExpandReferenceAssemblies = "ExpandReferenceAssemblies";
/// <summary>
/// DependsOn metadata name
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SDK", Justification = "Not worth breaking customers")]
public const string DependsOnSDK = "DependsOn";
/// <summary>
/// CopyRedist metadata name
/// </summary>
public const string CopyRedist = "CopyRedist";
/// <summary>
/// CopyLocalExpandedReferenceAssemblies metadata name
/// </summary>
public const string CopyLocalExpandedReferenceAssemblies = "CopyLocalExpandedReferenceAssemblies";
// Dev 12 new attributes
/// <summary>
/// MinOSVersion metadata name
/// </summary>
public const string MinOSVersion = "MinOSVersion";
/// <summary>
/// MinVSVersion metadata name
/// </summary>
public const string MinVSVersion = "MinVSVersion";
/// <summary>
/// MaxPlatformVersionAttribute metadata name
/// </summary>
public const string MaxPlatformVersion = "MaxPlatformVersion";
/// <summary>
/// MoreInfoAttribute metadata name
/// </summary>
public const string MoreInfo = "MoreInfo";
/// <summary>
/// MaxOSVersionTestedAttribute metadata name
/// </summary>
public const string MaxOSVersionTested = "MaxOSVersionTested";
}
/// <summary>
/// Helper class with elements of SDKManifest.xml
/// </summary>
private static class Elements
{
/// <summary>
/// Root element
/// </summary>
public const string FileList = "FileList";
}
}
}
|