빌드 시스템이란, 도대체 뭐죠?

발행: (2025년 12월 14일 오전 04:58 GMT+9)
12 min read

Source: Hacker News

big picture

높은 수준에서 빌드 시스템입력 데이터를 출력 데이터로 변환하는 일련의 변환정의하고 실행하는 방법을 제공하는 도구 또는 라이브러리이며, 이 변환들은 객체 저장소캐시되어 메모이제이션됩니다.

변환은 단계 또는 규칙1이라고 하며, 하나 이상의 입력으로부터 하나 이상의 출력을 생성하는 작업을 실행하는 방법을 정의합니다.
규칙은 일반적으로 캐시 단위이며, 즉 캐시 포인트는 규칙의 출력이고, 캐시 무효화는 규칙의 입력에서 발생해야 합니다.
규칙은 이전 출력에 의존성을 가질 수 있으며, 이는 의존성 그래프라는 방향 그래프를 형성합니다.
사이클을 이루는 그래프 형태의 의존성을 순환 의존성이라고 하며, 보통 금지됩니다.2

다른 규칙에만 사용되고 최종 사용자에게는 관심 대상이 아닌 출력은 중간 출력이라고 합니다.

출력의 의존성 중 하나가 수정되었거나, 전이적으로 의존성 중 하나가 오래된 경우 해당 출력은 구식, 더럽혀짐, 혹은 오래됨이라고 합니다. 오래된 출력은 캐시를 무효화하고 재빌드가 필요합니다. 캐시되어 있고 더럽혀지지 않은 출력은 최신 상태입니다. 규칙은 그 출력 중 하나라도 오래되면 오래된 것으로 간주됩니다. 출력이 전혀 없는 규칙은 언제나 오래된 것으로 간주됩니다.

빌드 도구의 각 호출을 빌드라고 합니다.

  • 전체 빌드 또는 클린 빌드는 캐시가 비어 있고 모든 변환이 배치 작업으로 실행될 때 발생합니다.
  • 캐시가 전체인 경우 모든 규칙이 최신 상태입니다.
  • 증분 빌드는 캐시가 부분적으로 채워져 있지만 일부 출력이 오래되어 재빌드가 필요할 때 발생합니다.
  • 캐시를 삭제하는 것을 정리(cleaning) 라고 합니다.

빌드가 정확하거나 건전하다는 것은 모든 가능한 증분 빌드가 전체 빌드와 동일한 결과를 산출한다는 의미입니다.3
빌드가 최소(때때로 최적)하다는 것은 규칙이 빌드당 최대 한 번만 재실행되고, 건전성을 위해 필요할 때만 실행된다는 뜻입니다(Build Systems à la Carte, Pluto).

빌드가 건전하려면 가능한 모든 캐시 무효화를 의존성으로 추적해야 합니다.

캐시가 없는 빌드 시스템은 작업 실행기(task runner) 또는 배치 컴파일러(batch compiler) 라고 부릅니다. 작업 실행기는 캐시를 지원하지 않더라도 종종 의존성을 지원합니다. 캐시가 있는 빌드 시스템은 출력이 전혀 없는 작업만 정의함으로써 작업 실행기를 흉내낼 수 있지만, 보통 이 용도로 설계되지 않았습니다.4

빌드 시스템 예시: make, docker build, rustc.
작업 실행기 예시: just, 셸 스크립트, gcc.

specifying dependencies

빌드는 프로세스 간(inter‑process) 일 수도 있는데, 이 경우 작업은 보통 입력·출력 파일을 갖는 단일 프로세스 실행이며, 프로세스 내(intra‑process) 일 수도 있는데, 이 경우 작업은 보통 인자와 반환값을 갖는 단일 함수 호출입니다.

의존성을 추적하려면 모든 입력과 출력을 미리 소스 코드에 선언하거나, 작업 실행으로부터 추론할 수 있어야 합니다.

규칙 정의의 변화를 추적하는 빌드 시스템을 자기 추적(self‑tracking) 이라고 합니다. 규칙의 과거 버전을 히스토리(history) 라고 부릅니다(Build Systems à la Carte).

런타임 동작으로부터 의존성을 추론하는 행위를 추적(tracing) 이라고 합니다.
추적된 규칙이 아직 빌드되지 않은 의존성에 의존한다면, 빌드 시스템은 오류를 발생시키거나, 작업을 일시 중단(suspend) 하고 의존성이 빌드된 뒤 재개(resume) 하거나, 작업을 중단(abort) 하고 의존성이 빌드된 뒤 재시작(restart) 할 수 있습니다(Build Systems à la Carte).

