다음을 통해 공유


일시적인 빌드 오류 수정

간헐적으로 발생하는 빌드 오류를 처리하는 것은 어려운 일일 수 있습니다. 이 문서는 근본 원인을 식별하고, 간헐적인 빌드 오류를 수정할 수 있도록 변경하여 빌드가 매번 일관되게 실행되도록 하는 데 도움이 됩니다.

MSBuild는 다른 CPU 코어에서 다른 작업자 노드 프로세스를 실행하여 병렬 빌드를 지원합니다. 병렬로 빌드하면 상당한 성능 이점이 있는 경우가 많지만, 이렇게 하면 여러 프로세스가 동시에 동일한 리소스를 사용하려고 할 때 발생하는 오류의 위험이 있을 수 있습니다. 이 상황은 경합 상태의 한 유형입니다. 경합 상태는 빌드마다 다른 동작을 나타낼 수 있습니다. 예를 들어 시간이 서로 다르면 한 프로세스가 다른 프로세스보다 앞서거나 뒤처질 수 있습니다.

파일 I/O 경합에서 발생하는 오류 메시지에는 항상 운영 체제 파일 I/O 오류가 포함되지만 파일 I/O 오류가 발생했을 때 빌드에서 발생한 상황에 따라 다른 MSBuild 오류 코드가 있을 수 있습니다. 일부 예는 Windows 플랫폼에서 다음과 같이 표시될 수 있습니다.

error MSB3677: Unable to move file "source" to "dest".
Cannot create a file when that file already exists. [{project file}] 
The process cannot access the file 'file' because it is being used by another process.

파일 경합은 특정 프로젝트가 둘 이상의 속성 설정 조합을 사용하여 빌드하도록 요청된 경우에 발생할 수 있습니다. MSBuild는 일반적으로 출력이 다를 수 있는 경우 속성 설정이 다를 때마다 참조된 프로젝트에 대해 별도의 빌드를 수행합니다. 빌드를 동시에 실행하는 타이밍에 따라 파일이 동일한 위치에 이미 있는 경우 이동 또는 복사 작업이 실패하거나 다른 MSBuild 프로세스에서 대상 파일을 사용 중이므로 실패할 수 있습니다. 또한 다른 MSBuild 프로세스가 동일한 파일을 읽거나 쓰는 경우 파일 읽기 작업이 실패할 수 있습니다.

원인을 이해하고 프로젝트 파일을 적절하게 변경하여 대부분의 빌드 파일 경합 문제를 영구적으로 해결할 수 있지만, 원인이 사용자 고유의 코드에 있는 경우에만 해결할 수 있습니다. 경합 상태는 SDK 코드의 버그로 인해 발생할 수도 있습니다. 이 경우 관련 SDK 소유자에 의해 문제를 보고하고 조사해야 합니다.

빌드 경합 상태의 원인

이 섹션에서는 경합 상태로 이어질 수 있는 다양한 유형의 문제에 대해 설명합니다. 다음 섹션인 경합 상태 진단 및 수정에서는 이러한 문제를 해결하기 위해 수행할 작업을 설명합니다.

일관되지 않은 ProjectReference 속성 설정

동일한 프로젝트의 여러 빌드는 많은 빌드 프로세스의 일반적인 부분입니다. MSBuild가 둘 이상의 설정 조합에 대한 출력을 빌드할 때 발생합니다. 예를 들어 솔루션에는 여러 대상 프레임워크(예: net472net7) 또는 여러 대상 플랫폼 아키텍처(예: Arm64x64)가 있을 수 있습니다. 이 빌드 요구 사항은 각 출력 조합에 대해 다른 출력 폴더를 지정하여 충족됩니다. 이렇게 하면, 어셈블리의 Arm64 net472 버전이 다른 조합과는 다른 폴더에 출력되어 충돌이 발생하지 않습니다. 기본 SDK 설정은 여기에 언급된 예제를 이미 처리하지만, 경우에 따라 설정의 여러 조합이 명확하지 않아 조사가 필요한 경우가 있습니다.

ProjectReference 속성이 전역 속성과 충돌

전역 속성이, 즉 /p 또는 /property 옵션을 사용하여 명령줄에 속성을 설정할 때, 참조된 프로젝트 빌드에 암시적으로 사용됩니다. 그러나 RemoveGlobalProperties 또는 GlobalPropertiesToRemove를 사용하여 지정된 프로젝트 참조에 대한 일부 또는 모든 전역 속성 설정을 생략할 수 있으므로 해당 속성이 일관되게 사용되지 않는 경우 참조된 프로젝트의 버전이 둘 이상 전역 속성 집합으로 빌드되고 다른 버전이 설정되지 않았거나 다른 값이 있는 상황이 발생할 수 있습니다.

패키징이 의도치 않게 프로젝트 빌드를 트리거

