A gradle build with Lombok and Checker Framework
NOTE: The delombok plugin for maven is unmaintained, and making the delombok process work with the Checker Framework gets more complicated. Therefore, it makes sense to have a build working with gradle instead. The gradle plugins used here play well together and are maintained.
Once we have typetools/jdk17u built
and ready for our OS (to get the most of the Checker Framework), the
following build.gradle
can be useful to apply:
- Java Google code rules
- Lombok/delombok with io.freefair.lombok gradle plugin.
- Checker Framework
checkers (e.g.
NullnessChecker
).
plugins {
id 'java'
id 'com.github.ben-manes.versions' version '0.51.0'
id 'io.freefair.lombok' version '6.3.0'
id 'org.checkerframework' version '0.6.44'
id 'com.diffplug.spotless' version "7.0.0.BETA2"
}
group = 'org.example'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.functionaljava:functionaljava:5.0'
implementation 'com.google.guava:guava:33.3.0-jre'
implementation 'ch.qos.logback:logback-classic:1.5.8'
implementation 'org.slf4j:slf4j-api:2.1.0-alpha1'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.18.0-rc1'
implementation 'com.opencsv:opencsv:5.9'
compileOnly 'org.checkerframework:checker-qual:3.47.0'
testCompileOnly 'org.checkerframework:checker-qual:3.47.0'
checkerFramework 'org.checkerframework:checker:3.47.0'
implementation 'org.checkerframework:checker-util:3.47.0'
testImplementation 'org.testng:testng:7.10.2'
testImplementation 'org.assertj:assertj-core:3.26.3'
compileOnly 'org.projectlombok:lombok:1.18.34'
// compileOnly 'org.projectlombok:lombok:1.18.22'
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs.addAll([
'--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED',
'--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED',
'--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED',
'--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED',
'--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED',
'--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED',
'--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
'--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED',
'--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED',
'-Xmaxerrs', '10000',
'-Xmaxwarns', '10000',
'-Awarns'
])
}
spotless {
java {
eclipse().configFile("${rootProject.projectDir}/eclipse-java-google-style.xml")
importOrder('','\\#')
.wildcardsLast(false)
.semanticSort()
removeUnusedImports()
formatAnnotations()
}
}
checkerFramework {
checkers = [
'org.checkerframework.checker.nullness.NullnessChecker'
]
}
test {
useTestNG()
maxHeapSize = '8G'
}
wrapper {
gradleVersion = '7.3'
}
The build.gradle
above is compatible with Java 17, and
the plugins are also compatible with JDK 17. Notice that we are using a
JDK 17 compatible version of gradle as well.
Spotless
To call spotless, use:
./gradlew :spotlessApply
(Trivial) example of NullnessChecker usage
The following simple example is meant to check that the
build.gradle
is working for the NullnessChecker:
package org.example;
import lombok.extern.java.Log;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.testng.annotations.Test;
@Log
public class NullnessChecks {
@Test
public void nullCheckOnString() {
@NonNull String str = null;
log.info(String.format("%d", str.length()));
}
}
Executing:
./gradlew clean build
gives
> Task :compileTestJava
warning: [options] --add-opens has no effect at compile time
...NullnessChecks.java:14: warning: [assignment] incompatible types in assignment.
String str = null;
^
found : null (NullType)
required: @UnknownInitialization @NonNull String
We could also have written:
@Log
public class NullnessChecks {
@Test
public void nullCheckOnString() {
String str = null;
log.info(String.format("%d", str.length()));
}
}
that is, not writing the @NonNull
annotation, because it
is the default. In that case, trying to execute the test
task would have given:
./gradlew test
> Task :compileTestJava
...NullnessChecks.java:14: warning: [dereference.of.nullable] dereference of possibly-null reference str
log.info(String.format("%d", str.length()));
^
1 warning
> Task :test FAILED
Gradle suite > Gradle test > org.example.NullnessChecks > nullCheckOnString FAILED
java.lang.NullPointerException at NullnessChecks.java:14
1 test completed, 1 failed
A simple CLI with Rust
This post will serve my memory for two purposes:
- How to write simple CLI parsers (in Rust!!) to further execute some other task.
- And most importantly, to document some of the configurations done to Doom Emacs to get a working Rust programming environment (for free!).
Simple CLI parsing with Rust
Let’s say we have to deal with a TestNG Suite that can be executed with the following options:
- Which (remote) testing environment will be used to execute a group
of tests. Can be any string, and if no value is provided,
qa1
should be used. - (Optionally) Which is the
xml
file specifying the tests that will be executed by TestNG. No provided value means “execute all tests”. - Which browser will be used. Valid options are:
- Chrome (the default)
- Firefox
- Edge
- Safari
- Maximum timeouts for the Selenium WebDriver (in seconds). Default is 5 seconds.
- Assume that we can accept a pattern expression (note: this is not exactly a regex expression, see Running a Single Test - Maven Failsafe Plugin), and if none is provided, then all tests should be executed.
Installing a rust toolchain
Install rustup for your OS. Answer the questions made by the installer to select a toolchain (example, the nightly toolchain).
Create a skeleton for a “Hello-World” application in rust using cargo
In the command line, use cargo to create a simple project:
cargo new test_executor
This should create a structure of files/directories like the following:
.
├── Cargo.toml
└── src
└── main.rs
This (first) parser will use the clap crate
(clap
is a “Command Line Argument Parser for Rust”) for the
main parsing functionality. So, let’s add that dependency to the
project:
cargo add clap --features derive
Let’s add one more library that provides a convenient derive macro
for the standard library’s std::error::Error
trait:
cargo add thiserror
Now our little skeleton should have 3 files:
.
├── Cargo.lock
├── Cargo.toml
└── src
└── main.rs
And Cargo.toml
should declare our dependencies. It might
look similar to this:
[package]
name = "test_executor"
version = "0.1.0"
edition = "2021"
[dependencies]
clap = { version = "4.5.18", features = ["derive"] }
thiserror = "1.0.63"
Sample code (for quick reference and modification)
The following code does the job:
use clap::Parser;
use std::{
fmt::{self, Debug, Display},
str::FromStr,
};
use thiserror::Error;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
enum Browser {
#[default]
Chrome,
Firefox,
Edge,
Safari,
}
#[derive(Debug, Error)]
#[error("Unknown browser")]
struct UnknownBrowser;
impl FromStr for Browser {
type Err = UnknownBrowser;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"chrome" => Ok(Browser::Chrome),
"firefox" => Ok(Browser::Firefox),
"edge" => Ok(Browser::Edge),
"safari" => Ok(Browser::Safari),
_ => Err(UnknownBrowser),
}
}
}
impl Display for Browser {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Chrome => write!(f, "Chrome"),
Self::Firefox => write!(f, "Firefox"),
Self::Edge => write!(f, "Edge"),
Self::Safari => write!(f, "Safari"),
}
}
}
/// Simple command-line argument parser
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
/// Name of the environment (and associated properties)
#[arg(short, long, default_value = "qa1")]
env: String,
/// Test suite to execute in src/test/resources/testsuites
#[arg(short, long)]
test_suite: Option<String>,
/// Browser to use for testing
#[arg(short, long, default_value_t)]
browser: Browser,
/// Selenium WebDriverWait value in seconds
#[arg(short, long, default_value_t = 5)]
wait: u8,
/// Pattern for test cases to execute with maven failsafe plugin
#[arg(short, long, default_value = ".*")]
pattern: String,
}
// cargo run -- --browser=Firefox --env=qa2 --wait=30 --test_suite=gui-testsuite.xml
fn main() {
let args = Args::parse();
println!("Environment: {}", args.env);
println!("Test suite: {:?}", args.test_suite);
println!("Browser: {:?}", args.browser);
println!("Selenium WebDriverWait: {} seconds", args.wait);
println!("Pattern for test cases: {:?}", args.pattern);
}
It can be compiled with
cargo build
And executed with something like
cargo run -- --browser=Firefox --env=qa2 --wait=30 --test_suite=gui-testsuite.xml
Once we’re happy with the end result, build with
cargo build --release
And a binary can be copied from
src/target/release/test_executor
to wherever you need it
(in your $PATH
).
Rust development environment with Doom Emacs
Enable lsp, rust and tree-sitter as well as debugger in
init.el
. Some excerpts ofinit.el
::tools (debugger +lsp) (lsp +peek) (tree-sitter) :lang (rust +lsp +tree-sitter)
My
config.el
(load! "my-defaults-config") (load! "my-banner-config") (load! "my-emoji-config") (load! "my-mu4e-config") (load! "my-company-config") (load! "my-yasnippet-config") (load! "my-tabnine-config") (load! "my-dap-config") (load! "my-gptel-config") ;; TODO: clean up order and setup for different languages in these 2 files (load! "my-rust-config") (load! "my-lsp-config") (load! "my-org-config") (load! "my-gui-appearance-config") (load! "my-spell-config") (load! "my-tree-sitter-config") (load! "my-clojure-config") (load! "my-api-testing-config") (load! "my-db-config") (load! "my-vterm-config") (load! "my-consult-omni-config")
My debugger config (
my-dap-config.el
). We’re relying on lldb(use-package dap-mode :config (dap-ui-mode) (dap-ui-controls-mode 1) (require 'dap-lldb) (require 'dap-gdb-lldb) ;; installs .extension/vscode (dap-gdb-lldb-setup) ;;(setq dap-gdb-lldb-path "/Users/oscarvarto/doom-emacs/.local/etc/dap-extension/vscode/webfreak.debug") ;;https://users.rust-lang.org/t/debugging-in-emacs-doom/99540/2 (require 'dap-codelldb) (dap-codelldb-setup) ;; TODO: Find a way to change the :program argument without hardcoding it's value (dap-register-debug-template "Rust::LLDB Run Configuration" (list :type "lldb" :request "launch" :name "LLDB::Run" :gdbpath "rust-lldb" :target nil :program "/Users/oscarvarto/rustCode/test_executor/target/debug/test_executor" :cwd nil)))
Contents of
my-rust-config.el
, considering that I’m using nushell to get the toolchain and rustic for an improved development experience:(custom-set-faces '(rustic-compilation-column ((t (:inherit compilation-column-number)))) '(rustic-compilation-line ((t (:foreground "LimeGreen"))))) (defun rustic-mode-auto-save-hook () "Enable auto-saving in rustic-mode buffers." (when buffer-file-name (setq-local compilation-ask-about-save nil))) (add-hook 'rustic-mode-hook 'rustic-mode-auto-save-hook) (setq rustic-rustfmt-args "+nightly") (setq rustic-rustfmt-config-alist '((hard_tabs . t) (skip_children . nil))) (use-package rust-mode :init (setq rust-mode-treesitter-derive t)) ;; <== Tree-sitter related! (use-package rustic :custom (let ((toolchain (or (getenv "RUST_TOOLCHAIN") (string-trim (shell-command-to-string "nu -c \"rustup show | lines | get 14 | split row ' ' | get 0\"")) ;; e.g. "1.77.2-aarch64-apple-darwin" "nightly"))) (setq rustic-analyzer-command `("rustup" "run" ,toolchain "rust-analyzer")))) (with-eval-after-load 'rustic-mode (add-hook 'rustic-mode-hook 'lsp-ui-mode) (add-hook 'flycheck-mode-hook #'flycheck-rust-setup)) ;; TODO: https://robert.kra.hn/posts/rust-emacs-setup/ (use-package lsp-mode :commands lsp :custom ;; what to use when checking on-save. "check" is default, I prefer clippy (lsp-rust-analyzer-cargo-watch-command "clippy") (lsp-eldoc-render-all t) (lsp-idle-delay 0.6) ;; enable / disable the hints as you prefer: (lsp-inlay-hint-enable t) ;; These are optional configurations. See https://emacs-lsp.github.io/lsp-mode/page/lsp-rust-analyzer/#lsp-rust-analyzer-display-chaining-hints for a full list (lsp-rust-analyzer-display-lifetime-elision-hints-enable "skip_trivial") (lsp-rust-analyzer-display-chaining-hints t) (lsp-rust-analyzer-display-lifetime-elision-hints-use-parameter-names nil) (lsp-rust-analyzer-display-closure-return-type-hints t) (lsp-rust-analyzer-display-parameter-hints nil) (lsp-rust-analyzer-display-reborrow-hints nil) :config (add-hook 'lsp-mode-hook 'lsp-ui-mode)) (require 'lsp-ui) (setq! lsp-ui-peek-always-show t lsp-ui-sideline-show-hover t lsp-ui-doc-enable t)
Adding
rustic
topackage.el
:(package! rustic :recipe (:repo "emacs-rustic/rustic"))
my-lsp-config.el
(use-package lsp-mode :init (setq lsp-keymap-prefix "C-c l") :hook ((java-mode . lsp) ;; if you want which-key integration (lsp-mode . lsp-enable-which-key-integration)) :commands lsp) (use-package lsp-ui :init (setq lsp-ui-doc-popup-enabled t lsp-ui-doc-popup-max-width 0.8 lsp-ui-peek-always-show t) :commands lsp-ui-mode) (use-package lsp-treemacs :commands lsp-treemacs-errors-list) ;; optionally if you want to use debugger (use-package dap-mode) ;; (use-package dap-LANGUAGE) to load the dap adapter for your language (load! "my-lsp-booster-config") (load! "my-lsp-java-config") (load! "my-lsp-nu-config") (load! "my-lsp-vtsls-config")
The (expected) contents for lsp-booster functionality.
- In my
init.el
:
(setenv "LSP_USE_PLISTS" "true")
my-lsp-booster-config.el
(after! lsp-mode (setq! lsp-file-watch-threshold 20000 lsp-inlay-hint-enable t)) (defun lsp-booster--advice-json-parse (old-fn &rest args) "Try to parse bytecode instead of json." (or (when (equal (following-char) ?#) (let ((bytecode (read (current-buffer)))) (when (byte-code-function-p bytecode) (funcall bytecode)))) (apply old-fn args))) (advice-add (if (progn (require 'json) (fboundp 'json-parse-buffer)) 'json-parse-buffer 'json-read) :around #'lsp-booster--advice-json-parse) (defun lsp-booster--advice-final-command (old-fn cmd &optional test?) "Prepend emacs-lsp-booster command to lsp CMD." (let ((orig-result (funcall old-fn cmd test?))) (if (and (not test?) ;; for check lsp-server-present? (not (file-remote-p default-directory)) ;; see lsp-resolve-final-command, it would add extra shell wrapper lsp-use-plists (not (functionp 'json-rpc-connection)) ;; native json-rpc (executable-find "emacs-lsp-booster")) (progn (message "Using emacs-lsp-booster for %s!" orig-result) (cons "emacs-lsp-booster" orig-result)) orig-result))) (advice-add 'lsp-resolve-final-command :around #'lsp-booster--advice-final-command)
Tree-sitter useful stuff:
(use-package! tree-sitter :config ;;(setq +tree-sitter-hl-enabled-modes '(not web-mode typescript-tsx-mode)) (setq +tree-sitter-hl-enabled-modes t) (add-hook 'tree-sitter-after-on-hook #'tree-sitter-hl-mode)) ;; Configure ts-fold (use-package! ts-fold :after tree-sitter :config ;; See also: https://github.com/emacs-tree-sitter/elisp-tree-sitter/issues/286 ;; (global-ts-fold-indicators-mode 1) ;; <- Breaks Doom initialization ;; Set global keybindings for `ts-fold'. (map! :leader (:prefix-map ("z" . "fold") :desc "Fold all" "a" #'ts-fold-close-all :desc "Unfold all" "A" #'ts-fold-open-all :desc "Fold current" "f" #'ts-fold-close :desc "Unfold current" "F" #'ts-fold-open :desc "Toggle fold" "t" #'ts-fold-toggle)) (require 'ts-fold-indicators) :hook (prog-mode . ts-fold-indicators-mode)) (require 'line-reminder) (add-hook! 'prog-mode-hook #'line-reminder-mode) (setq line-reminder-show-option 'indicators)
Native Debug (GDB/LLDB)
Check dap-mode page for Rust.
Thanks to this section of Robert Krahn’s post, I learned that lldb-mi could be used to debug Rust code.
Clone the lldb-mi repo
Build with
cmake
, as instructed. For me, I had to do:LLVM_DIR=/opt/homebrew/Cellar/llvm/18.1.8/lib/cmake cmake . LLVM_DIR=/opt/homebrew/Cellar/llvm/18.1.8/lib/cmake cmake --build .
Copy the binary
lldb-mi
binary to/usr/local/bin (a directory in my ~$PATH
).Part of the setup should call
(dap-gdb-lldb-setup)
, that will do important setup for debugging.To debug your program, execute
M-x dap-hydra
, and select theRust::LLDB Run Configuration
.
Useful plugins for your gradle builds
I’m going to use this
Java-with-gradle project as an example. The complete
build.gradle.kts
file is as follows (after updating at the
moment of writing):
import org.checkerframework.gradle.plugin.CheckerFrameworkExtension
plugins {
id("java")
id("application")
id("com.github.ben-manes.versions") version "0.51.0"
id("io.freefair.lombok") version "8.10"
id("org.checkerframework") version "0.6.44"
}
group = "mx.oscarvarto"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
//maven(url = "https://projectlombok.org/edge-releases")
maven(url = "https://oss.sonatype.org/content/repositories/snapshots/")
}
lombok {
version = "1.18.34"
}
dependencies {
implementation("org.functionaljava:functionaljava:5.0")
implementation("com.google.guava:guava:33.3.0-jre")
implementation("ch.qos.logback:logback-classic:1.5.8")
implementation("org.slf4j:slf4j-api:2.1.0-alpha1")
implementation("org.graalvm.polyglot:polyglot:24.0.2")
implementation("org.graalvm.polyglot:python:24.0.2")
implementation("org.graalvm.polyglot:tools:24.0.2")
implementation("org.graalvm.polyglot:dap:24.0.2")
compileOnly("org.checkerframework:checker-qual:3.47.0")
testCompileOnly("org.checkerframework:checker-qual:3.47.0")
checkerFramework("org.checkerframework:checker:3.47.0")
implementation("org.checkerframework:checker-util:3.47.0")
testImplementation(platform("org.junit:junit-bom:5.11.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testImplementation("org.assertj:assertj-core:3.26.3")
compileOnly("org.projectlombok:lombok:1.18.34")
}
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
application {
applicationDefaultJvmArgs = listOf("-Astubs=collection-object-parameters-may-be-null.astub")
}
configure<CheckerFrameworkExtension> {
checkers = listOf(
"org.checkerframework.checker.nullness.NullnessChecker"
)
}
tasks.test {
useJUnitPlatform()
}
How to keep your dependencies updated in a gradle build
It is worth mentioning gradle-versions-plugin, that helps to determine which dependencies have updates.
There is a related interactive tool that uses
gradle-versions-plugin
called gradle-upgrade-interactive.
Automatic lombok and delombok configuration
For that the sample project uses io.freefrair.lombok.
Checker Framework
To use the Checker Framework with your gradle builds, you can rely on org.checkerframework plugin.
The Checker Framework
Quoting from the manual:
The Checker Framework enhances Java’s type system to make it more powerful and useful. This lets software developers detect and prevent errors in their Java programs.
A “checker” is a compile-time tool that warns you about certain errors or gives you a guarantee that those errors do not occur.
And from Section 1.2 of the manual
The Checker Framework lets you define new type systems and run them as a plug-in to the javac compiler. Your code stays completely backward-compatible: your code compiles with any Java compiler, it runs on any JVM, and your coworkers don’t have to use the enhanced type system if they don’t want to. You can check part of your program, or the whole thing.
How to use with maven and select which checkers to enable
To enable the checker framework with maven, you can find instructions in Section 37.13 of the manual.
For each checker you want to enable, you need to add it as an
annotation processor. For example, if you want to enable the
NullnessChecker, you would include it in the
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.checkerframework</groupId>
<artifactId>checker</artifactId>
<version>3.x.x</version>
</path>
</annotationProcessorPaths>
<annotationProcessors>
<annotationProcessor>org.checkerframework.checker.nullness.NullnessChecker</annotationProcessor>
<!-- Add other checkers here -->
</annotationProcessors>
<compilerArgs>
<arg>-Xmaxwarns</arg>
<arg>10000</arg>
</compilerArgs>
</configuration>
</plugin>
How to use with gradle
In an (maybe a little old now) project I wrote, I tried using the
Kotlin DSL for gradle, and got this working
(build.gradle.kts
):
import org.checkerframework.gradle.plugin.CheckerFrameworkExtension
plugins {
id("java")
id("application")
id("com.github.ben-manes.versions") version "0.51.0"
id("io.freefair.lombok") version "8.6"
id("org.checkerframework") version "0.6.37"
}
group = "mx.oscarvarto"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
//maven(url = "https://projectlombok.org/edge-releases")
maven(url = "https://oss.sonatype.org/content/repositories/snapshots/")
}
lombok {
version = "1.18.32"
}
dependencies {
implementation("org.functionaljava:functionaljava:5.0")
implementation("com.google.guava:guava:33.1.0-jre")
implementation("ch.qos.logback:logback-classic:1.5.3")
implementation("org.slf4j:slf4j-api:2.1.0-alpha1")
implementation("org.graalvm.polyglot:polyglot:24.0.0")
implementation("org.graalvm.polyglot:python:24.0.0")
implementation("org.graalvm.polyglot:tools:24.0.0")
implementation("org.graalvm.polyglot:dap:24.0.0")
compileOnly("org.checkerframework:checker-qual:3.43.0-SNAPSHOT")
testCompileOnly("org.checkerframework:checker-qual:3.43.0-SNAPSHOT")
checkerFramework("org.checkerframework:checker:3.43.0-SNAPSHOT")
implementation("org.checkerframework:checker-util:3.43.0-SNAPSHOT")
testImplementation(platform("org.junit:junit-bom:5.10.2"))
testImplementation("org.junit.jupiter:junit-jupiter")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testImplementation("org.assertj:assertj-core:3.25.3")
compileOnly("org.projectlombok:lombok:1.18.32")
}
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
application {
applicationDefaultJvmArgs = listOf("-Astubs=collection-object-parameters-may-be-null.astub")
}
configure<CheckerFrameworkExtension> {
checkers = listOf(
"org.checkerframework.checker.nullness.NullnessChecker"
)
}
tasks.test {
useJUnitPlatform()
}
Enforcing a Coding style with Spotless
Maven has an awesome plugin to enforce coding styles automatically. You can find information about it spotless-maven-plugin.
In order to not forget some of my work, I’ll put some of my conclusions and final configuration of the plugin.
<dependencies>
<!-- Less boilerplate-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<scope>provided</scope>
</dependency>
<!-- More dependencies here-->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>21</release>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<!-- Enforce Java Google formatting conventions -->
<plugin>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
<version>2.44.0.BETA2</version>
<configuration>
<formats>
<!-- you can define as many formats as you want, each is
independent -->
<format>
<!-- define the files to apply to -->
<includes>
<include>.gitattributes</include>
<include>.gitignore</include>
</includes>
<!-- define the steps to apply to those files -->
<trimTrailingWhitespace/>
<endWithNewline/>
<indent>
<tabs>true</tabs>
<spacesPerTab>4</spacesPerTab>
</indent>
</format>
</formats>
<!-- define a language-specific format -->
<java>
<encoding>UTF-8</encoding>
<eclipse>
<version>4.26</version>
<file>${project.basedir}/eclipse-java-google-style.xml</file>
</eclipse>
<importOrder>
<wildcardsLast>false</wildcardsLast>
<order> <!-- Static imports last-->
,\#
</order>
<semanticSort>true</semanticSort>
</importOrder>
<removeUnusedImports/>
<formatAnnotations/>
</java>
<m2eEnableForIncrementalBuild>true</m2eEnableForIncrementalBuild> <!-- this is false by default -->
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
<!-- Uncomment the following to make formatting mandatory -->
<!-- <phase>compile</phase> -->
</execution>
</executions>
</plugin>
</plugins>
</build>
You can find the eclipse-java-google-style.xml here (referenced here).
This is useful to call the formatter in the current Java buffer while
working in Emacs (relies on projectile
):
(defun apply-spotless-to-current-buffer ()
"Apply Spotless formatter to the current buffer."
(interactive)
(when (buffer-file-name)
(save-buffer)
(let* ((file (buffer-file-name))
(project-root (projectile-project-root)))
(if project-root
(let ((default-directory project-root))
(shell-command (format "mvn spotless:apply -DspotlessFiles=%s" file))
(revert-buffer t t t)
(message "Spotless applied to %s in project %s" file project-root))
(message "Could not find project root. Is Projectile installed and configured?")))))
(global-set-key (kbd "C-c s") 'apply-spotless-to-current-buffer)
Additionally, to run spotless, you can:
Use maven directly to check all files:
mvn spotless:check
.Use maven directly to apply (overwriting the files) to all files:
mvn spotless:apply
.Use maven to check only some (or current) files:
mvn spotless:apply -DspotlessFiles=my/file/pattern.java,more/generic/.*-pattern.java
If you use IntelliJ IDEA for Java programming, use spotless applier during your development.
Using reveal.js
for presentations
reveal.js is an open source HTML presentation framework with a lot of built-in capabilities, mature, and capable of producing stunning slides.
The final slides will be HTML, but there are several ways to write your contents. You can always write the HTML markup directly, but there is also the possibility to use other markups that, if you find that more simple to handle, and then use an exporter to transform your contents to HTML.
Using markdown to write your contents
Please refer to the manual for more information on this approach
Using emacs & org to write your contents
Once you have a working installation of emacs on your system, you can install emacs-reveal, and use org markup to write your presentation contents. The repository has a demo that you can leverage to learn how to use this solution. It will be benefitial to check the howto.
From the howto, copy the elisp
(it contains the important publish.el
file) folder inside your project root folder. You may want to:
Create a
public
folder that will contain the final HTML file with your presentation.(shallow) Clone reveal.js source code inside
public
folder. That will bring some css, javascript files that will help your presentation look (and behave) stunning. You don’t need all the files of this repo, but I haven’t take the time to find out a clean way to get only the minimum necessary.Generate your website (presentation) with:
emacs --batch --load elisp/publish.el
Preview your project:
firefox public/name-of-your-presentation.html
.
I should mention that, if you don’t want to install everything yourself, you can use a docker image (provided by the author of emacs-reveal) ready with all already installed for you, although I haven’t tried myself that option, so I don’t have experience using this.