问题描述
我有一个大型 c# 解决方案文件(约 100 个项目),我正在努力缩短构建时间.我认为复制本地"在很多情况下对我们来说是一种浪费,但我想知道最佳实践.
I have a large c# solution file (~100 projects), and I am trying to improve build times. I think that "Copy Local" is wasteful in many cases for us, but I am wondering about best practices.
在我们的 .sln 中,应用程序 A 依赖于程序集 B,而程序集 B 又依赖于程序集 C.在我们的例子中,有几十个B"和少数C".由于这些都包含在 .sln 中,因此我们使用的是项目引用.当前所有程序集都构建到 $(SolutionDir)/Debug(或 Release)中.
In our .sln, we have application A depending on assembly B which depends on assembly C. In our case, there are dozens of "B" and a handful of "C". Since these are all included in the .sln, we're using project references. All assemblies currently build into $(SolutionDir)/Debug (or Release).
默认情况下,Visual Studio 将这些项目引用标记为复制本地",这会导致每个C"被复制到 $(SolutionDir)/Debug 中,对于每个构建的B".这似乎很浪费.如果我只关闭复制本地"会出现什么问题?其他拥有大型系统的人是做什么的?
By default, Visual Studio marks these project references as "Copy Local", which results in every "C" being copied into $(SolutionDir)/Debug once for every "B" that builds. This seems wasteful. What can go wrong if I just turn "Copy Local" off? What do other people with large systems do?
跟进:
很多回复建议将构建分解为更小的 .sln 文件...在上面的示例中,我将首先构建基础类C",然后是大部分模块B",然后是一些应用程序,A".在这个模型中,我需要从 B 获得对 C 的非项目引用.我遇到的问题是调试"或发布"被纳入提示路径,我最终构建了B"的发布版本针对C"的调试版本.
Lots of responses suggest breaking up the build into smaller .sln files... In the example above, I would build the foundation classes "C" first, followed by the bulk of the modules "B", and then a few applications, "A". In this model, I need to have non-project references to C from B. The problem I run into there is that "Debug" or "Release" gets baked into the hint path and I wind up building my Release builds of "B" against debug builds of "C".
对于那些将构建拆分为多个 .sln 文件的人,您如何处理这个问题?
For those of you that split the build up into multiple .sln files, how do you manage this problem?
推荐答案
在之前的项目中,我使用了一个带有项目引用的大型解决方案,但也遇到了性能问题.解决方案是三倍:
In a previous project I worked with one big solution with project references and bumped into a performance problem as well. The solution was three fold:
始终将 Copy Local 属性设置为 false 并通过自定义 msbuild 步骤强制执行此操作
Always set the Copy Local property to false and enforce this via a custom msbuild step
将每个项目的输出目录设置为同一个目录(最好相对于$(SolutionDir)
Set the output directory for each project to the same directory (preferably relative to $(SolutionDir)
框架附带的默认 cs 目标计算要复制到当前正在构建的项目的输出目录的引用集.由于这需要在参考"关系下计算传递闭包,这可能会变得非常代价高昂.我的解决方法是在导入 Microsoft 后在每个项目中导入的通用目标文件(例如
.导致每个项目文件如下所示:Common.targets
)中重新定义 GetCopyToOutputDirectoryItems
目标.CSharp.targets
The default cs targets that get shipped with the framework calculate the set of references to be copied to the output directory of the project currently being built. Since this requires calculating a transitive closure under the 'References' relation this can become VERY costly. My workaround for this was to redefine the GetCopyToOutputDirectoryItems
target in a common targets file (eg. Common.targets
) that's imported in every project after the import of the Microsoft.CSharp.targets
. Resulting in every project file to look like the following:
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
... snip ...
</ItemGroup>
<Import Project="$(MSBuildBinPath)Microsoft.CSharp.targets" />
<Import Project="[relative path to Common.targets]" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
这将我们在给定时间的构建时间从几个小时(主要是由于内存限制)减少到了几分钟.
This reduced our build time at a given time from a couple of hours (mostly due to memory constraints), to a couple of minutes.
可以通过从 C:WINDOWSMicrosoft.NETFrameworkv2.0.50727Microsoft 复制第 2,438–2,450 和 2,474–2,524 行来创建重新定义的
变成 GetCopyToOutputDirectoryItems
.Common.targetsCommon.targets
.
The redefined GetCopyToOutputDirectoryItems
can be created by copying the lines 2,438–2,450 and 2,474–2,524 from C:WINDOWSMicrosoft.NETFrameworkv2.0.50727Microsoft.Common.targets
into Common.targets
.
为了完整起见,生成的目标定义变为:
For completeness the resulting target definition then becomes:
<!-- This is a modified version of the Microsoft.Common.targets
version of this target it does not include transitively
referenced projects. Since this leads to enormous memory
consumption and is not needed since we use the single
output directory strategy.
============================================================
GetCopyToOutputDirectoryItems
Get all project items that may need to be transferred to the
output directory.
============================================================ -->
<Target
Name="GetCopyToOutputDirectoryItems"
Outputs="@(AllItemsFullPathWithTargetPath)"
DependsOnTargets="AssignTargetPaths;_SplitProjectReferencesByFileExistence">
<!-- Get items from this project last so that they will be copied last. -->
<CreateItem
Include="@(ContentWithTargetPath->'%(FullPath)')"
Condition="'%(ContentWithTargetPath.CopyToOutputDirectory)'=='Always' or '%(ContentWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"
>
<Output TaskParameter="Include" ItemName="AllItemsFullPathWithTargetPath"/>
<Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectoryAlways"
Condition="'%(ContentWithTargetPath.CopyToOutputDirectory)'=='Always'"/>
<Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectory"
Condition="'%(ContentWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"/>
</CreateItem>
<CreateItem
Include="@(_EmbeddedResourceWithTargetPath->'%(FullPath)')"
Condition="'%(_EmbeddedResourceWithTargetPath.CopyToOutputDirectory)'=='Always' or '%(_EmbeddedResourceWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"
>
<Output TaskParameter="Include" ItemName="AllItemsFullPathWithTargetPath"/>
<Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectoryAlways"
Condition="'%(_EmbeddedResourceWithTargetPath.CopyToOutputDirectory)'=='Always'"/>
<Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectory"
Condition="'%(_EmbeddedResourceWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"/>
</CreateItem>
<CreateItem
Include="@(Compile->'%(FullPath)')"
Condition="'%(Compile.CopyToOutputDirectory)'=='Always' or '%(Compile.CopyToOutputDirectory)'=='PreserveNewest'">
<Output TaskParameter="Include" ItemName="_CompileItemsToCopy"/>
</CreateItem>
<AssignTargetPath Files="@(_CompileItemsToCopy)" RootFolder="$(MSBuildProjectDirectory)">
<Output TaskParameter="AssignedFiles" ItemName="_CompileItemsToCopyWithTargetPath" />
</AssignTargetPath>
<CreateItem Include="@(_CompileItemsToCopyWithTargetPath)">
<Output TaskParameter="Include" ItemName="AllItemsFullPathWithTargetPath"/>
<Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectoryAlways"
Condition="'%(_CompileItemsToCopyWithTargetPath.CopyToOutputDirectory)'=='Always'"/>
<Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectory"
Condition="'%(_CompileItemsToCopyWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"/>
</CreateItem>
<CreateItem
Include="@(_NoneWithTargetPath->'%(FullPath)')"
Condition="'%(_NoneWithTargetPath.CopyToOutputDirectory)'=='Always' or '%(_NoneWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"
>
<Output TaskParameter="Include" ItemName="AllItemsFullPathWithTargetPath"/>
<Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectoryAlways"
Condition="'%(_NoneWithTargetPath.CopyToOutputDirectory)'=='Always'"/>
<Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectory"
Condition="'%(_NoneWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"/>
</CreateItem>
</Target>
有了这个解决方法,我发现在一个解决方案中拥有多达 120 个项目是可行的,这主要的好处是项目的构建顺序仍然可以由 VS 确定,而不是通过拆分手动完成提出你的解决方案.
With this workaround in place I found it workable to have as much as > 120 projects in one solution, this has the main benefit that the build order of the projects can still be determined by VS instead of doing that by hand by splitting up your solution.
这篇关于“复制本地"的最佳做法是什么?和项目参考?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!