I'm programming a project with plugin support. Since many of the plugins are relatively small (only one source-file/class) I would like to have them all in one project in visual studio, but to successfully do this I would need each source-file/class to be compiled into its own dll file, is this possible using visual studio?
If this is not possible with visual studio, would it be possible using another build system, while still coding and debugging with visual studio?
Currently I开发者_运维问答've set the plugins project output type to console, and programmed a main() method that will compile all .cs files in the source directory to dlls and copy those to the proper directory. Then I set that console app to be the post-build event of the plugins project. It works, but it seems like a very ugly hack.
Using visual studio 2010.
You could create one project for each plugin and group all projects in a solution.
If you don't want to have one project per plugin, you could create a custom build with MSBuild using CSC task
How to generate a dll for each plugin file
In a project you add all plugins files
Edit the project file to specify which class will generate a plugin library :
<ItemGroup> <Compile Include="Class1.cs"> <Plugin>true</Plugin> </Compile> <Compile Include="Class2.cs" /> <Compile Include="Class3.cs"> <Plugin>true</Plugin> </Compile> <Compile Include="Program.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> </ItemGroup>
Add a new target in your project file to generate the plugins library
<Target Name="BuildPlugins"> <CSC Condition="%(Compile.Plugin) == 'true'" Sources="%(Compile.FullPath)" TargetType="library" OutputAssembly="$(OutputPath)%(Compile.FileName).dll" EmitDebugInformation="true" /> </Target>
If you want to create the plugins library after each build, add an after build target :
<Target Name="AfterBuild" DependsOnTargets="BuildPlugins"> </Target>
You just have to create a Solution
then add as many projects you want.
You can have like 5 Class Library
projects and compile them, generating 5 DLLs.
To expand upon Julien Hoarau's answer above, here is a solution that allows you to compile multiple DLL files from within a single project, and have those DLL files be compiled from multiple CS files. Just open your csproj file and add this before the </Project> tag:
<!-- Plugin Building -->
<!-- 1. Hardlink to NuGet References - CSC does not read HintPaths, so you will have to create these for all your packages -->
<ItemGroup>
<PluginReference Include="..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll" ><InProject>false</InProject></PluginReference>
<PluginReference Include="..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll" ><InProject>false</InProject></PluginReference>
<PluginReference Include="..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll" ><InProject>false</InProject></PluginReference>
<PluginReference Include="..\packages\Microsoft.AspNet.Web.Optimization.1.1.3\lib\net40\System.Web.Optimization.dll" ><InProject>false</InProject></PluginReference>
<PluginReference Include="..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll" ><InProject>false</InProject></PluginReference>
</ItemGroup>
<!-- 2. Each Plugin CS Files -->
<!-- You see that each tag in here has it's own name starting with Plugin -->
<!-- We can reference that later e.g. as @(PluginBlue) to get an array list to pass to the CSC sources, allowing us to have multiple files -->
<!-- Plugin.Blue\**\*.cs gets all the files in the "Plugin.Blue" folder -->
<!-- Plugin.Green just has a specific file list -->
<ItemGroup>
<PluginBlue Include="Plugin.Blue\**\*.cs"><InProject>false</InProject></PluginBlue>
<PluginGreen Include="Plugin.Green\File1.cs"><InProject>false</InProject></PluginGreen>
<PluginGreen Include="Plugin.Green\File2.cs"><InProject>false</InProject></PluginGreen>
</ItemGroup>
<!-- 3. Build each Plugin -->
<Target Name="BuildPlugins">
<!-- Plugin Blue -->
<CSC Sources="@(PluginBlue)" References="@(PluginReference)" TargetType="library" OutputAssembly="$(OutputPath)Plugin.Blue.dll" EmitDebugInformation="true" />
<!-- Plugin Green -->
<CSC Sources="@(PluginGreen)" References="@(PluginReference)" TargetType="library" OutputAssembly="$(OutputPath)Plugin.Green.dll" EmitDebugInformation="true" />
</Target>
<!-- 4. Require Build on Solution Compile -->
<Target Name="AfterBuild" DependsOnTargets="BuildPlugins">
</Target>
This is my approach - it lets you keep everything organized at the bottom instead of spread out throughout the project file. Using
<InProject>false</InProject>
allows us to hide the files from the SolutionExplorer, and have a separate defined list of files instead of simply adding the plugin tag to the files we want. In your main solution, be sure to set the Build Action to "none" on all the files you are compiling in the plugin so there is no duplication in the main project file.
Some more reading about CSC:
https://msdn.microsoft.com/en-us/library/78f4aasd.aspx Command-line Building With csc.exe
https://msdn.microsoft.com/en-us/library/ms379563(v=vs.80).aspx Working with the C# 2.0 Command Line Compiler
https://msdn.microsoft.com/en-us/library/s5c8athz.aspx Csc Task
https://msdn.microsoft.com/en-us/library/7szfhaft.aspx MSBuild Conditions
I hope this is useful to someone.
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.
If you want each code file to be it's own DLL, then you have to make a new project for each code file. But you can put multiple project files (.csproj) into one big solution.
If this doesn't suite you, then you could always compile your project using the command line. That would allow you to customize your build to suite your needs. For instance you could write a batch scrip, or a powershell script that would compile all your code files into a separate DLL.
精彩评论