Skip to main content

Building

njMule builds with the Gradle Wrapper on a Java 25 toolchain. One command format-checks, compiles, analyses and tests every module, the interface included; another starts the headless engine straight from the build. This page covers that build and the desktop window; the native executables and the installers have pages of their own (Native images, Packaging).

Prerequisites​

ToolNeeded for
JDK 25 (Eclipse Temurin, for example)every build; Gradle's toolchain support uses it
Gitgetting the sources
GraalVM Community 25.0.4.1.1, with GRAALVM_HOME setnative executables only (Native images)
Visual Studio 2022 Build Tools, C++ workloadnative executables on Windows only
DockerLinux native executables without a local GraalVM, the Docker image
WiX 5.0.2, nfpmthe Windows MSI, the deb and rpm packages (Packaging)
rustup, which installs the Rust that njmule-desktop/src-tauri/rust-toolchain.toml pins; on Linux WebKitGTK 4.1 and GTK 3 with their headers (libwebkit2gtk-4.1-dev, libgtk-3-dev, librsvg2-dev, libayatana-appindicator3-dev, libxdo-dev on Debian and Ubuntu)the desktop window (:njmule-desktop:shell), not part of build

You do not install Gradle or Node.js yourself: the Gradle Wrapper (gradlew, gradlew.bat) downloads the Gradle version the project pins, and Gradle downloads the Node.js release that builds the interface, the desktop window's Tauri command line and this site, checked against its SHA-256 like every other download (Dependencies).

Build and test​

From the root of your clone:

.\gradlew.bat build

The rest of this guide writes ./gradlew; on Windows use .\gradlew.bat.

build runs, for every module:

  • Spotless: the Java sources must be formatted with palantir-java-format and start with the license header of config/license/header-java.txt, with LF line endings.
  • Checkstyle: the conventions the formatter does not cover, such as this. on fields (Code style); any warning fails the build.
  • Error Prone: compile-time bug patterns, on top of javac -Xlint:all.
  • The tests (JUnit), spread over several JVMs per module (Testing).
  • JaCoCo: coverage reports for every module, and at least 70% of the lines of njmule-core covered (jacoco.minimum.line.coverage). The test kit also aggregates one report over the end-to-end tests, which SonarQube Cloud reads too.
  • SpotBugs with FindSecBugs: maximum effort, medium confidence, on the production code; the few exclusions are in config/spotbugs/exclude.xml, each with its reason.
  • For the interface (njmule-ui): the API's types generated from openapi.yaml, a type check (vue-tsc) and the Vite bundle, the Vitest tests and the Prettier check (Interface).

The desktop window, which needs Rust, is not part of build (see below), nor is the documentation site (Documentation).

Format the sources​

Format before you commit, or the build stops at the first unformatted file:

./gradlew spotlessApply

The interface's TypeScript and Vue files are formatted with Prettier instead: run npm run format in njmule-ui (Interface). The desktop window's Rust code is formatted with cargo fmt in njmule-desktop/src-tauri.

Incremental builds​

Builds are incremental and cached: gradle.properties turns on parallel execution, the build cache and the configuration cache. With nothing changed, ./gradlew build takes about a second; a change re-runs only what depends on it. Git worktrees of the same clone share the build cache in ~/.gradle. You never need clean for a correct build. In CI the build cache lives apart, where NJMULE_BUILD_CACHE points: each build of main saves it, and pull requests start from it.

Run from the build​

The command line and headless engine, from the module path:

./gradlew :njmule-app:run --args="--help"

./gradlew :njmule-app:installDist also installs it with a launcher script in njmule-app/build/install/njmule/; run njmule-app/build/install/njmule/bin/njmule.

It uses your normal njMule profile. To keep your own settings and downloads out of the way, give it a profile of its own with --config-dir, for example ./gradlew :njmule-app:run --args="--config-dir /home/you/njmule-dev". The options are those of the command line. With a web interface password it also serves the interface in your browser (Interface).

The desktop window​

The desktop window is a Tauri 2 shell written in Rust, in njmule-desktop/src-tauri: it starts the native engine beside it, which opens a control endpoint for it, and shows the interface, which it embeds. Build both:

./gradlew :njmule-app:nativeCompile :njmule-desktop:shell

shell builds the interface (:njmule-ui:site) and runs tauri build with the Rust toolchain that njmule-desktop/src-tauri/rust-toolchain.toml pins. The window lands in njmule-desktop/src-tauri/target/release/njmule-gui (njmule-gui.exe on Windows); copy njmule-app/build/native/nativeCompile/njmule (njmule.exe, with the files beside it) next to it, or point NJMULE_CORE at the engine to start, and run it. --config-dir <folder> gives it a profile of its own.

Cargo checks the shell as the CI does, in njmule-desktop/src-tauri once ./gradlew :njmule-ui:site has built the interface it embeds:

cargo fmt --check
cargo clippy --locked --release --all-targets -- -D warnings
cargo test --locked --release

cargo fmt formats it at 120 columns (rustfmt.toml).

Test options​

Gradle properties (-P) choose which tests run and how:

OptionDefaultWhat it does
test.excludedTagsinterop,private-fixtures,perfJUnit tags left out of the run
test.tagsnonerun only the tests with these tags
test.forks4JVMs that run one module's test classes at once; 1 runs them one after the other
test.maxHeap4gheap of each test JVM, instead of Gradle's own 512 MiB
test.parallelModulesno limitmodules whose tests run at the same time; 1 suits machines with few cores (the CI uses it on its four CPUs)
njmule.<name>nonepassed to the tests as the system property njmule.<name>, for example -Pnjmule.interop.kadPort=14897
jacoco.minimum.line.coverage0.70the share of njmule-core's lines the tests must cover

Run one test class:

./gradlew :njmule-core:test --tests '*TagCodecTest'
PowerShell

PowerShell splits an unquoted argument at its first dot, so -Ptest.forks=1 reaches Gradle as two arguments. Quote every such option: '-Ptest.forks=1', '-Ptest.tags=perf'.

The tags, the test kit and the interface's tests are explained in Testing.

Other tasks​

TaskWhat it makes
./gradlew :njmule-app:generateLicenseReportbuild/reports/licenses/THIRD-PARTY.txt, the third-party licenses of the engine's runtime, which holds every distributed Java dependency
./gradlew cyclonedxBomthe CycloneDX software bill of materials in build/reports/cyclonedx
./gradlew dependencyCheckAggregatethe OWASP dependency-check report; fails on a vulnerability scored 7 or more; needs an NVD API key in NVD_API_KEY
./gradlew build sonar -Dsonar.token=<token>a SonarQube Cloud analysis (organisation njmule, project njMule_njMule); in CI the Linux verify job runs it once the SONAR_TOKEN secret is set; it waits for the quality gate and fails when the gate is red
./gradlew iconToolscopies the JSVG jar the icon tools need into build/tools (see branding/README.md)
./gradlew :njmule-app:nativeCompilethe native command-line executable (Native images)
./gradlew :njmule-app:nativeServicethe Windows service host njmule-service.exe (Native images)
./gradlew :website:sitethis site (Documentation)
  • Dependencies: versions, lock files and checksums.
  • Testing: writing and running tests, and what the CI runs.
  • Code style: what Spotless and Checkstyle enforce, and what they cannot.