Динамическая c зависимость задачи, основанная на значении свойства - PullRequest
1 голос
/ 17 февраля 2020

Мне нужно создать плагин, который бы предоставлял интерактивную задачу, вызываемую так:

> ./gradlew analyzeSomething --configuration compileClasspath

Здесь аргумент --configuration должен быть именем конфигурации в проекте, который используется для анализа.

Однако этот анализ требует некоторых данных, вычисленных из конфигурации, что может быть дорогостоящим. Поэтому я хотел бы кэшировать эти данные, но, естественно, таким образом, что это приведет к аннулированию кэша в случае изменения содержимого конфигурации (например, добавление зависимости пользователем). В идеале я бы воспользовался встроенными механизмами зависимостей задач Gradle, которые бы отслеживали зависимости задач и позволяли не запускать вычисления, если их входные данные не изменились.

Это решение, которое я придумал, но это не работает. Во-первых, объявите два свойства в классе AnalyzeSomething:

abstract class AnalyzeSomethingTask : DefaultTask() {
    @get:Internal
    @get:Option(option = "configuration", description = "Configuration")
    abstract val configuration: Property<String>

    @get:Internal
    abstract val dataCacheFile: RegularFileProperty
}

Во-вторых, создайте правило задачи для создания экземпляров задач, выполняющих дорогостоящие вычисления:

abstract class PrecomputeTask : DefaultTask() {
    @get:InputFiles
    abstract val classpath: ConfigurableFileCollection

    @get:OutputFile
    abstract val outputFile: RegularFileProperty
}

project.tasks.addRule("Pattern precompute<Configuration>") {
    val taskName = this
    if (taskName.startsWith("precompute")) {
        val configurationName = taskName.removePrefix("precompute").decapitalize()
        val configuration = project.configurations.getByName(configurationName)
        project.tasks.register<PrecomputeTask>(taskName) {
            classpath.from(configuration)
            outputFile.set(temporaryDir.resolve("data.txt"))
        }
    }
}

Наконец, создайте analyzeSomething task и в блоке project.afterEvalute (поэтому все свойства задачи уже заданы, в том числе в командной строке) задайте зависимость между analyzeSomething и одной из задач, созданных правилом:

val analyzeSomething = project.tasks.register<AnalyzeSomethingTask>("analyzeSomething")
project.afterEvaluate {
    analyzeSomething.configure {
        val precomputeTaskName = "precompute" + configuration.get().capitalize()
        dataCacheFile.set(
            project.tasks.named<PrecomputeTask>(precomputeTaskName)
                .flatMap { it.outputFile }
        )
    }
}

Этот подход, однако, не работает, потому что, очевидно, register() и любые другие методы конфигурации коллекции не могут быть вызваны в любой точке, в которой они вызываются при такой настройке:

> ./gradlew analyzeSomething --configuration runtimeClasspath
Caused by: org.gradle.api.internal.tasks.DefaultTaskContainer$TaskCreationException: Could not create task ':analyzeSomething'.
...
Caused by: org.gradle.api.internal.AbstractMutationGuard$IllegalMutationException: DefaultTaskContainer#create(String, Class, Action) on task set cannot be executed in the current context.

Так что мой вопрос есть ли способ установить зависимости задачи на основе имени свойства, и если нет, то какой будет лучший способ сделать то, что я хочу?

1 Ответ

0 голосов
/ 17 февраля 2020

Интересно, что этот подход сработает, как только я избавлюсь от project.afterEvaluate, когда некоторые поставщики средней сложности перетасовывают:

// instead of
val analyzeSomething = project.tasks.register<AnalyzeSomethingTask>("analyzeSomething")
project.afterEvaluate {
    analyzeSomething.configure {
        val precomputeTaskName = "precompute" + configuration.get().capitalize()
        dataCacheFile.set(
            project.tasks.named<PrecomputeTask>(precomputeTaskName)
                .flatMap { it.outputFile }
        )
    }
}

// do this

project.tasks.register<AnalyzeSomethingTask>("analyzeSomething") {
    dataCacheFile.set(
        configuration.flatMap { c ->
            val precomputeTaskName = "precompute" + c.capitalize()
            project.tasks.named<PrecomputeTask>(precomputeTaskName)
                .flatMap { it.outputFile }
        }
    )
}

Мне действительно любопытно, почему этот подход работает по сравнению с project.afterEvaluate - основанный на этом, но он действительно , кажется, работает правильно, поскольку это приводит к добавлению правильной задачи precompute* к графу задач и ее выводу, используемому для анализа.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...