Run a List of Gradle Tasks In Specific Order

I recently set out to add a few “synonym tasks” to our gradle based build. This is something which is quite easy to do in the ant world, and it seemed like a concept that would be very easy to translate into gradle.

What’s a synonym task? Its a task that just runs a series of other tasks, in a specified order (it could also be called an “alias”). For example, instead of typing this long series of tasks:


gradlew clean assemble runProvisioner stopTomcat installTomcat deployToTomcat startTomcat

Its much more convenient to just type something like this (where “all” is an alias for the above list of tasks) :

gradlew all 

Now – if you explicitly tell Gradle which tasks to run via the command line (by typing them out each time), it will respect the ordering you provide. It will execute each of those tasks (along with each of their dependencies) in the order in which you specified via the command line.

Given that – you’d think it would be pretty simple to create an alias task which just calls those things in the expected order.

Not so.

This turned out to be a lot harder than it should have been (which is what drove me to write this, on the chance it might help someone else).

Why is defining a synonym task hard?

Gradle task execution order is typically dependency driven. Left to its own devices, Gradle likes to determine the order in which to run tasks based on a DAG (“Directed Acyclic Graph).

In Gradle, dependencies aren’t necessarily ordered.

Its seems that this has been a point of contention for a while. Many of the discussions I found reference GRADLE-427 (which was opened in 2009).

In recent versions of Gradle, they added a .mustRunAfter method which allows a task to say, “When this other task is going to be run anyway, I should run after it.” That was meant to address the 427 issue, but doesn’t really seem like a great solution for the synonym / alias use case. Here, another Gradle user laments the same problem, and one of the Gradle devs chimes in with a utility function that can provide the equivalent of ordered dependencies:

http://forums.gradle.org/gradle/topics/mustrunafter_not_a_usable_fix_fot_the_dependson_ordering_bug

My first solution to the build aliases problem was to solving it similarly to the way the above post suggests. I wasn’t happy with this solution (its a bit too heavy), but I’ll share it here, just for completeness :

void wireOrderedDependencies( task, others) {
    task.dependsOn(others)
    for (int i = 0; i < others.size() - 1; i++) {
       others[i+1].mustRunAfter(others[i])
    }
}

task all(description: 'an alias for :  clean assemble stopTomcat runProvisioner installTomcat deployToTomcat startTomcat') {
}

//--- set up the "all" target's dependencies IN ORDER in this List
def all_dependenciesInOrder = []

subprojects.each () { sp ->
   all_dependenciesInOrder.add sp.clean
}
all_dependenciesInOrder += [project(':myapp-provisioner').assemble, project(':myapp-web').assemble, stopTomcat, runProvisioner, installTomcat, deployToTomcat, startTomcat]

wireOrderedDependencies(all, all_dependenciesInOrder)

This initial solution seems like something that wouldn’t scale very well. For example, things would get very messy if we had 8-9 different aliases.

As I researched the issue a bit further, I found what seemed like a much cleaner suggestion referenced within one of the (many) comments on GRADLE-427. It was this suggestion.

That seemed like a much cleaner approach, and is the basis for what I ended up doing. Here’s the cleaner solution :

//--- build aliases : define a synonym here if you want a shortcut to run multiple targets

def buildAliases = [
   'all' : ['clean', 'assemble', 'runProvisioner', 'stopTomcat', 'installTomcat', 'deployToTomcat', 'startTomcat'],
   'rebuild' : ['clean', 'assemble']
]
def expandedTaskList = []

gradle.startParameter.taskNames.each {
   expandedTaskList << (buildAliases[it] ? buildAliases[it] : it)
}

gradle.startParameter.taskNames = expandedTaskList.flatten()

println "\n\n\texpanded task list: ${gradle.startParameter.taskNames }\n\n"

This solution works off the fact we discussed earlier: If you explicitly invoke Gradle from the command line with a specific list of tasks, it respects the supplied ordering for those tasks. gradle.startParameter.taskNames contains the list of tasks that the user supplied on the command line.

This solution simply iterates the user supplied list of task names, and if it finds one of these task names in the buildAliases map, it “expands” the task name to be the associated list of tasks. Much better.

Auto Create Quartz Tables at Startup

I recently needed to add Quartz support to a Spring project, along with a mechanism for automatically creating the Quartz tables at application startup. I did a bit of googling on how to get Quartz to auto-create its tables, but didn’t find anything. Here’s what I ended up doing to solve the problem.

