diff --git a/src/MSBuild/MSBuild.csproj b/src/MSBuild/MSBuild.csproj index 99a71a59e11..803083dd1fc 100644 --- a/src/MSBuild/MSBuild.csproj +++ b/src/MSBuild/MSBuild.csproj @@ -330,4 +330,14 @@ + + + + + + + + + + diff --git a/src/MSBuild/ValidateMSBuildPackageDependencyVersions.cs b/src/MSBuild/ValidateMSBuildPackageDependencyVersions.cs new file mode 100644 index 00000000000..6834799b28b --- /dev/null +++ b/src/MSBuild/ValidateMSBuildPackageDependencyVersions.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Xml; +namespace MSBuild +{ + public class ValidateMSBuildPackageDependencyVersions : Task + { + [Required] + public string AppConfig { get; set; } + [Required] + public string AssemblyPath { get; set; } + + // Microsoft.Build.Conversion.Core and Microsoft.Build.Engine are deprecated, but they're still used in VS for now. This project doesn't directly reference them, so they don't appear in its output directory. + // Microsoft.NET.StringTools uses API not available in net35, but since we need it to work for TaskHosts as well, there are simpler versions implemented for that. Ensure it's the right version. + // Microsoft.Activities.Build and XamlBuildTask are loaded within an AppDomain in the XamlBuildTask after having been loaded from the GAC elsewhere. See https://github.com/dotnet/msbuild/pull/856 + private string[] assembliesToIgnore = { "Microsoft.Build.Conversion.Core", "Microsoft.NET.StringTools.net35", "Microsoft.Build.Engine", "Microsoft.Activities.Build", "XamlBuildTask" }; + + public override bool Execute() + { + XmlDocument doc = new XmlDocument(); + doc.Load(AppConfig); + XmlNamespaceManager namespaceManager = new(doc.NameTable); + namespaceManager.AddNamespace("asm", "urn:schemas-microsoft-com:asm.v1"); + bool foundSystemValueTuple = false; + foreach (XmlElement dependentAssemblyElement in doc.DocumentElement.SelectNodes("/configuration/runtime/asm:assemblyBinding/asm:dependentAssembly[asm:assemblyIdentity][asm:bindingRedirect]", namespaceManager)) + { + string name = (dependentAssemblyElement.SelectSingleNode("asm:assemblyIdentity", namespaceManager) as XmlElement).GetAttribute("name"); + string version = (dependentAssemblyElement.SelectSingleNode("asm:bindingRedirect", namespaceManager) as XmlElement).GetAttribute("newVersion"); + if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(version) && !assembliesToIgnore.Contains(name, StringComparer.OrdinalIgnoreCase)) + { + string path = Path.Combine(AssemblyPath, name + ".dll"); + string assemblyVersion = AssemblyName.GetAssemblyName(path).Version.ToString(); + if (!version.Equals(assemblyVersion)) + { + // Ensure that the binding redirect is to the GAC version, but + // we still ship the version we explicitly reference to let + // API consumers bind to it at runtime. + // See https://github.com/dotnet/msbuild/issues/6976. + if (String.Equals(name, "System.ValueTuple", StringComparison.OrdinalIgnoreCase) && String.Equals(version, "4.0.0.0") && String.Equals(assemblyVersion, "4.0.3.0")) + { + foundSystemValueTuple = true; + } + else + { + Log.LogError($"Binding redirect for '{name}' redirects to a different version ({version}) than MSBuild ships ({assemblyVersion})."); + } + } + } + } + if (!foundSystemValueTuple) + { + Log.LogError("Binding redirect for 'System.ValueTuple' missing."); + } + return !Log.HasLoggedErrors; + } + } +} diff --git a/src/MSBuild/app.amd64.config b/src/MSBuild/app.amd64.config index c44189ed7f9..517c74df63f 100644 --- a/src/MSBuild/app.amd64.config +++ b/src/MSBuild/app.amd64.config @@ -54,7 +54,7 @@ - + diff --git a/src/MSBuild/app.config b/src/MSBuild/app.config index 96ee3eb038b..f22bacc449e 100644 --- a/src/MSBuild/app.config +++ b/src/MSBuild/app.config @@ -45,7 +45,7 @@ - +