You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Gradle插件开发:无需afterEvaluate自动注册NamedDomainObjectContainer实例

问题描述

我正在开发Gradle插件,定义了如下扩展:

abstract class MyExtension {
    abstract val inputFiles: ConfigurableFileCollection
    val names: Provider<List<String>> by lazy {
        // 读取输入文件以获取名称列表
        inputFiles.elements.map { ... }
    }
    
    abstract val instances: NamedDomainObjectContainer<MyType>
}

我希望确保names提供的每个名称,若未在instances中注册则自动注册,逻辑如下:

names.get().forEach {
    if (!instances.names.contains(it)) {
        instances.register(it) {
            // 此处添加额外配置
        }
    }
}

已知可使用project.afterEvaluate(),但该方式不被推荐,请问有什么替代实现方案?

解决方案

1. 利用Provider.onComplete监听值计算完成

Gradle 6.0+ 提供的Provider.onComplete可以在Provider的值首次计算完成后触发逻辑,结合NamedDomainObjectContainer.maybeCreate(自动处理已存在实例的情况),代码更简洁安全:

abstract class MyExtension {
    abstract val inputFiles: ConfigurableFileCollection
    abstract val instances: NamedDomainObjectContainer<MyType>

    val names: Provider<List<String>> = inputFiles.elements.map { elements ->
        // 读取输入文件解析名称列表的逻辑
        elements.map { file -> /* 示例:file.nameWithoutExtension */ }
    }.also { provider ->
        provider.onComplete { namesResult ->
            namesResult.get().forEach { name ->
                instances.maybeCreate(name).apply {
                    // 此处添加额外配置逻辑
                    // 例如:someProperty.set("default-value")
                }
            }
        }
    }
}
  • onComplete仅在Provider的值成功计算后执行一次,避免重复触发。
  • maybeCreate会自动返回已注册的实例(如果存在),无需手动判断contains。

2. 绑定到任务生命周期(推荐用于任务依赖场景)

如果你的插件中有依赖instances的任务,可将注册逻辑放在任务的doFirst阶段,确保在任务执行前完成实例注册,符合Gradle的增量构建原则:

// 定义插件中的任务类型
abstract class MyTask : DefaultTask() {
    @get:Input
    abstract val names: Provider<List<String>>
    @get:Internal
    abstract val instances: NamedDomainObjectContainer<MyType>

    @TaskAction
    fun execute() {
        // 任务执行逻辑,使用instances
    }
}

// 在插件中注册任务时绑定逻辑
class MyPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        val myExtension = project.extensions.create<MyExtension>("myExtension")
        
        project.tasks.register<MyTask>("processInstances") {
            names = myExtension.names
            instances = myExtension.instances
            
            doFirst {
                names.get().forEach { name ->
                    instances.maybeCreate(name).apply {
                        // 额外配置
                    }
                }
            }
        }
    }
}

这种方式确保逻辑仅在任务需要执行时触发,不会提前消耗构建资源。

3. 使用ProviderListener监听值读取事件

如果需要在names的值被任何地方读取时同步更新instances,可以注册ProviderListener:

abstract class MyExtension {
    abstract val inputFiles: ConfigurableFileCollection
    abstract val instances: NamedDomainObjectContainer<MyType>
    private val project: Project // 需在扩展初始化时注入Project实例

    val names: Provider<List<String>> = inputFiles.elements.map { elements ->
        elements.map { /* 解析逻辑 */ }
    }.also { provider ->
        val listener = object : ProviderListener {
            override fun afterRead(provider: Provider<*>) {
                val currentNames = (provider as Provider<List<String>>).get()
                currentNames.forEach { name ->
                    instances.maybeCreate(name).apply {
                        // 额外配置
                    }
                }
            }
        }
        provider.addListener(listener)
        
        // 构建结束后移除监听器,避免内存泄漏
        project.gradle.buildFinished {
            provider.removeListener(listener)
        }
    }
}

注意:这种方式会在每次读取names值时触发,若names被多次读取需考虑重复执行的问题。

核心原则

  • 避免使用afterEvaluate:它会破坏Gradle的延迟配置特性,导致构建效率下降且难以调试。
  • 优先使用Gradle提供的延迟API(Provider、NamedDomainObjectContainer的标准方法),确保逻辑仅在必要时执行。
  • 用maybeCreate替代手动判断实例是否存在,简化代码同时保证一致性。

内容的提问来源于stack exchange,提问作者Kelvin Chung

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 11:05:15