First, lets consider the basic configuration of a Quartz Scheduler in Spring. If you’re planning on storing Jobs in a relational database, then your entry will look something like this :

   <bean id="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
      <property name="dataSource">
         <ref bean="dataSource"/>
      </property>
      <property name="transactionManager">
         <ref bean="transactionManager"/>
      </property>
      <property name="quartzProperties">
         <props>
            <prop key="org.quartz.jobStore.tablePrefix">QRTZ_</prop>
            <prop key="org.quartz.jobStore.selectWithLockSQL">SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?</prop>
         </props>
      </property>
   </bean>

At this point, you could start up the application, but you’d get JDBC errors when Quartz tries to access its tables. You could pop out to a SQL console and manually create these tables, but you’d be faced with the same problem each time you deployed to a new environment.

To remedy this, we’ll make use of Spring’s DataSourceInitializer class, which will run designated SQL scripts when deployed :

<!--
   This will execute SQL scripts to recreate the quartz tables at 
   appserver boot time. 
-->
<bean id="quartzDbInitializer" class="org.springframework.jdbc.datasource.init.DataSourceInitializer">
  <property name="dataSource" ref="dataSource"/>
  <property name="enabled" value="true"/>
  <property name="databasePopulator">
    <bean class="org.springframework.jdbc.datasource.init.ResourceDatabasePopulator">
      <property name="continueOnError" value="true"/>
      <property name="ignoreFailedDrops" value="true"/>
      <property name="sqlScriptEncoding" value="UTF-8"/>
      <property name="scripts">
        <array>
          <value type="org.springframework.core.io.Resource">
            classpath:META-INF/quartz/oracle/drop-quartz-tables.sql
          </value>
          <value type="org.springframework.core.io.Resource">
            classpath:META-INF/quartz/oracle/create-quartz-tables.sql
          </value>
        </array>
      </property>
    </bean>
  </property>
</bean>

The above will execute two sql scripts each time the application is started :

  • META-INF/quartz/oracle/drop-quartz-tables.sql
  • META-INF/quartz/oracle/create-quartz-tables.sql

Note – you’ll probably want to remove / comment out the drop-quartz-tables.sql eventually (it will drop your quartz tables each time you restart the application – ok for kicking off development, but not viable long term).

Just one more small thing left to do : we need to add a dependency between our quartzScheduler bean and the quartzDbInitializer bean (so that the quartzDbInitializer bean will be instantiated BEFORE the quartzScheduler bean). To do this, simply add a “depends-on” attribute to the quartzScheduler bean (as shown below) :

   <bean id="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" depends-on="quartzDbInitializer">
      <property name="dataSource">
         <ref bean="dataSource"/>
      </property>
      <property name="transactionManager">
         <ref bean="transactionManager"/>
      </property>
      <property name="quartzProperties">
         <props>
            <prop key="org.quartz.jobStore.tablePrefix">QRTZ_</prop>
            <prop key="org.quartz.jobStore.selectWithLockSQL">SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?</prop>
         </props>
      </property>
   </bean>

That’s it.

Launching Previously Run Webstart Apps Via The Java Control Panel

I’m a huge fan of Java WebStart.

Most people generally launch WebStart based apps by simply hitting a URL which pulls down a JNLP file. A JNLP file is simply an XML descriptor which describes the jars that make up the application, the main class, etc. Once the JNLP file is pulled in, Java takes over and will retrieve the specified jars and then launch the Java application.

To re-launch the app in the future, you can simply bookmark the original JNLP URL in your browser (note to MotioPI users – you can simply bookmark the original URL sent via email and do this with MotioPI).

But suppose you’re in disconnected mode (perhaps waiting in the airport, or on a plane). You can still launch previously executed WebStart based applications by using the Java Control Panel.

If you’re using Windows XP, you can get to this via Start | Control Panel, then double click on the “Java” icon.


Step 1 - Launch Java Control Panel

This will launch the Java Control Panel. From there, select the “View” button (bottom right).


Step 2 - Select the View Button

This will open the Java Cache Viewer. You should have an entry for each WebStart app that you’ve previously run (screenshot below):


Step 3 - Java Cache Viewer

From here, you can right click on any of the cached WebStart apps and do things such as :

  • Run the application
  • Create a shortcut on your desktop to launch the app (so it behaves like a typical client side app)
  • Show the JNLP File
  • Delete the App

An Easy Way to Test against Multiple Versions of IE…

Most web developers have felt the pain of having to test their webapp in N different browsers (and having to work around the quirks in each).

