With Groovy we can configure the compiler for our source files just like we can configure the Groovy compilation unit when we use GroovyShell
to execute scripts. We can for example add annotations to source files, before they are compiled, without adding them to the actual source code ourselves. Suppose we want to apply the TypeChecked
or CompileStatic
AST transformation annotation to all source files in our project. We only have to write a configuration file and specify the name of the configuration file with the --configscript
option of the Groovy compiler. If we use Gradle to build our Groovy project we can also customise the GroovyCompile
task to set the configuration file.
The configuration file has an implicit object with the name configuration
of type CompilerConfiguration
. Also there is a builder syntax available via the CompilerCustomizationBuilder
class. Let's look at both ways to define our custom configuration. We want to add the CompileStatic
annotation to all classes, together with the ToString
AST transformation annotation. Next we also want to add the package java.time
as implicit import for our source files. This means we don't have to write an import
statement in our code to include classes from this package. Finally we add a ExpressionChecker
that will fail the compilation of our project if a variable name is only 1 character. We assume we use Gradle to build our project and we place the file groovycConfig.groovy
in the directory src/groovyCompile
. We must not name the file configuration.groovy
, because there is already a variable with the name configuration
in the script and this will confuse the compiler.
// File: src/groovyCompile/groovycConfig.groovy import groovy.transform.CompileStatic import groovy.transform.ToString import org.codehaus.groovy.ast.expr.VariableExpression import org.codehaus.groovy.control.customizers.SecureASTCustomizer.ExpressionChecker import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer import org.codehaus.groovy.control.customizers.ImportCustomizer import org.codehaus.groovy.control.customizers.SecureASTCustomizer // Add the AST annotation @CompileStatic // and @ToString to all classes. configuration.addCompilationCustomizers( new ASTTransformationCustomizer(CompileStatic)) configuration.addCompilationCustomizers( new ASTTransformationCustomizer(ToString)) // Add implicit import for all classes // for the package java.time. def imports = new ImportCustomizer() imports.addStarImports('java.time') configuration.addCompilationCustomizers(imports) // Define expression checker to deny // usage of variable names with length of 1. def smallVariableNames = { expr -> if (expr instanceof VariableExpression) { expr.variable.size() > 1 } else { true } } as ExpressionChecker def secureAstCustomizer = new SecureASTCustomizer() secureAstCustomizer.addExpressionCheckers(smallVariableNames) configuration.addCompilationCustomizers(secureAstCustomizer)
With the builder syntax of CompilerCustomizatioBuilder
we also have the flexibility to pass parameters to the AST transformations we want to apply. We configure the ToString
annotation to include the names of the properties in the generated toString
method implementation:
// File: src/groovyCompile/groovycConfig.groovy import org.codehaus.groovy.ast.expr.VariableExpression import org.codehaus.groovy.control.customizers.SecureASTCustomizer.ExpressionChecker // Using CompilerCustomizationBuilder.withConfig // method, where the class // CompilerCustomizationBuilder is implicitly // imported for this script. withConfig(configuration) { ast(groovy.transform.CompileStatic) // Define includeNames parameter for ToString // AST annotation. ast(includeNames: true, groovy.transform.ToString) imports { star('java.time') } // Define expression checker to deny // usage of variable names with length of 1. def smallVariableNames = { expr -> if (expr instanceof VariableExpression) { expr.variable.size() > 1 } else { true } } as ExpressionChecker secureAst { addExpressionCheckers smallVariableNames } }
Next we configure the GroovyCompile
task compileGroovy
in our Gradle project to use our configuration file when the code is compiled. We do this via the property groovyOptions
of the compile task:
// File: build.gradle plugins { id "groovy" } repositories { jcenter() } dependencies { compile "org.codehaus.groovy:groovy-all:2.4.5" testCompile "org.spockframework:spock-core:1.0-groovy-2.4" } // Add the configuration script file // to the compiler options. compileGroovy.groovyOptions.configurationScript = file('src/groovyCompile/groovycConfig.groovy')
Andre Steingress wrote a good blog post about the Groovy compiler configuration script.
Written with Groovy 2.4.5.