Skip to content

VS Cache Busting in a VSIX Project #1226

@NightOwl888

Description

@NightOwl888

I am building a Roslyn analyzer and found that having a VSIX project is helpful for:

  1. Troubleshooting loading issues with the analyzer and its dependencies.
  2. Analyzing the lifecycle of the analyzer to optimize analyzer designs to take advantage of it (such as designing caches).

The VSIX project is not intended to be used for deployment, only for debugging. But, Visual Studio is very particular when it comes to loading an analyzer because it caches the last build and the best way I know around how to get it to load a new build with changes is to bump the AssemblyFileVersion.

However, with NBGV installed, the AssemblyFileVersion doesn't change unless I commit the changes before debugging them, which kind of defeats the purpose.

Side note, I haven't confirmed it is the AssemblyFileVersion that needs to be bumped, as this was info offered up by ChatGPT, but it is definitely either that or AssemblyVersion.

So, I started looking for a way to bump the version number based on a timestamp and it turns out that the functionality has been removed in the .NET SDK, but it is not hard to add it:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <!-- This is very limited support for version wildcards (1.0.* and 1.0.0.*) and does
      not take into consideration $(Version) or $(VersionPrefix) -->

  <PropertyGroup Condition="$(FileVersion.Contains('*'))">
    <WildcardFileVersion>$(FileVersion)</WildcardFileVersion>
    <!-- Empty the FileVersion so that MSBuild doesn't complain later about it not being valid -->
    <FileVersion></FileVersion>
    <GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
  </PropertyGroup>
  <PropertyGroup Condition="$(AssemblyVersion.Contains('*'))">
    <WildcardAssemblyVersion>$(AssemblyVersion)</WildcardAssemblyVersion>
    <!-- Empty the AssemblyVersion so that MSBuild doesn't complain later about it not being valid -->
    <AssemblyVersion></AssemblyVersion>
    <GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
  </PropertyGroup>

  <Target Name="EmitVersionAttributes" BeforeTargets="BeforeCompile">
    <GenerateTimestampVersion VersionTemplate="$(WildcardAssemblyVersion)" Condition="'$(WildcardAssemblyVersion)' != ''">
      <Output TaskParameter="OutputVersion" PropertyName="GeneratedAssemblyVersion" />
    </GenerateTimestampVersion>
    <GenerateTimestampVersion VersionTemplate="$(WildcardFileVersion)" Condition="'$(WildcardFileVersion)' != ''">
      <Output TaskParameter="OutputVersion" PropertyName="GeneratedFileVersion" />
    </GenerateTimestampVersion>
    <ItemGroup Condition="'$(WildcardFileVersion)' != ''">
      <AssemblyAttribute Include="System.Reflection.AssemblyFileVersion">
        <_Parameter1>$(GeneratedFileVersion)</_Parameter1>
      </AssemblyAttribute>
      <Message Importance="high" Text="WildcardFileVersion: $(WildcardFileVersion), GeneratedFileVersion: $(GeneratedFileVersion)"/>
    </ItemGroup>
    <ItemGroup Condition="'$(WildcardAssemblyVersion)' != ''">
      <AssemblyAttribute Include="System.Reflection.AssemblyVersion">
        <_Parameter1>$(GeneratedAssemblyVersion)</_Parameter1>
      </AssemblyAttribute>
      <Message Importance="high" Text="WildcardAssemblyVersion: $(WildcardAssemblyVersion), GeneratedAssemblyVersion: $(GeneratedAssemblyVersion)"/>
    </ItemGroup>
  </Target>

  <UsingTask TaskName="GenerateTimestampVersion"
             TaskFactory="RoslynCodeTaskFactory"
             AssemblyFile="$(MSBuildBinPath)\Microsoft.Build.Tasks.Core.dll">
    <ParameterGroup>
      <VersionTemplate ParameterType="System.String" Required="false" />
      <OutputVersion ParameterType="System.String" Output="true" />
    </ParameterGroup>
    <Task>
      <Using Namespace="System" />
      <Code Type="Fragment" Language="cs">
        <![CDATA[
        string baseVersion = VersionTemplate ?? "1.0.*";
        var now = DateTime.UtcNow;
        var build = (now - new DateTime(2000, 1, 1)).Days;
        var revision = (int)(now.TimeOfDay.TotalSeconds / 2);

        string[] parts = baseVersion.Split('.');
        int major = parts.Length > 0 && int.TryParse(parts[0], out var m) ? m : 1;
        int minor = parts.Length > 1 && int.TryParse(parts[1], out var n) ? n : 0;
        int buildPart = parts.Length > 2 && parts[2] != "*" ? int.Parse(parts[2]) : build;
        int revPart = parts.Length > 3 && parts[3] != "*" ? int.Parse(parts[3]) : revision;

        OutputVersion = $"{major}.{minor}.{buildPart}.{revPart}";
        ]]>
      </Code>
    </Task>
  </UsingTask>
</Project>

However, there doesn't seem to be any NBGV equivalent to the GenerateAssemblyFileVersionAttribute which is exactly what I need to pull this off. Ideally, I would feed the MajorMinorVersion into this generator to create a timestamp based version number only when in Debug mode.

  <PropertyGroup Condition="'$(Configuration)' == 'Debug'">
    <!-- Override Nerdbank.GitVersioning's output to bust the VS cache for debugging -->
    <NGBV_EmitAssemblyFileVersion>false</NGBV_EmitAssemblyFileVersion>
    <FileVersion>$(MajorMinorVersion).*</FileVersion>
  </PropertyGroup>

  <Import Project="WildcardVersionSupport.targets"/>

The problem is there doesn't appear to be a way to prevent NBGV from outputting one of the assembly attributes like there is in MSBuild, and it doesn't respect when <FileVersion> is set in project file to override what is generated by NBGV.

It seems the only way to pull this off is to completely disable NBGV in debug mode for the analyzer project before it is packed by the VSIX project.

  <PropertyGroup Condition="'$(Configuration)' == 'Debug'">
    <FileVersion>1.0.*</FileVersion>
  </PropertyGroup>

  <ItemGroup Condition=" '$(Configuration)' != 'Debug'>
    <PackageReference Include="Nerdbank.GitVersioning" Version="$(NerdBankGitVersioningPackageReferenceVersion)" PrivateAssets="All" />
  </ItemGroup>

  <Import Project="WildcardVersionSupport.targets"/>

Or am I missing something?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions