Недавно я хотел как построить, так и применить локальные аст-преобразования в проекте Gradle. Хотя я мог найти несколько примеров написания преобразований, я не смог найти полный пример, показывающий весь процесс сборки. Преобразование должно быть скомпилировано отдельно, а затем помещено в путь к классам, поэтому его источник не может просто находиться в остальной части исходного кода Groovy. Это та деталь, которая на некоторое время сбила меня с толку.
Сначала я настроил отдельную задачу GroovyCompile для обработки аннотации перед остальным источником (вытекает из полезного предложения Питера Нидервизера на форумах Gradle). Хотя это сработало, гораздо более простым решением для применения преобразований является настройка многопроектной сборки. Основной проект зависит от подпроекта с исходными файлами преобразования ast. Вот минимальная примерная структура каталогов:
ast/build.gradle
Ast build file
ast/src/main/groovy/com/cholick/ast/Marker.groovy
интерфейс маркера
ast/src/main/groovy/com/cholick/ast/Transform.groovy
Аст трансформация
build.gradle
основной файл сборки
settings.gradle
конфигурация иерархии проекта
src/main/groovy/com/cholick/main/Main.groovy
источник для преобразования
Для полного рабочего источника (с простыми тестами и без * импорта), клон https://github.com/cholick/gradle_ast_example
Корневой файл build.gradle содержит зависимость от проекта ast:
1
2
3
4
|
dependencies { ... compile(project( ':ast' )) } |
Корень settings.gradle определяет подпроект ast:
1
|
include 'ast' |
Базовый проект также имеет src / main / groovy / com / cholick / main / Main.groovy, с исходным файлом для преобразования. В этом примере преобразование ast, которое я написал, помещает метод с именем ‘добавлен’ в класс.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
package com.cholick.main import com.cholick.ast.Marker @Marker class Main { static void main(String[] args) { new Main().run() } def run() { println 'Running main' assert this . class .declaredMethods.find { it.name == 'added' } added() } } |
В подпроекте ast ast / src / main / groovy / com / cholick / ast / Marker.groovy определяет интерфейс для маркировки классов для преобразования ast:
01
02
03
04
05
06
07
08
09
10
|
package com.cholick.ast import org.codehaus.groovy.transform.GroovyASTTransformationClass import java.lang.annotation.* @Retention (RetentionPolicy.SOURCE) @Target ([ElementType.TYPE]) @GroovyASTTransformationClass ([ 'com.cholick.ast.Transform' ]) public @interface Marker {} |
Наконец, класс преобразования ast обрабатывает исходные классы и добавляет метод:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
package com.cholick.ast import org.codehaus.groovy.ast.* import org.codehaus.groovy.ast.builder.AstBuilder import org.codehaus.groovy.control.* import org.codehaus.groovy.transform.* @GroovyASTTransformation (phase = CompilePhase.INSTRUCTION_SELECTION) class Transform implements ASTTransformation { void visit(ASTNode[] astNodes, SourceUnit sourceUnit) { if (!astNodes) return if (!astNodes[ 0 ]) return if (!astNodes[ 1 ]) return if (!(astNodes[ 0 ] instanceof AnnotationNode)) return if (astNodes[ 0 ].classNode?.name != Marker. class .name) return ClassNode annotatedClass = (ClassNode) astNodes[ 1 ] MethodNode newMethod = makeMethod(annotatedClass) annotatedClass.addMethod(newMethod) } MethodNode makeMethod(ClassNode source) { def ast = new AstBuilder().buildFromString(CompilePhase.INSTRUCTION_SELECTION, false , "def added() { println 'Added' }" ) return (MethodNode) ast[ 1 ].methods.find { it.name == 'added' } } } |
Спасибо Гамлету Д’Арси за отличный пример трансформации, а также Питеру Нидервизеру за ответ на мой вопрос на форумах.
Ссылка: | Использование Gradle для создания и применения преобразований AST от нашего партнера по JCG Мэтта Чолика в блоге Cholick.com . |