XDS
XDS

Reputation: 4206

How to prevent the package manager of Visual Studio / Rider from tweaking certain .csproj <PackageReference> elements

Essentially what the subject says. I have certain nuget package references that are meant to be loaded only in simulators for the sake of debugging:

<PackageReference Include="Foo.Bar" Version="1.2.4" Condition=" '$(IsForSimulator)' == 'false' ">

<PackageReference Include="Foo.Bar" Version="1.2.3-force-dud" Condition=" '$(IsForSimulator)' == 'true' ">
            <NoWarn>$(NoWarn);NU1605</NoWarn>
</PackageReference>

If I update the nuget package using the nuget package manager GUI to version 1.2.5, then both elements will be updated!

I want the second one to remain unaffected somehow. Is this possible?

Upvotes: 0

Views: 46

Answers (2)

XDS
XDS

Reputation: 4206

I came up with a method that works but note that if you need to update the -force-dud nuget to a newer version you must do so manually (I don't mind much in my own case since those nugets are meant to cater to the needs of simulators used in internal QA ...)

  1. Sniff whether you're building for a simulator:
<Target Name="EvaluateWhetherWeAimRealDevicesOrSimulators" BeforeTargets="BeforeBuild">
        <!-- the evaluation of these variables needs to happen late in the build and not at the beginning because RuntimeIdentifier is    -->
        <!-- empty during the early stages of the build                                                                                                          -->
        <PropertyGroup>
            <IsForSimulator Condition=" '$(RuntimeIdentifier.Contains(simulator))' == 'false' ">false</IsForSimulator>
            <IsForSimulator Condition=" '$(RuntimeIdentifier.Contains(simulator))' == 'true'  ">true</IsForSimulator>
        </PropertyGroup>
</Target>
  1. Inside the .csproj keep only this bit
<!-- it's safer to keep the condition on the item-group (rather on the package-ref element) so that it won't be yanked-off by mistake -->
<ItemGroup Condition=" '$(IsForSimulator)' == '' or '$(IsForSimulator)' == 'false' ">
     <PackageReference Include="Foo.Bar" Version="1.2.4">
</ItemGroup>
  1. Now create a separate file called 'NugetTweaksForSimulators.targets' and put this in:
<?xml version="1.0" encoding="utf-8"?>

<!-- the package manager cannot affect these nuget-entries because they're living inside this side-file! -->
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup Condition=" '$(IsForSimulator)' == 'true' ">
    <PackageReference Include="Foo.Bar" Version="1.2.3-force-dud">
       <NoWarn>$(NoWarn);NU1605</NoWarn>
    </PackageReference>
  </ItemGroup>
</Project>
  1. Include the 'NugetTweaksForSimulators.targets' on your main project:
<Import Project="NugetTweaksForSimulators.targets"/>

Mission accomplished!

Upvotes: 0

Jonathan Dodds
Jonathan Dodds

Reputation: 5163

This is effectively an addendum to the answer from @XDS that wouldn't fit in a comment.

When a project file is changed by a tool, the tool should evaluate the project, should honor Condition attributes, and should add or update but not remove content (including attributes).

Unfortunately, NuGet seems to break with this etiquette. I suspect it just parses the XML for PackageReference and doesn't understand the file content as an MSBuild Project.

The code shown in the answer from @XDS can be streamlined by being changed as follows.

project file

The test for $(IsForSimulator) can be moved to the Import.

  <ItemGroup>
    <PackageReference Include="Foo.Bar" Version="1.2.4">
  </ItemGroup>
  <Import Project="NugetTweaksForSimulators.targets" Condition="'$(IsForSimulator)' == 'true'"/>

NugetTweaksForSimulators.targets file

The reason the technique of importing a separate file works is because the project is apparently not being evaluated. If the project were evaluated, the Import would be performed and the content of the NugetTweaksForSimulators.targets would be visible to the package manager.

The NugetTweaksForSimulators.targets file can be more self contained. It doesn't need to know the $(IsForSimulator) property and it can remove any existing Foo.Bar PackageReference before adding the special one for the simulator.

<?xml version="1.0" encoding="utf-8"?>

<!-- the package manager cannot affect these nuget-entries because they're living inside this side-file! -->
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <!-- Remove 'Foo.Bar' if it is already in the item collection -->
    <PackageReference Remove="Foo.Bar">
    <!-- Replace with the version for simulator -->
    <PackageReference Include="Foo.Bar" Version="1.2.3-force-dud">
       <NoWarn>$(NoWarn);NU1605</NoWarn>
    </PackageReference>
  </ItemGroup>
</Project>

If there are other changes required for the simulator, they could all be located in the NugetTweaksForSimulators.targets file. Instead of potentially lots of Conditions in the project, there would be just the one Condition on the Import.

Upvotes: 1

Related Questions