Pages

Wednesday, August 26, 2015

A Maven Plugin Developer's Thoughts on Gradle

Introduction
I use both Gradle and Maven, and I maintain a plugin for working with Groovy (GMavenPlus). I thought this gave me a somewhat unique perspective. I know this comparison is far from exhaustive, but I wanted to share some thoughts I'd jotted down.

When I first heard about Gradle, I was fairly dubious. Maven had been around for about 6 years at that point and they were making some rather large promises. Although I was intrigued, my reaction was "come back to me when you have a non-RC 1.0 release". Since then, they've had several stable releases and about 6 years of their own maturing.

What I like about Gradle
  • They kept the best parts of Maven
    • The lifecycle concept
    • The standardized project layout
    • The dependency management concept
  • Very concise syntax.  It's an amazing feeling to be able to do a standard Java build with a single line of code.
  • Keeps the simple things simple, while allowing you to use the power of Groovy and Ant if you need something non-standard.
  • Gradle Wrapper (gradlew) is a nice way to help keep builds reproducible
  • Application Plugin (Maven has ways to do this, but having it in a plugin is convenient)
  • Much more flexible than Maven (in nearly every way -- dependency management, lifecycle customization, custom tasks, etc)
  • Can put environment config and dev helper scripts right into the build as tasks instead of separate script files
  • HTML test report is nicer than Surefire's, and I now prefer it over console text
  • Has a test runtime scope (unlike Maven)
  • implementation vs api scopes are brilliant
  • Can lock transitive dependency versions for superior build reproducibility
  • Can share dependency versions between projects (called platforms)
What I don't like about Gradle
  • Allowing people to do custom things so easily might lead to the tangled mess that was Ant. There's already some pretty crazy builds out there (though arguably this isn't much riskier than what you could do with AntRun or GMavenPlus).
  • Because it's Groovy instead of XML, you don't answers get as helpful hints from your IDE.
  • Can't include multiple source directories each with their own includes/excludes patterns (they have a TODO for this).
  • Groovy building Groovy can sometimes be problematic
  • No equivalent of mvn install, which makes end-to-end testing of plugins clunkier (though they now offer TestKit that should help with this)
  • Some areas might be perceived as being less mature
  • No isolation of plugin dependencies -- they all run on the same classpath (unlike Maven), unless the author took the time to do custom classloader work, which most don't.
  • Plugins use whatever repository is declared instead of project repositories. So if you don't want to use JCenter, for example, but a plugin you use does, then you end up using that repository too (see #10376).
Notable differences from Maven
  • In Gradle, default target bytecode version is version of JDK, whereas in Maven default is currently 1.5
  • Gradle's commands are case-insensitive (something I actually really like)
  • Gradle doesn't compile tests unless specifically asked for (gradle classes != mvn compile, gradle testClasses == mvn compile) (not really a positive or negative, just something to be aware of)
  • Gradle will stop testing after the first module to have a test failure. You have to use the --continue argument to continue on to the other modules the way Maven does. I kinda wish this was the default.
What I like About Maven
  • Maven's inflexibility has largely proven very successful in preventing pointless deviation from the established pattern.
  • The lifecycle concept
  • The standardized project layout
  • The dependency management concept
