diff --git a/.github/buildcheck/__init__.py b/.github/buildcheck/__init__.py deleted file mode 100644 index 9fd81d86..00000000 --- a/.github/buildcheck/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Bernard Ladenthin -# -# SPDX-License-Identifier: MIT OR Apache-2.0 -"""This repository's own build checks. The checks it shares with its sibling repositories live in -bernardladenthin/actions (actionlib); these are the ones only srcmorph has. - -Run the tests from the repository root: - python3 -m unittest discover -s .github/buildcheck/tests -t .github -""" diff --git a/.github/buildcheck/tests/__init__.py b/.github/buildcheck/tests/__init__.py deleted file mode 100644 index 4833295f..00000000 --- a/.github/buildcheck/tests/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Bernard Ladenthin -# -# SPDX-License-Identifier: MIT OR Apache-2.0 diff --git a/.github/buildcheck/tests/helpers.py b/.github/buildcheck/tests/helpers.py deleted file mode 100644 index d74bdea8..00000000 --- a/.github/buildcheck/tests/helpers.py +++ /dev/null @@ -1,8 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Bernard Ladenthin -# -# SPDX-License-Identifier: MIT OR Apache-2.0 -"""Fixtures of this repository's build-check tests.""" - -import os - -REPO = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) diff --git a/.github/buildcheck/tests/test_verify_classifier_fatjars.py b/.github/buildcheck/tests/test_verify_classifier_fatjars.py deleted file mode 100644 index 2aad20b7..00000000 --- a/.github/buildcheck/tests/test_verify_classifier_fatjars.py +++ /dev/null @@ -1,118 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Bernard Ladenthin -# -# SPDX-License-Identifier: MIT OR Apache-2.0 -"""Drives .github/verify-classifier-fatjars.sh against fixture jars built here. - -srcmorph's own test, not a shared file: the script is srcmorph's alone. Every case of a check the -script makes itself asserts the exit code AND the ::error:: line it promises, so a silent exit cannot -pass for a diagnosed one; the corrupt-jar case asserts unzip's own exit code and that the script does -not pass the file off as a jar without natives. -""" - -import os -import shutil -import subprocess -import tempfile -import unittest -import zipfile - -from buildcheck.tests.helpers import REPO - -SCRIPT = os.path.join(REPO, ".github", "verify-classifier-fatjars.sh") -BASH = shutil.which("bash") -UNZIP = shutil.which("unzip") - -PREFIX = "net/ladenthin/llama/" -LINUX_CPU = PREFIX + "Linux/x86_64/cpu/libjllama.so" -WINDOWS_CPU = PREFIX + "Windows/x86_64/cpu/jllama.dll" -LINUX_CUDA = PREFIX + "Linux/x86_64/cuda13/libggml-cuda.so" -NON_NATIVE = "net/ladenthin/srcmorph/cli/Main.class" -DEFAULT = "srcmorph-cli-1.3.0-SNAPSHOT-jar-with-dependencies.jar" - - -def classifier_jar(classifier): - return f"srcmorph-cli-1.3.0-SNAPSHOT-jar-with-dependencies-{classifier}.jar" - - -@unittest.skipUnless(BASH and UNZIP, "needs bash and unzip") -class VerifyClassifierFatjarsTest(unittest.TestCase): - - def setUp(self): - self.dir = tempfile.mkdtemp() - self.addCleanup(shutil.rmtree, self.dir) - - def jar(self, name, entries): - with zipfile.ZipFile(os.path.join(self.dir, name), "w") as z: - z.writestr("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n") - for entry in entries: - z.writestr(entry, b"x") - - def run_script(self, *classifiers): - return subprocess.run([BASH, SCRIPT, self.dir, *classifiers], capture_output=True, text=True) - - def assert_fails_with(self, result, message): - self.assertEqual(result.returncode, 1, result.stdout + result.stderr) - self.assertIn("::error::", result.stderr) - self.assertIn(message, result.stderr) - - def test_a_classifier_jar_with_its_backend_and_the_cpu_natives_passes(self): - self.jar(DEFAULT, [LINUX_CPU, WINDOWS_CPU, NON_NATIVE]) - self.jar(classifier_jar("cuda13-linux-x86-64"), [LINUX_CPU, WINDOWS_CPU, LINUX_CUDA, NON_NATIVE]) - result = self.run_script("cuda13-linux-x86-64") - self.assertEqual(result.returncode, 0, result.stdout + result.stderr) - self.assertIn("matches Linux/x86_64/cuda13/", result.stdout) - self.assertEqual(result.stderr, "") - - def test_a_classifier_jar_holding_only_the_cpu_natives_fails(self): - self.jar(DEFAULT, [LINUX_CPU, WINDOWS_CPU]) - self.jar(classifier_jar("cuda13-linux-x86-64"), [LINUX_CPU, WINDOWS_CPU]) - self.assert_fails_with(self.run_script("cuda13-linux-x86-64"), "does not contain what its name promises") - - def test_a_classifier_jar_that_lost_a_default_native_fails(self): - self.jar(DEFAULT, [LINUX_CPU, WINDOWS_CPU]) - self.jar(classifier_jar("cuda13-linux-x86-64"), [LINUX_CPU, LINUX_CUDA]) - self.assert_fails_with(self.run_script("cuda13-linux-x86-64"), "lost natives of the default jar") - - def test_an_unmapped_classifier_shape_fails(self): - self.jar(DEFAULT, [LINUX_CPU, WINDOWS_CPU]) - self.jar(classifier_jar("cuda13-macos-aarch64"), [LINUX_CPU, WINDOWS_CPU, LINUX_CUDA]) - self.assert_fails_with(self.run_script("cuda13-macos-aarch64"), "has no expected native path") - - def test_a_classifier_jar_without_any_native_fails_naming_it(self): - self.jar(DEFAULT, [LINUX_CPU, WINDOWS_CPU]) - self.jar(classifier_jar("cuda13-linux-x86-64"), [NON_NATIVE]) - self.assert_fails_with(self.run_script("cuda13-linux-x86-64"), - classifier_jar("cuda13-linux-x86-64") + " carries no native library at all") - - def test_a_default_jar_without_any_native_fails_naming_it(self): - self.jar(DEFAULT, [NON_NATIVE]) - self.jar(classifier_jar("cuda13-linux-x86-64"), [LINUX_CPU, WINDOWS_CPU, LINUX_CUDA]) - self.assert_fails_with(self.run_script("cuda13-linux-x86-64"), DEFAULT + " carries no native library at all") - - def test_a_default_jar_with_natives_of_one_os_fails(self): - self.jar(DEFAULT, [LINUX_CPU]) - self.jar(classifier_jar("cuda13-linux-x86-64"), [LINUX_CPU, LINUX_CUDA]) - self.assert_fails_with(self.run_script("cuda13-linux-x86-64"), "carries natives for only 1 OS") - - def test_a_corrupt_jar_fails_with_unzip_s_own_error_not_as_a_jar_without_natives(self): - """A jar unzip cannot read must end the script with unzip's own failure, not read as a jar - without natives: were the helper's failure masked (a `|| true` on its call, or a lost - pipefail), the script would report "carries no native library at all" with exit code 1 and - an ::error:: line, a wrong diagnosis. So the case pins unzip's exit code (9, - "End-of-central-directory signature not found") and the absence of the script's diagnosis, - not merely a non-zero exit. (A `|| true` on the grep branch alone is not observable this way: - pipefail carries unzip's status whatever that branch does; the branch only decides whether - grep's own error, exit 2, is tolerated, and nothing a fixture can provoke makes grep fail.)""" - self.jar(DEFAULT, [LINUX_CPU, WINDOWS_CPU]) - with open(os.path.join(self.dir, classifier_jar("cuda13-linux-x86-64")), "wb") as f: - f.write(b"not a zip") - result = self.run_script("cuda13-linux-x86-64") - self.assertEqual(result.returncode, 9, result.stdout + result.stderr) - self.assertIn("End-of-central-directory signature not found", result.stderr) - self.assertNotIn("::error::", result.stderr) - self.assertNotIn("carries no native library", result.stderr) - self.assertNotIn("ok: ", result.stdout) - - -if __name__ == "__main__": - unittest.main() diff --git a/.github/release-assets.json b/.github/release-assets.json index 6af75abf..8d8244cc 100644 --- a/.github/release-assets.json +++ b/.github/release-assets.json @@ -1,27 +1,3 @@ { - "schema": 1, - "files": [ - { - "source": "fatjar-assets/srcmorph-cli-{version}-jar-with-dependencies.jar" - }, - { - "source": "fatjar-assets/srcmorph-cli-{version}-jar-with-dependencies-{classifier}.jar", - "classifiers": [ - "cuda13-linux-x86-64", - "cuda13-windows-x86-64", - "vulkan-linux-x86-64", - "vulkan-linux-aarch64", - "vulkan-windows-x86-64", - "vulkan-windows-aarch64", - "opencl-windows-x86-64", - "opencl-windows-aarch64", - "rocm-linux-x86-64", - "rocm-windows-x86-64", - "sycl-linux-x86-64", - "sycl-windows-x86-64", - "openvino-linux-x86-64", - "openvino-windows-x86-64" - ] - } - ] + "schema": 1 } diff --git a/.github/sign-fatjars.sh b/.github/sign-fatjars.sh deleted file mode 100755 index a99844f0..00000000 --- a/.github/sign-fatjars.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env bash - -# SPDX-FileCopyrightText: 2026 Bernard Ladenthin -# -# SPDX-License-Identifier: MIT OR Apache-2.0 - -# srcmorph's own since java-llama.cpp dropped its fat jars (and its copy of this script). -# GPG-signs the fat jars (jar-with-dependencies) in a directory with a -# detached, armored .asc signature — the authenticity counterpart to any .sha256 integrity -# file. The caller builds/collects the fat jars; this only signs every -# *-jar-with-dependencies*.jar it finds in (thin jars are left untouched). -# -# Signed in the GitHub-Release attach path, not at Maven build time, because only that -# dispatch-gated path receives the signing key: GPG_PRIVATE_KEY / GPG_PASSPHRASE are scoped -# to the `maven-central` GitHub Environment. Cross-repo convention + per-repo shapes: -# workspace/policies/fat-jar-release-assets.md. -# -# Usage: sign-fatjars.sh -# directory holding the fat jars (and possibly thin jars); every -# *-jar-with-dependencies*.jar in it is signed in place (-> .asc). -# -# Env: GPG_PRIVATE_KEY armored secret key (required) -# GPG_PASSPHRASE passphrase for the key (may be empty) -# -# Fail-loud: aborts if the key is absent, if no fat jar is found, or if any signature -# fails to verify. - -set -euo pipefail - -DIR="${1:?usage: sign-fatjars.sh }" - -if [ -z "${GPG_PRIVATE_KEY:-}" ]; then - echo "::error::GPG_PRIVATE_KEY is empty — cannot sign the fat jars. The maven-central environment did not deliver the secret to this ref." >&2 - exit 1 -fi -if [ -n "${GPG_PASSPHRASE:-}" ]; then echo "::add-mask::${GPG_PASSPHRASE}"; fi - -# Ephemeral, private keyring; removed on exit (do NOT touch the runner's default one). -GNUPGHOME="$(mktemp -d)" -export GNUPGHOME -chmod 700 "$GNUPGHOME" -cleanup() { gpgconf --kill gpg-agent >/dev/null 2>&1 || true; rm -rf "$GNUPGHOME"; } -trap cleanup EXIT - -printf '%s\n' "$GPG_PRIVATE_KEY" | gpg --batch --import -KEYID="$(gpg --list-secret-keys --with-colons --fixed-list-mode | awk -F: '$1=="sec"{print $5; exit}')" -if [ -z "$KEYID" ]; then - echo "::error::No secret key imported from GPG_PRIVATE_KEY." >&2 - exit 1 -fi - -shopt -s nullglob -jars=("$DIR"/*-jar-with-dependencies*.jar) -shopt -u nullglob -if [ "${#jars[@]}" -eq 0 ]; then - echo "::error::No *-jar-with-dependencies*.jar found in '$DIR' to sign." >&2 - exit 1 -fi - -for f in "${jars[@]}"; do - # Skip a signature file itself if the glob ever catches one. - case "$f" in *.asc) continue ;; esac - printf '%s' "${GPG_PASSPHRASE:-}" | gpg --batch --yes --pinentry-mode loopback \ - --passphrase-fd 0 --local-user "$KEYID" --detach-sign --armor "$f" - gpg --batch --verify "$f.asc" "$f" - echo "signed + verified: $(basename "$f") -> $(basename "$f").asc" -done - -echo "Signed ${#jars[@]} fat jar(s) in '$DIR'." diff --git a/.github/verify-classifier-fatjars.sh b/.github/verify-classifier-fatjars.sh deleted file mode 100755 index 08efa176..00000000 --- a/.github/verify-classifier-fatjars.sh +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env bash - -# SPDX-FileCopyrightText: 2026 Bernard Ladenthin -# -# SPDX-License-Identifier: MIT OR Apache-2.0 - -# Structural check of the srcmorph-cli fat jars before they are attached to a GitHub release. -# -# WHY THIS IS STRUCTURAL AND NOT A LAUNCH. The cross-repo rule is "no release asset is attached that -# CI has not run" (workspace/policies/fat-jar-release-assets.md), and the `smoke-fatjar` job does -# exactly that -- for the DEFAULT jar. The GPU-classifier jars cannot be covered the same -# way: a GitHub-hosted runner has no CUDA/ROCm/SYCL/OpenVINO device, and the only command that would -# load the native library is a real generation. `Plan` with the mock provider -- the one thing that -# does run on a GPU-less runner -- never touches it, so "launching" a classifier jar would assert -# nothing about the classifier. Pretending otherwise is worse than not checking: it reads as covered. -# -# What CAN be asserted without a device is that each jar is the artifact it claims to be: -# -# 1. exactly one jar per requested classifier, and no unexpected classifier jar; -# 2. every jar carries at least one native library (a jar with none is a broken assembly that -# would fail at runtime on every platform); -# 3. every classifier jar carries the backend directory its NAME promises: net.ladenthin:llama -# natives jars (6.0.0 on) are named -- and hold /// -- -# for a GPU jar one ggml backend module (libggml-.so / ggml-.dll), which the -# loader puts next to the CPU library of llama-platform's jar for that platform. -# This is the one that catches the failure the whole loop is exposed to. If -# `-Dllama.classifier=` ever stops being wired through (a renamed property, a pom refactor), -# Maven still resolves the default natives and the loop happily produces byte-similar jars -# under GPU names. No other check in this pipeline would notice; -# 4. every classifier jar still carries every native of the default jar: the GPU backend is added -# next to the CPU natives, which are the loader's fallback when the GPU runtime is missing; -# 5. the default jar carries natives for more than one OS (it is the all-platform CPU variant). -# -# A new classifier SHAPE fails this script until its expected native path is declared, rather than -# being skipped silently. -# -# Usage: verify-classifier-fatjars.sh ... - -set -euo pipefail - -ASSET_DIR="${1:?usage: verify-classifier-fatjars.sh ...}" -shift -CLASSIFIERS=("$@") - -# Native libraries live under this prefix inside the jar, as ///. -NATIVE_PREFIX="net/ladenthin/llama/" - -fail() { - echo "::error::$*" >&2 - exit 1 -} - -[ -d "$ASSET_DIR" ] || fail "asset directory '$ASSET_DIR' does not exist" -[ "${#CLASSIFIERS[@]}" -gt 0 ] || fail "no classifiers given -- pass the same list the build loop used" - -# Sorted list of native-library entries in a jar, one per line; empty for a jar without any. -# grep's "no match" (exit 1) is tolerated so the explicit checks below name the jar -- under pipefail -# it used to end the script on the assignment, silently. An unzip failure or a grep error (exit 2) -# still fails the pipeline. -natives_of() { - unzip -Z1 "$1" | { grep -E "^${NATIVE_PREFIX}.*\.(so|dll|dylib)$" || [ "$?" -eq 1 ]; } | sort -} - -# "///" directory a classifier's name promises (--). The -# catch-all is deliberate: a classifier shape nobody has mapped must red the job, not pass unchecked. -expected_path_of() { - local c="$1" arch os_arch - case "$c" in - *-linux-x86-64) os_arch="Linux/x86_64" ;; - *-linux-aarch64) os_arch="Linux/aarch64" ;; - *-windows-x86-64) os_arch="Windows/x86_64" ;; - *-windows-aarch64) os_arch="Windows/aarch64" ;; - *) fail "classifier '$c' has no expected native path -- add its shape to expected_path_of()" ;; - esac - case "$c" in - *-x86-64) arch="x86-64" ;; - *) arch="${c##*-}" ;; - esac - local rest="${c%-"$arch"}" - echo "$os_arch/${rest%-*}/" -} - -default_jars=() -while IFS= read -r j; do default_jars+=("$j"); done < <( - find "$ASSET_DIR" -maxdepth 1 -type f -name '*-jar-with-dependencies.jar' | sort -) -[ "${#default_jars[@]}" -eq 1 ] \ - || fail "expected exactly 1 default fat jar in '$ASSET_DIR', got ${#default_jars[@]}: ${default_jars[*]:-none}" -DEFAULT_JAR="${default_jars[0]}" - -default_natives="$(natives_of "$DEFAULT_JAR")" -[ -n "$default_natives" ] || fail "$(basename "$DEFAULT_JAR") carries no native library at all" - -# The default jar is the all-platform CPU variant: more than one OS directory under the prefix. -default_os_count=$(echo "$default_natives" | sed "s|^${NATIVE_PREFIX}||" | cut -d/ -f1 | sort -u | wc -l) -[ "$default_os_count" -gt 1 ] \ - || fail "$(basename "$DEFAULT_JAR") carries natives for only $default_os_count OS -- it is supposed to be the all-platform CPU jar" -echo "default: $(basename "$DEFAULT_JAR") -- $(echo "$default_natives" | wc -l) native(s) across $default_os_count OS" - -# Every classifier jar present must be one we asked for; an extra one means the loop and this check -# disagree about what is being shipped. -for jar in "$ASSET_DIR"/*-jar-with-dependencies-*.jar; do - [ -e "$jar" ] || continue - name="$(basename "$jar")" - suffix="${name##*-jar-with-dependencies-}" - found="${suffix%.jar}" - matched=0 - for c in "${CLASSIFIERS[@]}"; do [ "$c" = "$found" ] && matched=1 && break; done - [ "$matched" -eq 1 ] || fail "unexpected classifier jar '$name' -- '$found' is not in the requested list" -done - -for c in "${CLASSIFIERS[@]}"; do - jars=() - while IFS= read -r j; do jars+=("$j"); done < <( - find "$ASSET_DIR" -maxdepth 1 -type f -name "*-jar-with-dependencies-${c}.jar" | sort - ) - [ "${#jars[@]}" -eq 1 ] \ - || fail "expected exactly 1 fat jar for classifier '$c', got ${#jars[@]}: ${jars[*]:-none}" - jar="${jars[0]}" - - natives="$(natives_of "$jar")" - [ -n "$natives" ] || fail "$(basename "$jar") carries no native library at all" - - want="$(expected_path_of "$c")" - echo "$natives" | grep -qF -- "${NATIVE_PREFIX}${want}" \ - || fail "$(basename "$jar") carries no native under ${NATIVE_PREFIX}${want} -- it does not contain what its name promises: $(echo "$natives" | tr '\n' ' ')" - - # Every CPU native of the default jar must still be there: it is the loader's fallback. - missing="$(comm -23 <(echo "$default_natives") <(echo "$natives"))" - [ -z "$missing" ] \ - || fail "$(basename "$jar") lost natives of the default jar (the CPU fallback): $(echo "$missing" | tr '\n' ' ')" - - echo "ok: $(basename "$jar") -- $(echo "$natives" | wc -l) native(s), matches ${want}" -done - -echo "classifier fat jars verified: ${#CLASSIFIERS[@]} classifier(s) + the default" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2034bdc1..faa5234d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -78,10 +78,6 @@ jobs: persist-credentials: false - name: Files equal their templates in bernardladenthin/actions uses: bernardladenthin/actions/common/check-templates@44390bd464395f560f4170d2c4bfb7a6348541b3 # v1.1.0 - # This repository's own build checks build on actionlib (the setup step puts it on PYTHONPATH). - - uses: bernardladenthin/actions/common/setup@44390bd464395f560f4170d2c4bfb7a6348541b3 # v1.1.0 - - name: Build-check tests of this repository - run: python3 -m unittest discover -s .github/buildcheck/tests -t .github - name: Release gate (every job gates both publish jobs, or release-gate-exemptions.txt says why) uses: bernardladenthin/actions/common/check-release-gate@44390bd464395f560f4170d2c4bfb7a6348541b3 # v1.1.0 - name: Run scripts parse (bash -n over every bash run script of the workflows and actions) @@ -178,23 +174,8 @@ jobs: java-version-file: .java-version distribution: temurin cache: maven - # srcmorph-cli runs the license collector of bernardladenthin/actions while packaging - # (ACTIONS_DIR, exported here). - - uses: bernardladenthin/actions/common/setup@44390bd464395f560f4170d2c4bfb7a6348541b3 # v1.1.0 - name: Build run: mvn --batch-mode --no-transfer-progress -DskipTests package - - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 - with: - name: plugin-jars - # All three reactor modules produce their own jars: srcmorph (core library), - # srcmorph-cli (includes the jar-with-dependencies fat jar — the CLI's deliverable), - # and srcmorph-maven-plugin (the Maven plugin, renamed from llamacpp-ai-index-maven-plugin - # in the migration). The former 4th module — a pom-only relocation-stub for the retired - # coordinates — was published once and then removed from this reactor; see CLAUDE.md. - path: | - srcmorph/target/*.jar - srcmorph-cli/target/*.jar - srcmorph-maven-plugin/target/*.jar test: name: Test @@ -209,9 +190,6 @@ jobs: java-version-file: .java-version distribution: temurin cache: maven - # `verify` packages srcmorph-cli, whose license collector comes from bernardladenthin/actions - # (ACTIONS_DIR, exported here) -- as in `build`. - - uses: bernardladenthin/actions/common/setup@44390bd464395f560f4170d2c4bfb7a6348541b3 # v1.1.0 - name: Memory before tests run: free -h - name: Test @@ -321,52 +299,61 @@ jobs: if-no-files-found: ignore # --------------------------------------------------------------------------- - # Fat-jar smoke test — cross-repo standard job, kept in the same shape in - # BitcoinAddressFinder and java-llama.cpp (see workspace/policies/fat-jar-release-assets.md, - # "No release asset is attached that CI has not run"). The fat jar is a GitHub-Release asset, - # and unit tests never touch it: they run off target/classes, so an unrunnable uber jar — no - # Main-Class, a shade-mangled resource, a missing SLF4J binding — is built and GPG-signed - # without anything noticing. Signing proves who built it, not that it works. + # Jar smoke test. srcmorph publishes no runnable jar: a user builds one from + # examples/build-your-own-jar (srcmorph-cli plus the natives of their OS/arch). This job takes + # exactly that path against the commit under test: it installs the reactor into the runner's + # local repository, builds the example with srcmorph.version and llama.version from the reactor, + # and launches the result the way a user would. Unit tests run off target/classes and never + # notice an unrunnable jar -- no Main-Class, a missing SLF4J binding, a Java 11 class on a Java 8 + # floor. # - # srcmorph-specific: config_Plan.json uses the `mock` generation provider, so this needs no - # GGUF model, no GPU and no network — it is the cheapest possible real launch of the CLI. - # `Plan` is the command that cannot fail for configuration reasons (planOnly, nothing written); - # `All` over a real source tree fails by design when files exceed the demo model's context - # window, which would make the smoke non-deterministic rather than more thorough. + # config_Plan.json uses the `mock` generation provider, so this needs no GGUF model, no GPU and + # no network beyond Maven -- the cheapest possible real launch of the CLI. `Plan` is the command + # that cannot fail for configuration reasons (planOnly, nothing written); `All` over a real + # source tree fails by design when files exceed the demo model's context window, which would make + # the smoke non-deterministic rather than more thorough. # --------------------------------------------------------------------------- - smoke-fatjar: - name: Smoke test fat jar - needs: [build] + smoke-jar: + name: Smoke test the user-built jar + needs: startgate runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8 - with: - name: plugin-jars - path: fatjar/ - uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad # v6 with: java-version-file: .java-version distribution: temurin + cache: maven + - name: Install the reactor, then build examples/build-your-own-jar against it + run: | + set -euo pipefail + mvn --batch-mode --no-transfer-progress -DskipTests install + version=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version | tail -n1) + llama=$(mvn -q -DforceStdout -pl srcmorph help:evaluate -Dexpression=llama.version | tail -n1) + echo "srcmorph $version, llama $llama" + mvn --batch-mode --no-transfer-progress -f examples/build-your-own-jar/pom.xml -Dsrcmorph.version="$version" -Dllama.version="$llama" package + mkdir -p smoke-jars + cp examples/build-your-own-jar/target/srcmorph.jar smoke-jars/ + cp "srcmorph-maven-plugin/target/srcmorph-maven-plugin-$version.jar" smoke-jars/ # Class-file floor. Production code here targets Java 8, so anything a consumer's JVM # can load must be major 52 or lower -- a single Java 11 class (logback's # LogbackServiceProvider was the real case) kills the process with - # UnsupportedClassVersionError before any of our code runs. The script recurses into the - # whole download directory, so every jar the build produced is checked, not just the fat - # jar; module-info.class and META-INF/versions/** are skipped unconditionally because a - # classpath JVM never loads them. The java/verify-bytecode action of bernardladenthin/actions. + # UnsupportedClassVersionError before any of our code runs. srcmorph.jar holds the CLI with + # its whole runtime classpath; the plugin jar is what Maven loads. module-info.class and + # META-INF/versions/** are skipped because a classpath JVM never loads them. The + # java/verify-bytecode action of bernardladenthin/actions. - name: Verify Java 8 bytecode (no class newer than major 52) uses: bernardladenthin/actions/java/verify-bytecode@44390bd464395f560f4170d2c4bfb7a6348541b3 # v1.1.0 with: max-major: "52" paths: | - fatjar - - name: Run fat-jar smoke test + smoke-jars + - name: Run the jar uses: bernardladenthin/actions/java/smoke-fatjar-cli@44390bd464395f560f4170d2c4bfb7a6348541b3 # v1.1.0 with: - jar-dir: fatjar - jar-glob: 'srcmorph-cli-*-jar-with-dependencies.jar' + jar-dir: smoke-jars + jar-glob: 'srcmorph.jar' work-dir: examples marker: 'Main#run end\.' args: config_Plan.json @@ -374,7 +361,7 @@ jobs: if: failure() uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 with: - name: smoke-fatjar-logs + name: smoke-jar-logs path: | smoke-out.log smoke-err.log @@ -513,7 +500,7 @@ jobs: needs: - check-snapshot - code-style - - smoke-fatjar + - smoke-jar - plugin-it - repository-checks - vmlens @@ -545,8 +532,7 @@ jobs: *-SNAPSHOT) echo "OK: -SNAPSHOT version, continuing local snapshot staging." ;; *) echo "::error::Refusing to publish non-SNAPSHOT version '$VERSION' from the snapshot job. Snapshot publishing requires a -SNAPSHOT version; releases go through the v* tag path."; exit 1 ;; esac - # srcmorph-cli runs the license collector of bernardladenthin/actions while packaging - # (ACTIONS_DIR, exported here). + # The collect step runs actionlib of bernardladenthin/actions (on PYTHONPATH from here). - uses: bernardladenthin/actions/common/setup@44390bd464395f560f4170d2c4bfb7a6348541b3 # v1.1.0 - name: Show effective POM (debug) run: mvn --batch-mode --no-transfer-progress -P release -DskipUpload=true help:effective-pom @@ -554,44 +540,8 @@ jobs: run: mvn --batch-mode --no-transfer-progress -P release -DskipUpload=true deploy -DskipTests env: MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - - name: Collect signed Central artifacts (plain/sources/javadoc; fat jars handled next) + - name: Collect signed Central artifacts (pom/jar/sources/javadoc) run: python3 -m actionlib.java.releaseassets central-assets --maven-only - - name: Build, sign & verify classifier fat jars (GitHub-only assets, never Central) - env: - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - run: | - set -euo pipefail - # Source of truth: java-llama.cpp's .github/natives.csv for the pinned ${llama.version}: - # every row of kind `module` (a GPU backend module, additive to the library jar of its - # platform) except the Android one, which no `java -jar` can use; the cpu-*/metal-* - # library jars come with llama-platform. Each jar here is ADDED to the default CPU - # natives (-Dllama.classifier activates the gpu-natives profile), so every classifier - # fat jar keeps the CPU fallback: ggml loads the module when its runtime is installed. - python3 -m actionlib.java.releaseassets --list-classifiers 1 > fatjar-classifiers.txt - mapfile -t CLASSIFIERS < fatjar-classifiers.txt - build() { mvn -B --no-transfer-progress -pl srcmorph-cli -am -DskipTests -Dmaven.javadoc.skip=true -Dllama.classifier="$1" package; } - for c in "${CLASSIFIERS[@]}"; do - build "$c" - f=$(ls srcmorph-cli/target/srcmorph-cli-*-jar-with-dependencies.jar) - cp "$f" "${f%.jar}-${c}.jar" - done - # default CPU build LAST so the unsuffixed fat jar is the all-platform CPU variant - build "" - # Collect every fat jar variant, then GPG-sign each via the cross-repo shared script - # (.github/sign-fatjars.sh; java-llama.cpp dropped its copy with its fat jars) — a .asc per fat jar. - mkdir -p fatjar-assets - cp srcmorph-cli/target/srcmorph-cli-*-jar-with-dependencies*.jar fatjar-assets/ - bash .github/sign-fatjars.sh fatjar-assets - # Structural check of what the loop just produced. The smoke-fatjar job LAUNCHES the - # default jar; the classifier jars cannot be covered that way (a GitHub runner has no - # such device, and the only command that would load the native library is a real - # generation), so this asserts instead that each jar is the artifact its name claims -- - # one jar per classifier, the backend directory the name promises, and every default - # CPU native kept next to it. The backend directory is the point: if -Dllama.classifier= - # ever stops being wired through, the loop silently ships copies of the CPU build under - # GPU names and nothing else in this pipeline notices. - bash .github/verify-classifier-fatjars.sh fatjar-assets "${CLASSIFIERS[@]}" - name: Finalize and secure the signed publication id: finalize uses: bernardladenthin/actions/java/finalize-publication@44390bd464395f560f4170d2c4bfb7a6348541b3 # v1.1.0 @@ -622,7 +572,7 @@ jobs: needs: - check-tag - code-style - - smoke-fatjar + - smoke-jar - plugin-it - repository-checks - vmlens @@ -645,8 +595,7 @@ jobs: server-password: MAVEN_PASSWORD gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} gpg-passphrase: MAVEN_GPG_PASSPHRASE - # srcmorph-cli runs the license collector of bernardladenthin/actions while packaging - # (ACTIONS_DIR, exported here). + # The collect step runs actionlib of bernardladenthin/actions (on PYTHONPATH from here). - uses: bernardladenthin/actions/common/setup@44390bd464395f560f4170d2c4bfb7a6348541b3 # v1.1.0 - name: Show effective POM (debug) run: mvn --batch-mode --no-transfer-progress -P release -DskipUpload=true help:effective-pom @@ -654,44 +603,8 @@ jobs: run: mvn --batch-mode --no-transfer-progress -P release -DskipUpload=true deploy -DskipTests env: MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - - name: Collect signed Central artifacts (plain/sources/javadoc; fat jars handled next) + - name: Collect signed Central artifacts (pom/jar/sources/javadoc) run: python3 -m actionlib.java.releaseassets central-assets --maven-only - - name: Build, sign & verify classifier fat jars (GitHub-only assets, never Central) - env: - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - run: | - set -euo pipefail - # Source of truth: java-llama.cpp's .github/natives.csv for the pinned ${llama.version}: - # every row of kind `module` (a GPU backend module, additive to the library jar of its - # platform) except the Android one, which no `java -jar` can use; the cpu-*/metal-* - # library jars come with llama-platform. Each jar here is ADDED to the default CPU - # natives (-Dllama.classifier activates the gpu-natives profile), so every classifier - # fat jar keeps the CPU fallback: ggml loads the module when its runtime is installed. - python3 -m actionlib.java.releaseassets --list-classifiers 1 > fatjar-classifiers.txt - mapfile -t CLASSIFIERS < fatjar-classifiers.txt - build() { mvn -B --no-transfer-progress -pl srcmorph-cli -am -DskipTests -Dmaven.javadoc.skip=true -Dllama.classifier="$1" package; } - for c in "${CLASSIFIERS[@]}"; do - build "$c" - f=$(ls srcmorph-cli/target/srcmorph-cli-*-jar-with-dependencies.jar) - cp "$f" "${f%.jar}-${c}.jar" - done - # default CPU build LAST so the unsuffixed fat jar is the all-platform CPU variant - build "" - # Collect every fat jar variant, then GPG-sign each via the cross-repo shared script - # (.github/sign-fatjars.sh; java-llama.cpp dropped its copy with its fat jars) — a .asc per fat jar. - mkdir -p fatjar-assets - cp srcmorph-cli/target/srcmorph-cli-*-jar-with-dependencies*.jar fatjar-assets/ - bash .github/sign-fatjars.sh fatjar-assets - # Structural check of what the loop just produced. The smoke-fatjar job LAUNCHES the - # default jar; the classifier jars cannot be covered that way (a GitHub runner has no - # such device, and the only command that would load the native library is a real - # generation), so this asserts instead that each jar is the artifact its name claims -- - # one jar per classifier, the backend directory the name promises, and every default - # CPU native kept next to it. The backend directory is the point: if -Dllama.classifier= - # ever stops being wired through, the loop silently ships copies of the CPU build under - # GPU names and nothing else in this pipeline notices. - bash .github/verify-classifier-fatjars.sh fatjar-assets "${CLASSIFIERS[@]}" - name: Finalize and secure the signed publication id: finalize uses: bernardladenthin/actions/java/finalize-publication@44390bd464395f560f4170d2c4bfb7a6348541b3 # v1.1.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 29cba730..6c1d3968 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,32 @@ The release procedure (prompt template and step-by-step instructions) lives in [ ## [Unreleased] +### Removed +- **No more fat jars on the GitHub release.** Up to 1.3.0 every release attached 15 signed + `srcmorph-cli--jar-with-dependencies[-].jar` files (a CPU jar for every + desktop platform plus one per llama GPU module); they were never on Maven Central. There are too + many OS, CPU and GPU combinations to ship one per machine, so from 1.4.0 you build the runnable jar + yourself (see Changed). Gone with them: the `package`-bound assembly in `srcmorph-cli`, its + `fat-jar.xml` descriptor, the runtime license collector that only those jars needed (and with it + the build's dependency on a bernardladenthin/actions checkout and `third-party-notices/`), and in + CI the classifier build loop, `sign-fatjars.sh`, `verify-classifier-fatjars.sh` and its tests. + `.github/release-assets.json` names no files beyond the Maven artifacts. + +### Changed +- **`examples/windows-fatjar/` is now `examples/build-your-own-jar/`, for every platform.** Its + `pom.xml` takes `srcmorph-cli` from Maven Central, picks the natives jar of the build machine's + OS/arch with an OS-activated profile (`-Dnatives=` overrides it, and the enforcer + names the property when no profile matches), adds one GPU module with `-Dgpu=`, and + packs everything with the logging defaults into `target/srcmorph.jar`. `build.sh`/`build.bat` + pass their arguments to Maven; `run.sh`/`run.bat` start the jar. The eight `examples/run_*` + scripts now run that jar instead of `srcmorph-cli/target/*-jar-with-dependencies.jar`. The README + mentions JBang as a shortcut (`srcmorph-cli`'s jar names its main class); Maven is the supported + path. +- **CI: `smoke-fatjar` is now `smoke-jar` and tests the user's path.** It installs the reactor, + builds `examples/build-your-own-jar` against it (`srcmorph.version` and `llama.version` read from + the reactor), checks the Java 8 bytecode floor over that jar and the plugin jar, and runs `Plan`. + The example pom is therefore under test on every pull request. + ## [1.3.0] - 2026-10-26 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index 858c07ce..ade42267 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -59,13 +59,14 @@ llamacpp-ai-index-maven-plugin/ (repo root; reactor parent) │ ├── indexer/ SourceFileIndexer, PackageIndexer, ProjectIndexer, AiFieldGenerationSupport, ... │ ├── document/ prompt/ provider/ support/ │ └── src/test/resources/SmolLM2-135M-Instruct-Q3_K_M.gguf (real-model tests live here) -├── srcmorph-cli/ CLI net.ladenthin:srcmorph-cli (fat jar = deliverable) +├── srcmorph-cli/ CLI net.ladenthin:srcmorph-cli (thin jar; users build the runnable one) │ └── src/main/java/net/ladenthin/srcmorph/cli/ │ ├── Main.java BitcoinAddressFinder cli/Main.java pattern │ └── configuration/ CConfiguration + CCommand (BAF public-field style) ├── srcmorph-maven-plugin/ Maven plugin net.ladenthin:srcmorph-maven-plugin, goalPrefix srcmorph │ └── src/main/java/net/ladenthin/maven/srcmorph/mojo/ (4 goal mojos + the abstract AbstractAiIndexMojo base; renamed package/properties) ├── examples/ config_*.json/.yaml + run_*.sh/.bat + simplelogger.properties +│ └── build-your-own-jar/ pom.xml + build/run scripts: srcmorph-cli + this machine's natives -> srcmorph.jar ├── docs/ RELEASE.md + the ai-index model-benchmark writeups └── .github/workflows/ CI adapted to the 3-module reactor ``` @@ -166,16 +167,21 @@ CLI driven by a single JSON or YAML configuration file: `@Parameter` fields — a JSON/YAML key under `srcMorph` reads identically to the matching plugin XML element. `CCommand` is `Plan | GenerateFileIndex | AggregatePackages | AggregateProject | All | Calibrate`; the default is `Plan` (safe: no model load, nothing written). -- The fat jar (`srcmorph-cli--jar-with-dependencies.jar`, main class - `net.ladenthin.srcmorph.cli.Main`) is bound **unconditionally** to the `package` phase (a deliberate - divergence from BAF's `-P assembly` opt-in — for this module the fat jar IS the deliverable). -- Ships its own SLF4J binding (`org.slf4j:slf4j-simple`, runtime scope) — unlike the library +- **No runnable jar is built or published here** (since 1.4.0). `mvn package` produces only the thin + `srcmorph-cli` jar (its manifest names `net.ladenthin.srcmorph.cli.Main`, so `jbang` can run it). + Users build the runnable jar with `examples/build-your-own-jar/pom.xml`: `srcmorph-cli` from + Central, `llama-platform` excluded, the one natives jar of the OS/arch picked by an OS-activated + profile (`-Dnatives=` overrides), an optional GPU module via `-Dgpu=`, packed by + `jar-with-dependencies` into `target/srcmorph.jar`. Up to 1.3.0 this module built a fat jar on + every `package` and CI released 15 of them on GitHub; there are too many OS/CPU/GPU combinations + to keep doing that. +- Declares its own SLF4J binding (`org.slf4j:slf4j-simple`, runtime scope) — unlike the library (consumer picks any SLF4J binding) and the plugin (gets one for free from Maven's own `maven-slf4j-provider`), a standalone `java -jar` process needs to bring its own or every log line is - silently dropped. It is **not** logback: see "Java 8 bytecode floor" below. Fat-jar defaults live in - `srcmorph-cli/src/main/assembly-resources/simplelogger.properties`, added by the custom assembly - descriptor `srcmorph-cli/src/assembly/fat-jar.xml` (the predefined `jar-with-dependencies` ref - verbatim plus that one file) so they never reach the published `srcmorph-cli` jar. + silently dropped. It is **not** logback: see "Java 8 bytecode floor" below. The logging defaults + (INFO, stdout, timestamps) are `examples/build-your-own-jar/simplelogger.properties`, which that + pom packs into the user's jar; the published `srcmorph-cli` jar carries none, so an embedder's own + configuration is never overridden. - **Architecture rules** (`CliArchitectureTest`): `cliIsLeaf` (nothing else in the reactor may depend on this module — it is the leaf-most consumer), `noPublicMutableFields` (with the `configuration` package carve-out), `noSystemExit`, `mavenFree` (must never depend on the Maven Plugin API — that @@ -272,7 +278,7 @@ keep working. ```bash mvn compile # Compiles every module (Java + generates nothing native; pure Java reactor) mvn test # Runs every module's tests -mvn package # Builds all four reactor projects: parent pom + 3 jars (incl. the CLI's fat jar) +mvn package # Builds all four reactor projects: parent pom + 3 jars mvn install # Installs all four into ~/.m2 (needed before iterating on a single module — see below) ``` @@ -307,8 +313,9 @@ mvn -pl srcmorph-maven-plugin srcmorph:generate -P srcmorph-selftest ### Run the CLI ```bash -mvn -pl srcmorph-cli package -java -jar srcmorph-cli/target/srcmorph-cli-1.4.0-SNAPSHOT-jar-with-dependencies.jar examples/config_All.json +mvn install -DskipTests +mvn -f examples/build-your-own-jar/pom.xml package -Dsrcmorph.version=1.4.0-SNAPSHOT +java -jar examples/build-your-own-jar/target/srcmorph.jar examples/config_All.json ``` See `examples/` (repo root) for ready-to-run `config_*.json`/`.yaml` + paired `run_*.sh`/`.bat` @@ -480,7 +487,8 @@ and both are easy to undo by accident: fails every build with `Could not load type: org.checkerframework.framework.qual.DoesNotUnrefineReceiver`. Processor and qualifiers must share a major version. `provided` satisfies both: 4.3.0 where the checker needs it, and excluded from - consumers' graph **and** from the fat jar (`jar-with-dependencies` takes scope `runtime`). + consumers' graph **and** from any `jar-with-dependencies` built from it (that descriptor takes + scope `runtime`). `true` alone would not have been enough — that descriptor filters on scope only. Safe because no source in this reactor imports `org.checkerframework`. @@ -493,7 +501,7 @@ exceeds `max-major`: with: max-major: "52" paths: | # jars or directories, one per line - fatjar + smoke-jars allow: | # optional: ':' globs to tolerate ``` @@ -502,7 +510,8 @@ whole artifact set. `module-info.class` and `META-INF/versions/**` are skipped u a classpath JVM never loads either, which is why a `release 9` `module-info` is fine. `allow` takes globs matched against `:` for anything else that must be tolerated. Exit codes: 0 clean, 1 violations, **2 nothing to scan** (an empty input is a failure, -never a pass). Wired into the `smoke-fatjar` job at `max-major` 52. +never a pass). Wired into the `smoke-jar` job at `max-major` 52, over the user-built `srcmorph.jar` +(the CLI with its whole runtime classpath) and the plugin jar. ## Javadoc Conventions @@ -543,7 +552,7 @@ Run PIT with the lifecycle prefix. Reactor-wide (what CI does): `-f srcmorph/pom.xml`. All three modules gate at `mutationThreshold` 100 — `srcmorph` (830 mutations), `srcmorph-maven-plugin` (62, the five mojo classes) and `srcmorph-cli` (16). The CLI's `Main.main(String[])` is the one documented exclusion: it is the process entry point, and the -`smoke-fatjar` release-gating job already runs the real `java -jar` artifact and asserts +`smoke-jar` release-gating job already runs the real `java -jar` artifact and asserts `Main#run end.` in its output, which is an end-to-end check a unit mutant cannot reach. **`LlamaCppJniAiGenerationProvider` is split so the gate can reach it, and the split is load-bearing.** @@ -593,17 +602,14 @@ clear of the JPMS module-mode javadoc trap that bit BAF. **Before raising the Ja level to ≥ 9 in any module, read** [`../workspace/policies/jpms-module-descriptor.md`](../workspace/policies/jpms-module-descriptor.md). -## Fat-jar release assets - -The `srcmorph-cli` fat jar (`jar-with-dependencies`) is a **GitHub-Release asset only — never -Maven Central** (`srcmorph-cli/pom.xml` sets `false`), attached with a detached -GPG `.asc`. CI builds **one fat jar per desktop GPU module jar of `net.ladenthin:llama`** (the default -CPU jar + 14: `cuda13`, `vulkan`, `opencl`, `rocm`, `sycl`, `openvino` per OS/arch -- the `CLASSIFIERS` -list in `publish.yml`, every `module` row of java-llama.cpp's `natives.csv` but the Android one; no -`msvc`, `sycl-fp*` or 32-bit jar exists since llama 6.0.0) and signs them via `.github/sign-fatjars.sh` -(srcmorph's own since java-llama.cpp dropped its fat jars with its modular natives). The convention + -per-repo shapes + the classifier keep-in-sync rule are documented -in [`../workspace/policies/fat-jar-release-assets.md`](../workspace/policies/fat-jar-release-assets.md). +## Release assets + +srcmorph releases **only the Maven artifacts**: the four POMs and the three modules' jar, sources +and javadoc, each with a detached GPG `.asc`, on Central and as GitHub release assets. +`.github/release-assets.json` is `{"schema": 1}`: no extra file rules. Up to 1.3.0 the publish jobs +also built, signed and attached one `srcmorph-cli` fat jar per desktop GPU module of +`net.ladenthin:llama` (15 per release, never on Central); 1.4.0 dropped them together with +`sign-fatjars.sh`, `verify-classifier-fatjars.sh` and the license collector that only they needed. Every publication goes through the one gate of [bernardladenthin/actions](https://github.com/bernardladenthin/actions) (full text: [docs/publication.md](https://github.com/bernardladenthin/actions/blob/main/docs/publication.md)). `check-snapshot` / `check-tag` run `java/check-release-ref`: a release publishes only from the tag `v` of a non-SNAPSHOT version, a snapshot only a `-SNAPSHOT` version and never from a tag. @@ -621,14 +627,13 @@ complete upload and starts as a draft; a versioned tag is refused), `central-sna (environment, permissions, concurrency); `java/check-publication` checks their wiring in every pull request. -The publish jobs stage deploy-signed Maven outputs with `--maven-only` before the classifier -loop rebuilds `target/`. The final collector uses `--maven-assets central-assets` and requires -all configured fat jars from `fatjar-assets/`. The loop's classifier list comes from file rule 1 -in `.github/release-assets.json` via `--list-classifiers 1` (default CPU is built last). +The publish jobs collect the deploy-signed Maven outputs with `--maven-only` into +`central-assets/`, and `finalize-publication` reads them from there (`--maven-assets`). -**srcmorph-specific smoke.** The cross-repo rule "no release asset is attached that CI has not run" -is implemented here by the `smoke-fatjar` job (`needs: [build]`, gates both publish jobs): it -downloads the `plugin-jars` artifact and runs the `java/smoke-fatjar-cli` action of [bernardladenthin/actions](https://github.com/bernardladenthin/actions) +**srcmorph-specific smoke.** The `smoke-jar` job (`needs: startgate`, gates both publish jobs) takes +the path a user takes: `mvn install` of the reactor, then `examples/build-your-own-jar` with +`srcmorph.version` and `llama.version` read from the reactor, so the example pom itself is under test +on every pull request. It then runs the `java/smoke-fatjar-cli` action of [bernardladenthin/actions](https://github.com/bernardladenthin/actions) (BAF runs it too) from `examples/` against `config_Plan.json`, asserting exit 0 plus `Main#run end.` in the output. `Plan` with the `mock` provider needs no GGUF, no GPU and no network, which makes this the cheapest possible real launch of the CLI. **Do not "strengthen" it to `config_All.json` over a real source @@ -676,15 +681,6 @@ in **`.github/actions-config.json`**: - **publication**: the publication jobs are wired as the actions expect, and a dry run of `.github/release-assets.json` against the real POMs collects exactly what it promises. -**Packaging srcmorph-cli needs bernardladenthin/actions.** Its `package` runs the license collector -(`python3 -m actionlib.java.thirdpartylicenses`): CI provides it through `common/setup` -(`ACTIONS_DIR`) and the reusable SonarQube workflow, locally a checkout next to this repository -(`../actions`) does; when neither is there, the collector call says so and where it looked, instead -of a Python traceback. - -**srcmorph's own build checks** stay here (`.github/buildcheck/`, the classifier fat-jar check): -`python3 -m unittest discover -s .github/buildcheck/tests -t .github`, run by `repository-checks`. - The JDK every workflow uses is `.java-version` (setup-java's `java-version-file`), a template of the `java` set. Why a repository instead of copies: [`../workspace/crossrepostatus.md`](../workspace/crossrepostatus.md). diff --git a/README.md b/README.md index 3ad1563a..aaafbf22 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ diagrams, or even source-to-source transformations. | Module | Artifact | What it is | |---|---|---| | [`srcmorph/`](srcmorph/README.md) | `net.ladenthin:srcmorph` | The core library: a framework-free Java API (no Maven dependency) — the shared `SrcMorphConfiguration` plus the `generate`/`aggregate-packages`/`aggregate-project`/`calibrate` engines. Use this to embed srcmorph in your own tooling. | -| [`srcmorph-cli/`](srcmorph-cli/README.md) | `net.ladenthin:srcmorph-cli` | A standalone CLI driven by one JSON or YAML configuration file (ships as a `java -jar`-ready fat jar). No Maven project required. | +| [`srcmorph-cli/`](srcmorph-cli/README.md) | `net.ladenthin:srcmorph-cli` | A standalone CLI driven by one JSON or YAML configuration file. You build the runnable jar for your machine with one `mvn package` ([`examples/build-your-own-jar/`](examples/build-your-own-jar/)). | | [`srcmorph-maven-plugin/`](srcmorph-maven-plugin/README.md) | `net.ladenthin:srcmorph-maven-plugin` | The Maven plugin, a thin wrapper around `srcmorph` — **renamed** from `net.ladenthin:llamacpp-ai-index-maven-plugin` (goal prefix `srcmorph`, was `ai-index`; properties `srcmorph.*`, were `aiIndex.*`). | All three modules (plus the reactor parent pom) are released together from this one repository at @@ -162,19 +162,29 @@ model recommendations: [`srcmorph-maven-plugin/README.md`](srcmorph-maven-plugin ### 2. Standalone CLI +srcmorph publishes no runnable jar: there are too many OS, CPU and GPU combinations to ship one per +machine. You build your own with Maven from +[`examples/build-your-own-jar/`](examples/build-your-own-jar/): its `pom.xml` takes `srcmorph-cli` +from Maven Central plus exactly the llama natives of your OS/arch (picked automatically), and +optionally one GPU module. + ```bash -java -jar srcmorph-cli--jar-with-dependencies.jar examples/config_All.json +cd examples/build-your-own-jar +./build.sh # or build.bat; add e.g. -Dgpu=vulkan-linux-x86-64 +java -jar target/srcmorph.jar ../config_All.json ``` Every `examples/config_*.json`/`.yaml` file works out of the box with the `mock` provider (no GGUF model required) — see [`examples/`](examples/) for the full set plus paired `run_*.sh`/`run_*.bat` launcher scripts, and [`srcmorph-cli/README.md`](srcmorph-cli/README.md) for the config-file -reference. - -**To build a fat jar with only your machine's natives** (for example the CPU and Vulkan classifiers -on Windows), see [`docs/windows-intel-core-ultra.md`](docs/windows-intel-core-ultra.md). It covers a -pom, `build.bat` and `run.bat` in [`examples/windows-fatjar/`](examples/windows-fatjar/), and a -commented configuration. +reference. A walkthrough for a Windows laptop with Vulkan is in +[`docs/windows-intel-core-ultra.md`](docs/windows-intel-core-ultra.md). + +Without building anything, [JBang](https://www.jbang.dev/) can run the CLI straight from Maven +Central. It resolves `srcmorph-cli` with the CPU natives of every desktop platform, and a GPU +module is one more `--deps` jar: +`jbang --deps net.ladenthin:llama:6.0.0:vulkan-linux-x86-64 net.ladenthin:srcmorph-cli: config.json`. +Maven is the supported path; JBang is a shortcut. ### 3. Library dependency diff --git a/REUSE.toml b/REUSE.toml index af0c9fb2..014fe01b 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -30,26 +30,6 @@ precedence = "aggregate" SPDX-FileCopyrightText = "HuggingFaceTB" SPDX-License-Identifier = "Apache-2.0" -# Upstream license texts of runtime dependencies whose JARs carry none, bundled into the CLI fat -# jar by the license collector of bernardladenthin/actions (--supplemental, third-party-notices/notices.json). -[[annotations]] -path = "third-party-notices/notices.json" -precedence = "aggregate" -SPDX-FileCopyrightText = "2026 Bernard Ladenthin " -SPDX-License-Identifier = "Apache-2.0" - -[[annotations]] -path = "third-party-notices/jspecify-1.0.1/**" -precedence = "override" -SPDX-FileCopyrightText = "2018-2020 The JSpecify Authors" -SPDX-License-Identifier = "Apache-2.0" - -[[annotations]] -path = "third-party-notices/snakeyaml-2.5/**" -precedence = "override" -SPDX-FileCopyrightText = "2008 SnakeYAML" -SPDX-License-Identifier = "Apache-2.0" - [[annotations]] path = "LICENSE" precedence = "aggregate" diff --git a/THIRD_PARTY.md b/THIRD_PARTY.md index 3c2928a6..13f5c84c 100644 --- a/THIRD_PARTY.md +++ b/THIRD_PARTY.md @@ -8,8 +8,10 @@ SPDX-License-Identifier: MIT OR Apache-2.0 The project is Apache-2.0. Logback in the concrete core dependency declaration is test-scoped; its parent dependencyManagement pin is not a runtime dependency. -The runnable CLI uses SLF4J Simple and bundles the selected llama runtime/native artifacts. -Those native artifacts have additional third-party contents requiring their own review. +srcmorph publishes no runnable jar. The jar a user builds from `examples/build-your-own-jar` +bundles SLF4J Simple and the llama natives they select; those native artifacts have additional +third-party contents requiring their own review, and redistributing such a jar is the builder's +responsibility. ## Review evidence @@ -22,10 +24,7 @@ For unchanged Maven components, source is available from as `--sources.jar`; POMs and upstream source repositories provide provenance. Preserve copyright, license and NOTICE files when redistributing dependencies. -Runnable BAF/srcmorph CLI packaging uses Python 3.8+ to collect original dependency -legal files before the project JAR and fat JAR are assembled. Override `-Dlicense.python=python` -if that is the Python command on the build host. Legal files are under -`META-INF/third-party/dependencies/-/`. -`inventory.json` maps exact JAR hashes/classifiers to preserved files and also lists JARs -without embedded legal material, which require review or supplemental notices. +Up to 1.3.0 srcmorph also released fat jars of the CLI, with the original dependency legal files +collected under `META-INF/third-party/dependencies/`. From 1.4.0 there are none, and that collection +step is gone with them. This source-location statement does not clear unknown proprietary or historical Git findings. diff --git a/TODO.md b/TODO.md index bccdd19a..4b911b8f 100644 --- a/TODO.md +++ b/TODO.md @@ -10,48 +10,16 @@ everything below is genuinely still open. ## Open -- **After the 1.3.0 release: build `examples/windows-fatjar` once against Central only**, with an - empty local repository. The release PR set it to `1.3.0` and removed the `central-snapshots` - block; the same check against the `1.3.0-SNAPSHOT` from Publish run - [40367427445](https://github.com/bernardladenthin/srcmorph/actions/runs/40367427445) passed - (2026-10-26, 32 MiB jar), so a failure now would point at the release upload, not the example. - -- **The GPU classifier fat jars are verified structurally, never launched.** Since 1.2.0 - `.github/verify-classifier-fatjars.sh` asserts each is the artifact its name claims (one jar per - classifier, the promised backend directory next to the default CPU natives, so a broken - `-Dllama.classifier=` cannot silently ship CPU-only builds under GPU names). What it cannot assert - is that the jar *works*: a GitHub-hosted runner has no CUDA/ROCm/SYCL/OpenVINO device, and the only - command that would load the native library is a real generation. Closing this needs hardware — - a self-hosted runner, or a manual pre-release pass on one GPU box per backend. Worth knowing which - half is covered before reading the green check as "the CUDA jar runs". - - **`cuda13-windows-x86-64` has had that pass (2026-10-23, against `llama 6.0.0-SNAPSHOT`).** On an - RTX 3070 Laptop the `-P gpu-cuda` fat jar (258 MB, carrying `Windows/x86_64/cuda13/` next to the six - CPU library directories) ran a real `GenerateFileIndex` over the production prompt set through the - CLI *and* through `srcmorph:generate`, both at 3474 t/s prefill / 86.8 t/s decode, with the loader - naming the module (`with GPU module(s) cuda13`) and ggml registering `[CUDA, RPC, CPU]`. The same - config on the default CPU fat jar is 63 / 7.1 with `[RPC, CPU]`, so the jar demonstrably loads its - backend rather than falling back. **Still unlaunched: `rocm`, `sycl`, `openvino` and `vulkan`** — - no AMD, Intel or second GPU here. Vulkan is reachable on any NVIDIA box and is the cheapest one to - add next. - -- **Drop the fat jars in 1.4.0 (owner's decision, 2026-10-26).** 1.3.0 is the last release with - fat jars on GitHub; they were never on Central (`false`, `--maven-only`, and no - `"central": true` in `.github/release-assets.json`), so removing them touches only the GitHub - release. Users build their own jar locally, as `examples/windows-fatjar` shows. Since its - modular natives java-llama.cpp ships no fat jar at all: the thin jar on Central is the deliverable, - a GPU is `jbang --deps net.ladenthin:llama:: …`, and its smoke jobs launch the - published jars on the classpath a consumer resolves. The same shape fits `srcmorph-cli` - (`jbang net.ladenthin:srcmorph-cli: config.json` resolves slf4j-simple and llama-platform from its - pom; a GPU is one more `--deps` jar). Removing them drops the default and 14 signed classifier fat - jars per release, the build loop in `publish.yml`, `sign-fatjars.sh`, `verify-classifier-fatjars.sh` - and `.github/release-assets.json`'s file rules. It also closes a gap the current shape has: the - artifact `smoke-fatjar` launches is the fat jar built by the `build` job (a GitHub asset, rebuilt by - the publish jobs' loop), while the `srcmorph-cli` jar that goes to Central is never launched by any - job. The smoke has to move to the thin jar plus its resolved runtime classpath (what - java-llama.cpp's smoke sets are), so it tests what is published. `README.md`, - `srcmorph-cli/README.md`, `CLAUDE.md` and the eight `examples/run_*` scripts that name the fat jar - go with it. +- **No GPU module is launched in CI.** `smoke-jar` builds `examples/build-your-own-jar` with the CPU + natives only and runs `Plan`; a GitHub-hosted runner has no CUDA/ROCm/SYCL/OpenVINO device, and + only a real generation loads the native library. Closing this needs hardware: a self-hosted runner, + or a manual pre-release pass on one GPU box per backend (`build.sh -Dgpu=`, then a + `GenerateFileIndex` with `gpuLayers` set). **`cuda13-windows-x86-64` has had that pass** (2026-10-23, + `llama 6.0.0-SNAPSHOT`, still as a fat jar): on an RTX 3070 Laptop a real `GenerateFileIndex` through + the CLI *and* through `srcmorph:generate` ran at 3474 t/s prefill / 86.8 t/s decode with ggml + registering `[CUDA, RPC, CPU]`, against 63 / 7.1 with `[RPC, CPU]` on the CPU jar. **Still + unlaunched: `rocm`, `sycl`, `openvino` and `vulkan`** — no AMD, Intel or second GPU here. Vulkan is + reachable on any NVIDIA box and is the cheapest one to add next. - **jqwik pin policy** — see [`../workspace/policies/jqwik-prompt-injection.md`](../workspace/policies/jqwik-prompt-injection.md). `jqwik.version ≤ 1.9.3` is mandatory (declared in `srcmorph/pom.xml`, the only reactor module with a jqwik test dependency). diff --git a/docs/RELEASE.md b/docs/RELEASE.md index b23c25bc..718b3f09 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -23,7 +23,10 @@ procedure: - `srcmorph-maven-plugin/README.md` — its own `` snippet; - `CLAUDE.md` — the reactor version and "Last released version" in the overview and layout blocks; - `SECURITY.md` — the supported-release-line table; - - `CHANGELOG.md` — the `## [X.Y.Z] - ` heading and the two link refs at the bottom. + - `CHANGELOG.md` — the `## [X.Y.Z] - ` heading and the two link refs at the bottom; + - `examples/build-your-own-jar/pom.xml` — `srcmorph.version` (and `llama.version` when the + reactor's moved). It names what a user resolves from Central, so it carries the release, never + the `-SNAPSHOT`; CI overrides both with the reactor's values in `smoke-jar`. `grep -rn "" --include="*.md" .` finds what was missed; run it before tagging, not after. diff --git a/docs/windows-intel-core-ultra.md b/docs/windows-intel-core-ultra.md index 3c23d4c7..0cda258b 100644 --- a/docs/windows-intel-core-ultra.md +++ b/docs/windows-intel-core-ultra.md @@ -4,51 +4,50 @@ SPDX-FileCopyrightText: 2026 Bernard Ladenthin SPDX-License-Identifier: MIT --> -# srcmorph on Windows: build your own fat jar, then run it +# srcmorph on Windows: build your own jar, then run it -Two steps, four files, all in [`examples/windows-fatjar/`](../examples/windows-fatjar/): +Two steps, all in [`examples/build-your-own-jar/`](../examples/build-your-own-jar/): | File | What it does | |---|---| -| `pom.xml` | names srcmorph and **the llama natives this machine needs** (the classifiers) | -| `build.bat` | one `mvn package` on a JDK 8 → `target\srcmorph.jar` | +| `pom.xml` | srcmorph from Maven Central plus **the llama natives this machine needs** | +| `build.bat` | one `mvn package` → `target\srcmorph.jar` | | `srcmorph.yaml` | what to index, with which prompt and which model | | `run.bat` | `java -jar target\srcmorph.jar srcmorph.yaml` | -Needed on the machine: a JDK 8 or newer, Maven, and a GGUF model. Nothing else: no C++ toolchain and -no GPU SDK. The Vulkan module uses the GPU driver that is already installed. +Needed on the machine: a JDK 8 or newer and Maven on `PATH`, and a GGUF model. Nothing else: no C++ +toolchain and no GPU SDK. The Vulkan module uses the GPU driver that is already installed. -Why build the jar yourself: the fat jar on the GitHub release carries the CPU natives of every -desktop platform (~111 MiB with Vulkan). Your own carries only the ones you name (32 MiB for the two -below), and you choose the GPU module. +Why build the jar yourself: srcmorph publishes no runnable jar, because no set of pre-built jars +covers every OS, CPU and GPU. Yours carries only the natives of this machine (32 MiB for the two +below) and the GPU module you choose. > [!NOTE] > This is for **srcmorph 1.3.0** (on `net.ladenthin:llama` 6.0.0); both resolve from Maven Central. -## Step 1 — pick the classifiers, build the jar +## Step 1 — pick the GPU module, build the jar -The pom declares one **library** jar for the OS/arch and, optionally, **GPU module** jars of the same -OS/arch. For a Windows x86-64 machine with an Intel Core Ultra (Arc iGPU), as shipped: +The pom takes one **library** jar for the OS/arch, picked by a Maven profile (`cpu-windows-x86-64` +here), and optionally one **GPU module** jar of the same OS/arch. For a Windows x86-64 machine with an +Intel Core Ultra (Arc iGPU): | Classifier | Size | Why | |---|--:|---| | `cpu-windows-x86-64` | 16.6 MiB | `jllama.dll`, llama/ggml and all CPU variants; ggml picks the best one at start (`alderlake` on a Core Ultra: AVX2 + AVX-VNNI, no AVX-512) | | `vulkan-windows-x86-64` | 12.0 MiB | the iGPU (or any GPU) through the driver's `vulkan-1.dll`; without a working driver ggml skips it and runs on the CPU | -Other machines swap the classifiers: for example `cuda13-windows-x86-64` for an NVIDIA GPU (it needs -the CUDA 13 runtime on `PATH`), or `cpu-linux-x86-64` on Linux. The full table is in the java-llama.cpp -README, [Choosing the natives +Other machines pick another GPU module: for example `cuda13-windows-x86-64` for an NVIDIA GPU (it +needs the CUDA 13 runtime on `PATH`). On Linux and macOS the same pom picks the library jar of that +OS. The full table is in the java-llama.cpp README, [Choosing the natives jars](https://github.com/bernardladenthin/java-llama.cpp#choosing-the-natives-jars). `openvino-*` is opt-in and can be slower than the CPU. Leave it out unless you want to experiment with the NPU. -Set `JAVA_HOME` in `build.bat` to your JDK, then: - ```bat -build.bat +build.bat -Dgpu=vulkan-windows-x86-64 ``` -The result is `target\srcmorph.jar`. +The result is `target\srcmorph.jar`. Without `-Dgpu=...` it holds the CPU natives only (20 MiB). ## Step 2 — the configuration, then run @@ -87,7 +86,7 @@ load_backends_next_to_this_library: 3 backend(s) registered [RPC, Vulkan, CPU], [jllama] native backend 'cpu' loaded from with GPU module(s) vulkan; 18 file(s) in place for ggml to load ``` -- **`with no GPU module`**: the Vulkan jar is not in the build. +- **`with no GPU module`**: the jar was built without `-Dgpu=vulkan-windows-x86-64`. - **Two Vulkan devices** (an iGPU and a discrete GPU): choose one with `devices: Vulkan1` in the `aiDefinition`. - **`gpuLayers: 0`**, or `-Dnet.ladenthin.llama.backend=cpu` before `-jar` in `run.bat`: runs on the @@ -103,7 +102,8 @@ load_backends_next_to_this_library: 3 backend(s) registered [RPC, Vulkan, CPU], ## Tested -On Windows, `cmd.exe`, srcmorph 1.3.0-SNAPSHOT: +On Windows, `cmd.exe`, srcmorph 1.3.0-SNAPSHOT, with the former `examples/windows-fatjar` (the same +CPU + Vulkan natives, fixed in the pom instead of chosen by profile and `-Dgpu`): - `build.bat` ran on a JDK 8 (1.8.0_504) and produced a 32 MiB jar holding only the Windows x86-64 natives. - `Plan` ran on the same JDK 8. @@ -111,3 +111,7 @@ On Windows, `cmd.exe`, srcmorph 1.3.0-SNAPSHOT: machine has no JDK 8 installed. - The test machine is a Ryzen with an AMD iGPU and an RTX 3070, which is why the log above was written down for a Core Ultra rather than copied from it. + +With `examples/build-your-own-jar` and srcmorph 1.3.0 from Maven Central (2026-10-26, JDK 21, empty +local repository): `mvn package` picked `cpu-windows-x86-64` and gave a 20 MiB jar, +`-Dgpu=vulkan-windows-x86-64` a 32 MiB jar, and `Plan` ran. diff --git a/examples/windows-fatjar/build.bat b/examples/build-your-own-jar/build.bat similarity index 60% rename from examples/windows-fatjar/build.bat rename to examples/build-your-own-jar/build.bat index 3645b7a8..da0e56ef 100644 --- a/examples/windows-fatjar/build.bat +++ b/examples/build-your-own-jar/build.bat @@ -3,9 +3,7 @@ REM REM SPDX-License-Identifier: MIT OR Apache-2.0 @echo off -rem Builds target\srcmorph.jar from pom.xml (the CLI plus the natives the pom names). Adapt JAVA_HOME. +rem Builds target\srcmorph.jar from pom.xml (the CLI plus the natives of this OS/arch). +rem Extra arguments go to Maven, e.g. build.bat -Dgpu=vulkan-windows-x86-64 cd /d "%~dp0" -set "JAVA_HOME=C:\Program Files\Java\openjdk1.8.0_491" -set "PATH=%JAVA_HOME%\bin;%PATH%" - -mvn -B package +mvn -B package %* diff --git a/examples/build-your-own-jar/build.sh b/examples/build-your-own-jar/build.sh new file mode 100755 index 00000000..3e364a3d --- /dev/null +++ b/examples/build-your-own-jar/build.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: 2026 Bernard Ladenthin +# +# SPDX-License-Identifier: MIT OR Apache-2.0 + +# Builds target/srcmorph.jar from pom.xml (the CLI plus the natives of this OS/arch). +# Extra arguments go to Maven, e.g. ./build.sh -Dgpu=vulkan-linux-x86-64 +set -euo pipefail +cd "$(dirname "$0")" +mvn -B package "$@" diff --git a/examples/build-your-own-jar/pom.xml b/examples/build-your-own-jar/pom.xml new file mode 100644 index 00000000..0817b62e --- /dev/null +++ b/examples/build-your-own-jar/pom.xml @@ -0,0 +1,160 @@ + + + + + 4.0.0 + local + srcmorph-local + 1 + + jar + + + 1.3.0 + 6.0.0 + UTF-8 + + + + + net.ladenthin + srcmorph-cli + ${srcmorph.version} + + + + net.ladenthin + llama-platform + + + + + + net.ladenthin + llama + ${llama.version} + ${natives} + + + + + + + ${project.basedir} + + simplelogger.properties + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.6.3 + + + require-natives + + enforce + + + + + natives + No natives jar for this OS/arch. Pass one: -Dnatives=cpu-linux-x86-64 (see the java-llama.cpp README, "Choosing the natives jars"). + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.8.0 + + + jar-with-dependencies + + srcmorph + false + false + + + net.ladenthin.srcmorph.cli.Main + + + + + + package + + single + + + + + + + + + + + linux-x86-64 + Linuxamd64 + cpu-linux-x86-64 + + + linux-aarch64 + Linuxaarch64 + cpu-linux-aarch64 + + + windows-x86-64 + windowsamd64 + cpu-windows-x86-64 + + + windows-aarch64 + windowsaarch64 + cpu-windows-aarch64 + + + macos-aarch64 + macaarch64 + metal-macos-aarch64 + + + + gpu + gpu + + + net.ladenthin + llama + ${llama.version} + ${gpu} + + + + + diff --git a/examples/build-your-own-jar/run.bat b/examples/build-your-own-jar/run.bat new file mode 100644 index 00000000..9bb357c9 --- /dev/null +++ b/examples/build-your-own-jar/run.bat @@ -0,0 +1,8 @@ +REM SPDX-FileCopyrightText: 2026 Bernard Ladenthin +REM +REM SPDX-License-Identifier: MIT OR Apache-2.0 + +@echo off +rem Runs srcmorph with srcmorph.yaml from this directory (or the configuration named as argument). +cd /d "%~dp0" +if "%~1"=="" (java -jar target\srcmorph.jar srcmorph.yaml) else (java -jar target\srcmorph.jar "%~1") diff --git a/examples/build-your-own-jar/run.sh b/examples/build-your-own-jar/run.sh new file mode 100755 index 00000000..af1c2eb5 --- /dev/null +++ b/examples/build-your-own-jar/run.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: 2026 Bernard Ladenthin +# +# SPDX-License-Identifier: MIT OR Apache-2.0 + +# Runs srcmorph with srcmorph.yaml from this directory (or the configuration named as argument). +set -euo pipefail +cd "$(dirname "$0")" +java -jar target/srcmorph.jar "${1:-srcmorph.yaml}" diff --git a/srcmorph-cli/src/main/assembly-resources/simplelogger.properties b/examples/build-your-own-jar/simplelogger.properties similarity index 55% rename from srcmorph-cli/src/main/assembly-resources/simplelogger.properties rename to examples/build-your-own-jar/simplelogger.properties index f475e1ce..5430606a 100644 --- a/srcmorph-cli/src/main/assembly-resources/simplelogger.properties +++ b/examples/build-your-own-jar/simplelogger.properties @@ -2,13 +2,12 @@ # # SPDX-License-Identifier: Apache-2.0 -# slf4j-simple defaults for the RUNNABLE FAT JAR only. +# slf4j-simple defaults baked into target/srcmorph.jar (pom.xml packs this file into the jar). # -# This file is deliberately NOT under src/main/resources: that would put it into the -# published srcmorph-cli jar, where anyone embedding the CLI would find our logging -# configuration on their classpath. slf4j-simple reads whichever simplelogger.properties -# the classloader hands it first, so a consumer with their own file would get a coin flip. -# A library must not decide that; an application may, and the fat jar is the application. +# The published srcmorph-cli jar deliberately carries no such file: anyone embedding the CLI +# would find our logging configuration on their classpath, and slf4j-simple reads whichever +# simplelogger.properties the classloader hands it first. A library must not decide that; the +# jar you build here is the application, so it may. # # Every setting here is also overridable at launch with -Dorg.slf4j.simpleLogger.. @@ -16,8 +15,7 @@ org.slf4j.simpleLogger.defaultLogLevel=info # stdout, not the slf4j-simple default of stderr: the CLI's own output belongs on the -# same stream as everything else a user pipes or redirects. The fat-jar smoke test greps -# for "Main#run end." on stdout, so this is load-bearing, not cosmetic. +# same stream as everything else a user pipes or redirects. org.slf4j.simpleLogger.logFile=System.out # Wall-clock timestamps -- a run log without them cannot be correlated with anything. diff --git a/examples/windows-fatjar/srcmorph.yaml b/examples/build-your-own-jar/srcmorph.yaml similarity index 81% rename from examples/windows-fatjar/srcmorph.yaml rename to examples/build-your-own-jar/srcmorph.yaml index fef557dc..adf4c6be 100644 --- a/examples/windows-fatjar/srcmorph.yaml +++ b/examples/build-your-own-jar/srcmorph.yaml @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT OR Apache-2.0 -# What run.bat does with it: every file under baseDirectory/subtrees with one of the fileExtensions +# What run.sh / run.bat do with it: every file under baseDirectory/subtrees with one of the fileExtensions # is matched to a rule in fieldGenerations. The rule names a prompt (promptDefinitions) and a model # (aiDefinitions). srcmorph sends the prompt as the system message and the file's name and source as # the user message, and writes the model's answer to outputDirectory as .ai.md. @@ -15,16 +15,17 @@ command: Plan srcMorph: - # Use forward slashes in Windows paths here: in a double-quoted YAML string, "C:\x" is an escape. - baseDirectory: "C:/path/to/project" - outputDirectory: "C:/path/to/project/target/srcmorph-ai" + # On Windows use forward slashes ("C:/path/to/project"): in a double-quoted YAML string, "C:\x" is + # an escape. + baseDirectory: "/path/to/project" + outputDirectory: "/path/to/project/target/srcmorph-ai" subtrees: - "src/main/java" fileExtensions: - ".java" generationProvider: llamacpp-jni # "mock" runs without a model, for a dry test of the setup - modelDirectory: "C:/models" # a relative modelPath below is relative to this directory + modelDirectory: "/path/to/models" # a relative modelPath below is relative to this directory promptDefinitions: - key: java @@ -37,7 +38,7 @@ srcMorph: maxOutputTokens: 1024 temperature: 0.15 threads: 4 # the default; more only after measuring, never more than cores - gpuLayers: 99 # 99 = everything on the GPU (Vulkan), 0 = CPU only + gpuLayers: 99 # 99 = everything on the GPU module in the jar, 0 = CPU only fieldGenerations: - promptKey: java diff --git a/examples/run_all.bat b/examples/run_all.bat index b56dd14a..4eabe9f9 100644 --- a/examples/run_all.bat +++ b/examples/run_all.bat @@ -7,16 +7,14 @@ rem Runs the srcmorph CLI against config_All.json: all three phases in order (Ge rem AggregatePackages, AggregateProject). Uses the mock provider, so no GGUF model is required - rem point generationProvider at "llamacpp-jni" and set a real modelPath to run a model. rem -rem The fat jar's file name is version-qualified (e.g. -rem srcmorph-cli-1.2.0-SNAPSHOT-jar-with-dependencies.jar) and changes on every version bump, so -rem the loop below picks whichever one was last built by "mvn package" in ..\srcmorph-cli. +rem Runs the jar of build-your-own-jar\ (srcmorph-cli plus the natives of this machine); build it +rem once with build-your-own-jar\build.bat. setlocal cd /d "%~dp0" -set "JARFILE=" -for %%f in (..\srcmorph-cli\target\srcmorph-cli-*-jar-with-dependencies.jar) do set "JARFILE=%%f" -if "%JARFILE%"=="" ( - echo srcmorph-cli fat jar not found under ..\srcmorph-cli\target\ - run "mvn package" first. 1>&2 +set "JARFILE=build-your-own-jar\target\srcmorph.jar" +if not exist "%JARFILE%" ( + echo %JARFILE% not found - run build-your-own-jar\build.bat first. 1>&2 exit /b 1 ) diff --git a/examples/run_all.sh b/examples/run_all.sh index 67f6a36e..b3607ace 100755 --- a/examples/run_all.sh +++ b/examples/run_all.sh @@ -7,15 +7,14 @@ # AggregatePackages, AggregateProject). Uses the mock provider, so no GGUF model is required - # point generationProvider at "llamacpp-jni" and set a real modelPath to run a model. # -# The fat jar's file name is version-qualified (e.g. -# srcmorph-cli-1.2.0-SNAPSHOT-jar-with-dependencies.jar) and changes on every version bump, so the -# glob below picks whichever one was last built by `mvn package` in ../srcmorph-cli. +# Runs the jar of build-your-own-jar/ (srcmorph-cli plus the natives of this machine); build it +# once with build-your-own-jar/build.sh. set -euo pipefail cd "$(dirname "$0")" -JAR=$(ls ../srcmorph-cli/target/srcmorph-cli-*-jar-with-dependencies.jar 2>/dev/null | head -n 1) -if [ -z "$JAR" ]; then - echo "srcmorph-cli fat jar not found under ../srcmorph-cli/target/ - run 'mvn package' first." >&2 +JAR=build-your-own-jar/target/srcmorph.jar +if [ ! -f "$JAR" ]; then + echo "$JAR not found - run build-your-own-jar/build.sh first." >&2 exit 1 fi diff --git a/examples/run_calibrate.bat b/examples/run_calibrate.bat index 944172ad..6872ab24 100644 --- a/examples/run_calibrate.bat +++ b/examples/run_calibrate.bat @@ -8,16 +8,14 @@ rem and prints a paste-ready block per model. With the mock provid rem this is a no-op smoke check; swap generationProvider to "llamacpp-jni" and set a real rem modelPath to calibrate an actual GGUF model on this machine. rem -rem The fat jar's file name is version-qualified (e.g. -rem srcmorph-cli-1.2.0-SNAPSHOT-jar-with-dependencies.jar) and changes on every version bump, so -rem the loop below picks whichever one was last built by "mvn package" in ..\srcmorph-cli. +rem Runs the jar of build-your-own-jar\ (srcmorph-cli plus the natives of this machine); build it +rem once with build-your-own-jar\build.bat. setlocal cd /d "%~dp0" -set "JARFILE=" -for %%f in (..\srcmorph-cli\target\srcmorph-cli-*-jar-with-dependencies.jar) do set "JARFILE=%%f" -if "%JARFILE%"=="" ( - echo srcmorph-cli fat jar not found under ..\srcmorph-cli\target\ - run "mvn package" first. 1>&2 +set "JARFILE=build-your-own-jar\target\srcmorph.jar" +if not exist "%JARFILE%" ( + echo %JARFILE% not found - run build-your-own-jar\build.bat first. 1>&2 exit /b 1 ) diff --git a/examples/run_calibrate.sh b/examples/run_calibrate.sh index 789fadd9..bb606658 100755 --- a/examples/run_calibrate.sh +++ b/examples/run_calibrate.sh @@ -8,15 +8,14 @@ # a no-op smoke check; swap generationProvider to "llamacpp-jni" and set a real modelPath to # calibrate an actual GGUF model on this machine. # -# The fat jar's file name is version-qualified (e.g. -# srcmorph-cli-1.2.0-SNAPSHOT-jar-with-dependencies.jar) and changes on every version bump, so the -# glob below picks whichever one was last built by `mvn package` in ../srcmorph-cli. +# Runs the jar of build-your-own-jar/ (srcmorph-cli plus the natives of this machine); build it +# once with build-your-own-jar/build.sh. set -euo pipefail cd "$(dirname "$0")" -JAR=$(ls ../srcmorph-cli/target/srcmorph-cli-*-jar-with-dependencies.jar 2>/dev/null | head -n 1) -if [ -z "$JAR" ]; then - echo "srcmorph-cli fat jar not found under ../srcmorph-cli/target/ - run 'mvn package' first." >&2 +JAR=build-your-own-jar/target/srcmorph.jar +if [ ! -f "$JAR" ]; then + echo "$JAR not found - run build-your-own-jar/build.sh first." >&2 exit 1 fi diff --git a/examples/run_generate.bat b/examples/run_generate.bat index d76153f7..4aed6970 100644 --- a/examples/run_generate.bat +++ b/examples/run_generate.bat @@ -7,16 +7,14 @@ rem Runs the srcmorph CLI against config_GenerateFileIndex.json (Phase 1 only: i rem and fill in their AI-generated summary bodies). Uses the mock provider, so no GGUF model is rem required - point generationProvider at "llamacpp-jni" and set a real modelPath to run a model. rem -rem The fat jar's file name is version-qualified (e.g. -rem srcmorph-cli-1.2.0-SNAPSHOT-jar-with-dependencies.jar) and changes on every version bump, so -rem the loop below picks whichever one was last built by "mvn package" in ..\srcmorph-cli. +rem Runs the jar of build-your-own-jar\ (srcmorph-cli plus the natives of this machine); build it +rem once with build-your-own-jar\build.bat. setlocal cd /d "%~dp0" -set "JARFILE=" -for %%f in (..\srcmorph-cli\target\srcmorph-cli-*-jar-with-dependencies.jar) do set "JARFILE=%%f" -if "%JARFILE%"=="" ( - echo srcmorph-cli fat jar not found under ..\srcmorph-cli\target\ - run "mvn package" first. 1>&2 +set "JARFILE=build-your-own-jar\target\srcmorph.jar" +if not exist "%JARFILE%" ( + echo %JARFILE% not found - run build-your-own-jar\build.bat first. 1>&2 exit /b 1 ) diff --git a/examples/run_generate.sh b/examples/run_generate.sh index 97410bd6..6884f10b 100755 --- a/examples/run_generate.sh +++ b/examples/run_generate.sh @@ -7,15 +7,14 @@ # and fill in their AI-generated summary bodies). Uses the mock provider, so no GGUF model is # required - point generationProvider at "llamacpp-jni" and set a real modelPath to run a model. # -# The fat jar's file name is version-qualified (e.g. -# srcmorph-cli-1.2.0-SNAPSHOT-jar-with-dependencies.jar) and changes on every version bump, so the -# glob below picks whichever one was last built by `mvn package` in ../srcmorph-cli. +# Runs the jar of build-your-own-jar/ (srcmorph-cli plus the natives of this machine); build it +# once with build-your-own-jar/build.sh. set -euo pipefail cd "$(dirname "$0")" -JAR=$(ls ../srcmorph-cli/target/srcmorph-cli-*-jar-with-dependencies.jar 2>/dev/null | head -n 1) -if [ -z "$JAR" ]; then - echo "srcmorph-cli fat jar not found under ../srcmorph-cli/target/ - run 'mvn package' first." >&2 +JAR=build-your-own-jar/target/srcmorph.jar +if [ ! -f "$JAR" ]; then + echo "$JAR not found - run build-your-own-jar/build.sh first." >&2 exit 1 fi diff --git a/examples/run_plan.bat b/examples/run_plan.bat index 15883aea..338b04c1 100644 --- a/examples/run_plan.bat +++ b/examples/run_plan.bat @@ -7,16 +7,14 @@ rem Runs the srcmorph CLI against config_Plan.json: builds and logs the routing rem GenerateFileIndex phase with planOnly forced true by the Plan command) - no model is loaded rem and nothing is written. Safe to run with no GGUF model on disk (generationProvider is "mock"). rem -rem The fat jar's file name is version-qualified (e.g. -rem srcmorph-cli-1.2.0-SNAPSHOT-jar-with-dependencies.jar) and changes on every version bump, so -rem the loop below picks whichever one was last built by "mvn package" in ..\srcmorph-cli. +rem Runs the jar of build-your-own-jar\ (srcmorph-cli plus the natives of this machine); build it +rem once with build-your-own-jar\build.bat. setlocal cd /d "%~dp0" -set "JARFILE=" -for %%f in (..\srcmorph-cli\target\srcmorph-cli-*-jar-with-dependencies.jar) do set "JARFILE=%%f" -if "%JARFILE%"=="" ( - echo srcmorph-cli fat jar not found under ..\srcmorph-cli\target\ - run "mvn package" first. 1>&2 +set "JARFILE=build-your-own-jar\target\srcmorph.jar" +if not exist "%JARFILE%" ( + echo %JARFILE% not found - run build-your-own-jar\build.bat first. 1>&2 exit /b 1 ) diff --git a/examples/run_plan.sh b/examples/run_plan.sh index f51e2734..31e13107 100755 --- a/examples/run_plan.sh +++ b/examples/run_plan.sh @@ -7,15 +7,14 @@ # GenerateFileIndex phase with planOnly forced true by the Plan command) - no model is loaded and # nothing is written. Safe to run with no GGUF model on disk (generationProvider is "mock"). # -# The fat jar's file name is version-qualified (e.g. -# srcmorph-cli-1.2.0-SNAPSHOT-jar-with-dependencies.jar) and changes on every version bump, so the -# glob below picks whichever one was last built by `mvn package` in ../srcmorph-cli. +# Runs the jar of build-your-own-jar/ (srcmorph-cli plus the natives of this machine); build it +# once with build-your-own-jar/build.sh. set -euo pipefail cd "$(dirname "$0")" -JAR=$(ls ../srcmorph-cli/target/srcmorph-cli-*-jar-with-dependencies.jar 2>/dev/null | head -n 1) -if [ -z "$JAR" ]; then - echo "srcmorph-cli fat jar not found under ../srcmorph-cli/target/ - run 'mvn package' first." >&2 +JAR=build-your-own-jar/target/srcmorph.jar +if [ ! -f "$JAR" ]; then + echo "$JAR not found - run build-your-own-jar/build.sh first." >&2 exit 1 fi diff --git a/examples/simplelogger.properties b/examples/simplelogger.properties index 0f47105d..3dcf28b7 100644 --- a/examples/simplelogger.properties +++ b/examples/simplelogger.properties @@ -2,21 +2,20 @@ # # SPDX-License-Identifier: Apache-2.0 -# Example slf4j-simple configuration for srcmorph-cli, replacing the former -# logbackConfiguration.xml (the fat jar ships slf4j-simple now, because logback 1.4.0+ is -# Java 11 bytecode while this project targets Java 8 -- see the pin in the reactor pom). +# Example slf4j-simple configuration for srcmorph-cli (slf4j-simple, not logback, because +# logback 1.4.0+ is Java 11 bytecode while this project targets Java 8 -- see the pin in the +# reactor pom). # -# Two ways to use it: +# Two ways to use it with the jar of build-your-own-jar/: # * put a copy named simplelogger.properties FIRST on the classpath: -# java -cp examples:srcmorph-cli--jar-with-dependencies.jar \ +# java -cp examples:examples/build-your-own-jar/target/srcmorph.jar \ # net.ladenthin.srcmorph.cli.Main examples/config_Plan.json # * or set the individual keys as system properties, which always win: # java -Dorg.slf4j.simpleLogger.defaultLogLevel=debug \ -# -jar srcmorph-cli--jar-with-dependencies.jar examples/config_Plan.json +# -jar examples/build-your-own-jar/target/srcmorph.jar examples/config_Plan.json # -# The fat jar carries these same defaults internally -# (srcmorph-cli/src/main/assembly-resources/simplelogger.properties), so this file is only -# needed to change them. +# That jar carries defaults of its own (build-your-own-jar/simplelogger.properties), so this +# file is only needed to change them. # debug shows the per-file indexer decisions; the shipped default is info. org.slf4j.simpleLogger.defaultLogLevel=info diff --git a/examples/windows-fatjar/pom.xml b/examples/windows-fatjar/pom.xml deleted file mode 100644 index e7e09674..00000000 --- a/examples/windows-fatjar/pom.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - - - 4.0.0 - local - srcmorph-local - 1 - pom - - - 1.3.0 - 6.0.0 - UTF-8 - - - - - net.ladenthin - srcmorph-cli - ${srcmorph.version} - - - - net.ladenthin - llama-platform - - - - - - net.ladenthin - llama - ${llama.version} - cpu-windows-x86-64 - - - - net.ladenthin - llama - ${llama.version} - vulkan-windows-x86-64 - - - - - - - org.apache.maven.plugins - maven-assembly-plugin - 3.7.1 - - - jar-with-dependencies - - srcmorph - false - - - net.ladenthin.srcmorph.cli.Main - - - - - - package - - single - - - - - - - diff --git a/examples/windows-fatjar/run.bat b/examples/windows-fatjar/run.bat deleted file mode 100644 index c926df0d..00000000 --- a/examples/windows-fatjar/run.bat +++ /dev/null @@ -1,11 +0,0 @@ -REM SPDX-FileCopyrightText: 2026 Bernard Ladenthin -REM -REM SPDX-License-Identifier: MIT OR Apache-2.0 - -@echo off -rem Runs srcmorph with srcmorph.yaml from this directory. Adapt JAVA_HOME. -cd /d "%~dp0" -set "JAVA_HOME=C:\Program Files\Java\openjdk1.8.0_491" -set "PATH=%JAVA_HOME%\bin;%PATH%" - -java -jar target\srcmorph.jar srcmorph.yaml diff --git a/pom.xml b/pom.xml index 9087e74a..b68724b6 100644 --- a/pom.xml +++ b/pom.xml @@ -62,10 +62,6 @@ SPDX-License-Identifier: Apache-2.0 - - ${maven.multiModuleProjectDirectory}/../actions @@ -193,7 +189,7 @@ SPDX-License-Identifier: Apache-2.0 COMPILE-ONLY, and that is the whole point. checker-qual is class-file major 55 from 4.0.0 on, its annotations are @Retention(RUNTIME), and anything that reflects over an annotated element (Jackson does, and srcmorph-cli binds its whole configuration with - Jackson) loads them — so a copy inside the fat jar is an UnsupportedClassVersionError + Jackson) loads them — so a copy on a consumer's runtime classpath is an UnsupportedClassVersionError waiting for a Java 8 consumer. Pinning the SHIPPED copy to the last Java 8 line (3.55.1) does not work and was @@ -205,7 +201,7 @@ SPDX-License-Identifier: Apache-2.0 Scope `provided` satisfies both constraints at once: 4.3.0 on the compile classpath where the checker needs it, and excluded from consumers' transitive graph AND from - the fat jar (jar-with-dependencies takes scope runtime), so no checker-qual class of + any jar-with-dependencies built from it (that descriptor takes scope runtime), so no checker-qual class of any version reaches a consumer's JVM. true alone would not have been enough — that descriptor filters on scope only. Safe because no source in this reactor imports org.checkerframework. @@ -264,19 +260,6 @@ SPDX-License-Identifier: Apache-2.0 - - - actions-dir-from-environment - - - env.ACTIONS_DIR - - - - ${env.ACTIONS_DIR} - - org.checkerframework @@ -122,8 +121,9 @@ SPDX-License-Identifier: Apache-2.0 slf4j-simple, not logback: logback 1.4.0+ is Java 11 bytecode and the production code here targets Java 8 — see the rationale on the pin in the parent's dependencyManagement. - Defaults ship in src/main/assembly-resources/simplelogger.properties (fat jar only); - override at runtime with -Dorg.slf4j.simpleLogger.* or a classpath simplelogger.properties. + This jar ships no simplelogger.properties (it would override an embedder's own); the + runnable jar of examples/build-your-own-jar carries the defaults. Override at runtime with + -Dorg.slf4j.simpleLogger.* or a classpath simplelogger.properties. --> org.slf4j @@ -198,11 +198,6 @@ SPDX-License-Identifier: Apache-2.0 git-commit-id-maven-plugin 10.0.1 - - org.apache.maven.plugins - maven-assembly-plugin - 3.8.0 - org.apache.maven.plugins maven-compiler-plugin @@ -289,64 +284,6 @@ SPDX-License-Identifier: Apache-2.0 - - org.apache.maven.plugins - maven-dependency-plugin - - - runtime-license-classpath - process-classes - build-classpath - - runtime - jar - true - ${project.build.directory}/runtime-license-classpath.txt - - - - - - org.codehaus.mojo - exec-maven-plugin - 3.6.4 - - - collect-runtime-licenses - prepare-package - exec - - ${license.python} - - ${ci.actions.dir}/lib - - - - -c - import importlib.util, sys; importlib.util.find_spec('actionlib') or sys.exit('The license collector comes from bernardladenthin/actions: set ACTIONS_DIR to a checkout of it, or check it out next to this repository (../actions). Looked in ${ci.actions.dir}/lib'); from actionlib.java.thirdpartylicenses import main; sys.exit(main(sys.argv[1:])) - --classpath - ${project.build.directory}/runtime-license-classpath.txt - --output - ${project.build.directory}/third-party-dependencies - - --supplemental - ${project.basedir}/../third-party-notices - --require-complete - - - - - io.github.git-commit-id git-commit-id-maven-plugin @@ -572,56 +509,6 @@ SPDX-License-Identifier: Apache-2.0 - - - org.apache.maven.plugins - maven-assembly-plugin - - - - src/assembly/fat-jar.xml - - - - net.ladenthin.srcmorph.cli.Main - - - false - - - - build-fat-jar - package - - single - - - - - org.jacoco jacoco-maven-plugin @@ -721,10 +608,10 @@ SPDX-License-Identifier: Apache-2.0 diff --git a/srcmorph-cli/src/assembly/fat-jar.xml b/srcmorph-cli/src/assembly/fat-jar.xml deleted file mode 100644 index 36e232da..00000000 --- a/srcmorph-cli/src/assembly/fat-jar.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - jar-with-dependencies - - jar - - false - - - / - true - true - runtime - - - - - ${project.basedir}/src/main/assembly-resources - / - - simplelogger.properties - - - - - ${project.build.directory}/third-party-dependencies - META-INF/third-party/dependencies - - - diff --git a/srcmorph-cli/src/main/java/module-info.java b/srcmorph-cli/src/main/java/module-info.java index 03f4222b..eab3b25c 100644 --- a/srcmorph-cli/src/main/java/module-info.java +++ b/srcmorph-cli/src/main/java/module-info.java @@ -29,9 +29,9 @@ *