빌드가 이전에 빌드된 프로젝트의 출력을 패키지하는 경우 패키징 빌드 논리가 빌드할 때 사용된 원래 프로젝트와 다른 속성 설정을 지정할 때 경합 상태가 발생할 수 있습니다. 이 경우 MSBuild는 일반적으로 속성의 불일치로 인해 해당 프로젝트의 다시 빌드를 트리거합니다. 이 상황은 경합 조건으로 이어질 수 있습니다. 패키징 중인 프로젝트를 빌드하도록 요청하지 않도록 패키징 프로젝트에서 BuildProjectReferencesfalse로 설정하는 것이 좋습니다. 즉, 프로젝트 빌드가 이전에 완료되고 최신 상태인 경우에만 패키징 빌드를 요청해야 합니다.

경합 상태 진단 및 수정

빌드에서 생성된 파일에 대한 이동 작업, 복사 작업 또는 파일 쓰기가 간헐적으로 실패할 때 경합 상태 오류를 의심할 수 있습니다.

문제를 해결하는 방법은 원하는 결과에 따라 달라집니다. 빌드된 프로젝트의 두 가지 버전이 정말로 필요한가요? 이 경우 두 가지 속성 구성에 대해 출력 폴더를 다르게 만듭니다. 그렇지 않은 경우 ProjectReference 요소를 변경하여 각 참조에 대해 동일한 속성이 설정되도록 할 수 있습니다.

경합 상태를 진단하고 수정하려면 다음 단계를 수행합니다.

  1. 동일한 컴퓨터의 Visual Studio 디버거 세션과 같이 해당 파일을 사용하는 다른 프로그램이 실행되고 있지 않은지 확인합니다.

  2. MSBuild /m:1 옵션을 사용하여 빌드를 실행하는 경우 문제가 발생하는지 확인합니다. MSBuild 명령줄 참조를 참조하세요. 이는 MSBuild에 빌드에 사용할 노드 수를 알려주는 명령줄 옵션입니다. 1로 설정하면 빌드가 직렬로 진행되며 경합 상태가 발생할 수 없습니다. 이 /m:1 옵션을 사용하는 것은 경합 상태를 방지하는 데 사용할 수 있는 해결 방법이지만 장기적인 해결책은 아닙니다. 빌드 출력은 여전히 두 번 이상 빌드되며, 가능한 차이는 overbuild라는 오류 조건입니다. 또한 직렬로 빌드하면 빌드를 완료하는 데 필요한 시간이 크게 늘어나게 됩니다. 병렬 빌드가 사용하도록 설정된 경우(프로세서 수가 1보다 큰 경우) 빌드에서 간헐적인 파일 I/O 오류만 보여 주는 경우 빌드 경합 조건입니다.

  3. 로그를 생성합니다. 세부 정보 표시를 Normal 이상으로 설정하여 빌드를 실행합니다(예: 명령줄 옵션 -verbosity:Normal 사용). 경합 상태를 해결하려면 이진 로그를 생성하고(/bl 또는 /binlog 옵션 사용) 구조적 로그 뷰어로 보는 것이 좋습니다. 경합 상태를 진단하는 데 유용한 로그를 얻으려면 오류를 생성한 출력에 액세스할 수 있는 여러 위치를 찾을 수 있으므로 로그가 실패한 실행의 로그일 필요는 없습니다.

  4. 특정 실행이 실패했는지 여부에 관계없이 로그(또는 .binlog 파일)를 열고 오류를 트리거하는 파일의 파일 이름을 검색하고 파일이 사용되는 모든 위치를 찾습니다.

    다음 스크린샷은 예제에서 솔루션을 빌드하여 생성된 로그를 보는 구조적 로그 뷰어를 보여줍니다. 표시된 내용은 net5.0\Base.dll 파일의 검색 결과이며, 이것이 오류 메시지에 언급됩니다. 동일한 출력 파일이 검색 결과에서 Csc 작업에 대한 OutputAssembly의 두 배로 표시되어 두 번 이상 빌드 중임을 나타냅니다. Structured Log Viewer에서의 검색 결과를 보여주는 스크린샷.

  5. 해당 프로젝트 빌드의 각 인스턴스에 적용되는 속성 설정을 확인합니다. 모든 개별 프로젝트 빌드에는 해당 프로젝트 빌드에 적용되는 모든 속성 설정을 나열하는 속성 노드가 있으므로 구조적 로그 뷰어를 사용하면 이 작업이 더 쉬워집니다. 텍스트 로그를 사용하는 경우 빌드에 대해 설정된 속성은 세부 정보 표시 설정이 Normal 이상이면 텍스트로 출력됩니다. 실패한 출력을 생성하는 프로젝트의 각 빌드에 대한 속성 목록을 비교합니다. 문제가 실제로 경합 상태인 경우 차이가 표시됩니다.

    다음 스크린샷은 하나의 프로젝트 빌드에 대해 속성 노드가 확장된 구조적 로그 뷰어를 보여줍니다. 이 빌드는 다른 프로젝트의 ProjectReferences 노드 아래에 있습니다. 즉, 이 빌드는 다른 프로젝트의 ProjectReference 요소에 의해 트리거되었습니다. 트리 위로 노드를 따라가면 참조한 프로젝트를 확인할 수 있습니다.

    프로젝트 구축 속성을 보여주는 Structured Log Viewer의 스크린샷.

    목록을 동일한 프로젝트의 다른 빌드와 비교하면 SpecialMode이 누락된 것을 확인할 수 있습니다. 이 빌드는 최상위 빌드입니다. 즉, 다른 프로젝트에서 참조했기 때문이 아니라 솔루션 자체에 있었기 때문에 빌드되었습니다.

    동일한 프로젝트에 대한 서로 다른 속성 집합을 보여주는 스크린샷.

  6. Include 특성이 해당 프로젝트를 지정하는 ProjectReference에 대한 프로젝트 파일을 검색합니다. 메타데이터 SetConfiguration, SetPlatform, SetTargetFramework, AdditionalProperties, RemoveGlobalProperties 또는 GlobalPropertiesToRemove 중에서 하나를 찾습니다. 솔루션 전체의 여러 ProjectReference 요소 간에 이러한 메타데이터에 대해 설정된 값의 차이점을 확인합니다. 예제에서 문제의 원인인 일관성 없는 AdditionalProperties 메타데이터 설정(한 곳에는 있지만 다른 위치에는 없음)입니다.

  7. 다른 속성 설정의 의미와 빌드 출력에 실질적으로 영향을 줄지 여부를 고려합니다. 속성 설정 차이가 의미 있고 출력이 고유하도록 의도된 경우 해결책은 .NET SDK가 플랫폼, 구성(예: 디버그 또는 릴리스) 또는 대상 프레임워크에 대해 수행하는 것처럼 설정의 각 변형에 대해 다른 폴더를 사용하는 것입니다. 속성 설정 차이가 의도하지 않거나 중요하지 않은 경우 프로젝트 코드를 변경하여 속성의 차이를 제거하는 방법을 찾아보세요. 예제에서, AdditionalProperties="SpecialMode=true" 메타데이터를 Middle2.csprojProjectReference에 추가하거나 Middle1.csproj에서 AdditionalProperties 메타데이터를 제거하여 이 작업을 수행할 수 있습니다. 적절한 변경은 애플리케이션, 서비스 또는 대상의 특정 요구 사항에 따라 달라집니다.

  8. 오류가 제어되지 않는 SDK에 있는 경우 SDK 소유자에게 문제를 보고합니다.

