Ant hooks using Groovy via XML transform

This time the Ant Hook scripts using Groovy is implemented by transforming the target Ant build script. (old post in draft status)

In the post, Ant Hooks using Groovy Script via Scriptdef, we used the Ant BuildListener interface to add a hooks feature that invokes a Groovy script mapped to build events. Then in the last post Ant hooks using Groovy, continued we added the ability to skip a target execution.

The problem with the former implementations is that the target Ant script must be modified to take advantage of hooks. Using XMLTask, we can modify the ant script directly. The InsertHooks.groovy script reads the hooks.inix file and transforms the build.xml to build-hooked.xml. The build-hooked.xml file will have an Ant build listener set to the Hook.groovy script.

The scripts are not general purpose, of course. Just a proof of concept thing.

Approach

hooks.inix:

[>hook/root/compile?when=after,skip=false]
	println " hook: root,{target=${event.target.name},when=post,event=$event}"
[<]

[>hook/demo1/deploy?when=before,skip=true]
	println "  hook: {project=${event.project.name},target=${event.target.name},when=pre,event=$event}"
[<]

[>hook/demo1/compile?when=before,skip=false]
	println "  hook: {project=${event.project.name},target=${event.target.name},when=pre,event=$event}"
[<]

[>fragment]
	<path id="libs">    
		<fileset dir="lib">
            <include 
                name="groovy-all-2.2.1.jar" />
        </fileset> 
    	
    	<pathelement location="src/main/groovy"/>
	</path>    
     
    <!-- Groovy library -->
    <taskdef name="groovy"
       classname="org.codehaus.groovy.ant.Groovy"
       classpathref="libs"/> 
    
    <!-- sets a BuildListener to the project -->
    <scriptdef name="set-listener" 
        language="Groovy"
        classpathref="libs" 
        src="src/main/groovy/com/octodecillion/util/ant/Hook.groovy"> 
    	<attribute name="path"/>
    </scriptdef>
     
    <!-- install the listener -->
    <set-listener path="hooks.inix"/>   
     
[<fragment]

InsertHooks.groovy

package com.octodecillion.util.ant

import static com.octodecillion.util.inix.Inix.EventType.END

import com.octodecillion.util.inix.Inix

import org.apache.tools.ant.*

import static groovy.io.FileType.FILES

/**
 * Insert the XML into Ant script to enable hooks.
 *   
 * @author josef betancourt
 */
class InsertHooks{

	def ant
	def DEBUG = false
	static final String srcFilePath='build.xml'
	static final String destFilePath="build-hooked.xml"
	static final String INIXFILE = 'hooks.inix'	
	static final String XMLTASK = 'com.oopsconsultancy.xmltask.ant.XmlTask'
	
	static main(args){
		new InsertHooks().execute()
	}
	
	/** An Ant task entry point */
	public void execute() throws BuildException{
		def ant = new AntBuilder()
		
		try {
			
			def fragment = loadFragment(INIXFILE)
			if(!fragment){
				throw new BuildException("'fragment' from $INIXFILE is invalid")
			}		
			
			def engine = new groovy.text.SimpleTemplateEngine()
			def template = engine.createTemplate(fragment)			 
			def xml = template.make([hookFilePath:INIXFILE])
			  
			ant.path(id: "path") {
				fileset(dir: 'lib') {
				   include(name: "**/xml*.jar")
				}
			}
	 
			ant.taskdef(name:'xmltask',classname:
				XMLTASK,
				classpathref: 'path')
			 
			def xpath = '(//target)[1]' 
			ant.xmltask(source:srcFilePath,dest:destFilePath, 
				expandEntityReferences:false,report:false){
				insert(position:"before",path:xpath,xml:xml)				 
			}
				 
			new File(destFilePath).eachLine{
				println it
			}
			
		} catch (Exception e) {
			e.printStackTrace()
			throw new BuildException(e.getMessage(), e)
		}
	}
	
	def loadFragment(String path){
		def text = ''
		def inix = new Inix(path)
		def theEvent = inix.next()
		 
		while(theEvent && theEvent != Inix.EventType.END ){
			Inix.Event event = inix.getEvent()

			if(event.isSection("fragment")){
				text = event.text
				break
			}
			
			theEvent = inix.next()
		}
		
		return text
	}	
	
}

// end Script

Hook.groovy

package com.octodecillion.util.ant

import groovy.transform.TypeChecked;
import groovy.transform.TypeCheckingMode;

import java.util.List;
import java.util.Map;
import java.util.regex.Pattern

import com.octodecillion.util.inix.Inix

import org.apache.tools.ant.BuildEvent
import org.apache.tools.ant.BuildException
import org.apache.tools.ant.Project
import org.apache.tools.ant.SubBuildListener;

import static groovy.io.FileType.FILES

// wire in the listener
def path = binding.attributes.get('path')
if(!path){
	throw new BuildException("'path' to hook inix not set")
}

