Using a Code Formatter
Code formatters exist to ensure that the style of code written is consistent throughout the entire codebase. This is used in many major projects; from Android to OpenCV. Teams may wish to add a formatter throughout their robot code to ensure that the codebase maintains readability and consistency throughout.
For this article, we will highlight using Spotless for Java teams and wpiformat for C++ teams.
Spotless
Configuration
Necessary build.gradle
changes are required to get Spotless functional. In the plugins {}
block of your build.gradle
, add the Spotless plugin so it appears similar to the below.
plugins {
id "java"
id "edu.wpi.first.GradleRIO" version "2024.1.1"
id 'com.diffplug.spotless' version '6.20.0'
}
Then ensure you add a required spotless {}
block to correctly configure spotless. This can just get placed at the end of your build.gradle
.
spotless {
java {
target fileTree('.') {
include '**/*.java'
exclude '**/build/**', '**/build-*/**'
}
toggleOffOn()
googleJavaFormat()
removeUnusedImports()
trimTrailingWhitespace()
endWithNewline()
}
groovyGradle {
target fileTree('.') {
include '**/*.gradle'
exclude '**/build/**', '**/build-*/**'
}
greclipse()
indentWithSpaces(4)
trimTrailingWhitespace()
endWithNewline()
}
format 'xml', {
target fileTree('.') {
include '**/*.xml'
exclude '**/build/**', '**/build-*/**'
}
eclipseWtp('xml')
trimTrailingWhitespace()
indentWithSpaces(2)
endWithNewline()
}
format 'misc', {
target fileTree('.') {
include '**/*.md', '**/.gitignore'
exclude '**/build/**', '**/build-*/**'
}
trimTrailingWhitespace()
indentWithSpaces(2)
endWithNewline()
}
}
Running Spotless
Spotless can be ran using ./gradlew spotlessApply
which will apply all formatting options. You can also specify a specific task by just adding the name of formatter. An example is ./gradlew spotlessmiscApply
.
In addition to formatting code, Spotless can also ensure the code is correctly formatted; this can be used by running ./gradlew spotlessCheck
. Thus, Spotless can be used as a CI check, as shown in the following GitHub Actions workflow:
on: [push]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
spotless:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 17
- run: ./gradlew spotlessCheck
Explanation of Options
Each format
section highlights formatting of custom files in the project. The java
and groovyGradle
are natively supported by spotless, so they are defined differently.
Breaking this down, we can split this into multiple parts.
Formatting Java
Formatting Gradle files
Formatting XML files
Formatting Miscellaneous files
They are all similar, except for some small differences that will be explained. The below example will highlight the java {}
block.
java {
target fileTree('.') {
include '**/*.java'
exclude '**/build/**', '**/build-*/**'
}
toggleOffOn()
googleJavaFormat()
removeUnusedImports()
trimTrailingWhitespace()
endWithNewline()
}
Let’s explain what each of the options mean.
target fileTree('.') {
include '**/*.java'
exclude '**/build/**', '**/build-*/**'
}
The above example tells spotless where our Java classes are and to exclude the build
directory. The rest of the options are fairly self-explanatory.
toggleOffOn()
adds the ability to have spotless ignore specific portions of a project. The usage looks like the following
// format:off
public void myWeirdFunction() {
}
// format:on
googleJavaFormat()
tells spotless to format according to the Google Style GuideremoveUnusedImports()
will remove any unused imports from any of your Java classestrimTrailingWhitespace()
will remove any extra whitespace at the end of your linesendWithNewline()
will add a newline character to the end of your classes
In the groovyGradle
block, there is a greclipse
option. This is the formatter that spotless uses to format gradle files.
Additionally, there is a eclipseWtp
option in the xml
block. This stands for “Gradle Web Tools Platform” and is the formatter to format xml
files. Teams not using any XML files may wish to not include this configuration.
Note
A full list of configurations is available on the Spotless README
Issues with Line Endings
Spotless will attempt to apply line endings per-OS, which means Git diffs will be constantly changing if two users are on different OSes (Unix vs Windows). It’s recommended that teams who contribute to the same repository from multiple OSes utilize a .gitattributes
file. The following should suffice for handling line endings.
*.gradle text eol=lf
*.java text eol=lf
*.md text eol=lf
*.xml text eol=lf
wpiformat
Requirements
You can install wpiformat by typing pip3 install wpiformat
into a terminal or command prompt.
Usage
wpiformat can be ran by typing wpiformat
in a console. This will format with clang-format
. Three configuration files are required (.clang-format
, .styleguide
, .styleguide-license
). These must exist in the project root.
An example styleguide is shown below:
cppHeaderFileInclude {
\.h$
\.hpp$
\.inc$
\.inl$
}
cppSrcFileInclude {
\.cpp$
}
modifiableFileExclude {
gradle/
}
Note
Teams can adapt .styleguide
and .styleguide-license
however they wish. It’s important that these are not deleted, as they are required to run wpiformat!
You can turn this into a CI check by running git --no-pager diff --exit-code HEAD
, as shown in the example GitHub Actions workflow below:
name: Lint and Format
on:
pull_request:
push:
jobs:
wpiformat:
name: "wpiformat"
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Fetch all history and metadata
run: |
git checkout -b pr
git branch -f main origin/main
- name: Set up Python 3.10
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Install wpiformat
run: pip3 install wpiformat==2024.42
- name: Run
run: wpiformat
- name: Check output
run: git --no-pager diff --exit-code HEAD