出力ビルドターゲット

2007年4月26日

ここ数日、同僚のJulian Simpsonが執筆中のAntファイルの リファクタリングに関する記事をレビューしています。Julianは、アジャイル指向の作業習慣をシステムのデプロイに適用する責任を担う、当社の「デプロイ担当者」の一人です。この過程で、Julianは厄介なantビルドスクリプトを山ほど見てきました。彼の記事は、その混乱を解消するための彼のお気に入りの方法をうまく説明しています。

彼の観察の一つで特に興味深かったのは、antファイルがタスク(例:「コンパイル」、「ユニットテスト」)の名前を付けたターゲットに分解される傾向があるという指摘です。これらのタスクは、しばしば命令型のターゲット呼び出しに組み合わされます。

<target name="cruise" depends="clean, compile, copy-static-files, 
unit-test, publish, javadoc, tag"/>

この種のスタイルは、ビルドファイルが基づいている依存関係ベースの計算モデルとの不一致であるため、間違っているように感じます。これを使用する場合、各ターゲットを見て、何に依存しているかを問い、それらの依存関係をターゲットに入れます。したがって、ここのunit-testタスクは、直接compileおよびcopy-static-filesターゲットに依存する必要があり、後続のターゲットで言及されるべきではありません。

明らかな愚かさに直面したとき、安易なのは「おそらく彼らはコンパイルせずにテストを実行できるほど単純なシステムを持っているのだろう」というような軽率なコメントをすることです。しかし、多くの場合、もう少し深く掘り下げる価値があります。

Julianも私もUnixのバックグラウンドを持っているため、makeビルド言語に精通しています。Antと同様に、makeは依存関係ベースのプログラミングを使用します。どちらのシステムも、依存関係を通じてリンクされたターゲットに作業を分解します。しかし、微妙な違いがあります。antでは、ターゲットに名前を付け、どの他のターゲットに依存するかを示します。一方、makeでは、出力ファイル、それが依存する入力ファイル、およびそれらの間をどのように移行するかを記述します。

したがって、makeで2つのファイルで構成されるhello world(Cで)をコンパイルすると、次のようになります。

hello : hello.o greet.o
  gcc -o hello hello.o greet.o

hello.o : hello.c 
  gcc -c hello.c

greet.o: greet.c
  gcc -c greet.c

最初の行は、ターゲットファイルhelloがファイルhello.ogreet.oに依存すると述べています。2行目は、ソースファイルからターゲットを作成する方法を示しています(実際のmakeファイルでは、このほとんどは一般的なルールであり、重複させる必要はありません)。次に、トップルールの入力を作成する方法を示す同様のルールがあります。

ant(Javaを使用)では、次のようになります。

<project name = "hello" default = "test">
  <target name = "compile">
    <javac 
      srcdir = "src"
      destdir = "build"
    />
  </target>
  <target name = "test" depends = "compile">
    <junit printsummary="on">
      <classpath location = "build"/>
      <test name = "Tester"/>
    </junit>
  </target>   
</project>

ここでは、テストタスクがコンパイルタスクに依存すると述べており、これはテストタスクを実行する前に、コンパイルタスクをある時点で実行する必要があることを意味します。

これらのフラグメントはどちらも依存関係を示していますが、この命名の違いは依存関係についてどのように考えるかに影響を与えます。その命名規則が、依存関係について考えることから命令型のルールについて考えるように、あなたの脳をどのように引き離しているのか不思議に思います。

この別の側面は、不要な作業にどのように対処するかということです。ビルドは常に予想よりも時間がかかるように思われるため、不要な作業を避けるために多くのことを行います。実際、これがそもそも依存関係ベースのプログラミングを使用する目的です。ターゲットが複数回言及されたとしても、実行されるのは1回だけです。

しかし、不要な作業を避けるというこの欲求は、ターゲット自体の実行にも関わってきます。makeで作業を避けるための基本的なメカニズムは、出力ファイルと入力ファイルの変更時間を比較することです。出力が最新の入力よりも新しい場合、明らかに最新であるため、再作成する必要はありません。

Antはこれを行わず、代わりにAntタスクが独自の分析を実行して、実行する必要があるものを確認します。コンパイラーが変更後に再構築する必要がある出力クラスを把握する必要がある場合は、これは非常に理にかなっています。しかし、多くの場合、人々はチェックを行わないターゲットを自分で作成します。ターゲットを使用する場合は、何かが変更された可能性がある場合を除き、重要な作業を行わないようにする必要があります。出力に焦点を当てるのは良い方法です。

出力が明確でない場合があります。ユニットテストタスクの出力は何ですか?makeの視点で見ると、「何らかの種類のファイル」と言えます。テストレポートを結果にして、そのテストレポートファイルが入力よりも古いかどうかをタスクで確認できます。ただし、これはそれほど簡単ではない可能性があります。レポートには何も含まれていなくてもかまいません。ただのTouchFileでもかまいません。

私のUnix-makeのルーツはやりすぎですか?おそらく。しかし、ビルドファイルを確認し、次の点を検討することをお勧めします。

  • 各ターゲットに、そのアクティビティではなく、その製品の名前を付けます。
  • ターゲットは、必要がない場合は重要な作業を行わないようにしてください。
  • 各ターゲットの依存関係を直接記述し、ビルドシステムに実行する命令型シーケンスを検討させます。