{@code requires static org.jspecify} is needed only at compile time of this * descriptor; JSpecify annotations carry {@code RetentionPolicy.CLASS} so module-path * consumers never need jspecify on their runtime path. Checker Framework qualifiers are - * likewise compile-time only. The fat jar produced by {@code maven-assembly-plugin} is a - * classpath-only artifact and flattens multiple {@code module-info.class} entries harmlessly, - * same as any other jar-with-dependencies build.

+ * likewise compile-time only. The jar a user builds with {@code examples/build-your-own-jar} + * ({@code maven-assembly-plugin}) is a classpath-only artifact and flattens multiple + * {@code module-info.class} entries harmlessly, same as any other jar-with-dependencies build.

* *

This descriptor is NOT purely decorative — unlike the core/plugin modules' * own {@code module-info.java} (which only need to satisfy the isolated, release-9 diff --git a/srcmorph-cli/src/test/java/net/ladenthin/srcmorph/cli/ExamplesConfigBindingTest.java b/srcmorph-cli/src/test/java/net/ladenthin/srcmorph/cli/ExamplesConfigBindingTest.java index 352bfeb5..adabd170 100644 --- a/srcmorph-cli/src/test/java/net/ladenthin/srcmorph/cli/ExamplesConfigBindingTest.java +++ b/srcmorph-cli/src/test/java/net/ladenthin/srcmorph/cli/ExamplesConfigBindingTest.java @@ -83,7 +83,7 @@ private static List listExampleConfigFiles() throws IOException { } /** - * {@code examples/windows-fatjar/} is the build-it-yourself example (docs/windows-intel-core-ultra.md): + * {@code examples/build-your-own-jar/} is how a user builds the runnable jar (docs/windows-intel-core-ultra.md): * its {@code srcmorph.yaml} is meant for a real model, so it is not one of the mock fixtures above, * but it must still bind under the strict mapper, start with the safe {@code Plan}, and route every * file through a fallback rule that handles an oversized file. Its {@code pom.xml} names the CLI's @@ -92,8 +92,8 @@ private static List listExampleConfigFiles() throws IOException { * @throws IOException if an example file cannot be read */ @Test - public void windowsFatJarExample_bindsAndNamesTheMainClass() throws IOException { - final Path directory = EXAMPLES_DIRECTORY.resolve("windows-fatjar"); + public void buildYourOwnJarExample_bindsAndNamesTheMainClass() throws IOException { + final Path directory = EXAMPLES_DIRECTORY.resolve("build-your-own-jar"); final CConfiguration configuration = Main.fromYaml(Main.readString(directory.resolve("srcmorph.yaml"))); assertThat(configuration.command, is(CCommand.Plan)); diff --git a/srcmorph-maven-plugin/README.md b/srcmorph-maven-plugin/README.md index 652523f4..0d482df0 100644 --- a/srcmorph-maven-plugin/README.md +++ b/srcmorph-maven-plugin/README.md @@ -606,7 +606,7 @@ In both cases: - **CUDA** needs a matching CUDA 13 toolkit + driver, and the toolkit's `bin\x64` (with `cudart64_13.dll`, `cublas64_13.dll`, `cublasLt64_13.dll`) on `PATH` — the natives jar bundles only the backend module `ggml-cuda.dll`, not the CUDA runtime. -- **Vulkan** needs only the GPU driver (it brings `vulkan-1.dll`). A CLI fat jar built with exactly +- **Vulkan** needs only the GPU driver (it brings `vulkan-1.dll`). A CLI jar built with exactly the CPU and Vulkan natives of a Windows machine is in [docs/windows-intel-core-ultra.md](../docs/windows-intel-core-ultra.md). - **GPU layer offload** — set `` inside your `` (see "Per-model `` parameters" above): `-1` (default) does **not** pin a layer count, so llama.cpp diff --git a/srcmorph-maven-plugin/pom.xml b/srcmorph-maven-plugin/pom.xml index 970cbf1f..9eb2e657 100644 --- a/srcmorph-maven-plugin/pom.xml +++ b/srcmorph-maven-plugin/pom.xml @@ -147,7 +147,7 @@ SPDX-License-Identifier: Apache-2.0 org.checkerframework diff --git a/srcmorph/pom.xml b/srcmorph/pom.xml index 9e9c3b3c..57c821b2 100644 --- a/srcmorph/pom.xml +++ b/srcmorph/pom.xml @@ -111,7 +111,7 @@ SPDX-License-Identifier: Apache-2.0 org.checkerframework @@ -816,8 +816,7 @@ SPDX-License-Identifier: Apache-2.0 run pays a one-time shader-compilation cost. Example: mvn srcmorph:generate -P gpu-cuda -Dai.gpuLayers=20 (run from the plugin module, which pulls this module's GPU natives jar transitively). - Any other natives jar: -Dllama.classifier= (gpu-natives), which is also how CI - builds one srcmorph-cli fat jar per GPU backend. + Any other natives jar: -Dllama.classifier= (gpu-natives). (OpenCL is intentionally not offered: llama.cpp's OpenCL backend does not support NVIDIA.) --> gpu-cuda diff --git a/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/EngineSupport.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/EngineSupport.java index 8af1be95..76c03e0b 100644 --- a/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/EngineSupport.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/EngineSupport.java @@ -168,8 +168,8 @@ static LlamaCppJniConfig resolveLlamaCppJniConfig( *

Gated on the provider on purpose. Only {@code llamacpp-jni} loads a GGUF; * the {@code mock} provider ignores {@code modelPath} entirely, and every shipped example config * sets a deliberately non-existent {@code "unused-with-mock-provider.gguf"}. An ungated check - * would red those examples, their binding tests, the CLI end-to-end test and the fat-jar release - * smoke. + * would red those examples, their binding tests, the CLI end-to-end test and the jar smoke + * ({@code smoke-jar}) that gates every release. * * @param generationProvider the configured provider key * @param modelDefinitionSupport model lookup built from the configured {@code } diff --git a/third-party-notices/jspecify-1.0.1/LICENSE b/third-party-notices/jspecify-1.0.1/LICENSE deleted file mode 100644 index d6456956..00000000 --- a/third-party-notices/jspecify-1.0.1/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/third-party-notices/notices.json b/third-party-notices/notices.json deleted file mode 100644 index 86704dde..00000000 --- a/third-party-notices/notices.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "format": 1, - "notices": [ - { - "directory": "jspecify-1.0.1", - "license": "Apache-2.0", - "artifacts": [ - "jspecify" - ] - }, - { - "directory": "snakeyaml-2.5", - "license": "Apache-2.0", - "artifacts": [ - "snakeyaml" - ] - } - ] -} diff --git a/third-party-notices/snakeyaml-2.5/LICENSE.txt b/third-party-notices/snakeyaml-2.5/LICENSE.txt deleted file mode 100644 index d9a10c0d..00000000 --- a/third-party-notices/snakeyaml-2.5/LICENSE.txt +++ /dev/null @@ -1,176 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS