Tweaking the compiler configuration
Whether you are using groovyc to compile classes or a GroovyShell, for example, to execute scripts, under the hood, a compiler configuration is used. This configuration holds information like the source encoding or the classpath but it can also be used to perform more operations like adding imports by default, applying AST transformations transparently or disabling global AST transformations.
Before Groovy 1.8.0, doing tasks like adding imports transparently (for DSLs) was a bit complicated. It involved writing a custom GroovyClassLoader and lots of trickery. The goal of compilation customizers is to make those common tasks easy to implement. For that, the CompilerConfiguration class is the entry point. The general schema will always be based on the following code:
Compilation customizers must extend the org.codehaus.groovy.control.customizers.CompilationCustomizer class. A customizer works:
- on a specific compilation phase
- on every class node being compiled
You can implement your own compilation customizer but Groovy includes some of the most common operations.
The import customizer
Using this compilation customizer, your code will have imports added transparently. This is in particular useful for scripts implementing a DSL where you want to avoid users from having to write imports. The import customizer will let you add all the variants of imports the Groovy language allows, that is:
- class imports, optionally aliased
- star imports
- static imports, optionally aliased
- static star imports
The AST transformation customizer
The AST transformation customizer is meant to apply AST transformations transparently. Unlike global AST transformations that apply on every class beeing compiled as long as the transform is found on classpath (which has drawbacks like increasing the compilation time or side effects due to transformations applied where they should not), the customizer will allow you to selectively apply a transform only for specific scripts or classes.
As an example, let's say you want to be able to use @Log in a script. The problem is that @Log is normally applied on a class node and a script, by definition, doesn't require one. But implementation wise, scripts are classes, it's just that you cannot annotate this implicit class node with @Log. Using the AST customizer, you have a workaround to do it:
That's all! Internally, the @Log AST transformation is applied to every class node in the compilation unit. This means that it will be applied to the script, but also to classes defined within the script.
If the AST transformation that you are using accepts parameters, you can use parameters in the constructor too:
As the AST transformation customizers works with objects instead of AST nodes, not all values can be converted to AST transformation parameters. For example, primitive types are converted to ConstantExpression (that is 'LOGGER' is converted to new ConstantExpression('LOGGER'), but if your AST transformation takes a closure as an argument, then you have to give it a ClosureExpression, like in the following example:
This customizer will allow the developer of a DSL to restrict the grammar of the language, to prevent users from using some constructs, for example. It is only "secure" in that sense only and it is very important to understand that it does not replace a security manager. The only reason for it to exist is to limit the expressiveness of the language. This customizer only works at the AST (abstract syntax tree) level, not at runtime! It can be strange at first glance, but it makes much more sense if you think of Groovy as a platform to build DSLs. You may not want a user to have a complete language at hand. In the example below, we will demonstrate it using an example of language that only allows arithmetic operations, but this customizer allows you to:
- allow/disallow creation of closures
- allow/disallow imports
- allow/disallow package definition
- allow/disallow definition of methods
- restrict the receivers of method calls
- restrict the kind of AST expressions a user can use
- restrict the tokens (grammar-wise) a user can use
- restrict the types of the constants that can be used in code
For all those features, the secure AST customizer works using either a whitelist (list of elements that are allowed) or a blacklist (list of elements that are disallowed). For each type of feature (imports, tokens, ...) you have the choice to use either a whitelist or a blacklist, but you can mix whitelists and blacklists for distinct features. In general, you will choose whitelists (disallow all, allow selected).
If what the secure AST customizer provides out of the box isn't enough for your needs, before creating your own compilation customizer, you might be interested in the expression and statement checkers that the AST customizer supports. Basically, it allows you to add custom checks on the AST tree, on expressions (expression checkers) or statements (statement checkers). For this, you must implement org.codehaus.groovy.control.customizers.SecureASTCustomizer.StatementChecker or org.codehaus.groovy.control.customizers.SecureASTCustomizer.ExpressionChecker.
Those interfaces define a single method called isAuthorized, returning a boolean, and taking a Statement (or Expression) as a parameter. It allows you to perform complex logic over expressions or statements to tell if a user is allowed to do it or not. As an example, let's think of a DSL for which you want to make sure that users only call methods for which the name is in lowercase:
Here, we say that if the expression is a method call expression, then we can check the name and return true only if it's all lowercase. Otherwise, the expression is allowed.
This customizer, available since Groovy 2.1.0 only, is a bit special in the sense that it may be used as a filter for other customizers. The filter, in that case, is the org.codehaus.groovy.control.SourceUnit. For this, the source aware customizer takes another customizer as a delegate, and it will apply customization of that delegate only and only if predicates on the source unit match.
SourceUnit gives you access to interesting things, in particular the file being compiled (if compiling from a file, of course), which gives you the potential to perform operation based on the file name, for example. Here is how you would create a source aware customizer:
Then you can use predicates on the source aware customizer:
The customization builder
If you are using compilation customizers in Groovy code (like the examples above) and you are using Groovy 2.1+, then you can use an alternative syntax to customize the compilation. A builder org.codehaus.groovy.control.customizers.builder.CompilerCustomizationBuilder is available. Creating a customizer has never been so easy!
The code sample above shows how to use the builder. A static method, withConfig, takes a closure corresponding to the builder code, and automatically registers compilation customizers to the configuration. You can use:
For the AST customizer
Inlining a customizer
Inlined customizer allows you to write a compilation customizer directly, without having to create a dedicated class for it.
Of course, the builder allows you to define multiple customizers at once:
Configuring compilation using groovyc or the ant task
For now, we've shown you how to customize compilation using a CompilationConfiguration access, but this is only possible if you embed Groovy and that you create your own instances of CompilerConfiguration (be it with GroovyShell, GroovyScriptEngine, ...). But if you want it to be applied on the classes you compile (with groovyc, ant or gradle, for example), until Groovy 2.1.0, there was no way to do that.
Since Groovy 2.1.0 (and Groovy 2.1.1 for the groovy Ant task), it is possible to use a compilation flag named configscript that takes a groovy configration script as a parameter. This script gives you access to the CompilerConfiguration instance before the files are compiled (exposed as a variable named configuration), so that you can tweak it. It also transparently integrates the compiler configuration builder above.
Static compilation by default
Since static compilation has been released, many people asked for it to be enabled by default. For various reasons, including the fact that we think you should only limit static compilation to pieces of code where you have performance problems, we never included such a feature. Other people asked for default imports too. Since we didn't want to add lots of flags for each and every magic that Groovy can do, we decided to go for a configuration script. This means that having static compilation by default is just a matter of compiling classes using this configuration file. And the content is very easy:
You don't need to add an import for the builder, it's automatically added. Then, compile your files using the following command line:
We strongly recommand you to separate configuration files from classes, hence the src/main and src/conf directories above.