Build, testing and native image

After 0.0.8. Release 0.0.8 has only the release.yml workflow. It runs the tests on Linux, and starts each executable once without arguments. The build.yml workflow and the tests and smoke tests on all three systems are on the master branch.

Hedgehog is a program that people run to take part in the Unigrid network. This page describes how that program is built from source, how it is tested, and how it reaches users as a single download for Linux, macOS and Windows that runs without installing Java first. For what the program does once it runs, see Architecture overview.

What gets built

The build is a Maven project of three modules. Maven is the standard build tool of the Java world, and the project requires Java 25 or newer; the build refuses to start on an older version.

Module Role
application The program itself: command line, network, REST interface and sporks. It holds all the tests.
common A very small library that knows the program’s name, version and per-user directories.
native-image The launcher that packages the program as one executable per operating system.

One mvn clean install produces three kinds of output:

  • A thin jar, which holds only Hedgehog’s own code.
  • A fat jar, which holds Hedgehog and every library it needs. It runs anywhere with Java 25 through java -jar, and it is the download for platforms without an executable, such as Intel Macs.
  • A native executable for Linux, macOS on Apple Silicon and Windows.

Why the native executable is not truly native

The executable is built with GraalVM, a compiler that turns Java into machine code. Hedgehog cannot be compiled that way as a whole, because it relies on a dependency-injection container (CDI container and component lifecycle) that inspects and builds objects at run time, which GraalVM’s ahead-of-time analysis does not support without extensive configuration. The project therefore takes another route: GraalVM compiles a small launcher, and the launcher carries a trimmed-down Java runtime and the real program inside it.

flowchart LR
    C[common] --> A[application]
    A -->|assembly| FAT[fat jar]
    A --> N[native-image]
    C --> N
    N -->|jlink| Z[trimmed Java runtime<br/>and Hedgehog as a zip]
    Z -->|embedded by GraalVM| B[hedgehog.bin / hedgehog.exe]

The first time the executable starts, the launcher unpacks the embedded runtime into the user’s data directory, in a folder named after the SHA-1 hash of the bundle, and then starts Hedgehog on it with the arguments the user gave. A later build has a different hash and unpacks next to the old copy instead of overwriting it, and --force-unpack repeats the unpacking on demand. The cost is size: one earlier Linux build came to about 109 MB for the executable and roughly 175 MB on disk once unpacked.

Running it

Fact Value
Commands daemon (a network node), cli, util and bootstrap
Peer-to-peer port 52883, listening on 0.0.0.0 by default
REST port 52884, listening on localhost by default
Needed to build Java 25 and Maven; the native build also needs GraalVM 25 in GRAALVM_HOME
Native targets Linux x86_64, macOS Apple Silicon, Windows x86_64

A node is started with hedgehog daemon, or with java -jar on the fat jar. The ports are explained in Peer-to-peer network protocol and REST interface.

How it is tested

Almost every test is a property-based test written with jqwik: instead of a handful of fixed examples, the test states a rule and the framework tries it against many generated inputs. This suits a network daemon well. For example, every network packet type has a test that encodes generated packets, decodes them again and checks that nothing was lost or left unread (Peer-to-peer network protocol).

Many tests start real servers. The test harness brings up as many as twenty independent nodes inside one test run, each with its own container, so that handlers, schedules and the REST interface are exercised over real connections rather than against stand-ins. The tests replace the user’s config, data and log directories with temporary ones, so a test run never touches a developer’s real spork database.

Code style is enforced as part of the build. The Checkstyle rules in checkstyle.xml run at the verify step and fail the build on a violation, so mvn install and mvn verify check style while mvn test alone does not.

Continuous integration and releases

Two GitHub workflows cover the work:

  • build.yml builds and tests every push and pull request to master on Linux, and publishes the test coverage figure shown on the project page.
  • release.yml runs when a tag such as v0.0.8 is pushed. It tests on Linux, macOS and Windows, builds the native executable on each of the three systems, smoke-tests it by starting a node and asking it for its version over REST, and prepares a draft release.

A draft becomes a release on the maintainer’s machine with release.sh. There the assets are signed with the Unigrid Foundation release key, whose public half is release-key.asc in the repository, and a signed chain snapshot is attached (Legacy chain snapshot). The signing steps are described in the repository README.md under Releases.

License

The code is under the GNU Affero General Public License version 3, together with an addendum from The Unigrid Foundation that adds attribution terms. Among them, the software may not be used to run a separate network that communicates independently of the Hedgehog network, and the Foundation must stay visibly credited in the code and in the command-line header. The terms are in COPYING and COPYING.addendum.

In the source

  • pom.xml - the parent build: modules, pinned versions, the Java 25 requirement.
  • application/pom.xml - the program’s dependencies, the fat jar and the test configuration.
  • application/src/test/java/org/unigrid/hedgehog/jqwik/ - the test harness that boots nodes and mocks.
  • native-image/src/main/java/org/unigrid/hedgehog/nativeimage/NativeImage.java - the launcher.
  • native-image/src/main/java/org/unigrid/hedgehog/nativeimage/BundleFeature.java - embeds the runtime.
  • .github/workflows/build.yml and .github/workflows/release.yml - the two workflows.
  • release.sh - cutting and publishing a release.
  • checkstyle.xml - the style rules the build enforces.