diff --git a/.github/workflows/nightly-artifacts.yml b/.github/workflows/nightly-artifacts.yml index 4809147..5daba82 100644 --- a/.github/workflows/nightly-artifacts.yml +++ b/.github/workflows/nightly-artifacts.yml @@ -63,6 +63,8 @@ jobs: shell: pwsh run: | New-Item -ItemType Directory -Force -Path dist | Out-Null + $addModules = & java packaging/AddModules.java + if ($LASTEXITCODE -ne 0) { throw "Failed to resolve --add-modules from module-info.java" } jpackage ` --type app-image ` --name praktiKST ` @@ -71,7 +73,7 @@ jobs: --main-jar app.jar ` --main-class kst4contest.view.Kst4ContestApplication ` --module-path target/dist-libs ` - --add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec,jdk.net ` + --add-modules $addModules ` --dest dist - name: Create Windows ZIP @@ -120,6 +122,7 @@ jobs: - name: Build app-image with jpackage run: | mkdir -p dist + ADD_MODULES="$(java packaging/AddModules.java)" jpackage \ --type app-image \ --name KST4Contest \ @@ -128,7 +131,7 @@ jobs: --main-jar app.jar \ --main-class kst4contest.view.Kst4ContestApplication \ --module-path target/dist-libs \ - --add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec,jdk.net \ + --add-modules "$ADD_MODULES" \ --dest dist - name: Create AppDir metadata @@ -206,6 +209,7 @@ jobs: - name: Build Debian package run: | mkdir -p dist + ADD_MODULES="$(java packaging/AddModules.java)" jpackage \ --type deb \ --name KST4Contest \ @@ -214,7 +218,7 @@ jobs: --main-jar app.jar \ --main-class kst4contest.view.Kst4ContestApplication \ --module-path target/dist-libs \ - --add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec,jdk.net \ + --add-modules "$ADD_MODULES" \ --linux-package-deps "libgstreamer1.0-0,libgstreamer-plugins-base1.0-0,gstreamer1.0-plugins-good" \ --dest dist DEB="$(ls dist/*.deb | head -n 1)" @@ -266,6 +270,7 @@ jobs: - name: Build Fedora package run: | mkdir -p dist + ADD_MODULES="$(java packaging/AddModules.java)" jpackage \ --type rpm \ --name KST4Contest \ @@ -274,7 +279,7 @@ jobs: --main-jar app.jar \ --main-class kst4contest.view.Kst4ContestApplication \ --module-path target/dist-libs \ - --add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec,jdk.net \ + --add-modules "$ADD_MODULES" \ --linux-package-deps "gstreamer1,gstreamer1-plugins-base,gstreamer1-plugins-good" \ --dest dist RPM="$(ls dist/*.rpm | head -n 1)" @@ -328,6 +333,7 @@ jobs: - name: Build app-image with jpackage run: | mkdir -p dist + ADD_MODULES="$(java packaging/AddModules.java)" jpackage \ --type app-image \ --name KST4Contest \ @@ -336,7 +342,7 @@ jobs: --main-jar app.jar \ --main-class kst4contest.view.Kst4ContestApplication \ --module-path target/dist-libs \ - --add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec,jdk.net \ + --add-modules "$ADD_MODULES" \ --dest dist - name: Build Arch Linux package artifact @@ -447,6 +453,7 @@ jobs: ./mvnw -B -DskipTests package dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/dist-libs cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar mkdir -p target/flatpak-src + ADD_MODULES="$(java packaging/AddModules.java)" jpackage \ --type app-image \ --name KST4Contest \ @@ -455,7 +462,7 @@ jobs: --main-jar app.jar \ --main-class kst4contest.view.Kst4ContestApplication \ --module-path target/dist-libs \ - --add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec,jdk.net \ + --add-modules "$ADD_MODULES" \ --dest target/flatpak-src - name: Create Flatpak manifest @@ -635,6 +642,7 @@ jobs: - name: Build macOS DMG with jpackage run: | mkdir -p dist + ADD_MODULES="$(java packaging/AddModules.java)" jpackage \ --type dmg \ --name KST4Contest \ @@ -643,7 +651,7 @@ jobs: --main-jar app.jar \ --main-class kst4contest.view.Kst4ContestApplication \ --module-path target/dist-libs \ - --add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec,jdk.net \ + --add-modules "$ADD_MODULES" \ --dest dist env: diff --git a/.github/workflows/pr-compile-check.yml b/.github/workflows/pr-compile-check.yml index 9a11928..fd3228d 100644 --- a/.github/workflows/pr-compile-check.yml +++ b/.github/workflows/pr-compile-check.yml @@ -27,5 +27,8 @@ jobs: - name: Ensure mvnw is executable run: chmod +x mvnw + - name: Verify packaging module list matches module-info.java + run: java packaging/AddModules.java --verify-pom + - name: Compile run: ./mvnw -B -DskipTests compile diff --git a/.github/workflows/tagged-release.yml b/.github/workflows/tagged-release.yml index 5e0b82e..56e3a4c 100644 --- a/.github/workflows/tagged-release.yml +++ b/.github/workflows/tagged-release.yml @@ -48,6 +48,8 @@ jobs: shell: pwsh run: | New-Item -ItemType Directory -Force -Path dist | Out-Null + $addModules = & java packaging/AddModules.java + if ($LASTEXITCODE -ne 0) { throw "Failed to resolve --add-modules from module-info.java" } jpackage ` --type app-image ` --name praktiKST ` @@ -56,7 +58,7 @@ jobs: --main-jar app.jar ` --main-class kst4contest.view.Kst4ContestApplication ` --module-path target/dist-libs ` - --add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec,jdk.net ` + --add-modules $addModules ` --dest dist - name: Create Windows ZIP @@ -98,6 +100,7 @@ jobs: - name: Build app-image with jpackage run: | mkdir -p dist + ADD_MODULES="$(java packaging/AddModules.java)" jpackage \ --type app-image \ --name KST4Contest \ @@ -106,7 +109,7 @@ jobs: --main-jar app.jar \ --main-class kst4contest.view.Kst4ContestApplication \ --module-path target/dist-libs \ - --add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec,jdk.net \ + --add-modules "$ADD_MODULES" \ --dest dist - name: Create AppDir metadata @@ -177,6 +180,7 @@ jobs: - name: Build Debian package run: | mkdir -p dist + ADD_MODULES="$(java packaging/AddModules.java)" jpackage \ --type deb \ --name KST4Contest \ @@ -185,7 +189,7 @@ jobs: --main-jar app.jar \ --main-class kst4contest.view.Kst4ContestApplication \ --module-path target/dist-libs \ - --add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec,jdk.net \ + --add-modules "$ADD_MODULES" \ --linux-package-deps "libgstreamer1.0-0,libgstreamer-plugins-base1.0-0,gstreamer1.0-plugins-good" \ --dest dist DEB="$(ls dist/*.deb | head -n 1)" @@ -230,6 +234,7 @@ jobs: - name: Build Fedora package run: | mkdir -p dist + ADD_MODULES="$(java packaging/AddModules.java)" jpackage \ --type rpm \ --name KST4Contest \ @@ -238,7 +243,7 @@ jobs: --main-jar app.jar \ --main-class kst4contest.view.Kst4ContestApplication \ --module-path target/dist-libs \ - --add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec,jdk.net \ + --add-modules "$ADD_MODULES" \ --linux-package-deps "gstreamer1,gstreamer1-plugins-base,gstreamer1-plugins-good" \ --dest dist RPM="$(ls dist/*.rpm | head -n 1)" @@ -283,6 +288,7 @@ jobs: - name: Build app-image with jpackage run: | mkdir -p dist + ADD_MODULES="$(java packaging/AddModules.java)" jpackage \ --type app-image \ --name KST4Contest \ @@ -291,7 +297,7 @@ jobs: --main-jar app.jar \ --main-class kst4contest.view.Kst4ContestApplication \ --module-path target/dist-libs \ - --add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec,jdk.net \ + --add-modules "$ADD_MODULES" \ --dest dist - name: Build Arch Linux package artifact @@ -393,6 +399,7 @@ jobs: ./mvnw -B -DskipTests package dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/dist-libs cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar mkdir -p target/flatpak-src + ADD_MODULES="$(java packaging/AddModules.java)" jpackage \ --type app-image \ --name KST4Contest \ @@ -401,7 +408,7 @@ jobs: --main-jar app.jar \ --main-class kst4contest.view.Kst4ContestApplication \ --module-path target/dist-libs \ - --add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec,jdk.net \ + --add-modules "$ADD_MODULES" \ --dest target/flatpak-src - name: Create Flatpak manifest @@ -523,6 +530,7 @@ jobs: - name: Build macOS DMG with jpackage run: | mkdir -p dist + ADD_MODULES="$(java packaging/AddModules.java)" jpackage \ --type dmg \ --name KST4Contest \ @@ -531,7 +539,7 @@ jobs: --main-jar app.jar \ --main-class kst4contest.view.Kst4ContestApplication \ --module-path target/dist-libs \ - --add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec,jdk.net \ + --add-modules "$ADD_MODULES" \ --dest dist env: diff --git a/packaging/AddModules.java b/packaging/AddModules.java new file mode 100644 index 0000000..e3d8f2b --- /dev/null +++ b/packaging/AddModules.java @@ -0,0 +1,128 @@ +/* + * Derives the jpackage --add-modules list from module-info.java so that the + * packaging scripts never drift from the module descriptor again. + * + * Run as a single file source program, which behaves identically on the Linux, + * macOS and Windows runners: + * + * java packaging/AddModules.java print the module list + * java packaging/AddModules.java --verify-pom fail if pom.xml drifted + * + * Only platform modules are emitted. Third party requires such as jlayer are + * skipped because they are supplied as ordinary jars on the class path, and + * automatic modules cannot be linked into a runtime image at all. Test only + * requires such as org.junit.jupiter.api are skipped for the same reason. + */ + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Set; +import java.util.TreeSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class AddModules { + + private static final Path DESCRIPTOR = + Path.of("src", "main", "java", "module-info.java"); + + private static final Path POM = Path.of("pom.xml"); + + /** Matches "requires [transitive] [static] some.module;" in any order. */ + private static final Pattern REQUIRES = Pattern.compile( + "requires\\s+((?:transitive\\s+|static\\s+)*)([A-Za-z0-9_.]+)\\s*;"); + + private static final Pattern ADD_MODULE = + Pattern.compile("\\s*([A-Za-z0-9_.]+)\\s*"); + + private static final Pattern BLOCK_COMMENT = + Pattern.compile("/\\*.*?\\*/", Pattern.DOTALL); + + private static final Pattern LINE_COMMENT = Pattern.compile("//[^\\n]*"); + + private AddModules() { + } + + public static void main(String[] args) throws IOException { + boolean verifyPom = args.length > 0 && "--verify-pom".equals(args[0]); + + Set required = platformModules(read(DESCRIPTOR)); + if (required.isEmpty()) { + fail("No platform modules found in " + DESCRIPTOR); + } + + if (!verifyPom) { + System.out.println(String.join(",", required)); + return; + } + + Set declared = new TreeSet<>(); + Matcher matcher = ADD_MODULE.matcher(read(POM)); + while (matcher.find()) { + declared.add(matcher.group(1)); + } + + if (declared.equals(required)) { + System.out.println("pom.xml matches module-info.java (" + + required.size() + " modules)"); + return; + } + + Set missing = new TreeSet<>(required); + missing.removeAll(declared); + Set extra = new TreeSet<>(declared); + extra.removeAll(required); + + System.err.println("pom.xml drifted from module-info.java."); + if (!missing.isEmpty()) { + System.err.println(" missing in pom.xml: " + String.join(", ", missing)); + } + if (!extra.isEmpty()) { + System.err.println(" not required by module-info.java: " + + String.join(", ", extra)); + } + System.err.println(" expected: " + String.join(",", required)); + System.exit(1); + } + + /** Returns the platform modules required by the given descriptor, sorted. */ + static Set platformModules(String source) { + String stripped = LINE_COMMENT.matcher( + BLOCK_COMMENT.matcher(source).replaceAll(" ")).replaceAll(" "); + + Set modules = new TreeSet<>(); + Matcher matcher = REQUIRES.matcher(stripped); + while (matcher.find()) { + // "requires static" is a compile time only dependency and must not + // be linked into the shipped runtime image. + if (matcher.group(1).contains("static")) { + continue; + } + String module = matcher.group(2); + if (isPlatformModule(module)) { + modules.add(module); + } + } + return modules; + } + + private static boolean isPlatformModule(String module) { + return module.startsWith("java.") + || module.startsWith("jdk.") + || module.startsWith("javafx."); + } + + private static String read(Path path) throws IOException { + if (!Files.isRegularFile(path)) { + fail("Not found: " + path.toAbsolutePath() + + " (run this from the repository root)"); + } + return Files.readString(path); + } + + private static void fail(String message) { + System.err.println(message); + System.exit(2); + } +} diff --git a/packaging/aur/kst4contest-git/PKGBUILD b/packaging/aur/kst4contest-git/PKGBUILD index ca3fb74..4452949 100644 --- a/packaging/aur/kst4contest-git/PKGBUILD +++ b/packaging/aur/kst4contest-git/PKGBUILD @@ -33,6 +33,7 @@ build() { cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar mkdir -p dist + ADD_MODULES="$(java packaging/AddModules.java)" jpackage \ --type app-image \ --name KST4Contest \ @@ -40,7 +41,7 @@ build() { --main-jar app.jar \ --main-class kst4contest.view.Kst4ContestApplication \ --module-path target/dist-libs \ - --add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec,jdk.net \ + --add-modules "$ADD_MODULES" \ --dest dist } diff --git a/packaging/aur/kst4contest/PKGBUILD b/packaging/aur/kst4contest/PKGBUILD index 467b08b..7ce00d8 100644 --- a/packaging/aur/kst4contest/PKGBUILD +++ b/packaging/aur/kst4contest/PKGBUILD @@ -26,6 +26,14 @@ build() { cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar mkdir -p dist + # This PKGBUILD builds from a released source tarball, which may predate + # packaging/AddModules.java. Older tarballs carry the same list in pom.xml, + # which the build keeps in sync with module-info.java from v1.42.0 onwards. + if [ -f packaging/AddModules.java ]; then + ADD_MODULES="$(java packaging/AddModules.java)" + else + ADD_MODULES="$(sed -n 's:.*\(.*\).*:\1:p' pom.xml | paste -sd,)" + fi jpackage \ --type app-image \ --name KST4Contest \ @@ -33,7 +41,7 @@ build() { --main-jar app.jar \ --main-class kst4contest.view.Kst4ContestApplication \ --module-path target/dist-libs \ - --add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec,jdk.net \ + --add-modules "$ADD_MODULES" \ --dest dist } diff --git a/pom.xml b/pom.xml index a167eb8..eca17aa 100644 --- a/pom.xml +++ b/pom.xml @@ -56,6 +56,7 @@ 0.0.8 4.9.8.2 4.9.8 + 3.1.0 UTF-8 @@ -208,6 +209,36 @@ + + + org.codehaus.mojo + exec-maven-plugin + ${exec.maven.plugin} + + + verify-packaging-module-list + validate + + exec + + + ${java.home}/bin/java + ${project.basedir} + + packaging/AddModules.java + --verify-pom + + + + + + org.apache.maven.plugins maven-compiler-plugin @@ -436,16 +467,26 @@ + - javafx.controls - javafx.graphics - javafx.fxml - javafx.web - javafx.media - java.sql + java.desktop java.net.http + java.sql + javafx.controls + javafx.fxml + javafx.media + javafx.web jdk.crypto.ec + jdk.jsobject jdk.net + jdk.xml.dom ${main.class} ${project.build.directory}/modules