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:

  1. Java Google code rules
  2. Lombok/delombok with io.freefair.lombok gradle plugin.
  3. 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

September 21, 2024






A simple CLI with Rust

This post will serve my memory for two purposes:

  1. How to write simple CLI parsers (in Rust!!) to further execute some other task.
  2. 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:

  1. 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.
  2. (Optionally) Which is the xml file specifying the tests that will be executed by TestNG. No provided value means execute all tests”.
  3. Which browser will be used. Valid options are:
    • Chrome (the default)
    • Firefox
    • Edge
    • Safari
  4. Maximum timeouts for the Selenium WebDriver (in seconds). Default is 5 seconds.
  5. 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

  1. Enable lsp, rust and tree-sitter as well as debugger in init.el. Some excerpts of init.el:

    :tools
    (debugger +lsp)
    (lsp +peek)
    (tree-sitter)
    
    :lang
    (rust +lsp +tree-sitter)
    
  2. 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")
    
  3. 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)))
    
  4. 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)
    
  5. Adding rustic to package.el:

    (package! rustic :recipe (:repo "emacs-rustic/rustic"))
    
  6. 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")
    
  7. 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)
    
  1. 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)

  1. Check dap-mode page for Rust.

  2. 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 .
      
  3. Copy the binary lldb-mi binary to /usr/local/bin (a directory in my ~$PATH).

  4. Part of the setup should call (dap-gdb-lldb-setup), that will do important setup for debugging.

  5. To debug your program, execute M-x dap-hydra, and select the Rust::LLDB Run Configuration.

September 21, 2024






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.

September 7, 2024






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 list.

<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()
}

September 7, 2024






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:

  1. Use maven directly to check all files: mvn spotless:check.

  2. Use maven directly to apply (overwriting the files) to all files: mvn spotless:apply.

  3. Use maven to check only some (or current) files:

    mvn spotless:apply -DspotlessFiles=my/file/pattern.java,more/generic/.*-pattern.java
    
  4. If you use IntelliJ IDEA for Java programming, use spotless applier during your development.

September 6, 2024






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:

  1. Create a public folder that will contain the final HTML file with your presentation.

  2. (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.

  3. Generate your website (presentation) with:

    emacs --batch --load elisp/publish.el
  4. 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.

September 6, 2024