How to trigger a design-time build on file change in Rider?
Asked Answered
A

1

6

I made a NuGet package which generates C# code from DSL files at design and build time. It works fine in Visual Studio but has some issues in Rider (which I'll describe below).

In Visual Studio

The package declares custom items for the DSL files, and assigns them a MSBuild:Compile Generator metadata, which triggers a design-time build in Visual Studio every time a file changes.

Then, a custom target hooks to the build with BeforeTargets="CoreCompile". It generates the C# code and adds Compile items to the project. The target runs in both normal and design-time builds, and supports incremental builds to avoid unnecessary work.

Here's the relevant (simplified and commented) MSBuild code:

The props file:

<Project>

  <!-- Define the item type with its default metadata -->
  <ItemDefinitionGroup>
    <ZebusMessages>
      <Generator>MSBuild:Compile</Generator>
    </ZebusMessages>
  </ItemDefinitionGroup>

  <!-- Make the item type user-visible in VS -->
  <ItemGroup>
    <AvailableItemName Include="ZebusMessages" />
    <PropertyPageSchema Include="$(MSBuildThisFileDirectory)ZebusMessages.xml" />
  </ItemGroup>

  <!-- Include all .msg files by default -->
  <ItemGroup>
    <ZebusMessages Include="**\*.msg" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
  </ItemGroup>

</Project>

The targets file:

<Project>

  <ItemGroup>
    <!-- Add a GeneratorTargetPath metadata for the target file -->
    <ZebusMessages Update="@(ZebusMessages)" GeneratorTargetPath="$([MSBuild]::ValueOrDefault('$(IntermediateOutputPath)ZebusMessages/%(RecursiveDir)%(FileName)%(Extension).cs', '').Replace('\', '/'))" />

    <!-- Remove all None items for .msg files -->
    <None Remove="**\*.msg" />
  </ItemGroup>

  <Target Name="GenerateZebusMessages"
          BeforeTargets="CoreCompile"
          Condition="'@(ZebusMessages)' != ''"
          Inputs="@(ZebusMessages)"
          Outputs="@(ZebusMessages->'%(GeneratorTargetPath)')">

    <!-- Generate output files -->
    <GenerateZebusMessagesTask InputFiles="@(ZebusMessages)" />

    <ItemGroup>
      <!-- Add the output items -->
      <Compile Include="@(ZebusMessages->'%(GeneratorTargetPath)')" Visible="false" />
      <FileWrites Include="@(ZebusMessages->'%(GeneratorTargetPath)')" />
    </ItemGroup>
  </Target>

</Project>

The full files are available here.

In Rider

Tested with Rider 2018.3.3:

This didn't work out of the box in Rider, as it doesn't seem to trigger design-time builds like VS does.

At first, Rider was totally unaware of the generated C# files, which caused it to display errors in code that references the generated classes.

I used Rider's internal mode which exposes a "Reload Project and Show Logs" action on the project, which shows that Rider calls MSBuild on project load with the following targets:

GenerateAssemblyInfo;_CheckForInvalidConfigurationAndPlatform;GetFrameworkPaths;ResolvePackageDependenciesDesignTime;CollectPackageReferences;BeforeResolveReferences;ResolveAssemblyReferences;ResolveComReferences;ResolveSDKReferences;ResolveCodeAnalysisRuleSet

Notice the lack of CompileDesignTime or Compile in that list.

So I added the following target to support Rider:

  <Target Name="GenerateZebusMessagesRiderDesignTime"
          Condition="'$(BuildingByReSharper)' == 'true'"
          AfterTargets="PrepareForBuild"
          DependsOnTargets="GenerateZebusMessages" />

It hooks to PrepareForBuild which ResolveAssemblyReferences depends on, and calls GenerateZebusMessages.

This makes Rider aware of the generated files, but there are issues I'd like to solve:

  • If I edit a .msg file, the generated C# code is only updated when the project is built, and Rider's code completion is not up to date until then.
  • If I add a .msg file to the project, I need to reload the project for it to be taken into account. Building the project doesn't help here.

Question

I'd like to make Rider call a MSBuild target and reevaluate the project whenever a .msg file is edited or is added/removed.

I only found the following in the logs when adding a file, but it doesn't help much:

10:54:05.076 |I| ProjectModel                   | RequestBuilder thread:7        | Add item ZebusMessages = 'OtherFile.msg'...
10:54:05.076 |I| ProjectModel                   | RequestBuilder thread:7        | Item matches to a wildcard pattern, mark project as dirty
10:54:05.095 |I| ProjectModel                   | RequestBuilder thread:7        | Project file content requested: ZebusMessages.csproj
10:54:08.330 |I| ProjectModel                   | RequestBuilder thread:7        | Item with EvaluatedInclude 'OtherFile.msg' was already changed. Perform project reevaluation before processing.
10:54:08.361 |I| ProjectModel                   | RequestBuilder thread:7        | Project 'ZebusMessages.csproj' was reevaluated in 22 ms, EvaluationCounter: 9

Nothing else mentions MSBuild in this time frame in the logs.

Is it possible to trigger something similar to a design-time build in Rider when a given file type changes?

Or more generally, under what circumstances does Rider call MSBuild to reevaluate a project?

Adjudicate answered 18/2, 2019 at 10:31 Comment(0)
W
4

I have two stories for you - the short one and the long one =)

The short story

It should work out of the box in the upcoming 2019.1 EAP 1

The long story

Rider (unlike Visual Studio) does not perform design time build during project loading stage. We thinks it is too expensive (especially for .net sdk based projects). So Rider evaluates every project and then builds some set of predefined targets to obtain generated files and assembly references.

Sometimes it cause issues (like this one) so in 2019.1 we have implemented additional algorithm which scans all imported targets and looks for a custom item-factory targets. As far as I can see, your target perfectly fits so Rider will be able to find it and build alongs with predefined targets.

If it will not work somehow you still will have two options:

  1. We have rewritten Build Tools option page, so you will be able to add any custom target to loading process
  2. Fire an issue here: https://youtrack.jetbrains.com/newIssue?project=Rider and I will try to fix your case ASAP. Such issues have the highest priority for us.
Wageworker answered 18/2, 2019 at 16:55 Comment(2)
I tried 2019.1 EAP 1 and it's much better (no workaround is required anymore for basic support), but some of the issues are still there. I filed an issue here.Adjudicate
I will reply you there =)Wageworker

© 2022 - 2024 — McMap. All rights reserved.