From 0ec390a77002d420bd3199f59e86fe9113a3018d Mon Sep 17 00:00:00 2001 From: Forgind Date: Fri, 8 Jan 2021 14:17:49 -0800 Subject: [PATCH] Optionally output unresolved assembly conflicts (#5990) Allow users to specify in OutputUnresolvedAssemblyConflicts (an optional parameter to RAR) that they want assembly conflicts provided in an output item and, if true, creates it. Fixes #5934 --- .../net/Microsoft.Build.Tasks.Core.cs | 3 ++ .../netstandard/Microsoft.Build.Tasks.Core.cs | 3 ++ .../AssemblyDependency/Miscellaneous.cs | 31 +++++++++++++++++ .../ResolveAssemblyReference.cs | 33 +++++++++++++++++-- .../Microsoft.Common.CurrentVersion.targets | 3 ++ 5 files changed, 70 insertions(+), 3 deletions(-) diff --git a/ref/Microsoft.Build.Tasks.Core/net/Microsoft.Build.Tasks.Core.cs b/ref/Microsoft.Build.Tasks.Core/net/Microsoft.Build.Tasks.Core.cs index cbc25f139be..dc26ebeb9ba 100644 --- a/ref/Microsoft.Build.Tasks.Core/net/Microsoft.Build.Tasks.Core.cs +++ b/ref/Microsoft.Build.Tasks.Core/net/Microsoft.Build.Tasks.Core.cs @@ -926,6 +926,7 @@ public ResolveAssemblyReference() { } public Microsoft.Build.Framework.ITaskItem[] InstalledAssemblySubsetTables { get { throw null; } set { } } public Microsoft.Build.Framework.ITaskItem[] InstalledAssemblyTables { get { throw null; } set { } } public string[] LatestTargetFrameworkDirectories { get { throw null; } set { } } + public bool OutputUnresolvedAssemblyConflicts { get { throw null; } set { } } public string ProfileName { get { throw null; } set { } } [Microsoft.Build.Framework.OutputAttribute] public Microsoft.Build.Framework.ITaskItem[] RelatedFiles { get { throw null; } } @@ -954,6 +955,8 @@ public ResolveAssemblyReference() { } public string[] TargetFrameworkSubsets { get { throw null; } set { } } public string TargetFrameworkVersion { get { throw null; } set { } } public string TargetProcessorArchitecture { get { throw null; } set { } } + [Microsoft.Build.Framework.OutputAttribute] + public Microsoft.Build.Framework.ITaskItem[] UnresolvedAssemblyConflicts { get { throw null; } } public bool UnresolveFrameworkAssembliesFromHigherFrameworks { get { throw null; } set { } } public string WarnOrErrorOnTargetArchitectureMismatch { get { throw null; } set { } } public override bool Execute() { throw null; } diff --git a/ref/Microsoft.Build.Tasks.Core/netstandard/Microsoft.Build.Tasks.Core.cs b/ref/Microsoft.Build.Tasks.Core/netstandard/Microsoft.Build.Tasks.Core.cs index 349308aac70..0d85a2cc928 100644 --- a/ref/Microsoft.Build.Tasks.Core/netstandard/Microsoft.Build.Tasks.Core.cs +++ b/ref/Microsoft.Build.Tasks.Core/netstandard/Microsoft.Build.Tasks.Core.cs @@ -671,6 +671,7 @@ public ResolveAssemblyReference() { } public Microsoft.Build.Framework.ITaskItem[] InstalledAssemblySubsetTables { get { throw null; } set { } } public Microsoft.Build.Framework.ITaskItem[] InstalledAssemblyTables { get { throw null; } set { } } public string[] LatestTargetFrameworkDirectories { get { throw null; } set { } } + public bool OutputUnresolvedAssemblyConflicts { get { throw null; } set { } } public string ProfileName { get { throw null; } set { } } [Microsoft.Build.Framework.OutputAttribute] public Microsoft.Build.Framework.ITaskItem[] RelatedFiles { get { throw null; } } @@ -699,6 +700,8 @@ public ResolveAssemblyReference() { } public string[] TargetFrameworkSubsets { get { throw null; } set { } } public string TargetFrameworkVersion { get { throw null; } set { } } public string TargetProcessorArchitecture { get { throw null; } set { } } + [Microsoft.Build.Framework.OutputAttribute] + public Microsoft.Build.Framework.ITaskItem[] UnresolvedAssemblyConflicts { get { throw null; } } public bool UnresolveFrameworkAssembliesFromHigherFrameworks { get { throw null; } set { } } public string WarnOrErrorOnTargetArchitectureMismatch { get { throw null; } set { } } public override bool Execute() { throw null; } diff --git a/src/Tasks.UnitTests/AssemblyDependency/Miscellaneous.cs b/src/Tasks.UnitTests/AssemblyDependency/Miscellaneous.cs index 2d0f453a127..2d0aaec17b3 100644 --- a/src/Tasks.UnitTests/AssemblyDependency/Miscellaneous.cs +++ b/src/Tasks.UnitTests/AssemblyDependency/Miscellaneous.cs @@ -3686,6 +3686,37 @@ public void ConflictGeneratesMessageReferencingAssemblyName() warningMessage.ShouldContain(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("ResolveAssemblyReference.FourSpaceIndent", ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("ResolveAssemblyReference.ReferenceDependsOn", "D, Version=1.0.0.0, CulTUre=neutral, PublicKeyToken=aaaaaaaaaaaaaaaa", Path.Combine(s_myLibraries_V1Path, "D.dll")))); } + [Fact] + public void ConflictOutputsExtraInformationOnDemand() + { + ResolveAssemblyReference t = new ResolveAssemblyReference(); + + MockEngine e = new MockEngine(_output); + t.BuildEngine = e; + + t.Assemblies = new ITaskItem[] + { + new TaskItem("B"), + new TaskItem("D, Version=1.0.0.0, Culture=neutral, PublicKeyToken=aaaaaaaaaaaaaaaa") + }; + + t.SearchPaths = new string[] + { + s_myLibrariesRootPath, s_myLibraries_V2Path, s_myLibraries_V1Path + }; + + t.TargetFrameworkDirectories = new string[] { s_myVersion20Path }; + t.OutputUnresolvedAssemblyConflicts = true; + + Execute(t); + + ITaskItem[] conflicts = t.UnresolvedAssemblyConflicts; + conflicts.Length.ShouldBe(1); + conflicts[0].ItemSpec.ShouldBe("D"); + conflicts[0].GetMetadata("victorVersionNumber").ShouldBe("1.0.0.0"); + conflicts[0].GetMetadata("victimVersionNumber").ShouldBe("2.0.0.0"); + } + /// /// Consider this dependency chain: /// diff --git a/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs b/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs index 90af55781f5..b7e72734291 100644 --- a/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs +++ b/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs @@ -86,6 +86,7 @@ public ResolveAssemblyReference() private ITaskItem[] _scatterFiles = Array.Empty(); private ITaskItem[] _copyLocalFiles = Array.Empty(); private ITaskItem[] _suggestedRedirects = Array.Empty(); + private List _unresolvedConflicts = new List(); private string[] _targetFrameworkSubsets = Array.Empty(); private string[] _fullTargetFrameworkSubsetNames = Array.Empty(); private string _targetedFrameworkMoniker = String.Empty; @@ -214,6 +215,11 @@ public bool IgnoreTargetFrameworkAttributeVersionMismatch /// public bool FindDependenciesOfExternallyResolvedReferences { get; set; } + /// + /// If true, outputs any unresolved assembly conflicts (MSB3277) in UnresolvedAssemblyConflicts. + /// + public bool OutputUnresolvedAssemblyConflicts { get; set; } + /// /// List of target framework subset names which will be searched for in the target framework directories /// @@ -915,6 +921,13 @@ public String DependsOnNETStandard private set; } + /// + /// If OutputUnresolvedAssemblyConflicts then a list of information about unresolved conflicts that normally would have + /// been outputted in MSB3277. Otherwise empty. + /// + [Output] + public ITaskItem[] UnresolvedAssemblyConflicts => _unresolvedConflicts.ToArray(); + #endregion #region Logging @@ -990,16 +1003,30 @@ quiet at the engine level. // Log the reference which lost the conflict and the dependencies and source items which caused it. LogReferenceDependenciesAndSourceItemsToStringBuilder(fusionName, conflictCandidate, logDependencies.AppendLine()); + string output = StringBuilderCache.GetStringAndRelease(logConflict); + string details = string.Empty; if (logWarning) { // This warning is logged regardless of AutoUnify since it means a conflict existed where the reference // chosen was not the conflict victor in a version comparison. In other words, the victor was older. - Log.LogWarningWithCodeFromResources("ResolveAssemblyReference.FoundConflicts", assemblyName.Name, StringBuilderCache.GetStringAndRelease(logConflict)); + Log.LogWarningWithCodeFromResources("ResolveAssemblyReference.FoundConflicts", assemblyName.Name, output); } else { - Log.LogMessage(ChooseReferenceLoggingImportance(conflictCandidate), StringBuilderCache.GetStringAndRelease(logConflict)); - Log.LogMessage(MessageImportance.Low, StringBuilderCache.GetStringAndRelease(logDependencies)); + details = StringBuilderCache.GetStringAndRelease(logDependencies); + Log.LogMessage(ChooseReferenceLoggingImportance(conflictCandidate), output); + Log.LogMessage(MessageImportance.Low, details); + } + + if (OutputUnresolvedAssemblyConflicts) + { + _unresolvedConflicts.Add(new TaskItem(assemblyName.Name, new Dictionary() + { + { "logMessage", output }, + { "logMessageDetails", details }, + { "victorVersionNumber", victor.ReferenceVersion.ToString() }, + { "victimVersionNumber", conflictCandidate.ReferenceVersion.ToString() } + })); } } } diff --git a/src/Tasks/Microsoft.Common.CurrentVersion.targets b/src/Tasks/Microsoft.Common.CurrentVersion.targets index cacc0e712a8..3f73528bfe4 100644 --- a/src/Tasks/Microsoft.Common.CurrentVersion.targets +++ b/src/Tasks/Microsoft.Common.CurrentVersion.targets @@ -2156,6 +2156,7 @@ Copyright (C) Microsoft Corporation. All rights reserved. $(BuildingProject) $(BuildingProject) $(BuildingProject) + false @@ -2218,6 +2219,7 @@ Copyright (C) Microsoft Corporation. All rights reserved. IgnoreTargetFrameworkAttributeVersionMismatch ="$(ResolveAssemblyReferenceIgnoreTargetFrameworkAttributeVersionMismatch)" FindDependenciesOfExternallyResolvedReferences="$(FindDependenciesOfExternallyResolvedReferences)" ContinueOnError="$(ContinueOnError)" + OutputUnresolvedAssemblyConflicts="$(ResolveAssemblyReferenceOutputUnresolvedAssemblyConflicts)" Condition="'@(Reference)'!='' or '@(_ResolvedProjectReferencePaths)'!='' or '@(_ExplicitReference)' != ''" > @@ -2233,6 +2235,7 @@ Copyright (C) Microsoft Corporation. All rights reserved. +