“复制本地”和项目参考的最佳实践是什么?

我有一个大的 c # 解决方案文件(大约100个项目) ,我正在努力提高构建时间。我认为“复制本地”在许多情况下对我们来说是浪费,但我想知道最佳实践。

在我们的。我们的应用程序 A 依赖于程序集 B,而程序集 B 依赖于程序集 C。在我们的例子中,有几十个“ B”和少量的“ C”。因为这些都包含在。Sln,我们使用项目引用。当前构建到 $(SolutionDir)/Debug (或发布)中的所有程序集。

默认情况下,Visual Studio 将这些项目引用标记为“ Copy Local”,这将导致每个“ C”被复制到 $(SolutionDir)/Debug 中,每个“ B”被构建一次。这看起来很浪费。如果我把“本地复制”关掉会出什么问题呢?其他拥有大型系统的人做什么?

后续:

许多回复建议将构建分解为更小的版本。在上面的例子中,我首先构建基础类“ C”,然后是大量的模块“ B”,然后是一些应用程序“ A”。在这个模型中,我需要从 B 中获得对 C 的非项目引用。我遇到的问题是,“ Debug”或“ Release”被嵌入到提示路径中,我最终构建了“ B”版本的发布版本,而“ C”版本的调试版本则构建了“ B”版本的发布版本。

对于那些将构建分解为多个.sln 文件的用户,您如何处理这个问题?

97212 次浏览

我们的“最佳实践”是避免使用多个项目的解决方案。 我们有一个名为“ Matrix”的目录,其中包含程序集的当前版本,并且所有引用都来自此目录。如果您更改了某个项目,并且您可以说“现在更改已完成”,那么您可以将程序集复制到“ Matrix”目录中。因此,所有依赖于此程序集的项目都将具有当前(= 最新)版本。

如果解决方案中的项目很少,则构建过程会快得多。

您可以使用可视化工作室宏或“ menu-> tools-> foreign tools...”来自动执行“将程序集复制到矩阵目录”步骤。

我倾向于构建到一个公共目录(例如. bin) ,这样我就可以创建小型的测试解决方案。

在我看来,拥有一个包含100个项目的解决方案是一个巨大的错误。您可以将解决方案分割成有效的逻辑小单元,从而简化了维护和构建。

通常,如果希望项目使用 Bin 中的 DLL 而不是其他地方的 DLL (GAC、其他项目等等) ,那么只需要复制本地

我倾向于同意其他人的观点,即如果可能的话,你也应该尝试打破这种解决方案。

您还可以使用配置管理器在一个解决方案中为自己创建不同的生成配置,这个解决方案只生成给定的项目集。

如果所有100个项目都相互依赖,这看起来很奇怪,因此您应该能够将它们分开或者使用 Configuration Manager 来帮助自己。

您可以尝试使用一个文件夹,其中将复制项目之间共享的所有程序集,然后在每个开发人员的工作站上创建一个 devPATH 环境变量,并在 machine.config 文件中设置 < br/> < br/> < br/> 。您唯一需要做的事情是将任何新的版本复制到您的文件夹中的 devPATH 变量点。

如果可能的话,还可以将您的解决方案分成几个较小的解决方案。

这可能不是最佳实践,但这就是我的工作方式。

我注意到 ManagedC + + 将它的所有二进制文件转储到 $(SolutionDir)/‘ DebugOrrelease’中。 所以我把所有的 C # 项目也扔在那里了。我还关闭了解决方案中对项目的所有引用的“复制本地”。在我的10个小项目解决方案中,我有明显的构建时间改进。这个解决方案是 C # 、托管 C + + 、本机 C + + 、 C # webservice 和安装程序项目的混合体。

也许有些东西坏了,但是因为这是我工作的唯一方式,我没有注意到它。

要是能知道我打破了什么就有意思了。

您可以让项目引用指向 dll 的调试版本。 比起在 msbuild 脚本中,您可以设置 /p:Configuration=Release,因此您将拥有应用程序的发行版和所有附属程序集。

如果你通过项目引用或解决方案级别的依赖关系定义了依赖结构,那么可以安全地使用“复制本地”,我甚至会说这是一个最好的做法,因为这样可以让你使用 MSBuild 3.5并行运行构建(通过/maxcpuccount) ,而不会在试图复制引用的程序集时出现不同进程之间的相互跳跃。

在之前的一个项目中,我使用了一个带有项目引用的大型解决方案,也遇到了性能问题。解决办法有三个:

  1. 始终将 CopyLocal 属性设置为 false,并通过自定义 msbuild 步骤强制执行此属性

  2. 将每个项目的输出目录设置为相同的目录(最好相对于 $(SolutionDir))

  3. 框架附带的默认 cs 目标计算要复制到当前正在构建的项目的输出目录的引用集。由于这需要在“参考”关系下计算一个传递闭包,这可能会变得很昂贵。我的解决办法是在一个公共目标文件中重新定义 GetCopyToOutputDirectoryItems目标(例如。在导入 Microsoft.CSharp.targets之后,导入到每个项目中的 Common.targets)。导致每个项目文件看起来如下:

    <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.

The redefined GetCopyToOutputDirectoryItems can be created by copying the lines 2,438–2,450 and 2,474–2,524 from C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Microsoft.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 决定,而不是通过拆分解决方案手工完成。

