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

問題を報告する ソースを表示 夜間 7.4 をタップします。 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 は、 targetタスクを表し、「task」はタスクを指す言葉として使用します。 コマンドをご覧ください)。各タスクは、Ant で定義された実行可能なコマンドのリストを実行します。ここでは、ディレクトリの作成と削除、javac の実行、JAR ファイルの作成などがあります。この一連のコマンドは、ユーザー提供のプラグインによって拡張して、あらゆる種類のロジックをカバーできます。各タスクは、depends 属性を使用して、依存するタスクを定義することもできます。これらの依存関係は、図 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/*

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

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

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

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

ビルドステップの並列化の難しさ

最新の開発ワークステーションはかなり強力で、複数のコアを使用して複数のビルドステップを並列に実行できます。ただし、タスクベースのシステムでは、タスクの実行を並列化できると思われる場合でも、並列化できないことがよくあります。タスク A がタスク B と C に依存しているとします。タスク B とタスク C は、 同時に実行しても安全か どうすればよいでしょうか。もし何も触れなかった場合には 必要ありません。ただし、両方が同じファイルを使用してステータスを追跡し、同時に実行すると競合が発生する可能性があります。システムがこれを認識する方法は一般的にないため、これらの競合のリスクを負うか(まれではあるがデバッグが非常に難しいビルドの問題につながる)、ビルド全体を単一のプロセス内の単一スレッドで実行するように制限する必要があります。これは、強力なデベロッパー マシンを大幅に浪費する可能性があり、ビルドを複数のマシンに分散する可能性を完全に排除します。

増分ビルドの実行が困難

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

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

最後に、タスクベースのビルドシステムによって課せられるビルド スクリプトは、多くの場合、扱いにくいものです。ビルドスクリプトは、ビルドされるシステムと同じようにコードであるため、バグが隠れやすく、多くの場合、精査が行き届いていません。ここでは、Google Cloud SDK を扱う際によく見られるバグを タスクベースのビルドシステム:

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

ここで説明するタスクベースのフレームワークでは、パフォーマンス、正確性、メンテナンス性に関する問題を解決するための汎用的な方法はありません。ずっと エンジニアはビルド中に任意のコードを記述できるため、 情報が不足しているため 常にビルドを素早く実行し 確認します。この問題を解決するには、エンジニアの負担を軽減し、システムに負担を移行し、システムの役割をタスクの実行ではなくアーティファクトの生成として再定義する必要があります。

このアプローチが、Blaze のようなアーティファクト ベースのビルドシステムの作成につながりました。 Bazel によるものです。