共用建置服務允許任務分享狀態或資源。例如,任務可以分享預先計算值的快取,或使用 Web 服務或資料庫實例。

建置服務是一個物件,用於保存任務使用的狀態。它提供了一種替代機制,用於掛鉤到 Gradle 建置並接收關於任務執行和操作完成的資訊。

建置服務是組態可快取的。

Gradle 管理服務生命週期,僅在需要時建立服務實例,並在不再需要時清理它。 Gradle 也可以協調對建置服務的存取,確保並行使用該服務的任務數量不超過指定數量。

實作建置服務

要實作建置服務,請建立一個實作 BuildService 的抽象類別。然後,定義您希望任務在此類型上使用的方法。

abstract class BaseCountingService implements BuildService<CountingParams>, AutoCloseable {

}

建置服務實作被視為自訂 Gradle 類型,並且可以使用自訂 Gradle 類型可用的任何功能。

建置服務可以選擇性地接受參數,Gradle 在建立服務實例時將這些參數注入到服務實例中。要提供參數,您需要定義一個保存參數的抽象類別(或介面)。參數類型必須實作(或擴展)BuildServiceParameters。服務實作可以使用 this.getParameters() 訪問參數。參數類型也是自訂 Gradle 類型

當建置服務不需要任何參數時,您可以使用 BuildServiceParameters.None 作為參數的類型。

interface CountingParams extends BuildServiceParameters {
    Property<Integer> getInitial()
}

建置服務實作也可以選擇性地實作 AutoCloseable,在這種情況下,當 Gradle 丟棄服務實例時,將呼叫建置服務實例的 close() 方法。這發生在最後一個使用建置服務的任務完成與建置結束之間的某個時間。

以下是一個服務範例,該服務接受參數並且可關閉

WebServer.java
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.services.BuildService;
import org.gradle.api.services.BuildServiceParameters;

import java.net.URI;
import java.net.URISyntaxException;

public abstract class WebServer implements BuildService<WebServer.Params>, AutoCloseable {

    // Some parameters for the web server
    interface Params extends BuildServiceParameters {
        Property<Integer> getPort();

        DirectoryProperty getResources();
    }

    private final URI uri;

    public WebServer() throws URISyntaxException {
        // Use the parameters
        int port = getParameters().getPort().get();
        uri = new URI(String.format("https://127.0.0.1:%d/", port));

        // Start the server ...

        System.out.println(String.format("Server is running at %s", uri));
    }

    // A public method for tasks to use
    public URI getUri() {
        return uri;
    }

    @Override
    public void close() {
        // Stop the server ...
    }
}

請注意,您不應實作 BuildService.getParameters() 方法,因為 Gradle 將提供此方法的實作。

建置服務實作必須是執行緒安全的,因為它可能會被多個任務並行使用。

註冊建置服務並將其連接到任務

要建立建置服務,您可以使用 BuildServiceRegistry.registerIfAbsent() 方法註冊服務實例。

註冊服務不會建立服務實例。這會在任務首次使用該服務時依需求發生。如果在建置期間沒有任務使用該服務,則不會建立服務實例。

目前,建置服務的作用域限定於建置,而不是專案,這些服務可供所有專案的任務共用。您可以透過 Project.getGradle().getSharedServices() 訪問共用建置服務的登錄檔。

註冊建置服務以透過 @ServiceReference 任務屬性使用

以下是一個外掛範例,當使用該服務的任務屬性使用 @ServiceReference 註解時,該外掛會註冊先前的服務

DownloadPlugin.java
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.provider.Provider;

public class DownloadPlugin implements Plugin<Project> {
    public void apply(Project project) {
        // Register the service
        project.getGradle().getSharedServices().registerIfAbsent("web", WebServer.class, spec -> {
            // Provide some parameters
            spec.getParameters().getPort().set(5005);
        });

        project.getTasks().register("download", Download.class, task -> {
            task.getOutputFile().set(project.getLayout().getBuildDirectory().file("result.zip"));
        });
    }
}

如您所見,無需將 registerIfAbsent() 返回的建置服務提供者指派給任務,服務會自動注入到所有使用 @ServiceReference 註解的相符屬性中。

