Я пытаюсь разработать плагин 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]
Я не знаю, что я делаю неправильно. Я уверен, что это просто простая вещь, которую я забываю. Кто-нибудь знает, что это может быть?
Я понял, что пошло не так. Это было сочетание плохих навыков копирования/вставки и того факта, что 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.SourceSet
(и org.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
}
Мне удалось решить это, опубликую ответ позже.