インクリメンタル・プロジェクト・ビルダー

インクリメンタル・プロジェクト・ビルダーは、プロジェクト内のリソースを、 ビルダー自体によって定義された方法で操作するオブジェクトです。 インクリメンタル・プロジェクト・ビルダーは、変換をリソースに適用するために頻繁に使用され、 リソースまたは他の種類の成果物を生成します。

プラグインは、専門化されたリソース変換のインプリメントを行うために、プラットフォームにインクリメンタル・プロジェクト・ビルダーを追加します。 たとえば、SDK プラットフォームと共に提供される Java 開発ツール (JDT) プラグインは、 ファイルが Java プロジェクトに追加または変更される毎に、Java ソース・ファイルをクラス・ファイルにコンパイルし、 その変更によって影響される他のすべてのファイルを再コンパイルする、 インクリメンタル・プロジェクト・ビルダーを定義します。

プラットフォームは、以下の 2 種類のビルドを定義します。

インクリメンタル・ビルドは、リソース変更デルタと共に取り込まれます。デルタは、ビルダーがプロジェクトを最後にビルドしてから行われたすべてのリソース変更による実質的な影響を反映しています。 このデルタは、リソース変更イベント内で使用されるものと同様です。

ビルダーを理解するには、例を見るのが一番です。Java 開発ツール (JDT) は、 変更によって影響されるファイルを再コンパイルするために、Java インクリメンタル・プロジェクト・ビルダーによって駆動される Java コンパイラーを提供します。 完全ビルドが起動されると、プロジェクト内のすべての .java ファイルがコンパイルされます。  検出されたすべてのコンパイルの問題は、影響された .java ファイルに問題のあるマーカーとして追加されます。インクリメンタル・ビルドが起動されると、 ビルダーは、リソース・デルタで記述されている、追加、変更または影響された .java ファイルを選択して再コンパイルし、 必要に応じて問題マーカーを更新します。適切でなくなった .class ファイルまたはマーカーは除去されます。

インクリメンタル・ビルドは、数百または数千のリソースを持ち、そのほとんどは指定時間に変更されることはないプロジェクトのパフォーマンスを著しく向上させます。

インクリメンタル・ビルドにおけるテクニカルな問題は、再ビルドする必要があるものを正確に判別するということです。たとえば、Java ビルダーが保持する内部状態には、 依存性グラフおよび報告されたコンパイル時の問題のリストが含まれています。  この情報は、インクリメンタル・ビルド中に Java リソース内の変更に応じて再コンパイルする必要があるクラスの識別に使用されます。

ビルドにおける基本構造がプラットフォームに定義されているにも関らず、実際の作業はユーザー・プラグインで実行されます。複雑なインクリメンタル・ビルダーをインプリメントするパターンは、インプリメンテーションがビルダー特有の設計に依存しているため、本書の解説の対象外としています。

ビルドの起動

ビルダーは、以下の方法のいずれかで明示的に起動可能です。

実際には、ワークベンチ・ユーザーは、「リソース・ナビゲーター (Resource Navigator)」メニューで対応するコマンドを選択することによって、ビルドを起動します。

インクリメンタル・プロジェクト・ビルダーは、自動ビルド中にプラットフォームによって暗黙的にも起動されます。 自動ビルドが使用可能になっている場合には、ワークスペースが変更されるごとに自動ビルドが実行されます。

インクリメンタル・プロジェクト・ビルダーの定義

org.eclipse.core.resources.builders 拡張ポイントは、 プラットフォームへのインクリメンタル・プロジェクト・ビルダーの追加に使用します。次のマークアップは、 仮想プラグイン com.example.builders がインクリメンタル・プロジェクト・ビルダーをどのように追加するかを示しています。

<extension
    point="org.eclipse.core.resources.builders">
    <builder
        id="com.example.builders"
        name="MyBuilder">
        <run 
            class="com.example.builders.BuilderExample">
            <parameter name="optimize" value="true" />
            <parameter name="comment" value="Builder comment" />
        </run>

    </builder>

</extension>

拡張ポイントで識別されるクラスは、プラットフォーム・クラス IncrementalProjectBuilder を拡張する必要があります。

public class BuilderExample extends IncrementalProjectBuilder {
    IProject[] build(int kind, Map args, IProgressMonitor monitor)
            throws CoreException {
        // add your build logic here
        return null;
    }
    protected void startupOnInitialize() {
        // add builder init logic here
    }
}