예시

패턴을 보여 주는 간단한 사례가 여기에 표시됩니다. 두 클래스 라이브러리에서 참조하는 여러 프로젝트, 즉 하나의 프런트 엔드 클라이언트(App), 두 개의 클래스 라이브러리(Middle1Middle2), 라이브러리(Base)가 있는 솔루션이 있다고 가정해 보겠습니다.

다음 코드 섹션의 프로젝트 파일은 모두 단일 솔루션의 일부입니다. 이 프로젝트 컬렉션은 Base의 서로 다른 빌드 두 개를 생성합니다. 하나는 SpecialMode=true가 포함된 빌드이고 다른 하나는 포함되지 않은 빌드입니다. Base.dll 출력을 참조하는 일시적인 오류가 발생할 수 있습니다. "다른 프로세스에서 사용 중이므로" Base.dll을 작성할 수 없는 오류가 발생할 수 있습니다.

<!-- Base.csproj -->
<Project Sdk="Microsoft.NET.Sdk">

  <ItemGroup>
    <ProjectReference Include="..\Base\Base.csproj" />
  </ItemGroup>

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

</Project>
<!-- Middle1.csproj -->
<Project Sdk="Microsoft.NET.Sdk">

  <ItemGroup>
    <ProjectReference Include="..\Base\Base.csproj" AdditionalProperties="SpecialMode=true" />
  </ItemGroup>

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

</Project>
<!-- Middle2.csproj -->
<Project Sdk="Microsoft.NET.Sdk">

  <ItemGroup>
    <ProjectReference Include="..\Base\Base.csproj" />
  </ItemGroup>

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

</Project>
<!-- App.csproj -->
<Project Sdk="Microsoft.NET.Sdk">

  <ItemGroup>
    <ProjectReference Include="..\Middle1\Middle1.csproj" />
    <ProjectReference Include="..\Middle2\Middle2.csproj" />
  </ItemGroup>

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

</Project>

원하는 동작이 SpecialMode를 사용하는 것인 경우 적절한 수정은 동일한 AdditionalProperties 메타데이터 값을 Middle2.csprojProjectReference에 추가하는 것입니다.

<!-- Middle2.csproj -->
<Project Sdk="Microsoft.NET.Sdk">

  <ItemGroup>
    <ProjectReference Include="..\Base\Base.csproj" AdditionalProperties="SpecialMode=true" />
  </ItemGroup>

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

</Project>

의도에 맞는 경우 Middle1.csproj에서 AdditionalProperties 메타데이터를 제거하여 빌드 문제를 해결할 수도 있습니다.