I just spent the better part of the afternoon testing some fluid resize logic in IE7, Chrome and Firefox. I got everything working between those three, but I knew IE6 was probably going to bite me. I only have IE7 installed locally, so I was kind of resigned to testing it later, from a separate machine.

Then a friend pointed me at the following utility which will let you run multiple prior versions of IE, side by side with your existing IE7. Its a breeze to install and works great. You can actually have IE6 and IE7 up at the same time (sweet!).

XP Tip: “right-click on file and send-to cygwin tail”

During a typical “development” day, there are many times when I want to “start a tail” on some arbitrary log file. Wouldn’t it be nice if you could just right-click on a file in Windows Explorer and start a tail on it…? Here’s how.

First, we create a simple 1 line batch file called “startTailOn.cmd”. The batch file will expect a single parameter (the path to the file).

      start "tail on %1" c:\dev\cygwin\bin\tail.exe -n 1000 -f %1

This batch file simply starts a new command prompt with a title of “tail on “, and then executes the cygwin tail command on the specified file.

Now we just need to add a shortcut to our batch file to our Windows “send to” menu (shown when you right click on a item in Windows Explorer). To add a new action to your “send to”, you just need to add a shortcut to the action in the SendTo folder in your home directory (e.g. mine is at C:\Documents and Settings\lhankins\SendTo).

So in keeping with our earlier example, we’ll add a shortcut here to our “startTailOn.cmd” file (as depicted below).

add shortcut on sendto

Note – you can rename the default shortcut name of “Shortcut to startTailOn.cmd” to just “tail”.

That’s it. Now suppose we right click on d:\temp\boot.log and select our new “send to tail” action :

tail on log

The only shortcoming here is that the batch file we created will barf if the path to the in question has spaces in it (the part that fails is the ‘window title’ argument to the ‘start’ command). I generally keep a second batch file on my SendTo menu to handle these cases, e.g. “startTailOnPathWithSpaces.cmd”.

   start "tail" c:\dev\cygwin\bin\tail.exe -f %1

More Readable Classpaths for Ant Builds…

Its always a bit painful when you hit the inevitable ClassNotFound exception during development and you end up double checking an Ant classpath variable against the jars you think should be there. The way I typically debug this is to assign the particular ant classpath to a property, then echo the property value, e.g. :

<property name="current.classpath" refid="dao.compile.classpath"/>
<echo level="info" message="current.classpath=${current.classpath}"/>

Which will end up producing hard to read output that looks something like this (a big, semicolon delimited list of jars) :

[echo] current.classpath=d:\work\proto\dist\foo-toolkit.jar;d:\work\proto\lib\hibernate-2.1.8\lib\dom4j-.4.jar;d:\work\proto\lib\commons-discovery-0.2\commons-discovery.jar;d:\work\proto\lib\hibernate-.1.8\hibernate2.jar;d:\work\proto\lib\hibernate-2.1.8\lib\c3p0-0.8.4.5.jar;d:\work\proto\lib\hibernate-2.1.8\lib\cglib-full-2.0.2.jar;d:\work\proto\lib\hibernate-2.1.8\lib\ehcache-0.9.jar;d:\work\proto\lib\hibernate-2.1.8\lib\jta.jar;d:\work\proto\lib\hibernate-2.1.8\lib\odmg-3.0.jar;d:\work\proto\lib\hibernate-2.1.8\lib\oscache-2.0.jar;d:\work\proto\lib\hibernate-2.1.8\lib\swarmcache-1.0rc2.jar;d:\work\proto\lib\oracle-10g\classes12-10g.jar;d:\work\proto\lib\jakarta-struts-1.2.4\lib\commons-beanutils.jar;d:\work\proto\lib\commons-lang-2.0\commons-lang.jar;d:\work\proto\build\dao\classes;d:\work\proto\lib\jakarta-struts-1.2.4\lib\commons-logging.jar;d:\work\proto\lib\commons-collections-3.1\commons-collections.jar;d:\work\proto\lib\commons-io-1.0\commons-io.jar;d:\work\proto\lib\commons-dbutils-1.0\commons-dbutils.jar;d:\work\proto\lib\spring-1.2\acegi-security-0.8.2.jar;d:\work\proto\lib\spring-1.2\commons-codec.jar;d:\work\proto\lib\spring-1.2\ehcache-1.1.jar;d:\work\proto\lib\spring-1.2\spring-aop.jar;d:\work\proto\lib\spring-1.2\spring-beans.jar;d:\work\proto\lib\spring-1.2\spring-context.jar;d:\work\proto\lib\spring-1.2\spring-core.jar;d:\work\proto\lib\spring-1.2\spring-dao.jar;d:\work\proto\lib\spring-1.2\spring-hibernate.jar;d:\work\proto\lib\spring-1.2\spring-jdbc.jar;d:\work\proto\lib\spring-1.2\spring-mock.jar;d:\work\proto\lib\spring-1.2\spring-orm.jar;d:\work\proto\lib\spring-1.2\spring-remoting.jar;d:\work\proto\lib\spring-1.2\spring-support.jar;d:\work\proto\lib\spring-1.2\spring-web.jar;d:\work\proto\lib\spring-1.2\spring-webmvc.jar;d:\work\proto\lib\spring-1.2\spring.jar;d:\work\proto\lib\util.concurrent-1.0\util.concurrent.jar