ビルド処理は、要求されたビルドの種類に関する情報、FULL_BUILDINCREMENTAL_BUILD、または AUTO_BUILD を含む、 build() メソッドで始まります。インクリメンタル・ビルドが要求されると、 最後のビルド以後にプロジェクトのリソースで行われた変更を記述するために、リソース・デルタが提供されます。 次のコードの断片的は、 build() メソッドをさらに洗練したものになっています。

protected IProject[] build(int kind, Map args, IProgressMonitor monitor
        throws CoreException {
    if (kind == IncrementalProjectBuilder.FULL_BUILD) {
        fullBuild(monitor);
    } else {
        IResourceDelta delta = getDelta(getProject());
        if (delta == null) {
            fullBuild(monitor);
        } else {
            incrementalBuild(delta, monitor);
        }
    }
    return null;
}

プロジェクト "X" をビルド時に、ビルダーが、他のプロジェクト "Y" における変更情報を必要とする場合があります。"  (たとえば、X の Java クラスが Y で提供されるインターフェースをインプリメントする場合。) X をビルド中に、Y のデルタは getDelta(Y) を呼び出すことで選択可能になります。  プラットフォームがこのようなデルタを提供できるようにするには、X のビルダーが、 直前の build() 呼び出しから Y を含んでいる配列を戻すことによって、X と Y の間の依存性を宣言する必要があります。  ビルダーに依存性がない場合、単に NULL が戻ります。  詳しくは、IncrementalProjectBuilder を参照してください。

完全ビルド

完全ビルド要求の処理に必要なロジックは、プラグインに固有です。プロジェクト内のすべてのリソースの訪問 (ビルドがプロジェクトに対して起動された場合)、またはプロジェクト間に依存性がある場合には、他のプロジェクトの検査が必要な場合があります。  以下のコードの断片は、完全ビルドのインプリメント方法を示しています。

protected void fullBuild(final IProgressMonitor monitor) throws CoreException {
    try {
        getProject().accept(new MyBuildVisitor());
    } catch (CoreException e) { }
}

ビルド・ビジターは、特定のリソースに対してビルドを実行 (および true を戻してすべての子リソースの訪問を継続) します。

class MyBuildVisitor implements IResourceVisitor {
    public boolean visit(IResource res) {
        //build the specified resource.
        //return true to continue visiting children.
        return true;
    }
}

訪問処理は、リソース・ツリーが完全に訪ねられるまで継続します。

インクリメンタル・ビルド

インクリメンタル・ビルドの実行時には、ユーザーはプロジェクト全体ではなく、リソース変更デルタで作業します。

protected void incrementalBuild(IResourceDelta delta, 
        IProgressMonitor monitor) throws CoreException {
    // the visitor does the work.
    delta.accept(new MyBuildDeltaVisitor());
}

インクリメンタル・ビルドにおいて、ビルド・ビジターは、完全なリソース・ツリーではなく、リソース・デルタ・ツリーで作業することに注意してください。

訪問処理は、リソース・デルタ・ツリーが完全に訪ねられるまで継続します。変更に固有の特質は、 『リソース変更リスナーのインプリメント』に記述されている特質と同様です。 1 つの重要な違いは、 インクリメンタル・プロジェクト・ビルダーでは、ユーザーは、ワークスペース全体ではなく、 通常は特定のプロジェクトに基づくリソース・デルタで作業するということです。

インクリメンタル・プロジェクト・ビルダーとプロジェクトの関連付け

ビルダーを指定されたプロジェクト用に使用可能にするには、そのプロジェクトに対するビルド仕様にそのビルダーが組み込まれている必要があります。プロジェクトのビルド仕様は、プロジェクトのビルド時に、順次実行するコマンドのリストです。それぞれのコマンドは、 単一のインクリメンタル・プロジェクト・ビルダーを命名します。

以下のコードの断片は、新規ビルダーを、ビルダーの既存リスト内の最初のビルダーとして追加します。

IProjectDescription desc = project.getDescription();
ICommand[] commands = desc.getBuildSpec();
boolean found = false;

for (int i = 0; i < commands.length; ++i) {
    if (commands[i].getBuilderName().equals(BUILDER_ID)) {
        found = true;
        break;
    }
}
if (!found) { 
    //add builder to project
    ICommand command = desc.newCommand();
    command.setBuilderName(BUILDER_ID);
    ICommand[] newCommands = new ICommand[commands.length + 1];

    // Add it before other builders.
    System.arraycopy(commands, 0, newCommands, 1, commands.length);
    newCommands[0] = command;
    desc.setBuildSpec(newCommands);
    project.setDescription(desc, null);
}

プロジェクト・ビルダーの構成は、通常はプロジェクトの作成時に、一度だけ実行されます。