Customize Gradle to setup Intellij

Automating Efficiency

Setting up Intellij isn’t hard. Getting everyone on your team to do it is another thing. Getting it right so developers are efficient is no small matter. The little extra integration and goodness can really let people fly. Configured Intellij environments allow developers to get near real-time feedback on their work. Lisa Crispin talks about this in her blog about continuous integration and farming with donkeys:

If we have a continuous integration process that runs our regression tests on each new version of the code, we know within a few minutes or hours whether new or updated code has broken something. When we know right away, it’s easy to fix. Problems don’t worry us, because we know we can fix them in a timely manner and move on. Short feedback loops give us confidence. Confidence leads to enjoyment.

Source

While Lisa talks about minutes and hours, I think we should be thinking now in milliseconds and seconds for a feedback loop.

Setting up the Intellij Project

Let’s start with the project. Gradle has documentation here.

Below is an example idea configuration used in my base build.gradle file in the project. I’ll go into detail on what each of the different sections mean.

true

Looking at lines 7 and 8, there are two basic settings that can be set: the JDK level and the version control system. These are basic options in the idea plugin Source Being documented and pretty straight-forward, it’s not hard to understand what these do. Let’s move the crazier part.

Setting Annotation Processing

Line 10 is interesting because here, we are going to modify the XML. Intellij stores its project configuration in a .ipr file in the root project directory. So line 10 says “let’s modify that .ipr file.” The first block is where we find the node “CompilerConfiguration” and then modify the contents. We have to think of in-place changes because we don’t know the original state of the ipr file. So this block:

          // enable 'Annotation Processors'
           xmlFile.asNode().component.find {
               it.@name == 'CompilerConfiguration'
           }['annotationProcessing'][0].replaceNode {
               annotationProcessing {
                   profile(default: true, name: 'Default', useClasspath: 'true', enabled: true)
               }
           }

will produce the XML snippet:

<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
 [...]
  <component name="CompilerConfiguration">
    <annotationProcessing>
      <profile default="true" name="Default" enabled="true">
        <processorPath useClasspath="true" />
      </profile>
    </annotationProcessing>
  </component>

We put this specific change in to automatically turn on annotation processing in Intellij. This allows the Lombok plugin to compile correctly in Intellij. You might ask “What’ lombok?”, well my friend, it makes POJOs into what I think that they should be.

Setting Git VCS root

Intellij made this interesting “feature” where when it knows that you are using Git, it wants to know where the root of the repository is set so it can properly track changes. In virtually every instance, it is the annoying popup in the top left where it asks you to set the VCS root. Then when you open the dialog, it has everything set and all you have to hit is “ok”. Well clicking buttons is annoying. So lines 21 to 26 do this for us. The gradle xml changes below:

      // setup Git root
      xmlFile.asNode().component.find { it.@name == 'VcsDirectoryMappings' }.replaceNode {
          component(name: 'VcsDirectoryMappings') {
              mapping(directory: "", vcs: "")
              mapping(directory: "\$PROJECT_DIR\$", vcs: 'Git')
          }
      }

will produce the XML snippet:

<?xml version="1.0" encoding="UTF-8"?>
<project version="4">

 [...]

   <component name="VcsDirectoryMappings">
     <mapping directory="$PROJECT_DIR$" vcs="Git" />
   </component>

Voilla!

Setting up the Intellij Module

In Intellij, “A module is a discrete unit of functionality that can be run, tested, and debugged independently.” Source. Here, we are going to use the idea plugin to setup our spring inspection and Infinitest setup.

As before, below is the gist of the project changes. We’ll go into details about what each line is.

true

The first thing to note is that we are going to be modifying the ‘.iml’ file in the project. This will allow us to modify which plugins will be active for the module. The idea plugin will allow us to write to this with lines: 7. The first thing that we do is get the “FacetManager” node. This node allows us to set which plugins are active in the module. Lines 10 through 17 are where we get the xml tag or build one.

The code below gets the facet manager node and then if it finds it, removes the web facet from it.

// Find or define the facetManager XML node.
def facetManager = xmlFile.asNode().component.find { it.@name == 'FacetManager' } as Node
  if (facetManager) {
    Node webFacet = facetManager.facet.find { it.@type == 'web' }
      if (webFacet)
          facetManager.remove(webFacet)
  } else {
      facetManager = xmlFile.asNode().appendNode('component', [name: 'FacetManager']);
  }

Next, in line 18 we are going to build our own elements so we can attach them. Lines 20 through 28 build the nodes for spring inpsection. Line 29 is where we attach that as a sub-element of the “FacetManager” element.

def builder = new NodeBuilder()

// Setup Spring Wiring inspection.
def springFacet = builder.facet(type: 'Spring', name: 'Spring') {
    configuration {
        fileset(id: 'fileset', name: 'Spring Application Context',  removed:'false') {
            file('file://$MODULE_DIR$/src/[PATH TO SPRING BOOT APP]/[SPRING BOOT APP CLASS].java'){
            }
        }
    }
}
facetManager.append springFacet

Lastly, still using the node build object, I build another xml element for Infinitest.

// Setup Infinitest integration.
def infinitestFacet = builder.facet(type: 'Infinitest', name: 'Infinitest') {
    configuration { }
}
facetManager.append infinitestFacet

Both of these produces the final output of:

<?xml version="1.0" encoding="UTF-8"?>
<module relativePaths="true" type="JAVA_MODULE" version="4">
  <component name="FacetManager">
    <facet type="Spring" name="Spring">
      <configuration>
        <fileset id="fileset" name="Spring Application Context" removed="false">
          <file>file://$MODULE_DIR$/src/[PATH TO SPRING BOOT APP]/[SPRING BOOT APP CLASS].java</file>
        </fileset>
      </configuration>
    </facet>
    <facet type="Infinitest" name="Infinitest">
      <configuration />
    </facet>
  </component>
  <!-- ... -->
</module>

So how do you add your own plugin configurations?

I do the following steps:

  1. Make a copy of your iml or ipr file.
  2. In Intellij, modify your settings to exactly what you want.
  3. Diff the two and determine what XML elements need to be created.
  4. Build the gradle configuration until you get exactly what you want.

Lastly: Is it worth the work?

If it’s just you, maybe not. I tend to work with teams of 3 - 15 developers. A good chunk of them are really great developers and many of them can get stuck trying to get things setup. Most developers are lazy (which isn’t always thought of as a bad thing) and so making things work “auto-magically” will result in the highest rate of adoption and the lowest rate of grumbling.

I am always open to hear your thoughts though…