タスクベースのビルドシステム

<ph type="x-smartling-placeholder"></ph> 問題を報告する <ph type="x-smartling-placeholder"></ph> ソースを表示 夜間 · 7.3 · 7.2 · 7.1 · 7.0 · 6.5

このページでは、タスクベースのビルドシステムとその仕組み、および 複雑さを軽減できます。シェルスクリプトの後 タスクベースのビルドシステムは、ビルドを論理的に進化させたものです。

タスクベースのビルドシステムについて

タスクベースのビルドシステムの場合、作業の基本単位はタスクです。各 任意の種類のロジックを実行できるスクリプトで、タスクは 依存関係として定義できます。使用中のほとんどの主要なビルドシステム 現在、Ant、Maven、Gradle、Grunt、Rake などはタスクベースです。以前の シェル スクリプト。最新のビルドシステムでは、エンジニアがビルドファイルを作成する必要がある ビルドの実行方法を記述します。

たとえば Ant マニュアル:

<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>

ビルドファイルは XML で記述され、ビルドに関する簡単なメタデータを定義します。 タスクのリスト(XML 内の <target> タグ)も付けます。(Ant は、 ターゲットタスクを表し、「タスク」はタスクを指す コマンドを使用できます)。各タスクでは、Ant で定義された可能なコマンドのリストが実行されます。 これには、ディレクトリの作成と削除、javac の実行、 JAR ファイルを作成します。このコマンドセットは、ユーザーが用意したコマンドで拡張 あらゆるロジックに対応しています。各タスクでは、自身が管理するタスクを 依存します。これらの依存関係により、非巡回グラフが形成されます。 必要があります。

依存関係を示すアクリル グラフ