def listener = new HookListener(project,path)
listener.project = project
project.addBuildListener(listener)

// end wiring

/**
 * Ant build listener that invokes groovy hook scripts.
 *  
 * @author josef betancourt
 *
 */
//@TypeChecked
class HookListener implements SubBuildListener {
	Project project
	boolean DEBUG = false
	
	/**                          */
    def HookListener(Project project, String path){
		this.project = project
		loadInix(path)				
    }    
    
	/** load scripts in inix file */
	def loadInix(String path){
		debugln("load inix")
		def inix = new Inix()
		inix.reader = new BufferedReader(
			new FileReader(new File(path)))
		 
		def theEvent = inix.next()
		def found = false
		 
		while(theEvent && theEvent != Inix.EventType.END ){
			def event = inix.getEvent()

			if(isHook(event)){
				found = true
				def key = [event.path[2],((String)(event.params['when'])).
					toUpperCase()].join('/')
					
				String txt = event.text
				String skString = event.params['skip']
				boolean sk = (skString.compareTo('true')==0 ? true : false)
				debugln "key=$key, ${event.params['skip']}, skip=$sk"
					
				def node = new HookNode(txt, sk)
			
				def prj = event.path[1]				
				if(!hooks[prj]){
					hooks[prj] = [:]
				}	
				
				hooks[prj].put(key,node);			
			}
			
			theEvent = inix.next()
		}
		
		dumpHooks()		
		
	}

    /** invoked by Ant build */
	@Override
    public void targetStarted(BuildEvent event) {
		die("targetStarted invoked with null event", !event)
		invokeTargetHook(event, When.BEFORE)
    }
    
	/** invoked by Ant build */
    @Override
    public void targetFinished(BuildEvent event) {
		die("targetFinished invoked with null event", !event)
		invokeTargetHook(event, When.AFTER)
    }

    /** Invoke the target's hook script */
    def invokeTargetHook(BuildEvent event, When when){
        def b = new Binding()
        b.setProperty("event",event)
		b.setProperty("hook",this)
		
        def shell = new GroovyShell(b)
		
		def hookName = "${event.target.name}/$when"
		def pHook = hooks[event.project.name][hookName]
		def rHook = hooks['root'][hookName]	
		debugln("invokeTargetHook: $hookName\npHook:  $pHook\nrHook:  $rHook")
			
		boolean skipSet = false
		
		if(pHook){
			skipSet = pHook.skip	
			debugln("skipSet=$skipSet")
			shell.evaluate(pHook.text)
			
			if(!override && rHook){
				skipSet = skipSet ? skipSet : rHook.skip
				shell.evaluate(rHook.text)
			}			
			
		}else if(rHook){
			skipSet = rHook.skip			
			shell.evaluate(rHook.text)		
		}
		
		if( skipSet && (pHook || rHook) && (when == When.BEFORE) ){
			createSkipforTarget(event)
		}		
    } 

	/**   */
	private createSkipforTarget(BuildEvent event) {
		debugln "setting skip: ${event.target.name}_skipTarget"
		event.project.setProperty("${event.target.name}_skipTarget", "true")
		event.target.setUnless("${event.target.name}_skipTarget")
	}
	
	/** throw exception if flg is true */
	private die(Object msgObject, boolean flg){
		if(flg){
			throw new IllegalArgumentException(String.valueOf(msgObject))			
		}		
	}	
	
	@TypeChecked(TypeCheckingMode.SKIP)
	private isHook(ev){		
		ev.path && ev.path[0] == 'hook'		
	}
    
	private dumpHooks() {
		if(!DEBUG){
			return			
		}
		
		hooks.each{
			it.each{ node ->
				debugln(node)
			}
		}		
	}
	
	private debugln(Object msg){
		if(DEBUG){
			println(msg)
		}
	}
	
	String TARGETHOOK = "target"
	def override = true;
	
	enum When{
		BEFORE('before'),AFTER('after')
		String name
		
		When(s) {this.name = s}
	}
	
	private class HookNode {
		String text
		boolean skip
		public HookNode(String text, boolean skip){
			this.text = text
			this.skip = skip
		}
		
		def String toString() {return "s:$skip"};
	}
	
	Map<String, Map<String,HookNode>> hooks = [:]
	
    //@formatter:off
    @Override
    public void subBuildFinished(BuildEvent event) {}
    @Override
    public void subBuildStarted(BuildEvent event) {}
    @Override
    public void buildFinished(BuildEvent event) {}
    @Override
    public void buildStarted(BuildEvent event) {}
    @Override
    public void messageLogged(BuildEvent event) {}
    @Override
    public void taskFinished(BuildEvent event) {}
    @Override
    public void taskStarted(BuildEvent event) {}
	//@formatter:on
} // end class HookListener

// end Script

Further reading

Similar Posts:

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

One thought on “Ant hooks using Groovy via XML transform”

Leave a Reply

Your email address will not be published. Required fields are marked *