diff --git a/Directory.Build.props b/Directory.Build.props
index 784fa37b6f2b78..be3d42e18ba93f 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -228,7 +228,7 @@
https://github.com/dotnet/$(GitHubRepositoryName)
https://dot.net
microsoft,dotnetframework
- $([MSBuild]::NormalizePath('$(LibrariesProjectRoot)', 'Microsoft.NETCore.Platforms', 'pkg', 'runtime.json'))
+ $([MSBuild]::NormalizePath('$(LibrariesProjectRoot)', 'Microsoft.NETCore.Platforms', 'src', 'runtime.json'))
$(MSBuildThisFileDirectory)LICENSE.TXT
MIT
$(CopyrightNetFoundation)
diff --git a/eng/Versions.props b/eng/Versions.props
index bdb9ffcb33dbe7..d74990f09b32d7 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -139,12 +139,12 @@
These are used as reference assemblies only, so they must not take a ProdCon/source-build
version. Insert "RefOnly" to avoid assignment via PVP.
-->
- 15.7.179
+ 16.8.0
$(RefOnlyMicrosoftBuildVersion)
$(RefOnlyMicrosoftBuildVersion)
$(RefOnlyMicrosoftBuildVersion)
- 4.9.4
- 4.9.4
+ 5.8.0
+ 5.8.0
1.0.1-prerelease-00006
16.9.0-preview-20201201-01
diff --git a/src/libraries/Directory.Build.props b/src/libraries/Directory.Build.props
index 7803ca3e6f1c5b..f36f83de45246b 100644
--- a/src/libraries/Directory.Build.props
+++ b/src/libraries/Directory.Build.props
@@ -21,7 +21,7 @@
$(RepositoryEngineeringDir)LicenseHeader.txt
-
+
$([System.Text.RegularExpressions.Regex]::Replace('$(TargetFramework)', '(-[^;]+)', ''))
diff --git a/src/libraries/Directory.Build.targets b/src/libraries/Directory.Build.targets
index 96fc0a734f8fa3..a04a93dfb0d6be 100644
--- a/src/libraries/Directory.Build.targets
+++ b/src/libraries/Directory.Build.targets
@@ -116,7 +116,7 @@
-
+
diff --git a/src/libraries/Microsoft.NETCore.Platforms/Microsoft.NETCore.Platforms.sln b/src/libraries/Microsoft.NETCore.Platforms/Microsoft.NETCore.Platforms.sln
new file mode 100644
index 00000000000000..a31d754662018e
--- /dev/null
+++ b/src/libraries/Microsoft.NETCore.Platforms/Microsoft.NETCore.Platforms.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.31004.24
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.NETCore.Platforms", "src\Microsoft.NETCore.Platforms.csproj", "{BFFF96CC-06AA-4291-9F93-3E77F23DBB11}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.NETCore.Platforms.Tests", "tests\Microsoft.NETCore.Platforms.Tests.csproj", "{0C60F372-5C73-4BFA-9B91-5659C88F9750}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {BFFF96CC-06AA-4291-9F93-3E77F23DBB11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BFFF96CC-06AA-4291-9F93-3E77F23DBB11}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BFFF96CC-06AA-4291-9F93-3E77F23DBB11}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BFFF96CC-06AA-4291-9F93-3E77F23DBB11}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0C60F372-5C73-4BFA-9B91-5659C88F9750}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0C60F372-5C73-4BFA-9B91-5659C88F9750}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0C60F372-5C73-4BFA-9B91-5659C88F9750}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0C60F372-5C73-4BFA-9B91-5659C88F9750}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {E946A528-C3E7-48EC-AD6D-AE84ED2B11AC}
+ EndGlobalSection
+EndGlobal
diff --git a/src/libraries/Microsoft.NETCore.Platforms/pkg/Microsoft.NETCore.Platforms.pkgproj b/src/libraries/Microsoft.NETCore.Platforms/pkg/Microsoft.NETCore.Platforms.pkgproj
deleted file mode 100644
index e24d21ae013bb4..00000000000000
--- a/src/libraries/Microsoft.NETCore.Platforms/pkg/Microsoft.NETCore.Platforms.pkgproj
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
- true
-
- false
- Provides runtime information required to resolve target framework, platform, and runtime specific implementations of .NETCore packages.
-
-
-
-
-
- lib/netstandard1.0
-
-
-
-
-
diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/AssemblyResolver.cs b/src/libraries/Microsoft.NETCore.Platforms/src/AssemblyResolver.cs
new file mode 100644
index 00000000000000..a0f4aaad29bc0c
--- /dev/null
+++ b/src/libraries/Microsoft.NETCore.Platforms/src/AssemblyResolver.cs
@@ -0,0 +1,108 @@
+// 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.Diagnostics;
+using System.IO;
+using System.Reflection;
+
+namespace Microsoft.NETCore.Platforms.BuildTasks
+{
+ ///
+ /// Used to enable app-local assembly unification.
+ ///
+ internal static class AssemblyResolver
+ {
+ static AssemblyResolver()
+ {
+ AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
+ }
+
+ ///
+ /// Call to enable the assembly resolver for the current AppDomain.
+ ///
+ public static void Enable()
+ {
+ // intentionally empty. This is just meant to ensure the static constructor
+ // has run.
+ }
+
+ private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
+ {
+ // apply any existing policy
+ AssemblyName referenceName = new AssemblyName(AppDomain.CurrentDomain.ApplyPolicy(args.Name));
+
+ string fileName = referenceName.Name + ".dll";
+ string assemblyPath = null;
+ string probingPath = null;
+ Assembly assm = null;
+
+ // look next to requesting assembly
+ assemblyPath = args.RequestingAssembly?.Location;
+ if (!string.IsNullOrEmpty(assemblyPath))
+ {
+ probingPath = Path.Combine(Path.GetDirectoryName(assemblyPath), fileName);
+ Debug.WriteLine($"Considering {probingPath} based on RequestingAssembly");
+ if (Probe(probingPath, referenceName.Version, out assm))
+ {
+ return assm;
+ }
+ }
+
+ // look next to the executing assembly
+ assemblyPath = Assembly.GetExecutingAssembly().Location;
+ if (!string.IsNullOrEmpty(assemblyPath))
+ {
+ probingPath = Path.Combine(Path.GetDirectoryName(assemblyPath), fileName);
+
+ Debug.WriteLine($"Considering {probingPath} based on ExecutingAssembly");
+ if (Probe(probingPath, referenceName.Version, out assm))
+ {
+ return assm;
+ }
+ }
+
+ // look in AppDomain base directory
+ probingPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);
+ Debug.WriteLine($"Considering {probingPath} based on BaseDirectory");
+ if (Probe(probingPath, referenceName.Version, out assm))
+ {
+ return assm;
+ }
+
+ // look in current directory
+ Debug.WriteLine($"Considering {fileName}");
+ if (Probe(fileName, referenceName.Version, out assm))
+ {
+ return assm;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Considers a path to load for satisfying an assembly ref and loads it
+ /// if the file exists and version is sufficient.
+ ///
+ /// Path to consider for load
+ /// Minimum version to consider
+ /// loaded assembly
+ /// true if assembly was loaded
+ private static bool Probe(string filePath, Version minimumVersion, out Assembly assembly)
+ {
+ if (File.Exists(filePath))
+ {
+ AssemblyName name = AssemblyName.GetAssemblyName(filePath);
+
+ if (name.Version >= minimumVersion)
+ {
+ assembly = Assembly.Load(name);
+ return true;
+ }
+ }
+
+ assembly = null;
+ return false;
+ }
+ }
+}
diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/BuildTask.Desktop.cs b/src/libraries/Microsoft.NETCore.Platforms/src/BuildTask.Desktop.cs
new file mode 100644
index 00000000000000..29af5f186cfb41
--- /dev/null
+++ b/src/libraries/Microsoft.NETCore.Platforms/src/BuildTask.Desktop.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.NETCore.Platforms.BuildTasks
+{
+ public partial class BuildTask
+ {
+ static BuildTask()
+ {
+ AssemblyResolver.Enable();
+ }
+ }
+}
diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/BuildTask.cs b/src/libraries/Microsoft.NETCore.Platforms/src/BuildTask.cs
new file mode 100644
index 00000000000000..fa7dd7edabda9a
--- /dev/null
+++ b/src/libraries/Microsoft.NETCore.Platforms/src/BuildTask.cs
@@ -0,0 +1,151 @@
+// 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;
+
+namespace Microsoft.NETCore.Platforms.BuildTasks
+{
+ public abstract partial class BuildTask : ITask
+ {
+ private Log _log;
+
+ internal Log Log
+ {
+ get { return _log ?? (_log = new Log(new TaskLoggingHelper(this))); }
+ }
+
+ public BuildTask()
+ {
+ }
+
+ public IBuildEngine BuildEngine
+ {
+ get;
+ set;
+ }
+
+ public ITaskHost HostObject
+ {
+ get;
+ set;
+ }
+
+ public abstract bool Execute();
+ }
+
+ internal class Log : ILog
+ {
+ private readonly TaskLoggingHelper _logger;
+ public Log(TaskLoggingHelper logger)
+ {
+ _logger = logger;
+ }
+
+ public void LogError(string message, params object[] messageArgs)
+ {
+ _logger.LogError(message, messageArgs);
+ }
+
+ public void LogErrorFromException(Exception exception, bool showStackTrace)
+ {
+ _logger.LogErrorFromException(exception, showStackTrace);
+ }
+
+ public void LogMessage(string message, params object[] messageArgs)
+ {
+ _logger.LogMessage(message, messageArgs);
+ }
+
+ public void LogMessage(LogImportance importance, string message, params object[] messageArgs)
+ {
+ _logger.LogMessage((MessageImportance)importance, message, messageArgs);
+ }
+
+ public void LogWarning(string message, params object[] messageArgs)
+ {
+ _logger.LogWarning(message, messageArgs);
+ }
+
+ public bool HasLoggedErrors { get { return _logger.HasLoggedErrors; } }
+ }
+
+ public enum LogImportance
+ {
+ Low = MessageImportance.Low,
+ Normal = MessageImportance.Normal,
+ High = MessageImportance.High
+ }
+
+
+ public interface ILog
+ {
+ //
+ // Summary:
+ // Logs an error with the specified message.
+ //
+ // Parameters:
+ // message:
+ // The message.
+ //
+ // messageArgs:
+ // Optional arguments for formatting the message string.
+ //
+ // Exceptions:
+ // T:System.ArgumentNullException:
+ // message is null.
+ void LogError(string message, params object[] messageArgs);
+
+ //
+ // Summary:
+ // Logs a message with the specified string.
+ //
+ // Parameters:
+ // message:
+ // The message.
+ //
+ // messageArgs:
+ // The arguments for formatting the message.
+ //
+ // Exceptions:
+ // T:System.ArgumentNullException:
+ // message is null.
+ void LogMessage(string message, params object[] messageArgs);
+
+ //
+ // Summary:
+ // Logs a message with the specified string and importance.
+ //
+ // Parameters:
+ // importance:
+ // One of the enumeration values that specifies the importance of the message.
+ //
+ // message:
+ // The message.
+ //
+ // messageArgs:
+ // The arguments for formatting the message.
+ //
+ // Exceptions:
+ // T:System.ArgumentNullException:
+ // message is null.
+ void LogMessage(LogImportance importance, string message, params object[] messageArgs);
+
+ //
+ // Summary:
+ // Logs a warning with the specified message.
+ //
+ // Parameters:
+ // message:
+ // The message.
+ //
+ // messageArgs:
+ // Optional arguments for formatting the message string.
+ //
+ // Exceptions:
+ // T:System.ArgumentNullException:
+ // message is null.
+ void LogWarning(string message, params object[] messageArgs);
+ }
+}
diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/Directory.Build.props b/src/libraries/Microsoft.NETCore.Platforms/src/Directory.Build.props
new file mode 100644
index 00000000000000..ea706165b9f58f
--- /dev/null
+++ b/src/libraries/Microsoft.NETCore.Platforms/src/Directory.Build.props
@@ -0,0 +1,6 @@
+
+
+ false
+
+
+
\ No newline at end of file
diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/Extensions.cs b/src/libraries/Microsoft.NETCore.Platforms/src/Extensions.cs
new file mode 100644
index 00000000000000..128b56082e1fec
--- /dev/null
+++ b/src/libraries/Microsoft.NETCore.Platforms/src/Extensions.cs
@@ -0,0 +1,46 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Build.Framework;
+
+namespace Microsoft.NETCore.Platforms.BuildTasks
+{
+ public static class Extensions
+ {
+ public static string GetString(this ITaskItem taskItem, string metadataName)
+ {
+ var metadataValue = taskItem.GetMetadata(metadataName)?.Trim();
+ return string.IsNullOrEmpty(metadataValue) ? null : metadataValue;
+ }
+
+ public static bool GetBoolean(this ITaskItem taskItem, string metadataName, bool defaultValue = false)
+ {
+ bool result = false;
+ var metadataValue = taskItem.GetMetadata(metadataName);
+ if (!bool.TryParse(metadataValue, out result))
+ {
+ result = defaultValue;
+ }
+ return result;
+ }
+
+ public static IEnumerable GetStrings(this ITaskItem taskItem, string metadataName)
+ {
+ var metadataValue = taskItem.GetMetadata(metadataName)?.Trim();
+ if (!string.IsNullOrEmpty(metadataValue))
+ {
+ return metadataValue.Split(';').Where(v => !string.IsNullOrEmpty(v.Trim())).ToArray();
+ }
+
+ return Enumerable.Empty();
+ }
+
+ public static IEnumerable NullAsEmpty(this IEnumerable source)
+ {
+ return source ?? Enumerable.Empty();
+ }
+
+ }
+}
diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/GenerateRuntimeGraph.cs b/src/libraries/Microsoft.NETCore.Platforms/src/GenerateRuntimeGraph.cs
new file mode 100644
index 00000000000000..cbee78b71a6b06
--- /dev/null
+++ b/src/libraries/Microsoft.NETCore.Platforms/src/GenerateRuntimeGraph.cs
@@ -0,0 +1,392 @@
+// 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.IO;
+using System.Linq;
+using System.Text;
+using System.Xml.Linq;
+using Microsoft.Build.Framework;
+using Newtonsoft.Json;
+using NuGet.RuntimeModel;
+
+namespace Microsoft.NETCore.Platforms.BuildTasks
+{
+ public class GenerateRuntimeGraph : BuildTask
+ {
+
+ ///
+ /// A set of RuntimeGroups that can be used to generate a runtime graph
+ /// Identity: the base string for the RID, without version architecture, or qualifiers.
+ /// Parent: the base string for the parent of this RID. This RID will be imported by the baseRID, architecture-specific,
+ /// and qualifier-specific RIDs (with the latter two appending appropriate architecture and qualifiers).
+ /// Versions: A list of strings delimited by semi-colons that represent the versions for this RID.
+ /// TreatVersionsAsCompatible: Default is true. When true, version-specific RIDs will import the previous
+ /// version-specific RID in the Versions list, with the first version importing the version-less RID.
+ /// When false all version-specific RIDs will import the version-less RID (bypassing previous version-specific RIDs)
+ /// OmitVersionDelimiter: Default is false. When true no characters will separate the base RID and version (EG: win7).
+ /// When false a '.' will separate the base RID and version (EG: osx.10.12).
+ /// ApplyVersionsToParent: Default is false. When true, version-specific RIDs will import version-specific Parent RIDs
+ /// similar to is done for architecture and qualifier (see Parent above).
+ /// Architectures: A list of strings delimited by semi-colons that represent the architectures for this RID.
+ /// AdditionalQualifiers: A list of strings delimited by semi-colons that represent the additional qualifiers for this RID.
+ /// Additional qualifers do not stack, each only applies to the qualifier-less RIDs (so as not to cause combinatorial
+ /// exponential growth of RIDs).
+ ///
+ /// The following options can be used under special circumstances but break the normal precedence rules we try to establish
+ /// by generating the RID graph from common logic. These options make it possible to create a RID fallback chain that doesn't
+ /// match the rest of the RIDs and therefore is hard for developers/package authors to reason about.
+ /// Only use these options for cases where you know what you are doing and have carefully reviewed the resulting RID fallbacks
+ /// using the CompatibliltyMap.
+ /// OmitRIDs: A list of strings delimited by semi-colons that represent RIDs calculated from this RuntimeGroup that should
+ /// be omitted from the RuntimeGraph. These RIDs will not be referenced nor defined.
+ /// OmitRIDDefinitions: A list of strings delimited by semi-colons that represent RIDs calculated from this RuntimeGroup
+ /// that should be omitted from the RuntimeGraph. These RIDs will not be defined by this RuntimeGroup, but will be
+ /// referenced: useful in case some other RuntimeGroup (or runtime.json template) defines them.
+ /// OmitRIDReferences: A list of strings delimited by semi-colons that represent RIDs calculated from this RuntimeGroup
+ /// that should be omitted from the RuntimeGraph. These RIDs will be defined but not referenced by this RuntimeGroup.
+ ///
+ public ITaskItem[] RuntimeGroups
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Optional source Runtime.json to use as a starting point when merging additional RuntimeGroups
+ ///
+ public string SourceRuntimeJson
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Where to write the final runtime.json
+ ///
+ public string RuntimeJson
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Optionally, other runtime.jsons which may contain imported RIDs
+ ///
+ public string[] ExternalRuntimeJsons
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// When defined, specifies the file to write compatibility precedence for each RID in the graph.
+ ///
+ public string CompatibilityMap
+ {
+ get;
+ set;
+ }
+
+
+ ///
+ /// True to write the generated runtime.json to RuntimeJson and compatibility map to CompatibilityMap, otherwise files are read and diffed
+ /// with generated versions and an error is emitted if they differ.
+ /// Setting UpdateRuntimeFiles will overwrite files even when the file is marked ReadOnly.
+ ///
+ public bool UpdateRuntimeFiles
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// When defined, specifies the file to write a DGML representation of the runtime graph.
+ ///
+ public string RuntimeDirectedGraph
+ {
+ get;
+ set;
+ }
+
+ public override bool Execute()
+ {
+ if (RuntimeGroups != null && RuntimeGroups.Any() && RuntimeJson == null)
+ {
+ Log.LogError($"{nameof(RuntimeJson)} argument must be specified when {nameof(RuntimeGroups)} is specified.");
+ return false;
+ }
+
+ RuntimeGraph runtimeGraph;
+ if (!string.IsNullOrEmpty(SourceRuntimeJson))
+ {
+ if (!File.Exists(SourceRuntimeJson))
+ {
+ Log.LogError($"{nameof(SourceRuntimeJson)} did not exist at {SourceRuntimeJson}.");
+ return false;
+ }
+
+ runtimeGraph = JsonRuntimeFormat.ReadRuntimeGraph(SourceRuntimeJson);
+ }
+ else
+ {
+ runtimeGraph = new RuntimeGraph();
+ }
+
+ foreach (var runtimeGroup in RuntimeGroups.NullAsEmpty().Select(i => new RuntimeGroup(i)))
+ {
+ runtimeGraph = SafeMerge(runtimeGraph, runtimeGroup);
+ }
+
+ Dictionary externalRids = new Dictionary();
+ if (ExternalRuntimeJsons != null)
+ {
+ foreach (var externalRuntimeJson in ExternalRuntimeJsons)
+ {
+ RuntimeGraph externalRuntimeGraph = JsonRuntimeFormat.ReadRuntimeGraph(externalRuntimeJson);
+
+ foreach (var runtime in externalRuntimeGraph.Runtimes.Keys)
+ {
+ // don't check for duplicates, we merely care what is external
+ externalRids.Add(runtime, externalRuntimeJson);
+ }
+ }
+ }
+
+ ValidateImports(runtimeGraph, externalRids);
+
+ if (!string.IsNullOrEmpty(RuntimeJson))
+ {
+ if (UpdateRuntimeFiles)
+ {
+ EnsureWritable(RuntimeJson);
+ WriteRuntimeGraph(RuntimeJson, runtimeGraph);
+
+ }
+ else
+ {
+ // validate that existing file matches generated file
+ if (!File.Exists(RuntimeJson))
+ {
+ Log.LogError($"{nameof(RuntimeJson)} did not exist at {RuntimeJson} and {nameof(UpdateRuntimeFiles)} was not specified.");
+ }
+ else
+ {
+ var existingRuntimeGraph = JsonRuntimeFormat.ReadRuntimeGraph(RuntimeJson);
+
+ if (!existingRuntimeGraph.Equals(runtimeGraph))
+ {
+ Log.LogError($"The generated {nameof(RuntimeJson)} differs from {RuntimeJson} and {nameof(UpdateRuntimeFiles)} was not specified. Please specify {nameof(UpdateRuntimeFiles)}=true to commit the changes.");
+ }
+ }
+ }
+ }
+
+ if (!string.IsNullOrEmpty(CompatibilityMap))
+ {
+ var compatibilityMap = GetCompatibilityMap(runtimeGraph);
+ if (UpdateRuntimeFiles)
+ {
+ EnsureWritable(CompatibilityMap);
+ WriteCompatibilityMap(compatibilityMap, CompatibilityMap);
+ }
+ else
+ {
+ // validate that existing file matches generated file
+ if (!File.Exists(CompatibilityMap))
+ {
+ Log.LogError($"{nameof(CompatibilityMap)} did not exist at {CompatibilityMap} and {nameof(UpdateRuntimeFiles)} was not specified.");
+ }
+ else
+ {
+ var existingCompatibilityMap = ReadCompatibilityMap(CompatibilityMap);
+
+ if (!CompatibilityMapEquals(existingCompatibilityMap, compatibilityMap))
+ {
+ Log.LogError($"The generated {nameof(CompatibilityMap)} differs from {CompatibilityMap} and {nameof(UpdateRuntimeFiles)} was not specified. Please specify {nameof(UpdateRuntimeFiles)}=true to commit the changes.");
+ }
+ }
+ }
+ }
+
+ if (!string.IsNullOrEmpty(RuntimeDirectedGraph))
+ {
+ WriteRuntimeGraph(runtimeGraph, RuntimeDirectedGraph);
+ }
+
+ return !Log.HasLoggedErrors;
+ }
+
+ private void EnsureWritable(string file)
+ {
+ if (File.Exists(file))
+ {
+ var existingAttributes = File.GetAttributes(file);
+
+ if ((existingAttributes & FileAttributes.ReadOnly) != 0)
+ {
+ File.SetAttributes(file, existingAttributes &= ~FileAttributes.ReadOnly);
+ }
+ }
+ }
+
+ public static void WriteRuntimeGraph(string filePath, RuntimeGraph runtimeGraph)
+ {
+ using (var fileStream = new FileStream(filePath, FileMode.Create))
+ using (var textWriter = new StreamWriter(fileStream))
+ using (var jsonWriter = new JsonTextWriter(textWriter))
+ using (var writer = new JsonObjectWriter(jsonWriter))
+ {
+ jsonWriter.Formatting = Formatting.Indented;
+
+ // workaround https://github.com/NuGet/Home/issues/9532
+ writer.WriteObjectStart();
+
+ JsonRuntimeFormat.WriteRuntimeGraph(writer, runtimeGraph);
+
+ writer.WriteObjectEnd();
+ }
+ }
+
+ private RuntimeGraph SafeMerge(RuntimeGraph existingGraph, RuntimeGroup runtimeGroup)
+ {
+ var runtimeGraph = runtimeGroup.GetRuntimeGraph();
+
+ foreach (var existingRuntimeDescription in existingGraph.Runtimes.Values)
+ {
+ RuntimeDescription newRuntimeDescription;
+
+ if (runtimeGraph.Runtimes.TryGetValue(existingRuntimeDescription.RuntimeIdentifier, out newRuntimeDescription))
+ {
+ // overlapping RID, ensure that the imports match (same ordering and content)
+ if (!existingRuntimeDescription.InheritedRuntimes.SequenceEqual(newRuntimeDescription.InheritedRuntimes))
+ {
+ Log.LogError($"RuntimeGroup {runtimeGroup.BaseRID} defines RID {newRuntimeDescription.RuntimeIdentifier} with imports {string.Join(";", newRuntimeDescription.InheritedRuntimes)} which differ from existing imports {string.Join(";", existingRuntimeDescription.InheritedRuntimes)}. You may avoid this by specifying {nameof(RuntimeGroup.OmitRIDDefinitions)} metadata with {newRuntimeDescription.RuntimeIdentifier}.");
+ }
+ }
+ }
+
+ return RuntimeGraph.Merge(existingGraph, runtimeGraph);
+ }
+
+ private void ValidateImports(RuntimeGraph runtimeGraph, IDictionary externalRIDs)
+ {
+ foreach (var runtimeDescription in runtimeGraph.Runtimes.Values)
+ {
+ string externalRuntimeJson;
+
+ if (externalRIDs.TryGetValue(runtimeDescription.RuntimeIdentifier, out externalRuntimeJson))
+ {
+ Log.LogError($"Runtime {runtimeDescription.RuntimeIdentifier} is defined in both this RuntimeGraph and {externalRuntimeJson}.");
+ }
+
+ foreach (var import in runtimeDescription.InheritedRuntimes)
+ {
+ if (!runtimeGraph.Runtimes.ContainsKey(import) && !externalRIDs.ContainsKey(import))
+ {
+ Log.LogError($"Runtime {runtimeDescription.RuntimeIdentifier} imports {import} which is not defined.");
+ }
+ }
+ }
+ }
+
+ private static IDictionary> GetCompatibilityMap(RuntimeGraph graph)
+ {
+ Dictionary> compatibilityMap = new Dictionary>();
+
+ foreach (var rid in graph.Runtimes.Keys.OrderBy(rid => rid, StringComparer.Ordinal))
+ {
+ compatibilityMap.Add(rid, graph.ExpandRuntime(rid));
+ }
+
+ return compatibilityMap;
+ }
+
+ private static IDictionary> ReadCompatibilityMap(string mapFile)
+ {
+ var serializer = new JsonSerializer();
+ using (var file = File.OpenText(mapFile))
+ using (var jsonTextReader = new JsonTextReader(file))
+ {
+ return serializer.Deserialize>>(jsonTextReader);
+ }
+ }
+
+ private static void WriteCompatibilityMap(IDictionary> compatibilityMap, string mapFile)
+ {
+ var serializer = new JsonSerializer()
+ {
+ Formatting = Formatting.Indented,
+ StringEscapeHandling = StringEscapeHandling.EscapeNonAscii
+ };
+
+ string directory = Path.GetDirectoryName(mapFile);
+ if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
+ {
+ Directory.CreateDirectory(directory);
+ }
+
+ using (var file = File.CreateText(mapFile))
+ {
+ serializer.Serialize(file, compatibilityMap);
+ }
+ }
+
+ private static bool CompatibilityMapEquals(IDictionary> left, IDictionary> right)
+ {
+ if (left.Count != right.Count)
+ {
+ return false;
+ }
+
+ foreach (var leftPair in left)
+ {
+ IEnumerable rightValue;
+
+ if (!right.TryGetValue(leftPair.Key, out rightValue))
+ {
+ return false;
+ }
+
+ if (!rightValue.SequenceEqual(leftPair.Value))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static XNamespace s_dgmlns = @"http://schemas.microsoft.com/vs/2009/dgml";
+ private static void WriteRuntimeGraph(RuntimeGraph graph, string dependencyGraphFilePath)
+ {
+
+ var doc = new XDocument(new XElement(s_dgmlns + "DirectedGraph"));
+ var nodesElement = new XElement(s_dgmlns + "Nodes");
+ var linksElement = new XElement(s_dgmlns + "Links");
+ doc.Root.Add(nodesElement);
+ doc.Root.Add(linksElement);
+
+ var nodeIds = new HashSet(StringComparer.OrdinalIgnoreCase);
+
+ foreach (var runtimeDescription in graph.Runtimes.Values)
+ {
+ nodesElement.Add(new XElement(s_dgmlns + "Node",
+ new XAttribute("Id", runtimeDescription.RuntimeIdentifier)));
+
+ foreach (var import in runtimeDescription.InheritedRuntimes)
+ {
+ linksElement.Add(new XElement(s_dgmlns + "Link",
+ new XAttribute("Source", runtimeDescription.RuntimeIdentifier),
+ new XAttribute("Target", import)));
+ }
+ }
+
+ using (var file = File.Create(dependencyGraphFilePath))
+ {
+ doc.Save(file);
+ }
+ }
+ }
+}
diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/Microsoft.NETCore.Platforms.csproj b/src/libraries/Microsoft.NETCore.Platforms/src/Microsoft.NETCore.Platforms.csproj
index 987d53fcabe74f..3c00187ddb18e1 100644
--- a/src/libraries/Microsoft.NETCore.Platforms/src/Microsoft.NETCore.Platforms.csproj
+++ b/src/libraries/Microsoft.NETCore.Platforms/src/Microsoft.NETCore.Platforms.csproj
@@ -1,9 +1,42 @@
-
+
- netstandard1.0
- true
-
- false
+ $(NetCoreAppToolCurrent);net472
+ $(MSBuildProjectName)
+ true
+ Microsoft.NETCore.Platforms.BuildTasks
+
+ false
+ true
+ false
+ true
+ $(NoWarn);NU5128
+ Provides runtime information required to resolve target framework, platform, and runtime specific implementations of .NETCore packages.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/RID.cs b/src/libraries/Microsoft.NETCore.Platforms/src/RID.cs
new file mode 100644
index 00000000000000..357790fbfdd54d
--- /dev/null
+++ b/src/libraries/Microsoft.NETCore.Platforms/src/RID.cs
@@ -0,0 +1,59 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Text;
+
+namespace Microsoft.NETCore.Platforms.BuildTasks
+{
+ internal class RID
+ {
+ public string BaseRID { get; set; }
+ public string VersionDelimiter { get; set; }
+ public string Version { get; set; }
+ public string ArchitectureDelimiter { get; set; }
+ public string Architecture { get; set; }
+ public string QualifierDelimiter { get; set; }
+ public string Qualifier { get; set; }
+
+ public override string ToString()
+ {
+ StringBuilder builder = new StringBuilder(BaseRID);
+
+ if (HasVersion())
+ {
+ builder.Append(VersionDelimiter);
+ builder.Append(Version);
+ }
+
+ if (HasArchitecture())
+ {
+ builder.Append(ArchitectureDelimiter);
+ builder.Append(Architecture);
+ }
+
+ if (HasQualifier())
+ {
+ builder.Append(QualifierDelimiter);
+ builder.Append(Qualifier);
+ }
+
+ return builder.ToString();
+ }
+
+ public bool HasVersion()
+ {
+ return Version != null;
+ }
+
+ public bool HasArchitecture()
+ {
+ return Architecture != null;
+ }
+
+ public bool HasQualifier()
+ {
+ return Qualifier != null;
+ }
+ }
+
+}
diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/RuntimeGroup.cs b/src/libraries/Microsoft.NETCore.Platforms/src/RuntimeGroup.cs
new file mode 100644
index 00000000000000..2ddd60022c426a
--- /dev/null
+++ b/src/libraries/Microsoft.NETCore.Platforms/src/RuntimeGroup.cs
@@ -0,0 +1,264 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Build.Framework;
+using NuGet.RuntimeModel;
+
+namespace Microsoft.NETCore.Platforms.BuildTasks
+{
+
+ internal class RuntimeGroup
+ {
+ private const string rootRID = "any";
+ private const char VersionDelimiter = '.';
+ private const char ArchitectureDelimiter = '-';
+ private const char QualifierDelimiter = '-';
+
+ public RuntimeGroup(ITaskItem item)
+ {
+ BaseRID = item.ItemSpec;
+ Parent = item.GetString(nameof(Parent));
+ Versions = item.GetStrings(nameof(Versions));
+ TreatVersionsAsCompatible = item.GetBoolean(nameof(TreatVersionsAsCompatible), true);
+ OmitVersionDelimiter = item.GetBoolean(nameof(OmitVersionDelimiter));
+ ApplyVersionsToParent = item.GetBoolean(nameof(ApplyVersionsToParent));
+ Architectures = item.GetStrings(nameof(Architectures));
+ AdditionalQualifiers = item.GetStrings(nameof(AdditionalQualifiers));
+ OmitRIDs = new HashSet(item.GetStrings(nameof(OmitRIDs)));
+ OmitRIDDefinitions = new HashSet(item.GetStrings(nameof(OmitRIDDefinitions)));
+ OmitRIDReferences = new HashSet(item.GetStrings(nameof(OmitRIDReferences)));
+ }
+
+ public string BaseRID { get; }
+ public string Parent { get; }
+ public IEnumerable Versions { get; }
+ public bool TreatVersionsAsCompatible { get; }
+ public bool OmitVersionDelimiter { get; }
+ public bool ApplyVersionsToParent { get; }
+ public IEnumerable Architectures { get; }
+ public IEnumerable AdditionalQualifiers { get; }
+ public ICollection OmitRIDs { get; }
+ public ICollection OmitRIDDefinitions { get; }
+ public ICollection OmitRIDReferences { get; }
+
+ private class RIDMapping
+ {
+ public RIDMapping(RID runtimeIdentifier)
+ {
+ RuntimeIdentifier = runtimeIdentifier;
+ Imports = Enumerable.Empty();
+ }
+
+ public RIDMapping(RID runtimeIdentifier, IEnumerable imports)
+ {
+ RuntimeIdentifier = runtimeIdentifier;
+ Imports = imports;
+ }
+
+ public RID RuntimeIdentifier { get; }
+
+ public IEnumerable Imports { get; }
+ }
+
+ private RID CreateRuntime(string baseRid, string version = null, string architecture = null, string qualifier = null)
+ {
+ return new RID()
+ {
+ BaseRID = baseRid,
+ VersionDelimiter = OmitVersionDelimiter ? string.Empty : VersionDelimiter.ToString(),
+ Version = version,
+ ArchitectureDelimiter = ArchitectureDelimiter.ToString(),
+ Architecture = architecture,
+ QualifierDelimiter = QualifierDelimiter.ToString(),
+ Qualifier = qualifier
+ };
+ }
+
+ private IEnumerable GetRIDMappings()
+ {
+ // base =>
+ // Parent
+ yield return Parent == null ?
+ new RIDMapping(CreateRuntime(BaseRID)) :
+ new RIDMapping(CreateRuntime(BaseRID), new[] { CreateRuntime(Parent) });
+
+ foreach (var architecture in Architectures)
+ {
+ // base + arch =>
+ // base,
+ // parent + arch
+ var imports = new List()
+ {
+ CreateRuntime(BaseRID)
+ };
+
+ if (!IsNullOrRoot(Parent))
+ {
+ imports.Add(CreateRuntime(Parent, architecture: architecture));
+ }
+
+ yield return new RIDMapping(CreateRuntime(BaseRID, architecture: architecture), imports);
+ }
+
+ string lastVersion = null;
+ foreach (var version in Versions)
+ {
+ // base + version =>
+ // base + lastVersion,
+ // parent + version (optionally)
+ var imports = new List()
+ {
+ CreateRuntime(BaseRID, version: lastVersion)
+ };
+
+ if (ApplyVersionsToParent)
+ {
+ imports.Add(CreateRuntime(Parent, version: version));
+ }
+
+ yield return new RIDMapping(CreateRuntime(BaseRID, version: version), imports);
+
+ foreach (var architecture in Architectures)
+ {
+ // base + version + architecture =>
+ // base + version,
+ // base + lastVersion + architecture,
+ // parent + version + architecture (optionally)
+ var archImports = new List()
+ {
+ CreateRuntime(BaseRID, version: version),
+ CreateRuntime(BaseRID, version: lastVersion, architecture: architecture)
+ };
+
+ if (ApplyVersionsToParent)
+ {
+ archImports.Add(CreateRuntime(Parent, version: version, architecture: architecture));
+ }
+
+ yield return new RIDMapping(CreateRuntime(BaseRID, version: version, architecture: architecture), archImports);
+ }
+
+ if (TreatVersionsAsCompatible)
+ {
+ lastVersion = version;
+ }
+ }
+
+ foreach (var qualifier in AdditionalQualifiers)
+ {
+ // base + qual =>
+ // base,
+ // parent + qual
+ yield return new RIDMapping(CreateRuntime(BaseRID, qualifier: qualifier),
+ new[]
+ {
+ CreateRuntime(BaseRID),
+ IsNullOrRoot(Parent) ? CreateRuntime(qualifier) : CreateRuntime(Parent, qualifier:qualifier)
+ });
+
+ foreach (var architecture in Architectures)
+ {
+ // base + arch + qualifier =>
+ // base + qualifier,
+ // base + arch
+ // parent + arch + qualifier
+ var imports = new List()
+ {
+ CreateRuntime(BaseRID, qualifier: qualifier),
+ CreateRuntime(BaseRID, architecture: architecture)
+ };
+
+ if (!IsNullOrRoot(Parent))
+ {
+ imports.Add(CreateRuntime(Parent, architecture: architecture, qualifier: qualifier));
+ }
+
+ yield return new RIDMapping(CreateRuntime(BaseRID, architecture: architecture, qualifier: qualifier), imports);
+ }
+
+ lastVersion = null;
+ foreach (var version in Versions)
+ {
+ // base + version + qualifier =>
+ // base + version,
+ // base + lastVersion + qualifier
+ // parent + version + qualifier (optionally)
+ var imports = new List()
+ {
+ CreateRuntime(BaseRID, version: version),
+ CreateRuntime(BaseRID, version: lastVersion, qualifier: qualifier)
+ };
+
+ if (ApplyVersionsToParent)
+ {
+ imports.Add(CreateRuntime(Parent, version: version, qualifier: qualifier));
+ }
+
+ yield return new RIDMapping(CreateRuntime(BaseRID, version: version, qualifier: qualifier), imports);
+
+ foreach (var architecture in Architectures)
+ {
+ // base + version + architecture + qualifier =>
+ // base + version + qualifier,
+ // base + version + architecture,
+ // base + version,
+ // base + lastVersion + architecture + qualifier,
+ // parent + version + architecture + qualifier (optionally)
+ var archImports = new List()
+ {
+ CreateRuntime(BaseRID, version: version, qualifier: qualifier),
+ CreateRuntime(BaseRID, version: version, architecture: architecture),
+ CreateRuntime(BaseRID, version: version),
+ CreateRuntime(BaseRID, version: lastVersion, architecture: architecture, qualifier: qualifier)
+ };
+
+ if (ApplyVersionsToParent)
+ {
+ imports.Add(CreateRuntime(Parent, version: version, architecture: architecture, qualifier: qualifier));
+ }
+
+ yield return new RIDMapping(CreateRuntime(BaseRID, version: version, architecture: architecture, qualifier: qualifier), archImports);
+ }
+
+ if (TreatVersionsAsCompatible)
+ {
+ lastVersion = version;
+ }
+ }
+ }
+ }
+
+ private bool IsNullOrRoot(string rid)
+ {
+ return rid == null || rid == rootRID;
+ }
+
+
+ public IEnumerable GetRuntimeDescriptions()
+ {
+ foreach (var mapping in GetRIDMappings())
+ {
+ var rid = mapping.RuntimeIdentifier.ToString();
+
+ if (OmitRIDs.Contains(rid) || OmitRIDDefinitions.Contains(rid))
+ {
+ continue;
+ }
+
+ var imports = mapping.Imports
+ .Select(i => i.ToString())
+ .Where(i => !OmitRIDs.Contains(i) && !OmitRIDReferences.Contains(i))
+ .ToArray();
+
+ yield return new RuntimeDescription(rid, imports);
+ }
+ }
+
+ public RuntimeGraph GetRuntimeGraph()
+ {
+ return new RuntimeGraph(GetRuntimeDescriptions());
+ }
+ }
+}
diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/_._ b/src/libraries/Microsoft.NETCore.Platforms/src/_._
new file mode 100644
index 00000000000000..e69de29bb2d1d6
diff --git a/src/libraries/Microsoft.NETCore.Platforms/pkg/runtime.compatibility.json b/src/libraries/Microsoft.NETCore.Platforms/src/runtime.compatibility.json
similarity index 100%
rename from src/libraries/Microsoft.NETCore.Platforms/pkg/runtime.compatibility.json
rename to src/libraries/Microsoft.NETCore.Platforms/src/runtime.compatibility.json
diff --git a/src/libraries/Microsoft.NETCore.Platforms/pkg/runtime.json b/src/libraries/Microsoft.NETCore.Platforms/src/runtime.json
similarity index 100%
rename from src/libraries/Microsoft.NETCore.Platforms/pkg/runtime.json
rename to src/libraries/Microsoft.NETCore.Platforms/src/runtime.json
diff --git a/src/libraries/Microsoft.NETCore.Platforms/pkg/runtimeGroups.props b/src/libraries/Microsoft.NETCore.Platforms/src/runtimeGroups.props
similarity index 91%
rename from src/libraries/Microsoft.NETCore.Platforms/pkg/runtimeGroups.props
rename to src/libraries/Microsoft.NETCore.Platforms/src/runtimeGroups.props
index 5d4a3edf9e9561..75f41db11693e4 100644
--- a/src/libraries/Microsoft.NETCore.Platforms/pkg/runtimeGroups.props
+++ b/src/libraries/Microsoft.NETCore.Platforms/src/runtimeGroups.props
@@ -262,15 +262,22 @@
-
+
+ <_generateRuntimeGraphTargetFramework Condition="'$(MSBuildRuntimeType)' == 'core'">$(NetCoreAppToolCurrent)
+ <_generateRuntimeGraphTargetFramework Condition="'$(MSBuildRuntimeType)' != 'core'">net472
+ <_generateRuntimeGraphTask>$([MSBuild]::NormalizePath('$(BaseOutputPath)', '$(_generateRuntimeGraphTargetFramework)-$(Configuration)', '$(AssemblyName).dll'))
+
+
+
+
-
+
diff --git a/src/libraries/Microsoft.NETCore.Platforms/tests/AssemblyInfo.cs b/src/libraries/Microsoft.NETCore.Platforms/tests/AssemblyInfo.cs
new file mode 100644
index 00000000000000..baad52408c410d
--- /dev/null
+++ b/src/libraries/Microsoft.NETCore.Platforms/tests/AssemblyInfo.cs
@@ -0,0 +1,6 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Xunit;
+
+[assembly: SkipOnMono("MSBuild is not supported on Browser", TestPlatforms.Browser)]
\ No newline at end of file
diff --git a/src/libraries/Microsoft.NETCore.Platforms/tests/GenerateRuntimeGraphTests.cs b/src/libraries/Microsoft.NETCore.Platforms/tests/GenerateRuntimeGraphTests.cs
new file mode 100644
index 00000000000000..652ca7f87ec334
--- /dev/null
+++ b/src/libraries/Microsoft.NETCore.Platforms/tests/GenerateRuntimeGraphTests.cs
@@ -0,0 +1,58 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Linq;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.NETCore.Platforms.BuildTasks.Tests
+{
+ public class GenerateRuntimeGraphTests
+ {
+ private Log _log;
+ private TestBuildEngine _engine;
+
+ public GenerateRuntimeGraphTests(ITestOutputHelper output)
+ {
+ _log = new Log(output);
+ _engine = new TestBuildEngine(_log);
+ }
+
+ [Fact]
+ public void CanCreateRuntimeGraph()
+ {
+ string runtimeFile = "runtime.json";
+
+ Project runtimeGroupProps = new Project("runtimeGroups.props");
+
+ ITaskItem[] runtimeGroups = runtimeGroupProps.GetItems("RuntimeGroupWithQualifiers")
+ .Select(i => CreateItem(i)).ToArray();
+
+ Assert.NotEmpty(runtimeGroups);
+
+ // will generate and compare to existing file.
+ GenerateRuntimeGraph task = new GenerateRuntimeGraph()
+ {
+ BuildEngine = _engine,
+ RuntimeGroups = runtimeGroups,
+ RuntimeJson = runtimeFile
+ };
+ task.Execute();
+
+ _log.AssertNoErrorsOrWarnings();
+ }
+
+ private static ITaskItem CreateItem(ProjectItem projectItem)
+ {
+ TaskItem item = new TaskItem(projectItem.EvaluatedInclude);
+ foreach(var metadatum in projectItem.Metadata)
+ {
+ item.SetMetadata(metadatum.Name, metadatum.EvaluatedValue);
+ }
+ return item;
+ }
+ }
+}
diff --git a/src/libraries/Microsoft.NETCore.Platforms/tests/Log.cs b/src/libraries/Microsoft.NETCore.Platforms/tests/Log.cs
new file mode 100644
index 00000000000000..d1e7368ec0882b
--- /dev/null
+++ b/src/libraries/Microsoft.NETCore.Platforms/tests/Log.cs
@@ -0,0 +1,56 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.NETCore.Platforms.BuildTasks.Tests
+{
+ internal class Log : ILog
+ {
+ private readonly ITestOutputHelper _output;
+
+ public Log(ITestOutputHelper output)
+ {
+ _output = output;
+ Reset();
+ }
+
+ public int ErrorsLogged { get; set; }
+ public int WarningsLogged { get; set; }
+
+ public void LogError(string message, params object[] messageArgs)
+ {
+ ErrorsLogged++;
+ _output.WriteLine("Error: " + message, messageArgs);
+ }
+
+ public void LogMessage(string message, params object[] messageArgs)
+ {
+ _output.WriteLine(message, messageArgs);
+ }
+
+ public void LogMessage(LogImportance importance, string message, params object[] messageArgs)
+ {
+ _output.WriteLine(message, messageArgs);
+ }
+
+ public void LogWarning(string message, params object[] messageArgs)
+ {
+ WarningsLogged++;
+ _output.WriteLine("Warning: " + message, messageArgs);
+ }
+
+ public void Reset()
+ {
+ ErrorsLogged = 0;
+ WarningsLogged = 0;
+ }
+
+ public void AssertNoErrorsOrWarnings()
+ {
+ Assert.Equal(0, ErrorsLogged);
+ Assert.Equal(0, WarningsLogged);
+ }
+ }
+}
diff --git a/src/libraries/Microsoft.NETCore.Platforms/tests/Microsoft.NETCore.Platforms.Tests.csproj b/src/libraries/Microsoft.NETCore.Platforms/tests/Microsoft.NETCore.Platforms.Tests.csproj
new file mode 100644
index 00000000000000..cb254a5cb5070b
--- /dev/null
+++ b/src/libraries/Microsoft.NETCore.Platforms/tests/Microsoft.NETCore.Platforms.Tests.csproj
@@ -0,0 +1,24 @@
+
+
+ $(NetCoreAppCurrent);net472
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/libraries/Microsoft.NETCore.Platforms/tests/TestBuildEngine.cs b/src/libraries/Microsoft.NETCore.Platforms/tests/TestBuildEngine.cs
new file mode 100644
index 00000000000000..67dfefbe8c8c9b
--- /dev/null
+++ b/src/libraries/Microsoft.NETCore.Platforms/tests/TestBuildEngine.cs
@@ -0,0 +1,56 @@
+// 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 System;
+using System.Collections;
+
+namespace Microsoft.NETCore.Platforms.BuildTasks.Tests
+{
+ public class TestBuildEngine : IBuildEngine
+ {
+ private ILog _log;
+
+ public TestBuildEngine(ILog log)
+ {
+ ColumnNumberOfTaskNode = 0;
+ ContinueOnError = true;
+ LineNumberOfTaskNode = 0;
+ ProjectFileOfTaskNode = "test";
+ _log = log;
+ }
+
+ public int ColumnNumberOfTaskNode { get; set; }
+
+ public bool ContinueOnError { get; set; }
+
+ public int LineNumberOfTaskNode { get; set; }
+
+ public string ProjectFileOfTaskNode { get; set; }
+
+ public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void LogCustomEvent(CustomBuildEventArgs e)
+ {
+ _log.LogMessage(e.Message);
+ }
+
+ public void LogErrorEvent(BuildErrorEventArgs e)
+ {
+ _log.LogError(e.Message);
+ }
+
+ public void LogMessageEvent(BuildMessageEventArgs e)
+ {
+ _log.LogMessage((LogImportance)e.Importance, e.Message);
+ }
+
+ public void LogWarningEvent(BuildWarningEventArgs e)
+ {
+ _log.LogWarning(e.Message);
+ }
+ }
+}