diff --git a/src/Tasks/Common/ConflictResolution/ConflictItem.cs b/src/Tasks/Common/ConflictResolution/ConflictItem.cs index 0484c7aaa411..c3d05cad024e 100644 --- a/src/Tasks/Common/ConflictResolution/ConflictItem.cs +++ b/src/Tasks/Common/ConflictResolution/ConflictItem.cs @@ -24,6 +24,11 @@ internal interface IConflictItem Version FileVersion { get; } string PackageId { get; } string DisplayName { get; } + + // NOTE: Technically this should be NuGetVersion because System.Version doesn't work with semver. + // However, the only scenarios we need to support this property for in conflict resolution is stable versions + // of System packages. PackageVersion will be null if System.Version can't parse the version (i.e. if is pre-release) + Version PackageVersion { get; } } // Wraps an ITask item and adds lazy evaluated properties used by Conflict resolution. @@ -103,7 +108,7 @@ public string FileName { if (_fileName == null) { - _fileName = OriginalItem == null ? String.Empty : OriginalItem.GetMetadata(MetadataNames.FileName) + OriginalItem.GetMetadata(MetadataNames.Extension); + _fileName = OriginalItem == null ? String.Empty : Path.GetFileName(OriginalItem.ItemSpec); } return _fileName; } @@ -165,7 +170,31 @@ public string PackageId } private set { _packageId = value; } } - + + private bool _hasPackageVersion; + private Version _packageVersion; + public Version PackageVersion + { + get + { + if (!_hasPackageVersion) + { + _packageVersion = null; + + var packageVersionString = OriginalItem?.GetMetadata(nameof(MetadataNames.NuGetPackageVersion)) ?? String.Empty; + + if (packageVersionString.Length != 0) + { + Version.TryParse(packageVersionString, out _packageVersion); + } + + // PackageVersion may be null but don't try to recalculate it + _hasPackageVersion = true; + } + + return _packageVersion; + } + } private string _sourcePath; public string SourcePath diff --git a/src/Tasks/Common/ConflictResolution/ConflictResolver.cs b/src/Tasks/Common/ConflictResolution/ConflictResolver.cs index fcf8f8ea0be3..cae075fe3a90 100644 --- a/src/Tasks/Common/ConflictResolution/ConflictResolver.cs +++ b/src/Tasks/Common/ConflictResolution/ConflictResolver.cs @@ -15,11 +15,13 @@ internal class ConflictResolver where TConflictItem : class, ICon private Dictionary winningItemsByKey = new Dictionary(); private ILog log; private PackageRank packageRank; + private PackageOverrideResolver packageOverrideResolver; - public ConflictResolver(PackageRank packageRank, ILog log) + public ConflictResolver(PackageRank packageRank, PackageOverrideResolver packageOverrideResolver, ILog log) { this.log = log; this.packageRank = packageRank; + this.packageOverrideResolver = packageOverrideResolver; } public void ResolveConflicts(IEnumerable conflictItems, Func getItemKey, @@ -87,6 +89,12 @@ public void ResolveConflicts(IEnumerable conflictItems, Func + /// A PackageOverride contains information about a package that overrides + /// a set of packages up to a certain version. + /// + /// + /// For example, Microsoft.NETCore.App overrides System.Console up to version 4.3.0, + /// System.IO up to version version 4.3.0, etc. + /// + internal class PackageOverride + { + public string PackageName { get; } + public Dictionary OverridenPackages { get; } + + private PackageOverride(string packageName, IEnumerable> overridenPackages) + { + PackageName = packageName; + + OverridenPackages = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (Tuple package in overridenPackages) + { + OverridenPackages[package.Item1] = package.Item2; + } + } + + public static PackageOverride Create(ITaskItem packageOverrideItem) + { + string packageName = packageOverrideItem.ItemSpec; + string overridenPackagesString = packageOverrideItem.GetMetadata(MetadataKeys.OverridenPackages); + + return new PackageOverride(packageName, CreateOverridenPackages(overridenPackagesString)); + } + + private static IEnumerable> CreateOverridenPackages(string overridenPackagesString) + { + if (!string.IsNullOrEmpty(overridenPackagesString)) + { + overridenPackagesString = overridenPackagesString.Trim(); + string[] overridenPackagesAndVersions = overridenPackagesString.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + foreach (string overridenPackagesAndVersion in overridenPackagesAndVersions) + { + string trimmedOverridenPackagesAndVersion = overridenPackagesAndVersion.Trim(); + int separatorIndex = trimmedOverridenPackagesAndVersion.IndexOf('|'); + if (separatorIndex != -1) + { + if (Version.TryParse(trimmedOverridenPackagesAndVersion.Substring(separatorIndex + 1), out Version version)) + { + yield return Tuple.Create(trimmedOverridenPackagesAndVersion.Substring(0, separatorIndex), version); + } + } + } + } + } + } +} diff --git a/src/Tasks/Common/ConflictResolution/PackageOverrideResolver.cs b/src/Tasks/Common/ConflictResolution/PackageOverrideResolver.cs new file mode 100644 index 000000000000..1c3d7aac8a85 --- /dev/null +++ b/src/Tasks/Common/ConflictResolution/PackageOverrideResolver.cs @@ -0,0 +1,106 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using Microsoft.Build.Framework; + +namespace Microsoft.NET.Build.Tasks.ConflictResolution +{ + /// + /// Resolves conflicts between items by allowing specific packages to override + /// all items coming from a set of packages up to a certain version of each package. + /// + internal class PackageOverrideResolver where TConflictItem : class, IConflictItem + { + private ITaskItem[] _packageOverrideItems; + private Lazy> _packageOverrides; + + public PackageOverrideResolver(ITaskItem[] packageOverrideItems) + { + _packageOverrideItems = packageOverrideItems; + _packageOverrides = new Lazy>(() => BuildPackageOverrides()); + } + + public Dictionary PackageOverrides => _packageOverrides.Value; + + private Dictionary BuildPackageOverrides() + { + Dictionary result; + + if (_packageOverrideItems?.Length > 0) + { + result = new Dictionary(_packageOverrideItems.Length, StringComparer.OrdinalIgnoreCase); + + foreach (ITaskItem packageOverrideItem in _packageOverrideItems) + { + PackageOverride packageOverride = PackageOverride.Create(packageOverrideItem); + + if (result.TryGetValue(packageOverride.PackageName, out PackageOverride existing)) + { + MergePackageOverrides(packageOverride, existing); + } + else + { + result[packageOverride.PackageName] = packageOverride; + } + } + } + else + { + result = null; + } + + return result; + } + + /// + /// Merges newPackageOverride into existingPackageOverride by adding all the new overriden packages + /// and taking the highest version when they both contain the same overriden package. + /// + private static void MergePackageOverrides(PackageOverride newPackageOverride, PackageOverride existingPackageOverride) + { + foreach (KeyValuePair newOverride in newPackageOverride.OverridenPackages) + { + if (existingPackageOverride.OverridenPackages.TryGetValue(newOverride.Key, out Version existingOverrideVersion)) + { + if (existingOverrideVersion < newOverride.Value) + { + existingPackageOverride.OverridenPackages[newOverride.Key] = newOverride.Value; + } + } + else + { + existingPackageOverride.OverridenPackages[newOverride.Key] = newOverride.Value; + } + } + } + + public TConflictItem Resolve(TConflictItem item1, TConflictItem item2) + { + if (PackageOverrides != null) + { + PackageOverride packageOverride; + Version version; + if (item1.PackageId != null + && PackageOverrides.TryGetValue(item1.PackageId, out packageOverride) + && packageOverride.OverridenPackages.TryGetValue(item2.PackageId, out version) + && item2.PackageVersion != null + && item2.PackageVersion <= version) + { + return item1; + } + else if (item2.PackageId != null + && PackageOverrides.TryGetValue(item2.PackageId, out packageOverride) + && packageOverride.OverridenPackages.TryGetValue(item1.PackageId, out version) + && item1.PackageVersion != null + && item1.PackageVersion <= version) + { + return item2; + } + } + + return null; + } + } +} diff --git a/src/Tasks/Common/ConflictResolution/ResolvePackageFileConflicts.cs b/src/Tasks/Common/ConflictResolution/ResolvePackageFileConflicts.cs index 99f462662d2e..a862914bbd46 100644 --- a/src/Tasks/Common/ConflictResolution/ResolvePackageFileConflicts.cs +++ b/src/Tasks/Common/ConflictResolution/ResolvePackageFileConflicts.cs @@ -31,6 +31,17 @@ public class ResolvePackageFileConflicts : TaskBase /// public string[] PreferredPackages { get; set; } + /// + /// A collection of items that contain information of which packages get overriden + /// by which packages before doing any other conflict resolution. + /// + /// + /// This is an optimizaiton so AssemblyVersions, FileVersions, etc. don't need to be read + /// in the default cases where platform packages (Microsoft.NETCore.App) should override specific packages + /// (System.Console v4.3.0). + /// + public ITaskItem[] PackageOverrides { get; set; } + [Output] public ITaskItem[] ReferencesWithoutConflicts { get; set; } @@ -44,6 +55,7 @@ protected override void ExecuteCore() { var log = new MSBuildLog(Log); var packageRanks = new PackageRank(PreferredPackages); + var packageOverrides = new PackageOverrideResolver(PackageOverrides); // Treat assemblies from FrameworkList.xml as platform assemblies that also get considered at compile time IEnumerable compilePlatformItems = null; @@ -60,7 +72,7 @@ protected override void ExecuteCore() // resolve conflicts at compile time var referenceItems = GetConflictTaskItems(References, ConflictItemType.Reference).ToArray(); - var compileConflictScope = new ConflictResolver(packageRanks, log); + var compileConflictScope = new ConflictResolver(packageRanks, packageOverrides, log); compileConflictScope.ResolveConflicts(referenceItems, ci => ItemUtilities.GetReferenceFileName(ci.OriginalItem), @@ -74,7 +86,7 @@ protected override void ExecuteCore() } // resolve conflicts that class in output - var runtimeConflictScope = new ConflictResolver(packageRanks, log); + var runtimeConflictScope = new ConflictResolver(packageRanks, packageOverrides, log); runtimeConflictScope.ResolveConflicts(referenceItems, ci => ItemUtilities.GetReferenceTargetPath(ci.OriginalItem), @@ -95,7 +107,7 @@ protected override void ExecuteCore() // resolve conflicts with platform (eg: shared framework) items // we only commit the platform items since its not a conflict if other items share the same filename. - var platformConflictScope = new ConflictResolver(packageRanks, log); + var platformConflictScope = new ConflictResolver(packageRanks, packageOverrides, log); var platformItems = PlatformManifests?.SelectMany(pm => PlatformManifestReader.LoadConflictItems(pm.ItemSpec, log)) ?? Enumerable.Empty(); if (compilePlatformItems != null) diff --git a/src/Tasks/Common/MetadataKeys.cs b/src/Tasks/Common/MetadataKeys.cs index 9b16c29090b9..ff2582ae8743 100644 --- a/src/Tasks/Common/MetadataKeys.cs +++ b/src/Tasks/Common/MetadataKeys.cs @@ -50,5 +50,8 @@ internal static class MetadataKeys // Publish Target Manifest public const string RuntimeStoreManifestNames = "RuntimeStoreManifestNames"; + + // Conflict Resolution + public const string OverridenPackages = "OverridenPackages"; } } diff --git a/src/Tasks/Common/build/Microsoft.NET.DefaultPackageConflictOverrides.targets b/src/Tasks/Common/build/Microsoft.NET.DefaultPackageConflictOverrides.targets new file mode 100644 index 000000000000..10c52e0c1bd9 --- /dev/null +++ b/src/Tasks/Common/build/Microsoft.NET.DefaultPackageConflictOverrides.targets @@ -0,0 +1,237 @@ + + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + + + Microsoft.CSharp|4.4.0; + Microsoft.Win32.Primitives|4.3.0; + Microsoft.Win32.Registry|4.4.0; + runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl|4.3.0; + runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl|4.3.0; + runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl|4.3.0; + runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl|4.3.0; + runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl|4.3.0; + runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple|4.3.0; + runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl|4.3.0; + runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl|4.3.0; + runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl|4.3.0; + runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl|4.3.0; + runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl|4.3.0; + System.AppContext|4.3.0; + System.Buffers|4.4.0; + System.Collections|4.3.0; + System.Collections.Concurrent|4.3.0; + System.Collections.Immutable|1.4.0; + System.Collections.NonGeneric|4.3.0; + System.Collections.Specialized|4.3.0; + System.ComponentModel|4.3.0; + System.ComponentModel.EventBasedAsync|4.3.0; + System.ComponentModel.Primitives|4.3.0; + System.ComponentModel.TypeConverter|4.3.0; + System.Console|4.3.0; + System.Data.Common|4.3.0; + System.Diagnostics.Contracts|4.3.0; + System.Diagnostics.Debug|4.3.0; + System.Diagnostics.DiagnosticSource|4.4.0; + System.Diagnostics.FileVersionInfo|4.3.0; + System.Diagnostics.Process|4.3.0; + System.Diagnostics.StackTrace|4.3.0; + System.Diagnostics.TextWriterTraceListener|4.3.0; + System.Diagnostics.Tools|4.3.0; + System.Diagnostics.TraceSource|4.3.0; + System.Diagnostics.Tracing|4.3.0; + System.Dynamic.Runtime|4.3.0; + System.Globalization|4.3.0; + System.Globalization.Calendars|4.3.0; + System.Globalization.Extensions|4.3.0; + System.IO|4.3.0; + System.IO.Compression|4.3.0; + System.IO.Compression.ZipFile|4.3.0; + System.IO.FileSystem|4.3.0; + System.IO.FileSystem.AccessControl|4.4.0; + System.IO.FileSystem.DriveInfo|4.3.0; + System.IO.FileSystem.Primitives|4.3.0; + System.IO.FileSystem.Watcher|4.3.0; + System.IO.IsolatedStorage|4.3.0; + System.IO.MemoryMappedFiles|4.3.0; + System.IO.Pipes|4.3.0; + System.IO.UnmanagedMemoryStream|4.3.0; + System.Linq|4.3.0; + System.Linq.Expressions|4.3.0; + System.Linq.Queryable|4.3.0; + System.Net.Http|4.3.0; + System.Net.NameResolution|4.3.0; + System.Net.Primitives|4.3.0; + System.Net.Requests|4.3.0; + System.Net.Security|4.3.0; + System.Net.Sockets|4.3.0; + System.Net.WebHeaderCollection|4.3.0; + System.ObjectModel|4.3.0; + System.Private.DataContractSerialization|4.3.0; + System.Reflection|4.3.0; + System.Reflection.Emit|4.3.0; + System.Reflection.Emit.ILGeneration|4.3.0; + System.Reflection.Emit.Lightweight|4.3.0; + System.Reflection.Extensions|4.3.0; + System.Reflection.Metadata|1.5.0; + System.Reflection.Primitives|4.3.0; + System.Reflection.TypeExtensions|4.3.0; + System.Resources.ResourceManager|4.3.0; + System.Runtime|4.3.0; + System.Runtime.Extensions|4.3.0; + System.Runtime.Handles|4.3.0; + System.Runtime.InteropServices|4.3.0; + System.Runtime.InteropServices.RuntimeInformation|4.3.0; + System.Runtime.Loader|4.3.0; + System.Runtime.Numerics|4.3.0; + System.Runtime.Serialization.Formatters|4.3.0; + System.Runtime.Serialization.Json|4.3.0; + System.Runtime.Serialization.Primitives|4.3.0; + System.Security.AccessControl|4.4.0; + System.Security.Claims|4.3.0; + System.Security.Cryptography.Algorithms|4.3.0; + System.Security.Cryptography.Cng|4.4.0; + System.Security.Cryptography.Csp|4.3.0; + System.Security.Cryptography.Encoding|4.3.0; + System.Security.Cryptography.OpenSsl|4.4.0; + System.Security.Cryptography.Primitives|4.3.0; + System.Security.Cryptography.X509Certificates|4.3.0; + System.Security.Cryptography.Xml|4.4.0; + System.Security.Principal|4.3.0; + System.Security.Principal.Windows|4.4.0; + System.Text.Encoding|4.3.0; + System.Text.Encoding.Extensions|4.3.0; + System.Text.RegularExpressions|4.3.0; + System.Threading|4.3.0; + System.Threading.Overlapped|4.3.0; + System.Threading.Tasks|4.3.0; + System.Threading.Tasks.Extensions|4.3.0; + System.Threading.Tasks.Parallel|4.3.0; + System.Threading.Thread|4.3.0; + System.Threading.ThreadPool|4.3.0; + System.Threading.Timer|4.3.0; + System.ValueTuple|4.3.0; + System.Xml.ReaderWriter|4.3.0; + System.Xml.XDocument|4.3.0; + System.Xml.XmlDocument|4.3.0; + System.Xml.XmlSerializer|4.3.0; + System.Xml.XPath|4.3.0; + System.Xml.XPath.XDocument|4.3.0; + + + + + Microsoft.Win32.Primitives|4.3.0; + System.AppContext|4.3.0; + System.Collections|4.3.0; + System.Collections.Concurrent|4.3.0; + System.Collections.Immutable|1.4.0; + System.Collections.NonGeneric|4.3.0; + System.Collections.Specialized|4.3.0; + System.ComponentModel|4.3.0; + System.ComponentModel.EventBasedAsync|4.3.0; + System.ComponentModel.Primitives|4.3.0; + System.ComponentModel.TypeConverter|4.3.0; + System.Console|4.3.0; + System.Data.Common|4.3.0; + System.Diagnostics.Contracts|4.3.0; + System.Diagnostics.Debug|4.3.0; + System.Diagnostics.FileVersionInfo|4.3.0; + System.Diagnostics.Process|4.3.0; + System.Diagnostics.StackTrace|4.3.0; + System.Diagnostics.TextWriterTraceListener|4.3.0; + System.Diagnostics.Tools|4.3.0; + System.Diagnostics.TraceSource|4.3.0; + System.Diagnostics.Tracing|4.3.0; + System.Dynamic.Runtime|4.3.0; + System.Globalization|4.3.0; + System.Globalization.Calendars|4.3.0; + System.Globalization.Extensions|4.3.0; + System.IO|4.3.0; + System.IO.Compression|4.3.0; + System.IO.Compression.ZipFile|4.3.0; + System.IO.FileSystem|4.3.0; + System.IO.FileSystem.DriveInfo|4.3.0; + System.IO.FileSystem.Primitives|4.3.0; + System.IO.FileSystem.Watcher|4.3.0; + System.IO.IsolatedStorage|4.3.0; + System.IO.MemoryMappedFiles|4.3.0; + System.IO.Pipes|4.3.0; + System.IO.UnmanagedMemoryStream|4.3.0; + System.Linq|4.3.0; + System.Linq.Expressions|4.3.0; + System.Linq.Queryable|4.3.0; + System.Net.Http|4.3.0; + System.Net.NameResolution|4.3.0; + System.Net.Primitives|4.3.0; + System.Net.Requests|4.3.0; + System.Net.Security|4.3.0; + System.Net.Sockets|4.3.0; + System.Net.WebHeaderCollection|4.3.0; + System.ObjectModel|4.3.0; + System.Private.DataContractSerialization|4.3.0; + System.Reflection|4.3.0; + System.Reflection.Emit|4.3.0; + System.Reflection.Emit.ILGeneration|4.3.0; + System.Reflection.Emit.Lightweight|4.3.0; + System.Reflection.Extensions|4.3.0; + System.Reflection.Primitives|4.3.0; + System.Reflection.TypeExtensions|4.3.0; + System.Resources.ResourceManager|4.3.0; + System.Runtime|4.3.0; + System.Runtime.Extensions|4.3.0; + System.Runtime.Handles|4.3.0; + System.Runtime.InteropServices|4.3.0; + System.Runtime.InteropServices.RuntimeInformation|4.3.0; + System.Runtime.Loader|4.3.0; + System.Runtime.Numerics|4.3.0; + System.Runtime.Serialization.Formatters|4.3.0; + System.Runtime.Serialization.Json|4.3.0; + System.Runtime.Serialization.Primitives|4.3.0; + System.Security.AccessControl|4.4.0; + System.Security.Claims|4.3.0; + System.Security.Cryptography.Algorithms|4.3.0; + System.Security.Cryptography.Csp|4.3.0; + System.Security.Cryptography.Encoding|4.3.0; + System.Security.Cryptography.Primitives|4.3.0; + System.Security.Cryptography.X509Certificates|4.3.0; + System.Security.Cryptography.Xml|4.4.0; + System.Security.Principal|4.3.0; + System.Security.Principal.Windows|4.4.0; + System.Text.Encoding|4.3.0; + System.Text.Encoding.Extensions|4.3.0; + System.Text.RegularExpressions|4.3.0; + System.Threading|4.3.0; + System.Threading.Overlapped|4.3.0; + System.Threading.Tasks|4.3.0; + System.Threading.Tasks.Extensions|4.3.0; + System.Threading.Tasks.Parallel|4.3.0; + System.Threading.Thread|4.3.0; + System.Threading.ThreadPool|4.3.0; + System.Threading.Timer|4.3.0; + System.ValueTuple|4.3.0; + System.Xml.ReaderWriter|4.3.0; + System.Xml.XDocument|4.3.0; + System.Xml.XmlDocument|4.3.0; + System.Xml.XmlSerializer|4.3.0; + System.Xml.XPath|4.3.0; + System.Xml.XPath.XDocument|4.3.0; + + + + diff --git a/src/Tasks/Microsoft.NET.Build.Extensions.Tasks/Microsoft.NET.Build.Extensions.Tasks.csproj b/src/Tasks/Microsoft.NET.Build.Extensions.Tasks/Microsoft.NET.Build.Extensions.Tasks.csproj index cd5a728780b6..9e179faf972a 100644 --- a/src/Tasks/Microsoft.NET.Build.Extensions.Tasks/Microsoft.NET.Build.Extensions.Tasks.csproj +++ b/src/Tasks/Microsoft.NET.Build.Extensions.Tasks/Microsoft.NET.Build.Extensions.Tasks.csproj @@ -73,6 +73,9 @@ + + msbuildExtensions\Microsoft\Microsoft.NET.Build.Extensions\%(FileName)%(Extension) + diff --git a/src/Tasks/Microsoft.NET.Build.Extensions.Tasks/msbuildExtensions/Microsoft/Microsoft.NET.Build.Extensions/Microsoft.NET.Build.Extensions.ConflictResolution.targets b/src/Tasks/Microsoft.NET.Build.Extensions.Tasks/msbuildExtensions/Microsoft/Microsoft.NET.Build.Extensions/Microsoft.NET.Build.Extensions.ConflictResolution.targets index 75f324e73e71..dc94dff39a76 100644 --- a/src/Tasks/Microsoft.NET.Build.Extensions.Tasks/msbuildExtensions/Microsoft/Microsoft.NET.Build.Extensions/Microsoft.NET.Build.Extensions.ConflictResolution.targets +++ b/src/Tasks/Microsoft.NET.Build.Extensions.Tasks/msbuildExtensions/Microsoft/Microsoft.NET.Build.Extensions/Microsoft.NET.Build.Extensions.ConflictResolution.targets @@ -23,6 +23,8 @@ Copyright (c) .NET Foundation. All rights reserved. <_HandlePackageFileConflictsBefore>ResolveAssemblyReferences + + diff --git a/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenAConflictResolver.cs b/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenAConflictResolver.cs index 8ce0ba48d7be..1365ba767ae8 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenAConflictResolver.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenAConflictResolver.cs @@ -3,13 +3,12 @@ using System; using System.Collections.Generic; -using System.Text; +using System.Linq; using FluentAssertions; using Microsoft.Build.Framework; -using Microsoft.Extensions.DependencyModel; -using Xunit; using Microsoft.NET.Build.Tasks.ConflictResolution; -using System.Linq; +using Microsoft.NET.Build.Tasks.UnitTests.Mocks; +using Xunit; namespace Microsoft.NET.Build.Tasks.UnitTests { @@ -315,12 +314,73 @@ public void WhenCommitWinnerIsFalseConflictsWithDifferentKeysAreReported() result.UnresolvedConflicts.Should().BeEmpty(); } + [Fact] + public void WhenPackageOverridesAreSpecifiedTheyAreUsed() + { + var systemItem1 = new MockConflictItem("System.Ben") { PackageId = "System.Ben", PackageVersion = new Version("4.3.0") }; + var systemItem2 = new MockConflictItem("System.Immo") { PackageId = "System.Immo", PackageVersion = new Version("4.2.0") }; + var systemItem3 = new MockConflictItem("System.Dave") { PackageId = "System.Dave", PackageVersion = new Version("4.1.0") }; + + var platformItem1 = new MockConflictItem("System.Ben") { PackageId = "Platform", PackageVersion = new Version("2.0.0") }; + var platformItem2 = new MockConflictItem("System.Immo") { PackageId = "Platform", PackageVersion = new Version("2.0.0") }; + var platformItem3 = new MockConflictItem("System.Dave") { PackageId = "Platform", PackageVersion = new Version("2.0.0") }; + + var result = GetConflicts( + new[] { systemItem1, systemItem2, systemItem3, platformItem1, platformItem2, platformItem3 }, + Array.Empty(), + new[] { + new MockTaskItem("Platform", new Dictionary + { + { MetadataKeys.OverridenPackages, "System.Ben|4.3.0;System.Immo|4.3.0;System.Dave|4.3.0" }, + }) + }); + + result.Conflicts.Should().Equal(systemItem1, systemItem2, systemItem3); + result.UnresolvedConflicts.Should().BeEmpty(); + } + + [Fact] + public void WhenAHigherPackageIsUsedPackageOverrideLoses() + { + var platformItem1 = new MockConflictItem("System.Ben") { PackageId = "Platform", PackageVersion = new Version("2.0.0") }; + var platformItem2 = new MockConflictItem("System.Immo") { PackageId = "Platform", PackageVersion = new Version("2.0.0") }; + var platformItem3 = new MockConflictItem("System.Dave") { PackageId = "Platform", PackageVersion = new Version("2.0.0") }; + + var systemItem1 = new MockConflictItem("System.Ben") { PackageId = "System.Ben", PackageVersion = new Version("4.3.0") }; + var systemItem2 = new MockConflictItem("System.Immo") { PackageId = "System.Immo", PackageVersion = new Version("4.2.0") }; + // System.Dave has a higher PackageVersion than the PackageOverride + var systemItem3 = new MockConflictItem("System.Dave") + { + PackageId = "System.Dave", + PackageVersion = new Version("4.4.0"), + AssemblyVersion = new Version(platformItem3.AssemblyVersion.Major + 1, 0) + }; + + var result = GetConflicts( + new[] { systemItem1, systemItem2, systemItem3, platformItem1, platformItem2, platformItem3 }, + Array.Empty(), + new[] { + new MockTaskItem("Platform", new Dictionary + { + { MetadataKeys.OverridenPackages, "System.Ben|4.3.0;System.Immo|4.3.0;System.Dave|4.3.0" }, + }) + }); + + result.Conflicts.Should().Equal(systemItem1, systemItem2, platformItem3); + result.UnresolvedConflicts.Should().BeEmpty(); + } + static ConflictResults GetConflicts(params MockConflictItem[] items) { return GetConflicts(items, Array.Empty()); } - static ConflictResults GetConflicts(MockConflictItem [] itemsToCommit, params MockConflictItem [] itemsNotToCommit) + static ConflictResults GetConflicts(MockConflictItem[] itemsToCommit, params MockConflictItem[] itemsNotToCommit) + { + return GetConflicts(itemsToCommit, itemsNotToCommit, Array.Empty()); + } + + static ConflictResults GetConflicts(MockConflictItem[] itemsToCommit, MockConflictItem[] itemsNotToCommit, ITaskItem[] packageOverrides) { ConflictResults ret = new ConflictResults(); @@ -341,7 +401,9 @@ void UnresolvedConflictHandler(MockConflictItem item) .OrderBy(id => id) .ToArray(); - var resolver = new ConflictResolver(new PackageRank(packagesForRank), new MockLog()); + var overrideResolver = new PackageOverrideResolver(packageOverrides); + + var resolver = new ConflictResolver(new PackageRank(packagesForRank), overrideResolver, new MockLog()); resolver.ResolveConflicts(itemsToCommit, GetItemKey, ConflictHandler, unresolvedConflict: UnresolvedConflictHandler); @@ -364,36 +426,6 @@ class ConflictResults public List UnresolvedConflicts { get; set; } = new List(); } - class MockConflictItem : IConflictItem - { - public MockConflictItem(string name = "System.Ben") - { - Key = name + ".dll"; - AssemblyVersion = new Version("1.0.0.0"); - ItemType = ConflictItemType.Reference; - Exists = true; - FileName = name + ".dll"; - FileVersion = new Version("1.0.0.0"); - PackageId = name; - DisplayName = name; - } - public string Key { get; set; } - - public Version AssemblyVersion { get; set; } - - public ConflictItemType ItemType { get; set; } - - public bool Exists { get; set; } - - public string FileName { get; set; } - - public Version FileVersion { get; set; } - - public string PackageId { get; set; } - - public string DisplayName { get; set; } - } - class MockLog : ILog { public void LogError(string message, params object[] messageArgs) diff --git a/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenAPackageOverrideResolver.cs b/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenAPackageOverrideResolver.cs new file mode 100644 index 000000000000..7e1b1a1620cb --- /dev/null +++ b/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenAPackageOverrideResolver.cs @@ -0,0 +1,52 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using Microsoft.Build.Framework; +using Microsoft.NET.Build.Tasks.ConflictResolution; +using Microsoft.NET.Build.Tasks.UnitTests.Mocks; +using Xunit; + +namespace Microsoft.NET.Build.Tasks.UnitTests +{ + public class GivenAPackageOverrideResolver + { + [Fact] + public void ItMergesPackageOverridesUsingHighestVersion() + { + ITaskItem[] packageOverrides = new[] + { + new MockTaskItem("Platform", new Dictionary + { + { MetadataKeys.OverridenPackages, "System.Ben|4.2.0;System.Immo|4.2.0;System.Livar|4.3.0;System.Dave|4.2.0" } + }), + new MockTaskItem("Platform", new Dictionary + { + { MetadataKeys.OverridenPackages, "System.Ben|4.2.0;System.Immo|4.3.0;System.Livar|4.2.0;System.Nick|4.2.0" } + }) + }; + + var resolver = new PackageOverrideResolver(packageOverrides); + + Assert.Single(resolver.PackageOverrides); + + PackageOverride packageOverride = resolver.PackageOverrides["Platform"]; + Assert.Equal(5, packageOverride.OverridenPackages.Count); + Assert.Equal(new Version(4, 2, 0), packageOverride.OverridenPackages["System.Ben"]); + Assert.Equal(new Version(4, 3, 0), packageOverride.OverridenPackages["System.Immo"]); + Assert.Equal(new Version(4, 3, 0), packageOverride.OverridenPackages["System.Livar"]); + Assert.Equal(new Version(4, 2, 0), packageOverride.OverridenPackages["System.Dave"]); + Assert.Equal(new Version(4, 2, 0), packageOverride.OverridenPackages["System.Nick"]); + } + + [Fact] + public void ItHandlesNullITaskItemArray() + { + var resolver = new PackageOverrideResolver(null); + + Assert.Null(resolver.PackageOverrides); + Assert.Null(resolver.Resolve(new MockConflictItem(), new MockConflictItem())); + } + } +} diff --git a/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/Mocks/MockConflictItem.cs b/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/Mocks/MockConflictItem.cs new file mode 100644 index 000000000000..e1d21a791a87 --- /dev/null +++ b/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/Mocks/MockConflictItem.cs @@ -0,0 +1,41 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Microsoft.NET.Build.Tasks.ConflictResolution; + +namespace Microsoft.NET.Build.Tasks.UnitTests.Mocks +{ + class MockConflictItem : IConflictItem + { + public MockConflictItem(string name = "System.Ben") + { + Key = name + ".dll"; + AssemblyVersion = new Version("1.0.0.0"); + ItemType = ConflictItemType.Reference; + Exists = true; + FileName = name + ".dll"; + FileVersion = new Version("1.0.0.0"); + PackageId = name; + PackageVersion = new Version("1.0.0"); + DisplayName = name; + } + public string Key { get; set; } + + public Version AssemblyVersion { get; set; } + + public ConflictItemType ItemType { get; set; } + + public bool Exists { get; set; } + + public string FileName { get; set; } + + public Version FileVersion { get; set; } + + public string PackageId { get; set; } + + public Version PackageVersion { get; set; } + + public string DisplayName { get; set; } + } +} diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/Microsoft.NET.Build.Tasks.csproj b/src/Tasks/Microsoft.NET.Build.Tasks/Microsoft.NET.Build.Tasks.csproj index 0f56b13f7ada..6deee44d305a 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/Microsoft.NET.Build.Tasks.csproj +++ b/src/Tasks/Microsoft.NET.Build.Tasks/Microsoft.NET.Build.Tasks.csproj @@ -76,7 +76,10 @@ - + + build\%(FileName)%(Extension) + + diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/build/Microsoft.NET.ConflictResolution.targets b/src/Tasks/Microsoft.NET.Build.Tasks/build/Microsoft.NET.ConflictResolution.targets index 9ab8fe7e5581..fcbbcc8e3b14 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/build/Microsoft.NET.ConflictResolution.targets +++ b/src/Tasks/Microsoft.NET.Build.Tasks/build/Microsoft.NET.ConflictResolution.targets @@ -16,8 +16,9 @@ Copyright (c) .NET Foundation. All rights reserved. $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + - @@ -53,14 +54,15 @@ Copyright (c) .NET Foundation. All rights reserved. %(PackageName) %(PackageVersion) - - + + + ReferenceCopyLocalPaths="@(ReferenceCopyLocalPaths)" + OtherRuntimeItems="@(_LockFileAssemblies)" + PlatformManifests="@(PackageConflictPlatformManifests)" + TargetFrameworkDirectories="$(TargetFrameworkDirectory)" + PackageOverrides="@(PackageConflictOverrides)" + PreferredPackages="$(PackageConflictPreferredPackages)"> @@ -92,4 +94,4 @@ Copyright (c) .NET Foundation. All rights reserved. - \ No newline at end of file + diff --git a/src/Tests/Microsoft.NET.Build.Tests/GivenThatWeWantToResolveConflicts.cs b/src/Tests/Microsoft.NET.Build.Tests/GivenThatWeWantToResolveConflicts.cs new file mode 100644 index 000000000000..5eeda7d62c7f --- /dev/null +++ b/src/Tests/Microsoft.NET.Build.Tests/GivenThatWeWantToResolveConflicts.cs @@ -0,0 +1,106 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; +using FluentAssertions; +using Microsoft.NET.TestFramework; +using Microsoft.NET.TestFramework.Assertions; +using Microsoft.NET.TestFramework.Commands; +using Microsoft.NET.TestFramework.ProjectConstruction; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.NET.Build.Tests +{ + public class GivenThatWeWantToResolveConflicts : SdkTest + { + public GivenThatWeWantToResolveConflicts(ITestOutputHelper log) : base(log) + { + } + + [Theory] + [InlineData("netcoreapp2.0")] + [InlineData("netstandard2.0")] + public void The_same_references_are_used_with_or_without_DisableDefaultPackageConflictOverrides(string targetFramework) + { + var defaultProject = new TestProject() + { + Name = "DefaultProject", + TargetFrameworks = targetFramework, + IsSdkProject = true + }; + AddConflictReferences(defaultProject); + GetReferences( + defaultProject, + expectConflicts: false, + references: out List defaultReferences, + referenceCopyLocalPaths: out List defaultReferenceCopyLocalPaths); + + var disableProject = new TestProject() + { + Name = "DisableProject", + TargetFrameworks = targetFramework, + IsSdkProject = true + }; + disableProject.AdditionalProperties.Add("DisableDefaultPackageConflictOverrides", "true"); + AddConflictReferences(disableProject); + GetReferences( + disableProject, + expectConflicts: true, + references: out List disableReferences, + referenceCopyLocalPaths: out List disableReferenceCopyLocalPaths); + + Assert.Equal(defaultReferences, disableReferences); + Assert.Equal(defaultReferenceCopyLocalPaths, disableReferenceCopyLocalPaths); + } + + private void AddConflictReferences(TestProject testProject) + { + foreach (var dependency in ConflictResolutionAssets.ConflictResolutionDependencies) + { + testProject.PackageReferences.Add(new TestPackageReference(dependency.Item1, dependency.Item2)); + } + } + + private void GetReferences(TestProject testProject, bool expectConflicts, out List references, out List referenceCopyLocalPaths) + { + string targetFramework = testProject.TargetFrameworks; + TestAsset tempTestAsset = _testAssetsManager.CreateTestProject(testProject) + .Restore(Log, testProject.Name); + + string projectFolder = Path.Combine(tempTestAsset.TestRoot, testProject.Name); + + var getReferenceCommand = new GetValuesCommand( + Log, + projectFolder, + targetFramework, + "Reference", + GetValuesCommand.ValueType.Item); + getReferenceCommand.DependsOnTargets = "Build"; + var result = getReferenceCommand.Execute("/v:normal").Should().Pass(); + if (expectConflicts) + { + result.And.HaveStdOutMatching("Encountered conflict", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); + } + else + { + result.And.NotHaveStdOutMatching("Encountered conflict", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); + } + + references = getReferenceCommand.GetValues(); + + var getReferenceCopyLocalPathsCommand = new GetValuesCommand( + Log, + projectFolder, + targetFramework, + "ReferenceCopyLocalPaths", + GetValuesCommand.ValueType.Item); + getReferenceCopyLocalPathsCommand.DependsOnTargets = "Build"; + getReferenceCopyLocalPathsCommand.Execute().Should().Pass(); + + referenceCopyLocalPaths = getReferenceCopyLocalPathsCommand.GetValues(); + } + } +} diff --git a/src/Tests/Microsoft.NET.TestFramework/Commands/GetValuesCommand.cs b/src/Tests/Microsoft.NET.TestFramework/Commands/GetValuesCommand.cs index e354ad9065d8..6646ccfa85cc 100644 --- a/src/Tests/Microsoft.NET.TestFramework/Commands/GetValuesCommand.cs +++ b/src/Tests/Microsoft.NET.TestFramework/Commands/GetValuesCommand.cs @@ -102,33 +102,39 @@ public List GetValues() public List<(string value, Dictionary metadata)> GetValuesWithMetadata() { - var ret = new List<(string value, Dictionary metadata)>(); - string outputFilename = $"{_valueName}Values.txt"; var outputDirectory = GetOutputDirectory(_targetFramework, Configuration ?? "Debug"); + string fullFileName = Path.Combine(outputDirectory.FullName, outputFilename); - return File.ReadAllLines(Path.Combine(outputDirectory.FullName, outputFilename)) - .Where(line => !string.IsNullOrWhiteSpace(line)) - .Select(line => - { - if (!MetadataNames.Any()) - { - return (value: line, metadata: new Dictionary()); - } - else + if (File.Exists(fullFileName)) + { + return File.ReadAllLines(fullFileName) + .Where(line => !string.IsNullOrWhiteSpace(line)) + .Select(line => { - var fields = line.Split('\t'); - - var dict = new Dictionary(); - for (int i=0; i()); } + else + { + var fields = line.Split('\t'); + + var dict = new Dictionary(); + for (int i = 0; i < MetadataNames.Count; i++) + { + dict[MetadataNames[i]] = fields[i + 1]; + } - return (value: fields[0], metadata: dict); - } - }) - .ToList(); + return (value: fields[0], metadata: dict); + } + }) + .ToList(); + } + else + { + return new List<(string value, Dictionary metadata)>(); + } } } }