build(distrib): add jdeploy packaging for native installers
Introduces native installer generation for Windows, macOS, and Linux using jDeploy. The packaging is driven by package.json and a GitHub Actions workflow that builds platform-specific bundles with embedded Azul Zulu FX JRE 26. Key additions: - package.json descriptor with jDeploy configuration targeting 7 platforms (excludes windows-arm64 due to missing Azul FX build) - jdeploy-maven-plugin behind a profile to sync descriptor fields from POM without requiring Node in default builds - GitHub workflow triggered by v* tags (versioned releases) or *-snapshot branches (rolling prereleases) - Version-free finalName (pholio.jar) so jar path stays stable - Launcher reads package-info.json from jdeploy tag for auto-update The spring-boot-maven-plugin's mainClass is the single source of truth for the entry point: it lands in the repackaged jar's manifest as Start-Class, which jDeploy's native launcher uses to bootstrap.
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
# Builds the native installers for Windows, macOS and Linux and publishes them as GitHub release assets.
|
||||
#
|
||||
# Two channels, distinguished by what was pushed:
|
||||
# * a `v*` tag -> a versioned release; the tag name minus the leading `v` becomes the app version.
|
||||
# * a `*-snapshot` branch -> a rolling prerelease named after the branch, replaced on every push.
|
||||
#
|
||||
# Auto-update needs nothing else from us. The jDeploy action writes a package-info.json to a `jdeploy` tag in
|
||||
# this repository, and the native launcher installed on a user's machine reads it on startup to notice a newer
|
||||
# release. That only works while the repository is public — a private repository needs a separate public
|
||||
# release-only repository passed as `target_repository`.
|
||||
name: jdeploy
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
branches:
|
||||
- '*-snapshot'
|
||||
# Lets the pipeline be exercised without cutting a tag. Running it on a branch produces the snapshot
|
||||
# prerelease for that branch, exactly as a push would.
|
||||
workflow_dispatch:
|
||||
|
||||
# The action rewrites a shared `jdeploy` tag, so two runs must never overlap. Queued rather than cancelled:
|
||||
# cancelling mid-upload is what leaves that tag half written.
|
||||
concurrency:
|
||||
group: jdeploy-release-${{ github.repository }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
bundle:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# Needed to create the release and to push the `jdeploy` tag that carries package-info.json.
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Zulu rather than Temurin: the JavaFX dependencies are pinned to a Zulu FX build locally, and Azul
|
||||
# publishes non-LTS feature releases, which Java 26 is.
|
||||
- name: Set up JDK 26
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '26'
|
||||
distribution: 'zulu'
|
||||
cache: maven
|
||||
|
||||
# The -Pjdeploy profile synchronises package.json by shelling out to `npm pkg set`, so Node has to be on
|
||||
# PATH before Maven runs, not just before the jDeploy action.
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
|
||||
- name: Build
|
||||
run: ./mvnw -B --no-transfer-progress -Pjdeploy package
|
||||
|
||||
- name: Build app installer bundles
|
||||
uses: shannah/jdeploy@v6.1.5
|
||||
with:
|
||||
github_token: ${{ github.token }}
|
||||
# Kept in step with jdeploy.cli.version in pom.xml. The action's own default lags its release tag.
|
||||
jdeploy_version: '6.1.5'
|
||||
|
||||
@@ -42,3 +42,9 @@ logs/
|
||||
*.mv.db
|
||||
*.trace.db
|
||||
|
||||
### jDeploy
|
||||
# Generated by the jDeploy CLI: the npm-shaped launcher bundle and the installer/release staging directory.
|
||||
jdeploy-bundle/
|
||||
jdeploy/
|
||||
node_modules/
|
||||
|
||||
|
||||
@@ -11,6 +11,35 @@ This project is a desktop application for images / pictures management. Like col
|
||||
* For packaging, use spring boot generated jar, and jdeploy to create OS package (jdk/jre included)
|
||||
* Use lombok when applicable (Constructor and getter / setter)
|
||||
|
||||
## Packaging & distribution (jDeploy)
|
||||
|
||||
Native installers with a bundled JRE are produced by jDeploy, driven from two files: `package.json` at the
|
||||
repository root (the descriptor) and `.github/workflows/jdeploy.yml` (the release pipeline).
|
||||
|
||||
* **Entry point.** jDeploy has no main-class setting — it runs the jar. So the entry point is declared once, as
|
||||
`spring-boot-maven-plugin`'s `<mainClass>${main.class}</mainClass>`, which lands in the repackaged jar's
|
||||
manifest as `Start-Class`. `jdeploy.jar` in `package.json` points at `target/pholio.jar`; `<finalName>` is
|
||||
version-free precisely so that path never needs updating.
|
||||
* **Runtime.** `jdeploy.javaVersion: "26"` with `jdeploy.javafx: true` provisions an Azul Zulu FX **JRE** per
|
||||
platform. `windows-arm64` is deliberately absent from `downloadPage.platforms`: Azul publishes no FX build for
|
||||
it at 26, so listing it would produce an installer that cannot start.
|
||||
* **Syncing the descriptor.** `mvn -Pjdeploy package` runs `jdeploy-maven-plugin:sync-package-json`, which
|
||||
copies version, name, description, title, jar path, Java version and the JavaFX flag from the POM into
|
||||
`package.json`. It works by shelling out to `npm pkg set`, which is why it sits behind a profile — a plain
|
||||
`mvn package` must not require Node. Every other key in `package.json` is hand-maintained.
|
||||
* **Releasing.** Push a `v<semver>` tag (the leading `v` is stripped to form the app version); the workflow
|
||||
builds the bundles and attaches them to that release. Pushing a `*-snapshot` branch publishes a rolling
|
||||
prerelease instead. Branch and tag names must be at most 16 characters of `[A-Za-z0-9._-]` — the jDeploy
|
||||
action silently skips anything else.
|
||||
* **Auto-update.** Handled entirely by the native launcher: it reads a `package-info.json` that the action
|
||||
writes to a `jdeploy` tag in this repository, and picks up a newer release on next start. This requires the
|
||||
repository to stay public; a private one needs a separate public release repository passed to the action as
|
||||
`target_repository`.
|
||||
* **Do not add `documentTypes`, `urlSchemes` or `singleton` without changing the launcher first.** jDeploy
|
||||
passes opened files and URIs to `main`, and `LaunchOptions` reads any unrecognised argument as a subcommand
|
||||
and switches to `HEADLESS`. File associations would therefore make a double-clicked photo start Pholio with
|
||||
no window.
|
||||
|
||||
## Testing
|
||||
* for unit test use AssertJ with SoftAssertion
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "pholio",
|
||||
"version": "0.1.0-SNAPSHOT",
|
||||
"description": "High-volume desktop photo and media library manager",
|
||||
"author": "Christophe Lallement",
|
||||
"repository": "https://github.com/Imag-In/pholio",
|
||||
"bin": {
|
||||
"pholio": "jdeploy-bundle/jdeploy.js"
|
||||
},
|
||||
"files": [
|
||||
"jdeploy-bundle"
|
||||
],
|
||||
"preferGlobal": true,
|
||||
"jdeploy": {
|
||||
"jar": "target/pholio.jar",
|
||||
"javaVersion": "26",
|
||||
"javafx": true,
|
||||
"jdk": false,
|
||||
"title": "Pholio",
|
||||
"args": [
|
||||
"-Djdeploy.file.limit=8192"
|
||||
],
|
||||
"downloadPage": {
|
||||
"platforms": [
|
||||
"mac-arm64",
|
||||
"mac-x64",
|
||||
"windows-x64",
|
||||
"linux-x64",
|
||||
"linux-arm64",
|
||||
"debian-x64",
|
||||
"debian-arm64"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,16 @@
|
||||
|
||||
<!-- caffeine, h2, flyway and jackson versions come from the Spring Boot 4.1.0 BOM. -->
|
||||
<main.class>io.pholio.PholioApplication</main.class>
|
||||
|
||||
<!-- ========================== Packaging =========================== -->
|
||||
<jdeploy-maven-plugin.version>1.0.8</jdeploy-maven-plugin.version>
|
||||
<!--
|
||||
The jDeploy CLI release that builds the native bundles. Kept in step with the version pinned in
|
||||
.github/workflows/jdeploy.yml on purpose: the CLI that generates package.json fields locally and the
|
||||
one that assembles the installers in CI must agree, or a locally-synced descriptor can carry keys the
|
||||
release build does not understand.
|
||||
-->
|
||||
<jdeploy.cli.version>6.1.5</jdeploy.cli.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -162,10 +172,22 @@
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<!--
|
||||
A version-free jar name. jDeploy points at the executable jar by path from package.json, so a name
|
||||
carrying the version would go stale on every release and have to be re-synced before the descriptor
|
||||
was usable. The installed Maven artifact keeps its normal versioned coordinates either way.
|
||||
-->
|
||||
<finalName>pholio</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<!--
|
||||
The repackaged jar is what jDeploy ships. Its manifest carries Main-Class=JarLauncher and
|
||||
Start-Class=${main.class}, which is how the native launcher finds the entry point: jDeploy has
|
||||
no main-class setting of its own, it runs the jar. So this mainClass is the single declaration
|
||||
of the entry point for the packaged application as well as for `java -jar`.
|
||||
-->
|
||||
<configuration>
|
||||
<mainClass>${main.class}</mainClass>
|
||||
</configuration>
|
||||
@@ -188,4 +210,48 @@
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<!--
|
||||
Keeps package.json in step with the POM before the native bundles are built.
|
||||
|
||||
Behind a profile rather than in the default build because the plugin does its work by shelling out to
|
||||
`npm pkg set` for every field, so it makes Node a hard requirement of the phase it runs in. A plain
|
||||
`mvn package` must stay buildable on a machine that has never seen npm; the release workflow activates
|
||||
this profile, and so should anyone about to run the jDeploy CLI by hand.
|
||||
|
||||
What it synchronises: version, name, description and jdeploy.title from the POM, jdeploy.jar from the
|
||||
build output, jdeploy.javaVersion from ${java.version}, and jdeploy.javafx from the presence of the
|
||||
org.openjfx dependencies. Everything else in package.json is hand-maintained and left untouched.
|
||||
-->
|
||||
<profile>
|
||||
<id>jdeploy</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>ca.weblite</groupId>
|
||||
<artifactId>jdeploy-maven-plugin</artifactId>
|
||||
<version>${jdeploy-maven-plugin.version}</version>
|
||||
<configuration>
|
||||
<jdeployVersion>${jdeploy.cli.version}</jdeployVersion>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sync-package-json</id>
|
||||
<!--
|
||||
Declared after spring-boot-maven-plugin's repackage, which is also bound to
|
||||
`package`: same-phase executions run in declaration order, and profile plugins
|
||||
come last, so the jar the descriptor points at already exists.
|
||||
-->
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>sync-package-json</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
|
||||
Reference in New Issue
Block a user