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
| Tool | Needed for |
|---|---|
| JDK 25 (Eclipse Temurin, for example) | every build; Gradle's toolchain support uses it |
| Git | getting the sources |
GraalVM Community 25.0.4.1.1, with GRAALVM_HOME set | native executables only (Native images) |
| Visual Studio 2022 Build Tools, C++ workload | native executables on Windows only |
| Docker | Linux native executables without a local GraalVM, the Docker image |
| WiX 5.0.2, nfpm | the 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:
- Windows
- macOS and Linux
.\gradlew.bat build
./gradlew 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-corecovered (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 fromopenapi.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:
| Option | Default | What it does |
|---|---|---|
test.excludedTags | interop,private-fixtures,perf | JUnit tags left out of the run |
test.tags | none | run only the tests with these tags |
test.forks | 4 | JVMs that run one module's test classes at once; 1 runs them one after the other |
test.maxHeap | 4g | heap of each test JVM, instead of Gradle's own 512 MiB |
test.parallelModules | no limit | modules whose tests run at the same time; 1 suits machines with few cores (the CI uses it on its four CPUs) |
njmule.<name> | none | passed to the tests as the system property njmule.<name>, for example -Pnjmule.interop.kadPort=14897 |
jacoco.minimum.line.coverage | 0.70 | the share of njmule-core's lines the tests must cover |
Run one test class:
./gradlew :njmule-core:test --tests '*TagCodecTest'
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
| Task | What it makes |
|---|---|
./gradlew :njmule-app:generateLicenseReport | build/reports/licenses/THIRD-PARTY.txt, the third-party licenses of the engine's runtime, which holds every distributed Java dependency |
./gradlew cyclonedxBom | the CycloneDX software bill of materials in build/reports/cyclonedx |
./gradlew dependencyCheckAggregate | the 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 iconTools | copies the JSVG jar the icon tools need into build/tools (see branding/README.md) |
./gradlew :njmule-app:nativeCompile | the native command-line executable (Native images) |
./gradlew :njmule-app:nativeService | the Windows service host njmule-service.exe (Native images) |
./gradlew :website:site | this site (Documentation) |
Related pages
- 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.