Bazel의 동시 평가 및 증분 모델입니다.
데이터 모델
데이터 모델은 다음 항목으로 구성됩니다.
SkyValue. 노드라고도 합니다.SkyValues는 빌드 과정에서 빌드된 모든 데이터와 빌드의 입력을 포함하는 변경 불가능한 객체입니다. 예로는 입력 파일, 출력 파일, 타겟, 구성된 타겟이 있습니다.SkyKey.SkyValue를 참조하는 짧은 변경 불가능한 이름입니다(예:FILECONTENTS:/tmp/foo또는PACKAGE://foo).SkyFunction. 키와 종속 노드를 기반으로 노드를 빌드합니다.- 노드 그래프. 노드 간의 종속 관계를 포함하는 데이터 구조입니다.
Skyframe. Bazel이 기반으로 하는 증분 평가 프레임워크의 코드 이름입니다.
평가
빌드는 빌드 요청을 나타내는 노드를 평가하여 이루어집니다.
먼저 Bazel은 최상위
SkyKey의 키에 해당하는 SkyFunction를 찾습니다. 그런 다음 함수는 최상위 노드를
평가하는 데 필요한 노드의 평가를 요청합니다. 그러면 리프 노드에 도달할 때까지 다른 SkyFunction 호출이 발생합니다.
리프 노드는 일반적으로 파일 시스템의
입력 파일을 나타내는 노드입니다. 마지막으로 Bazel은
최상위 SkyValue의 값, 일부 부작용 (예: 파일
시스템의 출력 파일), 빌드에 관련된 노드
간의 종속 항목의 비순환 방향 그래프를 얻게 됩니다.
A SkyFunction은 작업을 수행하는 데 필요한 모든 노드를 미리 알 수 없는 경우 여러 패스에서 SkyKeys를 요청할 수 있습니다. 간단한 예는 심볼릭 링크로 밝혀진 입력 파일 노드를 평가하는 것입니다. 함수는 파일을 읽으려고 시도하고 심볼릭 링크임을 인식하므로 심볼릭 링크의 타겟을 나타내는 파일 시스템 노드를 가져옵니다. 하지만 그 자체가 심볼릭 링크일 수 있으며, 이
경우 원래 함수도 타겟을 가져와야 합니다.
함수는 SkyFunction 인터페이스와
인터페이스에서 제공하는 서비스로 코드에 표시됩니다.SkyFunction.Environment 함수가 할 수 있는 작업은 다음과 같습니다:
env.getValue를 호출하여 다른 노드의 평가를 요청합니다. 노드를 사용할 수 있으면 값이 반환되고, 그렇지 않으면null이 반환되며 함수 자체에서null을 반환해야 합니다. 후자의 경우, 종속 노드가 평가된 다음 원래 노드 빌더가 다시 호출되지만 이번에는 동일한env.getValue호출이 null이 아닌null값을 반환합니다.env.getValues()를 호출하여 여러 다른 노드의 평가를 요청합니다. 종속 노드가 동시에 평가된다는 점을 제외하고는 기본적으로 동일한 작업을 수행합니다.- 호출 중에 계산을 실행합니다.
- 부작용이 있습니다(예: 파일 시스템에 파일 쓰기). 두 개의 서로 다른 함수가 서로의 발을 밟지 않도록 주의해야 합니다. 일반적으로 쓰기 부작용 (데이터가 Bazel에서 외부로 흐르는 경우) 은 괜찮지만 읽기 부작용 (등록된 종속 항목 없이 데이터가 Bazel로 내부로 흐르는 경우)은 등록되지 않은 종속 항목이므로 잘못된 증분 빌드를 일으킬 수 있으므로 괜찮지 않습니다.
잘 동작하는 SkyFunction 구현은 종속 항목을 요청하는 것 외에 다른 방법으로 데이터에 액세스하지 않습니다 (예: 파일 시스템을 직접 읽는 방법).
이렇게 하면 Bazel이 읽은 파일에 데이터 종속 항목을 등록하지 않아
잘못된 증분 빌드가 발생하기 때문입니다.
함수가 작업을 수행할 수 있는 충분한 데이터를 확보하면 완료를 나타내는 null
값을 반환해야 합니다.
이 평가 전략에는 다음과 같은 여러 가지 이점이 있습니다.
- 밀폐성. 함수가 다른 노드에 종속되는 방식으로만 입력 데이터를 요청하는 경우 Bazel은 입력 상태가 동일하면 동일한 데이터가 반환된다고 보장할 수 있습니다. 모든 스카이 함수가 결정적이면 전체 빌드도 결정적입니다.
- 정확하고 완벽한 증분성. 모든 함수의 모든 입력 데이터 가 기록되면 Bazel은 입력 데이터가 변경될 때 무효화해야 하는 정확한 노드 집합만 무효화할 수 있습니다.
- 동시 로드. 함수는 종속 항목을 요청하는 방식으로만 서로 상호작용할 수 있으므로 서로 종속되지 않는 함수는 동시에 실행할 수 있으며 Bazel은 순차적으로 실행되는 것과 동일한 결과가 나오도록 보장할 수 있습니다.
증분성
함수는 다른 노드에 종속되어야만 입력 데이터에 액세스할 수 있으므로 Bazel 은 입력 파일에서 출력 파일까지의 완전한 데이터 흐름 그래프를 빌드하고 이 정보를 사용하여 실제로 다시 빌드해야 하는 노드, 즉 변경된 입력 파일 집합의 역전이적 폐쇄만 다시 빌드할 수 있습니다.
특히 두 가지 가능한 증분 전략이 있습니다. 하향식 전략 과 상향식 전략입니다. 어떤 전략이 최적인지는 종속 항목 그래프의 모양에 따라 다릅니다.
상향식 무효화 중에 그래프가 빌드되고 변경된 입력 집합이 알려지면 변경된 파일에 전이적으로 종속된 모든 노드가 무효화됩니다. 동일한 최상위 노드가 다시 빌드되는 경우에 최적입니다. 상향식 무효화에는 변경되었는지 확인하기 위해 이전 빌드의 모든 입력 파일에서
stat()을 실행해야 합니다. 변경 파일을 파악하기 위해inotify또는 유사한 메커니즘을 사용하면 이를 개선할 수 있습니다.하향식 무효화 중에 최상위 노드의 전이적 폐쇄 가 확인되고 전이적 폐쇄가 깨끗한 노드만 유지됩니다. 노드 그래프가 크지만 다음 빌드에 작은 하위 집합만 필요한 경우에 더 좋습니다. 하향식 무효화는 두 번째 빌드의 작은 그래프만 탐색하는 하향식 무효화와 달리 첫 번째 빌드의 더 큰 그래프를 무효화합니다.
Bazel은 상향식 무효화만 실행합니다.
증분성을 높이기 위해 Bazel은 변경사항 가지치기를 사용합니다. 노드가 무효화되었지만 다시 빌드할 때 새 값이 이전 값과 동일한 것으로 확인되면 이 노드의 변경사항으로 인해 무효화된 노드가 '부활'합니다.
예를 들어 C++ 파일에서 주석을 변경하는 경우에 유용합니다. 그러면 생성된
.o 파일이 동일하므로
링커를 다시 호출할 필요가 없습니다.
증분 연결 / 컴파일
이 모델의 주요 제한사항은 노드의 무효화가 전부 아니면 전무라는 것입니다. 종속 항목이 변경되면 종속 노드는 변경사항을 기반으로 노드의 이전 값을 변경하는 더 나은 알고리즘이 있더라도 항상 처음부터 다시 빌드됩니다. 이 방법이 유용한 몇 가지 예는 다음과 같습니다:
- 증분 연결
- JAR 파일에서 단일 클래스 파일이 변경되면 처음부터 다시 빌드하는 대신 JAR 파일을 제자리에서 수정할 수 있습니다.
Bazel이 이러한 작업을 원칙적인 방식으로 지원하지 않는 이유는 두 가지입니다:
- 성능 향상이 제한적이었습니다.
- 변경사항의 결과가 깨끗한 재빌드의 결과와 동일한지 검증하기가 어렵고 Google은 비트 단위로 반복 가능한 빌드를 중요하게 생각합니다.
지금까지는 비용이 많이 드는 빌드 단계를 분해하고 이러한 방식으로 부분 재평가를 달성하여 충분한 성능을 달성할 수 있었습니다. 예를 들어, Android 앱에서 모든 클래스를 여러 그룹으로 분할하고 별도로 dex 할 수 있습니다. 이렇게 하면 그룹의 클래스가 변경되지 않은 경우 dexing을 다시 실행할 필요가 없습니다.
Bazel 개념에 매핑
다음은 Bazel이 빌드를 실행하는 데 사용하는 주요 SkyFunction 및 SkyValue
구현의 대략적인 요약입니다.
- FileStateValue.
lstat()의 결과입니다. 기존 파일의 경우 함수는 파일 변경사항을 감지하기 위해 추가 정보도 계산합니다. 이는 Skyframe 그래프의 최하위 노드이며 종속 항목이 없습니다. - FileValue. 파일의 실제 콘텐츠 또는
확인된 경로에 관심이 있는 모든 항목에서 사용됩니다. 상응하는
FileStateValue및 확인해야 하는 심볼릭 링크 (예:a/b의FileValue에는a의 확인된 경로와a/b의 확인된 경로가 필요함)에 따라 다릅니다.FileValue와FileStateValue의 구분은 후자가 파일의 콘텐츠가 실제로 필요하지 않은 경우에 사용될 수 있으므로 중요합니다. 예를 들어 파일 시스템 glob (예:srcs=glob(["*/*.java"]))을 평가할 때는 파일 콘텐츠가 관련이 없습니다. - DirectoryListingStateValue.
readdir()의 결과입니다.FileStateValue와 마찬가지로 이는 최하위 노드이며 종속 항목이 없습니다. - DirectoryListingValue. 디렉터리의 항목에 관심이 있는 모든 항목에서 사용됩니다. 상응하는
DirectoryListingStateValue와 디렉터리의 연결된FileValue에 따라 다릅니다. - PackageValue. BUILD 파일의 파싱된 버전을 나타냅니다. 연결된
BUILD파일의FileValue와 패키지의 glob을 확인하는 데 사용되는DirectoryListingValue(내부적으로BUILD파일의 콘텐츠를 나타내는 데이터 구조)에 전이적으로 따라 다릅니다. - ConfiguredTargetValue. 구성된 타겟을 나타냅니다. 이는 타겟 분석 중에 생성된 작업 집합과
종속 구성된 타겟에 제공된 정보의 튜플입니다. 상응하는 타겟이 있는
PackageValue, 직접 종속 항목의ConfiguredTargetValues, 빌드 구성을 나타내는 특수 노드에 따라 다릅니다. - ArtifactValue. 빌드의 파일을 나타냅니다(소스 아티팩트 또는
출력 아티팩트). 아티팩트는 파일과 거의 동일하며 빌드 단계의 실제 실행 중에 파일을
참조하는 데 사용됩니다. 소스 파일
은 연결된 노드의
FileValue에 따라 다르고 출력 아티팩트 는 아티팩트를 생성하는 작업의ActionExecutionValue에 따라 다릅니다. - ActionExecutionValue. 작업의 실행을 나타냅니다. 입력 파일의
ArtifactValues에 따라 다릅니다. 실행하는 작업은 SkyKey 내에 포함되어 있으며 이는 SkyKey가 작아야 한다는 개념과 반대됩니다. 실행 단계가 실행되지 않으면ActionExecutionValue및ArtifactValue가 사용되지 않습니다.
시각적 보조 도구로 이 다이어그램은 Bazel 자체의 빌드 후 SkyFunction 구현 간의 관계를 보여줍니다.
