How to implement a Groovy global AST transformation in a Grails plugin?

后端 未结 1 1082
感情败类
感情败类 2021-01-06 12:10

I\'d like to modify some of my Grails domain classes at compilation time. I initially thought this was a job for Groovy\'s global ASTTransformation since I

相关标签:
1条回答
  • 2021-01-06 12:20

    The thing about global transforms the transform code should be available when the compilation starts. Having the transformer in a jar was what i did first! But as you said it is a sloppy job. What you want to do is have your ast transforming class compile before others gets to the compilation phase. Here is what you do!

    Preparing the transformer

    Create a directory called precompiled in src folder! and add the Transformation class and the classes (such as annotations) the transformer uses in this directory with the correct packaging structure.

    Then create a file called org.codehaus.groovy.transform.ASTTransformation in called precompiled/META-INF/services and you will have the following structure.

    precompiled
    --amanu
    ----LoggingASTTransformation.groovy
    --META-INF
    ----services
    ------org.codehaus.groovy.transform.ASTTransformation
    

    Then write the fully qualified name of the transformer in the org.codehaus.groovy.transform.ASTTransformation file, for the example above the fully qualified name would be amanu.LoggingASTTransformation

    Implementation

    package amanu 
    
    import org.codehaus.groovy.transform.GroovyASTTransformation 
    import org.codehaus.groovy.transform.ASTTransformation 
    import org.codehaus.groovy.control.CompilePhase 
    import org.codehaus.groovy.ast.ASTNode 
    import org.codehaus.groovy.control.SourceUnit 
    
    @GroovyASTTransformation(phase=CompilePhase.CANONICALIZATION) 
    class TeamDomainASTTransformation implements ASTTransformation{ 
    
       public void visit(ASTNode[] nodes, SourceUnit sourceUnit) { 
           println ("*********************** VISIT ************")
           source.getAST()?.getClasses()?.each { classNode -> 
              //Class node is a class that is contained in the file being compiled
              classNode.addProperty("filed", ClassNode.ACC_PUBLIC, new ClassNode(Class.forName("java.lang.String")), null, null, null)
           }
       } 
    } 
    

    Compilation

    After implementing this you can go off in two ways! The first approach is to put it in a jar, like you did! and the other is to use a groovy script to compile it before others. To do this in grails, we use _Events.groovy script.

    You can do this from a plugin or the main project, it doesn't matter. If it doesn't exist create a file called _Events.groovy and add the following content.

    The code is copied from reinhard-seiler.blogspot.com with modifications

    eventCompileStart = {target ->  
        ...
        compileAST(pluginBasedir, classesDirPath)  
        ...
      }  
      def compileAST(def srcBaseDir, def destDir) {  
       ant.sequential {  
          echo "Precompiling AST Transformations ..."  
          echo "src ${srcBaseDir} ${destDir}"  
          path id: "grails.compile.classpath", compileClasspath  
          def classpathId = "grails.compile.classpath"  
          mkdir dir: destDir  
          groovyc(destdir: destDir,  
              srcDir: "$srcBaseDir/src/precompiled",  
              classpathref: classpathId,  
              stacktrace: "yes",  
              encoding: "UTF-8")
         copy(toDir:"$destDir/META-INF"){
            fileset(dir:"$srcBaseDir/src/precompiled/META-INF")
         }  
          echo "done precompiling AST Transformations"  
        }  
    }  
    

    the previous script will compile the transformer before others are compiled! This enable the transformer to be available for transforming your domain classes.

    Don't forget

    If you use any class other than those added in your classpath, you will have to precompile those too. The above script will compile everything in the precompiled directory and you can also add classes that don't need ast, but are needed for it in that directory!

    If you want to use domain classes in transformation, You might want to do the precompilation in evenCompileEnd block! But this will make things slower!

    Update

    @Douglas Mendes mentioned there is a simple way to cause pre compilation. Which is more concise.

    eventCompileStart = { 
       target -> projectCompiler.srcDirectories.add(0, "./src/precompiled") 
    }
    
    0 讨论(0)
提交回复
热议问题