The load-time Byte Buddy agent cost ~420 ms on every launch before the splash screen could show. FxThreadPlugin now weaves @FxThread at build time (byte-buddy-maven-plugin, process-classes) with the same inlined advice and method matcher as the agent, and marks each woven class @FxThreadWoven. FxThreadAgent.install() only attaches when the application's classes run from a directory (an IDE run), where an incremental compiler may have overwritten woven classes, and then weaves only the classes without the marker, so nothing is woven twice; -Dpholio.fxthread.agent=always|never overrides the choice. A Maven-built jar starts with no agent: the splash's first paint moves from ~0.83 s to ~0.47 s. The measurements behind this are in .claude/plans/startup-time-analysis.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011xpLSeYKKHX6o16jzYLgZv
973 lines
54 KiB
XML
973 lines
54 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
<modelVersion>4.0.0</modelVersion>
|
|
|
|
<parent>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-starter-parent</artifactId>
|
|
<version>4.1.1</version>
|
|
<relativePath/>
|
|
</parent>
|
|
|
|
<groupId>org.icroco.pholio</groupId>
|
|
<artifactId>pholio</artifactId>
|
|
<version>2026.9.0</version>
|
|
<name>Pholio</name>
|
|
<description>High-volume desktop photo and media library manager</description>
|
|
|
|
<properties>
|
|
<archunit.version>1.5.0</archunit.version>
|
|
<!--
|
|
Extra themes for AtlantaFx, from DLSC. Pinned to a release present in the local
|
|
repository so an offline build keeps working; Maven Central carries newer ones.
|
|
-->
|
|
<atlantafx-themes.version>1.9.0</atlantafx-themes.version>
|
|
|
|
<atlantafx.version>2.1.0</atlantafx.version>
|
|
<!--
|
|
Bytecode instrumentation for @FxThread (org.icroco.pholio.ui.common): woven at build time
|
|
by FxThreadPlugin (byte-buddy-maven-plugin below), with FxThreadAgent as a load-time safety
|
|
net for classes run from a directory, which an IDE may have recompiled unwoven — see
|
|
FxThread's own javadoc.
|
|
-->
|
|
<byte-buddy.version>1.18.12</byte-buddy.version>
|
|
<commons-codec.version>1.22.1</commons-codec.version>
|
|
<commons-imaging.version>1.0.0-alpha6</commons-imaging.version>
|
|
<!--
|
|
Scene-graph inspector, wired in by DevTools and dormant unless pholio.dev-tools.enabled is
|
|
set. Built for Java 21 / JavaFX 23; Pholio runs it on 26 in classpath mode, so it relies on
|
|
binary backward compatibility. DevTools catches a linkage failure rather than letting one
|
|
reach the user.
|
|
-->
|
|
<devtoolsfx.version>1.0.1</devtoolsfx.version>
|
|
<errorprone.version>2.50.0</errorprone.version>
|
|
<exec-maven-plugin.version>3.6.4</exec-maven-plugin.version>
|
|
<gatherers4j.version>0.14.0</gatherers4j.version>
|
|
<gemsfx.version>4.4.5</gemsfx.version>
|
|
<ikonli.version>12.4.0</ikonli.version>
|
|
<jSystemThemeDetector.version>3.9.1</jSystemThemeDetector.version>
|
|
<!--
|
|
JavaFX 26 is compiled for Java 24 (class file major 68), so a JDK >= 24 is required at runtime
|
|
regardless. The local toolchain is Zulu 26.0.1.fx; drop this to 25 if a CI runner or packaging
|
|
target only has the LTS available.
|
|
-->
|
|
<java.version>26</java.version>
|
|
|
|
<!--
|
|
Pinned to 26.0.1 to match the JavaFX modules bundled in the Zulu FX JDK. In classpath mode
|
|
the JDK's javafx.* modules are resolved into the boot layer and shadow these jars, so
|
|
keeping the versions identical avoids a split-version mismatch. The jars are still required
|
|
for packaging on JDKs that do not bundle JavaFX.
|
|
-->
|
|
<javafx.version>27</javafx.version>
|
|
|
|
<!-- ========================== 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>
|
|
<jilt.version>1.9.2</jilt.version>
|
|
<!--
|
|
Micro-benchmarks live under src/test, test-scoped, run explicitly — see
|
|
FileHasherBenchmark's javadoc for how — never as part of `mvn test`.
|
|
-->
|
|
<jmh.version>1.37</jmh.version>
|
|
<lombok.version>1.18.48</lombok.version>
|
|
|
|
<!-- caffeine, h2, flyway and jackson versions come from the Spring Boot 4.1.0 BOM. -->
|
|
<main.class>org.icroco.pholio.PholioApplication</main.class>
|
|
<!-- Spring Boot 4.1's BOM does not manage MapStruct, so it is pinned here directly. -->
|
|
<mapstruct.version>1.7.0.Beta2</mapstruct.version>
|
|
<maven-compiler-plugin.version>3.15.0</maven-compiler-plugin.version>
|
|
<maven-enforcer-plugin.version>3.6.3</maven-enforcer-plugin.version>
|
|
<metadata-extractor.version>2.21.0</metadata-extractor.version>
|
|
<nullaway.version>0.14.0</nullaway.version>
|
|
<!--
|
|
Local face/animal recognition (YuNet detection, SFace embedding, YOLOX-Nano animal
|
|
detection) runs on Microsoft's own ONNX Runtime Java API directly, not through Deep
|
|
Java Library: every model here needs fully custom pre/post-processing (letterboxing,
|
|
anchor decoding, NMS, 5-point face alignment), so DJL's Translator/ZooModel machinery
|
|
would add an abstraction layer with nothing left for it to actually do. This jar bundles
|
|
native libraries for every major desktop platform in one artifact — no per-OS classifier,
|
|
no runtime download.
|
|
-->
|
|
<onnxruntime.version>1.21.1</onnxruntime.version>
|
|
<picocli.version>4.7.7</picocli.version>
|
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
|
<sortpom-maven-plugin.version>4.0.0</sortpom-maven-plugin.version>
|
|
<threeten-extra.version>1.10.0</threeten-extra.version>
|
|
<twelvemonkeys.version>3.15.2</twelvemonkeys.version>
|
|
<versions-maven-plugin.version>2.22.0</versions-maven-plugin.version>
|
|
<!-- BSD-3-Clause. The real RDF/XMP packet builder/parser (mwg-rs face regions, dc:subject) — Commons
|
|
Imaging has no XMP support of its own, only EXIF/TIFF. -->
|
|
<xmpcore.version>6.1.11</xmpcore.version>
|
|
</properties>
|
|
|
|
<dependencies>
|
|
<?SORTPOM IGNORE?>
|
|
<!-- ============================ Spring ============================ -->
|
|
<dependency>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-starter</artifactId>
|
|
</dependency>
|
|
<!-- <dependency>-->
|
|
<!-- <groupId>org.springframework.boot</groupId>-->
|
|
<!-- <artifactId>spring-boot-starter-jdbc</artifactId>-->
|
|
<!-- </dependency>-->
|
|
<dependency>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-starter-data-jdbc</artifactId>
|
|
</dependency>
|
|
<!--
|
|
spring-boot-starter-validation is deliberately absent. Hibernate Validator probes the classpath
|
|
for JavaFX at startup to register its value extractors, which loads ~25 javafx.base interfaces
|
|
into a headless CLI run for no benefit. Nothing uses Bean Validation yet; if
|
|
@ConfigurationProperties constraints are wanted later, add it back knowing this side effect.
|
|
|
|
Note the qualifier "for no benefit": the CLI does now load javafx.base, because PreferenceItem
|
|
holds its value in a javafx.beans ObjectProperty so the settings view, the theme manager and the
|
|
locale service can all react to a change without an event bus in between. That is a paid-for
|
|
trade, not an accident. javafx.base is pure Java and needs no graphics stack; what must stay out
|
|
of the headless path is javafx.graphics and above, which is what ArchitectureRulesTest's
|
|
infrastructureTouchesOnlyJavaFxProperties and cliIsFreeOfJavaFx enforce.
|
|
-->
|
|
|
|
<!-- ========================= Persistence ========================== -->
|
|
<dependency>
|
|
<groupId>com.h2database</groupId>
|
|
<artifactId>h2</artifactId>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.flywaydb</groupId>
|
|
<artifactId>flyway-core</artifactId>
|
|
</dependency>
|
|
<!--
|
|
As of Boot 4.1, Flyway's autoconfiguration (used by the test datasource, where routing is off) no
|
|
longer ships inside spring-boot-autoconfigure — it is this separate module. The production path,
|
|
one migration per library pool, calls org.flywaydb.core.Flyway directly and does not need it, but
|
|
the plain single-DataSource path (tests, and any future run with routing disabled) does.
|
|
-->
|
|
<dependency>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-flyway</artifactId>
|
|
</dependency>
|
|
|
|
<!-- ===================== Preferences (YAML) ======================= -->
|
|
<!-- Jackson 3 (tools.jackson.*) — the default in Spring Boot 4.x. -->
|
|
<dependency>
|
|
<groupId>tools.jackson.dataformat</groupId>
|
|
<artifactId>jackson-dataformat-yaml</artifactId>
|
|
</dependency>
|
|
|
|
<!-- ============================ JavaFX ============================ -->
|
|
<!-- Platform classifier is resolved automatically by the openjfx POMs. -->
|
|
<dependency>
|
|
<groupId>org.openjfx</groupId>
|
|
<artifactId>javafx-controls</artifactId>
|
|
<version>${javafx.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.openjfx</groupId>
|
|
<artifactId>javafx-graphics</artifactId>
|
|
<version>${javafx.version}</version>
|
|
</dependency>
|
|
<!-- javafx-swing provides SwingFXUtils, needed to bridge ImageIO <-> FX Image. -->
|
|
<dependency>
|
|
<groupId>org.openjfx</groupId>
|
|
<artifactId>javafx-swing</artifactId>
|
|
<version>${javafx.version}</version>
|
|
</dependency>
|
|
|
|
<!-- ======================== UI toolkit =========================== -->
|
|
<dependency>
|
|
<groupId>io.github.mkpaz</groupId>
|
|
<artifactId>atlantafx-base</artifactId>
|
|
<version>${atlantafx.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<!-- Twenty-five more AtlantaFx themes, all implementing atlantafx.base.theme.Theme. -->
|
|
<groupId>com.dlsc.atlantafx</groupId>
|
|
<artifactId>themes</artifactId>
|
|
<version>${atlantafx-themes.version}</version>
|
|
<exclusions>
|
|
<!--
|
|
This artifact is built against atlantafx-base 2.0.1. The direct declaration above already
|
|
wins by proximity, but excluding it states the intent and makes a future version bump fail
|
|
loudly rather than silently downgrade the base. AppThemeTest loads all 32 themes, which is
|
|
what proves the cross-version binary compatibility this relies on.
|
|
-->
|
|
<exclusion>
|
|
<groupId>io.github.mkpaz</groupId>
|
|
<artifactId>atlantafx-base</artifactId>
|
|
</exclusion>
|
|
<!-- SASS sources, used to build those themes and of no use at runtime. -->
|
|
<exclusion>
|
|
<groupId>io.github.mkpaz</groupId>
|
|
<artifactId>atlantafx-styles</artifactId>
|
|
</exclusion>
|
|
</exclusions>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>io.github.mkpaz</groupId>
|
|
<artifactId>devtoolsfx-gui</artifactId>
|
|
<version>${devtoolsfx.version}</version>
|
|
<!--
|
|
devtoolsfx-connector declares its own test libraries at compile scope, so JUnit 6 and AssertJ
|
|
(~2.8 MB) would otherwise ship in the application. None of its classes reference either.
|
|
-->
|
|
<exclusions>
|
|
<exclusion>
|
|
<groupId>org.junit.jupiter</groupId>
|
|
<artifactId>*</artifactId>
|
|
</exclusion>
|
|
<exclusion>
|
|
<groupId>org.junit.platform</groupId>
|
|
<artifactId>*</artifactId>
|
|
</exclusion>
|
|
<exclusion>
|
|
<groupId>org.assertj</groupId>
|
|
<artifactId>assertj-core</artifactId>
|
|
</exclusion>
|
|
</exclusions>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.kordamp.ikonli</groupId>
|
|
<artifactId>ikonli-javafx</artifactId>
|
|
<version>${ikonli.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.kordamp.ikonli</groupId>
|
|
<artifactId>ikonli-feather-pack</artifactId>
|
|
<version>${ikonli.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<!-- Material2AL: the home/chevron icons on StatusBar's breadcrumbs. -->
|
|
<groupId>org.kordamp.ikonli</groupId>
|
|
<artifactId>ikonli-material2-pack</artifactId>
|
|
<version>${ikonli.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.kordamp.ikonli</groupId>
|
|
<artifactId>ikonli-materialdesign2-pack</artifactId>
|
|
<version>${ikonli.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>com.github.dansoftowner</groupId>
|
|
<artifactId>jSystemThemeDetector</artifactId>
|
|
<version>${jSystemThemeDetector.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>com.ginsberg</groupId>
|
|
<artifactId>gatherers4j</artifactId>
|
|
<version>${gatherers4j.version}</version>
|
|
</dependency>
|
|
|
|
<!-- ==================== Imaging & metadata ======================= -->
|
|
<dependency>
|
|
<groupId>com.drewnoakes</groupId>
|
|
<artifactId>metadata-extractor</artifactId>
|
|
<version>${metadata-extractor.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.apache.commons</groupId>
|
|
<artifactId>commons-imaging</artifactId>
|
|
<version>${commons-imaging.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>com.adobe.xmp</groupId>
|
|
<artifactId>xmpcore</artifactId>
|
|
<version>${xmpcore.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
|
<artifactId>imageio-webp</artifactId>
|
|
<version>${twelvemonkeys.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
|
<artifactId>imageio-tiff</artifactId>
|
|
<version>${twelvemonkeys.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>commons-codec</groupId>
|
|
<artifactId>commons-codec</artifactId>
|
|
<version>${commons-codec.version}</version>
|
|
</dependency>
|
|
|
|
<!-- ==================== Local ML (recognition) ==================== -->
|
|
<dependency>
|
|
<groupId>com.microsoft.onnxruntime</groupId>
|
|
<artifactId>onnxruntime</artifactId>
|
|
<version>${onnxruntime.version}</version>
|
|
</dependency>
|
|
<!-- =========================== Caching =========================== -->
|
|
<dependency>
|
|
<groupId>com.github.ben-manes.caffeine</groupId>
|
|
<artifactId>caffeine</artifactId>
|
|
</dependency>
|
|
<!--
|
|
Brings spring-context-support (CaffeineCacheManager) and CacheAutoConfiguration, which builds
|
|
that manager from spring.cache.* in application.yaml once spring.cache.type=caffeine is set —
|
|
see CacheConfig for why nothing here hand-wires a CacheManager bean the way PersistenceConfiguration
|
|
and ExecutorConfig do for the datasource and thread pools.
|
|
-->
|
|
<dependency>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-starter-cache</artifactId>
|
|
</dependency>
|
|
|
|
<!-- ============================= CLI ============================= -->
|
|
<dependency>
|
|
<groupId>info.picocli</groupId>
|
|
<artifactId>picocli</artifactId>
|
|
<version>${picocli.version}</version>
|
|
</dependency>
|
|
|
|
<!-- ======================= Code generation ======================= -->
|
|
<!--
|
|
Version managed by the Spring Boot 4.1.0 BOM (1.18.46).
|
|
|
|
`provided` keeps Lombok off the runtime classpath while leaving it on both the main and test compile
|
|
paths. It does not, on its own, keep it out of the packaged application: spring-boot:repackage bundles
|
|
provided-scope artifacts too, so the jar needs a matching <excludes> — see spring-boot-maven-plugin
|
|
below. The annotations are erased at compile time and the processor itself is supplied through
|
|
maven-compiler-plugin's annotationProcessorPaths, so nothing here is needed at runtime.
|
|
-->
|
|
<dependency>
|
|
<groupId>org.projectlombok</groupId>
|
|
<artifactId>lombok</artifactId>
|
|
<scope>provided</scope>
|
|
<version>${lombok.version}</version>
|
|
</dependency>
|
|
<!-- The @Builder annotation itself, source-retention only — the processor is wired below, next to Lombok's. -->
|
|
<dependency>
|
|
<groupId>cc.jilt</groupId>
|
|
<artifactId>jilt</artifactId>
|
|
<version>${jilt.version}</version>
|
|
<scope>provided</scope>
|
|
</dependency>
|
|
<!-- Maps entities to domain records and back; the processor is wired below, next to Lombok's. -->
|
|
<dependency>
|
|
<groupId>org.mapstruct</groupId>
|
|
<artifactId>mapstruct</artifactId>
|
|
<version>${mapstruct.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.threeten</groupId>
|
|
<artifactId>threeten-extra</artifactId>
|
|
<version>${threeten-extra.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>com.dlsc.gemsfx</groupId>
|
|
<artifactId>gemsfx</artifactId>
|
|
<version>${gemsfx.version}</version>
|
|
</dependency>
|
|
<!-- =================== Bytecode instrumentation =================== -->
|
|
<!--
|
|
Compile scope, not provided: FxThreadInterceptor's Advice annotations are compiled into its method
|
|
metadata, and FxThreadAgent needs both artifacts at actual runtime to self-attach and weave —
|
|
this is not a build-time-only tool.
|
|
-->
|
|
<dependency>
|
|
<groupId>net.bytebuddy</groupId>
|
|
<artifactId>byte-buddy</artifactId>
|
|
<version>${byte-buddy.version}</version>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>net.bytebuddy</groupId>
|
|
<artifactId>byte-buddy-agent</artifactId>
|
|
<version>${byte-buddy.version}</version>
|
|
</dependency>
|
|
|
|
<!-- ============================ Tests =========================== -->
|
|
<dependency>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-starter-test</artifactId>
|
|
<scope>test</scope>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>com.tngtech.archunit</groupId>
|
|
<artifactId>archunit-junit5</artifactId>
|
|
<version>${archunit.version}</version>
|
|
<scope>test</scope>
|
|
</dependency>
|
|
<!--
|
|
Micro-benchmarks (FileHasherBenchmark). jmh-generator-annprocess is also wired as an
|
|
annotation processor below — without that path, @Benchmark methods compile but JMH's Runner
|
|
finds nothing at run time, since the generated *_jmhTest/*_jmhType classes it actually invokes
|
|
never get produced.
|
|
-->
|
|
<dependency>
|
|
<groupId>org.openjdk.jmh</groupId>
|
|
<artifactId>jmh-core</artifactId>
|
|
<version>${jmh.version}</version>
|
|
<scope>test</scope>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.openjdk.jmh</groupId>
|
|
<artifactId>jmh-generator-annprocess</artifactId>
|
|
<version>${jmh.version}</version>
|
|
<scope>test</scope>
|
|
</dependency>
|
|
<?SORTPOM RESUME?>
|
|
</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>
|
|
<!--
|
|
Lombok is compile-time only, but repackage bundles provided-scope artifacts by default, so
|
|
without this the installer ships 2 MB of annotation processor that can never run.
|
|
-->
|
|
<excludes>
|
|
<exclude>
|
|
<groupId>org.projectlombok</groupId>
|
|
<artifactId>lombok</artifactId>
|
|
</exclude>
|
|
<!-- Same reason as Lombok: provided scope, source-retention annotations only (1.6 MB). -->
|
|
<exclude>
|
|
<groupId>cc.jilt</groupId>
|
|
<artifactId>jilt</artifactId>
|
|
</exclude>
|
|
</excludes>
|
|
<!--
|
|
OpenJFX jars are not shipped either (~9 MB). Every runtime this jar is meant for already
|
|
carries JavaFX as modules, which shadow class-path jars anyway: the JDK 26 FX the README
|
|
requires, the jlink'ed runtime of the jpackage installers, jDeploy's FX JRE. They were
|
|
also only ever the build machine's own natives (mac-aarch64 when built on a Mac), so they
|
|
could never have served another OS. Tests and IDE runs still get them from the Maven
|
|
classpath; only the repackaged jar drops them.
|
|
-->
|
|
<excludeGroupIds>org.openjfx</excludeGroupIds>
|
|
<!--
|
|
FxThreadAgent.install() self-attaches Byte Buddy at startup (see that class's own
|
|
javadoc for why this is a runtime agent rather than build-time weaving). Left nested
|
|
the way every other dependency is, `java -jar` on this repackaged jar throws
|
|
ClassNotFoundException: net.bytebuddy.dynamic.Nexus the moment any org.icroco.pholio.*
|
|
class loads after the agent installs: Byte Buddy's self-attach writes its own helper
|
|
jars to real temp files when it can locate itself as one, and a nested jar-in-a-jar
|
|
entry (BOOT-INF/lib/byte-buddy*.jar) is not one. Only java -jar is affected — the
|
|
plain classpath the IDE and the test JVM both run from already has these as real
|
|
files, which is exactly why this never showed up until jdeploy's packaged installer
|
|
(built from this same repackaged jar) was actually run. requiresUnpack is Spring
|
|
Boot's documented fix for precisely this class of problem: it flags these two entries
|
|
so the loader extracts them to a real temp directory before Byte Buddy ever looks for
|
|
them, at the one-time cost of that extraction on first launch.
|
|
-->
|
|
<requiresUnpack>
|
|
<dependency>
|
|
<groupId>net.bytebuddy</groupId>
|
|
<artifactId>byte-buddy</artifactId>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>net.bytebuddy</groupId>
|
|
<artifactId>byte-buddy-agent</artifactId>
|
|
</dependency>
|
|
</requiresUnpack>
|
|
</configuration>
|
|
<executions>
|
|
<!-- META-INF/build-info.properties: build.version is shown on the splash screen (SplashPreloader). -->
|
|
<execution>
|
|
<goals>
|
|
<goal>build-info</goal>
|
|
</goals>
|
|
</execution>
|
|
</executions>
|
|
</plugin>
|
|
<plugin>
|
|
<groupId>org.apache.maven.plugins</groupId>
|
|
<artifactId>maven-enforcer-plugin</artifactId>
|
|
<version>${maven-enforcer-plugin.version}</version>
|
|
<executions>
|
|
<execution>
|
|
<id>enforce-maven</id>
|
|
<goals>
|
|
<goal>enforce</goal>
|
|
</goals>
|
|
<configuration>
|
|
<rules>
|
|
<requireMavenVersion>
|
|
<version>3.9</version>
|
|
</requireMavenVersion>
|
|
</rules>
|
|
</configuration>
|
|
</execution>
|
|
</executions>
|
|
</plugin>
|
|
<plugin>
|
|
<groupId>org.apache.maven.plugins</groupId>
|
|
<artifactId>maven-compiler-plugin</artifactId>
|
|
<version>${maven-compiler-plugin.version}</version>
|
|
<configuration>
|
|
<!--
|
|
Lombok has to be named here, not just declared as a dependency. Since JDK 23 javac no
|
|
longer discovers annotation processors on the compile classpath, so a bare dependency
|
|
silently does nothing and every @Getter class fails with "variable not initialized in the
|
|
default constructor". Declaring the path also keeps the processor set explicit rather than
|
|
whatever happens to be on the classpath.
|
|
|
|
lombok.version comes from the Spring Boot BOM, so the processor and the dependency can
|
|
never drift apart.
|
|
-->
|
|
<annotationProcessorPaths>
|
|
<path>
|
|
<groupId>org.projectlombok</groupId>
|
|
<artifactId>lombok</artifactId>
|
|
<version>${lombok.version}</version>
|
|
</path>
|
|
<!--
|
|
Lombok and MapStruct both claim the same annotation-processing round; without this
|
|
binding artifact MapStruct generates mappers against the class as javac sees it before
|
|
Lombok's getters/setters/builders exist, silently emitting empty mapping methods.
|
|
-->
|
|
<path>
|
|
<groupId>org.projectlombok</groupId>
|
|
<artifactId>lombok-mapstruct-binding</artifactId>
|
|
<version>0.2.0</version>
|
|
</path>
|
|
<path>
|
|
<groupId>org.mapstruct</groupId>
|
|
<artifactId>mapstruct-processor</artifactId>
|
|
<version>${mapstruct.version}</version>
|
|
</path>
|
|
<path>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
|
</path>
|
|
<path>
|
|
<groupId>com.google.errorprone</groupId>
|
|
<artifactId>error_prone_core</artifactId>
|
|
<version>${errorprone.version}</version>
|
|
</path>
|
|
<path>
|
|
<groupId>com.uber.nullaway</groupId>
|
|
<artifactId>nullaway</artifactId>
|
|
<version>${nullaway.version}</version>
|
|
</path>
|
|
<!-- Generates FileHasherBenchmark's *_jmhType_*/*_jmhTest runner classes. -->
|
|
<path>
|
|
<groupId>org.openjdk.jmh</groupId>
|
|
<artifactId>jmh-generator-annprocess</artifactId>
|
|
<version>${jmh.version}</version>
|
|
</path>
|
|
<path>
|
|
<groupId>cc.jilt</groupId>
|
|
<artifactId>jilt</artifactId>
|
|
<version>${jilt.version}</version>
|
|
<!-- Check Maven Central for the latest version -->
|
|
</path>
|
|
</annotationProcessorPaths>
|
|
<compilerArgs>
|
|
<!--
|
|
this-escape is disabled deliberately. Every JavaFX view here extends a layout
|
|
container and configures itself in its constructor, which the check flags by
|
|
design; the pattern is idiomatic JavaFX and the alternative (two-phase init on
|
|
every view) trades a theoretical warning for real lifecycle bugs. Deprecation
|
|
warnings stay on and are suppressed case by case.
|
|
-->
|
|
<arg>-Xlint:all,-serial,-processing,-this-escape</arg>
|
|
<arg>-XDcompilePolicy=simple</arg>
|
|
<arg>-XDshould-stop.ifError=FLOW</arg>
|
|
<arg>-Xplugin:ErrorProne
|
|
-Xep:NullAway:ERROR
|
|
-XepOpt:NullAway:AnnotatedPackages=org.icroco.pholio
|
|
-XepOpt:NullAway:CustomNullableAnnotations=org.jspecify.annotations.Nullable
|
|
-XepOpt:NullAway:CheckOptionalEmptiness=true
|
|
-XepOpt:NullAway:AcknowledgeRestrictiveAnnotations=true
|
|
-XepExcludedPaths:.*/generated-(test-)?sources/.*</arg>
|
|
<!--
|
|
The pattern above now covers both target/generated-sources (Lombok/MapStruct,
|
|
main) and target/generated-test-sources (JMH's *_jmhType_*/*_jmhTest runners
|
|
for FileHasherBenchmark, test) — JMH's generated code lands in the same
|
|
package as the benchmark class it's generated from, so without the latter it
|
|
would otherwise be subject to the same NullAway:ERROR as hand-written code.
|
|
-->
|
|
<!-- Required for Java 16+ to allow ErrorProne to access JDK internals -->
|
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
|
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>
|
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
|
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg>
|
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>
|
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
|
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
|
|
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
|
|
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
|
|
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>
|
|
</compilerArgs>
|
|
</configuration>
|
|
</plugin>
|
|
|
|
<!-- <plugin>-->
|
|
<!-- <groupId>org.apache.maven.plugins</groupId>-->
|
|
<!-- <artifactId>maven-compiler-plugin</artifactId>-->
|
|
<!-- <version>3.13.0</version>-->
|
|
<!-- <configuration>-->
|
|
<!-- <source>17</source>-->
|
|
<!-- <target>17</target>-->
|
|
<!-- <compilerArgs>-->
|
|
<!-- <arg>-XDcompilePolicy=simple</arg>-->
|
|
<!-- <arg>-Xplugin:ErrorProne-->
|
|
<!-- -Xep:NullAway:ERROR-->
|
|
<!-- -XepOpt:NullAway:AnnotatedPackages=com.yourpackage-->
|
|
<!-- -XepOpt:NullAway:CustomNullableAnnotations=org.jspecify.annotations.Nullable-->
|
|
<!-- -XepOpt:NullAway:CheckOptionalEmptiness=true-->
|
|
<!-- </arg>-->
|
|
<!-- <!– Required for Java 16+ to allow ErrorProne to access JDK internals –>-->
|
|
<!-- <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>-->
|
|
<!-- <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>-->
|
|
<!-- <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>-->
|
|
<!-- <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg>-->
|
|
<!-- <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>-->
|
|
<!-- <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>-->
|
|
<!-- <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>-->
|
|
<!-- <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>-->
|
|
<!-- <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>-->
|
|
<!-- <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>-->
|
|
<!-- </compilerArgs>-->
|
|
<!-- <!– Allows -J JVM arguments to be passed directly to javac –>-->
|
|
<!-- <fork>true</fork>-->
|
|
<!-- <annotationProcessorPaths>-->
|
|
<!-- <path>-->
|
|
<!-- <groupId>org.projectlombok</groupId>-->
|
|
<!-- <artifactId>lombok</artifactId>-->
|
|
<!-- <version>1.18.30</version>-->
|
|
<!-- </path>-->
|
|
<!-- <path>-->
|
|
<!-- <groupId>com.google.errorprone</groupId>-->
|
|
<!-- <artifactId>error_prone_core</artifactId>-->
|
|
<!-- <version>2.26.0</version>-->
|
|
<!-- </path>-->
|
|
<!-- <path>-->
|
|
<!-- <groupId>com.uber.nullaway</groupId>-->
|
|
<!-- <artifactId>nullaway</artifactId>-->
|
|
<!-- <version>0.10.26</version>-->
|
|
<!-- </path>-->
|
|
<!-- </annotationProcessorPaths>-->
|
|
<!-- </configuration>-->
|
|
<!-- </plugin>-->
|
|
|
|
<plugin>
|
|
<!--
|
|
Weaves @FxThread at build time (org.icroco.pholio.ui.common.FxThreadPlugin) into target/classes,
|
|
at process-classes: after maven-compiler-plugin (Lombok, ErrorProne and NullAway are done), before
|
|
tests and packaging, so the tests and the repackaged jar both run woven classes. A jar-launched
|
|
application then needs no load-time agent (~420 ms off the time to splash screen); FxThreadAgent
|
|
only steps in for classes run from a directory, which an IDE may have recompiled unwoven.
|
|
-->
|
|
<groupId>net.bytebuddy</groupId>
|
|
<artifactId>byte-buddy-maven-plugin</artifactId>
|
|
<version>${byte-buddy.version}</version>
|
|
<configuration>
|
|
<transformations>
|
|
<transformation>
|
|
<plugin>org.icroco.pholio.ui.common.FxThreadPlugin</plugin>
|
|
</transformation>
|
|
</transformations>
|
|
</configuration>
|
|
<executions>
|
|
<execution>
|
|
<goals>
|
|
<goal>transform</goal>
|
|
</goals>
|
|
</execution>
|
|
</executions>
|
|
</plugin>
|
|
<plugin>
|
|
<groupId>org.apache.maven.plugins</groupId>
|
|
<artifactId>maven-surefire-plugin</artifactId>
|
|
<configuration>
|
|
<systemPropertyVariables>
|
|
<!--
|
|
Redirects config, data and cache under target/. PreferenceService writes on every
|
|
property change and LibraryService changes two of them at context startup, so
|
|
without this a test run would rewrite the developer's own preferences.yaml and
|
|
create libraries in their real profile.
|
|
-->
|
|
<pholio.home>${project.build.directory}/pholio-home</pholio.home>
|
|
<!--
|
|
HeaderBar is a JavaFX preview API. GuiBootstrap sets this before launching; the
|
|
tests that build the header bar need it for the same reason.
|
|
-->
|
|
<javafx.enablePreview>true</javafx.enablePreview>
|
|
</systemPropertyVariables>
|
|
</configuration>
|
|
</plugin>
|
|
<plugin>
|
|
<groupId>com.github.ekryd.sortpom</groupId>
|
|
<artifactId>sortpom-maven-plugin</artifactId>
|
|
<version>${sortpom-maven-plugin.version}</version>
|
|
<configuration>
|
|
<sortDependencyManagement>false</sortDependencyManagement>
|
|
<sortProperties>true</sortProperties>
|
|
<sortDependencies>groupId,artifactId</sortDependencies>
|
|
<sortDependencyExclusions>groupId,artifactId</sortDependencyExclusions>
|
|
<createBackupFile>false</createBackupFile>
|
|
<expandEmptyElements>false</expandEmptyElements>
|
|
<!--
|
|
nrOfIndentSpace, not indentSize: the latter is not a parameter of this plugin, so it was
|
|
silently ignored and every `mvn package` reflowed the file to the two-space default,
|
|
undoing 071d29b. Keeping the schemaLocation attribute on its own line is part of the same
|
|
formatting.
|
|
-->
|
|
<nrOfIndentSpace>4</nrOfIndentSpace>
|
|
<indentAttribute>schemaLocation</indentAttribute>
|
|
</configuration>
|
|
<executions>
|
|
<execution>
|
|
<goals>
|
|
<goal>sort</goal>
|
|
</goals>
|
|
<phase>prepare-package</phase>
|
|
</execution>
|
|
</executions>
|
|
</plugin>
|
|
<plugin>
|
|
<groupId>org.codehaus.mojo</groupId>
|
|
<artifactId>versions-maven-plugin</artifactId>
|
|
<version>${versions-maven-plugin.version}</version>
|
|
<configuration>
|
|
<ruleSet>
|
|
<!-- zero or more elements -->
|
|
<ignoreVersions>
|
|
<ignoreVersion>
|
|
<type>regex</type>
|
|
<version>.+-(alpha|beta|jdk5).+</version>
|
|
</ignoreVersion>
|
|
</ignoreVersions>
|
|
</ruleSet>
|
|
</configuration>
|
|
</plugin>
|
|
<!--
|
|
No execution bound to any phase: benchmarks never run as part of `mvn test` or `mvn
|
|
package`, only when explicitly invoked. classpathScope=test puts src/test/java (where
|
|
FileHasherBenchmark lives) on the classpath without needing a separate benchmarks module.
|
|
|
|
exec:java runs org.openjdk.jmh.Main in-process by reflection, so it never sets the real
|
|
java.class.path system property JMH's own fork launcher reads to relaunch the child JVM a
|
|
proper benchmark run forks per iteration — the fork then fails with
|
|
"ClassNotFoundException: org.openjdk.jmh.runner.ForkedMain". Fine for a quick, non-forked
|
|
sanity check (pass -f 0, and see the "Non-forked runs" warning JMH itself prints):
|
|
mvn -o test-compile exec:java -Dexec.args="FileHasherBenchmark -f 0"
|
|
For a real, forked measurement, build a genuine classpath and invoke java directly instead:
|
|
mvn -o dependency:build-classpath -Dmdep.outputFile=/tmp/pholio-bench-cp.txt -Dmdep.includeScope=test
|
|
java -cp "target/classes:target/test-classes:$(cat /tmp/pholio-bench-cp.txt)" \
|
|
org.openjdk.jmh.Main FileHasherBenchmark
|
|
-->
|
|
<plugin>
|
|
<groupId>org.codehaus.mojo</groupId>
|
|
<artifactId>exec-maven-plugin</artifactId>
|
|
<version>${exec-maven-plugin.version}</version>
|
|
<configuration>
|
|
<classpathScope>test</classpathScope>
|
|
<mainClass>org.openjdk.jmh.Main</mainClass>
|
|
</configuration>
|
|
</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>
|
|
<goals>
|
|
<goal>sync-package-json</goal>
|
|
</goals>
|
|
<!--
|
|
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>
|
|
</execution>
|
|
</executions>
|
|
</plugin>
|
|
</plugins>
|
|
</build>
|
|
</profile>
|
|
<!--
|
|
Cuts a release: ./mvnw -Prelease validate
|
|
|
|
Six steps, bound to `validate` in declaration order, the non-Maven ones done by
|
|
tools/release/Release.java (a single-file program, JDK only):
|
|
1. bump - version becomes YYYY.M.N: same month as the current version -> N+1, otherwise
|
|
build 0 of the current month. Refuses uncommitted changes unless
|
|
-Drelease.allowDirty=true, since the tag must describe the source that was built.
|
|
2. build - a second, fresh Maven runs `verify`. Maven reads the POM version once, at startup,
|
|
so this run - not the one executing the profile - is the one that sees the new
|
|
version (manifest Implementation-Version, `pholio -V`).
|
|
3. notes - distrib/release_note/release-note-<version>.md: the Conventional Commits since the
|
|
previous v<version> tag (last 30 commits for the first release), grouped by type
|
|
then scope, duplicate first lines merged.
|
|
4. publish - GitHub release v<version> in ${release.github.repo}, described by that note, with
|
|
pholio-<version>.jar, its .sha256 and the note as assets, through the gh CLI (must
|
|
be logged in). Only the tag and the assets go there: that repository's main branch
|
|
is the orphan distrib/ worktree, never source.
|
|
5. tag - commits pom.xml alone and tags it v<version>, locally only - nothing is pushed.
|
|
6. distrib - commits the release note alone in distrib/ and pushes it to ${release.github.repo}.
|
|
|
|
A failing step stops the chain; the POM is then left on the new version and the next run simply
|
|
skips that number.
|
|
-->
|
|
<profile>
|
|
<id>release</id>
|
|
<properties>
|
|
<release.allowDirty>false</release.allowDirty>
|
|
<release.distrib.dir>${project.basedir}/distrib</release.distrib.dir>
|
|
<release.github.repo>Imag-In/Pholio</release.github.repo>
|
|
<release.script>${project.basedir}/tools/release/Release.java</release.script>
|
|
</properties>
|
|
<build>
|
|
<plugins>
|
|
<plugin>
|
|
<groupId>org.codehaus.mojo</groupId>
|
|
<artifactId>exec-maven-plugin</artifactId>
|
|
<version>${exec-maven-plugin.version}</version>
|
|
<executions>
|
|
<execution>
|
|
<id>release-bump</id>
|
|
<goals>
|
|
<goal>exec</goal>
|
|
</goals>
|
|
<phase>validate</phase>
|
|
<configuration>
|
|
<executable>${java.home}/bin/java</executable>
|
|
<arguments>
|
|
<argument>${release.script}</argument>
|
|
<argument>bump</argument>
|
|
<argument>${project.basedir}/pom.xml</argument>
|
|
<argument>${release.allowDirty}</argument>
|
|
</arguments>
|
|
</configuration>
|
|
</execution>
|
|
<execution>
|
|
<id>release-build</id>
|
|
<goals>
|
|
<goal>exec</goal>
|
|
</goals>
|
|
<phase>validate</phase>
|
|
<configuration>
|
|
<executable>${project.basedir}/mvnw</executable>
|
|
<workingDirectory>${project.basedir}</workingDirectory>
|
|
<arguments>
|
|
<argument>-B</argument>
|
|
<argument>verify</argument>
|
|
</arguments>
|
|
</configuration>
|
|
</execution>
|
|
<execution>
|
|
<id>release-notes</id>
|
|
<goals>
|
|
<goal>exec</goal>
|
|
</goals>
|
|
<phase>validate</phase>
|
|
<configuration>
|
|
<executable>${java.home}/bin/java</executable>
|
|
<arguments>
|
|
<argument>${release.script}</argument>
|
|
<argument>notes</argument>
|
|
<argument>${project.basedir}/pom.xml</argument>
|
|
<argument>${release.distrib.dir}/release_note</argument>
|
|
</arguments>
|
|
</configuration>
|
|
</execution>
|
|
<execution>
|
|
<id>release-publish</id>
|
|
<goals>
|
|
<goal>exec</goal>
|
|
</goals>
|
|
<phase>validate</phase>
|
|
<configuration>
|
|
<executable>${java.home}/bin/java</executable>
|
|
<arguments>
|
|
<argument>${release.script}</argument>
|
|
<argument>publish</argument>
|
|
<argument>${project.basedir}/pom.xml</argument>
|
|
<argument>${project.build.directory}/${project.build.finalName}.jar</argument>
|
|
<argument>${release.github.repo}</argument>
|
|
<argument>${release.distrib.dir}/release_note</argument>
|
|
</arguments>
|
|
</configuration>
|
|
</execution>
|
|
<execution>
|
|
<id>release-tag</id>
|
|
<goals>
|
|
<goal>exec</goal>
|
|
</goals>
|
|
<phase>validate</phase>
|
|
<configuration>
|
|
<executable>${java.home}/bin/java</executable>
|
|
<arguments>
|
|
<argument>${release.script}</argument>
|
|
<argument>tag</argument>
|
|
<argument>${project.basedir}/pom.xml</argument>
|
|
</arguments>
|
|
</configuration>
|
|
</execution>
|
|
<execution>
|
|
<id>release-distrib</id>
|
|
<goals>
|
|
<goal>exec</goal>
|
|
</goals>
|
|
<phase>validate</phase>
|
|
<configuration>
|
|
<executable>${java.home}/bin/java</executable>
|
|
<arguments>
|
|
<argument>${release.script}</argument>
|
|
<argument>distrib</argument>
|
|
<argument>${project.basedir}/pom.xml</argument>
|
|
<argument>${release.distrib.dir}</argument>
|
|
</arguments>
|
|
</configuration>
|
|
</execution>
|
|
</executions>
|
|
</plugin>
|
|
</plugins>
|
|
</build>
|
|
</profile>
|
|
</profiles>
|
|
</project>
|