我建议你读读帕特里克•斯马奇亚(Patric Smacchia)关于这个主题的文章:

Net VS 项目依赖于设置为 true 的复制本地引用程序集选项 这不仅大大增加了编译时间(在 NUnit 的情况下是 x3) ,而且还扰乱了您的工作环境。最后但并非最不重要的是,这样做会带来对潜在问题进行版本控制的风险。顺便说一句,如果 NDepend 在2个不同的目录中发现2个具有相同名称但内容或版本不相同的程序集,它将发出警告。

正确的做法是定义2个目录 $RootDir $bin Debug 和 $RootDir $bin Release,并配置 VisualStudio 项目以在这些目录中发出程序集。所有项目引用都应该引用 Debug 目录中的程序集。

您还可以阅读 这篇文章,以帮助您减少项目数量并提高编译时间。

你说得对。CopyLocal 绝对会扼杀你的构建时间。如果您有一个大的源树,那么您应该禁用 CopyLocal。不幸的是,它并不像它应该是清洁地禁用它那么容易。我已经回答了关于在 如何从 MSBUILD 覆盖.NET 中引用的 CopyLocal (Private)设置禁用 CopyLocal 的问题。看看这个。还有 VisualStudio 中大型解决方案的最佳实践(2008)。

以下是我看到的 CopyLocal 的更多信息。

CopyLocal 的实现实际上是为了支持本地调试。在为打包和部署准备应用程序时,应该将项目生成到相同的输出文件夹,并确保在那里具有所需的所有引用。

我已经在 MSBuild: 创建可靠构建的最佳实践,第2部分文章中介绍了如何构建大型源代码树。

如果您希望有一个中心位置来引用使用 copy local false 的 DLL,除非您这样做,否则没有 GAC 的情况下将会失败。

Http://nbaked.wordpress.com/2010/03/28/gac-alternative/

我建议几乎所有项目都使用 copy local = false,除了位于依赖树顶部的项目。对于顶部集合中的所有引用,复制 local = true。我看到许多人建议共享一个输出目录; 我认为这是一个基于经验的可怕想法。如果您的启动项目持有对某个 dll 的引用,而其他任何项目持有对您的引用,那么在某个时刻都会遇到访问共享冲突,即使在所有内容上都拷贝 local = false,您的构建也将失败。这个问题很烦人,很难找到。我完全建议远离碎片输出目录,而不是让位于依赖链顶端的项目将所需的程序集写入相应的文件夹。如果你没有一个项目在“顶部”,那么我会建议一个后构建的副本,以获得一切在正确的地方。另外,我会尽量记住调试的简单性。我仍然保留 copy local = true 的任何 exe 项目,这样 F5调试体验就可以工作了。

设置 CopyLocal = false 可以减少构建时间,但是在部署期间会导致不同的问题。

当您需要将 CopyLocal’左转为 True 时,有许多情况,例如。

  • 顶级项目,
  • 二级依赖关系,
  • 通过反射调用 DLL

SO 题中可能出现的问题
Copy-local 什么时候应该设置为 true,什么时候不应该设置为 true?
错误信息 & # 39; 无法加载一个或多个请求的类型。检索 LoaderException 属性获取更多信息。 & # 39;
Aaron-Stainback回答来回答这个问题。

我设置 CopyLocal = false 的经验并不成功

解决问题的时间超过了设置 CopyLocal = false 的好处。

我很惊讶没有人提到使用硬链接。它不复制文件,而是创建到原始文件的硬链接。这不仅节省了磁盘空间,而且大大加快了构建速度。这可以通过以下属性在命令行上启用:

/p: CreateHardLinksForAdditionalFilesIfmight = true; CreateHardLinksForCopyAdditionalFilesIfmight = true; CreateHardLinksForCopyFilesToOutputDirectoryIfmight = true; CreateHardLinksForCopyLocalIfmight = true; CreateHardLinksForPublishFilesIfmight = true

您还可以将其添加到一个集中导入文件,以便您的所有项目也可以获得这一好处。

您不需要更改 CopyLocal 值。您所需要做的就是为解决方案中的所有项目预定义一个公共 $(OutputPath) ,并将 $(UseCommonOutputDirectory)预设为 true。看这个: Http://blogs.msdn.com/b/kirillosenkov/archive/2015/04/04/using-a-common-intermediate-and-output-directory-for-your-solution.aspx

如果引用不包含在 GAC 中,我们必须将 CopyLocal 设置为 true,这样应用程序才能工作,如果我们确信引用将预先安装在 GAC 中,那么它可以设置为 false。

嗯,我当然不知道这些问题是如何解决的,但是我接触过一个构建解决方案,它帮助自己创建了所有在 象征性的联系帮助下放在 内存磁盘上的文件。

  • 解决方案文件夹 bin-> ramdisk r: 方案文件夹 bin
  • 解决方案文件夹 obj-> ramdisk r: Solution 文件夹 obj

  • 此外,还可以告诉可视化工作室它可以使用哪个临时目录进行构建。

事实上,这并不是它所做的全部。但它真的打击了我对性能的理解。
100% 的处理器使用和一个庞大的项目在3分钟内与所有依赖。