From 53b227b9db7e9cedde705ba21e46e1287c7804b1 Mon Sep 17 00:00:00 2001 From: "Brett V. Forsgren" Date: Wed, 31 Jan 2024 12:48:21 -0700 Subject: [PATCH] force set `Condition="false"` on Microsoft.WebApplication.targets The file $(VSToolsPath)\WebApplications\Microsoft.WebApplications.targets can never be resolved in the updater as it runs in the Linux Docker container, so an always false condition is added to that import to make the minimal change necessary to allow the NuGet updater to successfully run. After completion, the condition attribute is restored. --- .../UpdateWorkerTests.PackagesConfig.cs | 169 ++++++++++++++++++ .../Updater/PackagesConfigUpdater.cs | 6 +- .../WebApplicationTargetsConditionPatcher.cs | 47 +++++ .../Updater/XmlFilePreAndPostProcessor.cs | 55 ++++++ .../Utilities/XmlExtensions.cs | 11 ++ 5 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/WebApplicationTargetsConditionPatcher.cs create mode 100644 nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/XmlFilePreAndPostProcessor.cs diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs index 9c750eeb079..b50bcaf7138 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs @@ -903,6 +903,175 @@ public async Task PackagesConfigUpdateCanHappenEvenWithMismatchedVersionNumbers( """); } + [Fact] + public async Task PackagesConfigUpdateIsNotThwartedBy_VSToolsPath_PropertyBeingSetInUserCode() + { + await TestUpdateForProject("Newtonsoft.Json", "7.0.1", "13.0.1", + projectContents: """ + + + Debug + AnyCPU + + + 2.0 + 68ed3303-52a0-47b8-a687-3abbb07530da + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + TestProject + TestProject + v4.5 + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + + packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + C:\some\path\that\does\not\exist + + + + + + """, + packagesConfigContents: """ + + + + """, + expectedProjectContents: """ + + + Debug + AnyCPU + + + 2.0 + 68ed3303-52a0-47b8-a687-3abbb07530da + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + TestProject + TestProject + v4.5 + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + + packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + C:\some\path\that\does\not\exist + + + + + + """, + expectedPackagesConfigContents: """ + + + + + """); + } + protected static async Task TestUpdateForProject( string dependencyName, string oldVersion, diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs index 428cc3451d5..acdb37c3940 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs @@ -7,6 +7,7 @@ using System.Xml.Linq; using Microsoft.Language.Xml; +using NuGetUpdater.Core.Updater; namespace NuGetUpdater.Core; @@ -56,7 +57,10 @@ public static async Task UpdateDependencyAsync(string repoRootPath, string proje args.Add(msbuildDirectory); // e.g., /usr/share/dotnet/sdk/7.0.203 } - RunNuget(args, packagesDirectory, logger); + using (new WebApplicationTargetsConditionPatcher(projectPath)) + { + RunNuget(args, packagesDirectory, logger); + } projectBuildFile = ProjectBuildFile.Open(repoRootPath, projectPath); projectBuildFile.NormalizeDirectorySeparatorsInProject(); diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/WebApplicationTargetsConditionPatcher.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/WebApplicationTargetsConditionPatcher.cs new file mode 100644 index 00000000000..a04746567ef --- /dev/null +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/WebApplicationTargetsConditionPatcher.cs @@ -0,0 +1,47 @@ +using System; +using System.IO; +using System.Linq; + +using Microsoft.Language.Xml; + +namespace NuGetUpdater.Core.Updater +{ + internal class WebApplicationTargetsConditionPatcher : IDisposable + { + private string? _capturedCondition; + private readonly XmlFilePreAndPostProcessor _processor; + + public WebApplicationTargetsConditionPatcher(string projectFilePath) + { + _processor = new XmlFilePreAndPostProcessor( + getContent: () => File.ReadAllText(projectFilePath), + setContent: s => File.WriteAllText(projectFilePath, s), + nodeFinder: doc => doc.Descendants() + .FirstOrDefault(e => e.Name == "Import" && e.GetAttributeValue("Project") == @"$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets") + as XmlNodeSyntax, + preProcessor: n => + { + var element = (IXmlElementSyntax)n; + _capturedCondition = element.GetAttributeValue("Condition"); + return (XmlNodeSyntax)element.RemoveAttributeByName("Condition").WithAttribute("Condition", "false"); + }, + postProcessor: n => + { + var element = (IXmlElementSyntax)n; + var newElement = element.RemoveAttributeByName("Condition"); + if (_capturedCondition is not null) + { + newElement = newElement.WithAttribute("Condition", _capturedCondition); + } + + return (XmlNodeSyntax)newElement; + } + ); + } + + public void Dispose() + { + _processor.Dispose(); + } + } +} diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/XmlFilePreAndPostProcessor.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/XmlFilePreAndPostProcessor.cs new file mode 100644 index 00000000000..a728a919a53 --- /dev/null +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/XmlFilePreAndPostProcessor.cs @@ -0,0 +1,55 @@ +using System; + +using Microsoft.Language.Xml; + +namespace NuGetUpdater.Core.Updater +{ + internal class XmlFilePreAndPostProcessor : IDisposable + { + public Func GetContent { get; } + public Action SetContent { get; } + public Func NodeFinder { get; } + public Func PreProcessor { get; } + public Func PostProcessor { get; } + + public XmlFilePreAndPostProcessor(Func getContent, Action setContent, Func nodeFinder, Func preProcessor, Func postProcessor) + { + GetContent = getContent; + SetContent = setContent; + NodeFinder = nodeFinder; + PreProcessor = preProcessor; + PostProcessor = postProcessor; + PreProcess(); + } + + public void Dispose() + { + PostProcess(); + } + + private void PreProcess() => RunProcessor(PreProcessor); + + private void PostProcess() => RunProcessor(PostProcessor); + + private void RunProcessor(Func processor) + { + var content = GetContent(); + var xml = Parser.ParseText(content); + if (xml is null) + { + return; + } + + var node = NodeFinder(xml); + if (node is null) + { + return; + } + + var replacementElement = processor(node); + var replacementXml = xml.ReplaceNode(node, replacementElement); + var replacementString = replacementXml.ToFullString(); + SetContent(replacementString); + } + } +} diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/XmlExtensions.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/XmlExtensions.cs index e31bb65b943..1a38affa7d7 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/XmlExtensions.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/XmlExtensions.cs @@ -30,6 +30,17 @@ public static IEnumerable GetElements(this IXmlElementSyntax return element.Attributes.FirstOrDefault(a => a.Name.Equals(name, comparisonType)); } + public static IXmlElementSyntax RemoveAttributeByName(this IXmlElementSyntax element, string attributeName, StringComparison comparisonType = StringComparison.Ordinal) + { + var attribute = element.GetAttribute(attributeName, comparisonType); + if (attribute is null) + { + return element; + } + + return element.RemoveAttribute(attribute); + } + public static string GetAttributeValue(this IXmlElementSyntax element, string name, StringComparison comparisonType) { return element.Attributes.First(a => a.Name.Equals(name, comparisonType)).Value;