That format gets a little painful to scan with the human eye, since all the paths typically begin with the same root. So here’s a better way to do this : We’ll use the ant task to replace the semicolons in our debug variable with a newline and some whitespace.


<property name="current.classpath" refid="dao.compile.classpath"/>

<propertyregex property="current.classpath"
               override="true"
               input="${current.classpath}"
               regexp=";"
               replace="&#13;&#10;&#9;"
               casesensitive="false"/>

<echo level="info">
   Classpath is :

      ${current.classpath}
</echo>
  

Now when we run the aforementioned Ant snippet, it will produce the following output (much easier on the eyes) :

[echo]
[echo] d:\work\proto\dist\foo-toolkit.jar
[echo] d:\work\proto\lib\hibernate-2.1.8\lib\dom4j-1.4.jar
[echo] d:\work\proto\lib\commons-discovery-0.2\commons-discovery.jar
[echo] d:\work\proto\lib\hibernate-2.1.8\hibernate2.jar
[echo] d:\work\proto\lib\hibernate-2.1.8\lib\c3p0-0.8.4.5.jar
[echo] d:\work\proto\lib\hibernate-2.1.8\lib\cglib-full-2.0.2.jar
[echo] d:\work\proto\lib\hibernate-2.1.8\lib\ehcache-0.9.jar
[echo] d:\work\proto\lib\hibernate-2.1.8\lib\jta.jar
[echo] d:\work\proto\lib\hibernate-2.1.8\lib\odmg-3.0.jar
[echo] d:\work\proto\lib\hibernate-2.1.8\lib\oscache-2.0.jar
[echo] d:\work\proto\lib\hibernate-2.1.8\lib\swarmcache-1.0rc2.jar
[echo] d:\work\proto\lib\oracle-10g\classes12-10g.jar
[echo] d:\work\proto\lib\jakarta-struts-1.2.4\lib\commons-beanutils.jar
[echo] d:\work\proto\lib\commons-lang-2.0\commons-lang.jar
[echo] d:\work\proto\build\dao\classes
[echo] d:\work\proto\lib\jakarta-struts-1.2.4\lib\commons-logging.jar
[echo] d:\work\proto\lib\commons-collections-3.1\commons-collections.jar
[echo] d:\work\proto\lib\commons-io-1.0\commons-io.jar
[echo] d:\work\proto\lib\commons-dbutils-1.0\commons-dbutils.jar
[echo] d:\work\proto\lib\spring-1.2\acegi-security-0.8.2.jar
[echo] d:\work\proto\lib\spring-1.2\commons-codec.jar
[echo] d:\work\proto\lib\spring-1.2\ehcache-1.1.jar
[echo] d:\work\proto\lib\spring-1.2\spring-aop.jar
[echo] d:\work\proto\lib\spring-1.2\spring-beans.jar
[echo] d:\work\proto\lib\spring-1.2\spring-context.jar
[echo] d:\work\proto\lib\spring-1.2\spring-core.jar
[echo] d:\work\proto\lib\spring-1.2\spring-dao.jar
[echo] d:\work\proto\lib\spring-1.2\spring-hibernate.jar
[echo] d:\work\proto\lib\spring-1.2\spring-jdbc.jar
[echo] d:\work\proto\lib\spring-1.2\spring-mock.jar
[echo] d:\work\proto\lib\spring-1.2\spring-orm.jar
[echo] d:\work\proto\lib\spring-1.2\spring-remoting.jar
[echo] d:\work\proto\lib\spring-1.2\spring-support.jar
[echo] d:\work\proto\lib\spring-1.2\spring-web.jar
[echo] d:\work\proto\lib\spring-1.2\spring-webmvc.jar
[echo] d:\work\proto\lib\spring-1.2\spring.jar
[echo] d:\work\proto\lib\util.concurrent-1.0\util.concurrent.jar