以下是一個任務範例,該任務透過使用 @ServiceReference 註解的屬性來使用先前的服務

Download.java
import org.gradle.api.DefaultTask;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.services.ServiceReference;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;

import java.net.URI;

public abstract class Download extends DefaultTask {
    // This property provides access to the service instance
    @ServiceReference("web")
    abstract Property<WebServer> getServer();

    @OutputFile
    abstract RegularFileProperty getOutputFile();

    @TaskAction
    public void download() {
        // Use the server to download a file
        WebServer server = getServer().get();
        URI uri = server.getUri().resolve("somefile.zip");
        System.out.println(String.format("Downloading %s", uri));
    }
}

已註冊的建置服務與服務參考屬性的自動匹配是透過類型和(可選地)名稱(對於宣告它們期望的服務名稱的屬性)完成的。如果多個服務與請求的服務類型匹配(即,為同一類型註冊了多個服務,並且在 @ServiceReference 註解中未提供服務名稱),您還需要手動將共用建置服務提供者指派給任務屬性。

請繼續閱讀以比較當使用該服務的任務屬性改為使用 @Internal 註解時的情況。

註冊建置服務以透過 @Internal 任務屬性使用

DownloadPlugin.java
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.provider.Provider;

public class DownloadPlugin implements Plugin<Project> {
    public void apply(Project project) {
        // Register the service
        Provider<WebServer> serviceProvider = project.getGradle().getSharedServices().registerIfAbsent("web", WebServer.class, spec -> {
            // Provide some parameters
            spec.getParameters().getPort().set(5005);
        });

        project.getTasks().register("download", Download.class, task -> {
            // Connect the service provider to the task
            task.getServer().set(serviceProvider);
            // Declare the association between the task and the service
            task.usesService(serviceProvider);
            task.getOutputFile().set(project.getLayout().getBuildDirectory().file("result.zip"));
        });
    }
}

在這種情況下,外掛會註冊服務並接收返回的 Provider<WebService>。此提供者可以連接到任務屬性以將服務傳遞給任務。請注意,對於使用 @Internal 註解的任務屬性,任務屬性需要 (1) 使用註冊期間獲得的提供者明確指派,並且 (2) 您必須透過 Task.usesService 告知 Gradle 任務使用該服務。當使用該服務的任務屬性使用 @ServiceReference 註解時,則不需要任何這些操作。

以下是一個任務範例,該任務透過使用 @Internal 註解的屬性來使用先前的服務

Download.java
import org.gradle.api.DefaultTask;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;

import java.net.URI;

public abstract class Download extends DefaultTask {
    // This property provides access to the service instance
    @Internal
    abstract Property<WebServer> getServer();

    @OutputFile
    abstract RegularFileProperty getOutputFile();

    @TaskAction
    public void download() {
        // Use the server to download a file
        WebServer server = getServer().get();
        URI uri = server.getUri().resolve("somefile.zip");
        System.out.println(String.format("Downloading %s", uri));
    }
}

請注意,目前不支援將服務與 @ServiceReference@Internal 以外的任何註解一起使用。例如,目前無法將服務標記為任務的輸入。

從組態動作中使用共用建置服務

通常,建置服務旨在供任務使用,並且由於它們通常表示一些可能昂貴的建立狀態,因此您應避免在組態時使用它們。但是,有時在組態時使用服務可能是有意義的。這是可能的;請呼叫提供者上的 get()

使用 Worker API 的建置服務

除了從任務中使用建置服務之外,您還可以從 Worker API 動作產出轉換 或另一個建置服務中使用建置服務。為此,請以與將其他參數傳遞給動作或服務相同的方式,將建置服務 Provider 作為使用動作或服務的參數傳遞。

例如,要將 MyServiceType 服務傳遞給 Worker API 動作,您可以將 Property<MyServiceType> 類型的屬性新增至動作的參數物件,然後將您在註冊服務時收到的 Provider<MyServiceType> 連接到此屬性

