큰 C # 솔루션 파일 (~ 100 프로젝트)이 있으며 빌드 시간을 개선하려고합니다. “로컬 복사”는 많은 경우에 낭비 적이라고 생각하지만 모범 사례가 궁금합니다.
.sln에는 어셈블리 C에 따라 어셈블리 B에 따라 응용 프로그램 A가 있습니다.이 경우 수십 개의 “B”와 소수의 “C”가 있습니다. 이들은 모두 .sln에 포함되어 있으므로 프로젝트 참조를 사용하고 있습니다. 모든 어셈블리는 현재 $ (SolutionDir) / Debug (또는 Release)에 빌드됩니다.
기본적으로 Visual Studio는 이러한 프로젝트 참조를 “로컬 복사”로 표시하므로 모든 “C”가 빌드되는 모든 “B”에 대해 $ (SolutionDir) / Debug에 한 번 복사됩니다. 이것은 낭비되는 것 같습니다. “로컬 복사”를 끄면 무엇이 잘못 될 수 있습니까? 대규모 시스템을 가진 다른 사람들은 무엇을합니까?
후속 조치 :
많은 응답을 통해 빌드를 더 작은 .sln 파일로 분할 할 것을 제안합니다 … 위의 예에서는 먼저 기본 클래스 “C”를 빌드 한 다음 대량의 모듈 “B”를 빌드 한 다음 몇 가지 애플리케이션 ” ㅏ”. 이 모델에서는 B에서 C에 대한 프로젝트가 아닌 참조가 필요합니다. 여기서 발생하는 문제는 “디버그”또는 “릴리스”가 힌트 경로에 구워지고 “B”의 릴리스 빌드를 빌드하는 것입니다. “C”의 디버그 빌드에 대해
빌드를 여러 .sln 파일로 분할 한 사용자는이 문제를 어떻게 관리합니까?
답변
이전 프로젝트에서는 프로젝트 참조가있는 하나의 큰 솔루션으로 작업했으며 성능 문제도 발생했습니다. 해결책은 세 가지였습니다.
-
항상 로컬 복사 속성을 false로 설정하고 사용자 지정 msbuild 단계를 통해이를 적용하십시오.
-
각 프로젝트의 출력 디렉토리를 동일한 디렉토리로 설정하십시오 (바람직하게는 $ (SolutionDir)
-
프레임 워크와 함께 제공되는 기본 cs 대상은 현재 빌드중인 프로젝트의 출력 디렉토리에 복사 될 참조 세트를 계산합니다. 이것이 ‘참조’와 관련하여 아래의 전이 폐쇄를 계산이 필요하므로이 될 수있는 매우 비용이 많이 드는. 이 문제를 해결하려면을 가져온 후 모든 프로젝트에서 가져온
GetCopyToOutputDirectoryItems
공통 대상 파일 (예 :)에서 대상 을 재정의Common.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>
이로 인해 주어진 시간에 빌드 시간이 몇 시간 (대부분 메모리 제약으로 인해)에서 몇 분으로 단축되었습니다.
재정의는 GetCopyToOutputDirectoryItems
선을에서 2,438-2,450 및 2,474-2,524 복사하여 만들 수 있습니다 C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Microsoft.Common.targets
에를 Common.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>
이 해결 방법을 사용하면 하나의 솔루션에 120 개가 넘는 프로젝트를 보유 할 수 있다는 것이 나타났습니다. 이는 솔루션을 나눠서 VS 대신 직접 프로젝트의 빌드 순서를 결정할 수 있다는 주요 이점이 있습니다. .
답변
그 주제에 대한 Patric Smacchia의 기사를 읽으라고 제안합니다.
- .NET 어셈블리 및 Visual Studio 프로젝트를 통해 코드베이스 파티션 -> 모든 Visual Studio 프로젝트가 실제로 자체 어셈블리에 있어야합니까? ‘Copy Local = True’는 실제로 무엇을 의미합니까?
- NUnit 코드베이스에서 배운 교훈 -> VisualStudio Project Reference + Copy Local true 옵션은 나쁘다! )
- CruiseControl.NET의 코드베이스 분석 -> 로컬 참조 어셈블리 복사 옵션의 잘못된 사용법이 True로 설정 됨)
CC.Net VS 프로젝트는 로컬 참조 어셈블리 복사 옵션을 true로 설정합니다. […] 이것은 컴파일 시간 (NUnit의 경우 x3)을 크게 증가시킬뿐만 아니라 작업 환경을 엉망으로 만듭니다. 마지막으로, 그렇게하면 버전 관리 문제가 발생할 위험이 있습니다. Btw, NDepend는 이름이 같지만 내용이나 버전이 같지 않은 2 개의 다른 디렉토리에서 2 개의 어셈블리를 발견하면 경고를 표시합니다.
올바른 작업은 $ RootDir $ \ bin \ Debug 및 $ RootDir $ \ bin \ Release라는 2 개의 디렉터리를 정의하고 VisualStudio 프로젝트가이 디렉터리에서 어셈블리를 내보내도록 구성하는 것입니다. 모든 프로젝트 참조는 디버그 디렉토리의 어셈블리를 참조해야합니다.
이 기사 를 읽고 프로젝트 수를 줄이고 컴파일 시간을 향상시킬 수 있습니다.
답변
의존성 트리의 최상위에있는 것을 제외한 거의 모든 프로젝트에 대해 local = false를 복사하는 것이 좋습니다. 그리고 최상위 집합에있는 모든 참조에 대해 local = true를 복사하십시오. 많은 사람들이 출력 디렉토리 공유를 제안합니다. 나는 이것이 경험에 근거한 끔찍한 아이디어라고 생각합니다. 시작 프로젝트에 dll에 대한 참조가 있으면 다른 프로젝트에 대한 참조가 있으면 어느 시점에서 copy local = false 인 경우에도 액세스 \ 공유 위반이 발생하여 빌드가 실패합니다. 이 문제는 매우 성가 시며 추적하기가 어렵습니다. 나는 샤드 출력 디렉토리에서 멀리 떨어져 프로젝트를 의존성 체인의 최상위에 두는 대신 필요한 어셈블리를 해당 폴더에 쓰도록 제안합니다. “맨 위”에 프로젝트가 없으면 그런 다음 모든 것을 올바른 장소에 가져 오기 위해 빌드 후 사본을 제안합니다. 또한 디버깅의 편의성을 염두에 두려고 노력합니다. exe 프로젝트는 여전히 복사 local = true로 남겨 두므로 F5 디버깅 경험이 작동합니다.
답변
당신이 올바른지. CopyLocal은 빌드 시간을 절대적으로 단축시킵니다. 큰 소스 트리가 있으면 CopyLocal을 비활성화해야합니다. 불행히도 깨끗하게 비활성화하는 것만 큼 쉽지는 않습니다. MSBUILD의 .NET에서 참조에 대한 CopyLocal (Private) 설정을 재정의하는 방법 에서 CopyLocal을 비활성화하는 것에 대한이 정확한 질문에 대답했습니다 . 확인 해봐. Visual Studio (2008)의 대규모 솔루션에 대한 모범 사례 뿐만 아니라
CopyLocal에 대한 추가 정보는 다음과 같습니다.
CopyLocal은 실제로 로컬 디버깅을 지원하도록 구현되었습니다. 패키징 및 배포를 위해 응용 프로그램을 준비 할 때 프로젝트를 동일한 출력 폴더에 빌드하고 필요한 모든 참조가 있는지 확인해야합니다.
MSBuild : 신뢰할 수있는 빌드를 만들기위한 모범 사례, Part 2 기사에서 큰 소스 트리를 작성하는 방법에 대해 작성했습니다 .
답변
내 생각에 100 개의 프로젝트로 솔루션을 얻는 것은 큰 실수입니다. 솔루션을 유효한 논리적 소형 단위로 분할하여 유지 보수 및 빌드를 단순화 할 수 있습니다.
답변
아무도 하드 링크 사용에 대해 언급하지 않은 것에 놀랐습니다. 파일을 복사하는 대신 원본 파일에 대한 하드 링크를 만듭니다. 이렇게하면 디스크 공간이 절약되고 빌드 속도가 크게 향상됩니다. 다음 속성을 사용하여 명령 줄에서 활성화 할 수 있습니다.
/ p : CreateHardLinksForAdditionalFilesIfPossible = true; CreateHardLinksForCopyAdditionalFilesIfPossible = true; CreateHardLinksForCopyFilesToOutputDirectoryIfPossible = true; CreateHardLinksForCopyLocalIfPossible = true; CreateHardLinksForPublishFilesIfPossible = true
모든 프로젝트에서이 혜택을 얻을 수 있도록이 파일을 중앙 가져 오기 파일에 추가 할 수도 있습니다.
답변
프로젝트 참조 또는 솔루션 레벨 종속성을 통해 정의 된 종속성 구조가있는 경우 “로컬 복사”를 켜도 안전합니다. MSBuild 3.5를 사용하여 빌드를 병렬로 실행할 수 있기 때문에이를 수행하는 것이 가장 좋습니다. 참조 된 어셈블리를 복사 할 때 서로 다른 프로세스가 서로 트립되지 않고 / maxcpucount를 통해).