작업 기반 빌드 시스템

컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.
문제 신고 출처 보기

이 페이지에서는 작업 기반 빌드 시스템과 그 작동 방식, 작업 기반 시스템에서 발생할 수 있는 일부 정보 표시를 다룹니다. 셸 스크립트 이후의 작업 기반 빌드 시스템은 빌드의 차세대 논리적 발전입니다.

작업 기반 빌드 시스템 이해

작업 기반 빌드 시스템에서 작업의 기본 단위는 작업입니다. 각 작업은 모든 종류의 로직을 실행할 수 있는 스크립트이며, 작업은 다른 작업을 작업 전에 실행해야 하는 종속 항목으로 지정합니다. Ant, Maven, Gradle, Grunt, Rake와 같이 현재 사용되는 대부분의 주요 빌드 시스템은 작업 기반입니다. 대부분의 최신 빌드 시스템은 셸 스크립트 대신 빌드 수행 방법을 설명하는 빌드 파일을 만들어야 합니다.

개미 매뉴얼에서 다음 예를 참고하세요.

<project name="MyProject" default="dist" basedir=".">
   <description>
     simple example build file
   </description>
   <!-- set global properties for this build -->
   <property name="src" location="src"/>
   <property name="build" location="build"/>
   <property name="dist" location="dist"/>

   <target name="init">
     <!-- Create the time stamp -->
     <tstamp/>
     <!-- Create the build directory structure used by compile -->
     <mkdir dir="${build}"/>
   </target>
   <target name="compile" depends="init"
       description="compile the source">
     <!-- Compile the Java code from ${src} into ${build} -->
     <javac srcdir="${src}" destdir="${build}"/>
   </target>
   <target name="dist" depends="compile"
       description="generate the distribution">
     <!-- Create the distribution directory -->
     <mkdir dir="${dist}/lib"/>
     <!-- Put everything in ${build} into the MyProject-${DSTAMP}.jar file -->
     <jar jarfile="${dist}/lib/MyProject-${DSTAMP}.jar" basedir="${build}"/>
   </target>
   <target name="clean"
       description="clean up">
     <!-- Delete the ${build} and ${dist} directory trees -->
     <delete dir="${build}"/>
     <delete dir="${dist}"/>
   </target>
</project>

buildfile은 XML로 작성되며 작업 목록과 함께 빌드에 관한 몇 가지 간단한 메타데이터를 정의합니다 (XML의 <target> 태그). (개미는 대상이라는 단어를 사용하여 작업을 나타내며, 작업이라는 단어는 명령어를 가리킵니다.) 각 작업은 Ant가 정의한 가능한 명령어 목록을 실행합니다. 여기에는 디렉터리 생성 및 삭제, javac 실행, JAR 파일 생성이 포함됩니다. 이 명령어 집합은 모든 종류의 로직을 포함하도록 사용자가 제공한 플러그인으로 확장할 수 있습니다. 각 태스크는 종속 항목 속성을 통해 종속되는 태스크를 정의할 수도 있습니다. 이러한 종속 항목은 그림 1과 같이 비순환 그래프를 형성합니다.

종속 항목을 보여주는 아크릴 그래프

그림 1. 종속 항목을 보여주는 비순환 그래프

사용자는 Ant의 명령줄 도구에 작업을 제공하여 빌드를 실행합니다. 예를 들어 사용자가 ant dist를 입력하면 Ant는 다음 단계를 실행합니다.

  1. 현재 디렉터리에 build.xml라는 파일을 로드하고 파싱하여 그림 1과 같이 그래프 구조를 만듭니다.
  2. 명령줄에 제공된 dist라는 작업을 찾아 compile라는 작업에 종속 항목이 있음을 확인합니다.
  3. 이름이 compile인 작업을 찾고 init라는 작업에 대한 종속 항목이 있음을 확인합니다.
  4. 이름이 init인 작업을 찾아 종속 항목이 없음을 확인합니다.
  5. init 태스크에 정의된 명령어를 실행합니다.
  6. 모든 태스크의 종속 항목이 실행되었음을 고려하여 compile 태스크에 정의된 명령어를 실행합니다.
  7. 모든 태스크의 종속 항목이 실행되었음을 고려하여 dist 태스크에 정의된 명령어를 실행합니다.

결국 dist 작업을 실행할 때 Ant가 실행하는 코드는 다음 셸 스크립트와 동일합니다.