프로세스 간 빌드는 종종 입력·출력을 선언하고, 프로세스 내 빌드는 종종 이를 추론하지만, 이것이 정의에 내재된 것은 아닙니다.5

프로세스 내 빌드 예시: 스프레드시트, wild linker, Python의 functools.cache 같은 메모이제이션 라이브러리.

applicative and monadic structure

모든 입력·출력·규칙이 미리 선언된 경우 빌드 그래프는 Applicative 합니다. 이 경우 그래프는 정적으로 알려져 있습니다. 순수하게 Applicative인 빌드 시스템은 거의 없으며, 대부분은 탈출구(escape hatch)를 가지고 있습니다.

그래프가 Monadic 하다는 것은 모든 출력이 미리 알려져 있지 않거나, 규칙이 런타임에 동적으로 다른 규칙을 생성할 수 있음을 의미합니다. 미리 알려지지 않은 입력은 동적 의존성(dynamic dependencies) 이라고 합니다. 동적 의존성은 완전한 Monadic 빌드 시스템보다 약하며, 표현할 수 있는 빌드 그래프가 더 적습니다(the capability‑tractability tradeoff).6

규칙 선언을 요구하지 않는 빌드 시스템은 항상 Monadic 합니다.

Monadic 빌드 시스템 예시: Shake, ninja의 dyndeps, Cargo 빌드 스크립트.
Applicative 빌드 시스템 예시: make(단, 재귀 make 금지), Bazel(네이티브 규칙 제외), 메모이제이션을 갖는 map/reduce 라이브러리, 예를 들어 이 Unison 프로그램.

early cutoff

더럽혀진 규칙 R이 오래된 출력을 가지고 재실행되어 이전과 동일한 새 출력을 만든 경우, 빌드 시스템은 R에 의존하는 이후 규칙들을 실행하지 않을 기회를 가집니다. 이 기회를 활용하는 것을 조기 차단(early cutoff) 이라고 합니다.

조기 차단에 대한 자세한 내용은 rustc‑dev‑guide를 참고하십시오.7

rebuild detection

건전하지 않은 빌드 시스템에서는 시스템이 재빌드가 필요함을 정확히 감지하지 못할 수 있습니다. 이러한 시스템은 때때로 대상 강제 재실행(force‑rerun) 방법을 제공하는데, 이는 기존 캐시는 유지하면서 단일 규칙만 재실행하는 방식입니다. 프로세스 간 빌드에서는 파일의 수정 시간을 현재 시각으로 설정하기 위해 touch 하는 것이 일반적입니다.

the executor

빌드 실행기(build executor) 는 작업을 실행하고 모든 의존성을 만족하도록 순서를 스케줄링합니다. 이때 의존성 깊이, 마지막 실행 시 걸린 시간 등과 같은 휴리스틱을 사용하기도 합니다.
또한 규칙 입력이 수정되었는지 감지해 규칙을 오래된 것으로 표시하는데, 이를 재빌드 감지(rebuild detection) 라고 합니다.
실행기는 작업을 재시작하거나 일시 중단할 수 있는 빌드 시스템에서는 이를 지원하고, 진행 상황 보고(progress reporting) 를 제공하며, 때때로 의존성 그래프 질의(querying) 를 허용합니다.
가끔 실행기는 작업이 사용한 입력을 추적해 선언된 의존성과 일치하는지 강제하거나 자동으로 내부 의존성 그래프에 추가하기도 합니다.

inter‑process builds

프로세스 간 빌드 맥락에서 아티팩트(artifact) 는 규칙에 의해 생성된 출력 파일을 의미합니다.8
소스 파일(source file) 은 현재 프로젝트(때때로 레포지토리 또는 워크스페이스)에 특화된 입력 파일이며, 여러 프로젝트에서 재사용되는 시스템 의존성(system dependency) 와는 구별됩니다.9


Footnotes

  1. 원문 기사에서 각주 14를 참고하십시오.

  2. 원문 기사에서 각주 5를 참고하십시오.

  3. 원문 기사에서 각주 1을 참고하십시오.

  4. 원문 기사에서 각주 7을 참고하십시오.

  5. 원문 기사에서 각주 8을 참고하십시오.

  6. 원문 기사에서 각주 15를 참고하십시오.

  7. 원문 기사에서 각주 9를 참고하십시오.

  8. 원문 기사에서 각주 6을 참고하십시오.

  9. 원문 기사에서 각주 11을 참고하십시오.

Back to Blog

관련 글

더 보기 »