빌드 상태
에픽게임즈에는 지속적으로 출시 중인 제품을 개발하는 대규모의 개발 팀이 있습니다. 에픽게임즈는 각 출시 버전을 위한 분기가 있으며 출시 분기에 대한 커밋이 메인라인에 자동으로 다시 병합되는 트렁크 기반 개발 모델을 사용합니다.
안정적이고 정상적인 코드베이스를 유지하는 것은 빠른 반복작업 능력에 중요하므로, 이를 지원하기 위해 다음과 같은 여러 정책을 적용하고 있습니다.
- 변경사항을 제출하기 전에 개발자가 실행할 수 있는 로컬 테스트 세트
- 호드의 프리플라이트 시스템을 통해 빌드 팜에서 프리서브밋(pre-submit) 테스트 세트를 실행하는 기능
- 빌드 오류를 식별, 그룹화, 분류하여 해당 오류에 책임이 있는 개발자에게 가능한 한 빨리 Slack 을 통해 알리기 위한 자동화된 빌드 상태(Build Health) 시스템
빌드 상태 시스템은 빌드 손상에 대해 문제 를 생성하는 기존의 버그 트래킹 데이터베이스와 유사하다고 생각할 수 있지만, 이 시스템은 자동화되어 있으며 컨텍스트를 인식하고 고도의 데이터 기반이면서 호드의 작업 대시보드와 로그에 긴밀하게 통합되어 있습니다.
이 시스템은 피딩되는 데이터만큼만 작동하므로, 문제가 발생할 때 시스템이 현명한 결정을 내리도록 하기 위해 가능한 많은 컨텍스트에서 오류에 신중하게 주석을 달고 있습니다.
빌드 손상 분석
각 분기는 여러 커밋으로 이루어진 선형 시퀀스로 모델링됩니다. 빌드 실패가 발생하면 마지막으로 성공한 빌드 이후로 어떤 커밋이 실패에 책임이 있는지 식별합니다.
컴파일에 실패한 특정 에셋이나 소스 파일을 수정하는 커밋 찾기, 코드/콘텐츠 오류 및 변경사항 구분하기, 수정된 소스 파일 이름에 일치하는 링커 심볼 이름 찾기 등 이러한 작업을 할 수 있는 휴리스틱 기법은 다양합니다.
또한 하나의 변경사항이 광범위한 오류를 유발하는 경우도 있습니다. 컴파일에 실패한 소스 파일이 MSVC, Clang, GCC에서 완전히 다른 오류를 생성할 수도 있습니다. 예를 들면 누락된 에셋이 다양한 플랫폼에서 쿠킹 오류를 유발할 수 있습니다. 이러한 오류는 빌드 파이프라인에서 때때로 발생할 수 있으며, 이를 가능한 한 빨리 파악하되 추후 발생하는 가짜 알림의 롱테일(Long tail) 현상을 방지하고자 합니다.
마지막으로 개발이 다수의 분기에서 진행되어 분기 간에 병합이 발생하는 경우가 많으므로, 여러 스트림에서 오류를 수집하고 책임이 있는 것으로 간주되는 원래 커밋으로 다시 트레이스하고자 합니다.
문제, 스팬, 지문
호드는 손상이 탐지될 때마다 빌드 상태 문제 를 생성합니다.
각 문제에는 여러 스팬 이 포함됩니다. 스팬은 문제가 관찰된 각 스트림에 하나씩 존재하며, 각 스팬은 마지막으로 성공한 커밋과 처음으로 실패한 커밋을 식별합니다. 스트림에서 연관된 오류가 더 이상 발생하지 않으면 스팬은 해결된 것으로 표시되며, 모든 스팬이 해결되면 문제가 해결된 것으로 표시됩니다.
빌드 오류는 지문 을 통해 스팬에 수집되고 어태치됩니다. 지문은 오류, 그리고 오류가 다른 오류와 일치되는 방식을 프로그래밍 방식으로 설명합니다.
지문의 콘텐츠
지문에는 다음 데이터가 포함되어 있습니다(IIssueFingerprint.cs 참고).
- Type: 문제 유형을 식별하는 스트링입니다. 지문은 동일한 타입의 다른 지문과만 일치합니다. 가장 일반적으로 발생하는 문제 타입 중 하나는
Compile로, 하나 이상의 소스 파일 내 컴파일 오류를 나타냅니다. - SummaryTemplate: 호드 대시보드 및 알림에서 문제에 대한 설명을 채우는 데 사용되는 스트링입니다. 지문이 함께 병합될 때 확장되는 자리표시자 스트링이 포함될 수 있습니다(요약 템플릿 참고).
- Keys: 오류와 연관된 파일의 이름과 같이, 지문을 일치시키고 그룹화하는 데 사용되는 식별 데이터 조각입니다. 각 키에는 이름과 유형이 있습니다.
- RejectKeys: 이 지문과 일치해서는 안 되는 키 세트입니다.
- Metadata: 요약 템플릿과 디버깅에 사용하기 위해 집계할 수 있는 임의 키/값 데이터입니다.
- ChangeFilter: 이 오류에 책임이 있다고 간주될 수 있는 파일에 대해 세미콜론으로 구분되는 일련의 와일드카드로, 예를 들면 C++ 소스 파일의
*.c;*.cpp;*.h;*.hpp*가 있습니다.
지문 일치 및 병합하기
지문은 IIssueFingerprint.cs 의 로직으로 일치됩니다. 파이프라인은 다음과 같습니다.
- 빌드 단계의 각 오류 또는 경고마다 지문이 생성됩니다.
- 각 지문은 동일한 스트림에서 해결되지 않은 스팬의 지문과 비교됩니다. 다음의 경우 새 지문이 기존 지문과 일치하는 것으로 간주됩니다.
- 타입이 동일합니다.
- 요약 템플릿이 동일합니다.
- 새 지문이 기존 지문과 공통된 하나의 키를 보유하거나, 두 지문 모두 키를 보유하지 않습니다.
- rejectKeys 에 나열된 키 중 아무것도 새 지문에 포함되지 않습니다.
- 기존 스팬과 일치하지 않는 모든 지문은 함께 그룹화되어 새 스팬에 추가됩니다. 이러한 지문은 보다 완화된 조건 세트를 사용하여 그룹화됩니다.
- 타입이 동일합니다.
- 두 지문 모두 다른 키의 거절 키에 있는 키를 보유합니다.
- 새 스팬이 있는 경우 원본 스트림에 대한 해당 커밋의 병합 히스토리를 따라 의심되는 커밋 목록을 찾습니다. 문제에 일치하는 지문이 있는 경우 의심되는 커밋을 공유하고, 아직 현재 스트림에 스팬이 없는 경우 해당 문제에 추가합니다. 그 밖의 경우 새 문제가 생성됩니다.
지문 생성하기
오류에 대한 지문은 다음 두 가지 방식으로 생성됩니다.
- 생성된 시점에 구조화된 로그 이벤트에서 직접 지문 정보를 포함하는 방식
- 빌드 단계가 완료된 후 호드 서버에서 구조화된 로그 이벤트를 포스트 프로세싱하는 방식
구조화된 로그 이벤트에 직접 지문을 포함하면 확장성이 향상되고 세부적인 컨텍스트 추가를 가장 잘 제어할 수 있으므로 이 방식이 선호됩니다.
오류에 지문 추가하기(C#)
EpicGames.Horde 라이브러리에는 표준 .NET ILogger 인터페이스를 사용하여 지문 메타데이터를 로그 이벤트에 어태치하는 스코프를 생성하기 위한 익스텐션 메서드가 포함됩니다. 이 인터페이스는 CommandUtils.Logger 프로퍼티를 통해 AutomationTool 명령에 노출됩니다.
지문은 다음 예시처럼 추가할 수 있습니다.
IssueFingerprint fingerprint = new IssueFingerprint("Compile", "Compile {Severity} in {Files}", IssueChangeFilter.Code);
fingerprint.Keys.Add(IssueKey.FromFile("Foo.cpp"));
using (IDisposable scope = Logger.BeginIssueScope(fingerprint))
{
Logger.LogError("Compile errors in {File}", new FileReference("Foo.cpp"));
}
포스트 프로세싱을 통해 지문 추가하기
포스트 프로세싱은 EpicGames.Horde 프로젝트의 IssueHandler 파생 클래스에 의해 처리됩니다.
핸들러는 서버에서 [IssueHandler] 어트리뷰트로 클래스를 검색하여 열거됩니다. 각 핸들러의 새 인스턴스는 처리될 이벤트가 포함된 각 빌드 단계에서 생성됩니다.
로그 이벤트는 true 를 반환하는 핸들러가 발견될 때까지 IssueHandler.Priorty 값의 내림차순으로 IssueHandler.HandleEvent 에 전달됩니다. 모든 이벤트가 처리되면 IssueHandler.GetIssues 는 일치하는 로그 이벤트와 지문 목록을 반환합니다.
핸들러가 로그 이벤트에 지문을 생성할 필요가 없다는 점에 유의하세요. 이는 컴파일 오류처럼 구체적인 오류가 모호한 오류(예: 언리얼 빌드 툴에서 실패한 종료 코드를 반환하는 경우)를 마스킹하는 데 유용할 수 있습니다. 이에 따라 HandleEvent 는 남은 핸들러 파이프라인에서 제외하려는 오류에 true를 반환할 수 있습니다.
요약 템플릿
다음 자리표시자 변수를 요약 템플릿에서 사용할 수 있습니다.
{Severity}: 이 문제로 그룹화된 가장 높은 진단 심각도에 따라 '경고(Warnings)' 또는 '오류(Errors)' 스트링으로 확장됩니다.{Files}: 타입이File인 키에서 파일 이름 목록으로 확장됩니다. 파일은 최대 3개까지 포함됩니다.{Meta:Key}:Key와 연관된 메타데이터 값 목록으로 확장됩니다.
워크플로
워크플로는 개발자들이 빌드 상태 문제에 대해 어떻게, 어디에서 알림을 받을지 정의합니다.
워크플로는 스트림 환경설정 파일의 Workflows 프로퍼티에서 환경설정되며, 작업은 작업 템플릿의 WorkflowId 프로퍼티를 통해 워크플로를 사용하도록 환경설정됩니다.
작업 내 개별 노드는 BuildGraph 스크립트에서 주석을 사용하여 워크플로를 사용하도록 환경설정됩니다.
알림
호드는 문제 알림을 위해 Slack을 지원합니다. 기본적으로 Slack 스레드는 식별된 문제별로 생성되며, 문제를 유발했다고 의심되는 개발자가 볼 수 있도록 @멘션합니다.
개발자는 인터랙티브 표면을 통해 호드 대시보드로 이동하지 않아도 Slack에서 문제를 인정하거나 거부할 수 있습니다.
스트림 환경설정 파일을 통해 Slack 채널과 사용자를 환경설정하는 경우, 대부분의 파라미터는 Slack 채널과 사용자의 이름이 아니라 ID를 제공할 것을 요구합니다.
보고서
정의된 간격마다 해결되지 않은 문제 목록, 적절한 스레드로 이동하는 링크, 스트림 상태에 대한 통계를 표시하는 요약 보고서를 전송하도록 워크플로를 환경설정할 수 있습니다.
보고서 전송은 워크플로 환경설정의 reportTimes 및 reportChannel 프로퍼티를 통해 환경설정할 수 있습니다.
주석
노드 어트리뷰트 를 사용하여 개별 작업 단계에 대한 문제 처리를 추가로 환경설정할 수 있습니다. 어트리뷰트는 BuildGraph 스크립트의 Node 엘리먼트에서 Annotations 어트리뷰트를 통해 노드에 지정될 수 있습니다.
<Node Name="Compile UnrealEditor Win64" Annotations="Workflow=my-workflow;BuildBlocker=true">
지원되는 주석 세트는 NodeAnnotations.cs 소스 파일에 정의되어 있으며, 현재 다음과 같은 주석을 사용할 수 있습니다.
Workflow: 이 노드에서 문제를 분류하기 위해 사용할 워크플로입니다.CreateIssues:false로 설정하면 이 노드에 대한 문제 생성을 비활성화할 수 있습니다.AutoAssign: 한 사용자로 인해서만 발생할 수 있는 문제나 수정된 파일과 확실하게 정의된 상관관계가 있는 문제를 자동으로 할당할지 여부입니다.AutoAssignToUser: 제공되는 실행인자에 의해 주어진 Perforce 사용자에게 이 단계의 문제를 자동으로 할당합니다.NotifySubmitters: 빌드 성공과 실패 사이의 모든 제출자에게 알려, 다음 단계로 진행하고 문제의 소유권을 가져올지 여부입니다.IssueGroup: 이 노드에서 생성된 문제의type프로퍼티에 추가될 접미사를 지정하여 다른 문제와의 병합을 방지합니다.BuildBlocker: 이 노드의 실패가 빌드 블로커로 간주되어야 하는지 여부입니다. 빌드 블로커로 식별된 문제는 Slack 알림에 특수 태그가 있습니다.