Skip to content

Commit

Permalink
force set Condition="false" on Microsoft.WebApplication.targets
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
brettfo committed Jan 31, 2024
1 parent 45e0181 commit 53b227b
Show file tree
Hide file tree
Showing 5 changed files with 287 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -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: """
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>
</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>68ed3303-52a0-47b8-a687-3abbb07530da</ProjectGuid>
<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TestProject</RootNamespace>
<AssemblyName>TestProject</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
<HintPath>packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.DynamicData" />
<Reference Include="System.Web.Entity" />
<Reference Include="System.Web.ApplicationServices" />
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Core" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Drawing" />
<Reference Include="System.Web" />
<Reference Include="System.Xml" />
<Reference Include="System.Configuration" />
<Reference Include="System.Web.Services" />
<Reference Include="System.EnterpriseServices" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<PropertyGroup>
<!-- some project files set this property which makes the Microsoft.WebApplication.targets import a few lines down always fail -->
<VSToolsPath Condition="'$(VSToolsPath)' == ''">C:\some\path\that\does\not\exist</VSToolsPath>
</PropertyGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
""",
packagesConfigContents: """
<packages>
<package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" />
</packages>
""",
expectedProjectContents: """
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>
</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>68ed3303-52a0-47b8-a687-3abbb07530da</ProjectGuid>
<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TestProject</RootNamespace>
<AssemblyName>TestProject</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
<HintPath>packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.DynamicData" />
<Reference Include="System.Web.Entity" />
<Reference Include="System.Web.ApplicationServices" />
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Core" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Drawing" />
<Reference Include="System.Web" />
<Reference Include="System.Xml" />
<Reference Include="System.Configuration" />
<Reference Include="System.Web.Services" />
<Reference Include="System.EnterpriseServices" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<PropertyGroup>
<!-- some project files set this property which makes the Microsoft.WebApplication.targets import a few lines down always fail -->
<VSToolsPath Condition="'$(VSToolsPath)' == ''">C:\some\path\that\does\not\exist</VSToolsPath>
</PropertyGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
""",
expectedPackagesConfigContents: """
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net45" />
</packages>
""");
}

protected static async Task TestUpdateForProject(
string dependencyName,
string oldVersion,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Xml.Linq;

using Microsoft.Language.Xml;
using NuGetUpdater.Core.Updater;

namespace NuGetUpdater.Core;

Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;

using Microsoft.Language.Xml;

namespace NuGetUpdater.Core.Updater
{
internal class XmlFilePreAndPostProcessor : IDisposable
{
public Func<string> GetContent { get; }
public Action<string> SetContent { get; }
public Func<XmlDocumentSyntax, XmlNodeSyntax?> NodeFinder { get; }
public Func<XmlNodeSyntax, XmlNodeSyntax> PreProcessor { get; }
public Func<XmlNodeSyntax, XmlNodeSyntax> PostProcessor { get; }

public XmlFilePreAndPostProcessor(Func<string> getContent, Action<string> setContent, Func<XmlDocumentSyntax, XmlNodeSyntax?> nodeFinder, Func<XmlNodeSyntax, XmlNodeSyntax> preProcessor, Func<XmlNodeSyntax, XmlNodeSyntax> 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<XmlNodeSyntax, XmlNodeSyntax> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ public static IEnumerable<IXmlElementSyntax> 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;
Expand Down

0 comments on commit 53b227b

Please sign in to comment.