增量式项目构建器是以构建器自己定义的方式来处理项目中的资源的一种对象。通常使用增量式项目构建器来对资源应用转换, 以便生成另一种类型的资源或助诊文件。
插件为平台添加增量式项目构建器,以实现专门的资源转换。例如,随平台 SDK 提供的“Java 开发工具”(JDT)插件定义增量式项目构建器, 每当在 Java 项目中添加或修改文件时,该构建器就会将 Java 源文件编译成类文件, 并重新编译受更改影响的所有其他文件。
平台定义两种类型的构建:
完整构建根据暂存区来执行构建。它将项目中的所有资源看作从未被构建器看到过一样。
增量式构建使用由构建器在内部维护的“上次构建状态” 根据自上次构建后的更改来进行优化构建。
增量式构建基于资源更改变化。变化反映自构建器上次构建项目后所有资源更改的净效应。此变化类似于在资源更改事件内部使用的变化。
通过示例最容易了解构建器。“Java 开发工具”(JDT)提供了 Java 编译器来重新编译受更改影响的文件, 该编译器是通过 Java 增量式项目构建器来驱动的。触发完整构建时,将编译项目中的所有 .java 文件。遇到的所有编译问题都会作为问题标记添加到受影响的 .java 文件上。触发增量式构建时,构建器会有选择性地重新编译在资源变化中描述的已添加、已更改或者受影响的 .java 文件,并根据需要来更新问题标记。任何不再适当的 .class 文件或标记都会除去。
对于具有成百上千的资源、并且大多数资源在任何时候都不发生变化的项目来说, 增量式构建具有明显的性能优势。
增量式构建的技术难点就在于如何精确地确定需要重构哪些内容。例如,Java 构建器维护的增量状态包括诸如从属项图和报告的编译问题的列表之类的内容。在进行增量构建期间,使用此信息来标识需要重新编译哪些类来响应 Java 资源中的更改。
尽管用于构建的基本结构是在平台中定义的,但是实际的工作是在插件中完成的。用于实现复杂增量式构建器的模式超出了讨论范围, 原因是该实现与特定的构建器设计有关。
可以使用下列其中一种方法来显式地调用构建器:
实际上,工作台用户通过在资源导航器菜单中选择相应的命令来触发构建。
在自动构建期间,平台也会隐式地调用增量式项目构建器。如果启用的话,每当更改工作区时,就会运行自动构建。
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 {
// 在此处添加构建逻辑
return null;
}
protected void startupOnInitialize() {
// 在此处添加构建器初始逻辑
}
}
构建处理从 build() 方法开始,该方法包含有关已请求的构建种类的信息: FULL_BUILD、INCREMENTAL_BUILD 或 AUTO_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) {
//构建指定资源。
//返回“true”以继续访问子代。
return true;
}
}
访问过程将继续,直到遍历了整个资源树为止。
执行增量式构建时,您将使用资源更改变化而不是整个项目。
protected void incrementalBuild(IResourceDelta delta,
IProgressMonitor monitor) throws CoreException {
// 访问器起作用。
delta.accept(new MyBuildDeltaVisitor());
}
注意,对于增量式构建,构建访问器将使用资源变化树而不是整个资源树。
访问过程将继续,直到遍历了整个资源变化树为止。更改的特定性质类似于实现资源更改侦听器中所描述的那样。他们的一个重要的区别是,对于增量式项目构建器, 通常使用基于特定项目的资源变化,而不是整个工作区。
为了让构建器可供给定项目使用,必须将它包含在该项目的构建规范中。项目的构建规范是在构建项目时要按顺序运行的命令的列表。每个命令命名一个增量式项目构建器。
以下代码片段添加新的构建器来作为现有构建器列表中的第一个构建器。
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) {
//将构建器添加至项目
ICommand command = desc.newCommand();
command.setBuilderName(BUILDER_ID);
ICommand[] newCommands = new ICommand[commands.length + 1];
// 将它添加在其他构建器之前。
System.arraycopy(commands, 0, newCommands, 1, commands.length);
newCommands[0] = command;
desc.setBuildSpec(newCommands);
project.setDescription(desc, null);
}
通常只在创建项目时配置一次项目的构建器。