"复制本地"和项目引用的最佳做法是什么?

Dav*_*ore 154 .net c# msbuild visual-studio

我有一个大的c#解决方案文件(~100个项目),我正在努力改善构建时间.我认为"复制本地"在很多情况下对我们来说都是浪费,但我想知道最佳做法.

在我们的.sln中,我们有应用程序A取决于程序集B,它取决于程序集C.在我们的例子中,有几十个"B"和一些"C".由于这些都包含在.sln中,我们正在使用项目引用.所有程序集当前都构建为$(SolutionDir)/ Debug(或Release).

默认情况下,Visual Studio将这些项目引用标记为"复制本地",这会导致每个"C"被复制到$(SolutionDir)/ Debug中,对于构建的每个"B".这似乎很浪费.如果我只关闭"复制本地",会出现什么问题?大型系统的其他人做什么?

跟进:

很多响应建议将构建分解为较小的.sln文件......在上面的示例中,我将首先构建基础类"C",然后是大量模块"B",然后是一些应用程序,"一个".在这个模型中,我需要对来自B的C进行非项目引用.我遇到的问题是"Debug"或"Release"被添加到提示路径中,我最终构建了我的Release版本的"B"反对"C"的调试版本.

对于那些将构建拆分为多个.sln文件的人,如何管理这个问题?

Bas*_*ink 85

在之前的项目中,我使用了一个带有项目引用的大解决方案,并遇到了性能问题.解决方案有三个方面:

  1. 始终将"复制本地"属性设置为false,并通过自定义msbuild步骤强制执行此操作

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

  3. 框架附带的默认cs目标计算要复制到当前正在构建的项目的输出目录的引用集.由于这需要在"参考"关系下计算传递闭包,因此这可能变得非常昂贵.我的解决方法是GetCopyToOutputDirectoryItemsCommon.targets导入后在每个项目中导入的公共目标文件(例如)中重新定义目标Microsoft.CSharp.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>
    
    Run Code Online (Sandbox Code Playgroud)

这将我们在给定时间内的构建时间从几个小时(主要是由于内存限制)减少到几分钟.

重新定义的GetCopyToOutputDirectoryItems可以通过复制从线和2,438-2,450 2,474-2,524被创建C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Microsoft.Common.targetsCommon.targets.

为了完整性,结果目标定义将变为:

<!-- 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>
Run Code Online (Sandbox Code Playgroud)

有了这个解决方法,我发现在一个解决方案中拥有多达120个项目是可行的,这样做的主要好处是项目的构建顺序仍然可以由VS确定,而不是通过拆分解决方案来手动完成.

  • 它仅从其直接子级而不是子级的子级收集内容项。发生这种情况的原因是 _SplitProjectReferencesByFileExistence 使用的 ProjectReferenceWithConfiguration 列表仅填充在当前项目中,而在子项中为空。空列表导致 _MSBuildProjectReferenceExistent 为空并终止递归。所以它似乎没有用。 (2认同)

Jul*_*rau 32

我建议你阅读Patric Smacchia关于这个主题的文章:

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

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

您还可以阅读本文以帮助您减少项目编号并缩短编译时间.


Aar*_*ack 23

我建议为几乎所有项目复制local = false,除了位于依赖树顶部的项目.并且对于顶部集合中的所有引用,复制local = true.我看到很多人建议共享输出目录; 我认为这是一个基于经验的可怕想法.如果你的启动项目持有对dll的引用,任何其他项目都持有对你的引用,那么即使在所有内容上复制local = false,你的构建也会失败,在某些时候会遇到访问\共享冲突.这个问题非常烦人,难以追查.我完全建议远离分片输出目录,而不是让依赖链顶部的项目将所需的程序集写入相应的文件夹.如果你没有"顶部"的项目,那么我会建议一个构建后的副本,以便将所有内容放在正确的位置.此外,我会尝试记住调试的简易性.任何exe项目我仍然留下copy local = true所以F5调试经验将起作用.

  • 我有同样的想法,并希望找到一个在这里有同样想法的人; 但是,我很好奇为什么这篇文章没有更多的赞成票.不同意的人:你为什么不同意? (2认同)

Say*_*imi 10

你是对的.CopyLocal绝对会扼杀你的构建时间.如果您有一个大的源树,那么您应该禁用CopyLocal.不幸的是,它并不像应该是干净利用它一样容易.我已经回答了有关禁用CopyLocal的确切问题,如何在MSBUILD中覆盖.NET中的引用的CopyLocal(私有)设置.看看这个.以及Visual Studio(2008)中大型解决方案的最佳实践.

以下是我看到的有关CopyLocal的更多信息.

CopyLocal实际上是为了支持本地调试而实现的.准备应用程序进行打包和部署时,应将项目构建到同一输出文件夹中,并确保拥有所需的所有引用.

我在文章MSBuild:创建可靠构建的最佳实践,第2部分中写过如何处理构建大型源代码树的文章.


小智 8

在我看来,拥有100个项目的解决方案是一个很大的错误.您可以将解决方案拆分为有效的逻辑小单元,从而简化维护和构建.


小智 7

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

/ P:CreateHardLinksForAdditionalFilesIfPossible = TRUE; CreateHardLinksForCopyAdditionalFilesIfPossible = TRUE; CreateHardLinksForCopyFilesToOutputDirectoryIfPossible = TRUE; CreateHardLinksForCopyLocalIfPossible = TRUE; CreateHardLinksForPublishFilesIfPossible =真

您还可以将其添加到中央导入文件,以便所有项目也可以获得此优势.


Tor*_*ing 5

如果您通过项目引用或通过解决方案级依赖关系定义了依赖关系结构,那么转向"复制本地"是安全的,我甚至会说这是最佳实践,因为这样可以让您使用MSBuild 3.5并行运行您的构建(当尝试复制引用的程序集时,没有不同的进程相互绊倒的via/maxcpucount).


归档时间:

查看次数:

87601 次

最近记录:

7 年,11 月 前