数据流操作支持是一项 孵化中的功能,可能会有所更改。

在 Gradle 构建中执行工作的首选方式是使用任务。然而,有些类型的工作不太适合任务,例如自定义处理构建失败的情况。

如果你希望在构建成功时播放欢快的音乐,在构建失败时播放悲伤的音乐怎么办?这项工作必须处理任务执行结果,因此它本身不能是一个任务。

Dataflow Actions API 提供了一种调度这类工作的方式。数据流操作是一个参数化且独立的工作单元,一旦所有输入参数可用,它就可以执行。

实现数据流操作

第一步是实现操作本身。你必须创建一个实现 FlowAction 接口的类

import org.gradle.api.flow.FlowAction
import org.gradle.api.flow.FlowParameters

abstract class ReportConsumption : FlowAction<ReportConsumption.Params> {

    interface Params : FlowParameters {

    }

    override fun execute(parameters: Params) {

    }
}

必须实现 execute 方法,因为这是执行工作的地方。操作实现被视为一种自定义 Gradle 类型,可以使用自定义 Gradle 类型可用的任何功能。特别是,可以将一些 Gradle 服务注入到实现中。

数据流操作可以接受参数。要提供参数,你需要定义一个抽象类(或接口)来容纳参数

当操作不需要参数时,你可以使用 FlowParameters.None 作为参数类型。

下面是一个数据流操作示例,它将共享构建服务和文件路径作为参数

SoundPlay.java
package org.gradle.sample.sound;

import org.gradle.api.flow.FlowAction;
import org.gradle.api.flow.FlowParameters;
import org.gradle.api.provider.Property;
import org.gradle.api.services.ServiceReference;
import org.gradle.api.tasks.Input;

import java.io.File;

public abstract class SoundPlay implements FlowAction<SoundPlay.Parameters> {
    interface Parameters extends FlowParameters {
        @ServiceReference (1)
        Property<SoundService> getSoundService();

        @Input (2)
        Property<File> getMediaFile();
    }

    @Override
    public void execute(Parameters parameters) {
        parameters.getSoundService().get().playSoundFile(parameters.getMediaFile().get());
    }
}
1 参数类型中的参数必须使用注解。如果一个参数使用 @ServiceReference 注解,那么在创建操作时,根据通常的规则,会自动为该参数分配一个合适的共享构建服务实现。
2 所有其他参数都必须使用 @Input 注解。

使用生命周期事件 Provider

除了通常的值 Provider,Gradle 还为构建生命周期事件(如构建完成)提供了专用的 Provider。这些 Provider 用于数据流操作,并在用作输入时提供额外的顺序保证。如果你通过调用 mapflatMap 等方法从事件 Provider 派生出另一个 Provider,该顺序也同样适用。你可以从 FlowProviders 类获取这些 Provider。

flowProviders.buildWorkResult.map {
    [
        buildInvocationId: scopeIdsService.buildInvocationId,
        workspaceId: scopeIdsService.workspaceId,
        userId: scopeIdsService.userId
    ]
}
如果你不使用生命周期事件 Provider 作为数据流操作的输入,那么操作执行的确切时间是不确定的,并且在 Gradle 的下一个版本中可能会发生变化。

提供操作供执行

你不应该手动创建 FlowAction 对象。相反,你应该在 FlowScope 的适当范围内请求执行它们。这样做,你可以为任务配置参数

SoundFeedbackPlugin.java
package org.gradle.sample.sound;

import org.gradle.api.Plugin;
import org.gradle.api.flow.FlowProviders;
import org.gradle.api.flow.FlowScope;
import org.gradle.api.initialization.Settings;

import javax.inject.Inject;
import java.io.File;

public abstract class SoundFeedbackPlugin implements Plugin<Settings> {
    @Inject
    protected abstract FlowScope getFlowScope(); (1)

    @Inject
    protected abstract FlowProviders getFlowProviders(); (1)

    @Override
    public void apply(Settings settings) {
        final File soundsDir = new File(settings.getSettingsDir(), "sounds");
        getFlowScope().always( (2)
            SoundPlay.class,  (3)
            spec ->  (4)
                spec.getParameters().getMediaFile().set(
                    getFlowProviders().getBuildWorkResult().map(result -> (5)
                        new File(
                            soundsDir,
                            result.getFailure().isPresent() ? "sad-trombone.mp3" : "tada.mp3"
                        )
                    )
                )
        );
    }
}
1 使用服务注入来获取 FlowScopeFlowProviders 实例。它们可用于项目插件和设置插件。
2 使用适当的作用域来运行你的操作。顾名思义,位于 always 作用域的操作在每次构建运行时都会执行。
3 指定实现该操作的类。
4 使用 spec 参数来配置操作参数。
5 生命周期事件 Provider 可以映射到其他内容,同时保留操作顺序。

因此,当你运行构建并成功完成时,操作将播放“tada”声音。如果构建在配置或执行时失败,你将听到“sad-trombone”声音——前提是构建配置进行到足以注册操作。