Настройка пользовательского исходного набора Gradle с помощью замыкания

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

Я пытаюсь определить контейнер исходных наборов, подобно тому, как это делает плагин Java. Я хотел бы иметь возможность использовать закрытие при настройке исходного набора. Конкретно, я хотел бы иметь возможность сделать следующее:

sourceSets {
    main {
        sv {
            include '*.sv'
        }
    }
}

Я определил свой собственный класс sourceSet:

class SourceSet implements Named {
    final String name
    final ObjectFactory objectFactory

    @Inject
    SourceSet(String name, ObjectFactory objectFactory) {
        this.name = name
        this.objectFactory = objectFactory
    }

    SourceDirectorySet getSv() {
        SourceDirectorySet sv = objectFactory.sourceDirectorySet('sv',
            'SystemVerilog source')
        sv.srcDir("src/${name}/sv")
        return sv
    }

    SourceDirectorySet sv(@Nullable Closure configureClosure) {
        configure(configureClosure, getSv());
        return this;
    }
}

Я использую org.gradle.api.file.SourceDirectorySet, потому что он уже реализует PatternFilterable, поэтому он должен дать мне доступ к include, exclude и т. д.

Если я правильно понимаю концепцию, метод sv(@Nullable Closure configureClosure) дает мне возможность написать sv { ... } для настройки через замыкание.

Чтобы добавить свойство sourceSets в проект, я сделал следующее:

project.extensions.add("sourceSets",
        project.objects.domainObjectContainer(SourceSet.class))

Согласно документам Gradle, это должно дать мне возможность настроить sourceSets с помощью замыкания. На этом сайте, где подробно рассказывается об использовании пользовательских типов, говорится, что с помощью NamedDomainObjectContainer Gradle предоставит DSL, который скрипты сборки могут использовать для определения и настройки элементов. Это будет часть sourceSets { ... }. Это также должно быть частью sourceSets { main { ... } }.

Если я создам sourceSet для main и использую в задаче, то все работает нормально:

project.sourceSets.create('main')

task compile(type: Task) {
    println 'Compiling source files'
    println project.sourceSets.main.sv.files
}

Если я попытаюсь настроить исходный набор main так, чтобы он включал только файлы с расширением .sv, я получаю сообщение об ошибке:

sourceSets {
    main {
        sv {
            include '*.sv'
        }
    }
}

Я получаю следующую ошибку:

No signature of method: build_47mnuak4y5k86udjcp7v5dkwm.sourceSets() is applicable for argument types: (build_47mnuak4y5k86udjcp7v5dkwm$_run_closure1) values: [build_47mnuak4y5k86udjcp7v5dkwm$_run_closure1@effb286]

Я не знаю, что я делаю неправильно. Я уверен, что это просто простая вещь, которую я забываю. Кто-нибудь знает, что это может быть?

Мне удалось решить это, опубликую ответ позже.

Tudor Timi 25.12.2020 11:28
Gradle за прокси-сервером
Gradle за прокси-сервером
Создайте проект Gradle под сетевым прокси.
"DevOps: Jenkins & AWS Series, часть 5: Установка Gradle на Ubuntu 22.04
"DevOps: Jenkins & AWS Series, часть 5: Установка Gradle на Ubuntu 22.04
В этой статье блога мы проведем вас через процесс установки Gradle на Ubuntu 22.04, интеграции его с Jenkins и создания задания Gradle. Мы...
1
1
796
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я понял, что пошло не так. Это было сочетание плохих навыков копирования/вставки и того факта, что Groovy — динамический язык.

Во-первых, давайте снова посмотрим на определение функции sv(Closure):

SourceDirectorySet sv(@Nullable Closure configureClosure) {
    configure(configureClosure, getSv());
    return this;
}

Как только я переместил этот код в собственный файл Groovy и использовал IDE, чтобы показать мне, что вызывается, я заметил, что он вызывает не ту функцию, которую я ожидал. Я ждал звонка org.gradle.util.ConfigureUtil.configure. Поскольку это часть общедоступного API, я ожидал, что он будет импортирован по умолчанию в скрипте сборки. Как говорится на этой странице, это не так.

Для решения проблемы достаточно добавить следующий импорт:

import static org.gradle.util.ConfigureUtil.configure

Это избавит от ошибки, связанной с загадочным закрытием. Однако он заменяется следующей ошибкой:

Cannot cast object 'SourceSet_Decorated@a6abab9' with class 'SourceSet_Decorated' to class 'org.gradle.api.file.SourceDirectorySet'

Это вызвано ошибкой копирования/вставки, о которой я упоминал. Когда я писал класс SourceSet, я сильно опирался на org.gradle.api.tasks.SourceSetorg.gradle.api.internal.tasks.DefaultSourceSet). Если мы посмотрим на метод java(Closure), то увидим, что он имеет следующую подпись:

SourceSet java(@Nullable Closure configureClosure);

Обратите внимание, что он возвращает SourceSet, а не SourceDirectorySet, как в моем коде. Использование правильного типа возврата устраняет проблему:

SourceSet sv(@Nullable Closure configureClosure)

С этим новым типом возвращаемого значения давайте еще раз посмотрим на код конфигурации для исходного набора:

sourceSets {
    main {
        sv {
            include '*.sv'
        }
    }
}

Изначально я думал, что это должно работать следующим образом: передать main { ... } как Closure в sourceSets, передать sv { ... } как Closure в main и обработать часть include ... внутри sourceDirectorySet. Некоторое время я бился головой о стену, потому что не мог найти кода в этой иерархии классов, который использует замыкания, подобные этому.

Теперь я думаю, что поток немного отличается: передать main { ... } как Closure в sourceSets (как изначально предполагалось), но вызвать функцию sv(Closure) для main (типа sourceSet), передав ее { include ... } в качестве аргумента.


Бонус: была еще одна проблема, не связанная с ошибками «компиляции», которые у меня были.

Даже после того, как код заработал без ошибок, он по-прежнему вел себя не так, как ожидалось. У меня были файлы с расширением *.svh, которые все еще подхватывались. Это потому, что при вызове getSv() он каждый раз создавал новый SourceDirectorySet. Любая конфигурация, которая была сделана ранее, отбрасывалась каждый раз, когда вызывалась эта функция.

Создание sourceDirectorySet членом класса и перемещение его создания в конструктор устранило проблему:

private SourceDirectorySet sv

SourceSet(String name, ObjectFactory objectFactory) {
    // ...
    sv = objectFactory.sourceDirectorySet('sv',
            'SystemVerilog source')
    sv.srcDir("src/${name}/sv")
}

SourceDirectorySet getSv() {
    return sv
}

Другие вопросы по теме