Download.java
import org.gradle.api.DefaultTask;
import org.gradle.api.provider.Property;
import org.gradle.api.services.ServiceReference;
import org.gradle.api.tasks.TaskAction;
import org.gradle.workers.WorkAction;
import org.gradle.workers.WorkParameters;
import org.gradle.workers.WorkQueue;
import org.gradle.workers.WorkerExecutor;

import javax.inject.Inject;
import java.net.URI;

public abstract class Download extends DefaultTask {

    public static abstract class DownloadWorkAction implements WorkAction<DownloadWorkAction.Parameters> {
        interface Parameters extends WorkParameters {
            // This property provides access to the service instance from the work action
            abstract Property<WebServer> getServer();
        }

        @Override
        public void execute() {
            // Use the server to download a file
            WebServer server = getParameters().getServer().get();
            URI uri = server.getUri().resolve("somefile.zip");
            System.out.println(String.format("Downloading %s", uri));
        }
    }

    @Inject
    abstract public WorkerExecutor getWorkerExecutor();

    // This property provides access to the service instance from the task
    @ServiceReference("web")
    abstract Property<WebServer> getServer();

    @TaskAction
    public void download() {
        WorkQueue workQueue = getWorkerExecutor().noIsolation();
        workQueue.submit(DownloadWorkAction.class, parameter -> {
            parameter.getServer().set(getServer());
        });
    }
}

目前,無法將建置服務與使用 ClassLoader 或程序隔離模式的 worker API 動作一起使用。

並行存取建置服務

您可以在註冊服務時使用 BuildServiceSpec.getMaxParallelUsages() 返回的 Property 物件來限制並行執行。當此屬性沒有值(這是預設值)時,Gradle 不會限制對服務的存取。當此屬性的值 > 0 時,Gradle 將允許不多於指定數量的任務並行使用該服務。

當使用服務的任務屬性使用 @Internal 註解時,為了使約束生效,建置服務必須透過 Task.usesService 註冊到使用任務。注意:目前,Gradle 無法發現服務的間接使用(例如,如果額外服務僅由任務直接使用的服務使用)。作為一種解決方法,可以透過將 @ServiceReference 屬性新增到任務並指派僅間接使用的服務(使其成為直接參考),或調用 Task.usesService,向 Gradle 明確宣告間接使用。

接收關於任務執行的資訊

建置服務可用於在執行任務時接收事件。為此,請建立並註冊一個實作 OperationCompletionListener 的建置服務

TaskEventsService.java
import org.gradle.api.services.BuildService;
import org.gradle.api.services.BuildServiceParameters;
import org.gradle.tooling.events.FinishEvent;
import org.gradle.tooling.events.OperationCompletionListener;
import org.gradle.tooling.events.task.TaskFinishEvent;

public abstract class TaskEventsService implements BuildService<BuildServiceParameters.None>,
    OperationCompletionListener { (1)

    @Override
    public void onFinish(FinishEvent finishEvent) {
        if (finishEvent instanceof TaskFinishEvent) { (2)
            // Handle task finish event...
        }
    }
}
1 實作 OperationCompletionListener 介面和 BuildService 介面。
2 檢查完成事件是否為 TaskFinishEvent

然後,在外掛中,您可以使用 BuildEventsListenerRegistry 服務上的方法開始接收事件

TaskEventsPlugin.java
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.provider.Provider;
import org.gradle.build.event.BuildEventsListenerRegistry;

import javax.inject.Inject;

public abstract class TaskEventsPlugin implements Plugin<Project> {
    @Inject
    public abstract BuildEventsListenerRegistry getEventsListenerRegistry(); (1)

    @Override
    public void apply(Project project) {
        Provider<TaskEventsService> serviceProvider =
            project.getGradle().getSharedServices().registerIfAbsent(
                "taskEvents", TaskEventsService.class, spec -> {}); (2)

        getEventsListenerRegistry().onTaskCompletion(serviceProvider); (3)
    }
}
1 使用 服務注入 來獲取 BuildEventsListenerRegistry 的實例。
2 照常註冊建置服務。
3 使用服務 Provider 訂閱建置服務以建置事件。