./createTimestamp.sh
mkdir build/
javac src/* -d build/
mkdir -p dist/lib/
jar cf dist/lib/MyProject-$(date --iso-8601).jar build/*

구문이 제거되더라도 빌드 파일과 빌드 스크립트는 실제로 다르지 않습니다. 하지만 이 작업을 통해 이미 많은 것을 얻었습니다. 다른 디렉터리에 새 빌드 파일을 만들어 함께 연결할 수 있습니다. 임의의 기존 작업에 종속되는 새 작업을 임의의 복잡한 방식으로 쉽게 추가할 수 있습니다. 단일 작업의 이름은 ant 명령줄 도구에 전달하기만 하면 되며, 실행해야 하는 모든 작업을 결정합니다.

Ant는 오래된 소프트웨어로 2000년에 출시되었습니다. Maven 및 Gradle과 같은 다른 도구는 그 사이에 몇 년 동안 Ant를 개선했으며 기본적으로 외부 종속 항목의 자동 관리와 XML 없이 더 깔끔한 구문과 같은 기능을 추가하여 이를 대체했습니다. 그러나 이러한 최신 시스템은 본질적으로 동일하게 유지됩니다. 즉, 엔지니어가 태스크와 같은 원칙에 따라 모듈식으로 빌드 스크립트를 작성하고 이러한 작업을 실행하고 종속 항목을 관리하는 도구를 제공할 수 있습니다.

작업 기반 빌드 시스템의 어두운 측면

엔지니어는 기본적으로 이러한 도구를 사용하여 작업을 작업으로 정의할 수 있으므로 매우 강력하게 상상할 수 있는 모든 것을 할 수 있습니다. 하지만 이 단점에는 단점이 있으며, 빌드 스크립트가 더 복잡해짐에 따라 작업 기반 빌드 시스템을 사용하기가 어려워질 수 있습니다. 이러한 시스템의 문제는 실제로 엔지니어에게 너무 많은 전력을 공급하고 시스템에 충분한 전력을 공급하지 못한다는 점입니다. 시스템은 스크립트가 하는 작업을 알 수 없으므로 빌드 단계를 예약하고 실행하는 방법이 매우 보수적이어야 하므로 성능이 저하됩니다. 또한 시스템이 각 스크립트가 제대로 작동하는지 확인할 수 있는 방법이 없기 때문에 스크립트는 점점 더 복잡해지고 디버깅이 필요한 또 다른 작업이 되기도 합니다.

빌드 병렬화의 어려움

최신 개발 워크스테이션은 여러 빌드 단계를 동시에 실행할 수 있는 여러 코어를 갖추고 있어 매우 강력합니다. 그러나 작업 기반 시스템은 가능해 보여도 작업 실행을 동시에 로드할 수 없는 경우가 많습니다. 작업 A가 작업 B와 C에 종속된다고 가정해 보겠습니다. 작업 B와 C는 서로 종속되지 않으므로 시스템에서 작업 A로 더 빠르게 이동할 수 있도록 두 작업을 동시에 실행하는 것이 안전한가요? 동일한 리소스를 터치하지 않을 수도 있습니다. 그러나 그럴 수 없습니다. 둘 다 같은 파일을 사용하여 상태를 추적하고 동시에 실행하면 충돌이 발생할 수 있습니다. 일반적으로 시스템이 알 수 있는 방법이 없으므로 이러한 충돌을 야기하거나(드물지만 디버그하기 어려운 빌드 문제를 야기함), 단일 빌드에서 전체 빌드를 단일 스레드에서 실행되도록 제한해야 합니다. 이는 강력한 개발자 머신으로 인해 막대한 낭비가 될 수 있으며 여러 머신에 빌드를 배포할 수 있는 가능성을 완전히 배제합니다.

증분 빌드를 수행하기 어려움

우수한 빌드 시스템을 사용하면 엔지니어가 신뢰할 수 있는 증분 빌드를 수행할 수 있으므로 작은 변경사항을 적용할 때 전체 코드베이스를 처음부터 다시 빌드할 필요가 없습니다. 이는 빌드 시스템이 느리고 앞서 언급한 이유로 빌드 단계를 동시에 로드할 수 없는 경우에 특히 중요합니다. 아쉽게도 작업 기반 빌드 시스템도 어려워합니다. 작업은 어떤 작업이든 할 수 있기 때문에 일반적으로 작업이 이미 완료되었는지 확인할 수 있는 방법은 없습니다. 많은 작업은 간단히 소스 파일 세트를 취하여 컴파일러를 실행하여 바이너리 집합을 생성합니다. 따라서 기본 소스 파일이 변경되지 않은 경우 작업을 다시 실행할 필요가 없습니다. 하지만 추가 정보 없이는 시스템에서 이를 확인할 수 없습니다. 작업이 변경되었을 수 있는 파일을 다운로드하거나, 실행마다 다를 수 있는 타임스탬프를 작성하는 경우가 이에 해당합니다. 정확성을 보장하기 위해 시스템은 일반적으로 각 빌드 중에 모든 작업을 다시 실행해야 합니다. 일부 빌드 시스템은 엔지니어가 작업을 다시 실행해야 하는 조건을 지정할 수 있도록 하여 증분 빌드를 사용 설정하려고 합니다. 이는 실현 가능한 경우가 많지만 표시되는 것보다 훨씬 더 까다로운 경우가 많습니다. 예를 들어 C++와 같은 언어에서는 파일을 다른 파일에 직접 포함할 수 있는 경우 입력 소스를 파싱하지 않고 변경사항을 감시해야 하는 전체 파일 세트를 결정할 수 없습니다. 엔지니어가 바로가기를 사용하는 경우가 많아 이러한 바로가기가 필요하지 않은 경우에도 작업 결과가 재사용되는 드물고 성가신 문제가 발생할 수 있습니다. 이 문제가 자주 발생하면 엔지니어는 모든 빌드가 새로운 상태를 얻기 전에 깔끔하게 실행되는 습관을 들이고 처음에 증분 빌드를 사용할 목적을 완전히 무너뜨립니다. 태스크를 다시 실행해야 할 때를 결정하는 것은 놀라울 정도로 미묘하며 사람보다 머신으로 더 잘 처리됩니다.

스크립트 유지보수 및 디버깅 문제

마지막으로, 작업 기반 빌드 시스템에서 적용하는 빌드 스크립트는 사용하기 어려운 경우가 많습니다. 이는 덜 철저한 검사를 받기도 하지만, 빌드 스크립트는 빌드된 시스템과 마찬가지로 코드이므로 버그를 숨길 수 있습니다. 다음은 작업 기반 빌드 시스템으로 작업할 때 매우 흔하게 발생하는 버그의 예입니다.

  • 작업 A는 작업 B를 사용하여 특정 파일을 출력으로 생성합니다. 태스크 B의 소유자는 다른 작업이 이에 종속된다는 사실을 모르기 때문에 이를 변경하여 다른 위치에 출력을 생성합니다. 이 작업은 누군가가 태스크 A를 실행하려고 할 때 실패하지 않을 때까지 감지할 수 없습니다.
  • 작업 A는 작업 C에 종속되는데, 작업 B는 작업 A에 필요한 특정 파일을 출력으로 생성하는 작업 C에 종속됩니다. 태스크 B의 소유자가 더 이상 태스크 C에 종속되지 않아도 된다고 결정합니다. 따라서 태스크 B가 태스크 C에 전혀 관심이 없더라도 태스크 A가 실패합니다.
  • 새 작업의 개발자는 실수로 도구의 위치 또는 특정 환경 변수의 값과 같이 작업을 실행하는 머신에 관해 가정합니다. 작업은 머신에서 작동하지만 다른 개발자가 작업을 시도할 때마다 실패합니다.
  • 작업은 인터넷에서 파일을 다운로드하거나 빌드에 타임스탬프를 추가하는 것과 같은 비확정적인 구성요소를 포함합니다. 이제 사용자는 빌드를 실행할 때마다 다른 결과를 얻을 수 있습니다. 즉, 엔지니어가 항상 자동화된 빌드 시스템에서 발생하는 각각의 실패나 실패를 재현하고 수정할 수 있는 것은 아닙니다.
  • 종속 항목이 여러 개인 작업은 경합 상태를 만들 수 있습니다. 태스크 A가 태스크 B와 태스크 C에 모두 종속되어 있고 태스크 B와 C가 모두 동일한 파일을 수정하는 경우 태스크 A와 태스크 C 중 먼저 완료되는 태스크에 따라 태스크 A가 다른 결과를 얻습니다.

여기에 나열된 작업 기반 프레임워크 내에서 이러한 성능, 정확성 또는 유지관리 가능성 문제를 해결할 수 있는 일반적인 방법은 없습니다. 엔지니어가 빌드 중에 실행되는 임의의 코드를 작성할 수 있는 한, 시스템은 항상 빠르고 정확하게 빌드를 실행할 수 있는 충분한 정보가 없습니다. 문제를 해결하려면 엔지니어의 역량을 최대한 활용하여 시스템 내에서 이를 다시 실행하며 시스템 역할을 실행 중인 작업이 아닌 아티팩트 생성으로 다시 개념화해야 합니다.

이러한 접근 방식으로 Blaze 및 Bazel과 같은 아티팩트 기반 빌드 시스템이 만들어졌습니다.