基于插件配置条件创建任务,是否必须用project.afterEvaluate?
背景
我正在编写一个Gradle插件,需要根据配置动态创建数量可变的任务。以下是简化后的代码:
配置扩展类
public class TypedMaterialsExtension{ private Property<Boolean> createTheTask; public TypedMaterialsExtension(Project project) { createTheTask = project.getObjects().property(Boolean.class); createTheTask.set(false); } public Property<Boolean> getCreateTheTask() { return createTheTask; } }
项目中的配置使用
typedMaterials{ createTheTask = true }
插件实现代码
public class TypedMaterialsPlugin implements Plugin<Project> { public void apply(Project project) { TypedMaterialsExtension extension = project.getExtensions().create("typedMaterials", TypedMaterialsExtension.class, project); // 这里的beforeTask无法成功注册 if(extension.getCreateTheTask().get()){ project.getTasks().register("beforeTask", TypedJarMaterials.class, task -> {}); project.getTasks().named("compileJava").configure(compileJava -> { compileJava.dependsOn("beforeTask"); }); } // 这里的afterTask可以成功注册 project.afterEvaluate(p -> { if(extension.getCreateTheTask().get()){ project.getTasks().register("afterTask", TypedJarMaterials.class, task -> {}); project.getTasks().named("compileJava").configure(compileJava -> { compileJava.dependsOn("afterTask"); }); } }); } }
实际运行时,afterTask能正常注册,但beforeTask无法被创建。
问题
我发现只有在project.afterEvaluate的Lambda中才能正确读取到用户配置的值。虽然可以把所有逻辑都放到afterEvaluate块里,但我知道这属于不良实践。那么,基于配置条件创建任务的正确做法是什么?
解决方案
问题根源
在Gradle的执行流程中,插件的apply方法会在项目配置阶段早期执行,此时用户在build.gradle中编写的typedMaterials{}配置代码还未运行,所以直接调用extension.getCreateTheTask().get()拿到的是扩展类中设置的默认值false,自然不会创建beforeTask。而afterEvaluate是在所有项目配置完成后才执行,此时能正确获取到用户设置的true。
正确实现方式
Gradle推荐利用延迟配置API和Property的响应式特性来避免直接读取配置值,从而不需要依赖afterEvaluate。
方式1:监听配置值变化
通过Property的whenTrue方法监听值的变化,当配置值变为true时,执行任务创建逻辑:
public class TypedMaterialsPlugin implements Plugin<Project> { public void apply(Project project) { TypedMaterialsExtension extension = project.getExtensions().create("typedMaterials", TypedMaterialsExtension.class, project); // 监听createTheTask的值变为true时的回调 extension.getCreateTheTask().whenTrue(shouldCreate -> { project.getTasks().register("beforeTask", TypedJarMaterials.class, task -> {}); project.getTasks().named("compileJava").configure(compileJava -> { compileJava.dependsOn("beforeTask"); }); }); } }
这种方式会在用户配置createTheTask = true后立即触发任务创建,完全符合Gradle的配置阶段执行逻辑。
方式2:利用Provider延迟绑定
如果需要更灵活的条件控制,可以使用Provider的map方法将配置值转换为任务创建逻辑,或者结合tasks.register的惰性特性:
public class TypedMaterialsPlugin implements Plugin<Project> { public void apply(Project project) { TypedMaterialsExtension extension = project.getExtensions().create("typedMaterials", TypedMaterialsExtension.class, project); Provider<Boolean> shouldCreateTask = extension.getCreateTheTask(); // 注册任务,但仅当配置值为true时才实际初始化任务 TaskProvider<TypedJarMaterials> beforeTask = project.getTasks().register("beforeTask", TypedJarMaterials.class, task -> {}); // 根据配置值控制任务是否启用,以及是否添加依赖 beforeTask.configure(task -> { task.setEnabled(shouldCreateTask.get()); }); project.getTasks().named("compileJava").configure(compileJava -> { // 仅当配置值为true时,添加依赖 shouldCreateTask.map(shouldCreate -> shouldCreate ? beforeTask : null).ifPresent(compileJava::dependsOn); }); } }
这种方式下任务会被注册,但只有当配置值为true时才会启用并添加依赖,适合需要保留任务存在但控制执行的场景。
注意事项
- 避免直接在
apply阶段调用Property.get(),这会强制立即解析配置值,导致无法获取用户的后续配置。 - 优先使用Gradle的响应式API(如
whenTrue、map、ifPresent)来实现延迟逻辑,这是Gradle官方推荐的最佳实践。
内容的提问来源于stack exchange,提问作者Richard Tingle