What I don't like About Maven
  • XML is pretty cumbersome (though Polyglot for Maven isn't completely dead). This leads to out of control copy-and-pasting of POMs, with dependencies that have no business being there.
  • Convention over configuration doesn't go far enough (ideally, a basic Java project should be just a few lines, like it is in Gradle)
  • No ability to specify ranges of versions that are acceptable for transitive resolution (I haven't used it much -- but it's a really cool feature)
  • No TestRuntime scope
  • No Maven Wrapper equivalent of gradlew (though there is an unofficial project)
How Gradle has improved Maven
It took a while, but you can finally opt out of transitive dependencies. I think Gradle (and Ivy) earned a good deal of credit for the pressure to offer this feature.

Conclusion?
I honestly think despite there being some areas Gradle can be improved, it's generally the better choice for most projects.  There are circumstances where this isn't the case (like if you're building a Maven plugin). But in most cases, I think Gradle's pros outweigh the cons. But if you prefer Maven (or your CTO/CIO/Architect is making the choice for you), rest assured that despite my recommendation, I'm committed to maintaining GMavenPlus for the indefinite future so that the choice is always yours to make.

More Resources

Update (2021): I've added some new bullet points. Some of the newer features (Gradle 5 and 6 especially) add some pretty compelling features that might tip the scales for you, depending on your use case.

Monday, August 10, 2015

Forcing Windows 10 Upgrade

Want to get your free upgrade to Windows 10, but don't want to wait your turn? Run this reg file
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\OSUpgrade]
"AllowOSUpgrade"=dword:0x00000001
Then open the command prompt as an administrator and run
wuauclt.exe /updatenow

Wednesday, July 15, 2015

Custom configuration script ASTs

Nikolay Totomanov asked on the Groovy mailing list how one could add a default constructor (if it doesn't already exist) to all classes. I realized there were no examples on the internet (that I could find anyway) of
  1. How to pass parameters into an AST in a configuration script
  2. How to use a custom AST in a configuration script
So I decided to try to remedy that with this post.

1. AST parameters in a configuration script
Let's say you wanted to do
@groovy.transform.TupleConstructor(includes=['foo'])
in a configuration script. How do you pass the includes? Use a map. The equivalent configuration script would be
withConfig(configuration) {
    ast(groovy.transform.TupleConstructor, includes:['foo'])
}

2. Custom AST in a configuration script
If you wanted to create your own AST and use it in a configuration script, I suggest looking at Groovy as a starting point. Here, I'll use groovy.transform.TupleConstructor and org.codehaus.groovy.transform.TupleConstructorASTTransformation as an example to solve Nikolay's problem. Here is the result
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
import org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.ast.AnnotatedNode
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.ConstructorNode
import org.codehaus.groovy.ast.Parameter
import org.codehaus.groovy.ast.stmt.BlockStatement
import org.codehaus.groovy.control.CompilePhase
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.transform.AbstractASTTransformation
import org.codehaus.groovy.transform.GroovyASTTransformation
import org.codehaus.groovy.transform.GroovyASTTransformationClass

@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
public class DefaultConstructorASTTransformation extends AbstractASTTransformation {
    public void visit(ASTNode[] nodes, SourceUnit source) {
        init(nodes, source)
        AnnotatedNode parent = (AnnotatedNode) nodes[1]
        if (parent instanceof ClassNode) {
            ClassNode cNode = (ClassNode) parent
            // doesn't already have a default constructor
            if (!cNode.getDeclaredConstructor(new Parameter[0])) {
                cNode.addConstructor(new ConstructorNode(
                    ACC_PUBLIC, new Parameter[0], cNode.EMPTY_ARRAY, new BlockStatement()))
            }
        }
    }
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@GroovyASTTransformationClass("DefaultConstructorASTTransformation")
public @interface DefaultConstructor {}

withConfig(configuration) {
    ast(DefaultConstructor)
}

Tuesday, May 26, 2015

Guava Hadoop Classpath Issue

Blogging this because it was slightly too large for a tweet.  If you've got a stacktrace like


java.lang.NoClassDefFoundError: com/google/common/io/LimitInputStream
at org.apache.hadoop.mapreduce.JobSubmitter.submitJobInternal(JobSubmitter.java:467) at org.apache.hadoop.mapreduce.Job$10.run(Job.java:1295) at org.apache.hadoop.mapreduce.Job$10.run(Job.java:1292) at java.security.AccessController.doPrivileged(Native Method) at javax.security.auth.Subject.doAs(Subject.java:415) at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1642) at org.apache.hadoop.mapreduce.Job.submit(Job.java:1292) at org.apache.hadoop.mapreduce.Job.waitForCompletion(Job.java:1313)


You may find this problematic dependency tree
\---org.apache.hadoop:hadoop-client
    +---org.apache.hadoop:hadoop-common
        +---org.apache.hadoop:hadoop-auth

It seems Google has once again broken compatibility in Guava by removing LimitInputStream in Guava 15.  And while much of Hadoop (except the new versions which have upgraded their Guava version) are on an older version of Guva, the hadoop-auth module contains a newer version of Guava that most dependency management tools (aka Maven and Gradle) will choose over the older version.  Adding an exclusion for this transitive dependency should resolve this issue.

Friday, May 15, 2015

IntelliJ and junit-hierarchicalcontextrunner

For those using junit-hierarchicalcontextrunner getting an exception like
java.lang.Exception: The inner class com.mycompany.SomeClassTest$InnerClass is not static.
 at org.junit.runners.BlockJUnit4ClassRunner.validateNoNonStaticInnerClass(BlockJUnit4ClassRunner.java:113)
...
You might be able to get rid of that error (especially if it happens when running in IntelliJ, but not the commandline) by upgrading to JUnit 4.12 and junit-hierarchicalcontextrunner 4.12.0. However, if you're an IntelliJ user, you'll find that running an individual test still runs all the tests. This will be fixed in an upcoming release.

Friday, May 1, 2015

Codehaus Migration

Since Codehaus is shutting down, you may be wondering where a project you use has moved. Here's where some of the more popular projects have moved.

Project Old Homepage New Homepage
EasyMock http://easymock.codehaus.org/ http://easymock.org/
Enunciate http://enunciate.codehaus.org/ http://enunciate.webcohesion.com/
Esper http://esper.codehaus.org/ http://www.espertech.com/esper/index.php
Fabric3 http://fabric3.codehaus.org/ http://www.fabric3.org/
Gant http://gant.codehaus.org/ http://gant.github.io/
Geb http://geb.codehaus.org/ http://www.gebish.org/
GMavenPlus http://gmavenplus.codehaus.org/ https://github.com/groovy/GMavenPlus/
GPars http://gpars.codehaus.org/ http://gpars.github.io/
Griffon http://griffon.codehaus.org/ http://new.griffon-framework.org/
Groovy http://groovy.codehaus.org/ http://www.groovy-lang.org/
GroovyFX http://docs.codehaus.org/display/GROOVY/GroovyFX http://groovyfx.org/
Gumtree http://gumtree.codehaus.org/ https://github.com/Gumtree/gumtree
IzPack http://docs.codehaus.org/display/IZPACK/Home http://izpack.org/
Jackson http://jackson.codehaus.org/ https://github.com/FasterXML/jackson
JavaNCSS http://javancss.codehaus.org/ NONE YET (though Codehaus has a mirror on Github)
jMock http://jmock.codehaus.org/ http://www.jmock.org/
JRuby http://jruby.codehaus.org/ http://jruby.org/
M2Eclipse http://m2eclipse.codehaus.org/ http://eclipse.org/m2e/
Mojo http://mojo.codehaus.org/ https://github.com/mojohaus/ (still transitioning)
MVEL http://mvel.codehaus.org/ https://github.com/mvel/mvel
Pico Container http://picocontainer.codehaus.org/ https://github.com/picocontainer
Plexus Classworlds http://plexus.codehaus.org/plexus-classworlds/ https://github.com/sonatype/plexus-classworlds
Plexus Containers http://plexus.codehaus.org/plexus-containers/ https://github.com/sonatype/plexus-containers
Sonar http://sonar.codehaus.org/ http://www.sonarqube.org/
Sonar http://sitemesh.codehaus.org/ http://wiki.sitemesh.org/wiki/display/sitemesh/Home
StaxMate http://woodstox.codehaus.org/ https://github.com/FasterXML/StaxMate
SVN4J http://svn4j.codehaus.org/ http://sourceforge.net/projects/svn4j/
Woodstox http://woodstox.codehaus.org/ https://github.com/FasterXML/woodstox
XStream http://xstream.codehaus.org/ http://x-stream.github.io/


I'll keep this page updated as more information becomes available (let me know if you spot something incorrect or out of date). Also if there's a project not on this list that you think should be, let me know and I'll add it. I know some of these moves are old news, but I listed them anyway since once Codehaus shuts down, any redirects they may have had will stop working.

Monday, March 23, 2015

Skype formatting

This seems to be nowhere documented, but here's some markup Skype recognizes.  An example of what you'd type is in gray, and what Skype would render is in blue.

*bold*
bold

_italic_
italic

~strikethrough~
strikethrough

@@ *not bold* (note the space after the two at signs, and that this must be the first thing in a message)
*not bold*

!! preformatted (note the space after the two exclamation points, and that this must be the first thing in a message)
preformatted

Preformatting also overrides other markup, for example
!! *not bold*
*not bold*

Preformatting also respects whitespaces, for example
!! above
below
above
below

Of course Skype also supports /me, like most chat clients do these days
/me yawns
[user name] yawns
[user name], will be whatever your name is in Skype (not your username)

And since 6.22.81.105, you can also enable/disable the markup feature with
/wikimarkup on/off

Wednesday, March 18, 2015

Preventing Chrome hijacks from coming back

It's easy enough to remove extensions in Chrome, but to prevent the really obnoxious ones from coming back again, look at paths in entries under HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Google\Chrome\Extensions\
Basically anything in C:\Users\USERNAME\AppData\Local\CRE or C:\Users\USERNAME\AppData\Local\Temp
can go.  My thanks to Mike for the helpful advice.