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).
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.
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:
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?
I have two stories for you - the short one and the long one =)
It should work out of the box in the upcoming 2019.1 EAP 1
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:
Build Tools
option page, so you will be able to add any custom target to loading processIf you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With