Some poetry:
In my case - I needed to have plugin dlls
built for the unit test
project specifically.. If you do it the "normal way", by creating separate project for each plugin - then you will end up having more unit-test related projects than core assemblies.. So I think that in some cases it is worth doing multi-builds within same project. With that being said - I would like to provide an improved version of the accepted answer.
New approach improvements:
- support for new
SDK
projects (used in .net Core)
- support to build assemblies that are referencing another projects within solution!
Create a new empty library project, make sure that the structure is following:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
</PropertyGroup>
</Project>
hint: you can create .Net core project and change the target to net472
, for example.
Add a plugin and some references if needed, then, per following documentation, add:
https://learn.microsoft.com/en-us/dotnet/core/tools/csproj
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
This will make it possible to manually include files for building.. otherwise the build will include all files by default.
Then explicitly add the item to be compiled:
<ItemGroup>
<Compile Include="TestApp1.cs">
<Plugin>true</Plugin>
</Compile>
</ItemGroup>
Then, as mentioned in the following source - you can aggregate all references into a single string: How to get paths to all referenced DLLs in MSBuild?
<Target Name="GatherReferences" DependsOnTargets="ResolveReferences">
<ItemGroup>
<MyReferencedAssemblies Include="@(ReferencePath)" />
</ItemGroup>
</Target>
To test:
<Target Name="TestMessage" AfterTargets="Build" >
<Message Text="Referenced assemblies: @(ReferencePath)" Importance="high"/>
</Target>
Then you have to append a build target that is triggered on PostBuild:
<Target Name="BuildPlugins" AfterTargets="PostBuildEvent">
<CSC Condition="%(Compile.Plugin) == 'true'"
Sources="%(Compile.FullPath)"
TargetType="library"
References="@(ReferencePath)"
OutputAssembly="$(OutputPath)%(Compile.FileName).dll"
EmitDebugInformation="true" />
</Target>
The difference here you will notice is a References
property which basically translates the value into appropriate -reference argument for CSC compiler: https://learn.microsoft.com/en-us/visualstudio/msbuild/csc-task?view=vs-2019
bonus: you might incorporate this logic into your project even without having to explicitly define the Plugin
property..
According to this topic here: MsBuild Condition Evaluate Property Contains
It is possible to use Regex expression on build metadata: https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-well-known-item-metadata?view=vs-2019
So basically, something like this is allowed:
Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('%(Compile.Filename)', 'Plugin'))"
You can make it so, that if the Class filename contains a specific keyword, then you can trigger a separate compilation for such file.. Or if the file is located in specific folder! In example above if the file has word "Plugin" in it, then it will get picked up by CSC task.. I recommend checking metadata page to see all options.
bonus: If you just like me like being able to step up into the code and also to be able to output into the Debug
output window, you can also define:
DefineConstants="DEBUG;TRACE"
DebugType="full"
My latest configuration looks like this:
<Target Name="BuildPlugins" AfterTargets="PostBuildEvent">
<CSC
Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('%(Compile.FullPath)', '.*\\AppUnitTests\\Plugins\\.*.cs'))"
Sources="%(Compile.FullPath)"
TargetType="library"
References="@(ReferencePath)"
OutputAssembly="$(OutputPath)Plugins\%(Compile.FileName).dll"
EmitDebugInformation="true"
DefineConstants="DEBUG;TRACE"
DebugType="full" />
</Target>
Hopefully it was useful for someone ;)
p.s. my thanks to original author for giving an example that I could improve upon.