creating a new junit 5 project in gradle in intellij

I haven’t created a new JUnit 5/Gradle project in a IntelliJ in a long time. When doing so today, I ran into a number of problems. I wound up just starting over, but writing up what I encountered/learned.

Java 16 vs 17

IntelliJ supports Java 17. When I set up Gradle, it told me that Gradle doesn’t yet support Java 17. I confirmed on the Gradle website this is true. Ok that’s fair. Java 17 isn’t fully released for another month. While there might be an early adopter package that does work with Java 17, my focus here is JUnit 5, no, Java 17. So I went with Java 16.

String not found – “Package java.lang is declared in module java.base, which is not in the module graph”

This surprised me. I’m not using modules in this project. Or at least I don’t want to be, nor do I have a module-info.java file. I tried invalidating the IntelliJ cache and restarting the IDE. That didn’t work. I then tried deleting the .gradle and .idea directories and reopening IntelliJ. That worked. IntellIJ even asked if I wanted to trust the gradle project as it recreated one.

This approach lost all IntelliJ settings including Java version, gradle version and source/test directories.

Recreating the project

At this point, I decided to delete my build.gradle and settings.gradle files in addition to the .idea and .gradle directories. My plan was to start over. I then created a new Gradle project in the same directory. I created a dummy file and had IntellIJ create a JUnit 5 test for me. That updated the Gradle file in a way that the IDEA was happy with. Now to commit to git lest anything else happen.

Switching your Gradle builds from JCenter to Maven Central

JCenter is being decommissioned on May 1st, 2021. Since many Gradle builds use JCenter by default, this means your Gradle build file is likely to have jcenter() in it. This means you have a few months to switch to Maven Central. Don’t worry, it’s easy.

Note: If you work for a company, you are hopefully using an internal binary repository proxy. (ex: Nexus, Artifactory, etc)

What’s the difference between JCenter and Maven Central?

The main benefits of JCenter are:

  • Some artifacts are in JCenter and not Maven Central – their authors are working on moving them to Maven Central. This is unlikely to affect FRC teams, but might affect people using an artifact that is more specialized.
  • It’s easier to publish to JCenter – Sonatype has been working on making it easier for Maven Central. Some of that is intrinsic though because Sonatype does a lot of verification.
  • JCenter is faster – Remember that artifacts are cached on your machine. So once you’ve downloaded the artifacts, your build performance is the same.

How do I find the affected files in GitHub?

Searching on github for either of these does not do what you might expect

  • jcenter user:boyarsky filename:build.gradle- the code tab returns 0 results
  • jcenter org:stuypulse filename:build.gradle – the code tab returns 2 results

That’s because github search only looks in files that have been updated or returned in search results in the past year. Unfortunately, the last year has not been particularly representative of a normal year. And people edit/search the contents of build.gradle files way less frequently than other file types.

I recommend just searching for the filename. That returns all your build.gradle files so you can edit them. (And since you are probably consistent in your choice of binary repository, you can sample a few matches to see if you have to change.

  • user:boyarsky filename:build.gradle – the code tab returns 5 results
  • org:stuypulse filename:build.gradle – the code tab returns 24 results

Tip: You may also have a reference to jcenter in your settings.gradle so I recommend searching that as well.

How do I edit the file?

GitHub has good Rest APIs so you can script this if you have a lot. If you don’t have a ton, either of the following is viable. (I had 5 build.gradle files in my personal repo and 3 of them were already using Maven)

Option 1 – Use the browser

  1. Open each link from the code tab of the search
  2. Choose the default branch (ex: main/master) from the pull down – search often returns a specific commit
  3. Drill down to the build.gradle file if not already there.
  4. Click the edit/pencil icon in the top right (just above the code)
  5. Change jcenter() to mavenCentral()
  6. Enter a commit comment and save
  7. If you want to make sure your build still works, run it (ex: Travis)

Option 2 – Clone the repos

(Tested on Mac; I don’t have Git Bash on my home computer so don’t know if it works exactly the same. I have used find on Git Bash though so I think it does)

  1. Clone the affected repos (if you don’t already have them)
  2. Go to a parent directory of the github repos (ex: <userHome>/git
  3. Run a UNIX command to update the list files find . -name build.gradle
  4. Run a UNIX command to update the affected files: find . -name build.gradle -exec sed -i ” -e ‘s/jcenter/mavenCentral/g’ {} \;
  5. Commit/push the affected repos (listed in step 3)
  6. If you want to make sure your build still works, run it (ex: ./gradlew build)

composite builds in gradle

In the lab today, I got a nice Gradle question. I’m answering it here on the blog so all the students can see it. (and anyone else who is curious).

The use case

We have a common library that this year (and all future year’s robots should use). However, this is the first year of competition with the library so it is likely we will need to make changes to it and rebuild at competition.

Constraint #1 – lack of wifi

For any professionals reading this wondering why we don’t use Jitpack, github or any other hosting – internet is “tricky” at competition.; It is common for there to not be any wifi in the pit and you aren’t allowed to make your own hotspot. So we need a solution that works without internet and easily lets us re-build on the same machines.

Constraint #2 – reuse

I really want the library (StuyLib) to stay in it’s own repository and not have the build file “polluted” by any changes. Why you ask? So it stays a library and not something we copy/paste from year to year.

The solution

Composite builds. Gradle has a nice system for composite builds that lets you refer to one build from another.

Directory structure

  • git clone https://github.com/StuyPulse/StuyLib
  • git clone https://github.com/StuyPulse/fantastic-spork (or any other robot)
  • That means StuyLib and fantastic-spork are in parallel directories

Updates to StuyLib

Yes, I know, I said I didn’t want updates. The only things I added were a group and version variable. That gives the artifact a proper name. (group id = com.stuypulse, artifact id = StuyLib, version = 1.0.0). StuyLib still doesn’t know it is part of a composite build.

plugins {
   id "java"
   id "edu.wpi.first.GradleRIO" version "2020.1.2"
}

// add the following two lines
group = 'com.stuypulse'
version = '1.0.0'

sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11

Updates to Robot

At end of settings.gradle, add this line:

includeBuild '../StuyLib'

Then in build.gradle, add a dependency on StuyLib

def ROBOT_MAIN_CLASS = "com.stuypulse.robot.Main"

// add a dependency
dependencies {
  implementation('com.stuypulse:StuyLib:1.0.0')
}

Testing

First, I tested that I could build each independently:

  1. cd StuyLib
  2. ./gradlew build
  3. cd ..
  4. cd fantastic-spork
  5. ./gradlew build

Then I added a class that used existing functionality in StuyLib to fantastic-spork

package jb;

import com.stuypulse.stuylib.util.*;

public class Jeanne {
  StopWatch s;
//  String str = StopWatch.TEST;
}

I re-ran the ./gradlew build in fantastic-spork and good. Then I added one line to StopWatch:

public static final String TEST = "test";

I uncommented the line in Jeanne.java. Then for the key part. I re-ran ./gradlew build in fantastic-spork and it worked. I was able to recompile fantastic-spork and have it see a change in StuyLib without having to rebuild StuyLib.

Plus this change to StuyLib only exists on my machine which proves it is being used.