図 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/*

構文が削除されると、ビルドファイルとビルド スクリプトは 違いはありませんしかし、これによってすでに多くのメリットを得ています。Google では、 他のディレクトリに新しいビルドファイルを作成し、それらをリンクさせる。Google の 既存のタスクに依存する新しいタスクを任意かつ複雑な方法で追加する。水 単一のタスクの名前を ant コマンドライン ツールに渡すだけで済み、 実行する必要があるすべてのものが決定されます。

Ant は、2000 年にリリースされた古いソフトウェアです。その他のツール Maven と Gradle はここ数年で Ant を改善し、 代わりに、組織の外部 IP アドレスの自動管理、 XML を使用しないすっきりした構文で構成できます。しかし、比較的新しいタイプの 変わりません。エンジニアがビルド スクリプトを 原則的かつモジュール方式のタスクをタスクとして 提供し それらのタスクを実行するためのツールを提供 リソース間の依存関係の管理などです

タスクベースのビルドシステムのダークサイド

これらのツールを使用すると、エンジニアは基本的に任意のスクリプトをタスクとして定義できるため、 非常に強力で、ユーザーが思い描く できます。ただし、その機能には欠点があり、タスクベースのビルドシステムを使用すると、 ビルド スクリプトが複雑になるにつれ、作業が困難になります。「 そのようなシステムで確認できるのは過剰な電力を システムへの電力が不足しています。なぜならシステムは パフォーマンスが損なわれるため、非常に控えめに ビルドステップのスケジュールと実行方法に 大きく影響しますそのシステムに対して 各スクリプトが意図したとおりに動作していることを確認するため、 デバッグが必要になります

ビルドステップの並列化が困難

最新の開発ワークステーションは非常にパワフルで、 複数のビルドステップを並行して実行できますしかしタスクベースのシステムは 想定しているように見えても、タスクの実行を並列化できないことが多々あります。 できます。タスク A がタスク B とタスク C に依存しているとします。タスク B とタスク C は、 同時に実行しても安全か どうすればよいでしょうか。もし何も触れなかった場合には 必要ありません。しかし、そうではないかもしれません。おそらく、両方が同じファイルを使用して 同時に実行すると競合が発生します。「 競合が起きるリスクをシステムが負うか (まれに発生するが、デバッグが非常に困難なビルドの問題を引き起こす)、または ビルド全体が単一プロセスの 1 つのスレッドで実行されるように制限します。 これは強力なデベロッパーマシンの無駄になり得ます。 複数のマシンにビルドを配布する可能性を排除します。

増分ビルドの実行が困難

優れたビルドシステムがあれば、エンジニアは信頼性の高い増分ビルドを実行できます。 小さな変更でもコードベース全体を再構築する必要がないことが 傷ます。これは、ビルドシステムの動作が遅く、コードを生成できない場合は特に重要です。 ビルドステップを並列化できますしかし残念ながら ここでもタスクベースのビルドシステムには苦労しています。タスクは何でもできるため 一般的に、アップグレードが完了したかどうかを確認する方法はありません。多数のタスク ソースファイルのセットからコンパイラを実行するだけで、 バイナリ。基盤となるソースファイルが 変更されていません。しかし、追加情報がなければ、システムは 変更された可能性のあるファイルがタスクによってダウンロードされたり、 実行ごとに異なる可能性があるタイムスタンプを書き込みます。保証 システムは通常、各ビルド中にすべてのタスクを再実行する必要があります。一部 増分ビルドを可能にするために、 条件を指定できます場合もありますが、 見かけよりもずっと難しい問題であることも多々あります。たとえば ファイルを直接他のファイルに含めることができる C++ など、 変更を監視する必要があるファイルのセット全体を特定することは不可能 入力ソースの解析は必要ありませんエンジニアは多くの場合、近道をしてしまい、 このようなショートカットを使用すると、タスクの結果がうまくいかず、 避けるべきですこの状況が頻繁に発生すると 毎回のビルドの前にクリーンを実行して新しい状態を取得する 最初の段階で増分ビルドを行うという目的が できます。タスクを再実行する必要があるタイミングの判断は驚くほど難しく、 人間よりも機械で処理できる仕事です。

スクリプトの管理とデバッグが困難

最後に、タスクベースのビルドシステムによって課されるビルド スクリプトは、多くの場合、 困難です。多くの場合、精査は少ないが、スクリプトをビルドする 構築中のシステムと同じようにコードであり、バグが簡単に隠れてしまう場所です。 ここでは、Google Cloud SDK を扱う際によく見られるバグを タスクベースのビルドシステム:

  • タスク A は、特定のファイルを出力として生成するためにタスク B に依存します。オーナー タスク B は、他のタスクがそれに依存していることに気付いていないため、これを 別の場所に出力を生成することになります。これは誰かが検出するまで検出されません が、タスク A を実行しようとしたときに失敗します。
  • タスク A はタスク B に依存しており、タスク C はタスク C に依存しており、 必要とする出力として識別します。タスク B のオーナー はタスク C に依存する必要がなくなったと判断し、 タスク B はタスク C にまったく関係していなくても、A は失敗します。
  • 新しいタスクの開発者が誤ってタスクについて仮定して、 ツールの場所やツールの値など、タスクを実行しているマシンが 確認します。タスクは自分のマシンでは動作するが、失敗する ダウンロードされます。
  • タスクに、ファイルのダウンロードなど、非決定的なコンポーネントが含まれている タイムスタンプを追加できます。現在は 毎回異なる結果になる可能性があるため エンジニアが常にお互いの障害を再現して修正できるわけではない 自動ビルドシステムで起こりうる障害を 対策できます
  • 複数の依存関係があるタスクは、競合状態を引き起こす可能性があります。タスク A の場合 タスク B とタスク C の両方に依存し、タスク B と C の両方が同じ タスク A の結果は、タスク B と C のどちらであるかによって 表示されます。

このようなパフォーマンス、正確性、信頼性の問題を解決する汎用的な方法は タスクベースのフレームワークで 保守性の問題を軽減できますずっと エンジニアはビルド中に任意のコードを記述できるため、 情報が不足しているため 常にビルドを素早く実行し 確認します。この問題を解決するには、 システムに取り戻し、システムの手元に 実行タスクとしてではなくアーティファクトの生成として システムの役割を担います

このアプローチにより、Blaze のようなアーティファクト ベースのビルドシステムが開発されました。 Bazel を使用しています。